Java反射机制简介

为什么要使用反射

需求: 书写一个工具方法,获取打印传入的 JavaBean 中的数据.

//测试代码
@Test
public void testUtil(){
    User user = new User("小普",18);
    //调用工具方法打印对象
    JavaBeanUtil.printJavaBean(user);
    // JavaBeanUtil.printJavaBean(person);

}

//工具类
class JavaBeanUtil{
    public static void printJavaBean(Object obj){
        //如果知道调用者传入的真实类型是User,代码如下
        User user = (User)obj;
        System.out.println(user.getName()); System.out.println(user.getAge());
    }
    
}

上面代码存在的问题:

  1. printJavaBean 方法中要获取传入的 User 对象的数据,就必须强转.
  2. 写的是工具类,在不知调用者带了什么对象时,无法强转,强转则有风险.
  3.  printJavaBean 方法中已经拿到对象了,得不到对象的真实类型.

解决方案: 使用反射 (可以通过一个对象,获取到它的真实类型以及调用该对象中的方法).

反射

在程序运行过程中,通过字节码对象,去获取到类中的成员信息(构造器,方法,字段),这就称为反射.

目的是通过程序自动获取构造器来创建对象.获取到方法并调用等等.

字节码对象

 Java ,万物皆对象.我们可以通过多个事物,发现他们的共性,来抽象成一个类,类就是对象的模板,而一个个的个体,就是对象. 比如人类和学生.

字节码也是真实存在的文件,每个字节码都是一个个例, JVM 要来存放这些字节码就需要抽象成模板,再通过模板来创建对象,存放每份字节码的信息.当要使用某份字节码时(例如要创建Person对象),就从 JVM 中调出存了Person.class 内容的Class 对象,然后拿去创建 Person 对象.

JDK 中定义好的 Class : java.lang.Class

该类中有大量的 get 开头的方法.表示可以使用字节码对象来获取信息.所以当我们拿到了字节码对象,就可以直接操作当前字节码中的构造器,方法,字段.

获取字节码对象

通过 API ,我们得知 Class ,没有公共的构造器,其原因是Class 对象是在加载类时由 Java 虚拟机自动构造的。

获取字节码的方式:

  1. 通过 Class 类的 forName() 方法来获取字节码对象

Class.forName(String classsName) : 通过类的全限定名获取字节码对象

 全限定名: 包名.类型

Class.forName("java.lang.String"); // JVM 中存在则返回,不存在则加载

2.通过对象的 getClass() 方法来获取字节码对象

new User().getClass(); 使用的是父类 Object 中的 getClass() 方法

3.通过类型(基本类型) class 字段来获取字节码对象

  int.class

上面三种方式应用都比较多,尤其是第一种方式,通过 Class.forName,大量的在框架中使用.

实例:

思考:

  1. 三种方式获取到的字节码是同一个吗?
  2. int 类型和 int[] 它们的字节码是同一个吗?
    @Test
    public void testGetClass() throws Exception { // 1 通过类的全限定名 Class.forName();
        
        Class clz1 = Class.forName("cn.wolfcode._01_reflect.Person");
        //2 通过对象的getClass() 方法 Person p = new Person(); Class clz2 = p.getClass();
        //3 通过class 字段去获取
        Class clz3 = Person.class;
        //字节码只会加载一次,所有不管用的哪种方式去获取字节码,都是同一个
        System.out.println(clz1 == clz2);	//true
        System.out.println(clz2 == clz3);	//true
        System.out.println(clz1 == clz3);	//true
        //int 类型和int数据类型不是同一个
        System.out.println(int.class); System.out.println(int[].class);
    }

获取构造器

正常调用方法的流程

  1.  static 修饰的方法: 创建对象,调用方法
  2. static 修饰的方法 : 类名调用方法

使用反射其目的无外乎就是使用程序动态操作类的成员,比如方法,而要操作方法首先得有对象,而对象是通过构造器来创建的,所以需要先获取构造器.

反射操作构造器

使用反射获取构造器 :

获取所有的构造器 public Constructor<?>[] getConstructors(): 获取所有的 public 修饰的构造器

public Constructor<?>[] getDeclaredConstructors(): 获取所有的构造器(包括非public)

 获取指定的构造器 public Constructor getConstructor(Class... parameterTypes) public Constructor getDeclaredConstructor(Class... parameterTypes): parameterTypes : 参数的类型(构造方法的参数列表的类型).

注意: 找构造器/方法,传递的是参数的类型.

结论 : 带着 s 表示获取多个.带着 Declared 表示忽略权限,包括私有的也可以获取到.

代码:

@Test

public void testGetConstructor() throws NoSuchMethodException {

    //获取字节码对象

    Class clz = Person.class;

    // 1 获取所有 public 构造器

    Constructor[] cons1 = clz.getConstructors(); for(Constructor con : cons1){
        System.out.println(con);

    }

    System.out.println("--------");

    // 2 获取所有构造器,包括 private

    Constructor[] cons2 = clz.getDeclaredConstructors(); for(Constructor con : cons2){
        System.out.println(con);

    }
    //3 获取无参构造器


    //4 获取带参构造器

    Constructor con2 = clz.getConstructor(Long.class, String.class);                             
     System.out.println(con2);
        // 5 获取指定 private 构造器

    Constructor con3 = clz.getDeclaredConstructor(String.class); System.out.println(con3);
}

常见错误: 参数不匹配,报错.找不到指定的构造器

调用构造器创建对象

实战:

@Test

public void testCreateObject() throws Exception {

    //获取字节码对象

    Class clz = Class.forName("cn.wolfcode._04_reflect.Person");

    3.获取带参数构造器,参数为参数类型

    Constructor con1 = clz.getConstructor(Long.class, String.class); //调用构造器

    Object obj = con1.newInstance(1L, "小狼");

    System.out.println(obj);

    //获取带有参数的 private 构造器

    Constructor con2 = clz.getDeclaredConstructor(String.class);

    //调用私有构造器,必须先设置为可访问 con2.setAccessible(true);

    Object obj2 = con2.newInstance("小码"); System.out.println(obj2);

}

注意: 不能直接访问没有权限(public)的成员,如果想要使用反射去操作非public的成员.必须设置一个可以访问的标记.

public void setAccessible(boolean flag): 传递一个true,表示可以访问,表示不管权限.

 API 中我们可以发现,Constructor,Field,Method AccessibleObject 的子类,因为这三种成员都是可以被访问private 修饰符修饰的.

代码:


@Test

public void testCreateObject() throws Exception {

//获取带有参数的 private 构造器

Constructor con2 = clz.getDeclaredConstructor(String.class);

//调用私有构造器,必须先设置为可访问 con2.setAccessible(true);

Object obj2 = con2.newInstance("小码"); System.out.println(obj2);

}

创建对象的快捷方式: 调用公共无参构造器

一般来说,大多数类都有公共的无参数的构造器.使用反射创建对象,需要 1.找到公共的无参数的构造器, 2.

调用构造器的newInstance.

SUN公司为了方便我们调用公共的无参数的构造器来创建对象,提供了一个方便的方法. Class对象.newInstance();

经验 : 只要看到传入全限定名,基本上都是要使用反射,通过全限定名来获取字节码对象. 只要看到无指定构造器但是能创建对象,基本上都是要通过字节码对象的 newInstance 去创建对象.

获取操作方法

在反射中,使用 Method 类来描述方法这一类事物.

反射获取方法

获取所有方法:

public Method[] getMethods(): 可以获取到所有的公共的方法,包括继承的.+

public Method[] getDeclaredMethods():获取到本类中所有的方法,包括非public,不包括继承的.

获取指定的方法:

public Method getMethod(String name, Class<?>... parameterTypes):

public Method getDeclaredMethod(String name, Class<?>... parameterTypes): name: 方法名

parameterTypes: 当前方法的参数列表的类型.

 注意,要找到某一个指定的方法,必须要使用方法签名才能定位到.而方法签名=方法名+参数列表,还记得我们获取构造器的经验吗?带着s表示获取多个,带着declared表示忽略访问权限.

1 获取所有 public 方法,包括父类的 2 获取所有方法,包括 private 不包括父类的 3 获取指定参数的 public 的方法,包括父类的 4 获取指定参数的private 方法,不包括父类的

@Test

public	void testGetMethod() throws Exception {

//1 获取字节码对象

5.2 获取构造器来创建对象

6.3 获取方法

//1 获取所有 public 方法,包括父类的

Method[] methods = clz.getMethods();

for(Method m : methods){

System.out.println(m);

}

System.out.println("---------");

//2 获取所有方法,包括 private 不包括父类的

Method[] methods2 = clz.getDeclaredMethods(); for(Method m : methods2){
System.out.println(m);

}

System.out.println("---------");

//3 获取指定参数的 public 的方法,包括父类的

Method sayHelloMethod = clz.getMethod("sayHello", String.class); System.out.println(sayHelloMethod);
//4 获取指定参数的private 方法,不包括父类的

Method doWorkMethod = clz.getDeclaredMethod("doWork", String.class); System.out.println(doWorkMethod);

}

调用方法

API :

public Object invoke(Object obj, Object... args): obj: 表示调用该方法要作用到那个对象上. args:调用方法的实际参数 方法的返回值表示,调用该方法是否有返回值,如果有就返回,如果没有,返回null.

传统创建对象调用方法: Person p = new Person(1L, "小狼"); p.sleep(5);// 小狼,,5个时辰.

使用反射创建对象调用方法: Method m = clz.getMethod(“sleep”, int.class);// 找到sleep方法.

m.invoke(obj, 5);// ,小狼,5个时辰.

注意:

  1. 方法也是可以被访问私有修饰符修饰的,所以,如果要访问非 public 修饰的方法,需要在访问之前设置可访问 method.setAccessible(true);
  1. 如果调用的是静态方法,是不需要对象的,所以此时在invoke方法的第一个参数,对象直接传递一个 null 即可.

Method staticSayHelloMethod = clz.getDeclaredMethod("sayHello", String.class, Long.class);

  1. 不需要对象去调用,但是参数必须加上null,不然会把后面的参数作为调用方法的对象了. staticSayHelloMethod.invoke(null,"小明",1L);
public class Person {

private Long id;

private String name;

public Person() {

}

public Person(Long id, String name) {

this.id = id;

this.name = name;

}

private Person(String name) {

this.name = name;

}

public void sayHello(){

System.out.println("hello");

}

public String sayHello(String name){

System.out.println(name + ": hello");

return "您好";

}

public static void sayHello(String name,Long id){ System.out.println("调用静态方法");

}

private void doWork(){

System.out.println("doWork");

}

private void doWork(String name){

System.out.println(name + ": doWork");

}

5.getter方法 setter 方法 public String toString() { return "Person{" +

"id=" + id +

", name='" + name + '\'' + '}';

}

}

操作字段

在反射中,使用 Field 去描述字段这一类事物.

获取字段

获取单个字段:

public Field getField(String name) : name 要获取的字段的名称 public Field getDeclaredField(String name) :

获取所有字段

public Field[] getFields() public Field[] getDeclaredFields()

@Test

public void testField() throws Exception {

//1 获取字节码对象

Class clz = Person.class;

Object obj = clz.newInstance();

//2 获取字段

Field[] fs = clz.getFields();

for(Field f: fs){

System.out.println(f);

}

Field[] fs2 = clz.getDeclaredFields();

for(Field f: fs2){

System.out.println(f);

}

5.获取单个字段

Field nameField = clz.getDeclaredField("name"); System.out.println(nameField);
}

操作字段

get(Object obj);

set(Object obj,Object value)

//设置私有字段可访问

nameField.setAccessible(true);

//操作name字段

//设置那么字段的数据nameField.set(obj,"小狼");

//获取name字段的数据

Object nameValue = nameField.get(obj);

System.out.println(nameValue);

  • 17
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值