反射(2)

反射

反射是Java体系中非常重要的内容,它被称为框架设计的灵魂,这里我们简单介绍下框架的概念。

框架:半成品软件,因为是半成品软件,所以框架不能独立的运行,我们在框架的基础上进行软件的开发,可以简化编码的动作,编写完整的软件。

在掌握反射后学习框架部分时就能有更深入的了解。

反射:将类的各个组成部分封装为其他对象,这就是反射机制。

这句话有点难以理解,我们讲解一下Java代码在计算机汇总经历的三个阶段:

在这里插入图片描述接下来我们对这三个阶段进行逐一讲解:

1.Source源代码阶段

我们在计算机中编写好了一个Java文件,这个文件中是我们编写好的类Person类(不是测试类,所以类中不存在主方法),这个类中存在Person类的成员变量,成员方法以及构造方法,文件以.java后缀结尾。

当然这个文件还不能运行,在运行前还有一个步骤:编译,我们使用Java中自带的编译器,通过javac命令来编译以.java为后缀的Java文件,如果.java文件中没有语法问题,在编译后会形成一个以.class后缀的字节码文件,这个字节码文件中包含三部分比较重要的东西:成员变量、成员方法以及构造方法,当然不止这三部分,还有类的名称等等。

但是不论是以.java后缀还是以.class后缀的文件,他们本质上都是文件,文件肯定都存在于硬盘之上,并没有进入内存,我们把这个阶段称为Java代码在计算机中的第一个阶段。

2.Runtime运行时阶段

在我们以前的编程经验中,如果要使用一个已经编写好的类,我们直接创建这个类的对象,通过调用这个对象的成员方法就达到使用这个类的目的。

那么在上述讲解中,如果要使用Person类,肯定是new Person();,之后通过对象.方法名来使程序运行。而此时的对象肯定是在内存中,这样我们才能对对象进行调用,对象在内存中的前提是Person类的字节码文件被加载进内存,这样我们才能创建Person类的对象,所以第二个阶段就是字节码文件加载进内存的过程。

3.Class类对象阶段
通过类加载器可以将Person.class字节码文件加载进内存中,在内存中如果我们要描述这个字节码文件该如何处理呢?

在Java中万物皆对象,存在一个Class类,这个类专门用来描述各种字节码文件,因为这个类存在各种字节码文件的一些共同特征:成员变量(获取值或修改值)、成员方法(运行程序)以及构造方法(创建对象)等等,当然我们主要研究的是成员变量、成员方法以及构造方法,这三样东西有他们各自的用途和特征,为了统一描述,我们将这三部分封装为其他的对象,成员变量封装为Field对象,构造方法封装为Constructor对象,成员方法封装为Method对象。一个类中可能有多个成员变量、成员方法和构造方法,所以也可以封装为相应对象的数组,至此我们通过Class类的对象可以完全的描述一个字节码文件了。

一个Class类的对象完全对应一个字节码文件,也就是说一个类在计算机中只有一个Class对象,这个在后面会验证。

在Runtime运行时阶段,我们就可以通过类的Class对象来进行对象的创建等操作,也就是说我们在第三阶段创建的Person类对象实际上都是通过操作Person类的Class对象来完成的,只不过是计算机帮我们完成的,我们以前并没有关注过。

上面就是Java文件在计算机中所经历的三个阶段,反射就是将类的各个组成部分封装为其他对象。

那么使用反射有什么好处呢?

  1. 在程序运行过程中操作这些对象,比如我们在使用IDE编写程序时,当创建一个对象后,如果再次输入这个对象IDE就会自动给我们进行提示,提示包括这个对象可以执行的成员方法,这其实就是将Class对象的Method对象数组提取出来后将各个Method对象所包含的成员方法的名字展示出来即可,这就是反射在现实中的一个应用
  2. 可以解耦,降低程序的耦合性,提高程序的可扩展型
  3. 等等…

获取字节码Class对象的三种方式

在上面我们讲解了反射的基本概念后,我们就可以获取类的Class对象,通过这个对象可以进行其他的一些操作

那么如何获取Class对象呢?有三种方法,每种方法对应Java代码在计算机中的一个阶段:

  1. 字节码文件在硬盘中,我们需要将字节码文件手动加载进内存中后生成一个该类的Class对象:Class.forName("全类名");,返回Class对象。
  2. 字节码文件已经加载进内存中,此时已经有了类的Class对象,我们直接获取即可:类名.class;,返回Class对象。
  3. 此时已经有了类的对象,我们通过这个对象获取类的Class对象:对象.getClass;,这中方法定义在Object类中,所有对象都可以使用这种方法获取对应类的Class对象。

下面我们进行演示:

我们在westos目录下创建一个reflect目录
在这里插入图片描述
在这个目录下我们编写一个类ReflectDemo1,在这个类中我们进行类的Class对象的获取,我们还需要在westos目录下创建一个pojo目录,这个目录中存放的是我们在测试中需要的一些实体类,在pojo目录下编写Person

Person类的内容为:

package com.westos.pojo;

public class Person {
    private String name;
    private int age;

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

    public Person() {

    }

    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;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

编写完该类后,这个文件被自动编译为Person.class字节码文件,但是这个文件依然存在于硬盘之中,我们在ReflectDemo1中获取Person类的Class对象。

package com.westos.reflect;

import com.westos.pojo.Person;

public class ReflectDemo1 {
    public static void main(String[] args) throws Exception {
        //1.Class.forName(全类名)获取Class对象
        Class cls = Class.forName("com.westos.pojo.Person");
        System.out.println(cls);
        //2.类名.class
        Class cls1 = Person.class;
        System.out.println(cls1);
        //3.对象.getClass()
        Person person = new Person();
        Class cls2 = person.getClass();
        System.out.println(cls2);
    }
}

在编写这段代码的过程中可能会抛出异常,我们将异常修改为 Exception ,运行代码,运行结果为:

class com.westos.pojo.Person
class com.westos.pojo.Person
class com.westos.pojo.Person

现在只能证明Person类的三个Class对象的字符串表现形式是一致的,并不能表示这三个Class对象是一样的,我们通过算术运算符“==”进行验证

System.out.println(cls == cls1);
System.out.println(cls1 == cls2);

结果为:

true
true

所以可以证明这三个Person类的Class类的对象实际上是同一个对象,也就是说一个类在内存中只存在一个Class类的对象,字节码文件在程序的一次运行过程中只被加载进内存中一次。

获取Class对象的三种方式有自己比较适用的场合:

  1. Class.forName("全类名");
    多用于配置文件,把全类名定义在配置文件中,读取文件,加载类
  2. 类名.class;
    多用于参数的传递
  3. 对象.getClass;
    多用于对象的获取字节码的方式

Class对象功能概述

获取Class对象后,我们就可以使用这个对象

  1. 获取成员变量们
    • Field[] getFields()
    • Field getField(String name)
    • Field[] getDeclaredFields()
    • Field getDeclaredField(String name)
  2. 获取成员方法们
    • Method[] getMethods()
    • Method getMethod(String name, Class<?>... parameterTypes)
    • Method[] getDeclaredMethods()
    • Method getDeclaredMethod(String name, Class<?>... parameterTypes)
  3. 获取构造方法们
    • Constructor<?>[] getConstructors()
    • Constructor<T> getConstructor(Class<?>... parameterTypes)
    • Constructor<?>[] getDeclaredConstructors()
    • Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
  4. 获取类名
    • String getName()

我们对上面的方法进行演示:

获取成员变量

我们在reflect目录下创建ReflectDemo2类,先测试获取成员变量

package com.westos.reflect;

import com.westos.pojo.Person;

import java.lang.reflect.Field;

public class ReflectDemo2 {
    public static void main(String[] args) {
        //获取Person类的Class类的对象
        Class cls = Person.class;
        //获取成员变量
        Field[] fields = cls.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }
    }
}

运行程序:
在这里插入图片描述没有打印出结果,这是因为这个getFields()方法只能获取被public修饰符修饰的成员变量,getField(String name)也是只能打印指定的被public修饰符修饰的成员变量。

我们将Person类的成员变量的权限修饰符进行修改

public String name;
public int age;

运行上述ReflectDemo2中的主方法,运行结果为:

public java.lang.String com.westos.pojo.Person.name
public int com.westos.pojo.Person.age

我们获取到了Person类的两个成员变量,在获取到后我们就可以进行设置值获取值

  1. Object get(Object obj)
  2. void set(Object obj, Object value)

要注意这两个方法都需要一个Object obj,这是因为成员变量值存在于对象之中,我们要设置成员变量需要指定设置和获取哪个对象的成员变量,所以我们需要给这两个方法中传入对象名,我们对ReflectDemo2文件进行相应的修改

package com.westos.reflect;

import com.westos.pojo.Person;

import java.lang.reflect.Field;

public class ReflectDemo2 {
    public static void main(String[] args) throws Exception {
        //获取Person类的Class类的对象
        Class cls = Person.class;
        Person person = new Person();
        person.setAge(8);
        //获取成员变量
        Field age = cls.getField("age");
        int a = (int) age.get(person);
        System.out.println(a);
    }
}

运行上面的代码,运行结果为:

8

获取值成功

我们对age成员变量进行设置,在ReflectDemo2中添加下面的代码:

age.set(person, 20);
System.out.println(person.age);

运行程序,运行结果为:

8
20

设置值成功

接下来讲解 getDeclaredFields()方法和getField()方法的区别:

我们将Person类中的成员变量的权限修饰符进行修改:

private String name;
private int age;

ReflectDemo2也进行修改

package com.westos.reflect;

import com.westos.pojo.Person;

import java.lang.reflect.Field;

public class ReflectDemo2 {
    public static void main(String[] args) throws Exception {
        //获取Person类的Class类的对象
        Class cls = Person.class;
        //获取成员变量
        Field[] fields = cls.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field);
        }
    }
}

运行上述程序,查看运行结果

private java.lang.String com.westos.pojo.Person.name
private int com.westos.pojo.Person.age

可以看到我们获取到了被private权限修饰符修饰的成员变量,所以可以看到 getDeclaredFields()方法和getField()方法的区别, getDeclaredFields()方法可以获得所有的成员变量包括私有的。在获取私有成员变量后我们就可以对这个成员变量进行获取值和设置值的操作。

我们下面进行演示,对ReflectDemo2中的内容进行修改

package com.westos.reflect;

import com.westos.pojo.Person;

import java.lang.reflect.Field;

public class ReflectDemo2 {
    public static void main(String[] args) throws Exception {
        //获取Person类的Class类的对象
        Class cls = Person.class;
        //获取成员变量
        Field age = cls.getDeclaredField("age");
        Person person = new Person();
        age.set(person, 10);
        Object o = age.get(person);
        System.out.println(o);
    }
}

运行程序,运行结果为:

C:\JAVA\Java\jdk1.8.0_152\bin\java.exe -javaagent:C:\IntelliJIDEA2018.2.6_install\lib\idea_rt.jar=63398:C:\IntelliJIDEA2018.2.6_install\bin -Dfile.encoding=UTF-8 -classpath C:\JAVA\Java\jdk1.8.0_152\jre\lib\charsets.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\deploy.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\access-bridge-64.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\cldrdata.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\dnsns.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\jaccess.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\jfxrt.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\localedata.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\nashorn.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\sunec.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\sunjce_provider.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\sunmscapi.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\sunpkcs11.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\zipfs.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\javaws.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\jce.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\jfr.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\jfxswt.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\jsse.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\management-agent.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\plugin.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\resources.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\rt.jar;C:\IdeaProjects\Juntit测试\out\production\Juntit测试;C:\maven\apache-maven-3.6.1\Repository\junit\junit\4.12\junit-4.12.jar;C:\maven\apache-maven-3.6.1\Repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar com.westos.reflect.ReflectDemo2
Exception in thread "main" java.lang.IllegalAccessException: Class com.westos.reflect.ReflectDemo2 can not access a member of class com.westos.pojo.Person with modifiers "private"
	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
	at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
	at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
	at java.lang.reflect.Field.set(Field.java:761)
	at com.westos.reflect.ReflectDemo2.main(ReflectDemo2.java:14)

Process finished with exit code 1

程序报错,出现了非法访问异常,所以我们虽然获取到了私有成员变量,但我们并不能对私有成员变量进行设置值和修改值的操作,我们需要干一件事情,在使用私有成员变量之前我们需要忽略访问权限修饰符的安全检查,这就需要一句代码:field.setAccessible(true);,这也叫做暴力反射,我们在ReflectDemo2中添加这段代码:

age.setAccessible(true);

运行程序,运行结果为:

10

成功设置和获取值,在反射面前,类的私有成员变量将不再私有

以上就是对反射中获取类的成员变量的讲解

获取构造方法

我们接下来讲解通过反射获取类的构造方法

ReflectDemo2中的内容进行更改

package com.westos.reflect;

import com.westos.pojo.Person;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

public class ReflectDemo2 {
    public static void main(String[] args) throws Exception {
        //获取Person类的Class类的对象
        Class cls = Person.class;
        //获取构造方法
        Constructor constructor = cls.getConstructor(String.class, int.class);
        System.out.println(constructor);

    }
}

运行程序,运行结果为:

public com.westos.pojo.Person(java.lang.String,int)

获取构造方法成功,我们可以通过构造方法来创建对象,查看API文档,获得创建对象的方法为:

 T newInstance(Object... initargs) 

我们用一用这个方法,对ReflectDemo2中的内容进行修改

 package com.westos.reflect;

import com.westos.pojo.Person;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

public class ReflectDemo2 {
    public static void main(String[] args) throws Exception {
        //获取Person类的Class类的对象
        Class cls = Person.class;
        //获取构造方法
        Constructor constructor = cls.getConstructor(String.class, int.class);
        Object o = constructor.newInstance("强静州", 20);
        System.out.println(o);
        System.out.println(constructor);

    }
}

运行上面的程序,运行结果为:

Person{name='强静州', age=20}
public com.westos.pojo.Person(java.lang.String,int)

我们可以看到对象已经创建成功,当然,上面使用的是类的有参构造,我们尝试一下使用空参构造,在ReflectDemo2中添加下面的代码

Constructor constructor1 = cls.getConstructor();
Object o1 = constructor1.newInstance();
System.out.println(o1);

运行程序,可以看到运行结果为:

Person{name='强静州', age=20}
Person{name='null', age=0}
public com.westos.pojo.Person(java.lang.String,int)

空参构造方法创建对象成功,但是在反射机制中,为空参构造创建对象提供了一种简化操作:Class对象的newInstance()方法,我们删除上面添加的代码后添加下面所示的代码:

Object o1 = cls.newInstance();
System.out.println(o1);

运行程序可以看到运行结果为:

Person{name='强静州', age=20}
Person{name='null', age=0}
public com.westos.pojo.Person(java.lang.String,int)

空参构造方法创建对象成功

如果使用私有的构造器来创建对象,我们也需要暴力反射,和成员变量类似,这里不做过多讲解

获取成员方法

我们在Person类中添加一个方法:

public void eat(){
    System.out.println("我正在吃");
}

当然通过反射获取成员方法的方法中有无Declared和成员变量和构造方法的效果类似

我们对ReflectDemo2中的内容进行修改:

package com.westos.reflect;

import com.westos.pojo.Person;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectDemo2 {
    public static void main(String[] args) throws Exception {
        //获取Person类的Class类的对象
        Class cls = Person.class;
        //获取成员方法
        Method eat_method = cls.getMethod("eat");
        //eat方法没有参数,所以getMethod()方法只需要Person类中的一个方法名也就是eat,如果存在重载,就需要在方法名后指定参数类型的Class对象
        
        //和对成员变量进行获取值和设置值的操作一样,都需要提前指定好一个对象,说明执行的是哪个对象的成员方法
        Person person = new Person();
        
        eat_method.invoke(person);
        
    }
}

运行上面的程序,运行结果为:

我正在吃

方法执行成功,我们在Person类中添加一个eat方法的重载:

public void eat(String food){
    System.out.println("我正在吃" + food);
}

ReflectDemo2类中添加几句代码:

Method eat_method1 = cls.getMethod("eat", String.class);
eat_method1.invoke(person, "蛋白粉");

运行程序,运行结果为:

我正在吃
我正在吃蛋白粉

方法执行成功,可以看到方法的重载的处理

这里需要注意一点,查看我们的Person类,只存在我们提前设置好的几个方法:构造方法成员变量的get、set方法重写的toString方法以及我们作为测试使用的两个eat方法重载,但是当我们执行Method[] getMethods()方法时,我们会看到许多方法我们并没有提前定义好,下面就是测试:

ReflectDemo2中的内容进行修改:

package com.westos.reflect;

import com.westos.pojo.Person;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectDemo2 {
    public static void main(String[] args) throws Exception {
        //获取Person类的Class类的对象
        Class cls = Person.class;
        //获取成员方法们
        Method[] methods = cls.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
    }
}

运行上面的代码,运行结果为:

public java.lang.String com.westos.pojo.Person.toString()
public java.lang.String com.westos.pojo.Person.getName()
public void com.westos.pojo.Person.setName(java.lang.String)
public void com.westos.pojo.Person.eat()
public void com.westos.pojo.Person.eat(java.lang.String)
public int com.westos.pojo.Person.getAge()
public void com.westos.pojo.Person.setAge(int)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

可以看到运行结果中包含了许多我们没在Person中定义的方法,观察可以发现,这些方法中包含一些Object类中的方法,也就是说Method[] getMethods()方法可以获取到类中所有被public修饰符修饰的成员方法包括继承自Object类的方法。

如果需要使用私有方法暴力反射同样可以起到作用

获取类名和方法名

在后续的使用中,我们可能需要获取成员方法的名称和类名,反射机制为我们提供了现成的方法

Class对象.getName();
Method对象.getName();

对ReflectDemo2内容进行修改:

package com.westos.reflect;

import com.westos.pojo.Person;

import java.lang.reflect.Method;

public class ReflectDemo2 {
    public static void main(String[] args) throws Exception {
        //获取Person类的Class类的对象
        Class cls = Person.class;
        System.out.println(cls.getName());
        //获取成员方法们
        Method[] methods = cls.getMethods();
        for (Method method : methods) {
            System.out.println(method);
            System.out.println(method.getName());
        }
    }
}

运行上面的代码,运行结果为:

com.westos.pojo.Person
public java.lang.String com.westos.pojo.Person.toString()
toString
public java.lang.String com.westos.pojo.Person.getName()
getName
public void com.westos.pojo.Person.setName(java.lang.String)
setName
public int com.westos.pojo.Person.getAge()
getAge
public void com.westos.pojo.Person.eat(java.lang.String)
eat
public void com.westos.pojo.Person.eat()
eat
public void com.westos.pojo.Person.setAge(int)
setAge
public final void java.lang.Object.wait() throws java.lang.InterruptedException
wait
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
wait
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
wait
public boolean java.lang.Object.equals(java.lang.Object)
equals
public native int java.lang.Object.hashCode()
hashCode
public final native java.lang.Class java.lang.Object.getClass()
getClass
public final native void java.lang.Object.notify()
notify
public final native void java.lang.Object.notifyAll()
notifyAll

获得了相应的类名和成员方法名。反射至此讲解完毕

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值