反射

Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制,反射被视为动态语言的关键。

:字节码对象其实就是类加载到java虚拟机中的⼀种状态/另⼀种表现形式(因为jvm不能直接运⾏自己写的类,所以需要转换字节码Class)

类加载模型

Java反射的原理就是获取Class对象(类在运⾏时叫字节码对象java.lang.Class)然后使用java.lang.reflect里提供的方法操作Class对象,Classjava.lang.reflect构成了java的反射技术,反射代码的⼊⼝就是字节码对象,所以如何获取到字节码对象是关键。

  • 优点:

    ⾮常灵活,功能强⼤(可以获取私有的信息,破坏别⼈代码)

  • 缺点:

    破坏封装(单例),影响性能(反射的效率要⽐⾮反射操作要低)

反射的使用

字节码对象的获取⽅式

  1. 类名.class

  2. 对象名.getClass()

  3. Class.forName(全类名)

public class TestReflect {
	
	/**
	 * Constructor<?>[] getConstructors():返回所有公共构造方法对象的数组
	 */
	@Test
	public void testContructor1() throws Exception {
		//1.获取Student类的Class对象
		Class<?> clazz = Class.forName("reflect.constructor.Student");
		//2.获取clazz的所有的公有构造方法
		Constructor<?>[] constructors = clazz.getConstructors();
		//3.遍历打印所有的构造方法
		for (Constructor<?> constructor:constructors) {
			System.out.println(constructor);
		}
	}
	
	/**
	 * Constructor<?>[] getDeclaredConstructors():返回所有构造方法对象的数组(包括私有)
	 */
	@Test
	public void testContructor2() throws Exception {
		//1.获取Student类的Class对象
		Class<?> clazz = Class.forName("reflect.constructor.Student");
		//2.获取clazz的所有的公有构造方法
		Constructor<?>[] Constructors = clazz.getDeclaredConstructors();
		//3.遍历打印所有的构造方法
		for (Constructor<?> constructor:Constructors) {
			System.out.println(constructor);
		}
	}
	
	/**
	 * Constructor<T> getConstructor(Class<?>... parameterTypes):返回单个公共构造方法对象
	 */
	@Test
	public void testContructor3() throws Exception {
		//1.获取Class对象
		Class<?> clazz = Class.forName("reflect.constructor.Student");
		//2.获取公有的无参构造方法   --注意:小括号中,一定要跟构造方法的形参保持一致.
		Constructor<?> constructor = clazz.getConstructor(String.class,int.class);
		//3.获取公有的有参构造方法
		System.out.println(constructor);
	}
	
	/**
	 * Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):返回单个构造方法对象
	 */
	@Test
	public void testContructor4() throws Exception {
		//1.获取Class对象
		Class<?> clazz = Class.forName("reflect.constructor.Student");
		//2.获取私有的构造方法   --注意:小括号中,一定要跟构造方法的形参保持一致.
		Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);

	}
}

反射获取构造⽅法并使⽤

方法 描述
Constructor<?>[] getConstructors() 返回所有公共构造⽅法对象的数组
Constructor<?>[] getDeclaredConstructors() 返回所有构造⽅法对象的数组
Constructor getConstructor(Class<?>... parameterTypes) 返回单个公共构造⽅法对象
Constructor getDeclaredConstructor(Class<?>... parameterTypes) 返回单个构造⽅法对象
T newInstance(Object...initargs) 根据指定的构造⽅法创建对象
setAccessible(boolean flag) 设置为true,表示取消访问检查
public class TestReflect2 {
	
	/**
	 *  T newInstance(Object...initargs)   | 根据指定的构造方法创建对象
	 */
	@Test
	public void testNewInstance1() throws Exception {
		//1.获取class对象
		Class<?> clazz = Class.forName("reflect.constructor.Student");
		//2.获取构造方法对象-公共有参
		Constructor<?> constructor = clazz.getConstructor();
		//3.利用newInstance创建Student的对象
		Object o = constructor.newInstance();
		System.out.println(o);
	}
	
	/**
	 *  T newInstance(Object...initargs)   | 根据指定的构造方法创建对象
	 */
	@Test
	public void testNewInstance2() throws Exception {
		//1.获取Student类的Class对象
		Class<?> clazz = Class.forName("reflect.constructor.Student");
		//2.获取clazz的所有的公有构造方法-空参
		Constructor<?> constructor = clazz.getConstructor();
		//3.利用空参来创建Student的对象
		Student stu = (Student)constructor.newInstance();
		System.out.println(stu);
		//补充:在Class类中,有一个newInstance方法,可以利用空参直接创建一个对象
	}
	
	/**
	 * T newInstance(Object...initargs)   | 根据指定的构造方法创建对象
	 * setAccessible(boolean flag)        | 设置为true,表示取消访问检查
	 */
	@Test
	public void testNewInstance3() throws Exception {
		//获取一个私有的构造方法并创建对象
		//1.获取Class对象
		Class<?> clazz = Class.forName("reflect.constructor.Student");
		//2.获取一个私有化的构造方法.
		Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
		//3.被private修饰的成员,不能直接使用的,如果用反射强行获取并使用,需要临时取消访问检查
		constructor.setAccessible(true);
		//4.创建对象
		Student stu = (Student)constructor.newInstance("Alex");
		System.out.println(stu);
	}
}

反射获取成员变量并使⽤

方法 描述
Field[] getFields() 返回所有公共成员变量对象的数组
Field[] getDeclaredFields() 返回所有成员变量对象的数组
Field getField(String name) 返回单个公共成员变量对象
Field getDeclaredField(String name) 返回单个成员变量对象
void set(Object obj, Object value) 给成员变量赋值
Object get(Object obj) 给成员变量获取值

反射获取成员方法并使⽤

方法 描述
Method[] getMethods() 返回所有公共成员⽅法对象的数组,包括继承的
Method[] getDeclaredMethods() 返回所有成员⽅法对象的数组,不包括继承的
Method getMethod(String name, Class<?>...parameterTypes) 返回单个公共成员⽅法对象
Method getDeclaredMethod(String name,Class<?>... parameterTypes) 返回单个成员⽅法对象
Object invoke(Object obj, Object... args) 运⾏⽅法

注解

注解(Annotation),也叫元数据,标签,注释。 它是JDK1.5及以后版本引⼊的⼀个特性,与类、接⼝、枚举是在同⼀个层次。

可以声明在包、类、字段、⽅法、局部变量、⽅法参数等的前⾯,⽤来对这些元素进⾏说明,注释。

  • 注解与注释的区别

    注释://,/**/ 写给程序员看的;

    注解:@Override 写给程序看的;

  • 注解的作用:

    1. 编写⽂档:事实上,在Java中我们可以通过注解去⽣成API⽂档,例如我们常⻅的参数值 @parameter,返回值@return等,只有标识了这些,才能通过API⽂档更快速和有条理的查看到对应的相关信息。

    2. 分析代码:(单独使⽤)是主要作⽤,通过代码⾥标识的注解对代码进⾏分析使⽤反射

    (⽐如 @Data@Service@Controller等)极⼤的提⾼开发效率

    1. 编译检查:通过代码⾥标识的注解让编译器能够实现基本的编译检查,⽐如@Override@SuppressWarnings

注解的语法

@ + 注解的名称 – ⽐如@Override@SuppressWarnings等;

@Override:编译检查,⽤来检查标记的⽅法是否复写了⽗类的⽅法

  1. 只能⽤在⽅法上,写在其他上⾯会报错(编译器报错);
  2. 可以不写,但是不进⾏检查,如果是复写的⽅法,写错了也不报错;
  3. 写上注解之后,复写⽅法⽅法名字和参数列表就必须和⽗类的⽅法⼀致,如果写错,编译器会

帮你检查,会报错;

@SuppressWarnings:抑制/增压警告

  1. 可以写在代码中,写在⽅法上,写在类上⾯,三者作⽤域不⼀样;
  2. 可以不写,但是代码/包/⼯程上会出现⻩⾊警告
  3. 写上之后,代码/包/⼯程上不会出现⻩⾊警告

@Deprecated:标志当前⽅法或者类已经过时

  1. 可以使⽤在类中,⽅法中,⽤在⽅法中居多;
  2. 过时的⽅法有可能在⽤,所以不能删除,不能注释掉。只是建议以后不要使⽤;
  3. Java中也有已经过时的⽅法(Date对象的toLocaleString()⽅法)

@SafeVarargs:抑制堆污染

堆污染:当使⽤带泛型和可变参数的⽅法时,就会出现编译警告,可以使⽤@SafeVarargs抑制该

警告。@SafeVarargs是jdk1.7才出现的,之前使⽤@SuppressWarings解决

元注解

注解上⾯的注解,写在注解上⾯的,对注解进⾏说明的。 元注解在注解上才有作⽤,如果⾃定义注解,需要了解元注解;

分类

@Target:指定该注解的使⽤范围,没有指定什么地⽅都可以⽤;

  1. CONSTRUCTOR:⽤于描述构造器

  2. FIELD:⽤于描述字段

  3. LOCAL_VARIABLE:⽤于描述局部变量

  4. METHOD:⽤于描述⽅法

  5. PACKAGE:⽤于描述包

  6. PARAMETER:⽤于描述参数

  7. TYPE:⽤于描述类、接⼝(包括注解类型) 或enum声明

@Retention:指定⾃定义注解在代码中⽣效的⽣命周期;

  1. SOURCE:源码阶段,⼀旦编译成字节码⽂件就不存在了, 在字节码⽂件中该注解是不存在的 。

  2. Class:字节码阶段,⼀旦加载到JVM就不存在了。

  3. Runtime:运⾏时,在JVM执⾏时还存在。

@Documented:指定⾃定义注解在⽣成⽂档时,是否显示。

  1. 如果某个注解使⽤了@Deprecated元注解,那么这个注解会显示在⽂档中,否则不出现; 相反@Override注解上没有@Documented⽂档,那么所有使⽤@Override注解的⽅法上,在⽣成 ⽂档时,都看不⻅@Override注解 。

@Inherited:指定⾃定义注解是否被继承

  1. 例如⾃定义注解上有元注解@Inherited 假设⼀个注解在定义时,使⽤了@Inherited,然后该注解在⼀个类上使⽤,如果这个类有⼦类, 那么通过反射我们可以从类的⼦类上获取到同样的注解。

注解的三要素

第⼀要素:⾸先需要⼀个注解(其他⼈写的注解 or ⾃定义的注解)。写好的注解:不需要⾃⼰去实现,别⼈已经写好了功能,直接使⽤就是;⾃定义注解:需要⾃⼰写代码去实现功能;

第⼆要素:使⽤方式,可以在哪些地方使用

第三要素:功能实现