基础加强二:
泛型:
ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:
整个称为ArrayList<E>泛型类型
ArrayList<E>中的E称为类型变量或类型参数
整个ArrayList<Integer>称为参数化的类型
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
ArrayList称为原始类型
参数化类型与原始类型的兼容性:
参数化类型可以引用一个原始类型的对象,编译报告警告,例如, Collection<String> c = new Vector();//可不可以,不就是编译器一句话的事吗?
原始类型可以引用一个参数化类型的对象,编译报告警告,例如, Collection c = new Vector<String>();//原来的方法接受一个集合参数,新的类型也要能传进去
参数化类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Object>(); //错误!///不写<Object>没错,写了就是明知故犯
Vector<Object> v = new Vector<String>(); //也错误!
编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句有错误:
Vector<Integer> vectorList[] = new Vector<Integer>[10];
下面的代码不会报错误
Vector v1 = new Vector<String>();
Vector<Object> v = v1;
获取父类泛型类型:Type getGenericSuperclass()
泛型类型:ParameterizedType
获取实际的泛型类型参数数组:getActualTypeArguments()
通过反射获得泛型的参数化类型
private static void applyGeneric(Vector<String> v){
}
-------------------------------------------------------
Method methodApply = GenericTest.class.getDeclaredMethod("applyGeneric", Vector.class);
Type[] paramTypes = methodApply.getGenericParameterTypes();
ParameterizedType param = (ParameterizedType)(paramTypes[0]);
System.out.println(param.getRawType());
System.out.println(param.getActualTypeArguments()[0]);
System.out.println(((Class)param.getRawType()).getName() + "<"
+ ((Class)param.getActualTypeArguments()[0]).getName()
+ ">");
类加载器:
系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader
BootStrap (JRE/lib/rt.jar)-->ExtClassLoader (JRE/lib/ext/ *.jar)-->AppClassLoade r(CLASSPATH指定的所有jar或目录)树状顺序关系
类加载器的委托机制
首先当前线程的类加载器去加载线程中的第一个类。
如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给其上级类加载器。
当所有父类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的子类,
因为没有getChild方法,即使有,那有多子类,无法确定用哪个子类去加载
自定义的类加载器:
自定义的类加载器的必须继承ClassLoader
loadClass方法与findClass方法需覆写findClass方法
defineClass方法获取
编程步骤:
编写一个对文件内容进行简单加密的程序。
编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。
编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。
程序中可以除了使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName。
实验步骤:
对不带包名的class文件进行加密,加密结果存放到另外一个目录,例如: java MyClassLoader MyTest.class F:\itcast
运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为AppClassLoader:java MyClassLoader MyTest F:\itcast
用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类装载器装载失败。
删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了。
动态代理:
JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
创建动态代理:Proxy 提供用于创建动态代理类和代理对象的静态方法, 它也是所有动态代理类的父类.
Proxy 提供了两个方法来创建动态代理类和动态代理实例
实例:
创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。
编码列出动态类中的所有构造方法和参数签名
编码列出动态类中的所有方法和参数签名
创建动态类的实例对象
用反射获得构造方法
编写一个最简单的InvocationHandler类
调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去
打印创建的对象和调用对象的没有返回值的方法和getClass方法
让jvm创建动态类及其实例对象,需要给它提供三个方面信息:
1,生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;
2,产生的类字节码必须有个一个关联的类加载器对象;
3,生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,
它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。
在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。
1. Class clazz = Proxy.getProxyClass(ProxyTest.class.getClassLoader(), Collection.class);
System.out.println(clazz.getName());
2. Method[] methods = clazz.getMethods();
for(Method m:methods)
{
System.out.println(m.getName());
}
System.out.println("---------------------");
3. Constructor[] contructors = clazz.getConstructors();
for(Constructor c:contructors)
{
System.out.print(c.getName());
StringBuilder sBuilder = new StringBuilder();
sBuilder.append('(');
Class[] paramTypes = c.getParameterTypes();
for(Class paramType:paramTypes)
{
sBuilder.append(paramType.getName());
sBuilder.append(',');
}
if(paramTypes.length>1)
sBuilder.deleteCharAt(sbf.length()-1);
sBuilder.append(')');
System.out.println(sBuilder.toString());
}
4. Collection proxy= (Collection)constructor.newInstance(new InvocationHandler(){
@Override
//这里的三个参数正好就是proxy.add(obj)中的三部分 。
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
});匿名法
实现Collection接口的动态类中的各个方法的代码又是怎样的呢?
InvocationHandler接口中定义的invoke方法接受的三个参数如下:
Client程序调用objProxy.add(“abc”)方法时,涉及三要素:objProxy对象、add方法、“abc”参数
Class Proxy$ {
add(Object object) {
return handler.invoke(Object proxy, Method method, Object[] args);
}
}