目录

反射机制


反射机制

1 为什么要用反射?

1.1 因为Java是静态的强类型语言,在编译阶段就需要确定类型

  • Java为了实现“动态性“特征,引入了反射机制

    • 变量可以使用Object声明,然后在运行时确定某个对象的运行时类型

    • 或者在运行时动态的”注入“某个类型的对象,动态的创建某个类型的对象

      • 例如:用这个类型的Class对象,然后创建它的实例
    • 。。。。

1.2 例如:JS等是动态的弱类型的语言,在运行时确定变量的类型,根据赋的值确定变量的类型

2 反射的根源

2.1 java.lang.Class

  • Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。

    • 示例代码

      • @Test public void test() { Class c1 = int.class; Class c2 = void.class; Class c3 = String.class; Class c4 = Comparable.class; Class c5 = ElementType.class; Class c6 = Override.class; Class c7 = int[].class;

      int[] arr1 = new int[5]; int[] arr2 = new int[10];

      System.out.println(arr1.getClass() == arr2.getClass()); System.out.println(int[].class == arr2.getClass());

      int[][] arr3 = new int[5][10]; System.out.println(arr1.getClass()); System.out.println(arr3.getClass()); }

2.2 四种获取Class对象的方式

  • (1)如果类型已知:

    • 类型名.class
  • (2)如果对象存在

    • 对象.getClass()
  • (3)如果在编译阶段未知,但是运行阶段可以获取它的类型全名称

    • Class.forName(“类型全名称”)
  • (4)如果在编译阶段未知,但是运行阶段可以获取它的类型全名称

    • 类加载对象.loadClass(“类型全名称”)

3 相关的API(了解)

3.1 java.lang.Class

  • 方法

    • (1)获取类型名:

      • getName()
    • (2)创建实例对象

      • newInstance()

        • 这个类型必须有无参构造
        • Class对象.newInstance()
    • (3)获取包的信息

      • getPackage()
    • (4)获取父类

      • Class getSuperClass()

        • 不带泛型
      • Type getGenericSuperClass()

        • 可以带泛型
    • (5)获取父接口

      • Class[] getInterfaces()

        • 不带泛型
      • Type[] getGenericInterfaces()

        • 可以带泛型
    • (6)获取该类型的属性

      • 获取全部可访问的公共的属性

        • Field[] getFields()
      • 获取全部已声明的属性

        • Field[] getDeclaredFields()
      • 获取某一个公共的属性

        • Field getField(“属性名”)
      • 获取某一个声明过的属性,可能是私有的等

        • Field getDeclaredField(“属性名”)
        • 通过属性名就可以唯一确定一个属性
    • (7)获取该类的构造器

      • 获取全部的公共的构造器

      • 获取全部已声明的构造器

      • 获取某一个公共的构造器

      • 获取某一个已声明的构造器

        • Constructor getDeclaredConstructor(形参列表的类型Class列表… )
        • 通过构造器的形参列表就可以唯一确定一个构造器
    • (8)获取该类的方法

      • 获取全部的公共的方法

      • 获取全部已声明的方法

      • 获取某一个公共的方法

      • 获取某一个已声明的方法

        • Method getDeclaredMethod(“方法名”, 形参列表的类型Class列表 ….)
        • 通过方法的名称+形参列表才能唯一确定一个方法
    • (9)获取类上的注解

      • 获取所有的注解/注释

        • Annotation[] getAnnotations()
      • 获取指定的注解

        • A getAnnotation(Class annotationClass)

3.2 java.lang.reflect

  • Package

    • 获取包名

      • getName()
  • Modifier

    • Modifier.toString(mod)
  • Constructor

    • 创建实例对象

      • newInstance(Object …)

        • 如果无参,那么就直接“构造器对象.newInstance()”
        • 如果有参:构造器对象.newInstance(给构造器的实参列表)
  • Field

    • (1)setAccessible(true)

    • (2)Object get(实例对象)

      • Object 属性对象.get(实例对象)
      • 原来是: 实例对象.get属性名();
    • (3)set(实例对象, 属性的新值)

      • 属性对象.set(实例对象,属性值)
      • 原来是:实例对象.set属性名(属性值)
  • Method

    • (1)setAccessible(true)

      • 如果方法不是public才需要
    • (2)Object invoke(实例对象, 传给被调用方法的实参列表)

      • Object returnValue = 方法对象.invoke(实例对象,实参列表…)

        • 如果原来的方法对象是没有返回值,即是void,那么returnValue是null
      • 原来:

        • 有返回值

          • 变量 = 实例对象.方法名(实参列表)
        • 无返回值

          • 实例对象.方法名(实参列表);

3.3 示例代码

package com.atguigu.reflect;

import java.lang.reflect.Constructor;

import java.lang.reflect.Field;

import java.lang.reflect.Method;

import java.lang.reflect.Modifier;

import java.nio.charset.Charset;

import java.util.Arrays;

/*

  • 有了Class对象后,都可以做什么事?你想干啥干啥

  • 1、获取类的详细信息

  • 2、创建实例对象

  • 3、获取属性,设置属性

  • 4、获取方法,设置方法

*/

public class TestReflectAPI {

public static void main(String[] args) throws Exception {

	Object obj = "hello";



	Class clazz = obj.getClass();



	//1、获取类名

	System.out.println("类名:" + clazz.getName());



	//2、获取包信息

	/*

	 * 所有的包有共同点-->Package

	 */

	Package pack = clazz.getPackage();

	System.out.println("包名:" + pack.getName());



	//3、获取类的修饰符

	int mod = clazz.getModifiers();

	//每一种修饰符,有一个常量表示

	//这个常量在Modifier类型声明

	System.out.println(Modifier.toString(mod));



	//4、父类

	Class superclass = clazz.getSuperclass();

	System.out.println("父类:" + superclass);



	//5、接口

	Class[] interfaces = clazz.getInterfaces();

	System.out.println("接口们:");

	for (Class class1 : interfaces) {

		System.out.println(class1);

	}



	//6、属性:Field

	/*

	 * 属性共同点:  修饰符   数据类型   属性名      属性对应set值,get值的操作

	 * 任意类型的一个属性对应Field对象

	 *

	 * 一切皆对象

	 */

// Field[] fields = clazz.getFields();//返回公共的属性

/* Field[] fields = clazz.getDeclaredFields();

	System.out.println("属性们:");

	for (Field field : fields) {

		System.out.println("属性的类型:"+field.getType());

		System.out.println("属性的名称:"+field.getName());

		System.out.println("属性的所有信息:"+field);

	}*/



	//单独获取某个属性对象,例如:获取value属性

	//假设从配置文件中知晓属性名是value

// Field field = clazz.getField(“value”);//得到公共的

	Field field = clazz.getDeclaredField("value");//得到已声明的

	System.out.println(field);

	//设置属性值,获取属性值

	//先有对象,才能有属性值

// 获取"hello"对象的value属性值

	field.setAccessible(true);//设置可访问



	Object object = field.get(obj);

	char[] v = (char[]) object;

	System.out.println(Arrays.toString(v));

	v[0] = 'w';

	v[1] = 'o';

	v[2] = 'r';

	v[3] = 'l';

	v[4] = 'd';



	//参数一:哪个对象的field属性,第二个参数:设置为xx新值

// field.set(“hello”, “world”);//因为是final

	System.out.println(obj);



	//7、创建对象    创建Class对应的类型的对象

// Object obj = clazz.newInstance();

// System.out.println(obj);

	//8、构造器

// clazz.getConstructors()//获取所有公共的构造器

// clazz.getDeclaredConstructors();//获取所有该类拥有的构造器

	/*

	 * 构造器的共同特点:修饰符   构造器名   形参列表      可以创建对象的操作

	 */



	/*

	 * 构造器可以重载,构造器的名称都一样

	 * 如何在类中唯一确定一个构造器:靠形参列表(个数和类型)

	 */

// Constructor c = clazz.getDeclaredConstructor();//获取无参构造

// Object newInstance = c.newInstance();//用无参构造创建对象

// System.out.println(“对象:"+newInstance);

	//public String(char value[])

	 Constructor c = clazz.getDeclaredConstructor(char[].class);//char[]数组类型

	 //用有参构造创建对象,需要实参列表

	 char[] params= {'c','h','a','i'};

	 Object newInstance = c.newInstance(params);

	System.out.println("对象:"+newInstance);





	//9、方法

	/*

	 * 所有方法共同特点:

	 * 修饰符  返回值类型  方法名(形参列表)抛出的异常列表

	 * 方法可以被调用

	 */

// clazz.getMethods()//获取所有公共的方法

// clazz.getDeclaredMethods();//获取所有方法

	/*

	 * 方法可以重载,如何在一个类中,唯一确定方法:方法名+形参列表(个数和类型)

	 *

	 * toString()

	 */

	Method m = clazz.getDeclaredMethod("toString");//获取无参的方法

	System.out.println(m);



	//调用方法

	//参数一:那个实例对象调用m方法,参数二:传给m方法的实参列表

	Object returnValue = m.invoke(obj);

	System.out.println(returnValue);



	// public byte[] getBytes(Charset charset)

	Method m2 = clazz.getDeclaredMethod("getBytes", Charset.class);

	Object returnValue2 = m2.invoke(obj, Charset.forName("GBK"));

	System.out.println(returnValue2);

	byte[] data = (byte[]) returnValue2;

	System.out.println(Arrays.toString(data));

}

}

4 如何获取类上的泛型

4.1 步骤

  • (1)先得到类的Class对象

  • (2)获取它的父类

    • Type getGenericSuperClass() 可以带泛型
  • (3)类型转换

    • 如果是父类是这样的类型 父类名<泛型实参>
    • ParameterizedType p = (ParameterizedType )type;
  • (4)获取泛型实参

    • Type[] getActualTypeArguments()

4.2 示例代码

package com.atguigu.reflect;

import java.lang.reflect.ParameterizedType;

import java.lang.reflect.Type;

import java.util.Date;

public class TestGenericType {

public static void main(String[] args) {

	GenericSub g = new GenericSub();

	System.out.println(g.getType1());

	System.out.println(g.getType2());



	GenericSub2 g2 = new GenericSub2();

	System.out.println(g2.getType1());

	System.out.println(g2.getType2());

}

}

//T叫做类型形参

abstract class GenericSuper<T,U>{

private Class<T> type1;

private Class<U> type2;



public GenericSuper(){

	Class clazz = this.getClass();//this是当前对象,在构造器中,就是代表那个正在创建的对象



	//Type是包含Class等的所有类型

	Type gs = clazz.getGenericSuperclass();

	//GenericSuper<String>:参数化的类型

	ParameterizedType p  = (ParameterizedType) gs;

	//获取类型实参

	Type[] arr = p.getActualTypeArguments();



	type1 = (Class<T>) arr[0];

	type2 = (Class<U>) arr[1];

}



public Class<T> getType1() {

	return type1;

}

public Class<U> getType2() {

	return type2;

}

}

//String是类型实参

class GenericSub extends GenericSuper<String,Integer>{

}

class GenericSub2 extends GenericSuper<Date,Double>{

}

4.3 核心代码

	Class clazz = GenericSub.class;

// Class sup = clazz.getSuperclass();//得不到泛型的信息

// System.out.println(sup);

	//Type是包含Class等的所有类型

	Type gs = clazz.getGenericSuperclass();

	//GenericSuper<String>:参数化的类型

	ParameterizedType p  = (ParameterizedType) gs;

	//获取类型实参

	Type[] arr = p.getActualTypeArguments();

	System.out.println(arr[0]);

	System.out.println(arr[1]);

5 获取注解

5.1 获取类上的注解

  • 步骤

    • (1)先得到类的Class对象

    • (2)

      • 获取指定的注解

        • A getAnnotation(Class annotationClass)
        • 可以获取到的注解,必须声明周期是RUNTIME
    • (3)获取注解的配置参数的值

  • 示例代码

    package com.atguigu.reflect;

    import java.lang.annotation.Retention;

    import java.lang.annotation.RetentionPolicy;

    import org.junit.Test;

    @MyAnnoation

    public class TestAnnotatio {

    @MyAnnoation(value = “尚硅谷”)

    private String info;

    //获取类上的注解信息

    @Test

    public void test() {

      // 1、先得到Class对象
    
      Class clazz = TestAnnotatio.class;
    
    
    
      // 2、获取类上的注解信息:得到MyAnnoation注解对象
    
      MyAnnoation m = (MyAnnoation) clazz.getAnnotation(MyAnnoation.class);
    
    
    
      // 3、得到注解的配置参数的值
    
      String value = m.value();
    
      System.out.println(value);
    

    }

    }

    @Retention(RetentionPolicy.RUNTIME) // 为了在反射阶段可以读取到该注解的信息,生命周期一定要在RUNTIME

    @interface MyAnnoation {

    String value() default “atguigu”;

    }

5.2 获取属性上的注解

  • 步骤

    • (1)先得到类的Class对象

    • (2)获取属性对象

    • (3)

      • 获取指定的注解

        • A getAnnotation(Class annotationClass)
        • 可以获取到的注解,必须声明周期是RUNTIME
    • (4)获取注解的配置参数的值

  • 示例代码

    package com.atguigu.reflect;

    import java.lang.annotation.Retention;

    import java.lang.annotation.RetentionPolicy;

    import java.lang.reflect.Field;

    import org.junit.Test;

    public class TestAnnotatio {

    @MyAnnoation(value = “尚硅谷”)

    private String info;

    //获取属性上的注解信息

    @Test

    public void test2() throws Exception {

      // 1、获取Class对象
    
      Class clazz = TestAnnotatio.class;
    
    
    
      // 2、先获取属性对象
    
      Field field = clazz.getDeclaredField("info");
    
    
    
      // 3、得到注解对象
    
      MyAnnoation m = (MyAnnoation) field.getAnnotation(MyAnnoation.class);
    
    
    
      // 4、得到属性值
    
      System.out.println(m.value());
    

    }

    }

    @Retention(RetentionPolicy.RUNTIME) // 为了在反射阶段可以读取到该注解的信息,生命周期一定要在RUNTIME

    @interface MyAnnoation {

    String value() default “atguigu”;

    }

6 类加载器

6.1 类加载的过程(了解)

  • 双亲委托模式/机制

    • 某个类加载器接到加载任务,先把加载任务交给“父”加载器,层层往上,一直到引导类加载器,如果“父”加载器可以加载,那么就由“父”加载器加载,如果不可以,传回它的“子”加载器,“子”加载器尝试加载,如果可以,那么就加载,如果不可以,再往回传,一到回到最初接到任务的那个加载器,如果它可以,也正常加载,如果它也不能加载,报异常:ClassNotFoundException
    • 作用:安全

6.2 类加载器的体系结构

  • 1、引导类加载器BootStrap

    • 非Java语言实现的

      • 获取不到它的对象,只能得到null
    • 加载核心类库rt.jar

    • 加载sun.boot.class.path路径下的内容

  • 2、扩展类加载器ExtClassLoader

    • 加载jre/ext目录
    • java.ext.dirs路径下的内容
  • 3、应用程序类加载器,系统类加载器AppClassLoader

    • 加载用户自定义的类型
    • 加载src目录下的内容(bin)
  • 4、自定义类加载器

6.3 类加载的作用

  • (1)加载类

  • (2)加载资源文件

    @Test

    public void test8()throws Exception{

      Properties pro = new Properties();
    
      //JavaSE和Web项目
    
      //在web项目中,因为项目部署到tomcat中运行,又因为tomcat用自己的类加载器的
    
      //把配置文件放在了src中,最终代码在WEB-INFO的classes目录
    
      //可以用类加载器加载这个配置文件,但是不是系统类加载器
    
      //this.getClass().getClassLoader()目的是得到tomcat的自定义类加载器对象
    
      pro.load(this.getClass().getClassLoader().getResourceAsStream("3.properties"));
    
    
    
      System.out.println(pro.getProperty("user3"));
    

    }

    @Test

    public void test7()throws Exception{

      Properties pro = new Properties();
    
      //JavaSE和Web都可以
    
      //把配置文件放在了项目根目录下,在src外面
    
      //不可以用类加载器加载这个配置文件
    
      //可以使用FileInputStream获取
    
      pro.load(new FileInputStream("2.properties"));
    
    
    
      System.out.println(pro.getProperty("user2"));
    

    }