一、类的加载
1、当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
(1)加载
就是指将class文件读入内存,并为之创建一个Class对象(class文件的对象)。
任何类被使用时系统都会建立一个Class对象
(2)连接
验证 是否有正确的内部结构,并和其他类协调一致
准备 负责为类的静态成员分配内存,并设置默认初始化值
解析 将类的二进制数据中的符号引用替换为直接引用
(3)初始化
2、类初始化时机
(1) 创建类的实例
(2)类的静态变量,或者为静态变量赋值
(3) 类的静态方法
(4)使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
(5)初始化某个类的子类(最先执行的是object类的registerNative方法,将对象注册到本地的操作系统)
(6) 直接使用java.exe命令来运行某个主类
3、类加载器
a.负责将.class文件加载到内在中,并为之生成对应的Class对象。
b.虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行
4、类加载器的组成
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包和类路径。
二、反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象。
1、Class类(lang包下的)
阅读API的Class类得知,Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的
获取Class对象的三种方式
方式一: 通过Object类中的getObject()方法
Person p = new Person();
Class c = p.getClass();
方式二: 通过 类名.class 获取到字节码文件对象(任意数据类型都具备一个class静态属性,看上去要比第一种方式简单)。
Class c2 = Person.class;
方式三: 通过Class类中的方法(将类名作为字符串传递给Class类中的静态方法forName即可)。
Class c3 = Class.forName("Person");
注意:第三种和前两种的区别
前两种你必须明确Person类型.
后面是指定这种类型的字符串就行.这种扩展更强.我不需要知道你的类.我只提供字符串,按照配置文件加载就可以了
示例:
package com.lm;
import com.bean.Person;
/*
* 获取一个类的class文件对象的三种方式
* 1、对象获取
* 2、类名获取
* 3、Class类的静态方法获取
*/
public class ReflectDemo {
public static void main(String[] args) {
//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);
System.out.println(c.equals(c1)); //true
System.out.println(c==c1); //true 只在类加载的时候产生一个class文件对象
//3、Class类的静态方法获取forName(全包名+类名)
try {
Class c2=Class.forName("com.bean.Person");
System.out.println(c2);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
附Person类
public class Person {
//成员变量
public String name;
public int age;
private String address;
//构造方法
public Person() {
System.out.println("空参数构造方法");
}
public Person(String name) {
this.name = name;
System.out.println("带有String的构造方法");
}
//私有的构造方法
private Person(String name, int age){
this.name = name;
this.age = age;
System.out.println("带有String,int的构造方法");
}
public Person(String name, int age, String address){
this.name = name;
this.age = age;
this.address = address;
System.out.println("带有String, int, String的构造方法");
}
//成员方法
//没有返回值没有参数的方法
public void method1(){
System.out.println("没有返回值没有参数的方法");
}
//没有返回值,有参数的方法
public void method2(String name){
System.out.println("没有返回值,有参数的方法 name= "+ name);
}
//有返回值,没有参数
public int method3(){
System.out.println("有返回值,没有参数的方法");
return 123;
}
//有返回值,有参数的方法
public String method4(String name){
System.out.println("有返回值,有参数的方法");
return "哈哈" + name;
}
//私有方法
private void method5(){
System.out.println("私有方法");
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", address=" + address+ "]";
}
}
2、通过反射获取构造方法并使用
在反射机制中,把类中的成员(构造方法、成员方法、成员变量)都封装成了对应的类进行表示。其中,构造方法使用类Constructor表示。可通过Class类中提供的方法获取构造方法:
返回一个构造方法
public Constructor<T> getConstructor(Class<?>... parameterTypes) 获取public修饰, 指定参数类型所对应的构造方法
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 获取指定参数类型所对应的构造方法(包含私有的)
返回多个构造方法
public Constructor<?>[] getConstructors() 获取所有的public 修饰的构造方法
public Constructor<?>[] getDeclaredConstructors() 获取所有的构造方法(包含私有的)
3、通过反射获取成员变量并使用
在反射机制中,把类中的成员变量使用类Field表示。可通过Class类中提供的方法获取成员变量:
返回一个成员变量
public Field getField(String name) 获取指定的 public修饰的变量
public Field getDeclaredField(String name) 获取指定的任意变量
返回多个成员变量
public Field[] getFields() 获取所有public 修饰的变量
public Field[] getDeclaredFields() 获取所有的 变量 (包含私有)
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() 获取本类中所有的方法(包含私有的)
三、练习
1、泛型擦除
package com.lm;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
/*
* 反射泛型擦除
* 定义集合类,泛型String
* 要求向集合中添加Integer类型
*
* 反射方式,获取出集合ArrayList类的class文件对象
* 通过class文件对象,调用add方法
*/
public class ReflectTest {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
ArrayList<String> array=new ArrayList<String>();
array.add("a");
Class c=array.getClass();
//获取ArrayList.class文件中的方法add(编译后的class文件里没有泛型)
Method method=c.getMethod("add",Object.class);//注意这里用的是Object
//使用invoke运行ArrayList方法add
method.invoke(array, 2);
method.invoke(array, 12);
method.invoke(array, 13);
method.invoke(array, 14);
System.out.println(array.size());
System.out.println(array);
//不能用增强for循环遍历,因为类型不一样,可以考虑迭代器
}
}
2、反射配置文件
(1)通过反射配置文件,运行配置文件中指定类的对应方法
读取config.properties文件中的数据,通过反射技术,来完成Person对象的创建
config.properties文件内容如下:
className=com.bean.Person
methodName=eat
(2)读取配置文件,调用指定类中的对应方法
package com.lm;
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();
pro.load(r);
r.close();
String className=pro.getProperty("className");
String methodName=pro.getProperty("methodName");
Class c=Class.forName(className);
Object obj=c.newInstance();
Method method=c.getMethod(methodName);
method.invoke(obj);
}
}
四、总结
(1)如何获取.Class文件对象
a, 通过Object类 getClass()方法获取 Class对象
b, 通过类名.class 方式 获取 Class对象
c, 通过反射的方式, Class.forName(String classname) 获取Class对象
public static Class<?> forName(String className)throws ClassNotFoundException
返回与带有给定字符串名的类或接口相关联的 Class 对象
(2)通过反射, 获取类中的构造方法,并完成对象的创建
获取指定的构造方法
public Constructor<T> getConstructor(Class<?>... parameterTypes)
获取指定的public修饰的构造方法
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
获取指定的构造方法,包含私有的
获取所有的构造方法
public Constructor<?>[] getConstructors() 获取所有的public 修饰的构造方法
public Constructor<?>[] getDeclaredConstructors() 获取所有的构造方法,包含私有的
(3)通过反射, 获取类中的构造方法,并完成对象的创建
步骤:
a,获取字节码文件对象
b,通过字节码文件对象 ,获取到指定的构造方法
getConstructor(参数);
c,通过构造方法,创建对象
public T newInstance(Object... initargs)
(4)私有构造方法,创建对象
a,获取字节码文件对象
b,通过字节码文件对象 ,获取到指定的构造方法
getDeclaredConstructor (参数);
c,暴力访问
con.setAccessible(true);
d,通过构造方法,创建对象
public T newInstance(Object... initargs)
(5)通过反射,获取Class文件中的方法
获取指定的方法
public Method getMethod(String name, Class<?>... parameterTypes)
获取指定的public方法
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
获取指定的任意方法,包含私有的
获取所有的方法
public Method[] getMethods() 获取本类与父类中所有public 修饰的方法
ublic Method[] getDeclaredMethods()获取本类中所有的方法,包含私有的
(6)通过反射,调用方法
步骤:
a,获取Class对象
b,获取构造方法,创建对象
c,获取指定的public方法
d,执行方法
public Object invoke(Object obj, Object... args)
(7)私有方法的调用:
a,获取Class对象
b,获取构造方法,创建对象
c,获取指定的private方法
d,开启暴力访问
m5.setAccessible(true);
e,执行方法
public Object invoke(Object obj, Object... args)
(8)通过反射,获取成员变量(字段)
获取指定的成员变量
public Field getField(String name) 获取public修饰的成员变量
public Field getDeclaredField(String name) 获取任意的成员变量,包含私有
获取所有的成员变量
public Field[] getFields() 获取所有public修饰的成员变量
public Field[] getDeclaredFields() 获取司所有的成员变量,包含私有
(9)通过反射,获取成员 变量,并赋值使用
步骤:
a,获取字节码文件对象
b,获取构造方法,创建对象
c,获取指定的成员变量
d,对成员变量赋值\获取值操作
public void set(Object obj, Object value) 赋值
public Object get(Object obj) 获取值
(10)私有成员变量的使用
步骤:
a,获取字节码文件对象
b,获取构造方法,创建对象
c,获取指定的成员变量
d,开启暴力访问
e,对成员变量赋值\获取值操作
public void set(Object obj, Object value) 赋值
public Object get(Object obj) 获取值