深入理解Java反射机制(总结)

什么是反射

反射是Java程序员开发语言的特征之一,它允许运行中的Java出现获取自身的信息,并且可以操作类或对象的内部属性。

通过反射机制,可以在运行时访问Java对象的属性,方法,构造方法等。

反射的应用场景:

开发通用框架:反射最重要的用途就是开发各种通用框架。很多框架都素hi配置化的,为了保证框架的通用性,它们可以需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射--运行时动态加载需要加载的对象。

动态代理:在切面编程中,需要拦截特定的方法,通常,会选择动态代理方式,这时,就需要反射技术来实现了。

注解:注解本身仅仅是起到标记作用,它需要利用反射机制,根据注解标记去调用注解解释器,执行行为。如果没有反射机制,注解并不比注释更有用。

可扩展性功能:应用程序可以通过使用完全限定名称创建可扩展性对象实例来使用外部的用户定义类。

反射的缺点:

性能开销:由于反射涉及及动态解析的类型,因此无法执行某些Java虚拟机优化。因此,反射操作的性能要比非反射操作的性能差,应该在性能敏感的应用程序中频繁调用的代码段中避免。

破坏封装性:反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

内部曝光:由于反射允许代码执行在非反射代码中非法的操作,例如访问私有字段和方法,所以反射的使用可能会导致意想不到的副作用,这可能会导致代码功能失常并可能破坏可移植性。反射代码打破了抽象,因此可能会随着平台的升级而改变行为。

反射机制:

类加载的完整过程

在编译时,Java编译器编译好 .java文件之后,在磁盘中产生.class文件。.class文件是二进制文件,内容是只有JVM能够识别的机器码。

JVM中的类加载器读取字节码文件,取出二进制数据,加载到内存中,解析.class文件内的信息。类加载器会根据类的全限定名来获取此类的二进制字节流,然后,将字节流所代表的静态存储结构转化为方法区的运行时数据结构,接着,在内存中生成代表这个类的java.lang.Class对象。

加载结束后,JVM开始进行连接阶段,经过这一系列操作,类的变量会被初始化。

Class对象:

要想使用反射,首先需要获得待操作的类所对应的Class对象。Java中,无论生成某个类的多少个对象,这些对象都会对应同一个Class对象。这个Class对象是由JVM生成的,通过它能够获悉整个类的结构。所以,java.lang.Class可以视为所有反射API的入口点。

反射的本质就是:在运行时,把Java类中各种成分映射成一个个的Java对象。

举例来说:

User user  =new User();

1.JVM加载方法的时候,遇到new User(),JVM会根据全限定名去加载user.class。

2.JVM会去本地磁盘查找User.class文件并加载JVM内存中。

3.JVM通过调用类加载器自动创建这个类对应的Class对象,并且存储在JVM的方法区。注意,一个类有且只有一个Class对象。

使用反射

java.lang.reflect 包

Java 中的 java.lang.reflect 包提供了反射功能。java.lang.reflect 包中的类都没有 public 构造方法。

java.lang.reflect 包的核心接口和类如下:

Member 接口 - 反映关于单个成员(字段或方法)或构造函数的标识信息。

Field 类 - 提供一个类的域的信息以及访问类的域的接口。

Method 类 - 提供一个类的方法的信息以及访问类的方法的接口。

Constructor 类 - 提供一个类的构造函数的信息以及访问类的构造函数的接口。

Array 类 - 该类提供动态地生成和访问 JAVA 数组的方法。

Modifier 类 - 提供了 static 方法和常量,对类和成员访问修饰符进行解码。

Proxy 类 - 提供动态地生成代理类和类实例的静态方法。

获得 Class 对象

获得 Class 的三种方法:

(1)使用 Class 类的 forName 静态方法

示例:

package io.github.dunwu.javacore.reflect;
public class ReflectClassDemo01 { 
public static void main(String[] args) throws ClassNotFoundException { 
Class c1 = Class.forName("io.github.dunwu.javacore.reflect.ReflectClassDemo01"); System.out.println(c1.getCanonicalName());
Class c2 = Class.forName("[D"); 
System.out.println(c2.getCanonicalName()); 
Class c3 = Class.forName("[[Ljava.lang.String;"); 
System.out.println(c3.getCanonicalName()); 
} 
}
 //Output:
 //io.github.dunwu.javacore.reflect.ReflectClassDemo01 
//double[] 
//java.lang.String[][]

使用类的完全限定名来反射对象的类。常见的应用场景为:在 JDBC 开发中常用此方法加载数据库驱动。

(2)直接获取某一个对象的 class

示例:

public class ReflectClassDemo02 {
    public static void main(String[] args) {
        boolean b;
        // Class c = b.getClass(); // 编译错误
        Class c1 = boolean.class;
        System.out.println(c1.getCanonicalName());

        Class c2 = java.io.PrintStream.class;
        System.out.println(c2.getCanonicalName());

        Class c3 = int[][][].class;
        System.out.println(c3.getCanonicalName());
    }
}
//Output:
//boolean
//java.io.PrintStream
//int[][][]

(3)调用 Object 的 getClass 方法,示例:

Object 类中有 getClass 方法,因为所有类都继承 Object 类。从而调用 Object 类来获取

示例:

package io.github.dunwu.javacore.reflect;

import java.util.HashSet;
import java.util.Set;

public class ReflectClassDemo03 {
    enum E {A, B}

    public static void main(String[] args) {
        Class c = "foo".getClass();
        System.out.println(c.getCanonicalName());

        Class c2 = ReflectClassDemo03.E.A.getClass();
        System.out.println(c2.getCanonicalName());

        byte[] bytes = new byte[1024];
        Class c3 = bytes.getClass();
        System.out.println(c3.getCanonicalName());

        Set set = new HashSet<>();
        Class c4 = set.getClass();
        System.out.println(c4.getCanonicalName());
    }
}
//Output:
//java.lang.String
//io.github.dunwu.javacore.reflect.ReflectClassDemo.E
//byte[]
//java.util.HashSet

判断是否为某个类的实例

判断是否为某个类的实例有两种方式:

用 instanceof 关键字

用 Class 对象的 isInstance 方法(它是一个 Native 方法)

示例:

public class InstanceofDemo {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        if (arrayList instanceof List) {
            System.out.println("ArrayList is List");
        }
        if (List.class.isInstance(arrayList)) {
            System.out.println("ArrayList is List");
        }
    }
}
//Output:
//ArrayList is List
//ArrayList is List

创建实例

通过反射来创建实例对象主要有两种方式:

用 Class 对象的 newInstance 方法。

用 Constructor 对象的 newInstance 方法。

示例:

public class NewInstanceDemo {
    public static void main(String[] args)
        throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class c1 = StringBuilder.class;
        StringBuilder sb = (StringBuilder) c1.newInstance();
        sb.append("aaa");
        System.out.println(sb.toString());

        //获取String所对应的Class对象
        Class c2 = String.class;
        //获取String类带一个String参数的构造器
        Constructor constructor = c2.getConstructor(String.class);
        //根据构造器创建实例
        String str2 = (String) constructor.newInstance("bbb");
        System.out.println(str2);
    }
}
//Output:
//aaa
//bbb

Field

Class 对象提供以下方法获取对象的成员(Field):

getFiled - 根据名称获取公有的(public)类成员。

getDeclaredField - 根据名称获取已声明的类成员。但不能得到其父类的类成员。

getFields - 获取所有公有的(public)类成员。

getDeclaredFields - 获取所有已声明的类成员。

示例如下:

public class ReflectFieldDemo {
    class FieldSpy {
        public boolean[][] b = { {false, false}, {true, true} };
        public String name = "Alice";
        public List list;
        public T val;
    }

    public static void main(String[] args) throws NoSuchFieldException {
        Field f1 = FieldSpy.class.getField("b");
        System.out.format("Type: %s%n", f1.getType());

        Field f2 = FieldSpy.class.getField("name");
        System.out.format("Type: %s%n", f2.getType());

        Field f3 = FieldSpy.class.getField("list");
        System.out.format("Type: %s%n", f3.getType());

        Field f4 = FieldSpy.class.getField("val");
        System.out.format("Type: %s%n", f4.getType());
    }
}
//Output:
//Type: class [[Z
//Type: class java.lang.String
//Type: interface java.util.List
//Type: class java.lang.Object

Method

Class 对象提供以下方法获取对象的方法(Method):

getMethod - 返回类或接口的特定方法。其中第一个参数为方法名称,后面的参数为方法参数对应 Class 的对象。

getDeclaredMethod - 返回类或接口的特定声明方法。其中第一个参数为方法名称,后面的参数为方法参数对应 Class 的对象。

getMethods - 返回类或接口的所有 public 方法,包括其父类的 public 方法。

getDeclaredMethods - 返回类或接口声明的所有方法,包括 public、protected、默认(包)访问和 private 方法,但不包括继承的方法。

获取一个 Method 对象后,可以用 invoke 方法来调用这个方法。

invoke 方法的原型为:

public Object invoke(Object obj, Object... args)

        throws IllegalAccessException, IllegalArgumentException,

           InvocationTargetException

示例:

public class ReflectMethodDemo {
    public static void main(String[] args)
        throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        // 返回所有方法
        Method[] methods1 = System.class.getDeclaredMethods();
        System.out.println("System getDeclaredMethods 清单(数量 = " + methods1.length + "):");
        for (Method m : methods1) {
            System.out.println(m);
        }

        // 返回所有 public 方法
        Method[] methods2 = System.class.getMethods();
        System.out.println("System getMethods 清单(数量 = " + methods2.length + "):");
        for (Method m : methods2) {
            System.out.println(m);
        }

        // 利用 Method 的 invoke 方法调用 System.currentTimeMillis()
        Method method = System.class.getMethod("currentTimeMillis");
        System.out.println(method);
        System.out.println(method.invoke(null));
    }
}

Constructor

Class 对象提供以下方法获取对象的构造方法(Constructor):

getConstructor - 返回类的特定 public 构造方法。参数为方法参数对应 Class 的对象。

getDeclaredConstructor - 返回类的特定构造方法。参数为方法参数对应 Class 的对象。

getConstructors - 返回类的所有 public 构造方法。

getDeclaredConstructors - 返回类的所有构造方法。

获取一个 Constructor 对象后,可以用 newInstance 方法来创建类实例。

示例:

public class ReflectMethodConstructorDemo {
    public static void main(String[] args)
        throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor[] constructors1 = String.class.getDeclaredConstructors();
        System.out.println("String getDeclaredConstructors 清单(数量 = " + constructors1.length + "):");
        for (Constructor c : constructors1) {
            System.out.println(c);
        }

        Constructor[] constructors2 = String.class.getConstructors();
        System.out.println("String getConstructors 清单(数量 = " + constructors2.length + "):");
        for (Constructor c : constructors2) {
            System.out.println(c);
        }

        System.out.println("====================");
        Constructor constructor = String.class.getConstructor(String.class);
        System.out.println(constructor);
        String str = (String) constructor.newInstance("bbb");
        System.out.println(str);
    }
}

Array

数组在 Java 里是比较特殊的一种类型,它可以赋值给一个对象引用。下面我们看一看利用反射创建数组的例子:

public class ReflectArrayDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        Class cls = Class.forName("java.lang.String");
        Object array = Array.newInstance(cls, 25);
        //往数组里添加内容
        Array.set(array, 0, "Scala");
        Array.set(array, 1, "Java");
        Array.set(array, 2, "Groovy");
        Array.set(array, 3, "Scala");
        Array.set(array, 4, "Clojure");
        //获取某一项的内容
        System.out.println(Array.get(array, 3));
    }
}
//Output:
//Scala

其中的 Array 类为 java.lang.reflect.Array 类。我们通过 Array.newInstance 创建数组对象,它的原型是:

public static Object newInstance(Class componentType, int length)
    throws NegativeArraySizeException {
    return newArray(componentType, length);
}

 

你可能感兴趣的:(java)