文章目录
静态语言和动态语言
在说反射前,先说一下静态语言和动态语言的区别。
- 动态语言:
在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数 可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
主要动态语言:Object-C、 C#、 JavaScript、 PHP、 Python。
- 静态语言:
与动态语言相对应的,运行时结构不可变的语言就是静态语言。Java不是动态语言,但Java 可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!
主要静态语言:Java、C、C++。
反射是什么?
Reflection (反射) 是Java被视为动态语言的关键,反射机制允许程序在执行期借助于 Reflection API
取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。 创建对象时,JVM会从本地磁盘中将class加载到内存中,之后会在堆内存的方法区中产生了一个Class类型的对象(一个类只有一个Class 对象,这点很重要),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子, 透过这个镜子可以看到类的结构,所以,我愿称之为“反射”(阿凯,你是最强的体术忍者)。
如何评价反射?
反射是框架设计的灵魂。
获取Class方式有哪些?
- Class.forName(“类的路径”)
Class.forName("com.example.puff.Test");
- 类名.class
Test.class;
- 对象名.getClass()
test.getClass();
- 内置基本数据类型可以通过包装类.TYPE获取
Integer.TYPE;
反射的作用?
通过反射机制,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。在java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。
反射机制提供的功能
- 运行时判断对象所属的类。
- 运行时构造一个类的对象。
- 运行时获得一个类的方法和属性。
- 运行时获取泛型信息。
- 运行时处理注解。
- 生成动态代理。
- …
反射的优点和缺点
优点:可以实现动态创建对象和编译,体现出很大的灵活性。
缺点:反射是一种解释性操作,速度较慢。
所以通过new创建对象的效率比较高。通过反射时,先找查找类资源,使用类加载器创建,过程比较繁琐,效率较低。
Class类常用方法
方法 | 描述 |
---|---|
static ClassforName(String name) | 返回指定类名name的Class对象 |
Object newlnstance() | 调用缺省构造函数,返回Class对象的一个实例 |
调用缺省构造函数,返回Class对象的一个实例 | 返回此Class对象所表示的实体(类,接口,数组类或void)的名称。 |
Class getSuperClass() | Class getSuperClass() 返回当前Class对象的父类的Class对象 |
Class[] getinterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Constructor[] getConstructors() | 返回一个包含某些Constructor对象的数组 |
Method getMothed(String name,Class… T) | 返回Field对象的一个数组 |
Field[] getDeclaredFields() | 返回Field对象的一个数组 |
什么时候会发生类的初始化?
类的主动引用(一定会发生类的初始化)
- 当虚拟机启动,先初始化main方法所在的类
- new一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,父类会被初始化,不会导致子类初始化
- 通过数组定义类引用,不会触发此类的初始化
- 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
类加载器:将类(class)加载进内存
- 引导类加载器(Bootstap Classloader):用C++编写的, JVM自带的类加载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取
- 扩展类加载器(Extension Classloader):负责/e/ib/ext目录下的jar包或-D java.ext.dirs指定目录下的jar包装入工作库
- 系统类加载器(System Classloader):负责java -classpath或-D java.class. path所指的目录下的类与jar包装入工作,是最常用的加载器
//获取系统类的加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//获取系统类加载器的父类加载器-->扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);
//获取扩展类加载器的父类加载器-->根加载器(C/c++) 无法获得
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
//测试当前类是哪个加载器加载的 系统加载器
ClassLoader classLoader = Class.forName("com.sia.User").getClassLoader();
System.out.println(classLoader);
//测试JDK内置的类是哪个加载器加载的 根加载器 无法获得
classLoader = Class.forName ("java.lang.0bject").getClassLoader();
System.out.println(classLoader);
//如何获得系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));
通过反射获取类结构
Class c = Class.forName("com.sia.User");
c.getName(); //获取包名+类名
c.getSimpleName(); //获取类名
c.getFields(); //获取public属性
c.getDeclaredFields(); //获取全部属性
c.getDeclaredFields("fieldName"); //获取指定属性
c.getMethods(); //获取本类及父类全部public方法
c.getDeclaredMethods(); //获取本类所有方法
//传入类型是因为重载
c.getDeclaredMethods("methodName","type"); //获取指定方法
c.getConstructor(); //获取本类所有的public构造器
c.getConstructor(); //获取本类所有构造器
c.getDeclaredConstructor("type","type"...); //获取指定构造器
动态创建对象
//创建pojo类
public class User{
private String name;
private int age;
public User(){
}
public User(String name,int age){
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
}
//通过class类创造对象
Class c = Class.forName(com.sia.User);
User user1 = (User)c.newInstance();
//[注意]:该方法调用无参构造方法,必须有无参构造方法
//通过构造器创建
Constructor constructor = c.getDeclaredConstructor(String.class,int.class);
User user2 = (User)constructor.newIstance("胡图图","18");
//通过反射获取一个方法
Method setName = c.getDeclaredMethods("setName",String.class);
setName.invoke(user2,"胡图图");
//通过反射获取属性
Field age = c.getDeclaredField("age");
age.setAccessible("true"); //关闭安全监测 允许访问私有方法(可以提高效率)
age.set(user2,"18");
反射操作泛型
- Java采用泛型擦除的机制来引入泛型, Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题, 但是,一旦编译完成,所有和泛型有关的类型全部擦除
- 为了通过反射操作这些类型, Java新增了ParameterizedType ,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
- ParameterizedType:表示一种参数化类型,比如
Collection<String>
。 - GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型 。
- TypeVariable:是各种类型变量的公共父接口 。
- WildcardType:代表一种通配符类型表达式 。
获取泛型类型
public static void main(String[] args) throws NoSuchMethodException {
//通过反射拿到方法
Method method = Test2.class.getMethod ( name: "test", Map.class, List.class) ;
//获得方法的泛型参数类型
Type[] genericParameterTypes = method.getGenericParameterTypes();
//遍历
for (Type genericParameterType : genericParameterTypes) {
//判断是否是参数化类型
if (genericParameterType instanceof ParameterizedType){
//获得真实类型
Type[] actualTypeArguments = ((ParameterizedType)genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument :actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
获取返回值类型
public static void main(String[] args) throws NoSuchMethodException {
//通过反射拿到方法
Method method = Test2.class.getMethod ( name: "test", null) ;
//获得方法的返回值参数类型
Type[] genericParameterTypes = method.getGenericReturnTypes();
//遍历
for (Type genericParameterType : genericParameterTypes) {
//判断是否是返回值类型
if (genericParameterType instanceof ParameterizedType){
//获得真实类型
Type[] actualTypeArguments = ((ParameterizedType)genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument :actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}