一、反射机制
定义:在运行期间,可以获取对象的类型、类型的方法、类型的属性、类型构造方法等。
简单来说,反射机制指的是程序在运行时能够获取自身的信息。在Java中,只要给定类的名字, 那么就可以通过反射机制来获得类的所有信息。
1.获取对象的类型(类对象)
有三种方法:
方法1:Object .getClass();
方法2: Class.forName(“类名”);
方法3: 类名.class.
public class User {
private String name;
private int age;
public User(int a) {
}
public User() {
}
private void m1() {
System.out.println("m1被执行");
}
public void m1(int a) {
System.out.println("m1(int a)被执行");
}
public void m1(int a, int b) {
System.out.println("m1(int a, int b)被执行");
}
public void m1(char a, String b) {
}
@Resource
public void m2() {
}
private void m3() {
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// 方法1
System.out.println(new User().getClass());
// 方法2
System.out.println(Class.forName("reflect.User"));
// 方法3
System.out.println(User.class);
// 注:这个类型信息在jvm仍表现为一个对象,而且只有一份
System.out.println(new User().getClass() == Class.forName("reflect.User"));
2.类对象的功能
a.用反射的方式创建对象
正常创建对象: new 类名();
反射创建对象: 类对象.newInstance();
// 反射创建对象, newInstance() 限制1: 要求对象有无参构造 限制2: 构造方法不能私有
User user = User.class.newInstance();
System.out.println(user);
b.获取方法信息
类对象.getMethods(); // 获取所有公共方法(public),包括继承的
类对象.getMethod(方法名, 参数类型); // 找公共方法, 包括继承的
类对象.getDeclaredMethods(); // 获取本类的所有方法不包括继承
类对象.getDeclaredMethod(方法名, 参数类型); // 找本类的,不包括继承的
// 获取方法信息
Method[] methods = User.class.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
}
c.获取属性信息
类对象.getFields(); // 获取所有公共属性包括继承的
类对象.getDeclaredFields(); // 获取本类所有属性不包括继承的
类对象.getField(“属性名”);
Field[] fields = User.class.getFields();
for (Field field : fields) {
System.out.println(field);
}
d.获取构造方法(了解)
类对象.getConstructors(); // 获取所有公共的构造方法
类对象.getDeclaredConstructors(); // 获取本类所有构造方法
类对象.getConstructor(int.class); // 获取int参数类型的构造
f.反射调用方法
正常调用方式: 对象.方法名(参数);
反射调用方式: 方法.invoke(对象, 参数);
优势:可以调用私有方法,突破正常方法的限制, 经常用在开源框架中;缺点:调用复杂,效率低。
如果方法为私有,先使用setAccessible(true)设置。
如果构造方法为私有,先使用getDeclaredConstructor(),再使用setAccessible(true)设置,最后newInstance();。
// 方法.invoke(对象, 参数);
Method m1 = User.class.getDeclaredMethod("m1");
m1.setAccessible(true); // 设置这个方法可以被访问,可以突破访问修饰符的限制
m1.invoke(u); // 反射调用方法(性能低)
// 反射调用 public void m1(int a)
Method m12 = User.class.getDeclaredMethod("m1", int.class);
m12.invoke(u, 1);
Method m13 = User.class.getDeclaredMethod("m1", int.class, int.class);
m13.invoke(u, 4, 5);
// 调用私有构造
Constructor<Student> cons = Student.class.getDeclaredConstructor();
cons.setAccessible(true);
Student s = cons.newInstance();
System.out.println(s);
二、类加载机制
类的加载由类加载器完成,类加载器通常由JVM提供,JVM提供的类加载器通常被称为系统类加载器,除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。
通过使用不同的类加载器,可以从不同来源加载 类的二进制数据,一般有如下几种来源:
a.从本地文件系统加载class文件
// 类加载器, 作用:加载一个不在classpath下的类
ClassLoader cl = new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
FileInputStream in = new FileInputStream("I:\\13\\20181201\\Treasure.class");
byte[] bytes = new byte[1024 * 8];
int len = in.read(bytes);
// 调用父类的方法根据字节数组加载类
return defineClass(name, bytes, 0, len);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
};
b.从jar包中加载class文件(常见的方式)
c.通过网络加载class文件
d.把一个java源文件动态编译,并执行加载。
类加载器无需等到“首次使用”该类时再加载该类,Java虚拟机规范允许系统预先加载某些类。