静态语言 & 动态语言
在讲反射之前先来了解一下什么是静态语言和动态语言
动态语言
是一类在运行时可以改变其结构的语言,例如新的函数、对象、甚至代码都可以被引进,已有的函数可以被删除,或是其他结构上的变化, 通俗地讲就是在运行时代码可以根据某些条件改变自身结构。
主要动态语言:Object-C、C#、JavaScript、PHP、Python
等。
静态语言
与动态语言相对应,运行时结构不可变的语言是静态语言,如Java、C、C++
。
Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射
机制获得类似动态语言的特性,这可以让Java程序的编写变得更加灵活。
反射机制介绍
JAVA 反射机制是在运行状态
中,对于任意一个类,都能够获取这个类的所有属性和方法
;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态
获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制
。
Reflection 是Java 被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
Class c = Class.forName("java.lang.String")
加载完类之后,在堆内存的方法区
中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过一个类的Class对象获取该类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们形象地称之为:反射
。
Class
类是理解反射机制的关键,这个后面会重点讲解。
一般方式获取一类的对象 vs 通过反射获取一个对象的类信息
正常方式:import package 包名
-> 通过new
实例化 -> 取得实例化对象
反射方式:实例化对象 -> getClass()
方法 -> 得到完整的“包类”名称
Java反射机制提供的功能
这些功能可能一时难以理解,但是首先得知道反射能干什么
在运行时 判断任意一个对象所属的类;
在运行时 构造任意一个类的对象;
在运行时 判断
任意一个类
所具有的成员变量和方法;
在运行时 调用
任意一个对象
的成员变量和方法;
在运行时 获取泛型信息;
在运行时 处理注解;
生成动态代理。
为什么要有反射?
假如有段代码:Object o = new Object();
当JVM启动,代码会编译成一个.class
文件,然后被类加载器加载到JVM的内存中,类Object加载到方法区中,创建了Object类的Class对象到堆中,注意这个不是new出来的对象,而是类的类型对象,每个类只有一个Class对象,作为方法区类的数据结构的接口。JVM创建对象前,会先检查类是否加载,寻找类对应的Class对象,若加载好,则为对象分配内存,初始化也就是代码:new Object()
。
为什么要讲这个呢?因为要理解反射必须知道它在什么场景下使用。
上面的程序对象是自己new的,程序相当于写死了给JVM去跑。假如一个服务器上突然遇到某个请求要用到某个类,但没加载进JVM,是不是要停下来自己写段代码,new一下,启动一下服务器。。。
有了反射之后…
当程序在运行
时,需要动态地加载一些类,这些类可能之前用不到所以不用加载到JVM,而是在运行时根据需要才加载,这样的好处对于服务器来说不言而喻,下面举几个例子来说明反射的作用:
- 我们的项目底层有时是用MySQL,有时用Oracle,需要动态地根据实际情况加载驱动类,这个时候反射就有用了,假设我们要用
com.java.dbtest.MyqlConnection
以及com.java.dbtest.OracleConnection
这两个类,这时候我们的程序就写得比较动态化,Class tc = Class.forName("com.java.dbtest.TestConnection");
通过类的全类名让JVM在服务器中找到并加载这个类,而如果是Oracle则传入的参数就变成另一个了。这时候就可以看到反射的好处了,这个动态性就体现出Java的特性了! - 在Spring中配置各种各样的bean时,是以配置文件的形式配置的,需要用到哪些bean就配哪些,Spring容器就会根据我们的需求去动态加载,我们的程序就能健壮地运行。
- Java的反射机制就是增加程序的灵活性,避免将程序写死到代码里, 例如: 实例化一个 Person()对象, 不使用反射就是 new Person(); 如果想变成实例化其他类, 那么必须修改源代码,并重新编译。 使用反射就是
class.forName("Person").newInstance();
而且这个类描述可以写到配置文件中,如.xml
,这样如果想实例化其他类,只要修改配置文件的"类描述"
就可以了,不需要重新修改代码并编译。
下面的话很重要 !
方法区存储的是类的信息,不是类对象,建议看一下JVM内存分配,类加载器加载类是通过方法区上类的信息在堆上创建一个类的Class对象,这个Class对象是唯一的(由JVM保证唯一),之后对这个类的创建都是根据这个Class对象来操作的。
可以理解成,某个类存在于方法区中,该类的Class对象存在于堆中,这个Class对象会作为运行时创建该类对象的模版。这个Class对象是唯一对应该类的,要区分所谓的实例和Class对象。为什么需要Class对象,想象一下,如果一个加载进方法区的类,在JVM运行时是动态加载进来的,如果没有这个Class对象,该如何访问一个未知的类并创建对象呢?没错,就是这个Class作为访问接口。
那么什么是Java的反射呢?
要让Java程序能够运行,那么就得让Java类被Java虚拟机加载。Java类如果不被Java虚拟机加载,是不能正常运行的。现在我们运行的所有的程序都是在编译期的时候就已经知道了所需要的那个类已经被加载了。
Java的反射机制就是,在编译并不确定是哪个类被加载了,而是在程序运行的时候才去加载、探知、自审。使用在编译期并不知道的类,这样的特点就是反射。
Java反射优点及缺点
优点
可以实现动态创建对象和编译,具有很大的灵活性。
缺点
对性能有影响。
使用反射基本上是一种解释操作,我们可以告诉JVM,希望做什么并且它满足我们的要求。这些操作总是慢于直接执行相同的操作。
反射相关的API
java.lang.Class
代表一个类
java.lang.reflect.Method
代表类的方法
java.lang.reflect.Field
代表类的成员变量
java.lang.reflect.Construct
代表类的构造器
Class类
前面一直说Class类,那么Class到底什么?
前言
在Java中一切皆对象,描述任何事物(不管是具体还是抽象),我们都可以用一个类来描述,同样,对于任何一个类,都有相同的特征,比如所有的类一般都包含属性、构造方法、一般方法、该类所继承的接口,该类所实现的接口等等,这些都是这个类所具备的信息,那么如何去描述这些信息,我们就用Class这个类的实例来描述,每个类对应一个Class类的实例。这样,我们可以通过这个实例,将该类复现出来。
Class类简介
Class类是描述类的类。
一个类被加载后,类的整个结构都会被封装在Class对象中。
一个类在内存中只有一个Class对象。
在Object类中定义了下面这个方法 ,此方法将被所有子类继承:
public final class getClass()
以上的方法返回值类型是一个Class类,此类是Java反射的源头。
实际上,所谓反射,从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称
。
对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。
对于每个类,JRE 都为其保留一个不变的Class类型的对象,一个Class对象包含了特定的某个结构(class/interface/enum/annotation/primitive type/void/[]
)的有关信息。
Class本身也是一个类;
Class对象只能由系统建立;
一个加载的类在JVM中只会有一个Class实例;
一个Class对象对应的是一个加载到JVM中的.class文件;
每个类的实例都会记得自己是由哪个Class实例所生成;
通过Class可以完整地得到一个类中的所有被加载的结构;
Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象。
Class类常用的方法
如下表:
方法名 | |
---|---|
static ClassforName(String name) |
返回指定类名name的Class对象 |
Object newInstance() |
调用缺省构造函数,返回Class对象的一个实例 |
getName() |
返回此Class对象所表示的实体(类、接口、数组类或void)的名称 |
Class getSuperclass() |
返回当前Class 对象的父类的Class对象 |
Class[] getinterfaces() |
获取当前Class对象的接口 |
ClassLoader getClassLoader() |
返回该类的类加载器 |
Constructor[] getConstructors() |
返回一个数组,该数组包含某些Constructor对象 |
Method getMethod(String name, Class..T) |
返回一个Method对象,此对象的形参类型为paramType |
Field[] getDeclaredFields() |
返回Field对象的一个数组 |
获取 Class 对象的三种方式
当类中方法定义为私有的时候我们能调用吗?不能!当变量是私有的时候我们能获取吗?不能!但是反射可以,比如源码中有你需要用到的方法,但是那个方法是私有的,这个时候你就可以通过反射去执行这个私有方法,并且获取私有变量。
如果我们想动态获取到这些信息,我们需要依靠 Class 对象。Class 类对象将一个类的方法、变量等信息告诉运行的程序。
Java 提供了三种方式获取 Class 对象:
- 已知具体类,通过
类的class属性
获取,该方法最为安全可靠,程序性能最高:
Class clazz1 = Person.class;
但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象。
- 通过
Class.forName()
传入类的路径获取:
Class clazz2 = Class.forName("cn.hory.Person");
- 通过
对象实例
获取:
Class clazz3 = p.getClass(); // p为Person类的实例
注意:以上通过不同方式多次获得一个类的Class对象都是同一个。
此外,还有其他方式
- 基本内置类型的包装类都有一个
TYPE
属性,所以我们可以通过TYPE
属性获得基本内置类型的Class对象:
Class clazz4 = Integer.TYPE;
- 知道其子类,获取其父类类型:
Class clazz5 = c1.getSuperclass();
测试1:
package com.hory.testReflect;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @Author Hory
* @Date 2020/9/20
*/
public class Person {
private String name;
private int age;
public Person() {
name = "Hory";
}
public Person(String name, int age){
this.name = name;
this.age = age;
}
public void publicMethod(String s) {
System.out.println("public:" + s);
}
private void privateMethod() {
System.out.println("name is " + name);
}
public static void main(String[] args) throws ClassNotFoundException,
IllegalAccessException, InstantiationException, NoSuchMethodException,
InvocationTargetException, NoSuchFieldException {
/**
* 获取 Person 类的 Class 对象并且创建Person类实例
*/
Class<?> personClass = Class.forName("com.hory.testReflect.Person");
Person personObject = (Person) personClass.newInstance(); //本质上是调用了类的无参构造器
// 也可以通过构造器创建对象
Constructor constructor = personClass.getDeclaredConstructor(String.class, int.class);
Person personObject01 = (Person) constructor.newInstance("hory", 25);
/**
* 获取所有类中所有定义的方法
*/
Method[] methods = personClass.getDeclaredMethods();
for(Method method : methods) {
System.out.println(method.getName());
}
/**
* 获取指定方法并调用
*/
Method publicMethod = personClass.getDeclaredMethod("publicMethod", String.class);
publicMethod.invoke(personObject,"this is a public method ");
/**
* 获取指定参数并对参数进行修改
*/
Field field = personClass.getDeclaredField("name");
// 不能直接操作私有属性,为了对类中的参数进行修改,需要取消安全检测
field.setAccessible(true);
field.set(personObject,"HoryChang");
/**
* 调用 private 方法
*/
Method privateMethod = personClass.getDeclaredMethod("privateMethod");
// 为了调用private方法我们取消安全检查
privateMethod.setAccessible(true);
privateMethod.invoke(personObject);
}
}
输出:
publicMethod
privateMethod
main
public:this is a public method
name is HoryChang
注意 : 有读者提到上面代码运行会抛出 ClassNotFoundException
异常,具体原因是你没有下面把这段代码的包名替换成自己创建的 TargetObject
所在的包 。
Class<?> personClass = Class.forName("com.hory.testReflect.Person");
区别:
方法 | 说明 |
---|---|
getFields |
只能找到public 属性的方法 |
getDeclaredFields |
可以找到全部的属性 |
getMethods |
获得本类及其父类的全部public 属性的方法 |
getDeclaredMethods |
获得本类全部方法 |
setAccessible:
- Method、Field、Constructor对象都有
setAccessible()
方法 setAccessible()
作用是启动或者禁用访问安全检查的开关- 参数为
true
的时候则指示反射的对象在使用时应该取消Java语言访问检查- 提高反射的效率。如果代码中必须用反射,而该方法需要频繁被调用,那么请设置为
true
- 是的原本无法访问的私有成员也可以被访问
- 提高反射的效率。如果代码中必须用反射,而该方法需要频繁被调用,那么请设置为
- 参数为
false
则指示反射的对象应该实施Java语言访问检查
测试2:
package testReflect;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Person();
System.out.println("包名: " + person.getClass().getPackage().getName() );
System.out.println("完整类名: " + person.getClass().getName());
Integer list = new Integer(2);
System.out.println(list);
//java中三种class获取方式
//方式一:利用对象调用getClass()方法获取该对象的Class实例
Class<? extends Person> personClazz01 = person.getClass();
//方式二:使用Class类的静态方法forName(),用类的名字获取一个Class实例
Class<?> personClazz02 = Class.forName("Person");
//方式三:运用.class的方式来获取Class实例,对于基本数据类型的封装类,
// 还可以采用.TYPE来获取相对应的基本数据类型的Class实例
Class<? extends Person> personClazz03 = Person.class;
Class<? extends Integer> listClazz = Integer.TYPE;
}
}
package test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
public class Test {
private int age;
private String name;
private int testint;
//Test类中我们定义是三个私有变量,生成两个公有的含参构造方法和一个私有的含参构造方法以及一个公有的无参构造方法
//下面我们通过反射获取这些构造方法
private Test(String name){
this.name = name;
}
public Test(int age){
this.age = age;
}
public Test(int age,String name){
this.age = age;
this.name = name;
}
public Test(){
}
public static void main(String[] args) {
// 根据一个字符串得到一个类
// String name = "ZHR";
// Class c1 = name.getClass();
// System.out.println(c1);
// //获取java.lang.String的类名
// String name = "java.lang.String";
// Class c1 = null;
// try{
// c1 = Class.forName(name);
// System.out.println(c1.getName());
// }catch (ClassNotFoundException e){
// }
// //我们还可以通过c2.getSuperclass()获取到他的父类
// Class c2 = null;
// try{
// c2 = Class.forName(name);
// System.out.println(c2.getSuperclass());
// }catch (ClassNotFoundException e){
// }
//获取类的所有构造方法
Test test = new Test();
Class c3 = test.getClass();
Constructor[] constructors;
constructors = c3.getDeclaredConstructors();
//通过 getDeclaredConstructors 可以返回类的所有构造方法,返回的是一个数组,因为构造方法可能不止一个,
//通过 getModifiers 可以得到构造方法的类型,
//通过 getParameterTypes 可以得到构造方法的所有参数,返回的是一个Class数组,
//所以我们如果想获取所有构造方法以及每个构造方法的参数类型,可以有如下代码
for(int i=0;i<constructors.length;i++){
System.out.print( 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(""); //换行
}
}
}
有哪些类型可以有Class对象?
class
:外部类、成员(成员内部类、静态内部类)、局部内部类、匿名内部类
interface
:接口
[]
:数组(二维数组也有)
enum
:枚举
annotation
:注解@interface
primitive type
:基本数据类型
void
参考:https://blog.csdn.net/qq_40406704/article/details/98060936?utm_source=app