java基础-反射
1 什么是反射
在程序运行期间,动态获取信息(类,属性,方法),动态操作对象的一种功能。能够将java类的类型,构造方法,成员变量,方法映射成一个java对象并进行操作。
优点:运行时获取类和对象的内容,提高了系统的灵活性和扩展性(可以利用多态,在运行时获取需要的对象)
缺点:性能比普通方法差,jvm无法优化代码,破坏类的封装性
2 反射相关的API
2.1 Class
Class用于封装java类型的相关信息,构造方法为private,只能由jvm类加载器加载
Class位于java.lang包下,Constructor,Method,Field等位于java.lang.reflect包下
常用的方法:
创建Class对象的三种方式:
- Class<?> clazz = Class.forName(String);
- Class clazz = T.class
- Class<? extends Obj> clazz = obj.getClass();
举个栗子:
package bean;
/**
* 创建一个Product类放在bean这个包下
*/
@Getter
@Setter
public class Product {
private String productName;
private Integer count;
public Product() {}
public Product(String productName, Integer count) {
this.productName = productName;
this.count = count;
}
public void queryCount() {
System.out.println("count:" + this.getCount());
}
}
@Test
public void test1() {
// 1.入参需要传入类的全路径 并且处理已检查异常
try {
Class<?> p = Class.forName("bean.Product");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 2.获取Class的第二种方式
Class<Product> productClass = Product.class;
// 3.获取Class的第三种方式
Product product = new Product();
Class<? extends Product> pClass = product.getClass();
}
类分析相关的方法:
- getName 获取类的名称
- getConstructor(s)\Field(s)\Method(s)\Annotation(s)\Class(es) 获取该类,父类,父接口的公有的构造器、属性、方法、注解、内部类
- getDeclareConstructor(s)\Field(s)\Method(s)\Annotation(s)\Class(es) 获取该类所有的构造器、属性、方法、注解、内部类
- isInstance() 判断是否是该类的实例
方法带Declare说明获取的不止是公有,否则获取的只能是公有
方法结尾带(s)说明是获取类中全部,否则获取的是指定的,需要传入参数列表来指定
举个栗子:
// 获取公有的构造方法
Constructor<?>[] constructors = p.getConstructors();
// 获取全部的构造方法
Constructor<?>[] declaredConstructors = p.getDeclaredConstructors();
// 获取指定的构造方法 入参决定获取的是哪个构造方法
// 空参构造方法
Constructor<?> constructor = p.getConstructor();
// 初始化商品名称和数量的构造方法
Constructor<?> constructor2 = p.getConstructor(String.class,Integer.class);
// Method
Method[] methods = p.getMethods();
Method[] declaredMethods = p.getDeclaredMethods();
Method setCount = p.getMethod("setCount", Integer.class);
Method queryCount = p.getMethod("queryCount");
// Field
Field[] fields = p.getFields();
Field[] declaredFields = p.getDeclaredFields();
Field count = p.getField("count");
创建实例:
newInstance()
使用该方法时,类中必须有空参构造方法。如果想用带参数的构造方法创建实例,需要先获取指定参数的构造方法对象,然后利用Constructor.newInstance()生成类的实例
举个栗子:
Class<?> p = Class.forName("bean.Product");
// 空参构造方法
Product o1 = (Product) p.newInstance();
Class<Product> productClass = Product.class;
Product product1 = productClass.newInstance();
Product product = new Product();
Class<? extends Product> pClass = product.getClass();
Product product2 = pClass.newInstance();
// 初始化商品名称和数量的构造方法
Constructor<?> constructor2 = p.getConstructor(String.class,Integer.class);
Product mobile = (Product) constructor2.newInstance("手机", 10);
获取类加载器:
getClassLoader()
类加载器与双亲委派机制(TODO)
2.2 Constructor
java类中构造方法的抽象,可以获取名称,参数类型,访问修饰符(getModifiers),注解
newInstance() 生成一个类的实例
举个栗子:
Class<?> p = Class.forName("bean.Product");
// 获取指定的构造方法 入参决定获取的是哪个构造方法
// 空参构造方法
Constructor<?> constructor = p.getConstructor();
Product o = (Product) constructor.newInstance();
// 初始化商品名称和数量的构造方法
Constructor<?> constructor2 = p.getConstructor(String.class,Integer.class);
Product mobile = (Product) constructor2.newInstance("手机", 10);
2.3 Method
java类中方法的抽象,可以获取名称,参数类型,访问修饰符(getModifiers),返回值类型,注解等
invoke() 方法调用,需要有对象实例。
举个栗子:
Class<?> p = Class.forName("bean.Product");
Product product = (Product) p.newInstance();
Method setProductName = p.getMethod("setProductName", String.class);
setProductName.invoke(product,"手机");
Method getProductName = p.getMethod("getProductName");
// console输出手机
System.out.println(getProductName.invoke(product));
2.4 Field
java类中属性的抽象
getType() 获取属性类型
get() 获取属性值
set() 设置属性
setAccessible()设置能否访问 方法和类的反射api也有这个方法
举个栗子:
// 反射生成一个Product实例
Class<?> p = Class.forName("bean.Product");
Product product = (Product) p.newInstance();
// 获取属性
Field productName = p.getDeclaredField("productName");
// productName是私有属性 要想直接访问需要setAccessible为true
productName.setAccessible(true);
productName.set(product,"手机");
System.out.println(productName.get(product));
2.5 Modifier
封装了访问修饰符的方法,可以查看是否是public,是否是private,是否是static,final等
举个栗子:
Class<?> p = Class.forName("bean.Product");
Product product = (Product) p.newInstance();
Method setProductName = p.getMethod("setProductName", String.class);
setProductName.invoke(product,"手机");
Field productName = p.getDeclaredField("productName");
// false
System.out.println(Modifier.isPublic(productName.getModifiers()));
// true
System.out.println(Modifier.isPrivate(productName.getModifiers()));
3 问题
3.1 Class.forName()和.class获取Class对象有什么区别?
Class.forName()会进行静态初始化 .class不会
Class.forName()有两个重载的方法,一个只传字符串,另一个传字符串,boolean,classLoader,这个boolean代表是否初始化这个类。只传字符串的方法里boolean默认是true。
3.2 用instanceof 可以和父类比较吗,且会返回true吗?
可以 会
3.3 用getClass并用== 可以和T.class比较吗,且会返回true吗?
不可以 编译报错 返回值不同 见2.1
3.4 用getClass并用.equals可以和父类比较吗,且会返回true吗?
可以比较 返回false
3.5 getDeclaredXXX 有哪几种?
- 注解Annotation
- 内部类Classed
- 构造方法Construcotor
- 字段Field
- 方法Method
3.6 getMethods()返回哪些方法, getDeclaredMethods()会返回哪些方法
getMethods()获取本类,父类,父接口public方法,getDeclaredMethods() 获取本类所有方法
3.7 Filed、Method、Constructor如何使用
Filed get,set赋值
Method invoke方法调用
Constructor newInstance实例化
setAccessible 设置能否访问
3.8 如何提高反射效率
- 使用高性能反射包,例如ReflectASM(ReflectASM 使用字节码生成的方式实现了更为高效的反射机制)
- 缓存反射的对象,避免每次都要重复去字节码中获取。(缓存!缓存!)
- method反射可设置method.setAccessible(true)来关闭安全检查。
- 尽量不要getMethods()后再遍历筛选,而直接用getMethod(methodName)来根据方法名获取方法
- 利用hotspot虚拟机中的反射优化技术(jit技术)
3.9 用反射获取到的method对象, 是返回一个method引用,还是返回1个拷贝的method对象?
拷贝的 可以翻翻源码,如下:
getReflectionFactory().copyMethod(res)
3.10 getMethods()后自己做遍历获取方法,和getMethod(methodName) 直接获取方法, 为什么性能会有差异?
因为返回的method是copy一份再返回的,copy多次返回,和copy一次返回 效率肯定有差异(开发时要注意)
3.11 获取方法时,jvm内部其实有缓存,但是返回给外部时依然会做拷贝。那么该method的缓存是持久存在的吗?
不是 缓存的属性是个软引用,内存不足时可能回收,也可以设置参数发生GC时回收
-XX:SoftRefLRUPolicyMSPerMB
3.12 反射是线程安全吗?
是 获取反射数据时利用cas
3.13 普通方法调用,反射调用,关闭安全检查调用性能差异(setAccessibile)
普通方法>关闭安全检查反射调用>反射调用
4 延伸
4.1 静态代理和动态代理
4.2 解释型语言和编译型语言
解释型:代码逐行翻译执行 性能比编译型差
编译型:通过编译器编译后再执行
java编译为字节码,jvm再将字节码解释执行
4.3 静态语言和动态语言
静态:编译时确定变量类型
动态:运行时确定变量类型,及对应的方法调用