1. 反射是什么
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性。Java 语言编译之后会生成一个 .class
文件,反射就是通过字节码文件找到某一个类、类中的方法以及属性等。
反射的作用:对于在编译期
无法确定使用哪个数据类的场景,通过反射可以在运行期
时构造出不同的数据类实例。
2. 获得 Class 对象的三种方法
对象.getClass()
、类名.class
、Class.forName("路径")
是三种获得Class对象的方法。
getClass()
接在对象的后面。getClass() 是 Object 类中的方法,而 Object 类是所有 Java 类的父类。.class
接在任意一个 Class 类的后面,在编译期加载(静态加载)。Class.forName
是 Class 类中的一个静态方法,从指定的 classloader 中装载类,返回与给定字符串对应类或接口的 Class 对象,在运行期加载(动态加载)。
关于静态加载和动态加载:
静态加载的类的源程序在编译时期加载(必须存在),而动态加载的类在编译时期可以缺席(源程序不存在时编译器也不会报错)。
当类找不到的时候,静态加载方式会抛出异常”NoClassDefFoundError”,而动态加载方式则抛出”ClassNotFoundException”异常。
这三种方法获得的 Class 对象都是一模一样的。在得到 Class 对象之后,我们可以调用一些方法来获取该类的各种信息,包括类名、父类以及它所实现接口的名字等。
Class.forName() 和 ClassLoader 的 loadClass() 之间的区别:
- Class.forName() 加载类是将类进了初始化,而 ClassLoader 的 loadClass() 方法并没有对类进行初始化,只是把类加载到了虚拟机中。
- Spring 框架中 IOC 容器的实现就是使用的 ClassLoader 的 loadClass() 方法。
- 而在我们使用 JDBC 时通常是使用 Class.forName() 方法来加载数据库连接驱动。这是因为在 JDBC 规范中明确要求 Driver (数据库驱动)类必须向 DriverManager 注册自己。
3. 获取一个类的所有信息
获取类中的变量(Field)
- Field[] getFields():获取类中所有被
public
修饰的所有变量 - Field getField(String name):根据变量名获取类中的一个变量,该变量必须被public修饰
- Field[] getDeclaredFields():获取类中所有的变量,但无法获取继承下来的变量
Field getDeclaredField(String name):根据姓名获取类中的某个变量,无法获取继承下来的变量
获取类中的方法(Method)
Method[] getMethods():获取类中被
public
修饰的所有方法- Method getMethod(String name, Class… paramTypes):根据名字和参数类型获取对应方法,该方法必须被
public
修饰 - Method[] getDeclaredMethods():获取
所有
方法,但无法获取继承下来的方法 Method getDeclaredMethod(String name, Class… paramTypes):根据名字和参数类型获取对应方法,无法获取继承下来的方法
获取类的构造器(Constructor)
Constuctor[] getConstructors():获取类中所有被
public
修饰的构造器- Constructor getConstructor(Class… paramTypes):根据
参数类型
获取类中某个构造器,该构造器必须被public
修饰 - Constructor[] getDeclaredConstructors():获取类中所有构造器
Constructor getDeclaredConstructor(class… paramTypes):根据
参数类型
获取对应的构造器获取注解
Annotation[] getAnnotations():获取该对象上的所有注解
- Annotation getAnnotation(Class annotaionClass):传入
注解类型
,获取该对象上的特定一个注解 - Annotation[] getDeclaredAnnotations():获取该对象上的显式标注的所有注解,无法获取
继承
下来的注解 Annotation getDeclaredAnnotation(Class annotationClass):根据
注解类型
,获取该对象上的特定一个注解,无法获取继承
下来的注解4. 通过反射获取泛型类型
public class Demo {
private List<Integer> list1;
public static void getFieldGenericType() {
try {
Field field = Demo.class.getDeclaredField("list1");
Type type = field.getGenericType();
ParameterizedType pt = (ParameterizedType) type; //取得field的type
Type[] genericTypes = pt.getActualTypeArguments(); //强转成具体的实现类
System.out.println(genericTypes[0]); //取得包含的泛型类型
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
getFieldGenericType();
}
}
5. 反射的优缺点
优点:反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,class.forName() 动态加载类,提高代码的灵活度。
- 缺点:如果一个功能可以不用反射完成,那么最好就不用。反射有以下几个问题:
- 性能开销:反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。不过在单次调用反射的过程中,性能损耗可以忽略不计。但是相比于反射带来的便利,如果不是高并发需要十分频繁的调用,反射的性能损耗可以忽略,并且反射性能损耗也有方法优化降低。(如 Spring 中的 BeanUtils 使用缓存进行优化)
- 安全限制:使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。
- 内部暴露:由于反射允许代码执行一些在正常情况下不被允许的操作(比如:访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
6. 反射和内省(Introspector)的区别
反射就像给类照镜子,这个的所有信息会毫无保留的反射到镜子中,将这个类的所有信息照出来,能照出来就是有,照不出来就是没有,得到的东西都是客观真实存在的。
内省是基于反射实现的,主要用于操作 JavaBean,相比反射使用起来要方便一些。可以获取 bean 的 getter/setter 方法,也就是说,只要 JavaBean 有getXxx()
方法,不管这个 Bean 有没有 Xxx 属性,如果使用内省我们都认为它有。
7. new 关键字和 newInstance 的区别
- 两者创建对象的方式不同,前者是实用类的加载机制,后者则是直接创建一个类。
- newInstance 创建类时这个类必须已经加载过且已经连接(Class.forName(“A”)这个过程),new 创建类不需要这个类加载过。
- newInstance 是弱类型(GC时回收对象的限制条件很低,容易被回收)、相对低效。而new是强类型(GC不会自动回收,只有所有的指向对象的引用被移除是才会被回收,若对象生命周期已经结束,但引用没有被移除,经常会出现内存溢出),相对高效。
- newInstance实例化对象只能调用无参构造方法(如果重写了一个带参构造方法,想要使用newInstance,则必须指定一个无参构造方法,否则会报初始化错误)。而new能调用任何public的构造方法。
一般来说,当我们不知道初始化哪个具体的类时,可以使用 Class 类的 newInstance 方法。
8. 总结
- 反射的思想:反射就像是一面镜子一样,在运行时才看到自己是谁,可获取到自己的信息,甚至实例化对象。
- 反射的作用:在运行时才确定实例化对象,使程序更加健壮,面对需求变更时,可以最大程度地做到不修改程序源码应对不同的场景,实例化不同类型的对象。
- 反射的特点:增加程序的灵活性、破坏类的封装性以及性能损耗。
- 反射的应用场景: