文章目录
- 1.java重载和重写以及隐藏
- 2.构造方法
- 3.访问修饰符
- 4.接口,抽象类以及它们的区别
- ⑴抽象类
- ⑵接口
- ⑶联系
- ⑷区别
- 5.关键字和保留字
- ⑴Java 关键字列表 (依字母排序 共50组):
- ⑵保留字列表 (依字母排序 共14组),Java保留字是指现有Java版本尚未使用,但以后版本可能会作为关键字使用:
- 6.析构函数
- 7.JAVA标识符
- 8.JAVA中lang sql util包
- 9.super和this关键字
- 10.JDK中的包和他们的基本功能
- 11.object类的基本方法
- 12.final关键字
- 13.J2EE常用名词
- 14.加载驱动的三种方式
- 15.java编译后缀
- 16.静态方法(类方法),静态变量,静态代码块
- 17.java表达式转型规则
- 18.基本数据类型和引用数据类型
- 19.JDK JRE JVM的区别
- 20.java变量初始值问题
- 21.java集合概览
- 21.过滤器 监听器 拦截器
- 22.深克隆和浅克隆
- 23.JAVA四种引用
- 24.JAVA序列化
- 25.拆装箱
- 26.数据类型及类型转化
- ⑴基本数据类型及对应的包装类
- ⑵自动类型转换,强制类型转换以及类型提升
- 27.servlet
- ⑴概述
- ⑵生命周期
- ⑶GenericServlet和HttpServlet
- ⑷Servlet和jsp的区别和联系
- 28.异常
- ⑴概述
- ⑵异常处理原则
- ⑶注意事项
- ①上级可以捕获没有显示向上抛的运行异常
- ②try里面出现没有catch的异常也会执行finally
- 29.多态
- ⑴多态的作用
- ⑵多态的条件
- ⑶多态的注意事项
- ⑷基于JVM解释多态
- 30.==和equals hashCode
- ⑴==和equals
- ⑵equals和hashCode
- ⑵例题
- 31.数组
- ⑴概述
- ⑵初始化数组
- ⑶数组四种拷贝方式
- 32.Comparable与Comparator的区别
- ⑴Comparable内部比较器
- ⑵Comparator外部比较器
- 33.值传递和引用传递
- ⑴概述
- ⑵分类
- ⑶引用传递其实传递的是引用的一个副本
- 34.类的生命周期以及双亲委托机制
- ⑴加载
- ⑵验证
- ⑶准备
- ⑷解析
- ⑸初始化
- ⑹使用
- ⑺卸载
- 35.动态绑定和静态绑定
- ⑴什么叫绑定
- ⑵静态绑定
- ⑶动态绑定
- 36.hashmap
- ⑴hashmap基本结构
- ①构造函数
- ②基本结构
- ⑵hashmap存取元素
- ①存元素
- ②取元素
- ③扩容机制
- ④线程不安全性
- 37.WeakHashMap、IdentityHashMap、EnumMap
- ⑴WeakHashMap
- ⑵IdentityHashMap
- ⑶EnumMap
- 38.HashMap和LinkedHashMap以及TreeMap
- 39.ArrayList
- ⑴概述
- ⑵扩容机制
- ⑶线程不安全
- 40.LinkedList
- ⑴概述
- 41.枚举
- 42.反射
- 43.内部类
- ⑴内部类分类
- ⑵成员内部类
- ⑶局部内部类
- ⑷匿名内部类
- ⑸静态内部类
- 44.泛型
- ⑴概述
- ⑵优点
- ⑶通配符
- ⑷注意事项
- 45.注解
- ⑴概述
- ⑵元注解
- ⑶预置注解
- 46.初始化对象的方式
- 47.SPI
- 48.openJDK和sunJDK
- 49 JSR
1.java重载和重写以及隐藏
重载(overload):是一个类中多态性的一种表现,只要方法名 一致 ,但要求每个方法具有不同的参数的类型或参数的个数,方法的返回类型、修饰符可以相同,也可不同。这里需要注意的是,函数重载不能基于返回值类型
重写(overriding):
1.“两小” :子类方法的返回值类型、抛出的异常 均应小于等于父类方法的
2.“两同”:子类方法的方法名、参数列表均应该与父类方法相同
3.“一大”:子类方法的访问权限应该大于等于父类方法的访问权限
隐藏:
隐藏是针对父类中的成员变量和静态方法而言 。当子类声明了与父类相同的变量名的变量时就实现了对父类成员变量的隐藏。当子类声明了与父类的静态成员方法中相同方法名,相同参数列表和相同返回类型的方法,则实现了对父类静态方法的隐藏
2.构造方法
- ⑴必须与类名相同。
- ⑵构造方法没有返回类型,也不能定义为void,在方法名前面不声明方法类型。
- ⑶构造方法的主要作用是完成对象的初始化工作,它能够把定义对象时的参数传给对象的域。
- ⑷一个类可以定义多个构造方法,如果在定义类时没有定义构造方法,则编译系统会自动插入一个无参数的默认构造器,这个构造器不执行任何代码。
- ⑸构造方法可以重载,以参数的个数,类型,顺序。
- ⑹ 构造方法不能被static、final、synchronized、abstract、native修饰,但可以被public、private、protected修饰;
- ⑺如果不写构造函数的就是使用一个默认的空的构造函数
- ⑻不能被继承,只能被显式或隐式的调用。
- ⑼父类如果没有无参的构造函数,子类就需要在自己的构造函数中显式调用父类的构造函数,否则会编译报错
添加super(“XX”); - ⑽构造方法每次都是构造出新的对象,不存在多个线程同时读写同一对象中的属性的问题,所以不需要同步
3.访问修饰符
public可以被当前类,子类,包,其他包,访问,
protected 可以被当前类,子类,包访问
default可以被可以被当前类,包内访问;
private只能被当前类访问
这里要注意一点:private的属性和方法子类是可以继承的,但是不能调用!
4.接口,抽象类以及它们的区别
⑴抽象类
- ①抽象类不能被实例化。
- ②抽象方法必须由子类来进行重写,子类没有实现它的抽象方法那么子类也是抽象类。
- ③只要包含一个抽象方法就是抽象类。
- ④抽象类中可以包含具体的方法,当然也可以不包含抽象方法。
- ⑤子类中的抽象方法不能与父类的抽象方法同名。
- ⑥abstract不能与final并列修饰同一个类。
- ⑦abstract 不能与private、static、final或native并列修饰同一个方法。
⑵接口
- ①一个类可以 实现 多个接口
- ②接口中的常量和方法必须是public的访问权限,可以写出来或者不写,不写就是默认
- ③在接口中只有方法的声明,没有方法体。
- ④接口中所有的方法必须都是抽象的,接口中也可以定义数据成员,但必须是常量
- ⑤接口中没有构造方法,也不能实例化接口的对象。
- ⑥接口可以实现多继承
- ⑦接口中定义的方法都需要有实现类来实现,如果实现类不能实现接口中的所有方法,.则实现类定义为抽象类。
- ⑧接口中不能包含静态方法
⑶联系
- ①接口和抽象类都不能被实例化。
- ②接口和抽象类都可以包含抽象方法。
⑷区别
- ①一个子类只能继承一个抽象类,但能实现多个接口
- ②抽象类可以有构造方法,接口没有构造方法
- ③抽象类可以有普通成员变量,接口没有普通成员变量
- ④抽象类和接口都可有静态成员变量,抽象类中静态成员变量访问类型任意,接口只能public static final(默认)
- ⑤抽象类可以没有抽象方法,抽象类可以有普通方法,接口中在JAVA8之前都是抽象方法,但是接口在JAVA8之后有了默认方法
- ⑥抽象类可以有静态方法,接口不能有静态方法
- ⑦抽象类中的方法可以是public、protected;接口方法只有public,JDK9接口可以定义私有方法,但不能是抽象的。
5.关键字和保留字
⑴Java 关键字列表 (依字母排序 共50组):
abstract, assert, boolean, break, byte, case, catch, char, class, const(保留关键字), continue, default, do, double, else, enum, extends, final, finally, float, for, goto(保留关键字), if, implements, import, instanceof, int, interface, long, native, new, package, private, protected, public, return, short, static, strictfp, super, switch, synchronized, this, throw, throws, transient, try, void, volatile, while
⑵保留字列表 (依字母排序 共14组),Java保留字是指现有Java版本尚未使用,但以后版本可能会作为关键字使用:
byValue, cast, false, future, generic, inner, operator, outer, rest, true, var, goto (保留关键字) , const (保留关键字) , null
6.析构函数
与 构造函数 相反,当对象结束其 生命周期时(例如对象所在的函数已调用完毕),系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,应在退出前在析构函数中用delete释放)
7.JAVA标识符
Java语言中,对于变量,常量,函数,语句块也有名字,我们统统称之为Java标识符。
标识符可以包括这4种字符:字母、下划线、$、数字;开头不能是数字;不能是关键字
简单的可以记为:数字划钱
注意:不能有空格!
8.JAVA中lang sql util包
- java.lang 提供利用 Java 编程语言进行程序设计的基础类
- java.sql 提供使用 JavaTM 编程语言访问并处理存储在数据源(通常是一个关系数据库)中的数据的 API。
- java.util 包含 collection 框架、遗留的 collection 类、事件模型、日期和时间设施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组)。
9.super和this关键字
this表示当前对象,也就是当前类对象,super表示当前类的父类。
举例:你定义一个新的类:A,这个A继承了类B,也就是说B是A的父类。那么如果A中 有个方法:aa();B中也有个方法: aa();
那么在A 中用this.aa()调用的就是A中定义的方法,而super.aa()调用的就是A的父类B中定义的方法aa();
注意:
- ①this和super需要放在构造方法的第一行
- ②都是对象,所以均不可以在static环境中使用
- ③不能同时出现在同一个构造函数里
Super中的参数表示的就是调用父类相同形参的构造方法
10.JDK中的包和他们的基本功能
JDK常用的包主要有以下几种
- ①java.lang:语言包 自动引入的
- ②java.util:实用包 实用包提供了各种实用功能的类,主要包括日期类、数据结构类和随机数类等
- ③java.awt:抽象窗口工具包 要用于编写GUI程序,包括按钮、标签等常用组件以及相应的事件类
- ④javax.swing:轻量级的窗口工具包,这是目前使用最广泛的GUI程序设计包
- ⑤java.io:输入输出包 java.io提供了系统输入输出类和接口,只要包括输入流类InputStream和输出流OutputStream就可以实现文件的输入输出、管道的数据传输以及网络数据传输的功能
- ⑥java.NET:网络函数包 java.Net提供了实现网络应用程序的类,主要包括用于实现Socket通信的Socket类,此外还提供了便于处理URL的类
- ⑦java.applet:编制applet用到的包(目前编制applet程序时,更多的是使用swing中的JApplet类)。 java.applet是专为创建Applet程序提供的包,它包含了基本的applet类和通信类,目前基本上被JApplet类所代替。
11.object类的基本方法
- ⑴clone方法
保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。 - ⑵getClass方法
final方法,获得运行时类型。 - ⑶toString方法
返回该对象的字符串表示 - ⑷finalize方法
该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。 - ⑸equals方法
该方法是非常重要的一个方法。一般equals和==是不一样的,但是在Object中两者是一样的。子类一般都要重写这个方法。 - ⑹hashCode方法
该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。
一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。 - ⑺wait方法
wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生。
①其他线程调用了该对象的notify方法。
②其他线程调用了该对象的notifyAll方法。
③其他线程调用了interrupt中断该线程。
④时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。 - ⑻notify方法
该方法唤醒在该对象上等待的某个线程。 - ⑼notifyAll方法
该方法唤醒在该对象上等待的所有线程。
12.final关键字
final修饰类、方法、属性!不能修饰抽象类,因为抽象类一般都是需要被继承的,final修饰后就不能继承了。
final修饰的方法不能被重写(覆盖)而不是重载!
final修饰属性,此属性就是一个常量,不能被再次赋值!
13.J2EE常用名词
web容器:给处于其中的应用程序组件(JSP,SERVLET)提供一个环境,使 JSP,SERVLET直接更容器中的环境变量接**互,不必关注其它系统问题。主要有WEB服务器来实现。例如:TOMCAT,WEBLOGIC,WEBSPHERE等。该容器提供的接口严格遵守J2EE规范中的WEB APPLICATION 标准。我们把遵守以上标准的WEB服务器就叫做J2EE中的WEB容器。
EJB容器:Enterprise java bean 容器。更具有行业领域特色。他提供给运行在其中的组件EJB各种管理功能。只要满足J2EE规范的EJB放入该容器,马上就会被容器进行高效率的管理。并且可以通过现成的接口来获得系统级别的服务。例如邮件服务、事务管理。
JNDI:(Java Naming & Directory Interface)JAVA命名目录服务。主要提供的功能是:提供一个目录系,让其它各地的应用程序在其上面留下自己的索引,从而满足快速查找和定位分布式应用程序的功能。
JMS:(Java Message Service)JAVA消息服务。主要实现各个应用程序之间的通讯。包括点对点和广播。
JTA:(Java Transaction API)JAVA事务服务。提供各种分布式事务服务。应用程序只需调用其提供的接口即可。
JAF:(Java Action FrameWork)JAVA安全认证框架。提供一些安全控制方面的框架。让开发者通过各种部署和自定义实现自己的个性安全控制策略。
RMI/IIOP:(Remote Method Invocation /internet对象请求中介协议)他们主要用于通过远程调用服务。例如,远程有一台计算机上运行一个程序,它提供股票分析服务,我们可以在本地计算机上实现对其直接调用。当然这是要通过一定的规范才能在异构的系统之间进行通信。RMI是JAVA特有的。
14.加载驱动的三种方式
- ⑴.Class.forName(“com.microsoft.sqlserver.jdbc.SQLServerDriver”);
- ⑵. DriverManager.registerDriver(new com.mysql.jdbc.Driver());
- ⑶.System.setProperty(“jdbc.drivers”, “com.mysql.jdbc.Driver”);
15.java编译后缀
java中源文件的后缀为.java,
经过javac.exe编译后生成字节码文件,后缀为.class,
再经过java.exe编译为可执行文件,后缀为.exe。
Java程序经编译后会产生byte code
16.静态方法(类方法),静态变量,静态代码块
⑴静态方法中不能引用非静态方法和非静态属性—理由很简单,因为静态方法直接可以不经过实例化就可以使用。但是非静态方法必须借助一个实例才能使用。静态方法中没有this指针
静态方法可以重载
static的方法在装载class得时候首先完成,比 构造方法早,此时非static得和方法还没有完成初始化,所以不能调用。
方法是static静态方法,直接使用"类.方法"即可,因为静态方法使用不依赖对象是否被创建。
⑵静态代码块优先于主方法,且只执行一次
⑶只有类才存在静态的变量 ,非静态成员函数中可以调用静态成员,方法只能对静态变量的操作 不能在方法内试图定义静态变量
⑷执行顺序:
其中涉及:静态初始化代码块、构造代码块、构造方法
当涉及到继承时,按照如下顺序执行:
1、执行父类的静态代码块
static {
System.out.println("static A");
}
输出:static A
2、执行子类的静态代码块
static {
System.out.println("static B");
}
输出:static B
3、执行父类的构造代码块
{
System.out.println("I’m A class");
}
输出:I'm A class
4、执行父类的构造函数
public HelloA() {
}
输出:无
5、执行子类的构造代码块
{
System.out.println("I’m B class");
}
输出:I'm B class
6、执行子类的构造函数
public HelloB() {
}
输出:无
17.java表达式转型规则
Java表达式转型规则由低到高转换:
byte→short(char)→int→long→float→double
- ⑴所有的byte,short,char型的值将被提升为int型;
- ⑵如果有一个操作数是long型,计算结果是long型;
- ⑶如果有一个操作数是float型,计算结果是float型;
- ⑷如果有一个操作数是double型,计算结果是double型;
- ⑸被final修饰的变量不会自动改变类型,当2个final修饰相操作时,结果会根据左边变量的类型而转化。
记住:
- ⑴所占位数少的可以转换为所占位数多的类型,比如byte转char,char转int等;
- ⑵而所占位数多的转为所占位数少的默认情况下不能实现转换,需要强制类型转换,这样可能会丢失一部分原始数据;
- ⑶此外,boolean类型数据和其他七种不能互相转换。
看一种会产生信息丢失的情况:
int a=(int)8846.0 double转int会信息丢失
例题:
下列代码片段中,存在编辑错误的语句是(134)
byte b1=1,b2=2,b3,b6,b8;
final byte b4=4,b5=6,b7;
b3=(b1+b2); //b3=(b1+b2);自动转为int,所以正确写法为b3=(byte)(b1+b2);或者将b3定义为int;
b6=b4+b5; //b6=b4+b5;b4、b5为final类型,不会自动提升,所以和的类型视左边变量类型而定,即b6可以是任意数值类型
b8=(b1+b4); //b8=(b1+b4);虽然b4不会自动提升,但b1仍会自动提升,所以结果需要强转,b8=(byte)(b1+b4);
b7=(b2+b5); //b7=(b2+b5); 同上。同时注意b7是final修饰,即只可赋值一次,便不可再改变
18.基本数据类型和引用数据类型
基本类型(属于原生类)只能保存一些常量数据,引用类型除了可以保存数据,还能提供操作这些数据的功能;
为了操作基本类型的数据,java也对它们进行了封装, 得到八个类,就是java中的基本类型的封装类;他们分别是:
八种基本类型: byte short int long float double char boolean
对应的包装类 : Byte Short Integer Long Float Double Character Boolean
引用类型包括类、接口和数组类型以及特殊的null类型。
引用数据类型:变量名指向存数据对象的内存地址,即变量名指向hash值
19.JDK JRE JVM的区别
JDK:JAVA语言开发工具包,在JDK的安装目录下有一个jre目录,里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib合起来就称为jre。
JRE:包含JVM标准实现和JAVA核心类库,JRE是JAVA运行环境而不是开发环境,不包含任何开发工具(如编译器和调试器)
JVM:JAVA虚拟机,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
JDK是整个Java的核心,包括了Java运行环境JRE、Java工具和Java基础类库。JRE是运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。JVM是整个java实现跨平台的最核心的部分,能够运行以Java语言写的程序。
当我们安装完JDK后会出现两个JRE,这是为什么呢?
一个jre是放在jdk文件夹下面的,这个是开发使用的jre.
一个是供系统其他java应用使用的jre使用的。如果我们不进行开发只运行JAVA程序的话,就不需要安装JDK
20.java变量初始值问题
成员变量有初始值,局部变量没有初始值,final修饰的变量一定要有初始值
21.java集合概览
21.过滤器 监听器 拦截器
- 过滤器(Filter)
属于Servlet,当你有一堆东西的时候,你只希望选择符合你要求的某一些东西,可以用过滤器。 - 监听器
当一个事件发生的时候,你希望获得这个事件发生的详细信息,而并不想干预这个事件本身的进程,这就要用到监听器。 - 拦截器
在一个流程正在进行的时候,你希望干预它的进展,甚至终止它进行,这是拦截器做的事情。
过滤器,监听器,拦截器的区别
- 1.过滤器
Servlet中的过滤器Filter是实现了javax.servlet.Filter接口的服务器端程序,依赖于servlet容器。 - 2.监听器
现在来说说Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener 接口的服务器端程序,它也是随web应用的启动而启动,只初始化一次,随web应用的停止而销毁。主要作用是:做一些初始化的内容添加工作、设置一些基本的内容、比如一些参数或者是一些固定的对象等等。 - 3.拦截器
拦截器是面向切面编程(AOP)的一种实现,是属于spring的。
22.深克隆和浅克隆
- 通常只是对克隆的实例进行复制,但里面的其他子对象,都是共用的
- 克隆的时候会复制它的子对象的引用,里面所有的变量和子对象都是又额外拷贝了一份
23.JAVA四种引用
- 强引用
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。 - 软引用
内存不足回收。可以用来做缓存。 - 弱引用
无论内存是否充足,都会回收被弱引用关联的对象,弱引用能用来在回调函数中防止内存泄露。因为回调函数往往是匿名内部类,隐式保存有对外部类的引用,所以如果回调函数是在另一个线程里面被回调,而这时如果需要回收外部类,那么就会内存泄露,因为匿名内部类保存有对外部类的强引用。 - 虚引用
它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。
24.JAVA序列化
JAVA序列化:把JAVA对象转化为字节序列
JAVA反序列化 :把字节序列转化为JAVA对象
序列化的作用:
⑴实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上
⑵利用序列化实现远程通信,即在网络上传送对象的字节序列
序列化注意事项:
- transient 修饰的变量在反序列化后对应的值没了
- static 序列化是不能序列化static变量的,static属于类,保存的是类的状态。
- 要加上序列化ID
25.拆装箱
自动装箱是Java编译器在基本数据类型和对应的对象包装类型之间做的一个转化,反之就是自动拆箱。
- 同类型的进行比较,如Integer 与int,Long与long进行==比较时,会自动拆箱;不同类型之间进行比较,如果有一方为非包装类,则会自动拆箱。如果两方不同类型且都为包装类,则不会拆箱,且不能比较,编译会报错
- 两个Integer类型进行“==”比较,如果其值在-128至127,那么返回true,否则返回false, 这跟Integer.valueOf()的缓冲对象有关
- 两个基本型的封装型进行equals()比较,首先equals()会比较类型,如果类型相同,则继续比较值,如果值也相同,返回true
- 基本型封装类型调用equals(),但是后面参数是基本类型,则后面参数会先装箱
public static void main(String[] args) {
int a=257;
Integer b=257;
System.out.println(a==b); //同类型基本类型和封装类型比,转化为基本类型 true
System.out.println(b.equals(a)); // true
System.out.println(b.equals(257.0)); // false
}
26.数据类型及类型转化
⑴基本数据类型及对应的包装类
- byte(Byte):8位,最大存储数据量是255,存放的数据范围是-128~127之间。
- short(Short):16位,最大数据存储量是65536,数据范围是-32768~32767之间。
- int(Integer):32位,最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1。
- long(Long):64位,最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1。
- float(Float):32位,浮点数,直接赋值时必须在数字后加上f或F。
- double(Double):默认的浮点数,64位,赋值时可以加d或D也可以不加。
- boolean(Boolean):只有true和false两个取值。
- char(Character):16位,存储Unicode码,用单引号赋值,里面只能有一个字母。还有一种情况,char a=98,这个赋值时一个ASCII码,输出也是对应的值。
⑵自动类型转换,强制类型转换以及类型提升
- 自动类型转换 目标类型大于原类型,int类型可以直接赋值给Double类型
- 强制类型转换 目标类型小于原类型,数值并非四舍五入,而是直接将多余的小数位截断。
- 类型提升 当一个java算术表达式中包含多个基本类型的值时,整个算术表达式的数据类型将发生自动提升
- 所有byte型、short型和char型将被提升到int型。
- 整个算术表达式的数据类型自动提升到与表达式中最高等级操作数同样的类型
例子1:
//定义一个short类型变量
short sValue = 5;
//表达式中的sValue将自动提升到int类型,则右边的表达式类型为int
//将一个int类型赋给short类型的变量将发生错误。
sValue = sValue - 2;
例子2:
byte b1=1,b2=2,b3,b6;
final byte b4=4,b5=6;
b6=b4+b5;
b3=(b1+b2);
System.out.println(b3+b6);
语句:b3=b1+b2编译出错
被final修饰的变量是常量,这里的b6=b4+b5可以看成是b6=10;在编译时就已经变为b6=10了
而b1和b2是byte类型,java中进行计算时候将他们提升为int类型,再进行计算,b1+b2计算后已经是int类型,赋值给b3,b3是byte类型,类型不匹配,编译不会通过,需要进行强制转换。
27.servlet
⑴概述
servlet就是一个Java接口,本质上是一套处理网络请求的规范。
实现了servlet的类也并不能直接和客户端打交道,请求需要通过servlet容器(例如tomcat)来和客户端打交道。
⑵生命周期
web容器加载servlet,生命周期开始,调用init()方法初始化,然后service实现,根据请求不同调用doGet或doPost方法。结束服务,web容器调用destroy()方法
- 初始化阶段,容器调用init()方法,仅执行一次
- 响应客户请求阶段,调用servlet()方法。由servlet()方法根据提交方式选择执行doGet()或者doPost()方法
- 终止阶段,容器调用destroy()方法, 仅执行一次
⑶GenericServlet和HttpServlet
- GenericServlet是个抽象类,必须给出子类才能实例化。它给出了设计servlet的一些骨架,定义了servlet生命周期,还有一些得到名字、配置、初始化参数的方法,其设计的是和应用层协议无关的,也就是说你有可能用非http协议实现它(其实目前Java Servlet还是只有Http一种)。
- HttpServlet是子类,当然就具有GenericServlet的一切特性,还添加了doGet, doPost, doDelete, doPut, doTrace等方法对应处理http协议里的命令的请求响应过程。
⑷Servlet和jsp的区别和联系
- JSP能够完成的工作,使用Servlet都可以完成,JSP经过容器解释之后就是servlet
- JSP更适合做页面展示,Servlet更适合做流程控制
- Servlet没有内置对象
28.异常
⑴概述
- 粉红色的是受检查的异常(checked exceptions),其必须被 try{}catch语句块所捕获,或者在方法签名里通过throws子句声明.
- 绿色的异常是运行时异常(runtime exceptions),需要程序员自己分析代码决定是否捕获和处理,比如 空指针,被0除…
- 而声明为Error的,则属于严重错误,如系统崩溃、虚拟机错误、动态链接失败等,这些错误无法恢复或者不可能捕捉,将导致应用程序中断,Error不需要捕捉。
⑵异常处理原则
- 具体明确
- 提早抛出
- 延迟捕获
- catch顺序注意,颗粒度小的放上面
- 正确记录异常
- 不要把异常包含在循环语句
⑶注意事项
①上级可以捕获没有显示向上抛的运行异常
/**
* @author bincai, bincai@mobvoi.com
* @date Jul 19 , 2018
*/
public class Test {
public static void main(String[] args) {
try {
test();
} catch (RuntimeException e) {
System.out.println("捕获下级方法的运行时异常");
}
}
private static void test() {
test2();
}
private static void test2() {
throw new RuntimeException("2323");
}
}
输出:
捕获下级方法的运行时异常
②try里面出现没有catch的异常也会执行finally
public static void main(String[] args) {
try {
System.out.println(1 / 0);
} finally {
System.out.println(2);
}
}
输出:
2
Exception in thread "main" java.lang.ArithmeticException: / by zero
at testswitch.TestTry.main(TestTry.java:11)
29.多态
⑴多态的作用
一个接口,多种实现,通过抽象化来提高程序的扩展性。
⑵多态的条件
- 要有继承关系
- 子类要重写父类的方法
- 父类引用指向子类
⑶多态的注意事项
- 多态后不能使用子类特有的属性和方法
- 成员变量和静态方法使用父类的
- 子类重写的普通方法使用子类的
⑷基于JVM解释多态
方法调用是依照方法的符号引用得到具体内存引用的过程,调用类的方法在类加载时间就直接可以转化为具体内存引用,这个是静态绑定,而调用对象方法则是动态绑定,最后得到真正的方法引用,完成调用。
30.==和equals hashCode
⑴==和equals
- ==是比较地址值是否相等,有基本类型的是比较值
- equals是object类的方法,所有没有重写这个方法的类中的这个方法比较的都是地址,也就是和’=='是一样的,重写过这个方法的类就按照重写的方法来比较,比如String类就重写了这个方法,比较的是内容
⑵equals和hashCode
- 关于hashCode方法和equals方法,一致的约定是
- 对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行hashcode比较时所用的信息没有被修改
- 重写了euqls方法的对象必须同时重写hashCode()方法
- 如果2个对象通过equals调用后返回是true,那么这个2个对象的hashCode方法也必须返回同样的int型散列码
- 如果2个对象通过equals返回false,他们的hashCode返回的值允许相同
- 为什么重写equals的时候要重写hashcode
如果2个对象通过equals调用后返回是true,那么这个2个对象的hashCode方法也必须返回同样的int型散列码。而且如果不重写hashcode,在hashmap以对象做key时也会出问题。
⑵例题
①例题1:
假设有以下代码
String s=”hello”;
String t=”hello”;
Char c[] ={‘h’,’e’,’l’,’l’,o’’};
下列选项中返回false的语句是: B
s.equals(t);
t.equals(c);
s==t;
t.equals(new String(“hello”));
这里s==t返回的是true,为什么呢?因为这里有个字符串缓存的机制,建立string t="hello"时会去string缓存池中找相同的对象,如果想让为fasle,可以new 一个新的string
②例题2:
public class StringDemo{
private static final String MESSAGE="taobao";
public static void main(String [] args) {
String a ="tao"+"bao";
String b="tao";
String c="bao";
System.out.println(a==MESSAGE); //true
System.out.println((b+c)==MESSAGE); //false
}
}
31.数组
⑴概述
数组是引用类型,不属于原生类,可以看成是一种对象,一旦大小指定就不可以进行改变了。
⑵初始化数组
- 左边的名称和括号顺序可以随便颠倒,但是右边第一个括号一定要有值
- 定义数组,等号左边不能出现数字,也就是数组的不管什么大小 不能出现在左边
int a[]; //声明未初始化
a = new int [10]; // 定义占用空间大小(10个int)
int a[] = new int [10]; //声明并定义大小(即分配了指定大小的空间)
int a[] = {1,2,3}; // 声明并初始化,占用空间大小是3个int。
float a[][]=new float[3][];
float []b=new float[6];
int []c=new int[3];
另外我们要知道:
String x[][]=new String[3][2],,实际上x为:
{{“A某些String”,"B 某些String "}
{"C 某些String ","D 某些String "}
{"E 某些String ","F 某些String "} }
x.length取的是最外层括号包含的元素个数,因此x.length=3;
x[0][1]取的是第一个最外层括号包含元素内的第二个元素,即x[0][1]= "B 某些String "
⑶数组四种拷贝方式
先来个效率对比:System.arraycopy > clone > Arrays.copyOf > for循环,我们一般用最快的,其实前三个效率差不多。
- for循环方法:效率低
- Object.clone()方法
从源码来看同样也是native方法,但返回为Object类型,所以赋值时将发生强转 - Arrays.copyOf()方法
public static int[] copyOf(int[] original, int newLength) {
int[] copy = new int[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
- System.arraycopy()
public static void arraycopy(Object src,
int srcPos,
Object dest,
int destPos,
int length)
32.Comparable与Comparator的区别
其实在JAVA8可以通过lambda来更简单的写比较器。
⑴Comparable内部比较器
若一个类实现了Comparable接口,就意味着该类支持排序。实现了Comparable接口的类的对象的列表或数组可以通过Collections.sort或Arrays.sort进行自动排序,一般要重写compareTo方法。
⑵Comparator外部比较器
外部比较器,位于java.util包下,Comparator是比较接口,我们如果需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口),那么我们就可以建立一个“该类的比较器”来进行排序,这个“比较器”只需要实现Comparator接口即可。一般要重写compare方法。
33.值传递和引用传递
⑴概述
- 值传递不可以改变原变量的内容和地址
- 引用传递不可以改变原变量的地址,但可以改变原变量的内容
⑵分类
- 对象就是传引用对象类型(Object,数组,容器……)
- 原始类型就是传值(int,char,……)
- String,Integer, Double等immutable类型因为没有提供自身修改的函数,每次操作都是新生成一个对象,所以要特殊对待。可以认为是传值。
⑶引用传递其实传递的是引用的一个副本
这里我们要看方法内是否修改了堆内存的内容,堆内存内容并没有变化则原变量的内容也没有发生变化。
package testSpringMVC;
//蔡彬
public class JstackCase {
public static void main(String[] args) {
girl a=new girl();
a.setAge(10);
girl b=new girl();
b.setAge(20);
test0(a,b);
System.out.println(a.getAge()); // 200
System.out.println(b.getAge()); // 100
girl c=new girl();
c.setAge(10);
girl d=new girl();
d.setAge(20);
test(c,d);
System.out.println(c.getAge()); // 10 因为在方法内并没有修改堆的内容
System.out.println(d.getAge()); // 20
}
private static void test0(girl a, girl b) {
// TODO Auto-generated method stub
a.setAge(200);
b.setAge(100);
}
private static void test(girl c, girl d) {
// TODO Auto-generated method stub
c=d;
}
}
34.类的生命周期以及双亲委托机制
生命周期包括:加载、验证、准备、解析、初始化、使用和卸载
⑴加载
整个类加载过程中,除了在加载阶段用户应用程序可以自定义类加载器参与之外,其余所有的动作完全由虚拟机主导和控制,
双亲委托机制:
Java的类加载使用双亲委派模式,即一个类加载器在加载类时,先把这个请求委托给自己的父类加载器去执行,如果父类加载器还存在父类加载器,就继续向上委托,直到顶层的启动类加载器。如果父类加载器能够完成类加载,就成功返回,如果父类加载器无法完成加载,那么子加载器才会尝试自己去加载。
- 启动类加载器(Bootstrap ClassLoader),JVM一部分,负责将放置在Jre\lib目录中的,或者被-Xbootclasspath参数所指定路径中的,并且是虚拟机能识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放置在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接使用。
- 扩展类加载器(Extension ClassLoader),这个类加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载Jre\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
- 应用程序类加载器(Application ClassLoader),它负责加载ClassPath所有jar或目录,开发者可以直接使用这个类加载器。
- 自定义加载器 自己编写的ClassLoader
为什么采用双亲委托机制:
一个可以避免类的重复加载,另外也避免了java的核心API被篡改
打破双亲委托机制:
不仅要继承ClassLoader类,还要重写loadClass和findClass方法。
⑵验证
验证的目的是为了确保Class文件中的字节流包含的信息符合当前虚拟机的要求。
⑶准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。
- 这里内存分配只是类变量(static修饰的),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。
- 这里所赋给的初始值只是默认0值(如0、0L、null、false等)
假设:
public static int value = 3;
准备阶段只会将它赋值为0,直到初始化阶段才会将它赋值为3.
但是如果被final同时修饰,在这里就会被直接赋值为3。
⑷解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
⑸初始化
初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。
⑹使用
使用该类所提供的功能,其中包括主动引用和被动引用
- 主动引用:
- 通过new关键字实例化对象、读取或设置类的静态变量、调用类的静态方法。
- 通过反射方式执行以上三种行为。
- 初始化子类的时候,会触发父类的初始化。
- 作为程序入口直接运行时(也就是直接调用main方法)。
- 被动引用:
- 引用父类的非final静态字段,只会引起父类的初始化,而不会引起子类的初始化。
- 引用父类的final静态字段,父子类都不会初始化。
- 定义类数组,不会引起类的初始化。
- 引用类的常量,不会引起类的初始化。
/***
* @author bincai
* @email 1355869831@qq.com
*/
class InitClass{
static {
System.out.println("初始化InitClass");
}
public static String a = null;
public final static String b = "b";
public static void method(){}
}
class SubInitClass extends InitClass{
static {
System.out.println("初始化SubInitClass");
}
}
public class testswitch {
public static void main(String[] args) throws Exception{
//String a = SubInitClass.a;// 引用父类的静态字段,只会引起父类初始化
//String b = SubInitClass.b;// 使用类的常量不会引起父类和子类初始化
SubInitClass[] sc = new SubInitClass[10];// 定义类数组不会引起类的初始化
}
}
⑺卸载
在类使用完之后,如果满足下面的情况,类就会被卸载
- 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例
- 加载该类的ClassLoader已经被回收
- 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法
如果以上三个条件全部满足,jvm就会在方法区垃圾回收的时候对类进行卸载。
类的卸载过程其实就是在方法区中清空类信息,java类的整个生命周期就结束了
35.动态绑定和静态绑定
⑴什么叫绑定
绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来。
⑵静态绑定
在编译过程中就已经知道这个方法到底是哪个类中的方法,静态绑定效率要高一些。
只有final,static(重写static方法使用多态,调用的仍是父类的static方法),private和构造方法是静态绑定
⑶动态绑定
在运行时进行绑定,具体原理:
父类不是接口,当子类和父类加载到虚拟机时,方法区就有了类的信息,方法区中有方法表,如果子类重写父类的方法,那么在各自方法表里方法的偏移量是一样的,当我们调用时:
- 先找到方法调用的符号引用
- 查看父类偏移量
- 找到具体的子类
- 根据父类偏移量查看子类方法表有没有重写
如果父类是接口的话,无法根据偏移量来确定,则采用搜索方法表的形式,效率要低些。
36.hashmap
⑴hashmap基本结构
①构造函数
// 默认构造函数。
HashMap()
// 指定“容量大小”的构造函数
HashMap(int capacity)
// 指定“容量大小”和“加载因子(可以大于1)”的构造函数
HashMap(int capacity, float loadFactor)
// 包含“子Map”的构造函数
HashMap(Map<? extends K, ? extends V> map)
②基本结构
- jdk1.7 数组+链表(头插)
- jdk1.8 数组+链表(尾插)/红黑树(链表长度到8时,将链表转化为红黑树来处理
⑵hashmap存取元素
①存元素
HashMap会对null值key进行特殊处理,总是放到table[0]位置
put过程是先计算hash然后通过hash与table.length取模计算index值,然后将key放到table[index]位置,当table[index]已存在其它元素时,会在table[index]位置形成一个链表,将新添加的元素放在table[index],原来的元素通过Entry的next进行链接,这样以链表形式解决hash冲突问题,当元素数量达到临界值(capactiyfactor)时,则进行扩容,是table数组长度变为table.length2
在JDK1.8版本中,链表长度超过8改成红黑树
②取元素
同样当key为null时会进行特殊处理,在table[0]的链表上查找key为null的元素
get的过程是先计算hash然后通过hash与table.length取摸计算index值,然后从table[index]上的链表或红黑树找到key,然后返回
③扩容机制
- HashMap 的默认大小是16个元素(必须是2的幂),2的n次幂可以减少碰撞
- 元素个数 超过 容量长度*加载因子的系数 时,进行扩容。
- 默认加载因子0.75
- HashMap扩容时,会重新计算元素位置,在jdk1.8中但是元素要么在原位置,要么新的位置为元素在原数组的位置+原数组的长度。
④线程不安全性
- jdk1.7中扩容死循环
假设两个线程同时进行resize, A->B 第一线程在处理过程中比较慢,第二个线程已经完成了倒序编程了B-A 那么就出现了循环B->A->B - 数据重复
如果有两个线程同时发现自己都key不存在,而这两个线程的key实际是相同的,在向链表中写入的时候第一线程将e设置为了自己的Entry,而第二个线程执行到了e.next,此时拿到的是最后一个节点,依然会将自己持有是数据插入到链表中,这样就出现了数据 重复。 - 数据丢失
如果有两条线程同时执行到这条语句table[i]=null,时两个线程都会区创建Entry,这样存入会出现数据丢失。
37.WeakHashMap、IdentityHashMap、EnumMap
⑴WeakHashMap
- 当除了自身有对key的引用之外,此key没有其他的引用,那么weakHashMap会在下一次对weakHashMap进行增删改查操作(重点,必须有增删改查操作时才会触发)的时候及时丢弃该键值对,节约内存的使用,此特性使得weakHashMap比较适合构建缓存系统。
public class TestTry {
public static void main(String[] args) {
WeakHashMap wak = new WeakHashMap();
//两个key都是匿名字符串对象(没有其他引用)
wak.put(new String("数学"), new String("优良"));
wak.put(new String("语文"), new String("良好"));
wak.put(new String("语文"), new String("良"));
//该key是一个系统缓存的字符串对象
wak.put("java", new String("好"));
System.out.println(wak);
//{java=良好, 数学=优良, 语文=良好}
//通知系统进行垃圾回收
System.gc();
System.runFinalization();
System.out.println(wak);//{java=好}
}
输出:
{java=好, 数学=优良, 语文=良}
{java=好}
⑵IdentityHashMap
在IdentityHashMap中,当且仅当两个key严格相等(key1==key2)时,IdentityHashMap才认为两个key相等;相对于普通HashMap而言,只要key1和key2通过equals()方法返回true,且它们的hashCode值相等即可。
package test;
import java.util.*;
public class test1 {
public static void main(String args[]) {
IdentityHashMap idenmap = new IdentityHashMap();
idenmap.put(new String("语文"), 80);
idenmap.put(new String("语文"), 89);
idenmap.put("java", 80);
idenmap.put("java", 81);
idenmap.put(null,123);
idenmap.put(null,234);
idenmap.put("test",null);
System.out.println(idenmap);
}
}
输出:
{test=null, 语文=80, java=81, 语文=89, null=234}
⑶EnumMap
EnumMap不允许使用null作为key,但允许使用null作为value。如果试图使用null作为key时将抛出NullpointerException。创建EnumMap是必须指定一个枚举类
如果只是查询是否包含值为null的key,或只是删除值为null的key,都不会抛出异常。
public class test1 {
public static void main(String args[]) {
EnumMap map = new EnumMap(Season.class);
map.put(Season.SUMMER, "夏天");
map.put(Season.SPRING, "春天");
System.out.println(map);
}
}
enum Season{
SPRING,SUMMER,FAIL,WINTER
}
38.HashMap和LinkedHashMap以及TreeMap
- LinkedHashMap保存了记录的插入顺序
- TreeMap默认是按键值的升序排序、也可以指定排序的比较器
39.ArrayList
⑴概述
ArrayList是基于数组的,是连续存放元素的,查找的时候直接根据数组下标就可以了。插入删除的时候,就要把修改的那个节点之后的所有数据都要移动,所以就慢了,插入删除速度和删除的元素离数组末端有多远有关,越远的话越慢。
长度默认是0,第一次往里面添加元素时底层数组会初始化一个长度为10的数组
⑵扩容机制
扩容的话扩容为原来的1.5倍。具体步骤
- 新建一个数组为原来的1.5倍
- 使用System.arraycopy方法把元素copy进去并返回
⑶线程不安全
在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。
那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。
多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。
40.LinkedList
⑴概述
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
基于双向链表,可以被当作堆栈、队列或双端队列进行操作,大量的插入删除比较快,但是查询慢。
41.枚举
JDK1.5引入了枚举类型。
public enum StatusEnum {
SUCCESS(0),
//... 想象这里有几十种状态,不按ordinal来的
FAILED(100);
private int code; //这里仅居int的例子,还有转String的,同理
private StatusEnum(int status) {
this.code =status;
}
public int getCode() {
return code;
}
// 下面这部分是解决的办法
private static final Map<Integer, StatusEnum> code2enumMap =
new HashMap<Integer, StatusEnum>();
static {
for(StatusEnum st: StatusEnum.values()) {
code2enumMap.put(st.getCode(), st);
}
}
/**
* 按code从映射中找到枚举
* @param statusCode
* @return
*/
public static StatusEnum getByCode(int statusCode) {
return code2enumMap.get(statusCode);
}
}
42.反射
通过类的全类名就能把这个类的所有方法和变量的信息(方法名,变量名,方法,修饰符,类型,方法参数等等所有信息)找出来。如果明确知道这个类里的某个方法名+参数个数 类型,还能通过传递参数来运行那个类里的那个方法,这就是反射。反射的具体方法不多说。这里只说三种获得类类型的方式:
- 通过类名.class获得,执行静态块、不执行动态构造块,动态构造块在newInstance的时候执行
- 通过对象名.getClass()获得 (需要获得实例对象),实例对象的时候执行静态块和动态构造块
- 通过Class.forName(“类全名”)获得,执行静态块、不执行动态构造块,动态构造块在newInstance的时候执行
43.内部类
⑴内部类分类
- 成员内部类
- 局部内部类
- 匿名内部类
- 静态内部类
⑵成员内部类
- 成员内部类可以访问外部类所有属性和方法,但是外部类访问内部类必须先创建内部类的实例。
- 成员内部类不能有static属性和方法。
- 成员内部类可以用四种修饰符,private修饰的内部类以及内部类的方法和成员变量只能它所属的外部类访问。
⑶局部内部类
定义在一个方法或者作用域内,访问仅限于该方法或作用域内
⑷匿名内部类
使用一个匿名内部类我们必须继承一个父类或实现一个接口,它也没有class关键字
不能是抽象的,也不能被重复使用~
不能定义构造函数,不能存在静态成员变量和静态方法
⑸静态内部类
- 完全不依赖于外部类
- 它不能访问外围类非static成员变量和方法
44.泛型
⑴概述
只在编译阶段有效,运行时擦除
⑵优点
- 通过变量类型限制,提高安全性
- 消除强制类型转换
⑶通配符
- 无界通配符(<?>)
- 带有下边界的通配符(<? super E>)
- 带有上边界的通配符(<? extends E>)
⑷注意事项
- 可以为一个泛型指定上边界或下边界, 但是不能同时指定上下边界
- 泛型遇到重载要注意,由于擦除机制下面的代码是通不过的:
public class GenericTypes {
public static void method(List<String> list) {
System.out.println("invoke method(List<String> list)");
}
public static void method(List<Integer> list) {
System.out.println("invoke method(List<Integer> list)");
}
}
- 泛型的静态变量是共享的
public class StaticTest{
public static void main(String[] args){
GT<Integer> gti = new GT<Integer>();
gti.var=1;
GT<String> gts = new GT<String>();
gts.var=2;
System.out.println(gti.var);
}
}
class GT<T>{
public static int var=0;
public void nothing(T x){}
}
45.注解
⑴概述
定义方式:
public @interface TestAnnotation {
}
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型,可以给成员变量赋默认值,也可以使用时候进行赋值
public @interface TestAnnotation {
int id();
String msg();
}
⑵元注解
- @Target 注解可以用于什么地方,如果不明确指出,该注解可以放在任何地方
- @Retention 注解在什么范围内有效
- SOURCE:在源文件中有效(即源文件保留)
- CLASS:在class文件中有效(即class保留)
- RUNTIME:在运行时有效(即运行时保留)
- @Document 能够将注解中的元素包含到 Javadoc 中去
- @Inherited 被注解修饰类的子类也相当于拥有了这个注解
- @Repeatable· Java 1.8 才加进来的,注解多次应用
@Repeatable(Authorities.class)
public @interface Authority {
String role();
}
public @interface Authorities {
Authority[] value(); // 容器注解,它里面必须要有一个 value 的属性,属性类型是一个被 @Repeatable 注解过的注解数组,注意它是数组
}
public class RepeatAnnotationUseOldVersion {
@Authorities({@Authority(role="Admin"),@Authority(role="Manager")})
public void doSomeThing(){
}
}
⑶预置注解
- @Deprecated 被弃用的代码
- @Override 覆盖
- @SuppressWarnings 关闭警告
- @SafeVarargs 参数安全类型注解,它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告
- @FunctionalInterface 函数式接口注解
46.初始化对象的方式
- new 语句(调用构造器)
- 反射机制(调用构造器)
- Object.clone 方法
- 反序列化
- Unsafe.allocateInstance
47.SPI
全名为Service Provider Interface,我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块、xml解析模块、jdbc模块等方案。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。
SPI具体约定
Java SPI的具体约定为:当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。在dubbo中就使用了SPI机制。
使用SPI的例子:
// 定义一个接口.
public interface Caibin {
void execute();
}
// 接口实现类1
public class Caibin1 implements Caibin {
@Override
public void execute(){
System.out.println("1");
}
}
// 接口实现类2
public class Caibin2 implements Caibin{
@Override
public void execute() {
System.out.println("2");
}
}
// 测试类
public class Test {
public static void main(String[] args) {
Iterator<Caibin> providers = Service.providers(Caibin.class);
ServiceLoader<Caibin> load = ServiceLoader.load(Caibin.class);
while(providers.hasNext()) {
Caibin ser = providers.next();
ser.execute();
}
System.out.println("--------------------------------");
Iterator<Caibin> iterator = load.iterator();
while(iterator.hasNext()) {
Caibin ser = iterator.next();
ser.execute();
}
}
}
配置:
输出:
1
2
--------------------------------
1
2
48.openJDK和sunJDK
- 授权协议的不同: OpenJDK采用GPL V2协议放出,而JDK则采用JRL放出。两者协议虽然都是开放源代码的,但是在使用上的不同在于GPL V2允许在商业上使用,而JRL只允许个人研究使用。
- OpenJDK不包含Deployment(部署)功能: 部署的功能包括:Browser Plugin、Java Web Start、以及Java控制面板,这些功能在OpenJDK中是找不到的。
- OpenJDK源代码不完整: 在采用GPL协议的OpenJDK中,Sun JDK的一部分源代码因为产权的问题无法开放OpenJDK使用,其中最主要的部份就是JMX中的可选元件SNMP部份的代码。因此这些不能开放的源代码将它作成plugin,以供OpenJDK编译时使用,你也可以选择不要使用plugin。而Icedtea则为这些不完整的部分开发了相同功能的源代码(OpenJDK6),促使OpenJDK更加完整。
- 部分源代码用开源代码替换: 由于产权的问题,很多产权不是SUN的源代码被替换成一些功能相同的开源代码,比如说字体栅格化引擎,使用Free Type代替。
- OpenJDK只包含最精简的JDK: OpenJDK不包含其他的软件包,比如Rhino Java DB JAXP……,并且可以分离的软件包也都是尽量的分离,但是这大多数都是自由软件,你可以自己下载加入。
- 不能使用Java商标: 这个很容易理解,在安装OpenJDK的机器上,输入“java -version”显示的是OpenJDK,但是如果是使用Icedtea补丁的OpenJDK,显示的是java。(未验证)
49 JSR
jsr是Java Specification Requests的缩写,意思是Java 规范提案