18 Java反射
- 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)方法,异常:
- java.lang.IllegalAccessException:
- Class com.test.accessible.Main
- can not access
- a member of class com.test.accessible.AccessibleTest
- 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
提供了get
和set
方法获取和设置属性的值,但是由于属性是私有类型,所以需要设置属性的可访问性为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 Driver
的Driver类
的代码都必须类似如下:
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));}}
- Driller源码阅读笔记(二)
- oracle中 rownum和rowid的用法
- 俞敏洪一分钟励志演讲
- platform创建说明
- 经纬度坐标转换为屏幕坐标
- AndroidWidget——GridView 学习笔记
- 正确性、健壮性、可靠性、效率、易用性、可读性(可理解性)、可扩展性、可复 用性、兼容性、可移植性
- 关于使用ComponentName连接俩个Activity运行闪退的问题
- 扎心了!37岁被裁,好几个月都没有找到工作,面试大公司被婉拒,无奈只能降薪去小公司,没想到还被人嫌弃技术太落后...
- 音频信号的数字化及压缩编码
- 大数定律,方差
- 数字集成电路(中)
- 【网络流量监控工具之Nethogs】
- Worse is Better
- MFCCList使用
- 磁盘分区形式:主启动记录(MBR)和全局唯一标识分区表(GPT)
- shiro反序列化漏洞学习(工具+原理+复现)