反射的概念
我们知道每个类加载到运行时数据区的时候会将类的结构信息保存到方法区中(JDK1.8之前的概念),再在堆中创建一个与被加载的类对应的特殊的对象,这个对象封装方法区中类的数据结构,可以使用该对象操作类的结构信息,这种使用Class对象操作方法区中对应的类的信息的方式就叫做反射机制
取得Class类对象
既然要使用这个特殊的对象去操作类的信息,那么就先要取得这个对象,取得这个对象有三种方式:
- 使用类的普通对象的getclass()方法取得,该方法是继承object类
- 使用class类的静态方法forname(“类的全名称”)
- 使用“类名.class”取得
DEMO:取得Date类的Class类对象
1、使用getClass()方法
public class Test {
public static void main(String[] args) throws Exception {
//实例化一个Date类对象
Date date=new Date();
//使用getClass()方法的取得Date类对应的Class<Date>对象
Class<?> dateClass=date.getClass();
System.out.println(dateClass);
}
}
2、使用Class类的forName()取得
package com.sun;
public class Test {
public static void main(String[] args) throws Exception {
//使用Class<T>的静态方法forName()取得Date类对应的Class<Date>对象
Class<?> dateClass=Class.forName("java.util.Date");
System.out.println(dateClass);
}
}
3、使用Date.class的方式取得
package com.sun;
import java.util.Date;
public class Test {
public static void main(String[] args) throws Exception {
//使用Date.class方式取得Date类对应的Class<Date>对象
Class<?> dateClass= Date.class;
System.out.println(dateClass);
}
}
以上输出的信息作用不大,只是输出之后表示该Class对象已经取得了。
DEMO:简单使用
package com.sun;
import java.util.Date;
public class Test {
public static void main(String[] args) throws Exception {
//使用Date.class方式取得Date类对应的Class<Date>对象
Class<?> dateClass= Date.class;
//使用Class类对象实例化一个普通对象
Date date=(Date)dateClass.newInstance();
System.out.println(date);
}
}
此时使用反射实例化了一个Date类型的对象,那么使用new关键字不是更好用呢?分析下面的DEMO就知道使用反射的好处了。
DEMO:原始工厂模式
package com.sun;
interface Animal{
public void eat();
}
//定义出子类
class Bird implements Animal{
@Override
public void eat() {
System.out.println("吃虫子");
}
}
class Pig implements Animal{
@Override
public void eat() {
System.out.println("吃食");
}
}
//定义工厂
class AnimalFacoty{
public static Animal getInstance(String animal){
if("bird".equals(animal)){
return new Bird();
}else if("pig".equals(animal)){
return new Pig();
}
return null;
}
}
public class Test {
public static void main(String[] args) throws Exception {
Animal animal=AnimalFacoty.getInstance("pig");
animal.eat();
}
}
以上的工厂存在什么问题呢? 假如有一万个子类,那么需要在工厂中使用if else if判断一万次,这样代码肯定不好,脆弱,应变性不高。接下来使用反射解决这个问题。
DEMO:反射改善工厂
//定义工厂
class AnimalFacoty {
/**
* 使用反射实例化动物对象返回
* @param className 类的全名称
* @return
*/
public static Animal getInstance(String className) {
try {
//取得反射的对象(堆区中的Class<T>类对象)
Class<?> classObj = Class.forName(className);
//实例化动物对象
return (Animal) classObj.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public class Test {
public static void main(String[] args) throws Exception {
Animal animal = AnimalFacoty.getInstance("com.sun.Bird");
animal.eat();
}
}
此时如果以后再继续增加Animal接口的 的子类也不需要再去修改工厂了,解决一定程度的耦合(在之前是使用抽象工厂解决的)。
总结:
1、反射的概念:使用加载类的时候在堆区创建的与被加载的类对象的Class对象操作类的属性、方法、构造方法、注解等信息的方式叫做反射机制。
2、取得堆区中Class类对象的方式有三种:
·getClass()
·Class.forName()
·类名.class
反射操作构造方法
我们知道可以使用反射机制操作类的结构,包括调用类的构造方法、操作类的普通方法、访问类的属性、取得类或者方法上注解信息等。
本次课学习的是反射操作构造方法,要使用反射操作构造方法,得先使用堆区的那个Class类对象取得构造方法的信息,构造方法的信息取得之后需要封装到一个对象中返回(到处都是面向对象的思想),这个保存了构造方法信息的对象是什么类型呢?在Java提供了一个类型专门描述构造方法,这个类型是“java.lang.reflect.Constructor”,就可以使用该类表示构造方法。但是如何取得这个表示构造方法的对象呢?使用堆区的Class类对象的方法:
·public Constructor getConstructor(Class<?>… parameterTypes)
【根据构造方法的参数类型的Class类对象取得构造方法,该参数是一个可变参数,参数表示构造方法的参数类型的对应的Class类对象】
public Constructor<?>[] getConstructors() throws SecurityException
Returns an array containing Constructor objects reflecting all the public constructors of the class
【返回所有的构造方法,这些构造方法保存到一个数组中返回】
DEMO:观察代码
package com.sun.vo;
import java.io.Serializable;
import java.util.Date;
public class Emp implements Serializable {
private Integer empno;
private String ename;
private String job;
private Double sal;
private Double comm;
private Integer mgr;
private Integer deptno;
private Date hierdate;
public Emp(Integer empno, String ename, String job, Double sal, Double comm, Integer mgr, Integer deptno, Date hierdate) {
this.empno = empno;
this.ename = ename;
this.job = job;
this.sal = sal;
this.comm = comm;
this.mgr = mgr;
this.deptno = deptno;
this.hierdate = hierdate;
}
public Emp(Integer empno, String ename, String job, Double sal, Double comm, Integer mgr, Integer deptno) {
this.empno = empno;
this.ename = ename;
this.job = job;
this.sal = sal;
this.comm = comm;
this.mgr = mgr;
this.deptno = deptno;
}
public Emp(Integer empno, String ename, String job, Double sal, Double comm, Integer mgr) {
this.empno = empno;
this.ename = ename;
this.job = job;
this.sal = sal;
this.comm = comm;
this.mgr = mgr;
}
public Emp(Integer empno, String ename, String job, Double sal, Double comm) {
this.empno = empno;
this.ename = ename;
this.job = job;
this.sal = sal;
this.comm = comm;
}
public Emp(Integer empno, String ename, String job, Double sal) {
this.empno = empno;
this.ename = ename;
this.job = job;
this.sal = sal;
}
public Emp(Integer empno, String ename, String job) {
this.empno = empno;
this.ename = ename;
this.job = job;
}
public Emp(Integer empno, String ename) {
this.empno = empno;
this.ename = ename;
}
public Emp(Integer empno) {
this.empno = empno;
}
@Override
public String toString() {
return "Emp{" +
"empno=" + empno +
", ename='" + ename + '\'' +
", job='" + job + '\'' +
", sal=" + sal +
", comm=" + comm +
", mgr=" + mgr +
", deptno=" + deptno +
", hierdate=" + hierdate +
'}';
}
}
package com.sun;
public class Test {
public static void main(String[] args) throws Exception {
//取得Emp堆区中的Class类对象
Class<?> empClass=Class.forName("com.sun.vo.Emp");
//实例化Emp类对象
Emp emp=(Emp)empClass.newInstance();
System.out.println(emp);
}
}
此时发现了没有找到无参数构造方法,所以我们知道使用newInstance()方法实例化对象的时候默认调用是无参数构造方法,因为我们为Emp提供了有参构造,就不会默认提供无参构造了,需要显示的定义出无参构造
DEMO:提供无参数构造方法
public Emp() {
}
此时默认调用了无参数构造方法,那么如何去调用有参数构造呢? 如果要调用有参构造那么需要使用反射取得有参数构造方法。
DEMO:取得所有的构造方法
要取得构造方法,那么我们要知道表示构造方法的类是“java.lang.reflect.Constructor”,里面的方法:
- public String getName():取得构造方法的名称
- public int getParameterCount():返回构造方法参数的个数
ackage com.sun;
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) throws Exception {
//取得Emp堆区中的Class类对象
Class<?> empClass=Class.forName("com.sun.vo.Emp");
//取得所有的构造方法
Constructor<?> [] cons=empClass.getConstructors();
for(int i=0;i<cons.length;i++){
System.out.println(cons[i].getParameterCount());
}
}
}
以上取得是所有构造方法,如何取得指定的某个构造方法呢?
DEMO:取得指定的某个构造方法
package com.sun;
import com.sun.vo.Emp;
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) throws Exception {
//取得Emp堆区中的Class类对象
Class<?> empClass=Class.forName("com.sun.vo.Emp");
//取得指定的构造方法
Constructor con1=empClass.getConstructor(Integer.class);
//使用该构造方法实例化对象
Emp emp1 =(Emp)con1.newInstance(1001);
System.out.println(emp1);
Constructor con2=empClass.getConstructor(Integer.class,String.class);
Emp emp2=(Emp)con2.newInstance(1002,"张三");
System.out.println(emp2);
Constructor con3=empClass.getConstructor(Integer.class,String.class,String.class);
Emp emp3=(Emp)con3.newInstance(1002,"李四","总裁");
System.out.println(emp3);
}
}
总结:
1、可以使用Class类对象的getConstructor()系列的方法取得构造方法对象
2、在反射中使用Constructor类表示构造方法。
3、getConstructor()只能取得类的public修饰的构造方法
4、getDeclaredConstructors()取得所有修饰符修饰的的构造方法
取得加载器
可以通过反射取得当前的类的加载器或者类的接口
DEMO:取得加载器
ackage com.sun;
public class Test {
public static void main(String[] args) throws Exception {
//取得反射对象
Class<?> empClass=Class.forName("com.sun.vo.Emp");
//使用反射取得Emp类的加载器
System.out.println(empClass.getClassLoader());
System.out.println(ClassLoader.getSystemClassLoader());
}
}
DEMO:取得类的接口
package com.sun;
public class Test {
public static void main(String[] args) throws Exception {
//取得反射对象
Class<?> empClass=Class.forName("com.sun.vo.Emp");
//使用反射取得Emp类的接口
System.out.println(empClass.getInterfaces()[1].getName());
}
}
DEMO:取得类的全名称和简单名称
package com.sun;
public class Test {
public static void main(String[] args) throws Exception {
//取得反射对象
Class<?> empClass=Class.forName("com.sun.vo.Emp");
//使用反射取得Emp类的名称
System.out.println(empClass.getName());
System.out.println(empClass.getSimpleName());
}
}
反射操作属性
在之前的课程中使用反射操作了构造方法,那么也可以使用反射操作属性,同样的要操作属性首先要取得属性,在Class类中提供了取得属性的相关方法,返回值类型的“java.lang.reflect.Field”
在Class类中取得属性的相关方法有:
- ublic Field getDeclaredField(String name)
【返回所有属性,参数是属性的名称】 - public Field getField(String name)
【返回public修饰的属性,参数是属性的名称】 - public Field[] getDeclaredFields()
【取得所有属性,返回的是一个数组】 - public Field[] getFields()
【取得所有public修饰的属性,返回的是一个数组】
以上的方法的返回值都与Filed类型有关,我们观察该类中的方法
|-public Object get(Object obj)
【取得属性值,参数是该属性所在的对象】
|-public String getName()
【取得属性名称】
|-public Class<?> getType()
【取得属性类型的Class类对象】
|-public void set(Object obj, Object value)
【为属性赋值,第一个参数:属性所在的对象,第二个参数:赋值的内容】
|-public void setAccessible(boolean flag)
【取消私有封装】
DEMO:操作属性
package com.sun;
import com.sun.vo.Emp;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Date;
public class Test {
public static void main(String[] args) throws Exception {
//取得反射对象
Class<?> empClass=Class.forName("com.sun.vo.Emp");
//创建一个Emp类对象
Constructor con=empClass.getConstructor(Integer.class,String.class,String.class,Double.class,Double.class,Integer.class,Integer.class, Date.class
);
Emp emp=(Emp)con.newInstance(1001,"张三","clerck",3000.00,1000.00,1002,10,new Date());
//取得属性对象
Field f=empClass.getDeclaredField("ename");
//取得属性的值(表示取得em对象的ename属性值)
System.out.println(f.get(emp));
}
}
DEMO:取消私有属性的封装
ackage com.sun;
import com.sun.vo.Emp;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Date;
public class Test {
public static void main(String[] args) throws Exception {
//取得反射对象
Class<?> empClass=Class.forName("com.sun.vo.Emp");
//创建一个Emp类对象
Constructor con=empClass.getConstructor(Integer.class,String.class,String.class,Double.class,Double.class,Integer.class,Integer.class, Date.class
);
Emp emp=(Emp)con.newInstance(1001,"张三","clerck",3000.00,1000.00,1002,10,new Date());
//取得属性对象
Field f=empClass.getDeclaredField("ename");
//为属性取消private权限
f.setAccessible(true);
//为属性重新复制
f.set(emp,"李四");
//取得属性的值(表示取得em对象的ename属性值)
System.out.println(f.get(emp));
}
}
DEMO:取得所有属性
package com.sun;
import com.sun.vo.Emp;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Date;
public class Test {
public static void main(String[] args) throws Exception {
//取得反射对象
Class<?> empClass=Class.forName("com.sun.vo.Emp");
//创建一个Emp类对象
Constructor con=empClass.getConstructor(Integer.class,String.class,String.class,Double.class,Double.class,Integer.class,Integer.class, Date.class
);
Emp emp=(Emp)con.newInstance(1001,"张三","clerck",3000.00,1000.00,1002,10,new Date());
//取得所有的属性
Field [] fs=empClass.getDeclaredFields();
for(Field f:fs){
f.setAccessible(true);
System.out.println(f.getName()+"="+f.get(emp));
}
}
}
总结:
1、操作属性其实还可以使用操作setter方法和getter方法实现属性的操作。
2、Constructor(构造方法的类)、Method(表示普通方法的类)、Field(表示属性的类),这三个类有一个共同的父类(java.lang.reflect.AccessibleObject)。
3、反射还可以操作注解,注解是后面的内容,当我们讲完注解的时候再使用反射进行操作。
反射操作普通方法
在上之前的课程中使用了反射操作构造方法,步骤是先取得构造方法,而且使用到一个表示构造方法的类型,普通方法也一样需要先使用Class类对象取得普通方法,同时也需要一个类表示普通方法,这个类是“java.lang.reflect.Method”。
先使用Class类对象取得普通方法:
- public Method[] getMethods() throws SecurityException
【取得所有public修饰普通方法】 - public Method getMethod(String name, Class<?>… parameterTypes)
【取得指定的public修饰的方法,第一个参数表示的是方法名称,第二个参数表示的是方法参数的Class类类型,主要是用来区分重载方法】 - public Method getDeclaredMethod(String name, Class<?>… parameterTypes)
【取得指定的任意方法,第一个参数表示的是方法名称,第二个参数表示的是方法参数的Class类类型,主要是用来区分重载方法】
以上所有方法的返回值都使用Method类表示的,当取得方法之后还需要使用该类中的方法实现我们取得的方法的调用,该类中的方法我们关注一个即可:
|-public String getName()【取得方法名称】
|-public Object invoke(Object obj, Object… args)
【调用方法,第一个参数:表示该方法作用对象,
第二个参数表示:调用方法需要传递的参数】
DEMO:操作普通方法
package com.sun;
import com.sun.vo.Emp;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
public class Test {
public static void main(String[] args) throws Exception {
//取得反射对象
Class<?> empClass=Class.forName("com.sun.vo.Emp");
//创建一个Emp对象
Emp emp=(Emp)empClass.getConstructor(Integer.class,String.class).newInstance(1001,"张三");
//取得指定的方法(setEname())
Method setEname=empClass.getMethod("setEname", String.class);
//执行setEname()方法
setEname.invoke(emp,"铁坚");
//取得getEname()方法
Method getEname=empClass.getMethod("getEname");
//调用getEname()方法
System.out.println(getEname.invoke(emp));
}
}
DEMO:观察代码
private void setEname(String ename) {
this.ename = ename;
}
private String getEname() {
return ename;
}
此时不能使用getMethod方法取得私有方法,它只能取得public修饰的方法,要使用“getDeclaredMethod”方法才能获取私有方法。
DEMO:取得私有方法
package com.sun;
import com.sun.vo.Emp;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
public class Test {
public static void main(String[] args) throws Exception {
//取得反射对象
Class<?> empClass=Class.forName("com.sun.vo.Emp");
//创建一个Emp对象
Emp emp=(Emp)empClass.getConstructor(Integer.class,String.class).newInstance(1001,"张三");
//取得指定的方法(setEname())
Method setEname=empClass.getDeclaredMethod("setEname", String.class);
//执行setEname()方法
setEname.invoke(emp,"铁坚");
//取得getEname()方法
Method getEname=empClass.getDeclaredMethod("getEname");
//调用getEname()方法
System.out.println(getEname.invoke(emp));
}
}
Exception in thread "main" java.lang.IllegalAccessException: Class com.sun.test.Test can not access a member of class com.sun.vo.Emp with modifiers "private"
以上已经取得了私有方法吗,但是不能调用,因为他是private修饰的,那么如果要调用,需要取消私有封装。
DEMO:取消封装
package com.sun;
import com.sun.vo.Emp;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
public class Test {
public static void main(String[] args) throws Exception {
//取得反射对象
Class<?> empClass=Class.forName("com.sun.vo.Emp");
//创建一个Emp对象
Emp emp=(Emp)empClass.getConstructor(Integer.class,String.class).newInstance(1001,"张三");
//取得指定的方法(setEname())
Method setEname=empClass.getDeclaredMethod("setEname", String.class);
//执行setEname()方法
setEname.setAccessible(true);
setEname.invoke(emp,"铁坚");
//取得getEname()方法
Method getEname=empClass.getDeclaredMethod("getEname");
//调用getEname()方法
getEname.setAccessible(true);
System.out.println(getEname.invoke(emp));
}
}
总结:
1、可以通过堆区的Class对象取得方法,取得的方法使用一个类“Method”类表示
2、如果要执行方法需要使用Method的invoke(Object,Class…parameters)
【第一个参数:表示方法作用的对象,第二个参数:调用方法需要传递的参数的Class类类型】
3、私有方法需要使用getDeclaredMethod()方法取得,取得之后如果要调用需要取消私有封装