在使用框架进行开发时,我们的开发速度大大提升。我们感叹于它的神奇之处,我们使用它的时候,也要知道其“灵魂”。正所谓,无反射,不框架,框架的灵魂就是反射。 另外,我们在eclipse或者IDEA中编辑Java代码时,它们是怎么知道我们的对象有哪些方法,输入一个点就能给提示呢? 带着问题我们来谈谈反射。
反射:是将一个类的各个部分封装为其他对象,这就是反射机制。 看着上面文邹邹的话语,想必大家并没有理解啥是反射。下面我们来通过一个例子来进行讲解。
我们来看一下我们的Java代码在计算机中经历的几个阶段: 「第一个阶段:源代码阶段」 首先,我们定义一个猫的类,包含名字,年龄,无参和全参构造方法,和一个猫叫的方法。 Cat.java如下:
public class Cat {
private String name;//猫的名字
private int age;//猫的年龄
public Cat() {//无参构造方法
}
public Cat(String name, int age) {//全参构造方法
this.name = name;
this.age = age;
}
public void meow(){//猫叫的方法
System.out.println("喵喵喵~~~");
}
}
写完这个代码之后,我们并不能运行它,我们需要执行一个操作:编译。 通过Java自带的编译器,使用 「javac」 这个命令,编译 「Cat.java」 文件,如果编写的代码没有问题,会在磁盘上生成一个字节码文件:「Cat.class」 文件。 这个字节码文件放的是什么呢? 它主要包含三个主要的内容:
字节码文件包含的主要内容
当然不止这三个内容,还有类的名称等等等等。
这就是java代码在计算机中的第一个阶段:
第一个阶段
这时我们的代码还在硬盘,并没有进入内存。
我们先不谈第二个阶段,我们先来谈谈第三个阶段,也就是我们通常new对象的阶段。 「第三个阶段:运行时阶段」
这就是我们的运行时阶段。 从字节码文件到new出类的对象这又是一个怎样的过程呢?我们需要把字节码文件加载到内存中才能使用,这就要介绍我们的第二个阶段了。 「第二个阶段:Class类对象阶段」 在Java中万物皆对象,有一个对象来描述字节码文件;这个对象是Class类对象; 需要把字节码中的成员变量,构造方法,成员方法都表示出来,又这些可能不止一个,所以这三种需要由一个数组来存储,所以主要由三个主要的部分组成:
Field[] fields;//成员变量数组
Constuctor[] constuctors;//构造方法数组
Method[] method;//成员方法数组
可以看到,我们把它们存储到对象中了,然后就能够知道有哪些成员变量,哪些方法了,这就解答了我们前面的如何提示问题。 然后通过类对象创建对应的Cat类等对象。 「三个阶段」
三个阶段关系
第一种方式 如果我们的java代码在第一个阶段时,它还没有进入内存,我们需要将它加载到内存,需要使用 Class.forName("全类名");方式将其加载到内存,获取Class对象; 第二种方式 如果我们的java代码在第二个阶段时,它还没有创建对象,但是我们已经把它它加载到了内存,获取到了它的类名,我们可以使用 「类名.class」的方式获取Class对象; 第三种方式 如果我们的java代码在第三个阶段时,已经有了该类的对象,我们只需要用 「对象.getClass()」 的方式获取Class对象; 下面我们就来演示一下这三种方法的使用; 首先我们创建一个包com.demo.domain(命名随自己来定),存放我们的实体类对象,本文使用Cat.java进行演示;
package com.demo.domain;
public class Cat {
private String name;//猫的名字
private int age;//猫的年龄
public Cat() {//无参构造方法
}
public Cat(String name, int age) {//全参构造方法
this.name = name;
this.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;
}
public void meow(){//猫叫的方法
System.out.println("喵喵喵~~~");
}
}
再创建一个包com.demo.reflect用来演示这三种方式;Demo1.java如下:
package com.demo.reflect;
import com.demo.domain.Cat;
public class Demo1 {
public static void main(String[] args) throws ClassNotFoundException {
//第一种方式,Class.forName("全类名")
Class cls1 = Class.forName("com.demo.domain.Cat");
System.out.println(cls1);
//输出class com.demo.domain.Cat
//第一种方式,类名.class
Class cls2 = Cat.class;//在使用Cat类之前需要进行导入
System.out.println(cls2);
//输出class com.demo.domain.Cat
//第三种方式,已经具有对象,使用对象.getClass
Cat cat = new Cat();
Class cls3 = cat.getClass();
System.out.println(cls3);
//输出class com.demo.domain.Cat
}
}
注意:本文没写Class的泛型。 那获取到的三个对象是什么关系呢?我们用一段代码,验证它们的内存地址是否相等。
//比较三个对象
System.out.println(cls1 == cls2);//true
System.out.println(cls2 == cls3);//true
输出均为true,可见它们的内存地址是相同的。 所以,同一个字节码文件,在同一个程序运行的过程中,只会被加载一次,三种方式获取的Class对象都是同一个。
反射有很多的优势:
相信大家对反射有了一定的了解,感谢大家的阅读。