反射

参考:(https://note.youdao.com/ynoteshare1/index.html?id=128508bf6a04968eced81ede9ac1b304&type=note)

反射

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

反射的好处:

  • 可以在程序运行过程中,操作这些对象
  • 可以解耦合,可以提高程序的可扩展性
    Java代码在计算机中经历的三个阶段
  • Source源代码阶段:.java文件被编译为.class字节码文件 (存储在硬盘)
  • Class类对象阶段:*.class字节码文件被类加载器加载进内存,并将其封装成Class对象(用于在内存中描述字节码文件),Class对象将源字节码文件中的成员变量(域或者叫属性)抽取出来封装成数组Field[],将字节码文件中的构造函数抽取出来封装成数组Constructor[],将成员方法抽取出来封装成Method[].当然,Class内不止这三个,还封装了很多,我们通常使用的也就这三个.
  • RunTime阶段:这就是我们经常使用的 new 对象阶段.

获取Class对象的方式

获取Class对象的三种方式对应这java代码在计算机中的三个阶段

  • Source源代码阶段 Class.forName("全类名"):将字节码文件加载进内存,返回Class对象
    *多用于配置文件,将全类名定义在配置文件当中,读取文件,进行加载
  • Class类对象阶段类名.class:通过类名的属性class获取
    *多用于参数的传递
  • Runtime运行时阶段 对象.getClass():getClass()方法是定义在Object类中的方法

结论:同一个字节码(*.class)在一次程序运行过程中,只会被加载一次,无论通过那种方式获得的Class对象都是同一个

测试三种方式

首先创建一个Employee类

public class Employee {
    private String name;
    private int age;
    private Double salary;

    public Employee() {
    }

    public Employee(String name, int age, Double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    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 Double getSalary() {
        return salary;
    }

    public void setSalary(Double salary) {
        this.salary = salary;
    }
}
public static void main(String[] args) throws Exception {
        //方式一:Class.forName("全类名")
        Class c1 = Class.forName("domain.Employee");
        System.out.println(c1);
        //方式二:类名.class
        Class c2 = Employee.class;
        System.out.println(c2);

        //方式三:对象.getClass();
        Employee employee = new Employee();
        Class c3 = employee.getClass();
        System.out.println(c3);
        // 比较三个对象
        System.out.println("c1 == c2:"+(c1 == c2));
        System.out.println("c1 == c3:"+(c1 == c3));
    }
class domain.Employee
class domain.Employee
class domain.Employee
c1 == c2:true     //结果为true,说明了同一个字节码文件,在一次运行程序中,只会被加载一次,无论通过那种方式获得的Class对象都是同一个
c1 == c3:true

Class对象功能

列举常用功能

  • 获取成员变量(域或者叫属性都是它)
Field[] getFields() : 获取所有public修饰的域(属性)
Field getField(String name)  :获取指定名称的public修饰的域

Field[] getDeclaredFields():  获取所有的域 (private protected public 没有修饰符的)
Field getDeclaredFields(String name) 
  • 获取构造方法们
Constructor<?>[] getConstructors()  
Constructor<T> getConstructor(Class<?>... parameterTypes)  

Constructor<?>[] getDeclaredConstructors()  
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)  
  • 获取成员方法们
Method[] getMethods()  
Method getMethod(String name, Class<?>... parameterTypes)  

Method[] getDeclaredMethods()  
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
  • 获取全类名
String getName()

Filed:成员变量

  • 设置值 void set(Object obj,Object value)
  • 获取值 get(object obj)
  • 忽略访问权限修饰符的安全检查 setAccessible(true) :暴力反射
    在Employee类中添加几个域
    public String a;
    protected String b;
    String c;
    private String d;

测试getFields和getField(String name)方法

public void testField() throws Exception {
        //获取Employee的Class对象
        Class employeeClass = Employee.class;
        //getFields 获得所有public修饰的域
        Field[] fields = employeeClass.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }
        System.out.println("---------------------------");
        //getFields(String name)
        Field a = employeeClass.getField("a");
        //获取域a的值[public修饰的]
        Employee employee = new Employee();
        Object value = a.get(employee);
        System.out.println("value:"+value);
        //设置a的值
        a.set(employee,"张三");
        System.out.println(employee);  //先给Employee类设置toString方法
    }
public java.lang.String domain.Employee.a   //获取Employee中所有public修饰的域
---------------------------
value:null            //实例化一个类String类型的域默认为null
Employee{name='null', age=0, salary=null, a='张三', b='null', c='null', d='null'}        //先给Employee类添加toString方法

测试getDeclaredFields和getDeclaredField(String name)方法

public void testDeclaredField() throws Exception {
        Class employeeClass = Employee.class;

        Field[] declaredFields = employeeClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
        }
        System.out.println("++++++++++++++++++++++++++++++=");

        Field d = employeeClass.getDeclaredField("d"); //private String d
        Employee employee = new Employee();
//        Object value = d.get(employee);   //或抛出异常,应为d的private ,不允许修改的
        //忽略访问权限修饰符的安全检测
        d.setAccessible(true);
        Object value = d.get(employee);
        System.out.println(value);

    }
private java.lang.String domain.Employee.name    //打印出所有的域
private int domain.Employee.age
private java.lang.Double domain.Employee.salary
public java.lang.String domain.Employee.a
protected java.lang.String domain.Employee.b
java.lang.String domain.Employee.c
private java.lang.String domain.Employee.d
++++++++++++++++++++++++++++++=
null    //对于非public修饰符需要使用暴力反射,获取操作权限

Constructor:构造方法

创建对象:T newInstance(Object... initargs)

注意:如果使用空格参数构造方法创建对象,操作可以简化,Class对象的newInstance方法

 public void testConstractor() throws Exception {
        Class employeeClass = Employee.class;
        Constructor[] constructors = employeeClass.getConstructors(); //获得所有public修饰的构造方法
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        System.out.println("=============================");
        //获取无参数的构造函数 注意Em
        // Employee类中必须要有无参数的构造函数,否则会报错
        Constructor constructor1 = employeeClass.getConstructor();
        System.out.println("constructor1"+constructor1);
        //获取到构造函数之后可以用于创建对象
        Object o = constructor1.newInstance();////Constructor类内提供了初始化方法newInstance();方法
        System.out.println("o:"+o);
        //获取有参数的构造函数Employee(String name,int age Double salary) 参数顺序类型要与构造函数一直,且参数类型为字节码类型
        Constructor constructor2 = employeeClass.getConstructor(String.class, int.class, Double.class);
        System.out.println("constructor2:"+constructor2);
        //创建对象
        Object o2 = constructor2.newInstance("张三", 23, 6578.32);
        System.out.println(o2);

        System.out.println("+++++++++++++++++++++++++++++++++++==");
        //对于一般的无参数构造函数,我们都不会先获取到无参数构造器再进行初始化.而是直接调用Class类内部的newInstance()方法
        Object o3 = employeeClass.newInstance();
        System.out.println("o3:"+o3);
    }
public domain.Employee()
public domain.Employee(java.lang.String,int,java.lang.Double)
=============================
constructor1public domain.Employee()
o:Employee{name='null', age=0, salary=null, a='null', b='null', c='null', d='null'}
constructor2:public domain.Employee(java.lang.String,int,java.lang.Double)
Employee{name='张三', age=23, salary=6578.32, a='null', b='null', c='null', d='null'}
+++++++++++++++++++++++++++++++++++==
o3:Employee{name='null', age=0, salary=null, a='null', b='null', c='null', d='null'}

对于getDeclaredConstructor方法和getDeclaredConstructors方法

和上面获取域相同,这两个方法是获取到所有的构造方法,而上面的是获取public修饰的构造方法,需要操作非public修饰的构造方法是也需要调用setAccessible(true) 方法,获取执行权限.

关于为什么要使用private访问权限的构造器,使用这个构造器不就不能外部访问了嘛,不也就无法进行实例化对象了吗?无法在类的外部实例化对象正是私有构造器的意义所在,在单例模式下经常使用,整个项目只有一个对象,外部无法实例化对象,可以在类内的进行实例化并通过静态方法返回(由于实例化的对象是静态的,故只有一个对象,也就是单例的)。网上说这就是单例模式中的饿汉模式,不管是否调用,都创建一个对象.

class SingleDemo{
    //私有构造方法
    private SingleDemo(){
        
    }
    //创建一个对象,类内实例化
    private static SingleDemo singleDemo = new SingleDemo();
    //提供一个public方法提供给外部访问,返回创建的对象
    public static SingleDemo getInstance(){
        return singleDemo;
    }
}
public void testSingle(){
        SingleDemo s1 = SingleDemo.getInstance();
        //打印成功则说明创建对象成功
        System.out.println(s1);
        SingleDemo s2 = SingleDemo.getInstance();

        System.out.println(s2);
        //如果为True,则说明是同一个对象
        System.out.println(s1 == s2);
    }
chapter03.SingleDemo@71423665
chapter03.SingleDemo@71423665
true

Method:方法对象

  • 执行方法:Object invoke(Object obj,Object... args)
  • 获取方法名称:String getName();

测试invoke方法

现在Employee中添加一个方法

    public void hello(){
        System.out.println("Hello............."); 
    }
public void testMethod() throws Exception {
        Class employeeClass = Employee.class;
        Method[] methods = employeeClass.getMethods(); //这里获取的只是Employee类中的public方法,还有继承的Object的public方法
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println("===============================");
        //获取指定的方法名称
        Method hello = employeeClass.getMethod("hello");
        //执行方法
        Employee employee = new Employee();
        Object value = hello.invoke(employee);
        //eat方法没有返回值,故输出为null
        System.out.println("value:"+value);

    }

public void domain.Employee.hello()
public int domain.Employee.getAge()
public void domain.Employee.setAge(int)
public java.lang.Double domain.Employee.getSalary()
public void domain.Employee.setSalary(java.lang.Double)
public java.lang.String domain.Employee.toString()
public java.lang.String domain.Employee.getName()
public void domain.Employee.setName(java.lang.String)
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 final void java.lang.Object.wait() 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()
===============================
Hello.............
value:null

getMethod方法获取方法名

public void testgetName(){
        Class employeeClass = Employee.class;
        Method[] methods = employeeClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
            System.out.println(method.getName());
            System.out.println("++++++++++++++++++++================");
        }
    }
public void domain.Employee.hello()
hello
++++++++++++++++++++================
public int domain.Employee.getAge()
getAge
++++++++++++++++++++================
public void domain.Employee.setAge(int)
setAge
++++++++++++++++++++================
public java.lang.Double domain.Employee.getSalary()
getSalary
++++++++++++++++++++================
public void domain.Employee.setSalary(java.lang.Double)
setSalary
++++++++++++++++++++================
public java.lang.String domain.Employee.toString()
toString
++++++++++++++++++++================
public java.lang.String domain.Employee.getName()
getName
++++++++++++++++++++================
public void domain.Employee.setName(java.lang.String)
setName
++++++++++++++++++++================
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 final void java.lang.Object.wait() 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
++++++++++++++++++++================

注明:获取域,构造函数,方法都有Declared的方法,参看域的就行

获取带全路径的类名

getClass()方法是Object类的方法,需要注意一点获取的类名是全类名(带有路径)

public void testgetClassName(){
        Class employeeClass = Employee.class;
        String className = employeeClass.getName();
        System.out.println(className);
    }
domain.Employee
posted @ 2020-09-23 16:40  刘指导  阅读(120)  评论(0编辑  收藏  举报