java反射机制

java反射机制是java基础重要的一部分,也是后面所学的框架的灵魂。我们先从反射概念开始说吧。

一、java反射的概述

(1)java反射的概念
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取类或对象的信息以及动态调用对象的方法的功能称为java语言的反射机制。
(2)反射的作用
从概念中不难看出,反射就是用来动态获取类或对象的信息以及调用对象的方法。所谓动态,就是运行时。利用反射可以将类的组成部分取出来当做一个对象看待,就是将类的组成部分映射成对象,这就是类信息的获取;操作映射出的对象就是动态调用类的方法。
在下面的总结中会需要定义一个类
首先我写了一个Person类

@MyAnnotation(value = "atguigu")
public class Person extends Creature<String> implements Comparable,MyInterface{
 public String name;
 private int age;
 int id;
 public Person() {
  super();
 }
 public Person(String name) {
  super();
  this.name = name;
 }
 private Person(String name, int age) {
  super();
  this.name = name;
  this.age = age;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public int getAge() {
  return age;
 }
 public void setAge(int age) {
  this.age = age;
 }
 public int getId() {
  return id;
 }
 public void setId(int id) {
  this.id = id;
 }
 @MyAnnotation(value = "abc123")//此注解为本人自定义注解
 public void show() {
  System.out.println("我是一个人!");
 }
 private Integer display(String nation,Integer i) throws Exception{
  System.out.println("我的国籍是:"+nation);
  return i;
 }
 @Override
 public String toString() {
  return "Person [name=" + name + ", age=" + age + "]";
 }
 @Override
 public int compareTo(Object o) {
  return 0;
 }
 public static void info() {
  System.out.println("中国人!");
 }
 class Bird{//内部类
 }
}

还有在Person类中用到的自定义注解

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
 String value();
}

二、类的加载和获取字节码引用

从反射的概念可以看出,类的字节码(.class文件里的信息就是类的字节码)是java反射机制的关键点。类的字节码包含类的所有信息。想要在程序中动态的获取类的信息及调用对象的方法,就必须将类的字节码加载到JVM内存中,并获取字节码的引用(引用的数据类型为Class)。
(1)什么是加载类?如何加载类?
加载类就是将类的字节码内容加载到JVM的方法区中,且一个类在JVM中只能存在一份。
加载类的方法有三种
①通过new一个类的对象加载类——静态加载(加载类、连接类、初始化类)
Person p = new Person();//虚拟机在创建对象前,必须先将类加载到JVM内存中,然后调用构造函数,实例化对象
②使用Class静态方法 Class.forName(全类名)——动态加载(加载类、连接类、初始化类)

  String className = "com.atguigu.java.Person";//需要加载的类的相对字符串表达方式,必须是类的全称
  Class.forName(className);//通过类名获取

③使用类加载器,加载类

  ClassLoader classLoader = this.getClass().getClassLoader();//获取当前类的类加载器
  classLoader.loadClass("com.atguigu.java.Person");//通过当前类的类加载器获取,传入的字符串也是类的全称

(2)如何获取加载到JVM内存中的类的引用
有四种获取类引用的方法
①通过运行时类的对象获取

Class clazz = p.getClass();//通过对象.getClass方法获取

②通过Class.forName(全类名)方法获取,此方法在加载类时,返回类的引用

Class clazz = Class.forName(className);//加载类并获取类的引用

③通过类加载器获取,类加载器在加载类时也会返回类的引用

Class clazz = classLoader.loadClass("com.atguigu.java.Person");//加载类,获取类的引用

④通过类的属性获得,每个都有class属性

Class clazz = Person.class;//调用Person类本身.class属性

加载类,并获取类的引用,就可以使用反射机制了

三、反射机制总结

反射机制就是通过类的引用获取类的信息,并对类进行操作
1、获取运行时类的成员变量及其相关信息并对其操作
①获取对应运行时类的成员变量

@Test
 public void test1() {
  Class clazz = Person.class;
  //1.获取运行时类本身及其父类中声明为public的属性
  Field[] fields = clazz.getFields();//获取Person类的public修饰的变量,返回的是数组
  for (Field field : fields) {//遍历属性数组
   System.out.println(field);//打印获取的变量名称,调用field的toString方法输出属性的全称,修饰符+数据类型+类名+属性名
  }
  //2.获取运行时类本身声明了的属性
  Field[] field1 = clazz.getDeclaredFields();//只要声明了就能获取,只限本身
  for (Field field : field1) {
   System.out.println(field.getName());//调用getName方法,只输出属性名,当然也可以用前者一样输出方式
  }
  
 }

运行结果
前者是public修饰的所有public属性;后者是类自身的所有属性
在这里插入图片描述
②获取属性的修饰符,变量类型,变量名

@Test
 public void test2() {
  Class clazz = Person.class ;//加载运行时类
  Field[] field1 = clazz.getDeclaredFields();//获取类本身所有的属性
  for (Field field : field1) {
   //1.获取每个属性的权限修饰符
   int i = field.getModifiers();//每一种权限对应一个值
   String str1 = Modifier.toString(i);//获取值对应的权限
   System.out.print(str1+" ");
   //2.获取属性的变量类型
   Class type = field.getType();//获取属性类型
   System.out.print(type.getName()+" ");//打印属性类型的名字
   //3.获取属性名并打印
   System.out.println(field.getName());
  }
 }

运行结果
id为默认修饰符,所有int前面只有一个空格
在这里插入图片描述
③赋值或修改运行时类指定的属性(这个操作时要熟练掌握的),即使是final修饰的对象也可被修改

@Test
 public void test3() throws Exception {
  Class<Person> clazz = Person.class;//加载运行时类
  Field name = clazz.getField("name");//获取运行时类的属性“name”,此声明必须为public
  Person p = clazz.newInstance();//运用反射创建对象(调用的是空参构造)
  
  name.set(p, "Jerry");//给指定p对象的name属性赋值
  System.out.println(p);
  
  Field age = clazz.getDeclaredField("age");//获取运行时类的属性“age”,此属性为任意类型类型
  age.setAccessible(true);//把指定对p象的不可访问的属性设置为可访问的,因为age时private修饰的属性
  age.set(p, 10);//给指定对象的age属性赋值
  System.out.println(p);
  
  Field id = clazz.getDeclaredField("id");//获取属性protect int id
  id.set(p, 8);//修改属性
  System.out.println(p);
 }

运行结果

在这里插入图片描述
2、通过反射获取类的方法及其信息,并调用方法
①获取运行时类的方法

@Test
 public void test1() {
  Class clazz = Person.class;//加载类
  //1.获取运行时类声明为public的方法
  Method[] m1 = clazz.getMethods();//能获取运行时类及其父类中所有声明为public的方法
  for (Method method : m1) {
   System.out.println(method);//自动调用toString方法,输出此方法的所有信息
  }
  System.out.println();
  //2.获取运行时类本身所有的方法
  Method[] m2 = clazz.getDeclaredMethods();//获取运行时类本身所有方法
  for (Method method : m2) {
   System.out.println(method);//同前者
  }
 }

运行结果
在这里插入图片描述
②获取运行时类方法的信息(注解 权限修饰符 返回值类型 方法名 形参类表 声明异常)

@Test
 public void Test2() {
  Class clazz = Person.class;//加载类
  //2.获取运行时类本身声明的所有的方法
  Method[] m2 = clazz.getDeclaredMethods();//获取运行时类本身所有方法
  for (Method method : m2) {
   //1.获取注解
   Annotation[] ann = method.getAnnotations();
   for (Annotation annotation : ann) {//遍历并输出该方法的注解,如果无注解则不进入循环
    System.out.println(annotation+" ");    
   }
   //2.获取权限修饰符
   String str = Modifier.toString(method.getModifiers());
   System.out.print(str+" ");
   //3.获取返回值类型
   Class<?> returnType = method.getReturnType();//获取返回值类型的类引用
   System.out.print(returnType.getSimpleName()+" ");//输出返回值类型的类引用的简称(SimpleName)
   //4.获取方法名
   System.out.print(method.getName());//获取方法名简称
   //5.获取性参列表
   System.out.print("(");
   Class[] params = method.getParameterTypes();//获取方法的参数数组
   for (Class class1 : params) {//遍历参数
    System.out.print(class1.getSimpleName()+" ");
   }
   System.out.print(") ");
   //6.获取异常类型
   Class[] exceptionTypes = method.getExceptionTypes();//获取该方法异常类型数组
   if(exceptionTypes.length!=0) {//如果有异常则...输出throws
    System.out.print("throws ");
   }
   for (Class class1 : exceptionTypes) {//如果有异常则进入循环遍历异常数组
    System.out.print(class1.getSimpleName());
   }
   System.out.println();
  }
 }

运行结果
在这里插入图片描述
③调用运行时类的方法(和成员变量同理,此操作需要熟练掌握)

@Test
 public void test3() throws Exception {
  Class<Person> clazz = Person.class;//加载类
  //public方法的调用
  //getMethod(String methodName,Class...params):获取运行时类中声明为public的指定的方法
  Method m1 = clazz.getMethod("show");//获取方法show()
  Person p = clazz.newInstance();//通过反射创建对象
  //调用指定的方法:Object invoke(Object obj,Object...objs)
  Object returnVal = m1.invoke(p);//调用p对象的m1获取的方法并用returnVal接收运行方法的返回值,void返回null
  System.out.println("returnVal:"+returnVal);//null
  //方法有返回值
  Method m2 = clazz.getMethod("toString");//获得方法toString()
  Object returnVal2 = m2.invoke(p);//调用p对象的m2获取的方法并用returnVal2接收运行方法的返回值
  System.out.println("returnVal2:"+returnVal2);//打印返回值
  //静态方法的调用
  Method m3 = clazz.getMethod("info");//获得静态方法info()
  m3.invoke(Person.class);//调用静态方法,参数可以是p对象,也可以是类的字节码引用
  
  //getDeclaredMethod(String methodName,Class...params):获取运行时类中任何声明的指定的方法
  Method m4 = clazz.getDeclaredMethod("display",String.class,Integer.class);//获取运行时类的私有方法,此方法的方法名为"display",参数为String.class,Integer.class(有多少写多少)
  m4.setAccessible(true);//设置不可访问的权限为可访问的
  Object value = m4.invoke(p,"CHN",10);//运行方法,传入参数,并将接收返回值
  System.out.println(value);//输出10
  
 }

运行结果

在这里插入图片描述
3、通过反射获取类的构造方法,并创建对象
①通过用字节码引用调用newInstance()方法创建对象

@Test
 public void test1() throws Exception {
  String className = "com.atguigu.java.Person";
  Class clazz = Class.forName(className);//运行时加载Person类,在此处可使用泛型,后面就无需类型转化
  //创建运行时类的对象,使用newInstance(),实际上就是调用了运行时类的空参构造器
  /*
   * 要想能够创建成功:1.要求对应的运行时类要有空参构造器。2.构造器的权限修饰符的权限要足够
   */
  Object obj = clazz.newInstance();//动态创建对象,内部其实调用空参构造器,类没有无参构造时不能使用
  Person p = (Person)obj;
  System.out.println(p);
 }

运行结果
在这里插入图片描述
②获取运行时类构造器

@Test
 public void test2() throws Exception {
  String className = "com.atguigu.java.Person";
  Class clazz = Class.forName(className);//运行时加载类
  
  Constructor[] cons = clazz.getDeclaredConstructors();//获取所有构造器
  for (Constructor constructor : cons) {//遍历数组
   System.out.println(constructor);//自动调用toString方法
  }
 }

运行结果
在这里插入图片描述
③调用指定的构造器,创建运行时类对象(需熟练掌握)

@Test
 public void test4() throws Exception {
  String className = "com.atguigu.java.Person";
  Class clazz = Class.forName(className);//运行时加载类
  
  Constructor cons = clazz.getDeclaredConstructor(String.class,int.class);
  cons.setAccessible(true);//设置不可访问的方法为可访问的,防止构造函数被private修饰,而无法使用
  Person p = (Person)cons.newInstance("罗伟",20);//用有参构造函数构造对象
  System.out.println(p);
 }

运行结果
在这里插入图片描述
4、通过反射获取类其他信息
以上三种就是类的主要组成部分,重点不在于获取信息,而在于调用运行,这才是反射的灵活之处。当然还有类的其他信息同样可以获取到,就不显得那么重要了

public class TestOthers {
 //1、获取运行时类的父类
 @Test
 public void test1() {
  Class clazz = Person.class;//加载运行时类
  Class superClass = clazz.getSuperclass();//获取运行时类的父类
  System.out.println(superClass);
 }
 //2、获取带泛型的父类
 @Test
 public void test2() {
  Class clazz = Person.class;//加载运行时类
  Type type = clazz.getGenericSuperclass();//获取带泛型的父类
  System.out.println(type);
 }
 //3、获取父类的泛型(关注一下,有点绕)
 @Test
 public void test3() {
  Class clazz = Person.class;//加载运行时类
  Type type = clazz.getGenericSuperclass();//获取带泛型的父类
  ParameterizedType param = (ParameterizedType)type;//强转,参数化
  Type[] types = param.getActualTypeArguments();//获取泛型,返回Type类型数组
  System.out.println(((Class)types[0]).getName());//将Type类型转化为Class类型
 }
 //4、获取实现的接口
 @Test
 public void test4() {
  Class clazz = Person.class;//加载运行时类
  Class[] interfaces = clazz.getInterfaces();//获取当前类的接口数组
  for (Class class1 : interfaces) {
   System.out.println(class1);
  }
 }
 //5、获取所在的包
 @Test
 public void test5() {
  Class clazz = Person.class;//加载运行时类
  Package pack = clazz.getPackage();
  System.out.println(pack);
 }
 //6、获取关于类的注解
 //和类的生命周期有关(注解上去的),生命周期为CLASS获取不到,RUNTIME能获取到
 @Test
 public void test6() {
  Class clazz = Person.class;//加载运行时类
  Annotation[] anns = clazz.getAnnotations();//获取关于类的注解数组
  for (Annotation annotation : anns) {
   System.out.println(annotation);
  }
 }
 //7、获取内部类
 @Test
 public void test7() {
  Class clazz = Person.class;//加载运行时类
  Class[] class1 = clazz.getDeclaredClasses();//获取运行时类的所有的内部类数组
  for (Class class2 : class1) {
   System.out.println(class2.getName());
  }
 }
}

以上就是反射的基本操作,而平日里用到的大概也就是创建对象,调用方法,赋值或更改变量。对于反射机制还有一个重要的应用,就是动态代理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值