一、类加载
类加载分为三个步骤:加载、连接、初始化。
1、加载
类加载指的是将class文件读入内存,并为之创建一个java.lang.Class对象,即程序中使用任何类时,系统都会为之建立一个java.lang.Class对象,系统中所有的类都是java.lang.Class的实例。
类的加载由类加载器完成,JVM提供的类加载器叫做系统类加载器,此外还可以通过继承ClassLoader基类来自定义类加载器。
通常可以用如下几种方式加载类的二进制数据:
- 从本地文件系统加载class文件。
- 从JAR包中加载class文件,如JAR包的数据库启驱动类。
- 通过网络加载class文件。
- 把一个Java源文件动态编译并执行加载。
2、连接
连接阶段负责把类的二进制数据合并到JRE中,其又可分为如下三个阶段:
- 验证:确保加载的类信息符合JVM规范,无安全方面的问题。
- 准备:为类的静态Field(局部变量)分配内存,并设置初始值。
- 解析:将类的二进制数据中的符号引用替换成直接引用。
3、初始化
- 该阶段主要是对静态Field进行初始化,在Java类中对静态Field指定初始值有两种方式:
- 声明时即指定初始值,如static int a = 5;
- 使用静态代码块为静态Field指定初始值,如:static{ b = 5; }
- JVM初始化一个类包含如下几个步骤:
- 假如这个类还没有被加载和连接,则程序先加载并连接该类。
- 假如该类的直接父类还没有被初始化,则先初始化其直接父类。
- 假如类中有初始化语句,则系统依次执行这些初始化语句。
所以JVM总是最先初始化java.lang.Object类。
- 类初始化的时机(对类进行主动引用时):
- 创建类的实例时(new、反射、反序列化)。
- 调用某个类的静态方法时。
- 使用某个类或接口的静态Field或对该Field赋值时。
- 使用反射来强制创建某个类或接口对应的java.lang.Class对象,如Class.forName(“Person”)
- 初始化某个类的子类时,此时该子类的所有父类都会被初始化。
- 直接使用java.exe运行某个主类时。
二、类加载器
类加载器负责将class文件读入内存,并为之创建一个java.lang.Class对象。
JVM的类加载机制主要有以下3种:
- 全盘负责:当一个类加载器加载某个Class时,该Class所依赖和引用的其它Class也将由该类加载器负责载入,除非显式的使用另外一个类加载器来载入。
- 双亲委派:当一个类加载器收到了类加载请求,它会把这个请求委派给父(parent)类加载器去完成,依次递归,因此所有的加载请求最终都被传送到顶层的启动类加载器中。只有在父类加载器无法加载该类时子类才尝试从自己类的路径中加载该类。(注意:类加载器中的父子关系并不是类继承上的父子关系,而是类加载器实例之间的关系。)
- 缓存机制:缓存机制会保证所有加载过的Class都会被缓存,当程序中需要使用某个类时,类加载器先从缓冲区中搜寻该类,若搜寻不到将读取该类的二进制数据,并转换成Class对象存入缓冲区中。这就是为什么修改了Class后需重启JVM才能生效的原因。
类的标识:
Java中:全限定类名(包括包名和类名)
JVM中:全限定类名和类加载器
三、反射
反射就是把java类中的各种成分映射成一个个的Java对象,而动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
class类的实例表示正在运行的 Java 应用程序中的类和接口。Class
没有公共构造方法。Class
对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass
方法自动构造的。
1、获得class对象
1.1 Object ——> getClass();
1.2 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
1.3 通过Class类的静态方法:forName(String className)(常用)
package fanshe;
/**
* 获取Class对象的三种方式
* 1 Object ——> getClass();
* 2 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
* 3 通过Class类的静态方法:forName(String className)(常用)
*
*/
public class Fanshe {
public static void main(String[] args) {
Student stu1 = new Student();
//第一种方式获取Class对象
Class stuClass = stu1.getClass();
System.out.println(stuClass.getName());
//第二种方式获取Class对象
Class stuClass2 = Student.class;
//第三种方式获取Class对象
try {
//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
Class stuClass3 = Class.forName("fanshe.Student");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
2、获得构造方法
(1)批量的方法:
- public Constructor[] getConstructors():所有"公有的"构造方法
- public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
(2)获取单个的方法:
- public Constructor getConstructor(Class… parameterTypes):获取单个的"公有的"构造方法:
- public Constructor getDeclaredConstructor(Class… parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;
(3)调用构造方法:
Constructor–>newInstance(Object… initargs)
package fanshe;
public class Student {
//---------------构造方法-------------------
//(默认的构造方法)
Student(String str){
System.out.println("(默认)的构造方法 s = " + str);
}
//无参构造方法
public Student(){
System.out.println("调用了公有、无参构造方法执行了。。。");
}
//有一个参数的构造方法
public Student(char name){
System.out.println("姓名:" + name);
}
//有多个参数的构造方法
public Student(String name ,int age){
System.out.println("姓名:"+name+"年龄:"+ age);//这的执行效率有问题,以后解决。
}
//受保护的构造方法
protected Student(boolean n){
System.out.println("受保护的构造方法 n = " + n);
}
//私有构造方法
private Student(int age){
System.out.println("私有的构造方法 年龄:"+ age);
}
}
package fanshe;
import java.lang.reflect.Constructor;
public class Constructors {
public static void main(String[] args) throws Exception {
//加载Class对象
Class clazz = Class.forName("fanshe.Student");
//获取所有公有构造方法
Constructor[] conArray = clazz.getConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
//获取所有的构造方法(包括:私有、受保护、默认、公有)
conArray = clazz.getDeclaredConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
//获取公有、无参的构造方法
//1>、因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型
//2>、返回的是描述这个无参构造函数的类对象。
Constructor con = clazz.getConstructor(null);
//调用构造方法
Object obj = con.newInstance();
//获取获取私有构造方法
con = clazz.getDeclaredConstructor(char.class);
//调用私有构造方法
//暴力访问(忽略掉访问修饰符)
con.setAccessible(true);
obj = con.newInstance('男');
}
}
3、获得成员变量
(1)批量的
- Field[] getFields():获取所有的"公有字段"
- Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有;
(2)获取单个的:
- public Field getField(String fieldName):获取某个"公有的"字段;
- public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)
(3)设置字段的值:
Field --> public void set(Object obj,Object value):
obj:要设置的字段所在的对象;value:要为字段设置的值;
package fanshe.field;
public class Student {
public Student(){
}
//**********字段*************//
public String name;
protected int age;
char sex;
private String phoneNum;
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", sex=" + sex
+ ", phoneNum=" + phoneNum + "]";
}
}
package fanshe.field;
import java.lang.reflect.Field;
public class Fields {
public static void main(String[] args) throws Exception {
//获取Class对象
Class stuClass = Class.forName("fanshe.field.Student");
//获取所有公有的字段
Field[] fieldArray = stuClass.getFields();
for(Field f : fieldArray){
System.out.println(f);
}
//获取所有的字段(包括私有、受保护、默认的)
fieldArray = stuClass.getDeclaredFields();
for(Field f : fieldArray){
System.out.println(f);
}
//获取公有字段
Field f = stuClass.getField("name");
//获取一个对象
Object obj = stuClass.getConstructor().newInstance();/
//为字段设置值
f.set(obj, "刘德华");
//获取私有字段
f = stuClass.getDeclaredField("phoneNum");
//暴力反射,解除私有限定
f.setAccessible(true);
f.set(obj, "18888889999");
System.out.println("验证电话:" + stu);
}
}
4、获得成员方法
(1)批量的:
- public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类)
- public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
(2)获取单个的:
- public Method getDeclaredMethod(String name,Class<?>… parameterTypes)
- public Method getMethod(String name,Class<?>… parameterTypes):
name : 方法名;
Class … : 形参的Class类型对象
(3)调用方法:
Method --> public Object invoke(Object obj,Object… args):
参数说明:
obj : 要调用方法的对象;
args:调用方式时所传递的实参;
package fanshe.method;
public class Student {
//**************成员方法***************//
public void show1(String s){
System.out.println("调用了:公有的,String参数的show1(): s = " + s);
}
protected void show2(){
System.out.println("调用了:受保护的,无参的show2()");
}
void show3(){
System.out.println("调用了:默认的,无参的show3()");
}
private String show4(int age){
System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age);
return "abcd";
}
}
package fanshe.method;
import java.lang.reflect.Method;
public class MethodClass {
public static void main(String[] args) throws Exception {
//获取Class对象
Class stuClass = Class.forName("fanshe.method.Student");
//获取所有公有方法
stuClass.getMethods();
Method[] methodArray = stuClass.getMethods();
for(Method m : methodArray){
System.out.println(m);
}
//获取所有的方法,包括私有的
methodArray = stuClass.getDeclaredMethods();
for(Method m : methodArray){
System.out.println(m);
}
//获取公有的show1()方法
Method m = stuClass.getMethod("show1", String.class);
System.out.println(m);
//实例化一个Student对象
Object obj = stuClass.getConstructor().newInstance();
m.invoke(obj, "刘德华");
//获取私有的show4()方法
m = stuClass.getDeclaredMethod("show4", int.class);
System.out.println(m);
//解除私有限定
m.setAccessible(true);
//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
Object result = m.invoke(obj, 20);
System.out.println("返回值:" + result);
}
}
5、越过泛型检查
import java.lang.reflect.Method;
import java.util.ArrayList;
/*
* 通过反射越过泛型检查
*
* 例如:有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值?
*/
public class Demo {
public static void main(String[] args) throws Exception{
ArrayList<String> strList = new ArrayList<>();
strList.add("aaa");
strList.add("bbb");
//获取ArrayList的Class对象
Class listClass = strList.getClass();
//获取add()方法
Method m = listClass.getMethod("add", Object.class);
//调用add()方法
m.invoke(strList, 100);
//遍历集合s
for(Object obj : strList){
System.out.println(obj);
}
}
}
四、注解
注解是一种特殊的标记,提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。
1、元注解
注解了注解的注解。
修饰符 @interface 注解名 {
注解元素的声明1
注解元素的声明2
}
@Target
用于描述注解的使用范围
@Retention
定义了该Annotation被保留的时间长短 (生命周期)
@Documented
描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。
@Inherited
标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
2、Java内置的注解
@Override
注解
告诉编译器这个是个覆盖父类的方法。如果父类删除了该方法,则子类会报错。
@Deprecated
注解
表示被注解的元素已被弃用。
@SuppressWarnings
注解
告诉编译器忽略警告。
@FunctionalInterface
注解
Java 1.8 引入的注解。该注释会强制编译器javac
检查一个接口是否符合函数接口的标准。
@Repeatable
注解
Java 1.8 引入的注解,标识注解是可重复使用的。
3、利用反射获取注解
注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
public @interface MyAnno {
String value();
}
被注解的元素
@MyAnno("class")
public class MyClass {
@MyAnno("feild")
private String str;
@MyAnno("method")
public void method() { }
}
获取注解信息
public class Test {
public static void main(String[] args) throws Exception {
MyClass obj = new MyClass();
Class<?> clazz = obj.getClass();
// 获取对象上的注解
MyAnno anno = clazz.getAnnotation(MyAnno.class);
System.out.println(anno.value());
// 获取属性
Field field = clazz.getDeclaredField("str");、
// 获取属性上的注解
anno = field.getAnnotation(MyAnno.class);
System.out.println(anno.value());
// 获取方法上
Method method = clazz.getMethod("method");
// 获取方法上的注解
anno = method.getAn notation(MyAnno.class);
System.out.println(anno.value());
}
}
五、动态代理
java中动态代理主要有JDK和CGLIB两种方式。区别主要是jdk是代理接口,而cglib是代理类。
JDK动态代理
关键在于创建动态代理对象及其方法。
//创建动态代理对象
//loader为类加载器,interfaces作为代理类实现的接口,h为代理对象实际调用的方法(即invoke方法)。
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
//执行动态代理对象的所有方法时,实际执行的是这个方法
//proxy: 代理对象本身 method:userServiceRef中的方法 args: method方法的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
}
例子见书860页后或https://blog.csdn.net/u014338530/article/details/88779017
- 代理对象的父类java.lang.reflect.Proxy类是实现了java.io.Serializable接口的,所以代理对象都是可序列化的;
- 对于有参和无参方法,都是通过invoke方法调用,无参方法会直接传入null,所以在invoke方法中使用args参数时一定要先进行null的判断;、
- 对于原始数据类型(int,boolean等8种),代理对象的方法中参数和返回值都进行了包装和解包装。
- 代理对象生成过程中用到了反射,生成字节码时,反射Object对象方法和反射接口方法;生成实例时,反射获取代理对象的构造器;代理对象方法调用过程中是没有使用反射的。