主要内容:
- 泛型
- 注解
- 动态代理
- 类加载器
一、泛型
1.1 泛型(Generic)的作用
- jdk5以前,对象保存到集合中就会时区其特性,取出时通常要程序员手工进行类型的强制转换,这样不可避免就会引发程序的一些安全性问题。
- jdk5中的泛型允许程序员在编写结合代码时,就限制集合的处理类型,从而把原来程序运行时可能发生的问题,转变为编译时的问题,以此提高程序的可读性和稳定性。
注意:泛型是提供给javac编译器使用的,它用于限定集合的输出类型,让编译器在源代码级别上挡住向集合中插入非法数据。但编译器编译完带有泛型的java程序后,生成的class文件中将不再带有泛型信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。
- 泛型的基本术语,以为例,念
•中的E称为类型参数变量
•中的Integer称为实际类型参数
•整个称为泛型类型
•整个称为参数化的类型
1.2 泛型的典型应用
- 使用迭代器迭代泛型集合中的元素
- 使用增强for循环迭代泛型集合中的元素
- 存取HashMap中的元素
- 使用泛型时的几个常见问题:
1.使用泛型时,泛型类型必须为引用类型,不能是基本数据类型
2.
3.
4.
5.
1.3 自定义泛型-泛型方法
1.3.1 回顾(工程)
说明:
- 1.一般我们定义泛型方法如上,如果我们先想使用泛型参数,那么java基础增强我们需要先定义再使用,即在方法中使用来定义泛型。
- 2.当多个方法都需要使用相同的泛型时,我们可以将泛型定义在类上,这样方法中就不需要再次定义了:
- 3.注意:只有对象类型才能作为泛型方法的实际参数类型,基本数据类型则不能。
1.3.2 一个小题目:编写一个泛型方法,接收一个任意数组,并颠倒数组中的所有元素
1.4 自定义泛型-泛型类和反射泛型
- 泛型的典型应用:BaseDao和反射泛型
说明:
- 1.以后我们的dao层实现中对于增删改查有很多类似的代码,这里我们希望只需要定义一个dao层实现,然后其他需要相关方法的dao层实现只需要继承此公共dao层即可,如。
- 2.当然这里我们要用到hibernate,而find方法中我们需要相关类的类型,我们可以使用构造函数传递进来,但是这种方式在继承的时候还需要显式调用构造方法,比较麻烦。还可以使用反射的方法进行传递:
此时我们只需要继承即可,无需再显式调用构造方法。此方法中第一步是拿到具体dao层的类型,然后通过此类型拿到具体的实体类型。
- 3.在实际开发中,我们一般是通过方法参数将具体的类的类型传递进来:
1.5泛型的高级应用-通配符
注意:记住,通配符主要用于引用对象,使用了通配符之后就只能调用对象与类型无关的方法,不能调用对象与类型有关的方法(不管什么情况下都是这样)。比如上面的save方法中我们就不能使用add这种与类型相关的方法,但是size方法与类型无关的方法。
1.6泛型的高级应用-有限制的通配符
- 限定通配符的上边界
正确:
错误: - 限定通配符的下边界
正确:
错误: - 问题:以下代码行不行?
答:显然是不行的,因为使用了通配符,所以不能使用和类型相关的方法。
二、Annotation(注解)
2.1概述
- 从jdk5开始,java增加了对元数据(MetaData)的支持,也就是Annotation(注解)。
- 什么是Annotation,以及注解的作用?三个基本的Annotation:
@Override: 限定重写父类方法, 该注解只能用于方法
@Deprecated: 用于表示某个程序元素(类, 方法等)已过时
@SuppressWarnings: 抑制编译器警告. - Annotation其实就是代码里的特殊标记,它用于替代配置文件,也就是说,传统方式通过配置文件告诉类如何运行,有了注解技术后,开发人员可以通过注解告诉类如何运行。在java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类。这个技术一般在框架中应用的比较多。
- 例1:
在servlet3.0中引入了注解,我们可以不使用xml文件进行配置而直接使用注解即可。
- 例2:
注意:注解只接收基本数据类型、String、Class、Annotation、枚举类型和以上所属类型的一维数组类型。
2.2 自定义Annotation
- 定义新的Annotation类型使用关键字
- 声明注解的属性
- 注解属性的作用:原来写在配置文件中的信息,可以通过注解的属性进行描述。
- Annotation的属性声明方式:
- 属性默认值的申明方式:
- 特殊属性value:如果注解中有一个名称value的属性,那么使用注解时可以省略value=部分,如
- 特殊属性
- 例1:
我们在使用时就可以这样:
使用时可以这样:
但是如果此注解中还有其他的字段或是属性名不是value,则不能省略”value=”。注意如果是数组则不能少了花括号。
注意:我们在使用自定义注解时,如果自定义注解中没有给出默认值则必须在使用时给出相关字段的值。
2.3 反射注解
我们直接通过相关例子进行说明:
这里我们首先定义了两个注解。下面我们使用这两个注解之后再反射相关方法上的注解。
其中
说明:
- 1.对于DbInfo注解类中的相关注解我们在后面会详细说明,这些都是一些元注解信息,用来修饰注解。
- 2.在类中我们使用反射对相关方法上的注解进行反射,之后可以得到方法上的一些注解信息。但是注意:我们知道java类有3种状态,一种是源代码,还有class和在jvm中,如果我们将注解的作用域修饰为前两种(默认为class),那么反射是不会取到任何信息的,因为如果作用域为java源代码级别,那么编译的时候就会抛弃注解,如果级别为class,那么在运行时也会抛弃注解,此时反射不到任何信息。
- 3.在开发中我们还经常使用注解将一个类通过容器注入到某个方法中,如上面的aa方法,其实也是通过反射实现的。
2.4 JDK的元注解
2.4.1 元注解
元Annotation指修饰Annotation的Annotation。Jdk中定义了如下元Annotation:
- 只能用于修饰一个Annotation的定义,用于指定该Annotation可以保留的域,包含一个类型的成员变量,通过这个变量指定域。
- 编译器将把注解记录在 class 文件中. 当运行 Java 程序时, JVM 不会保留注解. 这是默认值
- 编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 会保留注解. 程序可以通过反射获取该注释。这个值用的最多,一定不要忘记。
- 编译器直接丢弃这种策略的注释
我们可以看到在中就使用了这个注解。我们在反射一个注解时如果使用的不是,那么我们是拿不到相关的注解信息的。
- 指定注解用于修饰类的哪个成员。包含了一个名为value,类型为的成员变量。
- 用于指定被该元Annotation修饰的Annotation类将被工具提取成文档。
- 被它修饰的Annotation将具有继承性。如果某个类使用了被修饰的Annotation,则其子类将自动具有该Annotation具有的注解。在中就使用了这个注解。
2.4.2 模拟注解的实现
通过注解注入一个对象,前面例子中已经使用过,现在我们模拟其实现。而这一实现有两种方式:
首先我们看看使用注解的相关类
方式一:
说明:以上就是模拟容器通过注解注入的过程,基本过程就是先得到要注入的属性,这里是一个类,然后得到属性的写方法(即中),然后反射出其注解上的值,再使用这些值实例化相关对象,然后使用invoke方法将实例化的对象设置到上。当然有时候注解是配置在属性上而不是在方法上,那么下面我们看这种情况是如何实现的。
方式二:
说明:相关实现过程基本一样。
三、动态代理
3.1基本概念
其实前面我们已经讲过了,这里再详细说明一下。
- java提供了一个proxy类,调用它的方法可以生成某个对象的代理对象,使用该方法生成代理对象时,需要三个参数:
- 1.生成代理对象使用哪个类装载器
- 2.生成哪个对象的代理对象,通过接口指定
- 3.生成的代理对象的方法里是干什么事情的,由开发人员进行编写handler接口的实现来指定。
- 初学者必须理解并记住:
- 1.proxy类负责创建代理对象时,如果指定了handler(处理器),那么不管用户调用代理对象的什么方法,该方法都是调用处理器的invoke方法。
- 2.由于invoke方法被调用需要三个参数:代理对象、方法、方法的参数,因此不管代理对象哪个方法调用处理器的invoke方法,都必须把自己所在的对象、自己(调用invoke方法的方法)、方法的参数传递进来。
下面看一个简单的例子:
测试:
3.2 应用
- 在动态代理技术里,由于不管用户调用代理对象的什么方法,都是调用开发人员编写的处理器的invoke方法。
- 并且,开发人员通过invoke方法的参数,还可以在拦截的同时,知道用户调用的是什么方法,因此利用这个两个特性,就可以实现一些特殊的需求。例如:拦截用户的访问请求,以检查用户是否有访问权限、动态为某个对象添加额外的功能。同时,这种拦截手段较之前的拦截方式更为精细,因为它是在方法级别上的拦截。下面看几个例子(这些例子我们在过滤器中已经讲过,这里当作复习):
例1:对整个web的乱码过滤器
例2:压缩过滤器
注意:要产生某个类的动态代理对象,那此类必须有一个接口,这也叫aop编程;如果一个类没有接口,那一般的方式是不能实现动态代理的,但是我们使用开源框架则可以实现动态代理。但是如果一个类是final类型,则这种方式也不能完成动态代理,因为其是通过产生一个需要代理对象的子类来实现动态代理的,所以如果是final类型的,显然是不能实现的。那此时我们只能使用静态代理(包装设计模式)。
四、类加载器
- 类加载器负责将.class文件(可能在磁盘上,也可能在网络上)加载到内存中,并为之生成对应的对象。
- 当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构:
其中BootStrap是对JDK中的核心类进行加载的加载器,是扩展包中的类进行加载的加载器,而是对我们自己写的类进行加载的加载器。
4.1 BootStrap
- 是使用c语言实现的。引导(原始)类加载器,它负责加载java的核心类。这个加载器是非常特殊的,它实际上不是的子类,而是由JVM自身实现的。可以通过执行以下代码来获得此加载器加载了哪些核心类库:
- 因为JVM在启动的时候就自动加载它们,所以不需要在系统属性中指定这些类库。
4.2 ExtClassLoader
- 扩展类加载器,它负责加载JRE的扩展目录(或者由系统属性指定的)中的jar包。这为引入除java核心类以外的新功能提供了一个标准机制。因为默认的扩展目录对所有从同一个jre中启动的JVM都是通用的,所以放入这个目录的jar类包对所有的JVM和都是可见的。
4.3 AppClassLoader
- 系统(也称为应用)类加载器,它负责在JVM被启动时,加载来自在命令java中的或者系统属性或者操作系统属性所指定的jar类包和类路径。
- 可以通过静态方法
找到该类的加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它父加载器。
4.4 全盘负责委托机制
- 加载类用到是全盘负责委托机制。
- 全盘负责:即是当一个加载一个class的时候,这个class所依赖的和引用的其他class通常也由这个负责载入。
- 委托机制:先让父类加载器进行加载,只有在父类找不到的时候才从自己的类路径下寻找。
- 类加载还采用了cache机制:如果cache中保存了这个class就直接返回它,如果没有才从文件中读取和转换成class,并存入cache,这就是为什么修改了class但是必须重新启动JVM才能生效,并且类只加载一次的原因。
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.bianchenghao6.com/h6javajc/532.html