面试官再问你什么是反射,就把这篇文章发给他!

面试官再问你什么是反射,就把这篇文章发给他!

1 什么是反射?

反射是一种可以间接操作目标对象的机制。当使用反射时,JVM 在运行的时候才动态加载类,对于任意类,知道其属性和方法,并不需要提前在编译期知道运行的对象是谁,允许运行时的 Java 程序获取类的信息并对其进行操作。

对象的类型在编译期就可以确定,但程序运行时可能需要动态加载一些类(之前没有用到,故没有加载进 jvm),使用反射可以在运行期动态生成对象实例并对其进行操作。

2 反射的原理

在获取到 Class 对象之后,反向获取和操作对象的各种信息。

3 反射的使用

我们先建一个类

public class People {

private int age;

private String name;

private People() {

age = 18;

name = "Tony";

}

public People(int age,String name) {

this.age = age;

this.name = name;

}

private void print() {

System.out.println(this.toString());

}

public void setName(String name) {

this.name = name;

}

public String getName() {

return name;

}

}

获得 Class 对象

获取 Class 对象有三种方法:

调用对象的 getClass 方法任何数据类型都拥有的 class 属性通过 Class 类的静态方法 forName(String className) 进行调用,该方法最常用

在运行期间,一个类只能有一个 Class 对象产生

获取类的构造函数

通过 getDeclaredConstructors 方法我们可以得到类的所有构造方法。

public class Test {

public static void main(String[] args) {

Class c = People.class;

//得到类的所有构造方法

Constructor[] constructors = c.getDeclaredConstructors();

for(int i = 0; i < constructors.length; i++) {

//获得构造方法的类型

System.out.println("构造方法的类型:" + Modifier.toString(constructors[i].getModifiers()));

//获得构造方法的所有参数

Class[] parametertypes = constructors[i].getParameterTypes();

for (int j = 0; j < parametertypes.length; j++) {

System.out.print(parametertypes[j].getName() + " ");

}

System.out.println("");

}

}

}

返回结果如下:

通过该方法,我们可以获取类中所有构造方法和构造方法中的参数。

获取类中特定的构造方法

在 getDeclaredConstructor 方法中,我们未传入参数,表示希望得到类的特定构造方法。同时在代码中要进行异常捕获,因为可能不存在对应的构造方法。

public class Test {

public static void main(String[] args){

Class c = People.class;

Constructor constructor;

try {

//得到类的特定构造方法,无参构造方法不传参数

constructor = c.getDeclaredConstructor();

//获得构造方法的类型

System.out.println("构造方法的类型:" + Modifier.toString(constructor.getModifiers()));

//获得构造方法的所有参数

System.out.println("构造方法的参数:");

Class[] parametertypes = constructor.getParameterTypes();

for (int j = 0; j < parametertypes.length; j++) {

System.out.print(parametertypes[j].getName() + " ");

}

} catch (Exception e) {}

}

}

结果如下: 如果我们想得到这个构造函数

public People(int age,String name) {

this.age = age;

this.name = name;

}

在 getDeclaredConstructor 方法中,我们可以传入一个 Class 数组,里面包含 int 和 java.lang.String 的 Class 对象。

public class Test {

public static void main(String[] args){

Class c = People.class;

Class[] p = {int.class,String.class};

Constructor constructor;

try {

//得到类的特定构造方法,这次传入int和java.lang.String两个参数

constructor = c.getDeclaredConstructor(p);

//获得构造方法的类型

System.out.println("构造方法的类型:" + Modifier.toString(constructor.getModifiers()));

//获得构造方法的所有参数

System.out.println("构造方法的参数:");

Class[] parametertypes = constructor.getParameterTypes();

for (int j = 0; j < parametertypes.length; j++) {

System.out.print(parametertypes[j].getName() + " ");

}

} catch (Exception e) {}

}

}

结果如下:

调用构造方法

在上面。我们已经学习了如何获取类中特定的构造方法,在这里,我们不仅要获取,还要对类的构造方法进行调用。

我们先修改类的两个构造函数,分别加上一打印语句,代码如下:

private People() {

age = 18;

name = "Tony";

System.out.println("private People()调用成功");

}

public People(int age,String name) {

this.age = age;

this.name = name;

System.out.println("public People(int age,String name)调用成功");

}

我们先使用这个 public 访问类型的构造函数

public class Test {

public static void main(String[] args){

Class c = People.class;

Class[] p = {int.class,String.class};

Constructor constructor;

try {

constructor = c.getDeclaredConstructor(p);

//创建实例

constructor.newInstance(10,"HaWei");

} catch (Exception e) {}

}

}

结果如下 那么我们如何通过反射调用类的 private 访问类型的构造函数呢?其实大体与上面一样,只是我们需要设置constructors.setAccessible(true);罢了。

public class Test {

public static void main(String[] args){

Class c = People.class;

Constructor constructor;

try {

//获取类的无参构造函数

constructor = c.getDeclaredConstructor();

constructor.setAccessible(true);

//创建实例

constructor.newInstance();

} catch (Exception e) {}

}

}

结果如下

调用类的私有方法

我们尝试调用一下类的这个私有方法

private void print() {

System.out.println(this.toString());

}

关于调用方法,我们可以通过 getDeclaredMethod 来获取该方法,然后通过调用 invoke 执行。

public class Test {

public static void main(String[] args){

Class c = People.class;

Constructor constructor;

try {

//获取类的无参构造函数

constructor = c.getDeclaredConstructor();

constructor.setAccessible(true);

//创建实例

People obj = (People) constructor.newInstance();

//获取需要调用的方法,需要两个参数,第一个参数是方法名,第二个参数是参数类型(本例不需要传入参数)

Method method = c.getDeclaredMethod("print");

method.setAccessible(true);

//调用方法,需要两个参数,第一个参数是类的实例,第二个参数是方法参数

method.invoke(obj);

} catch (Exception e) {}

}

}

结果如下:

获取类的私有字段并修改值

public class Test {

public static void main(String[] args){

Class c = People.class;

Constructor constructor;

try {

//获取类的无参构造函数

constructor = c.getDeclaredConstructor();

constructor.setAccessible(true);

//创建实例

People obj = (People) constructor.newInstance();

//修改之前的name字段值

System.out.println("修改之前:" + obj.getName());

//获取类的name字段

Field field = c.getDeclaredField("name");

field.setAccessible(true);

//修改类的私有字段

field.set(obj,"HaWei");

//修改之后的name字段值

System.out.println("修改之后:" + obj.getName());

} catch (Exception e) {}

}

}

结果如下:

4 反射的优点

可以在运行时获得类的内容,对于 Java 这种先编译再运行的语言,能够让我们很方便的写出灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

5 反射的缺点

会消耗一定的系统资源反射调用方法时可以忽略权限检查,因此可能会破坏封装性从而导致安全问题

6 反射的用途

进行反编译,把 .class 文件变为 .java 文件通过反射机制访问 java 对象的属性,方法开发各种通用框架(例如 Spring),许多框架都是配置化的,为了保证框架的通用性,可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象可以通过反射运行配置文件内容,利用反射和配置文件可以使应用程序更新时,对源码无需进行任何修改,只需要将新类发送给客户端,并修改配置文件即可可以通过反射越过泛型检查,泛型使用在编译期,编译过后泛型擦除,反射作用在运行期,所以是可以通过反射越过泛型检查的

参考:Java反射技术详解 Java基础篇:反射机制详解

相关数据