面试中反射的用法简介

1.背景介绍
反射的概述

反射是框架设计的灵魂

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取信息以及动态调用对象的方法的特性称为java语言的反射机制。

要想获得类的各种信息,必须先要获取到该类的字节码文件对象。该对象也就是java.lang.Class类,Class类用于表示java程序编译后得到的.class文件。

Class类的理解

Java程序在运行时,系统会对所有的对象进行所谓的运行时类型标识(RTTI,Run-Time Type Identification),其作用是在运行时识别一个对象的类型和类的信息。

传统的”RRTI”,它假定我们在编译期已知道了所有类型(在没有反射机制时,一般都是编译期已确定其类型,如new对象时该类必须已定义好);另外一种是反射机制,它允许我们在运行时发现和使用类型的信息。

在Java中用来表示运行时类型信息的对应类就是Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中。

Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。

JVM为每种类型管理一个独一无二的Class对象。也就是说,无论创建多少个实例对象,在内存中每个类有且只有一个相对应的Class对象,并且基本的java类型和关键字void也都对应一个Class对象。运行程序时,JVM首先检查所要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象载入。

2.知识剖析
在这里先看一下java为我们提供了哪些反射机制中的类:

java.lang.Class;

java.lang.reflect.Constructor;

java.lang.reflect.Field; 

java.lang.reflect.Method;

java.lang.reflect.Modifier;

反射的基本运用:Class对象获取方式

之前说了,Class对象是jvm用来保存对应实例对象的相关信息的,除此之外,我们完全可以把Class对象看成一般的实例对象。得到一个实例对象对应的Class对象有以下三种方式:

1.通过实例变量的getClass()方法

Test3 test3 =new Test3();

Class c3 =test3.getClass();

根据已经实例化的对象,利用getClass方法

2.通过类Class的静态方法forName()

Classc2 =Class.forName("com.getClassObject.Test2");

传入的参数是对应类的全限定名

3.直接给出对象类文件的.class

Class c1 =Test1.class;

反射的基本运用:判断是否为某个类的实例

一般地,我们用instanceof关键字来判断是否为某个类的实例。同时我们也可以借助反射中Class对象的isInstance()方法来判断是否为某个类的实例,它是一个Native方法

Class cls =Class.forName("com.isInstance.Demo"); //创建了一个Demo类的Class对象

boolean b1 =cls.isInstance(new Integer(37));

反射的基本运用:创建实例

通过反射来生成对象主要有两种方式:

(1)使用Class对象的newInstance()方法来创建Class对象对应类的实例。

//获取class对象

Class c1 =Class.forName("com.createInstance.Instance1");

//第一种方式:根据class对象,创建对应的实例

 //调用无参数的构造函数,直接调用Class类中的newInstance

Instance1 instance1 = (Instance1)c1.newInstance();
 

(2)先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。

//获取Instance1所对应的Class对象

Class c2 =Instance1.class;

//获取Instance1类带一个int参数的构造器

//若想调用有参构造函数,则需要调用Constructor类中newInstance()方法

Constructor constructor =c2.getConstructor(int.class);

//根据构造器创建实例

Object obj =constructor.newInstance(3213);

反射的基本运用:获取方法

获取某个Class对象的方法集合,主要有以下几个方法:

(1) getDeclaredMethods() 方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。

(2) getMethods() 方法返回某个类的所有公用(public)方法,包括其继承类的公用方法。

(3) getMethod("add", int.class, int.class) 方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。

Class c =Class.forName("com.getMethod.methodClass");

//获取methodClass类的所有公有方法(可获取到父类的方法)

Method[]methods =c.getMethods();

//获取methodClass类的所有的方法

Method[]declaredMethods =c.getDeclaredMethods();

//获取指定方法,methodClass类的add方法

Method publicMethod =c.getMethod("add", int.class, int.class);

//获取指定私有方法,div方法

Method privateMethod =c.getDeclaredMethod("div", int.class, int.class);

反射的基本运用:获取构造器信息

获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例。

public T newInstance(Object ... initargs)

具体方法同获取对应类方法

反射的基本运用:获取类的成员变量(字段)信息

主要是这几个方法:

getFiled: 访问公有的成员变量

getDeclaredField:所有已声明的成员变量。但不能得到其父类的成员变量。

getFileds和getDeclaredFields用法同上(参照Method)

反射的基本运用:反射用于工厂模式

先看看传统的工厂模式

主类如下

interface Fruit {

void eat();

}
 

class Apple implements Fruit {
    public void eat(){
        System.out.println("Eat Apple");
    }
}
 

class Orange implements Fruit {
    public void eat(){
        System.out.println("Eat Orange");
    }
}
         

工厂类

public class Factory {

public static Fruit getInstance(String fruitName){

Fruit f=null;

if("Apple".equals(fruitName)){

    f=new Apple();

}

if("Orange".equals(fruitName)){

    f=new Orange();

}

    return f;

}

}


可以看到如果我要新增一个实现类,就要在工厂类中改代码,耦合度较高。

利用反射实现工厂

public class ReflexFactory {

public static Fruit2 getInstances(String className) {

  Fruit2 f2 =null;

  try {

//利用反射

f2 = (Fruit2)Class.forName(className).newInstance();

  }catch (Exception e) {

    e.printStackTrace();

  }

  return f2;

  }

}


当如果要新增实现类的话,只要将传入的参数改变就好,无需更改工厂内的代码。

3.常见问题
反射机制的作用?

1,反编译:.class-->.java

2,通过反射机制访问java对象的属性,方法,构造方法等;

暴力反射?

获取类的私有成员。通过setAccessible(true)方法,设置成可访问。

类加载的过程?

加载:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象。

链接:验证字节码的安全性和完整性,准备阶段正式为静态域分配存储空间,注意此时只是分配静态成员变量的存储空间,不包含实例成员变量,如果必要的话,解析这个类创建的对其他类的所有引用。

初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。

反射的应用场景?

Java的反射特性一般结合注解和配置文件(如:XML)来使用,这也是大部分框架(Spring等)支持两种配置方式的原因。还有著名的junit测试框架也是利用反射方法名和参数名来进行测试的。

理解泛化的Class对象引用

由于Class的引用总是指向某个类的Class对象,利用Class对象可以创建实例,这也就说明Class对象的引用指向的是对象确切的类型。在Java SE5引入泛型后,使我们可以利用泛型来表示Class对象更具体的类型,即使在运行期间会被擦除,但编译期足以确保我们使用正确的对象类型。