18 Java反射

时间: 2023-08-28 admin IT培训

18 Java反射

18 Java反射

获取class对象三种方式

Java运行的时候,某个类无论生成多少个对象,他们都会对应同一个Class对象,它表示正在运行程序中的类和接口。如何取得操作类的Class对象,常用的有三种方式:

  • 调用Class的静态方法forName,如Class.forName("java.lang.String");  
    (源文件阶段,此阶段只知道java源文件,通过java源文件获取对象)
  • 使用类的.class语法,如:Class<?> cls = String.class
    (类加载阶段,此阶段知道具体的java类,通过java类获得具体的对象,主要用于传参)
  • 调用对象的getClass方法,如:String str = "abc";Class<?> cls = str .getClass();
    Runtime,运行时阶段,通过 对象反向获得类

反射 .setAccessible(true)

一般情况下,我们并不能对类的私有字段进行操作,利用反射也不例外,但有的时候,例如要序列化的时候,我们又必须有能力去处理这些字段,这时候,我们就需要调用AccessibleObject上的setAccessible()方法来允许这种访问,而由于反射类中的Field,Method和Constructor继承自AccessibleObject,因此,通过在这些类上调用setAccessible()方法,我们可以实现对这些字段的操作。原文链接:

假设有一个实体类:

public class AccessibleTest {  private int id;  private String name;  public AccessibleTest() {  }  public int getId() {  return id;  }  public void setId(int id) {  this.id = id;  }  public String getName() {  return name;  }  public void setName(String name) {  this.name = name;  }  }  

测试类

public class Main {  public static void main(String[] args) throws Exception {  Class clazz = Class.forName("com.test.accessible.AccessibleTest");  AccessibleTest at = new AccessibleTest();  at.setId(1);  at.setName("AT");  for (Field f : clazz.getDeclaredFields()) {  f.setAccessible(true);//AccessibleTest类中的成员变量为private,故必须进行此操作  System.out.println(f.get(at));//获取当前对象中当前Field的value  }  }  }  

如果没有在获取Field之前调用setAccessible(true)方法,异常:

  1.  java.lang.IllegalAccessException:  
  2. Class com.test.accessible.Main   
  3. can not access   
  4. a member of class com.test.accessible.AccessibleTest  
  5. with modifiers "private"  

当然在AccessibleTest类的内部(AccessibleTest的内部类除外)
进行如上操作则不需要调用setAccesible()方法

反射获取属性、方法、构造函数 .getXXX .getDeclaredXXX

在获得类的方法、属性、构造函数时,会有.getXXX和.getDeclaredXXX两种对应的方法。之间的区别在于前者返回的是访问权限为public的方法和属性,包括父类中的;但后者返回的是所有访问权限的方法和属性,不包括父类的

反射获取单个方法

.getMethod()----仅可以获取类(及其父类) 的一个public方法

.getDeclareMethod()----可以获取类本身的一个方法(包括私有、共有、保护)

.getMethods----仅可以获取类(及其父类) 的 所有 public方法

.getDeclaredMethods----获取类本身的 所有 方法(包括私有、共有、保护)

package com.wanggc.reflection;  
import java.lang.reflect.Method;/*** Java 反射练习。* * @author Wanggc*/public class ReflectionTest {public static void main(String[] args) throws Exception {DisPlay disPlay = new DisPlay();// 获得ClassClass<?> cls = disPlay.getClass();// 通过Class获得DisPlay类的show方法Method method = cls.getMethod("show", String.class);// 调用show方法method.invoke(disPlay, "Wanggc");}}class DisPlay {public void show(String name) {System.out.println("Hello :" + name);}}

getMethod方法的第一个参数是方法名,第二个是此方法的参数类型,如果是多个参数,接着添加参数就可以了,因为getMethod是可变参数方法。

invoke方法,其实也就是执行show方法,注意invoke的第一个参数,是DisPlay类的一个对象,也就是调用DisPlay类哪个对象的show方法,第二个参数是给show方法传递的参数。类型和个数一定要与getMethod方法一致。

反射获取所有方法

.getMethod()----仅可以获取类(及其父类) 的一个public方法

.getDeclareMethod()----可以获取类本身的一个方法(包括私有、共有、保护)

.getMethods----仅可以获取类(及其父类) 的 所有 public方法

.getDeclaredMethods----获取类本身的 所有 方法(包括私有、共有、保护)

package com.wanggc.reflection;import java.lang.reflect.Method;/*** Java 反射练习。* * @author Wanggc*/
public class ForNameTest {/*** 入口函数。* * @param args*            参数* @throws Exception*             错误信息*/public static void main(String[] args) throws Exception {// 获得ClassClass<?> cls = Class.forName(args[0]);// 通过Class获得所对应对象的方法Method[] methods = cls.getMethods();// 输出每个方法名for (Method method : methods) {System.out.println(method);}}
}

反射一个类的属性值

 package com.wanggc.reflection;import java.lang.reflect.Field;/*** Java 反射之属性练习*/public class ReflectionTest {public static void main(String[] args) throws Exception {// 建立学生对象Student student = new Student();// 为学生对象赋值student.setStuName("Wanggc");student.setStuAge(24);// 建立拷贝目标对象Student destStudent = new Student();// 拷贝学生对象copyBean(student, destStudent);// 输出拷贝结果System.out.println(destStudent.getStuName() + ":"+ destStudent.getStuAge());}/*** 拷贝学生对象信息。* * @param from*            拷贝源对象* @param dest*            拷贝目标对象* @throws Exception*             例外*/private static void copyBean(Object from, Object dest) throws Exception {// 取得拷贝源对象的Class对象Class<?> fromClass = from.getClass();// 取得拷贝源对象的属性列表Field[] fromFields = fromClass.getDeclaredFields();// 取得拷贝目标对象的Class对象Class<?> destClass = dest.getClass();Field destField = null;for (Field fromField : fromFields) {// 取得拷贝源对象的属性名字String name = fromField.getName();// 取得拷贝目标对象的相同名称的属性destField = destClass.getDeclaredField(name);// 设置属性的可访问性fromField.setAccessible(true);destField.setAccessible(true);// 将拷贝源对象的属性的值赋给拷贝目标对象相应的属性destField.set(dest, fromField.get(from));}}}/*** 学生类。*/class Student {/** 姓名 */private String stuName;/** 年龄 */private int stuAge;/*** 获取学生姓名。* * @return 学生姓名*/public String getStuName() {return stuName;}/*** 设置学生姓名* * @param stuName*            学生姓名*/public void setStuName(String stuName) {this.stuName = stuName;}/*** 获取学生年龄* * @return 学生年龄*/public int getStuAge() {return stuAge;}/*** 设置学生年龄* * @param stuAge*            学生年龄*/public void setStuAge(int stuAge) {this.stuAge = stuAge;}
}

Java的发射机制中类有Class对应,类的方法有Method对应,当然属性也有Field与之对应。代码中注释已经做了详细的注释,在此不再赘述。但要注意,Field提供了getset方法获取和设置属性的值,但是由于属性是私有类型,所以需要设置属性的可访问性为true。也可以在为整个fields设置可访问性,使用AccessibleObject的静态方法setAccessible,如:AccessibleObject.setAccessible(fromFields, true);

反射创建类对象

前面讲述了如何用Java反射机制操作一个类的方法和属性,下面再通过一个实例讲述如何在运行时创建类的一个对象:

  package com.wanggc.reflection;import java.lang.reflect.Field;/*** Java 反射之属性练习。* * @author Wanggc*/public class ReflectionTest {public static void main(String[] args) throws Exception {// 建立学生对象Student student = new Student();// 为学生对象赋值student.setStuName("Wanggc");student.setStuAge(24);// 建立拷贝目标对象Student destStudent = (Student) copyBean(student);// 输出拷贝结果System.out.println(destStudent.getStuName() + ":"+ destStudent.getStuAge());}/*** 拷贝学生对象信息。* * @param from*            拷贝源对象* @param dest*            拷贝目标对象* @throws Exception*             例外
*/private static Object copyBean(Object from) throws Exception {// 取得拷贝源对象的Class对象Class<?> fromClass = from.getClass();// 取得拷贝源对象的属性列表Field[] fromFields = fromClass.getDeclaredFields();// 取得拷贝目标对象的Class对象Object ints = fromClass.newInstance();for (Field fromField : fromFields) {// 设置属性的可访问性fromField.setAccessible(true);// 将拷贝源对象的属性的值赋给拷贝目标对象相应的属性fromField.set(ints, fromField.get(from));}return ints;}
}/*** 学生类。*/class Student {/** 姓名 */private String stuName;/** 年龄 */private int stuAge;/*** 获取学生姓名。* * @return 学生姓名*/public String getStuName() {return stuName;}/*** 设置学生姓名* * @param stuName*            学生姓名*/public void setStuName(String stuName) {this.stuName = stuName;}/*** 获取学生年龄* * @return 学生年龄
*/public int getStuAge() {return stuAge;}/*** 设置学生年龄* * @param stuAge*            学生年龄*/public void setStuAge(int stuAge) {this.stuAge = stuAge;}
}

此例和上例运行的结果是相同的。但是copyBean方法返回的对象不再是外面传入的,而是由方法内部产生的。注意:Class的newInstance方法,只能创建只包含无参数的构造函数的类,如果某类只有带参数的构造函数,那么就要使用另外一种方式 

fromClass.getDeclaredConstructor(int.class,String.class)
.newInstance(24,"wanggc");

反射操作数组

反射中getComponentType()方法可以取得一个数组的Class对象

import java.lang.reflect.*;
class hello{public static void main(String[] args) {int[] temp={1,2,3,4,5};Class<?>demo=temp.getClass().getComponentType();System.out.println("数组类型: "+demo.getName());System.out.println("数组长度  "+Array.getLength(temp));System.out.println("数组的第一个元素: "+Array.get(temp, 0));Array.set(temp, 0, 100);System.out.println("修改之后数组第一个元素为: "+Array.get(temp, 0));}
}

newInstance()和new()区别

Java开发特别是数据库开发中,经常会用到Class.forName( )这个方法。通过查询Java Documentation我们会发现使用Class.forName( )静态方法的目的是为了动态加载类。在加载完成后,一般还要调用Class下的newInstance( )静态方法来实例化对象以便操作。因此,单单使用Class.forName( )是动态加载类是没有用的,其最终目的是为了实例化对象

那么Class下的newInstance()new有什么区别?
首先,newInstance( )是一个方法,而new是一个关键字,其次,Class下的newInstance()的使用有局限,因为它生成对象只能调用无参的构造函数,而使用new关键字生成对象没有这个限制。
即:Class.forName("")返回的是类,Class.forName("").newInstance()返回的是object

有数据库开发经验朋友会发现,为什么在我们加载数据库驱动包的时候有的却没有调用newInstance( )方法呢?有的jdbc连接数据库的写法里是Class.forName(xxx.xx.xx);而有一些Class.forName(xxx.xx.xx).newInstance(),为什么会有这两种写法呢?
Class.forName("");的作用是要求JVM查找并加载指定的类,如果在类中有静态初始化器的话,JVM必然会执行该类的静态代码段。而在JDBC规范中明确要求这个Driver类必须向DriverManager注册自己,即任何一个JDBC DriverDriver类的代码都必须类似如下:

public class MyJDBCDriver implements Driver {static {DriverManager.registerDriver(new MyJDBCDriver());}
}

既然在静态初始化器的中已经进行了注册,所以我们在使用JDBC时只需要Class.forName(XXX.XXX);就可以了

Java中工厂模式经常使用newInstance()方法来创建对象,因此从为什么要使用工厂模式上可以找到具体答案。 例如:

class c = Class.forName(“Example”);
factory = (ExampleInterface)c.newInstance();其中ExampleInterface是Example的接口,可以写成如下形式:
String className = "Example";
class c = Class.forName(className);
factory = (ExampleInterface)c.newInstance();进一步可以写成如下形式:
String className = readfromXMlConfig;//从xml 配置文件中获得字符串
class c = Class.forName(className);
factory = (ExampleInterface)c.newInstance();

上面代码已经不存在Example的类名称,它的优点是,无论Example类怎么变化,上述代码不变,甚至可以更换Example的兄弟类Example2 , Example3 , Example4……,只要他们继承ExampleInterface就可以。
JVM的角度看,我们使用关键字new创建一个类的时候,这个类可以没有被加载。但是使用newInstance()方法的时候,就必须保证:

  • 这个 类已经加载;
  • 这个类已经连接了。

而完成上面两个步骤的正是Class的静态方法forName()所完成的,这个静态方法调用了启动类加载器,即加载 java API的那个加载器。
现在可以看出,newInstance()实际上是把new这个方式分解为两步,即首先调用Class加载方法加载某个类,然后实例化。 这样分步的好处是显而易见的。我们可以在调用class的静态加载方法forName时获得更好的灵活性,提供给了一种降耦的手段。
最后用最简单的描述来区分new关键字和newInstance()方法的区别:

  • newInstance: 弱类型。低效率。只能调用无参构造。
  • new: 强类型。相对高效。能调用任何public构造

反射机制:getDeclaredField和getField的区别

在做后台开发时实体的固定字段一般会抽取为一个父类,其他类继承该父类,例如主键字段,会放到一个父类中(IdEntity),其他类继承该类,但是我们在操作时操作的是子类,在通过子类获取父类属性是getDeclaredField和getField是不一样的,简单说

getDeclaredFiled 仅能获取类本身的属性成员(包括私有、共有、保护) 
getField 仅能获取类(及其父类可以自己测试) public属性成员
因此在获取父类的私有属性时,要通过getSuperclass的之后再通过getDeclaredFiled获取。

父类,出去id作为父类字段

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import org.hibernate.annotations.GenericGenerator;
@MappedSuperclass
public abstract class IdEntity {private String id;@Id@GeneratedValue(generator = "paymentableGenerator")@GenericGenerator(name = "paymentableGenerator", strategy = "uuid")@Column(name ="ID",nullable=false,length=32)public String getId() {return id;}public void setId(String id) {this.id = id;}}

 子类

public class Reflect extends IdEntity {public String publicField;private String privateField;public Reflect(String publicField, String privateField) {super();this.publicField = publicField;this.privateField = privateField;}public String getPrivateField() {return privateField;}public void setPrivateField(String privateField) {this.privateField = privateField;}
}

测试类:

import java.lang.reflect.Field;public class ReflectDemo {/*** @Description: TODO* @param @param args* @return void* @throws SecurityException* @throws NoSuchFieldException* @throws IllegalAccessException* @throws IllegalArgumentException* @throws* @author ydecai* @date 2019-1-23*/public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {Reflect re = new Reflect("zhangsan", "lisi");re.setId("111");//getDeclaredFiled获取本类属性
//		Field publicField = re.getClass().getDeclaredField("publicField");
//		System.out.println(publicField.get(re));Field publicField = re.getClass().getField("publicField");System.out.println(publicField.get(re));//getField获取公有属性,包括父类的,下面语句报错//Field privateField = re.getClass().getField("privateField");Field privateField = re.getClass().getDeclaredField("privateField");privateField.setAccessible(true);System.out.println(privateField.get(re));//获取父类私有属性并获取值Field fileId = re.getClass().getSuperclass().getDeclaredField("id");fileId.setAccessible(true);System.out.println(fileId.get(re));}}