目录
2. 任何数据类型(包括基本的数据类型)都有一个“静态”的class属性
3. 通过class类的静态方法:forName(String className)(最常用)
一、什么是反射?
1,JAVA反射机制是在运行状态中
对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
2,反射提供的功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法
(要想解剖一个类,必须先要获取到该类的字节码文件对象(class对象)。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.)
二、反射的原理和过程
1. 当我们编写完一个类的代码(例如 Student.java),并进行编译时,会产生 Student.class 字节码文件。里面记录着Student类的信息。
2. 当代码 new 一个类时,例如 new Student()。JVM会首先从磁盘中查找Student.class文件,然后利用Student.class 生成一个关于Student的Class对象,在JVM中,每个类都有且只有一个对应的 Class对象保存在内存。当要 new 一个Student实例对象时,就会利用Student对应的Class对象来创建Student实例对象。
3. 因此,上面过程让我们知道了 Class对象 是保留了某个类的所有信息时,我们就可以通过 得到某个类的Class 对象,来获取该类的所有信息,包括方法,字段 等等。
2.1 Class 对象
什么是Class对象?
Class对象并不是 new 出来的实例对象。
在Java中,用来表示 运行时类型信息的的对应类 就是 Class类。Class类是真真切切存在的,它就在 java.lang包里,路径是 java.lang.Class。Class对象 表示的是自己手动编写类的类型信息,比如创建一个Shapes类,那么,JVM就会创建一个Shapes对应Class类的Class对象,该Class对象保存了Shapes类相关的类型信息。
实际上在Java中,每一个类都有一个Class对象,每当我们编写并编译新创建的类,就会产生一个对应Class对象并且这个Class对象会被保存在 同名的.class文件中(编译后的字节码文件保存的就是Class对象)。
那我们为什么需要这样一个Class对象呢?
当我们new一个新对象时,JVM的类加载器会将对应的Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要的实例对象。
(值得注意的是):手动编写的每个class类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象。通俗点说就是 .class文件加载进内存后,JVM会自动创建一个Class对象,一个类只会产生一个Class对象(具体可阅读双亲委派模型)。
JVM加载类 和 反射机制的过程如下图:
2.2 获得 Class 对象的方法(三种方法)
1. getClass()
//第一种方式获取Class对象
Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
Class stuClass = stu1.getClass();//获取Class对象
2. 任何数据类型(包括基本的数据类型)都有一个“静态”的class属性
//第二种方式获取Class对象
Class stuClass2 = Student.class;
3. 通过class类的静态方法:forName(String className)(最常用)
通过类所在的路径来获取 Class
//第三种方式获取Class对象
try {
Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
在上面三种方式中,最常用的是第三种,因为:
第一种:第一种对象都有了还要反射干什么。
第二种:需要导入类包,依赖太强。(导入Student类包)
第三种:一般都使用第三种,只要通过一个类的路径就可以获取该类的Class对象。 一个字符串可以传入也可以写在配置文件中等多种方法。这样做解耦性比较强(路径写在配置文件)。
2.3 获取Class对象后,如何使用
2.3.1 利用Class对象获取对应的类的各种信息
public class teext {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
String a = "java.lang.String";
// 根据一类的全名字符串来获得一个类的类对象
Class<?> clazz = Class.forName(a);
System.out.println("========================");
// 获得传递过来的类的所有方法
Method[] methods = clazz.getDeclaredMethods();
// 打印 类的所有方法
for (Method m : methods) {
System.out.println(m);
}
System.out.println("========================");
// 打印 类的所有属性变量
Field[] fields = clazz.getDeclaredFields();
for (Field f:fields){
System.out.println(f);
}
System.out.println("========================");
// 打印 类的所有构造器
Constructor[] constructors = clazz.getDeclaredConstructors();
for(Constructor c : constructors){
System.out.println(c);
}
}
}
2.3.2 利用Class对象创建实例
public class teext {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
String a = "java.lang.String";
//获取String的Class对象
Class<?> c = Class.forName(a);
//通过Class对象获取指定的Constructor构造器对象,该构造器是有一个String形参的
Constructor constructor=c.getConstructor(String.class);
//根据构造器创建实例:
Object obj = constructor.newInstance("hello reflection");
System.out.println(obj); //打印hello reflection
}
}
(1)批量获取的方法:
public Constructor[] getConstructors():所有"公有的"构造方法
public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
(2)单个获取的方法,并调用:
public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;
2.3.3 利用Class对象调用具体方法
String a = "java.lang.String";
//获取String的Class对象
Class<?> c = Class.forName(a);
//通过Class对象获取指定的Constructor构造器对象,该构造器是有一个String形参的
Constructor constructor=c.getConstructor(String.class);
//根据构造器创建实例:
Object obj = constructor.newInstance("hello reflection");
//获取指定的charAt方法
Method m = c.getMethod("charAt",int.class);
//利用 invoke() 调用方法,obj是指定调用方法的对象,2是给charAt()的形参
//如果反射的是静态方法,那么obj就要是 null
Object result = m.invoke(obj,2);
System.out.println(result);
2.3.4 反射方法的其他使用--通过反射运行配置文件内容
Student类:
public class Student {
public void show(){
System.out.println("is show()");
}
}
配置文件以txt文件为例子:
className = cn.fanshe.Student
methodName = show
测试类:
/*
* 我们利用反射和配置文件,可以使:应用程序更新时,对源码无需进行任何修改
* 我们只需要将新类发送给客户端,并修改配置文件即可
*/
public class Demo {
public static void main(String[] args) throws Exception {
//通过反射获取Class对象
Class stuClass = Class.forName(getValue("className"));//"cn.fanshe.Student"
//2获取show()方法
Method m = stuClass.getMethod(getValue("methodName"));//show
//3.调用show()方法
m.invoke(stuClass.getConstructor().newInstance());
}
//此方法接收一个key,在配置文件中获取相应的value
public static String getValue(String key) throws IOException{
Properties pro = new Properties();//获取配置文件的对象
FileReader in = new FileReader("pro.txt");//获取输入流
pro.load(in);//将流加载到配置文件对象中
in.close();
return pro.getProperty(key);//返回根据key获取的value值
}
}
三、反射的优缺点
优点:
反射提高了Java程序的灵活性和扩展性,降低耦合性,提高自适应能力。它允许程序创建和控制任何类的对象,无需提前硬编码目标类;
缺点:
1. 性能问题,反射机制的性能比直接代码要差
2. 由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用。(反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。)