1.反射机制怎么说
什么是反射机制?
Java不是动态语言,但是Java可以称为准动态语言,即Java有一定的动态性,可以利用反射机制获取类似动态语言的特性
反射机制有什么用?
通过java语言中的反射机制,可以操作字节码文件(比如class文件)。
后期要学习java的高级框架,高级框架中底层实现原理用的都是反射机制,学习反射机制有利于理解剖析框架底层的源代码。
反射机制相关的包和重要的类
包:java.lang.reflect.*
java.lang.Class:代表整个字节码,代表一个类型,代表一个类。
java.lang.reflect.Method:代表字节码中的方法字节码。
java.lang.reflect.Field:代表字节码中的属性字节码。
java.lang.reflect.Constructor:代表字节码中的构造方法字节码。
反射机制的优缺点
优点:反射机制具有强大的灵活性。
缺点,使用反射机制是一种解释操作,这类操作总是慢于直接执行相同的操作。
2.反射机制基本操作
java中有三种方式可以获取类
第一种方法,用过Class类中的静态方法forName
package reflectTest;
public class Test01 {
public static void main (String[] args) throws Exception {
// 通过Class.forName静态方法获取类名
// 要求,参数中的路径必须带有包名
Class c1 = Class.forName("myClass.myClass");
Class c2 = Class.forName("java.lang.String");
System.out.println(c1);
System.out.println(c2);
}
}
第二种方法,java中任何一个对象都有一个方法:getClass
String s = "abc";
Class c3 = s.getClass();
System.out.println(c3);
Integer num = new Integer(43);
Class c4 = num.getClass();
System.out.println(c4);
第三种方法,java语言中任意一种属性,包括基本类型数据都有class属性。
Class c5 = int.class;
System.out.println(c5);
Class c6 = Thread.class;
System.out.println(c6);
这三种方法实际上就是通过类,通过类方法,通过类属性来获取类的方法
// 获取两种类名
Class c1 = Class.forName("myClass.myClass");
Class c2 = Class.forName("java.lang.String");
// 这种方法获得的是完整的带包名的类型
System.out.println(c1.getName());
System.out.println(c2.getName());
// myClass.myClass
// java.lang.String
// 这种方法获得的是简单类名
System.out.println(c1.getSimpleName());
System.out.println(c2.getSimpleName());
// myClass
// String
通过反射机制,获取类,再通过类实例化对象**
// 通过反射机制获取类并实例化对象
// 注意用的是.而不是/
Class c1 = Class.forName("myClass.myClass");
// 此时forName的方法执行会导致类加载
// 会先执行类中的静态代码块
// 返回一个obj
Object o = c1.newInstance();
System.out.println(o);
// 实际上底层会调用的无参构造方法,然后才实例化的对象
注:涉及到了类加载问题,newInstance调用了类的无参构造,要保证类的无参构造存在。
反射机制的灵活就体现出来了,我们只需要修改forName方法中的参数,就可以创建不同类型的对象。
通过IO流读取properties文件
package reflectTest;
import java.io.FileReader;
import java.util.Properties;
public class Test03 {
public static void main(String[] args) throws Exception {
// 创建一个字符读取流
FileReader reader = new FileReader("src/class.properties");
//Properties类表示一组持久的属性。
// Properties可以保存到流中或从流中加载。
// 属性列表中的每个键及其对应的值都是一个字符串。
Properties pro = new Properties();
// 参数可以传InputStream类型的
// 参数也可以传Reader类型的
pro.load(reader);
// 关闭流
reader.close();
//使用此属性列表中指定的键搜索属性。
String className = pro.getProperty("className");
System.out.println(className);
}
}
灵活性体现在只需要操作配置文件而不是重写源码实现代码的重用。
我们properties文件的路径是在根目录下的src中的…文件,不具有通用性,我们希望有一段通用的代码可以得到src中的文件
String path = null;
path = Thread.currentThread().getContextClassLoader().getResource("class.properties").getPath();
System.out.println(path);
// 拿到了文件的绝对路径/D:/CODE/java/reflect/out/production/reflect/class.properties
但要注意的是,文件必须放在src的根目录下。
获取了这个path之后,就可以创建流,创建properties对象,加载….
实际上,java为我们提供了一种操作,可以直接获取到proterties文件:资源绑定器。
资源绑定器只能绑定properties文件,而且这个文件必须在类目录下。
// 这是一个静态方法
ResourceBundle bundle = ResourceBundle.getBundle("class");
String s = bundle.getString("className");
System.out.println(s);
3.类加载器
JDK中自带了三种类加载器
1.启动类加载器 : rt.jar
2.扩展类加载器 : ext/*.jar
3.应用类加载器 : classpath
代码在执行之前会把所需要的所有类加载到JVM之中。
java中为了保证类加载的安全,使用了双亲委派机制,优先从启动类加载器加载类,然后是扩展类加载器,如果这两个双亲都加载不到类,那么会从应用类加载器中加载类,知道加载到为止。
4.获取类中的成员
获取类中的所有field
package reflectTest;
import java.lang.reflect.Field;
public class Test06 {
public static void main(String[] args) throws Exception{
Class c = Class.forName("myClass.myClass");
// 获取类中的fields
// 这是Class类中的方法,返回一个field数组
Field [] fields1 = c.getFields();
for (Field f:fields1){
// 获取这个属性的名字
System.out.println(f.getName());
}
// 实际上这种方法只能够获取到public修饰的属性
// 若想获得所有属性,就要用到这个方法
// 获取都有已经声明过的属性
Field [] fields2 = c.getDeclaredFields();
for (Field f:fields2){
String s = f.getName();
System.out.println(s);
}
}
}
获取field的修饰符和类型
Field [] fields2 = c.getDeclaredFields();
for (Field f:fields2){
// 这个方法返回的是不同修饰如对应的数字
// 然后获取修饰符的字符串形式
int i = f.getModifiers();
// Modifier重写了toString方法
String s = Modifier.toString(i);
System.out.println(s);
// 返回的是不同修饰符对应的类
Class type = f.getType();
// 打印类的名字
System.out.println(type.getSimpleName());
System.out.println(f.getName());
System.out.println();
}
5.通过反射机制访问一个对象的属性
package reflectTest;
import java.lang.reflect.Field;
public class Test07 {
public static void main(String[] args) throws Exception {
// 首先获取到类
Class c = Class.forName("myClass.myClass");
Object o = c.newInstance();
// 然后获取到属性
Field f = c.getDeclaredField("no");
// 操作这个属性
// set方法在Filed类中,第一个参数写类的实例对性,第二个参数写属性值
f.set(o,100);
// get方法获得属性的值,单数写类的实例对象,返回一个Object类型的引用
Object o2 = f.get(o);
System.out.println(o2);
// 单上面的方式只能够访问public修饰的属性,如果要访问其他修饰符的属性,就要打破封装了
// Field类中的这个方法,可以打破封装
f.setAccessible(true);
}
}
6.通过反射机制访问一个对象的方法
引:关于可变长参数
在java中方法的参数可以是一个可变长度的在参数列表中写 数据类型… args就可以实现了。
package reflectTest;
public class Test08 {
public static void main(String[] args) {
// 不传参数可以
m();
// 传一个参数也可以
m(1);
// 传一个数组也可以
int [] arr = new int[10];
m(arr);
}
public static void m(int... args){
// 此时args有length属性,说明在底层args是一个int数组
for (int i=0 ; i<args.length ; i++){
}
}
}
注意:只能传一种可变长参数,如果传入可变长参数的同时还要传进去其他参数,那么可变长参数必须写在参数列表的最后。
通过反射机制获取类中的方法信息并调用方法
获取类中的方法信息和获取类中的属性流程类似。
package reflectTest;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class Test09 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("myClass.myClass");
Object o = c.newInstance();
// 获取方法
Method [] methods = c.getDeclaredMethods();
for (Method method:methods){
// 获取方法的修饰符
System.out.println(Modifier.toString(method.getModifiers()));
// 获取方法的返回值
System.out.println(method.getReturnType().getSimpleName());
// 获取方法的方法名
System.out.println(method.getName());
// 获取方法的参数列表
Class [] cc = method.getParameterTypes();
for (Class ccc:cc){
System.out.println(ccc.getSimpleName());
}
System.out.println("======");
}
}
}
通过反射调用类中的方法
// 获取到m1方法
Method m1 = c.getDeclaredMethod("m1", int.class);
// 返回值是一个obj类型的,用到m1的invoke方法,第一个参数写类的实例化对象,第二个参数写参数列表
Object obj_m1 = m1.invoke(o,12);
System.out.println(obj_m1);
Method m2 = c.getDeclaredMethod("m2",int.class,String.class,boolean.class);
m2.invoke(o,13,"abv",true);
要注意的是,首先要获得一个准确的方法(怎么获得:方法名,参数列表)传给Method类的引用m1,然后调用m1的invoke方法,参数是一个类对象和参数列表,返回值是Obj类型的,用于存储方法的返回值。
7.通过反射机制调用构造方法
通过反射机制调用构造方法和调用成员方法类似。
package reflectTest;
import java.lang.reflect.Constructor;
public class Test10 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("myClass.myClass");
// 这种方式实际上已经调用了无参构造方法
// Object o = c.newInstance();
// 调用类的getDeclaredConstructor方法
// 参数列表里面写你想调用哪一个构造方法
Constructor con = c.getDeclaredConstructor(int.class,String.class);
// 用Constructor类对象的newInstance方法,返回一个obj类型的对象
// 实际上这是一个创建对象的方法
Object o = con.newInstance(100,"zjl");
System.out.println(o);
}
}
8.获取类的父类和实现的接口
package reflectTest;
public class Test11 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("java.lang.String");
// 这种方法可以获得继承的父类,只有一个
Class superclass = c.getSuperclass();
System.out.println(superclass.getName());
// 这种方式可以获得实现的接口,很多
Class [] interfaces = c.getInterfaces();
for (Class in : interfaces){
System.out.println(in.getName());
}
}
}