Java内存到反射入门(一)
初识Java内存
平常我们最常提到的的JAVA分区是这三个分区
其中方法区是一个特殊的堆,功能如图上所示。
初识反射:框架设计的灵魂
反射的功能:将类的各个组成部分封装为对象,并在程序运行的过程中可以调用它们。
Java代码的三个阶段
我们平时书写的java代码,从书写完成到内存中执行主要经历了三个阶段:Source 源代码阶段,class类对象阶段,Runtime运行时阶段。下图以一个简单的Person类来举例说明。
第一阶段:Source源代码阶段
首先,我们编写好了一个Person类的代码,它由成员变量,构造方法,成员方法三部分组成。
public class Person{
private String name;
private int age;
public Person(){}
public void eat(){}
}
此时它是一个Person.java文件。还不能运行,接着我们要用javac命令编译它,使生成一个Person.Class字节码文件。
这个文件我们并不能看懂,其实里面主要包含三部分内容:成员变量,成员方法和构造方法,还有一些诸如包名的信息。
至此,源代码和字节码都是以文件的形式储存在硬盘上的,还未进入内存,此阶段我们称之为Source源代码阶段。
第二阶段:Class类对象阶段
要想由第一个阶段进入第二个阶段,要经过一个加载的过程。由类加载器ClassLoder将class文件加载到内存中,并生成该类的class类对象。class类对象是来描述字节码文件的,字节码文件被封装为三大部分:
1.成员变量被封装为Field对象,并用Filed[]存储。
2.构造方法被封装为Constructor对象,并用Constructor[]存储。
3.成员方法被封装为Method对象,并用Method[]存储。
这样的封装就是反射机制,经过了这样的封装后,我们就可以在程序运行的过程中来操作这些对象了。即获取,修改变量和执行方法等。
举个栗子:在IDEA等IDE中,写如下一段代码:
//定义了一个字符串变量,并尝试调用它自带的方法
String str = "abc";
str.
在按下str后面这个.后,IDEA会提示许多的方法。
那么为什么IDEA会提示这些方法呢?其实内部就是反射机制。 定义了一个String变量后,则字符串的字节码文件就会被加载进内存。在内存中就有了String的class类对象,里面封装了Method[]存储了所有String的方法。所以只要挨个显示一下就可以了。
第三阶段:Runtime运行时阶段
第二阶段通过创建对象等操作就会进入第三阶段,创建对象时就会根据这个class类对象创建一个真正的Person对象。
获得class类的三种方法
Class.forName("全类名"):将字节码文件加载进内存,返回Class对象 * 多用于配置文件,将类名定义在配置文件中。读取文件,加载类
//如获取Person类的.Class文件
Class a = Class.forName("Domain.Person");//Domian是Person类所在包
类名.class:通过类名的属性class获取,该方法多用于参数的传递。
Class a = Person.calss;
对象.getClass():getClass()方法在Object类中定义着,该方法多用于对象的获取字节码的方式
Person person = new Person();
Class a = person.getClass();
Tip:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
//采用多种方式得到.Class文件
Class a = Class.forName("Domain.Person");
Person person1 = new Person();
Person person2 = new Person();
Class b = person1.getClass();
Class c = Person.class;
Class d = person2.getClass();
//不管以哪种方式得到的.Class类,只要是Person类的,那就是一样的。
System.out.println(a==b); //true
System.out.println(c==b); //true
System.out.println(a==c); //true
System.out.println(a==d); //true
Class类的用法
在熟悉了Class类的获取方法后,自然要熟悉其用法。
//以下是测试用的Person类
public class Person {
private String name;
private int age;
private int id;
public Person() {
}
public Person(String name, int age, int id) {
this.name = name;
this.age = age;
this.id = id;
}
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 int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Person{" +
"name='" \+ name \+ '\\'' +
", age=" \+ age +
", id=" \+ id +
'}';
}
}
用法一:获取类的名字
方法有两个:
String getName(); //返回类的包名+类名
String getSimpleName();//获取简单类名
Class c1 = Person.class;
System.out.println(c1.getName()); //该方法获取类的全名,输出Domain.Person
System.out.println(c1.getSimpleName());//该方法获取类的简单名,即Person
用法二:获得类的属性
方法有四个:
Field[] getFields() :获取所有public修饰的成员变量
Field getField(String name) 获取指定名称的 public修饰的成员变量
Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
Field getDeclaredField(String name)
用法三:获得类的方法
方法有四个:
Method[] getMethods() ;
Method getMethod(String name, 类>... parameterTypes) ;
Method[] getDeclaredMethods() ;
Method getDeclaredMethod(String name, 类>... parameterTypes) ;
注意第一个方法会获取包括本类和父类的所有public方法。 第二个方法只会获取本类的所有方法,包括private的,不包括父类的方法。
获取指定方法的第一个参数是方法的名称,第二个参数是要获取的方法的参数类型,如setName(String name);函数,参数是String,则此处写String.Class;
用法四:获得构造器(构造方法)
有四种方法:
获取构造方法们
Constructor>[] getConstructors()
Constructor getConstructor(类>... parameterTypes)
Constructor getDeclaredConstructor(类>... parameterTypes)
Constructor>[] getDeclaredConstructors() ;