JavaNote13
反射
1. 类加载机制
当程序主动使用到某个类时,如果该类还未被加载进内存中,则系统会通过加载,连接,初始化三个步骤来对该类进行初始化操作.
类的加载:
- 类加载时指将类的class文件(字节码文件)载入内存中,并为之创建一个java.lang.Class对象,我们称之为字节码对象.
- 类的加载过程由类加载器(ClassLoader)完成,类加载器通常有JVM提供,我们称之为系统类加载器,我们也可以继承ClassLoader类来提供自定义类加载器.
- 不同的类加载器可以实现加载本地字节码文件,jar包中的字节码,通过网络加载字节码等.
类的连接:
当类被加载进内存之后,系统为之生产一个对应的Class对象,接着把类的二进制数据合并到JRE中.- 验证:检测被加载的类是否有正确的内部结构.
- 准备:负责为类的static变量分配内存,并设置默认值.
- 解析:把类的二进制数据中的符号引用替换为直接引用.
类的初始化:
在此阶段,JVM负责对类进行初始化,主要就是对static变量进行初始化.
类的初始化一个类包含以下几个步骤:- 如果该类还未被加载和连接,则程序先加载并连接该类.
- 如果该类的直接父类还未被初始化,则先初始化其父类.
- 如果类中有初始化语句(静态代码块),则系统依次执行这些初始化语句.
2. Class类和Class实例
- Class类:用来描述类或者接口的类型,描述类的类.
- Class类的实例:在JVM中的一份份字节码,Class实例表示在JVM中的类或者接口,枚举是一种特殊的类,注解是一种特殊的接口.
获取字节码对象(Class对象):
public static void main(String[] args) throws Exception {
//需求:获取java.util.Date类的字节码对象
//方式1:使用class属性
Class<Date> clz1 = Date.class;
//方式2:通过对象的getClass()来获取,getClass()是Object中的方法
Date date = new Date();
Class<Date> clz2 = date.getClass();
//方式3:通过Class类中的静态方法forName(String className);
Class<Date> clz3 = Class.forName("java.util.Date");
}
九大内置Class实例:JVM中预先提供好的Class实例,分别:
byte,short,int,long,float,double,boolean,char,void.
在8大基本数据类型的包装类中,都有一个常量:TYPE,用于返回该包装类对应基本类的字节码对象.
System.out.println(Integer.TYPE == int.class);//true
注意:Integer和int是不同的数据类型
System.out.println(Integer.class == int.class);//false
数组的Class实例:数组是引用数据类型,数组其实是对象.
表示数组的Class实例.
方式1: 数组类型.class;
方式2: 数组对象.getClass();
注意:所有的具有相同的维数和相同元素类型的数组共享同一份字节码对象,和元素没有关系.
3. 获取类中的构造器
- 1:获取该类的字节码对象.
- 2:从该字节码对象中去找需要获取的构造器.
Constructor类:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器
Class类获取构造器方法:
该方法只能获取当前Class所表示类的public修饰的构造器
public Constructor<?>[] getConstructors()
获取当前Class所表示类的所有的构造器,和访问权限无关
public Constructor<?>[] getDeclaredConstructors()
获取当前Class所表示类中指定的一个public的构造器
public Constructor<T> getConstructor(Class<?>... parameterTypes)
参数:parameterTypes表示:构造器参数的Class类型
4. 使用反射创建对象
- 1:找到构造器所在类的字节码对象.
- 2:获取构造器对象.
- 3:使用反射,创建对象
常用方法:
public T newInstance(Object… initargs):如调用带参数的构造器,只能使用该方式.
参数:initargs:表示调用构造器的实际参数
返回:返回创建的实例,T表示Class所表示类的类型
如果一个类中的构造器是public修饰,同时没有参数.,那么可以直接使用Class类中的newInstance方法创建对象.
注意:调用私有的构造器,访问私有的成员:必须先设置可访问的对象setAccessible(true);
5. 获取类中的方法(并调用)
- 1:获取方法所在类的字节码对象.
- 2:获取方法.
Class类中常用方法:
获取包括自身和继承过来的所有的public方法
public Method[] getMethods()
获取自身类中所有的方法(不包括继承的,和访问权限无关)
public Method[] getDeclaredMethods():
表示调用指定的一个公共的方法(包括继承的)
public Method getMethod(String methodName,Class<?>... parameterTypes)
参数:
methodName: 表示被调用方法的名字
parameterTypes:表示被调用方法的参数的Class类型如String.class
表示调用指定的一个本类中的方法(不包括继承的)
public Method getDeclaredMethod(String name,Class<?>... parameterTypes):
参数:
methodName: 表示被调用方法的名字
parameterTypes:表示被调用方法的参数的Class类型如String.class
在Method类中有方法:
表示调用当前Method所表示的方法
public Object invoke(Object obj,Object... args):
参数:
obj: 表示被调用方法底层所属对象
Method m = clz.getMethod("sayHi",String.class);
args:表示调用方法是传递的实际参数
返回:底层方法的返回结果
调用私有方法:
在调用私有方法之前:应该设置该方法为可访问的
方法对象.setAccessible(true);
7. 加载资源文件路径
加载properties文件,只能使用Properties类的load方法.
public static void main(String[] args) throws Exception {
//使用相对路径-相对于classpath的根路径(字节码输出目录).
private static void test2() throws Exception {
Properties p = new Properties();
//ClassLoader loader = LoadResourceDemo.class.getClassLoader();
ClassLoader loader = Thread.currentThread().getContextClassLoader();
InputStream inStream = loader.getResourceAsStream("db.properties");
p.load(inStream);//加载
System.out.println(p);
}
}
最后附上Java学习网址