-------android培训、java培训、期待与您交流! ----------
基础加强(下)
泛型:
在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,
而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
规则和限制:
1、泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
2、同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
3、泛型的类型参数可以有多个。4、泛型的参数类型可以使用extends语句,例如<T extends superclass>。习惯上称为“有界类型”。5、泛型的参数类型还可以是通配符类型。例如Class<?> classType = Class.forName("java.lang.String");
泛型是提供给javac编译器使用的,右以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译吕编译带类型说明的集合时会去掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法返回的值和原始类型完全一样,由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个集合中添加其它类型的数据,例如: 用反射得到集合,再调用其add方法即可
ArrayList<E>类定义和ArrayList<Integer>类引用涉及如下术语:
1.整个称为ArrayList<E>泛型类型2.ArrayList<E>中的E称为类型变量或类型参数3.整个ArrayList<Integer>称为参数化类型4.ArrayList<Integer>中的Integer被称为类型参数的实例或实际类型参数5.ArrayList<Integer>中的<>念 typeOf6.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<Object> vectorList[] = new Vector<Integer>[10];
泛型中的?通配符:
使用?通配符可以引用其它各种参数化的类型,?通配符定义变量主要用作引用,可以调用于参数无关的方法,不能调用与参数有关的方法
错误方式:
public static void printClooection(Collection<Object> cols){
for (Object obj : cols)
{
System.out.println(obj);
}cols.add("String"); // 正确: 因为String是Object类型的子类cols = new HashSet<Date>(); //错误
}
正确方式 :
public static void printCollection(Collection<?> cols){cols.add("String"); // 错误: 因为它不知道自己未来匹配的就一定是Stringcols.size(); //正确: 此方法与类型参数没有关系}
泛型中的?通配符的扩展:
限定通配符的上边界:
正确: Vector<?> extends Number > x = new Vector<Integer>(); // 因为Integer是Number的子类
错误: Vector<?> extends Number > x = new Vector<String>(); //String不是Number的子类
限定通配符的下边界:
正确: Vector<?> super Integer > x = new Vector<Number>(); //因为Number是Integer的父类
错误: Vector<?> super Integer > x = new Vector<Byte>(); //因为Byte的Integer是同类
自定义泛型:
交换数组中的两个元素位置的泛型方法语法定义如下:
static <E> void swap(E[] a, int i, int j){E t = a[i]; a[i] = a[j]; a[j] = t;}
用于放置泛型的类型参数的尖括号就出现在方法的其它所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前,按照惯例,类型参数通常用单个大写字母表示
只有引用类型才能作为泛型方法的实际参数,swap(new int[3],3,5), 语句会报告编译错误,这是因为编译器不会对new int[3]中的int自动拆箱和装箱了,
因为new int[3]本身已经是对象,你想要的有可能就是int数组呢,它装箱岂不是弄巧成拙了
类型参数的类型推断:
编译吕判断泛型方法实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是非学复杂的过程
根据调用泛型方法时传递参数类型或返回值的类型来推断,具体规则如下:
当某个类型变量只在某个参数列表中的所有参数和返回值中一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值类型来决定泛型参数的类型,例如:
swap(new String[3],3,4) -> Static <E>void swap(E[] a, int i, int j);
当某个类形迹在整个参数数列表中的所有参数和返回中的多处被应用了,如果调用方法这多处实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来
例如: add(3,5) -> static <T> T add(T a, T b);
当某个类形迹在整个参数数列表中的所有参数和返回中的多处被应用了,如果调用方法这多处实际应用类型都对应到了不同的类型,且没有返回值 ,这时候取多个参数中的最大交集类型例如: 下面的语句实际对应的类型就是Number了,编译没问题,只是运行时出问题
fill(new Integer[3],3,5) -> static <T> void fill(T[] a, T v);
当某个类形迹在整个参数数列表中的所有参数和返回中的多处被应用了,如果调用方法这多处实际应用类型都对应到了不同的类型,并且使用了返回值,这时候优先考虑返回值类型,
例如: 下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比Eclipse报告的错误提示,接着再将变量x类型改为Number,则正确
int x = (3,3.5f) -> static <T> T add(T a , T b);
类加载器:
Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader
类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是不是java类,这正是BootStrap。
Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载
类加载器的委托机制:
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢:
每个类加载器加载类时,又先委托给其上级类加载器:
- 首先当前线程的类加载器去加载线程中的第一个类。
- 如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
- 还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
- 当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
- 对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包中后,运行结果为ExtClassLoader的原因
编写类加载器:
知识讲解:
- 自定义的类加载器的必须继承ClassLoader
- loadClass方法与findClass方法
- defineClass方法
编程步骤:
- 编写一个对文件内容进行简单加密的程序。
- 编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。
- 编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中可以除了使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName
实验步骤:
- 对不带包名的class文件进行加密,加密结果存放到另外一个目录,例如: java MyClassLoader MyTest.class F:\itcast
- 运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为AppClassLoader:java MyClassLoader MyTest F:\itcast
- 用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类装载器装载失败。
- 删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了
代理:
代理的概念与作用:
生活中的代理:
武汉人从武汉的代理商手中买联想电脑和直接跑到北京传智播客旁边来找联想总部买电脑,你觉得最终的主体业务目标有什么区别吗?基本上一样吧,都解决了核心问题,但是,一点区别都没有吗?从代理商那里买真的一点好处都没有吗
程序中的代理:
- 要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?
- 编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。 (参看下页的原理图)
- 如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易
代理架构图:
动态代理技术:
- 要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!
- JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
- JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
- CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库
- 代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
分析JVM动态生成的类:
- 创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。
- 编码列出动态类中的所有构造方法和参数签名
- 编码列出动态类中的所有方法和参数签名
创建动态类的实例对象:
- 用反射获得构造方法
- 编写一个最简单的InvocationHandler类
- 调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去
- 打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其他有返回值的方法报告了异常。
- 将创建动态类的实例对象的代理改成匿名内部类的形式编写,锻炼大家习惯匿名内部类
总结思考:让jvm创建动态类及其实例对象,需要给它提供哪些信息:
三个方面:
- 生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;
- 产生的类字节码必须有个一个关联的类加载器对象;
- 生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了
动态代理的工作原理图: