您的位置:首页 > 职场人生

黑马程序员-张孝祥Java基础加强(PART2)

2014-03-02 12:52 501 查看
----------------------
ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------

知识点:

1.泛型

2.类加载器

3.代理

一、泛型

泛型也是jdk1.5的新特性

1.体验泛型

在jdk1.5之前向集合类中添加元素是这样的:

jdk1.5希望在定义集合时,明确表示要向集合中装那种类型的数据,不能加入指定类型以外的数据

泛型是提供给javac编译器使用的,可以限定集合中元素的类型,让编译器挡住源程序中的非法输入。编译器编译带类型说明的集合时会去除掉”类型“信息,使程序运行效率不受影响。对于参数化的类型,getClass()方法的返回值和原始类型完全相同。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其他类型的数据。如:

2.了解泛型

(1)一些术语,拿ArrayList<E>和ArrayList<Integer>举例

整个ArrayList<E>称为泛型类型,其中E称为类型变量或类型参数。整个ArrayList<Integer>称为参数化的类型,其中Integer称为类型参数的实例或实际参数类型。<>念做typeof。ArrayList称为原始类型。

(2)参数化类型和原始类型的兼容性

参数化类型可以引用一个原始类型的对象,编译报告警告,如Collection<String> c = new Vector();

原始类型可以引用一个参数化类型的对象,编译报告警告,如Collection c = new Vector<String>();

但下面的语句不会报错

Vector v = new Vector<String>();

Vector<Object> v1 = v;

(3)参数化类型不考虑类型参数的继承关系,下面两种写法都是错误的

Vector<String> v = new Vector<Object>();

Vector<Object> v = new Vector<String>();

(4)编译器不允许创建泛型变量的数组,即在创建数组实例时,数组的元素不能使用参数化的类型。例如,下面是错误的

Vector<Integer>[] vList = new Vector<Integer>[10];

3.泛型中的?通配符

如果想要定义一个方法,用于打印任意参数化类型的集合中的所有数据,该怎么定义呢?

错误方式:

正确方式:

总结:使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。

4.泛型中?通配符的扩展

限定通配符的上边界:

正确:Vector<? extends Number> v = new Vector<Integer>();

错误:Vector<? extends Number> v = new Vector<String>();

限定通配符的下边界:

正确:Vector<? super Integer> v = new Vector<Number>();

错误:Vector<? super Integer> v = new Vector<Byte>();

注意:

限定通配符总是包括自己;

?只能做引用,不能用它去给其他变量赋值

5.泛型集合类的综合案例

6.定义泛型方法

用于表示泛型的类型参数应该出现在方法的其他所有修饰符之后和返回类型之前,按照惯例,类型参数用单个大写字母表示。

交换数组中的两个元素位置的泛型方法语法定义入下:

只有引用类型才能作为泛型方法的实际参数,swap(new int[],3,5);会报告编译错误。

除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用,并且可以用&来指定多个边界,如<V extends Serializable&Clonable>。

普通方法、构造方法和静态方法中都可以使用泛型。

也可以使用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但不能用于catch子句中。如:

在泛型中可以同时有多个类型参数,在定义他们的<>中用逗号分隔,如:

public static <K,V> V getValue(K key) { return map.get(key);}

7.几个泛型方法示例

(1)自动将Object类型的对象转换成其他类型的泛型方法

(2)将任意类型的数组中的所有元素填充为相应类型的某个对象

(3)打印出任意参数化类型的集合中的所有内容

在这种情况下,前面的通配符方案要比泛型更加有效。当一个类型变量用来表达两个参数之间活着参数和返回值之间的关系时,即同一个类型变量在方法签名的两处被使用,或者类型变量在方法体代码中也被使用而不是仅在签名的时候用,才需要使用泛型方法。

(4)把任意参数类型的集合中的数据安全的复制到相应类型的数组中

(5)把任意参数类型的一个数组中的数组安全的复制到相应类型的另一个数组中

8.类型参数的类型推断

编译器判断泛型方法的实际类型参数的过程称为类型推断。

根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:

(1)当某个类型变量只在所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型。如:

swap(new String[3],1,2)-->static <E> void swap(E[],int i,int j),可以推出E为String

(2)当某个类型变量在所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都是同一种类型,很容易推出来。如:

add(3,5)-->static <T> add(T a,T b),可以推出T为Integer

(3)当某个类型变量在所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这个时候取多个参数的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没有问题,只是运行时出问题

fill(new Integer[3],3.5f)-->static <T> void fill(T[],T t)

(4)当某个类型变量在所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且使用返回值,这时优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x的类型改为Number,则没了错误

int x = add(3,3.5f)-->static <T> T add(T a,T b)

(5)参数类型的类型推断具有传递性,下面第一种情况判断实际类型参数为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:

copy(new Integer[5],new String[5])-->static <T> void copy(T[] a,T[] b);

copy(new Vector<String>(),new Integer[3])-->static <T> void copy(Collection<T> a,T[] b);

9.定义泛型类型

如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持一致时,就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:

类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如下面两种方式都可以:

GenericDao<String> dao = null;

new GenericDao<String>();

注意:

在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型;

当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。

10.通过反射获得泛型的参数化类型

二、类加载器
1.了解类加载器
类加载器是负责加载类的对象。
Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,BootStrap、ExtClassLoad、AppClassLoader,每个类负责加载特定位置的类。

类加载器也是Java类,因为其他是Java类的类加载器本身也要被类加载器加载,显然必须有一个不是Java类的类加载器,这就是BootStrap。

Java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其之指定一个父级类加载器对象或默认采用系统类加载器为其父级类加载器。



上面代码的输出结果为sun.misc.Launcher$AppClassLoader,sun.misc.Launcher$ExtClassLoader,null。最后的加载器其实是BootStrap,但因为它不是Java类,所以为空。

如果用eclipse的打包工具将ClassLoaderTest输出到jre/lib/ext目录下的itheima.jar包,再运行这个类时,输出结果为sun.misc.Launcher$ExtClassLoader,null。这是为什么呢?这时classpath目录下有ClassLoaderTest.class,jre/lib/ext/itheima.jar中也有ClassLoaderTest.class,我们需要了解类加载的具体过程和原理。

2.类加载器的委托机制

当Java虚拟机要加载一个类时,到底派哪个类加载器去加载呢?首先用当前线程的类加载器去加载线程中的第一个类;如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器去加载类B;也可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类加载器去加载类,这就是类加载器的委托机制。类加载器一级级委托到BootStrap类加载器,如果BootStrap在它对应的目录下找不到要加载的类,就一级级回退到子孙类加载器去找要加载的类。当回退到最初的类加载器还无法找到时,就抛出ClassNotFoundException。这就说明了上面的问题。

3.编写自己的类加载器

自定义的类加载器必须继承ClassLoader,并覆写findClass方法。该类加载器还实现简单的加密类功能。参考文档中的示例代码。

先创建一个被操作的类ClassLoaderAttachment

下面是自定义的类加载器,MyClassLoader

原文件路径即arg[0]为ClassLoaderAttachment.class的绝对路径,目标目录即arg[1]为工程根目录下的itheimalibs目录,运行MyClassLoader之后在该目录下会有一个加密后的ClassLoaderAttachment.class文件。用加密后的文件去覆盖原文件,在ClassLoaderTest中加载ClassLoaderAttachment

在ClassLoaderTest中运行以上代码,会出现“java.lang.ClassFormatError: Incompatible magic value xxx”的错误,这是因为加载类的时候会先去父加载器中寻找,在classpath下有CalssLoaderAttachment.class,但是加过密的,父加载器无法解析,就出现了上面错误。把classpath下的CalssLoaderAttachment.class删掉,最后就会通过MyClassLoader加载itheimalibs目录下的class文件,并输出结果“hello,beijing”。

4.一个类加载器的高级问题分析
编写一个能打印自己的类加载器名称和当前类加载器的父子结构关系链的MyServlet,正常发布后,看到打印结果为WebAppClassloader。

把MyServlet.class打包成jar包,放到jre/lib/ext目录下,重启tomcat,发现有找不到HttpServlet的错误。把servlet-api.jar也放到ext目录下,问题解决了,打印结果为ExtClassLoader。这是因为父级类加载器加载的类无法引用只能被子类加载器加载的类。原理图如下:



三、代理

1.代理的概念和作用

生活中的代理:从代理商手里买电脑和直接从厂家买电脑,主体义务目标是相同的。

程序中的代理:

为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,如异常处理、日志、计算方法的运行时间、事物管理等等,编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。



2.AOP和动态代理技术

AOP就是面向方面的编程,AOP的目标就是要使交叉业务模块化。使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。

要为系统中的各种接口的类增加代理功能,那将需要太多的代理类。JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。

CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。

代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在理方法中的如下四个位置加上系统功能代码:

(1)调用目标方法之前

(2)调用目标方法之后

(3)调用目标方法的前后

(4)在处理目标方法异常的catch块中

3.分析JVM动态生成的类

动态代理的工作原理图



4.实现AOP功能的封装与配置

(1)工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件进行切换。其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则返回该实例对象的getProxy方法返回的对象。

(2)BeanFacotry的构造方法接收代表配置文件的输入流对象,配置文件格式如下:

(3)ProxyFacotryBean充当封装生成动态代理的工厂,需要为工厂类提供目标和通知(Advice)两个参数。

(4)编写客户端应用,编写实现Advice接口的类和在配置文件中进行配置,调用BeanFactory获取对象。

----------------------
ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------

详细请查看:http://edu.csdn.net
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: