Java 反射

之前对反射总是有点迷糊,所以想系统的梳理一下,如有偏差,还望大家指正。

什么是反射?

在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法(包括私有的),对于任意一个对象,都能调用它的任意一个方法.这种动态获取信息,以及动态调用对象方法的功能叫java语言的反射机制.

概念总是晦涩难懂的,下面我用我的理解来解释一下:

要理解反射,首先我们必须明白一个对象产生的过程:我们所写的每个类都会被编译成一个二进制的字节码文件,以 类名.class 的形式保存在Java工程的bin文件夹下,当我们要运行某个程序的时候,Java虚拟机就会从硬盘上加载对应的类的字节码文件到虚拟机内存中,这就是我们常说的类加载。

那么具体是怎么加载的呢?
我们Java程序员常说一切皆对象,同一类事物都可以用类来描述。比如:为了描述人这类事物,我们可以定义一个Person类,抽象出人类的一些共同特点作为类的属性和方法。同样的,我们刚才所说Java类所对应的这些 .class文件也是一类具有共性的事物,Java语言使用 java.lang.Class 这个类来描述这一类文件,在加载我们所写的类的时候,本质上是创建了一个java.lang.Class类的实例,这个实例就是对应该的 .class 文件的对象,我们就拿Person这个类举例,有了这个 Person.class 这个文件对应的Class类的实例,我们便可以调用该实例的方法,得到我们所需要的Person类的实例。

这里写图片描述

//运行结果:
com.mumu.refectTest.Person@b92dc2
com.mumu.refectTest.Person@16b4e30

//以上结果说明objec 和 p 这两个对象都是Person的实例。

特别说明一下 :Class中的静态方法forName()会根据给定的类名在工程的classpath下寻找对应的 .class 文件并加载,我们可以在工程文件夹下看到 .classpath 文件,一般里边默认的classpath为src或bin,所以我们参数中要写清包名+类名的形式,只写类名找不到

反射机制说的就是我们可以通过获得某个类对应的Class对象(或实例)来创建该类的实例,或者操作其包含的所有属性和方法。

得到某个类的Class对象的方式有以下三种:

public static void main(String[] args) throws ClassNotFoundException {
    //1.根据类名加载 .class 文件来获取对应的Class对象
    Class c1=Class.forName("com.mumu.refectTest.Person");
    //2.通过类静态属性直接获取其对应的Class对象
    Class c2=Person.class;

    Person p=new Person();
    //3.通过类的实例获取其对应的Class对象
    Class c3=p.getClass();

    System.out.println(c1==c2&&c2==c3);
}

//运行结果:
true

通过运行结果我们可以看出一个类只有一个与之对应的Class实例,这里我可以告诉大家,在初次使用到该类的时候,类加载器首先会检查这个类的Class对象是否已被加载过,如果尚未加载,默认的类加载器才会根据类名查找对应的.class文件,进行加载。

在看下面的内容之前,大家有必要知道,在万物皆对象的Java世界中,一个类的构造函数、字段(属性)、方法也有被抽象成了对应的类:Constructor, Field, Method,有了这个心理准备,我们继续

通过反射用带参数的构造函数产生新对象

上边我们通过Class对象中的newInstance()方法创建过对应类的实例,该方法默认调用类的无参构造函数,但是如果我们想调用该类带参数的构造函数呢?这时候就需要得到该类的构造器对象了

//定义一个Person类
public class Person {
    private String name;
    private int age;

    public Person() {
        super();
    }

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
}

public static void main(String[] args) throws Exception {
    //加载类
Class clazz=Class.forName("com.mumu.refectTest.Person");
    //获取参数列表为(String,int)的构造器对象
Constructor con = clazz.getConstructor(String.class,int.class);
    //通过构造器对象创建对象
Object obj = con.newInstance("小明",18);
}

通过反射获取对象的字段,包括私有的

用到的主要方法有:
Class 类中的:
getField(String name) 根据字段名获取公共字段对象
getDeclaredField(String name) 根据字段名获取任意权限字段
对象

Field 类中的:
set(Object obj,Object value)将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
get(Object obj)返回指定对象上此 Field 表示的字段的值。
setAccessible(Boolean bol);取消该字段的访问权限检查

//创建一个Person类
public class Person {
    public String name; //公有字段
    private int 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 static void main(String[] args) throws Exception {
    Class clazz = Class.forName("com.mumu.refectTest.Person");
    Object obj = clazz.newInstance();

    //获取name字段对象
    Field nameField = clazz.getField("name");
    System.out.println("Person类中的name字段对象:\n"+nameField);

    //等效于obj=(Person)obj; person.setName("小明")
    nameField.set(obj, "小明");
    //等效于obj=(Person)obj; obj.getName()
    System.out.println("name:"+nameField.get(obj));

    //getField(String name)只能获取public的字段
    //对私有字段会报错:java.lang.NoSuchFieldException
    //Field ageField = clazz.getField("age"); 

    //getDeclaredField(String name)可以获得任何权限的字段对象
    Field ageField = clazz.getDeclaredField("age"); 

    //对私有字段的访问取消权限检查,暴力访问
    ageField.setAccessible(true);
    //如果不进行取消权限检擦操作,
    //则报错:java.lang.IllegalAccessException
    ageField.set(obj, 10);
    System.out.println("age:"+ageField.get(obj));

    //上边代码等效于:
    /*
     Person p=new Person();
     p.setName("小明");
     System.out.println(p.getName());
     p.setAge(10);
     System.out.println(p.getAge());
     */
}

//运行结果:
Person类中的name字段对象:
public java.lang.String com.mumu.refectTest.Person.name
name:小明
age:10

通过反射获取对象的方法,包括私有的

涉及的主要方法:
Class 类中的:
getMethods() 返回Method[] 包含该类的所有方法,包括父类中的
getDeclaredMethods() 返回Method[] 只包含该类中申明过的所有方法,不包括父类中的
getMethod(String name, Class… paramaterTypes)获取带参方法

Method 类中的:
invoke(Object obj, 实参列表) 调用某个实例的对应方法
setAccessible(boolean bol) 取消对应方法的权限检查

//定义Person类
public class Person {
    public String name;
    private int age;

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public void show(){
        System.out.println("无参公有方法:"+name+":"+age+"...show run......");
    }

    public void paramMethod(String message,boolean isShow){
        if(isShow)
            System.out.println("有参公有方法:"+"message:"+message);
    }

    private void myPrivateMethod(){
        System.out.println("私有方法:privateMethod run .......");
    }
}

/**
 * 主函数:通过反射,获取对象的方法并调用
 */
public static void main(String[] args) throws Exception {
    Class clazz = Class.forName("com.mumu.refectTest.Person");
    Constructor con=clazz.getConstructor(String.class,int.class);
    Object obj = con.newInstance("小红",25);

    //获取空参数的公共方法
    Method m1 = clazz.getMethod("show", null);
    //调用方法
    m1.invoke(obj, null);

    //获取带参数的公共方法
    Method m2 = clazz.getMethod("paramMethod", String.class,boolean.class);
    m2.invoke(obj, "hello world!",true);

    //获取私有方法
    Method m3 = clazz.getDeclaredMethod("myPrivateMethod", null);
    m3.setAccessible(true);
    m3.invoke(obj, null);
}

//运行结果:
无参公有方法:小红:25...show run......
有参公有方法:message:hello world!
私有方法:privateMethod run .......

反射机制的实际应用场景

看完上边反射机制的使用,是不是发现这比实际new对象要复杂很多,那为什么还要用反射呢?这里的复杂是为了的到更好的可扩展性。举一个例子:如果一个应用一直在线上运行,我们又想扩展该应用的一些功能,我们一般不会停止应用的运行,更好的方法是为可能扩展的功能预留好对应接口,将扩展功能的类从配置文件里读取读取加载。

例:一个显示不同动物叫声的系统,动物的种类随着发现不断增加。阅读下边代码,我们会发现,不管我们后期要增加多少种动物,我们要做的只是增加对应的类实现Animal接口,修改配置文件,而不用修改原来写好的代码,如这里的主函数。

//申明一个接口Animal
interface Animal{
    //所有动物都有自己的叫声
    public void cry();
}
//这是后期增加的代码
class Cat implements Animal{
    public void cry() {
        System.out.println("猫:喵。。。喵。。。");
    }
}
class Duck implements Animal{
    public void cry() {
        System.out.println("鸭子:嘎嘎。。。");
    }
}

//主方法:
public static void main(String[] args) throws Exception {
    Properties p=new Properties();

    //从 .properties配置文件中读取要加载的类   
        p.load(ClassLoader.getSystemResourceAsStream("animals.properties"));
    String s = p.getProperty("animals");
    //获取每个类的全限定名(类名+包名)
    String[] animals = s.split(",");
    for(String an : animals ){
        //按照预留接口,强转成所需类型
        Animal animal = (Animal)Class.forName(an).newInstance();
        animal.cry();
    }
}

配置文件内容:

这里写图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值