11.0....泛型
11.1...泛型是提供给 javac 编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器
编译带类型说明的集合时会去掉"类型"的信息,使程序欲行效率不受影响那个,对于参数化的泛型类型,getClass()
方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往
某个泛型集合中加入其他类型的数据,例如,用反射得到集合,再调用其 add 方法即可。
11.2..泛型中用到的术语
ArrayList<E> ArrayList<Integer>
/** 整个称为ArrayList<E> 泛型类型
ArrayLIst《E》中的E 称为类型变量或类型参数
整个ArrayList《Integer》称为参数化的类型
ArrayList《Integer》中的Integer称为类型参数的实例或实际类型参数
ArrayList《Integer》中的《》念 type of
ArrayList 称为原始类型
*/
参数化类型与原始类型的兼容性:
参数化类型可以引用一个原始类型的对像,编译报告警告,如
Collection<String> c = new Vector();
原始类型可以引用一个参数化类型的对象,编译报告警告,如
Collection c = new Vector<String>();
参数化类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Object>(); '错误
Vector<Object> v = new Vector<String>(); '错误
在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句有错误:
Vector<Integer> vectorList[] = new Vector<Integer>[10]; '这样不行,是错误的
11.3...通配符 ?
'使用 ?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要做引用,可以调用与参数化无关的方法,不能调用
'与参数化有关的方法。
<? extends Object>上限 <? super Integer>下限 //限定类型总是包括自己。
11.4...自定义泛型
用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前。
按照惯例,类型参数通常用单个大写字母表示。
'只有引用类型才能作为泛型方法的实际参数。
1.除了在应用类型时可以使用 extends 限定符,在定义类型时也可以使用 extends 限定符,例如,Class.getAnnotation()
方法的定义。并且可以用 & 符号来定义多个边界,如<V extends Serializable & cloneable> void method(){}
2,普通方法,构造方法和静态方法中都可以使用泛型。编译器也不允许创建类型变量的数组。
3,也可以用类型变量表示异常,称为参数化的异常,可以用于方法的 throws 列表中,但是不能用于 catch 子句中。
4,在泛型中可以同时有多个类型参数,在定义他们的尖括号中用逗号分。
5,只有引用类型才能作为泛型方法的实际参数,对于add 方法,使用个基本类型的数据进行测试没有问题,这是因为自动装箱
和拆箱了。而基本数据数组型的数据本身就是对象了,不会再进行装箱和拆箱操作。
11.5..定义泛型类型
1.如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要
采用泛型类型的方式进行定义,也就是类级别的泛型。如;
public class GennericDao<T>{
public void save(T obj){}
}
2,类级别的泛型是根据引用该类名时指定的类型信息来参数话类型变量的。
'在对泛型类型进行参数话时,类型参数的实例必须是引用类型,不能是基本类型。
'当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用。因为静态
成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。
12.0..类加载器
12.1..@Java虚拟机中可以安装多个累加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:
BootStrap , ExtClassLoader , AppClassLoader
2,类加载器也是Java类,因为其他是java类的累加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这就是
BootStrap
3,Java虚拟机中过的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指定一个父级
类装载器对象或者默认采用系统类装载器为其父级类加载。
BootStrap -----------> JRE / lib/rt.jar
^
|
ExtClassLoader ------> JRE /lib/ext/*.jar */
^
|
System classLoader --> AppClassLoader ------> classpath 指定的所有jar 或目录
/ \
MyClassLoader -------------> 指定的特殊目录
12.2..类加载器的委托机制
/* 每个ClassLoader本身只能分别加载指定位置和目录中的类,但他们可以委托其他的类加载器去加载类,这就是类加载器的委托
模式。 类加载器一级级委托到BootStrap 类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器
去进行真正的加载。 */
1.Java虚拟机要加载一个类时,会首先让当前线程的类加载器去加载线程中的第一个类。
2.如果类A 中引用了类B ,Java虚拟机将使用加载类A的类加载器来加载类B。
3,还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
'每个类加载器加载类时,又先委托给其上级类加载器。
当所有的最根的类加载器没有加载到类,就回到发起者类加载器,还加载不了,则抛 ClassNotFoundException.
13...代理类
13.0..@为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,如;异常处理,日志,计算方法的运行时间,事务管理
@等,就是代理。
1.编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。
/*如采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类,还是代理类,这样
以后很容易切换。如,想要日子功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,有想
去掉系统功能也很容易。
*/
13.1.. AOP
交叉业务的编程问题即为面向方面的编程(Aspect oriented program,简称AOP),AOP 的目标是要是交叉业务模块化。
可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的。
13.2..动态代理类
1.要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用动态代理方式,是一件非常麻烦的事。
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即为动态代理类。
2.JVM生成的动态类必须实现一个或个接口,所有,JVM生成的动态类只能用作具有相同接口的目标类的代理。
3,CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态
代理类,那么可以使用CGLIB库。
/* 代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统
功能代码:
1,在调用目标方法之前。
2,在调用目标方法之后。
3,在调用目标方法前后。
4,在处理目标方法异常的catch块中。*/
4. Proxy 提供了两个方法来创建动态代理类和 动态代理实例:
4.1.
static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces)
// 该处 ClassLoader 指定生成动态代理类的类加载器。 //创建一个动态代理类所对应的Class 对象,该代理类将实现 interfaces 所指定的多个接口。
Class clazzProxy1 =Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
//获得一个动态代理类的Collection 的字节码 Class 对象。 Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
//用代理类的构造方法创建对象,代理类没有空的构造函数,只有一个接收Handler 对象的
//构造函数,而InvocationHandler 是一个接口,只能new 他的子类。
class Handler implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 复写父类的方法,InvocationHandler只有一个invoke方法
//该方法接收三个参数
return null;
}
}
Collection proxy1 = (Collection)constructor.newInstance(new Handler()); Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//还可以直接写 InvocationHandler的匿名内部类,一步到位
return null;
}
});
4.1.创建动态代理类的步骤
4.1.1.用反射获得构造方法
4.1.2.编写一个InvocationHandler类
4.1.2.调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去
4.1.3.传Handler对象时,可以用匿名内部类的方式
4.2.'让JVM创建动态类,需要给他三个方面的信息
4.2.1.生成的类中有那些方法,通过让其实现那些接口的方法进行告知。
4.2.2.产生的类字节码必须有一个关良的类加载器对象。
4.2.3.生成的类的方法的代码是怎样的,也由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象创给他,他
调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,他是在创建动态类的实例
对象,他是在创建动态类的实例对象的构造方法时传递进去的。
5..Proxy 创建动态类的第二个方法
把第一个方法合二为一了,用newProxyInstance()方法,接收三个参数
Collection proxy3 = (Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(), //接收接口的类加载器
new Class[]{Collection.class}, //接收多个接口
new InvocationHandler(){ //接收一个InvocationHandler的子类对象
ArrayList target = new ArrayList();//定义目标类
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
return method.invoke(target, args);//把传入的参数应用在目标类方法上,并返回出去
}
});
proxy3.add("xxx");
System.out.println(proxy3.size());