反射&注解

1.内容介绍

1. 反射 reflect【掌握语法】
2. 注解 annotation【掌握使用】

2.反射

2.1 什么是反射
  • I 建立知识共识
    1 要让Java程序能够运行,那么就得让Java类要被Java虚拟机加载;
    2 Java类如果不被Java虚拟机加载,是不能正常运行的;
    3 我们运行的所有的程序都是在编译期的时候就已经知道了你所需要的那个类已经被加载了;
  • II 相对官方解释
    1反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力;
    2在运行时期,动态地去获取类中的信息(类的信息,方法信息,构造器信息,字段等信息);
  • III 前面所学知识涉及到反射的地方
    1 获得运行时类型 Object的getClass方法
    Aclass A{} class B extends A{}
    BA a = new B();
    C在编译的时候想得到a的运行时类型
    2 getClass();
    3 线程同步监听对象之类的字节码对象例如 String.class
    4 获得默认无参构造方法
    Aclass A{}
    5 单例模式
    6 Eclipse的OutLine功能
    7 面向对象的思想看待反射
  • IV反射的作用
    1 增加程序的灵活性,避免将程序写死到代码里(解除硬编码的问题);
    2 Java的反射机制它知道类的基本结构,可以动态的去获取类这样的结构的结构;
    3 可以让程序员在不知道其它程序员会有什么类的时候就编写完成自己的代码;
    4 反射的优点:灵活,功能强大(可以拿到私有的…);
    5 反射的缺点:破坏封装,影响性能;
2.2 反射常用API

1.java.lang.Class
2.java.lang.reflect.Constructor
3.java.lang.reflect.Method
4.java.lang.reflect.Field
5…
6.注意: lang包下面的直接类型是自动导入的,但是lang包的子包中的类型需要我们自己导入的,到时候不要忘记了哦!

学习下面的知识之前定几个目标:

1.验证一个类中有默认无参数构造方法
2.获得任意一个类(或者接口)例如String 内部有哪些成员(字段,方法,构造方法。。。)
3.想办法去创建单例模式的类的多个对象
4.验证元注解@Inherited,加强对@Inherited的理解。

2.3 Class类与实例
2.3.1 Class类

简单说Class 类就是一个类而已,呵呵。。。

2.3.2 Class实例

1.其实就是一些类型的字节码对象
2.Class 类的实例表示正在运行的 Java 应用程序中的类和接口;
3.枚举是一种类,注释(指的是注解Annotation)是一种接口;
4.每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象;
5.基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象;
注意 :

1、Class类 和它的实例的产生: Class的实例是已经存在的类型,所以不能够直接new一个Class对象出来,而通过已知的类型和Class来获得
2、同一种类型不管通过什么方式得到Class的实例都是相等的;一个类型的字节码对象只有一份!
3、Class的实例就看成是Java中我们学过的所有的数据类型在JVM中存在的一种状态(字节码对象)
String.class int.class List.class int[].class

2.3.3 Class实例的获得方式

1.类

  1. Class 类中的静态方法 static Class<?> forName(String className) className是类或者接口的权限定名
  2. 类型.class 例如 String.class
  3. 对象名.getClass() Object中,所有的对象都可以调用
Class c1 = String.class;
Class c2 = "abc".getClass();
Class c3 = Class.forName("java.lang.String");
System.out.println(c1==c2);//true
System.out.println(c2==c3);//true

2.接口
1)static Class<?> forName(String className) className是类或者接口的权限定名
2)类型.class

Class clz1 = Class.forName("java.util.List");
Class clz2 = List.class;
Class clz3 = new ArrayList().getClass();
System.out.println(clz1==clz2);//true
System.out.println(clz2==clz3);//false

3.数组
每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
1.类型.class 例如 int[].class
2.对象名.getClass() Object中,所有的对象都可以调用

int[] arr1 = new int[10];
int[] arr2 = new int[12];
int[][] arr3 = new int[12][];
System.out.println(arr1.getClass() == arr2.getClass());
System.out.println(arr1.getClass() == int[].class);

4.基本数据类型+void
1.类型.class 例如 String.class
2.基本类型的包装类中都分别提供了一个字段保存其基本数据类型的对应的Class实例
3.int和Integer是否是同一个类型 ; 对应的Class实例是否一样

Class cla5 = int.class;
Class cla6 = Integer.class;
Class cla7 = Integer.TYPE;
System.out.println(cla5 == cla6);
System.out.println(cla5 == cla7);

Class cla8 = void.class;
Class cla9 = Void.class;
Class cla10 = Void.TYPE;
System.out.println(cla8==cla9);
System.out.println(cla8==cla10);
2.4 反射的运用

通过反射获得以下结构中的结构

Class  c  =  String.class;
2.4.1 获得构造方法

Class文档里获得构造方法的方法摘要有(建议直接看API文档):

2.4.2 获得构造器结构的方式
  1. Constructor getConstructor(Class<?>… parameterTypes)
    通过一个条件得到一个对应的构造方法对象
    条件就是构造方法中的形成的类型对应的Class实例
Class cla = Student.class;
Constructor con= cla.getConstructor(String.class);//获得指定参数的构造方法
System.out.println(con);
  1. Constructor<?>[] getConstructors()
    获得一个类中的所有的构造方法
Constructor[] constructors = cla.getConstructors();
for (Constructor con : constructors) {
	System.out.println(con);
}

注意 : 上面1 2 得到是公共的public修饰的;3 4 得到所有的,和访问权限无关
5 验证获得一个类中的默认无参数的构造方法(private修饰私有的构造方法)

Constructor declaredConstructor = cla.getDeclaredConstructor(double.class);
System.out.println(declaredConstructor);
2.4.2.1.创建对象的方式
class Person{
	public Person(){
	}
	private Person(String name){
	}
	public Person(String name,int age){
	}
	private Person(double score){
	}
}

1.直接调用类的构造方法 new XXX();
必须是有权限访问到对应的构造方法
在这里插入图片描述
2.Class类中有对应的方法

  1. T newInstance() 创建此 Class 对象所表示的类的一个新实例
  2. 如同用一个带有一个空参数列表的 new 表达式实例化该类。
  3. 注意:只能够调用无参数的构造方法,
  4. 无参数的构造方法必须是有权限被访问到的
//获取有public权限构造方法
Constructor constructor = cla.getConstructor();
//使用公共权限的构造方法创建对象
Object pson = constructor.newInstance("name");
System.out.println(pson);

3.获得构造器对象之后,通过构造器来创建对象

  1. 获得Person类对应的Class实例
  2. 通过Class实例得到指定的构造方法
  3. 通过得到的构造方法对象来创建Person的实例
  4. 使用到了Constructor中的方法:
  5. T newInstance(Object… initargs)
  6. initargs 创建对象向构造方法中的传入的给对象那个初始化值的数据
Constructor<Person> con = cla.getConstructor(String.class,int.class);
Person pson = con.newInstance("二狗",18);
System.out.println(pson);

4.构造器是私有的如何在外部创建对象?(单例模式外面还能创建对象吗?)

//获取私有化的构造方法[declared忽略权限]
Constructor declaredConstructor = cla.getDeclaredConstructor(double.class);
Object pson = declaredConstructor.newInstance(1.0);
System.out.println(pson);
java.lang.IllegalAccessException: Class cn.itsource._10ClassConstructorCreatObject.TestClassGetConstructor can not access a member of class cn.itsource._10ClassConstructorCreatObject.Student with modifiers "private"
	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
	at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
	at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:413)
	at cn.itsource._10ClassConstructorCreatObject.TestClassGetConstructor.main(TestClassGetConstructor.java:38)

如果一个类的构造方法是私有的,通过反射得到构造方法来创建对象也会报错!

2.4.2.2.让访问权限失效

AccessibleObject
|-- Constructor,
|-- Field
|-- Method

它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力
void setAccessible(boolean flag)
值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查

思考:
为什么私有的成员在外部类中就不能访问呢? Java规定的

//获取私有化的构造方法[declared忽略权限]
Constructor declaredConstructor = cla.getDeclaredConstructor(double.class);
//取消默认 Java 语言访问控制检查的能力
declaredConstructor.setAccessible(true);
Object pson = declaredConstructor.newInstance(1.0);
System.out.println(pson);

通过上面的设置,现在我们来直接调用 Person中有一个String 参数的构造方法能够创建对象了吗?
不能,通过上面的方式对Person来讲没有任何改变

2.4.3 获得方法
2.4.4 获得方法结构的方式
interface C{
	void t5();
}
public class B{
	private void tb1(){
		System.out.println("B t1");
	}
	public void tb2(int i){
		System.out.println("B t2");
	}
}
public abstract class A extends B implements C{
	private void t1(){
		System.out.println("A t1");
	}
	private void t2(int i){
		System.out.println("A t2");
	}
	private void t3(String s,int i){
		System.out.println("A 3");
	}
}

注意 : 下面的1 2 找公共的,包括了本类(接口)以及父类和实现的接口中的公共的方法
下面的3 4 只在本类(接口)中找,和访问权限没有关系
1.Method getMethod(String name, Class<?>… parameterTypes)。

Class<A> clza = A.class;
Method m1 = clza.getMethod("t2", int.class);//获得类中的公共的
System.out.println(m1);
		
Method m2 = clza.getMethod("tb2", int.class);//父类中的
		
Method m3 = clza.getMethod("t5");//接口中的System.out.println(m3);

2.Method[] getMethods()

Class<A> clza = A.class;
//获得Class对象得到所有的公共的方法
Method[] methods = clza.getMethods();
for (Method m : methods) {
	System.out.println(m);
}

3.Method getDeclaredMethod(String name, Class<?>… parameterTypes)
4.Method[] getDeclaredMethods()

2.4.4.1.通过反射执行方法

1.传统的调用一个方法

public class A extends B implements C{
	private void t1(){
		System.out.println("A t1");
	}
	pubic static void t2(int i){
		System.out.println("A t2");
	}
	private void t3(String s,int i){
		System.out.println("A 3");
	}
	@Override
	public void t5() {
	}
}

//测试类…

public class TestBalala {
	public static void main(String[] args){
		A a = new A();
		a.tb2(10);
	}
}

2.通过反射调用方法
1.获得A对应的Class实例;
2.通过反射得到A中的指定的方法 t3
3.通过反射(Method类)来调用t3 方法
Method中的方法:
Object invoke(Object obj, Object… args)

Class<A> clz = A.class;
Method m = clz.getMethod("t3", String.class,int.class);
A a = new A();
m.invoke(a, "小猪猪",10);

注意 : m 只是一个方法的对应对象;
a 是A的一个实例
m 对应的这个方法,应该由m对应的方法所在的类的对象(a)来调用
“小猪猪”,10调用方法的时候传入实际参数
如果是静态方法呢?
传入对象的地方传入一个null 就可以了

Class<A> clz = A.class;
Method m = clz.getMethod("t2",int.class);
A a = new A();
m.invoke(null,10);
2.4.4.2.验证调用私有的方法

1.得到 Class实例
2.通过1 得到指定的私有的方法 m
3.m. void setAccessible(true) ;//取消java权限检查
4.m.invoke(…);

2.4.5 获得字段
2.4.5.1.获得字段的方式

1.Field getField(String name)
2.Field[] getFields()
3.Field getDeclaredField(String name)
4.Field[] getDeclaredFields()

3.注解

3.1 什么是注解

注解(Annotation),也叫元数据,标签,注释。注解叫注释的时候,容易和java的代码注释混淆,一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

注解的另一个称呼注释,是给程序看的,大家可以将注解看成是一个标签,只不过这个标签有特殊含义,可能会执行特定的操作

3.2 为什么学习注解
  1. 注解是开发中的利器,可以使得代码更加的简洁,逻辑更加清爽
  2. 以后能用别人的注解,能够理解注解
  3. 经常会看别人代码,或者使用框架,都会就有很多注解,如果不同注解无法看别人代码和使用框架
  4. 自己想要成为架构师,【想要深入学习注解 】可以在别人的注解及三方程序上面学习,写自己的东西
  5. 可以让别人觉得你的技术还算不错,NB, 见过一些东西(自定义注解);
  6. 我们的目标:能理解注解,将来会用别人写好的注解
  7. 反射,注解,线程 --不管用不用,都是必须学的;
3.3 怎么使用注解

前面已经认识了一下什么是注解,其实注解使用很简单;
@注解的名称
@Override
注解可以贴在方法,类,字段…
不需要自己去完成代码,别人的注解,已经是别人完成的功能,直接使用就是;自己写的注解,需要自己写代码去完成,这个不是绝对的;

public class Student {
	@Override
	public String toString() {
		return super.toString();
	}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

@Override验证方法是否是在覆写:但是我们发现删除或者不删都可以覆写:但是如果覆写方法写错了就会提示错误:那么注解是如何做好验证的呢,注解内部也没有写任何的代码?
注解:由第三方程序提供支持,从而实现验证功能,例如:猪屁股上面合格的章,如果没有国家赋予意义,那么就相当于一个普通的标签而已。

3.4 注解的作用
  1. 编写文档:事实上,在Java中我们可以通过注解去生成API文档,例如我们常见的参数值【@parameter】,返回值【@return】等,只有标识了这些,我们才能通过API文档更快速和有条理的查看到对应的相关信息
    导出含有注解的API文档:
     1.选中项目/代码,右键选中Export
     2.Java下的Javadoc命令
     3.Javadoc Generation中配置javadoc.exe【javadoc.exe在JDK环境中】
     4.不做任何修改next
     5.如果是UTF-8编码,且有中文,请输入-encoding UTF-8 -charset UTF-8 Finish
  2. 代码分析:通过代码里标识的元数据对代码进行分析【使用反射】(可以通过反射 看这个类有没有注解,有注解的时候,代码应该怎么写,没有注解的时候,应该怎么写)
  3. 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】(如果一个代码,添加一个注解,会帮我们检查是不是正确;)
3.5 注解的三要素

1.需要有一个注解;
2.使用这个注解(这个注解贴在什么地方);
3.第三方程序为注解实现功能;

比如Override,是Sun公司提供的,首先得有这么一个注解,然后会贴到需要贴的地方,
最后有第三方法程序为注解实现功能;

3.6 JDK四大内置注解

自此处可以去参看源码
@Override注解用于标识方法重写,例如当前类中重写tostring()方法
@Deprecated注解用于标识方法已过时
@SuppressWarings注解用于抑制编译器警告,直白的讲就是消除Eclipse上那条黄色警告线,但编译器里关于这条警告的信息还在。
代码示例

List list=null;

@SafeVarargs抑制堆污染警告(单独警告),直白的讲就是使用泛型和可变参数搭配使用将会产生的编译器警告,可以使用@SuppressWarnings消除警告,后来在Java7引入了新的注解,就是@SafeVarargs,如果开发人员确信某个使用了可变参数的方法,在与泛型类一起使用时不会出现类型安全问题,就可以用这个注解进行声明,这样编译器就不会报警告。
示例代码

public static <T> T useVarargs(T... args) {  
	return args.length > 0 ? args[0] : null;  
}

思考&讨论:
为什么有的只能贴在方法上,有的标签可以贴在类在,方法,字段上?
为什么有的标签可以加参数,有的标签不能加参数?

3.7 JDK的元注解
3.7.1 元注解的引入

我们刚在使用@Override 和 @SuppressWarings 他们在使用的时候有明显的区别。为什么override就只能贴到方法上面,而@SuppressWarings可以贴到类,方法上面。要知道这个,就需要了解JDK的元注解

3.7.2 什么是元注解

元注解的作用就是用于定义其他注解,是由JAVA为我们提供的,不能更改,从JAVA5.0开始一共为我们提供了四个元注解。

3.7.3 常用的元注解
3.7.3.1.@Target

@Target作用
用于描述注解的使用范围,也就是说使用了@Target去定义一个注解,那么可以决定定义好的注解能用在什么地方

@Target取值
@Target的取值使用ElementType,ElementType的相关说明可以通过JDK文档查询,也可以直接查看其源码

  1. CONSTRUCTOR:用于描述构造器
  2. FIELD:用于描述域
  3. LOCAL_VARIABLE:用于描述局部变量
  4. METHOD:用于描述方法
  5. PACKAGE:用于描述包
  6. PARAMETER:用于描述参数
  7. TYPE:用于描述类、接口(包括注解类型) 或enum声明
3.7.3.2.@Retention

@Retention作用
用于描述注解的生命周期,也就是说这个注解在什么范围内有效,注解的生命周期和三个阶段有关:源代码阶段、CLASS文件中有效、运行时有效,故其取值也就三个值,分别代表着三个阶段

@Retention取值
@Retention的取值适用RetentionPoicy,RetentionPoicy的相关说明可以通过JDK文档查询,也可以通过查看源码

  1. SOURCE:在源文件中有效(即源文件保留)
  2. CLASS:在class文件中有效(即class保留)
  3. RUNTIME:在运行时有效(即运行时保留)
    示例代码
3.7.3.3.@Documented

@Documented作用
使用@Documented定义后的注解,在该注解使用后,如果导出API文档,会将该注解相关的信息可以被例如javadoc此类的工具文档化。
Documented是一个标记注解,没有成员。

3.7.3.4.@Inherited

@Inherited作用
使用@Inherited定义的注解具备继承性
假设一个注解在定义时,使用了@Inherited,然后该注解在一个类上使用,如果这个类有子类,那么通过反射我们可以从类的子类上获取到同样的注解

3.8 自定义注解

语法

元注解
public @interface 注解名{
	类型 属性名()  default 默认值; //default可以不写
}

注解类型是和类、接口等一个级别的,要定义一个注解我们需要使用@interface关键字,同时需要注意定义的注解名前是不需要使用@符号的,但是在使用的时候,必须是@符号加上注解名,而且Java命名规范也不允许这样做
使用注解

@注解名(属性名=”值”)
@注解名(属性名={”值1,”值2})  //注解类型为数组

特别说明:
1.自定义注解:学习语法,验证元注解,参数的定义问题
2.重点,一个注解想要有意义,必须有第三方程序赋予注解意义

4.课程总结

4.1 重点
  1. 获取Class的实例
  2. 获取构造方法创建对象
  3. 获取方法并调用
  4. 获取字段并赋值
4.2 难点

1、很多人今天想完全理解反射这个概念(感觉说不清楚)

5.常见面试题

1、说说反射的原理
2、说说反射的应用场景
3、创建对象的方式有哪些

6.课后练习

1、熟悉Class实例的获得方式
2、通过反射获得指定类中的构造方法、方法、字段的方法有哪些并写出测试代码
3、通过反射来调用方法(私有的和非私有的都要测试)
4、通过反射来操作字段(私有的和非私有的都要测试)
5、通过反射验证元注解@Inherited

7.扩展知识或课外阅读推荐

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐