前言
反射是被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类得内部信息,并能够直接操作任意对象得内部属性和方法(包括私有)。
加载完类之后,在方法区中就会产生一个Class类型对象,这个对象就包含了完整得类得结构信息。我们可以通过这个对象看到类得结构。这个对象就像就像一面镜子,透过这个镜子可以看到类得结构,所以,我们形象得称之为:反射。
所以我们首先简单的讲一下类加载,然后在讲反射机制。
类加载
类加载步骤
分为三步:加载、链接、初始化
加载Loading 作用:在内存中生成一个代表这个类的Class对象,作为方法区中这和类的各种数据的访问入口。
什么时候加载? - new - Class.forName(“包名.类名”)
怎么加载?
- Bootstrap(根加载器,加载$JAVA_HOME/jre/lib/rt.jar包内的class文件,包含java运行环境所需的基础类)
- ExtClass(加载$JAVA_HOME/jre/lib/ext/.jar目录下的class文件)
- AppClass(用于`加载当前应用的classpath的所有类)
链接 Linking 该过程分三个阶段:验证、准备、解析
- 验证:
验证阶段 用于确保加载的Class文件的字节流包含的信息是否符合虚拟机要求,保证其合法性。 - 准备阶段: 为类变量(静态变量)分配内存并跟根据对象类型赋对应的默认值。
- 解析阶段:用于将符号引用转换为直接引用。
初始化 initialization
执行类的构造方法的过程
反射技术
运行时动态访问对象的属性和方法。
Class对象
上面我们简单的了解了以下类加载机制。 那我们如何得到Class对象呢?
通常我们又三种方法:
-
第一种,使用 Class.forName 静态方法。 前提:已明确类的全路径名。 -
第二种,使用 .class 方法。 说明:仅适合在编译前就已经明确要操作的 Class -
第三种,使用类对象的 getClass() 方法。 适合有对象示例的情况下
代码:
package com.dyit.clazz;
public class GetClass {
static class A {
public A() {
}
public void print() {
System.out.println("内部类");
}
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
System.out.println(A.class);
A b = new A();
System.out.println(b.getClass());
System.out.println(Class.forName("com.dyit.clazz.GetClass$A"));
}
}
结果截图: 那我们拿到Class对象可以干什么呢?
之前我们类的实例化有三种方式:
现在我们可以通过类的Class对象创建类的实例
通过Class.newInstance() 可以得到类的实例; 可以通过Class.getSimpleName() 得到类的名称
代码如下:
package com.dyit.clazz;
public class ClassDemo {
static class A {
public A() {
}
public void print() {
System.out.println("内部类");
}
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Class<A> a = A.class;
System.out.println(a.getName());
System.out.println(a.getSimpleName());
A b = a.newInstance();
b.print();
}
}
结果:
Field类
描述类中的属性,(数值)域
首先通过Class对象得到所有的属性 Class.getDeclaredFields()
通过设置field.setAccessible(true) 可以访问私有属性. field.get(c) 得到c关于这和属性的值 field.getName() 得到属性的名称 field.getType() 得到属性的类型 field.getType().getSimpleName() 得到属性的简单名称
可以通过set方法设置对象属性的值:field.set(c, 999.99)
代码:
package com.dyit.field;
import java.lang.reflect.Field;
public class FieldDemo {
static class Car {
private String brand;
private String color;
private double price;
public void setBrand(String brand) {
this.brand = brand;
}
public void setColor(String color) {
this.color = color;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Car [brand=" + brand + ", color=" + color + ", price=" + price + "]";
}
}
public static void main(String[] args) throws Exception, IllegalAccessException {
Car c = new Car();
c.setBrand("bwm");
c.setColor("白色");
c.setPrice(666.66);
System.out.println(c);
Class<Car> clazz = Car.class;
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
System.out.println(field.get(c));
System.out.println(field.getName());
System.out.println(field.getType());
System.out.println(field.getType().getSimpleName());
if (field.getType().getSimpleName().equals("double")) {
field.set(c, 999.99);
}
}
System.out.println(c);
}
}
结果截图:
Method类
通过Class对象得到关于类的方法 clazz.getDeclaredMethods() 通过getMrthod方法得到类的方法,参数为方法名和方法的参数类型 比如: clazz.getMethod("run"); clazz.getMethod("setColor", String.class);
然后通过invoke()方法调用方法 参数实例。
代码:
package com.dyit.method;
import java.lang.reflect.Method;
public class MethodDemo {
static class Car {
private String brand;
private String color;
private double price;
public void setBrand(String brand) {
this.brand = brand;
}
public void setColor(String color) {
this.color = color;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Car [brand=" + brand + ", color=" + color + ", price=" + price + "]";
}
public String getBrand() {
return brand;
}
public String getColor() {
return color;
}
public double getPrice() {
return price;
}
public void run() {
System.out.println("跑起来的");
}
}
public static void main(String[] args) throws Exception, SecurityException {
Car c = new Car();
c.setBrand("bwm");
c.setColor("red");
c.setPrice(666.666);
System.out.println(c);
Class<Car> clazz = Car.class;
Method[] Methods = clazz.getDeclaredMethods();
Method method = clazz.getMethod("run");
method.invoke(c);
Method setMethod = clazz.getMethod("setColor", String.class);
setMethod.invoke(c, "黑色");
System.out.println(c);
Method getMethod = clazz.getMethod("getColor");
Object value = getMethod.invoke(c);
System.out.println(value);
Method setMethod1 = clazz.getMethod("setPrice", double.class);
setMethod1.invoke(c, 99.6666);
System.out.println(c);
}
}
运行结果:
关于双亲委派机制
所谓的 双亲委派机制 就是:当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此。只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。 实例: 在src/main/java目录下新建java.lang包,然后在该包下新建一个String类 代码如下:
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println("helo");
}
}
程序输出结果:
错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为: public staticvoid main(String[] args) 否则 JavaFX 应用程序类必须扩展javafx.application.Application
什么意思呢?明明在类中我们有main方法为什么说找不到呢?
所以上面的例子中,AppClassLoader委派给它的父类ExtClassLoader去加载,ExtClassLoader又委托给它的父类BootstrapClassLoader去加载。BootstrapClassLoader从它的加载路径$JAVA_HOME/jre/lib/rt.jar 下找到了 java.lang.String 类,即rt.jar包下的String类,而该类里并没有main方法,所以便抛出了如上异常。
优点:
采用双亲委派的好处是:不管那个加载器加载这个类,最终都是委托给顶层的启动类(根)加载器进行加载。这样就保证了使用不同的类加载器最终得到的都是同一个string对象,所以我们自定义的java类并不会去污染jdk自带的类,这种保护机制也叫做沙箱安全机制
关于更多关于JVM的知识请看:JVM详解
|