目录
1. 反射
1.1 基本介绍
1.2 作用
1.3 反射实现的类
1.4 获取类的字节码对象
1.5 反射机制的运用
1.6 反射的基本方法
1.7 反射效率慢的原因
2. 泛型
2.1 基本介绍
2.2 限定通配符和非限定通配符
2.3 类型擦除
3. 注解-Annontation
3.1 基本介绍
3.2 注解的用处
3.3 注解的原理
3.4 元注解
3.5 常见标准的Annotation
3.6 自定义注解使用的规则
1. 反射
1.1 基本介绍
反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意方法和属性,这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
Java 的反射机制是指在运行状态中:
对任意一个类,都能够知道这个类的所有属性和方法
对任意一个对象,都能调用这个对象的所有属性和方法
1.2 作用
- 检查类的属性和方法
- 在运行时检查对象的类型
- 动态构造某个类的对象
- 可以任意调用对象的方法
我们知道反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括包括其modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods。那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现等等;但是需要注意的是反射使用不当会造成很高的资源消耗!
1.3 反射实现的类
在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中。
- Class类:封装了描述方法的Method,描述字段的Field,描述构造器的Constructor等属性。对于每个类,JRE都会为其保留一个不变的Class类型的对象
- Field类:代表类的成员变量(成员变量也称为类的属性)
- Method类:代表类的方法
- Constructor类:代表类的构造方法
1.4 获取类的字节码对象
要想解剖一个类,必须要获取到该类的字节码文件对象。常用的获取Class对象的3中方式:
- 使用Class类的静态方法
- 使用类的.class方法
- 使用对象的getClass()
public class Test2 {public static void main(String[] args) throws ClassNotFoundException {// 第一种获取字节码方式Class<?> aClass = Class.forName("java.lang.String");System.out.println(aClass);// 第二种获取字节码方式Class bClass = String.class;System.out.println(bClass);// 第三种获取字节码方式String string = new String();Class<? extends String> cClass = string.getClass();System.out.println(cClass);}
}
1.5 反射机制的运用
1、在运行时判断任意一个对象所属的类
public class Test3 {public static void main(String[] args) throws ClassNotFoundException {Class<?> aClass = Class.forName("java.lang.String");String s = "aaa";Integer a = 11;boolean instance = aClass.isInstance(s); // trueboolean instance1 = aClass.isInstance(a); // falseSystem.out.println(instance);System.out.println(instance1);}
}
2、在运行时构造任意一个类的对象
public class Test4 {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {Class<?> aClass = Class.forName("java.lang.String");// 无参构造Object o = aClass.newInstance();System.out.println(o.hashCode());// 有参构造Constructor<?> constructor = aClass.getConstructor(String.class);Object o2 = constructor.newInstance("aaa");System.out.println(o2);}
}
3、在运行时访问类所具有的成员变量
public class Teacher {private String username;private Integer age;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}
}
public class Test5 {public static void main(String[] args) throws Exception {Class<?> aClass = Class.forName("com.xt.learn.day1.Teacher");Teacher teacher = (Teacher) aClass.newInstance();teacher.setUsername("小头");teacher.setAge(24);Field[] declaredFields = aClass.getDeclaredFields();for (Field declaredField : declaredFields) {// 获取private访问权declaredField.setAccessible(true);System.out.println(declaredField.getName() + ":" + declaredField.get(teacher));}}
}
4、在运行时调用对象所具有的方法
public class Teacher {private String eat() {return "111";}public String eat2() {return "222";}
}public class Test6 {public static void main(String[] args) throws Exception {Class aClass = Class.forName("com.xt.learn.day1.Teacher");Object teacher = aClass.newInstance();// 获取私有方法Method method = aClass.getDeclaredMethod("eat");// 获取私有方法需要加上method.setAccessible(true);Object invoke = method.invoke(teacher);System.out.println(invoke);}
}public class Test6 {public static void main(String[] args) throws Exception {Class aClass = Class.forName("com.xt.learn.day1.Teacher");Object teacher = aClass.newInstance();// 获取公有方法Method method = aClass.getMethod("eat2");Object invoke = method.invoke(teacher);System.out.println(invoke);}
}
1.6 反射的基本方法
- java.lang.reflect 包中的三个类
- Field:成员变量
- Method:成员方法
- Constructor:构造方法
- 对 public 域的方法
- getField
- getMethod
- getConstructor
- 对其他域的方法
包括 private 和 protected 的成员,但不包括父类成员。
- getDeclaredField
- getDeclaredMethod
- getDeclaredConstructor
利用反射访问私有属性:使用 setAccessible(true)
1.7 反射效率慢的原因
反射的确会导致性能问题;
反射导致的性能问题是否严重跟使用的次数有关系,如果控制在100次以内,基本上没什么差别,如果调用次数超过了100次,性能差异会很明显;
四种访问方式,直接访问实例的方式效率最高;其次是直接调用方法的方式,耗时约为直接调用实例的1.4倍;接着是通过反射访问实例的方式,耗时约为直接访问实例的3.75倍;最慢的是通过反射访问方法的方式,耗时约为直接访问实例的6.2倍
具体原因:
1.Method#invoke 方法会对参数做封装和解封操作
2.需要检查方法可见性
3.需要校验参数
4.反射方法难以内联
5.JIT 无法优化
2. 泛型
2.1 基本介绍
在集合中存储对象并在使用前进行类型转换是多么的不方便。泛型防止了那种情况的发生。 它提供了编译期的类型安全,确保你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException。
- 不使用泛型
public class Apple {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}
- 使用泛型
public class Apple<T> {private T t;public T getName() {return t;}public void setName(T t) {this.t = t;}
}
2.2 限定通配符和非限定通配符
-
限定通配符
限定通配符对类型进行了限制。有两种限定通配符:
一种是<? extends T>它通过确保类型必须是T及T的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是T及T的父类设定类型的下界。
泛型类型必须用限定的类型来进行初始化,否则会导致编译错误。 -
非限定通配符
表示了非限定通配符,因为可以用任意类型来替代。
2.3 类型擦除
Java 中的泛型是伪泛型。
类型擦除就是Java泛型只能用于在编译期间的静态类型检查,然后编译器生成的代码会擦除相应的类型信息, 这样到了运行期间实际上JVM根本不知道泛型所代表的具体类型。这样做的目的是因为Java泛型是1.5之后才被引入的,为了保持向下兼容。 对于这一点,如果阅读 Java 集合框架的源码,可以发现有些类其实并不支持泛型。
类型擦除带来的问题
- 在 Java 中不允许创建泛型数组
- Java 泛型很大程度上只能提供静态类型检查,然后类型的信息就会被擦除,所以利用类型参数创建实例的做法编译器不会通过。
- 无法对泛型代码直接使用 instanceof 关键字,因为 Java 编译器在生成代码的时候会擦除所有相关泛型的类型信息。
3. 注解-Annontation
3.1 基本介绍
Annontation 是 Java5 开始引入的新特征,中文名称叫注解。 注解是插入到代码中的一种注释或者说是一种元数据。这些注解信息可以在编译期使用编译工具进行处理,也可以在运行期使用 Java 反射机制进行处理。
3.2 注解的用处
- 生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等
- 跟踪代码依赖性,实现替代配置文件功能。如Spring中@Service @Mapper @Componet;
- 在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。
3.3 注解的原理
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。 我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1。 通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。 该方法会从memberValues这个Map中索引出对应的值。 而memberValues的来源是Java常量池。
3.4 元注解
java.lang.annotation提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
-
@Documented
一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中。 -
@Retention
定义该注解的生命周期。
(1)RetentionPolicy.SOURCE : 在编译阶段丢弃。 这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。 @Override, @SuppressWarnings都属于这类注解。
(2)RetentionPolicy.CLASS : 在类加载的时候丢弃。 在字节码文件的处理中有用。注解默认使用这种方式
(3)RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解, 因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。
-
@Target 表示该注解用于什么地方。 默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括:
-
@Inherited
定义该注释和子类的关系。 @Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。 如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
3.5 常见标准的Annotation
- Override (RetentionPolicy.SOURCE : 在编译阶段丢弃。属于@Retention)
Override是一个标记类型注解,它被用作标注方法。 它说明了被标注的方法重载了父类的方法,起到了断言的作用。 如果我们使用了这种注解在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示。
-
Deprecated
Deprecated也是一种标记类型注解。 当一个类型或者类型成员使用@Deprecated修饰的话,编译器将不鼓励使用这个被标注的程序元素。 所以使用这种修饰具有一定的“延续性”: 如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员, 虽然继承或者覆盖后的类型或者成员并不是被声明为@Deprecated,但编译器仍然要报警。 -
SuppressWarnings
SuppressWarning不是一个标记类型注解。 它有一个类型为String[]的成员,这个成员的值为被禁止的警告名。 对于javac编译器来讲,对-Xlint选项有效的警告名也同样对@SuppressWarings有效,同时编译器忽略掉无法识别的警告名。 @SuppressWarnings("unchecked")
3.6 自定义注解使用的规则
(1) Annotation型定义为@interface, 所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口
(2)参数成员只能用public或默认(default)这两个访问权修饰
(3)参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型 和String、Enum、Class、Annotations等数据类型,以及这一些类型的数组
(4)要获取类方法和字段的注解信息,必须通过Java的反射技术来获取Annotation对象, 因为除此之外没有别的获取注解对象的方法
(5)注解也可以没有定义成员, 不过这样注解就没啥用了
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface BookColor {/*** 颜色枚举*/public enum Color {绿色, 红色, 青色};/*** 颜色属性 (注意:这里的属性指的就是方法)*/Color bookColor() default Color.绿色;//默认是是绿色的
}public class Book {@BookColor(bookColor = BookColor.Color.红色)private String color;
}