0.Java内存模型
首先我们了解一下JVM,什么是JVM,就是Java的虚拟机。这个JVM的最大特点就是可以使你的程序移植到其他平台上也可以运行使用,你可以简单的理解成一个进程,程序,只不过他的作用是用来跑你的代码的。上图是java的内存模型,我们主要关注的是方法区,Java堆和Java栈。接下来讲讲JVM的运行过程:
假如你写了一段代码:Object obj=new Object();
然后把他运行起来!(真正的代码不止这一段,这里只是举个例子)
首先JVM会启动,你的代码会编译成一个xxx.class文件,然后被类加载器加载进jvm的内存中,然后类Object加载到方法区中,创建了Object类的class对象到堆中,但是请注意这个对象不是new出来的对象,而是类的类型对象,每个类只有一个class对象,作为方法区类的数据结构的接口。jvm创建对象前,会先检查类是否加载,寻找类对应的class对象,若加载好,则为你的对象分配内存,之后就初始化,也就是执行代码:new Object()。
在jvm内存模型中有个叫类装载子系统,接下来讲讲这个系统中的类加载器吧!
1.类加载器
A.类的加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接(验证,准备,解析),初始化三步来实现对这个类进行初始化。
a 加载
就是指将编译后的class文件读入内存,并为之创建一个Class对象。
任何类被使用时系统都会建立一个Class对象
b 连接
验证:是否有正确的内部结构,并和其他类协调一致
准备:负责为类的静态成员分配内存,并设置默认初始化值
解析:将类的二进制数据中的符号引用替换为直接引用(在方法中的赋值或者四则运算直接替换成最终值,比如a=1替换为1)
c 初始化
简单而言就是new 对象,初始化成员变量等等
注:简单的说就是:把.class文件加载到内存里,并把这个.class文件封装成一个Class类型的对象。
B.类的加载时机
在以下的情况,会把这个类加载进来
a. 创建类的实例
b. 类的静态变量,或者为静态变量赋值
c. 使用到类的静态方法
d. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
e. 初始化某个类的子类
f. 直接使用java.exe命令来运行某个主类
C: 类加载器的种类(3种)
a. Bootstrap ClassLoader 根类加载器
也被称为引导类加载器,负责Java核心类的加载
比如System,String等。在JDK中JRE的lib目录下rt.jar文件中
b. Extension ClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载。
在JDK中JRE的lib目录下ext目录
c. System ClassLoader 系统类加载器
负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径。
2.反射
A. 反射定义
a. JAVA反射机制是在运行状态中,
对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
b.反射技术
条件:运行状态
已知:一个类或一个对象(根本是已知.class文件)
结果:得到这个类或对象的所有方法和属性
注: 要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象。
B. Class类
a. Class类及Class对象的了解
要想解剖一个类,必须先了解Class对象。
阅读API的Class类得知,Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。
b. 得到Class对象
1. 有三个方法(假设事先写好了一个类Person)
方式一: 通过Object类中的getClass()方法
Person person = new Person();
Class c1 = person.getClass();
方式二: 通过 类名.class 获取到字节码文件对象(任意数据类型都具备一个class静态属性,看上去要比第一种方式简单)。
Class c2 = Person.class;
方式三: 通过Class类中的方法(将类名作为字符串传递给Class类中的静态方法forName即可)。
Class c3 = Class.forName("Person");//注意引号内是类的全路径或者配置文件
注:第三种和前两种的区别是:
前两种你必须明确Person类型.
后面是指定这种类型的字符串就行.这种扩展更强.我不需要知道你的类.我只提供字符串,按照配置文件加载就可以了。另外要注意,得到class对象的操作中可能会有Exception,在main方法中throws Exception出来就可以了。
获取一个类的class文件对象的代码部分:
package cn.example.demo1;
/*
* 获取一个类的class文件对象的三种方式
* 1. 对象获取
* 2. 类名获取
* 3. Class类的静态方法获取
*/
public class ReflectDemo {
public static void main(String[] args)throws ClassNotFoundException {
//1. 对象获取
Person p = new Person();
//调用Person类的父类的方法 getClass
Class c = p.getClass();
System.out.println(c);
//2. 类名获取
//每个类型,包括基本和引用,都会赋予这个类型一个静态的属性,属性名字class
Class c1 = Person.class;
System.out.println(c1);
//3. Class类的静态方法获取 forName(字符串的类名)包名.类名
Class c2 = Class.forName("cn.example.demo1.Person");
System.out.println(c2);
}
}
下面是Person类的代码
package cn.example.demo1;
public class Person {
public String name;
private int age;
/*static{
System.out.println("静态代码块");
}*/
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
private Person(int age,String name){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("人吃饭");
}
public void sleep(String s, int a,double d){
System.out.println("人在睡觉"+s+"....."+a+"....."+d);
}
private void playGame(){
System.out.println("人在打游戏");
}
public String toString() {
return "Person [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.反射构造方法
在反射机制中,把类中的成员(构造方法、成员方法、成员变量)都封装成了对应的类进行表示。其中,构造方法使用类Constructor表示。可通过Class类中提供的方法获取构造方法:
返回一个构造方法
public Constructor<T> getConstructor(Class<?>... parameterTypes)
获取public修饰, 指定参数类型所对应的构造方法
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
获取指定参数类型所对应的构造方法(包含私有的)
返回多个构造方法
public Constructor<?>[] getConstructors()
获取所有的public 修饰的构造方法
public Constructor<?>[] getDeclaredConstructors()
获取所有的构造方法(包含私有的)
获取构造方法的代码部分:
package cn.example.demo1;
import java.lang.reflect.Constructor;
/*
* 通过反射获取class文件中的构造方法,运行构造方法
* 运行构造方法,创建对象
* 获取class文件对象
* 从class文件对象中,获取需要的成员
*
* Constructor 描述构造方法对象类
*/
public class ReflectDemo1 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("cn.example.demo1.Person");
//使用class文件对象,获取类中的构造方法
// Constructor[] getConstructors() 获取class文件对象中的所有公共的构造方法
/*Constructor[] cons = c.getConstructors();
for(Constructor con : cons){
System.out.println(con);
}*/
//获取指定的构造方法,空参数的构造方法
Constructor con = c.getConstructor();
//运行空参数构造方法,Constructor类方法 newInstance()运行获取到的构造方法
Object obj = con.newInstance();
System.out.println(obj.toString());
}
}
3.反射成员变量
在反射机制中,把类中的成员变量使用类Field表示。可通过Class类中提供的方法获取成员变量:
返回一个成员变量
public Field getField(String name)
获取指定的 public修饰的变量
public Field getDeclaredField(String name)
获取指定的任意变量
返回多个成员变量
public Field[] getFields()
获取所有public 修饰的变量
public Field[] getDeclaredFields()
获取所有的 变量 (包含私有)
获取成员变量的代码部分:
package cn.example.demo1;
import java.lang.reflect.Field;
/*
* 反射获取成员变量,并修改值
* Person类中的成员String name
*/
public class ReflectDemo5 {
public static void main(String[] args) throws Exception{
Class c = Class.forName("cn.itcast.demo1.Person");
Object obj = c.newInstance();
//获取成员变量 Class类的方法 getFields() class文件中的所有公共的成员变量
//返回值是Field[] Field类描述成员变量对象的类
/*Field[] fields = c.getFields();
for(Field f : fields){
System.out.println(f);
}*/
//获取指定的成员变量 String name
//Class类的方法 Field getField(传递字符串类型的变量名) 获取指定的成员变量
Field field = c.getField("name");
//Field类的方法 void set(Object obj, Object value) ,修改成员变量的值
//Object obj 必须有对象的支持, Object value 修改后的值
field.set(obj,"王五");
System.out.println(obj);
}
}
4.反射成员方法
在反射机制中,把类中的成员方法使用类Method表示。可通过Class类中提供的方法获取成员方法:
返回获取一个方法:
public Method getMethod(String name, Class<?>... parameterTypes)
获取public 修饰的方法
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
获取任意的方法,包含私有的
参数1: name 要查找的方法名称; 参数2: parameterTypes 该方法的参数类型
返回获取多个方法:
public Method[] getMethods()
获取本类与父类中所有public 修饰的方法
public Method[] getDeclaredMethods()
获取本类中所有的方法(包含私有的)
获取成员方法的代码演示:
package cn.example.demo1;
import java.lang.reflect.Method;
/*
* 反射获取成员方法并运行
* public void eat(){}
*/
public class ReflectDemo6 {
public static void main(String[] args) throws Exception{
Class c = Class.forName("cn.itcast.demo1.Person");
Object obj = c.newInstance();
//获取class对象中的成员方法
// Method[] getMethods()获取的是class文件中的所有公共成员方法,包括继承的
// Method类是描述成员方法的对象
/*Method[] methods = c.getMethods();
for(Method m : methods){
System.out.println(m);
}*/
//获取指定的方法eat运行
// Method getMethod(String methodName,Class...c)
// methodName获取的方法名 c 方法的参数列表
Method method = c.getMethod("eat");
//使用Method类中的方法,运行获取到的方法eat
//Object invoke(Object obj, Object...o)
method.invoke(obj);
}
}
5.反射配置文件运行类中的方法
通过反射配置文件,运行配置文件中指定类的对应方法
读取Peoperties.txt文件中的数据,通过反射技术,来完成Person对象的创建
Peoperties.txt文件内容:
#className=cn.example.demo3.Student
#methodName=study
className=cn.example.demo3.Person
methodName=eat
#className=cn.example.demo3.Worker
#methodName=job
反射配置文件运行类中的方法代码部分:
package cn.example.demo3;
import java.io.FileReader;
import java.lang.reflect.Method;
import java.util.Properties;
/*
* 调用Person方法,调用Student方法,调用Worker方法
* 类不清楚,方法也不清楚
* 通过配置文件实现此功能
* 运行的类名和方法名字,以键值对的形式,写在文本中
* 运行哪个类,读取配置文件即可
* 实现步骤:
* 1. 准备配置文件,键值对
* 2. IO流读取配置文件 Reader
* 3. 文件中的键值对存储到集合中 Properties
* 集合保存的键值对,就是类名和方法名
* 4. 反射获取指定类的class文件对象
* 5. class文件对象,获取指定的方法
* 6. 运行方法
*/
public class Test {
public static void main(String[] args) throws Exception{
//IO流读取配置文件
FileReader r = new FileReader("config.properties");
//创建集合对象
Properties pro = new Properties();
//调用集合方法load,传递流对象
pro.load(r);
r.close();
//通过键获取值
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
//反射获取指定类的class文件对象
Class c = Class.forName(className);
Object obj = c.newInstance();
//获取指定的方法名
Method method = c.getMethod(methodName);
method.invoke(obj);
}
}