反射机制
反射机制
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"));
}