一、类的加载概述和加载时机
1、概述
当程序要使用某个类的时,如果该类还没有被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
2、加载
就是将class文件读入内存中,并为之创建一个class对象,任何类被使用时系统都会建立一个class对象。
3、连接
- 验证:是否有正确结构,并和其他类协调一致。
- 准备:负责为类的静态成员分配内存,并设置默认值。
- 解析:将类的二进制数据中的符号引用替换为直接引用。
4、加载时机
- 创建类类的实例
- 调用类的静态方法
- 使用反射方式来强制创建某个类或接口对应的java.lang.class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
二、类加载器的概述和分类
1、概述
负责将.class文件加载到内存中,并为之生成对应的class对象
2、分类
- BootStrap ClassLoader根类加载器: 也称引导类加载器,负责Java核心类的加载。
- Extension ClassLoader扩展类加载器: 负责JRE的扩展目录中jar包的加载
- System ClassLoader系统类加载器: 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径
三、反射
1、概述
- Java反射机制是在 运行状态 中,对于任意一个实体类,都能知道这个类的所有属性和方法。
- 对于任意一个对象,都能调用它的任意一个方法和属性。
- 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
- 要想解剖一个类,必须要获取到该类的字节码文件对象。而解剖使用的就是class类中的方法,所以要先获取一个字节码文件对应的class对象。
2、框架
半成品项目。我们可以再框架的基础上进行软件开发,简化编码。
3、反射
框架的基础,也是框架的灵魂。将类的各个组成部分封装成其他的对象。
4、反射的好处
可以在程序运行过程中,操作这些对象。可以提高程序扩展性和复用性,可以解耦。
5、获取一个Class的三种方式
方式一:对象.Class;当做静态方法的锁对象
1)、新建一个User类
public class User {
private String name;
protected Integer age;
public String idNumber;
private User(String name) {
this.name = name;
}
public User(String name, Integer age, String idNumber) {
this.name = name;
this.age = age;
this.idNumber = idNumber;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
protected Integer getAge() {
return age;
}
protected void setAge(Integer age) {
this.age = age;
}
private String getIdNumber() {
return idNumber;
}
private void setIdNumber(String idNumber) {
this.idNumber = idNumber;
}
}
2)、测试
@Test
public void tsetClass1(){
//获取一个Class
Class<User> userClass = User.class;
//getSimpleName:获取不带包名的类名
System.out.println(userClass.getSimpleName());
}
方式二:Class.forName(“类名”);读取配置文件,动态加载类
@Test
public void tsetClass1 () throws ClassNotFoundException{
//获取一个Class
Class<?> user = Class.forName("com.itan.pojo.User");
System.out.println(user.getSimpleName());
}
方式三:创建对象.getClass();
@Test
public void tsetClass1 () throws ClassNotFoundException{
//获取一个Class
User user1=new User();
Class<? extends User> userClass1 = user1.getClass();
System.out.println(userClass1.getSimpleName());
}
6、Class
Class代表类的实体,在运行的Java应用程序中表示类和接口。在这个类中提供了很多有用的方法,如下
1.获得类相关的方法
方法 | 用途 |
---|---|
asSubclass(Class<?> clazz) | 把传递的类的对象转换成代表其子类的对象 |
getClassLoader() | 获得类的加载器 |
getClasses() | 返回一个数组,数组中包含该类中所有公共类和接口类的对象 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象 |
forName(String className) | 根据类名返回类的对象 |
getName() | 获得类的完整路径名字 |
newInstance() | 通过调用空参构造创建类的实例 |
getPackage() | 获得类的包 |
getSimpleName() | 获得类的名字 |
getSuperclass() | 获得当前类继承的父类的名字 |
getInterfaces() | 获得当前类实现的类或是接口类 |
.class | 获取当前对象的类 |
示例:
1)、创建实体类和接口
public class Animal {
private String species;
public String getSpecies() {
return species;
}
public void setSpecies(String species) {
this.species = species;
}
}
//接口类
public interface MyInterface {
void method();
}
//子类和实现类
public class User extends Animal implements MyInterface{
private String name;
protected Integer age;
public String idNumber;
public User() {
}
private User(String name) {
this.name = name;
}
public User(String name, Integer age, String idNumber) {
this.name = name;
this.age = age;
this.idNumber = idNumber;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
protected Integer getAge() {
return age;
}
protected void setAge(Integer age) {
this.age = age;
}
private String getIdNumber() {
return idNumber;
}
private void setIdNumber(String idNumber) {
this.idNumber = idNumber;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", idNumber='" + idNumber + '\'' +
'}';
}
@Override
public void method() {
}
public static class Bike{
private String name;
}
private static class Clothes{
private String name;
}
}
2)、测试
@Test
public void tsetClass1 () throws Exception{
//获取一个Class
Class<User> userClass = User.class;
//getName:获取类、字段、方法、构造的名称(完整路径名字)
System.out.println(userClass.getName());
//getSimpleName:获取不带包名的类名
System.out.println(userClass.getSimpleName());
//getPackage:获取包名。注意:这里包名有一个package 前缀
System.out.println(userClass.getPackage());
//newInstance:创建一个User实例
User user = userClass.newInstance();
System.out.println(user);
//getClasses:获取该类中的公共类
Class<?>[] classes = userClass.getClasses();
for (Class<?> aClass:classes){
System.out.println(aClass.getSimpleName());
}
//getDeclaredClasses:获取该类中的所有类(包括非公共的类)
Class<?>[] classes1 = userClass.getDeclaredClasses();
for (Class<?> aClass:classes1){
System.out.println(aClass.getSimpleName());
}
//asSubclass:把传递的类的对象转换成代表其子类的对象
Class<? extends Animal> aClass = userClass.asSubclass(Animal.class);
System.out.println(aClass.getName());
//getSuperclass:获取继承的父类
Class<? super User> superclass = userClass.getSuperclass();
System.out.println(superclass.getSimpleName());
//getInterfaces:获取当前类实现的类或是接口类
Class<?>[] interfaces = userClass.getInterfaces();
for (Class<?> anInterface:interfaces){
System.out.println(anInterface.getSimpleName());
}
}
2.获得类中字段相关的方法
方法 | 用途 |
---|---|
getField(String name) | 获得某个公有的字段对象 |
getFields() | 获得所有公有的字段对象 |
getDeclaredField(String name) | 获得某个字段对象 |
getDeclaredFields() | 获得所有字段对象 |
示例
@Test
public void test02() throws Exception{
Class<User> userClass = User.class;
//getFields:获取类中的所有公有字段对象
Field[] fields = userClass.getFields();
for (Field field:fields){
System.out.println(field.getName());
}
//getField:获取类中某个公有字段对象
Field name = userClass.getField("idNumber");
System.out.println(name.getName());
//getDeclaredField:获取类中的某个字段对象(包括私有)
Field name1 = userClass.getDeclaredField("name");
System.out.println(name1.getName());
//getDeclaredFields:获取类中所有字段(包括私有)
Field[] declaredFields = userClass.getDeclaredFields();
for (Field field:declaredFields){
System.out.println(field.getName());
}
}
3.获得类中注解相关的方法
方法 | 用途 |
---|---|
getAnnotation(Class<?> annotationClass) | 返回该类中与参数类型匹配的公有注解对象 |
4.获得类中构造器相关的方法
方法 | 用途 |
---|---|
getConstructor(Class…<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class…<?>parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
示例
@Test
public void test03() throws Exception{
Class<User> userClass = User.class;
//getConstructors:获取类中所有公共的构造方法
Constructor<?>[] constructors = userClass.getConstructors();
for (Constructor<?> constructor : constructors) {
int count = constructor.getParameterCount();
System.out.println(count+"个参数的构造方法"+constructor.getName());
}
//getDeclaredConstructors:所有构造方法(包括私有)
Constructor<?>[] declaredConstructors = userClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
int count = declaredConstructor.getParameterCount();
System.out.println(count+"个参数的构造方法"+declaredConstructor.getName());
}
//getDeclaredConstructor:该方法使用三点运算符,表示不定长参数,可以任意个参数,本质是数组
//根据参数类型获取指定的构造方法(包括私有)
Constructor<User> declaredConstructor = userClass.getDeclaredConstructor(String.class);
int count = declaredConstructor.getParameterCount();
System.out.println(count+"个参数的构造方法"+declaredConstructor.getName());
}
5.获得类中方法相关的方法
方法 | 用途 |
---|---|
getMethod(String name, Class…<?> parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class…<?>parameterTypes) | 获得该类的某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
示例
@Test
public void test04() throws Exception{
Class<User> userClass = User.class;
//getDeclaredMethod:根据名称和类型参数列表获取方法
Method setAge = userClass.getDeclaredMethod("setAge", Integer.class);
System.out.println(setAge);
}
6.类中其他重要的方法
方法 | 用途 |
---|---|
isAnnotation() | 如果是注解类型则返回true |
isAnnotationPresent(Class<? extendsAnnotation> annotationClass) | 判断这个Class是否拥有指定的注解 |
isArray() | 如果是一个数组类则返回true |
isEnum() | 如果是枚举类则返回true |
isInstance(Object obj) | 如果obj是该类的实例则返回true |
isInterface() | 如果是接口类则返回true |
7、Field
Field代表类的成员变量。成员变量(字段)和成员属性是两个概念。比如:当一个User类中有一个name变量,那么这个时候我们就说它有 name 这个字段。但是如果没有 getName 和 setName 这两个方法,那么这个类就没有 name 属性。反之,如果这个类拥有 getAge 和 setAge 这两个方法,不管有没有 age 字段,我们都认为它有 age 这个属性。
方法 | 用途 |
---|---|
get(Object obj) | 获得obj中对应的属性值 |
set(Object obj, Object value) | 设置obj中对应属性值 |
SetAccessible(true) | 暴力反射,忽略访问权限修饰符 |
示例
@Test
public void test05() throws Exception{
Class<User> userClass = User.class;
User user=new User("张三",14,"1001");
System.out.println("反射前"+user);
Field field = userClass.getDeclaredField("name");
//暴力反射,忽略访问修饰符
field.setAccessible(true);
//get:获取指定对象当前字段的值
System.out.println(field.get(user));
//set:设置指定对象当前字段的值
field.set(user,"李四");
System.out.println("反射后"+user);
}
8、Method
Method代表类的方法(不包括构造方法)。
方法 | 用途 |
---|---|
invoke(Object obj, Object… args) | 传递object对象及参数调用该对象对应的方法 |
getName() | 获取方法名 |
SetAccessible(true) | 暴力反射,忽略访问权限修饰符 |
Invoke方法的用处:SpringAOP在切面方法执行的前后进行某些操作,就是使用的invoke方法。动态代理设计模式中,使用的也是invoke
示例
@Test
public void test06() throws Exception{
Class<User> userClass = User.class;
User user=new User("张三",14,"1001");
System.out.println("反射前"+user);
//无参数的方法
Method method = userClass.getDeclaredMethod("getIdNumber");
//暴力反射
method.setAccessible(true);
//invoke:执行指定对象的该方法,参数从第二个开始,是该方法的参数值
Object value = method.invoke(user);
System.out.println(value);
//有参数方法
Method method1 = userClass.getDeclaredMethod("setIdNumber", String.class);
//暴力反射
method1.setAccessible(true);
Object value1 = method1.invoke(user, "666");
System.out.println(user);
}
Method类中的invoke方法应用
1.新建一个工具类
public final class MethodUtils {
private MethodUtils(){}
/**
* 执行对象的指定方法
* @param obj:要执行的方法对象
* @param methodName:要执行的方法名称
* @param params:方法参数
* @return
*/
public static Object invokeMethod(Object obj,String methodName,Object... params){
Object result=null;
try {
long startTime=System.currentTimeMillis();
Class<?> objClass = obj.getClass();
System.out.println("类:"+objClass.getName());
System.out.println("方法:"+methodName);
//判断是否有参数
if(null==params || params.length==0){
Method method = objClass.getDeclaredMethod(methodName);
//暴力反射
method.setAccessible(true);
result = method.invoke(obj);
System.out.println("返回值:"+result);
}else{
int size=params.length;
Class<?>[] classes = new Class[size];
Object[] paramValues=new Object[size];
for (int i = 0; i < params.length; i++) {
classes[i]=params[i].getClass();
paramValues[i]=params[i];
}
System.out.println("参数:"+ Arrays.toString(paramValues));
Method method = objClass.getDeclaredMethod(methodName,classes);
method.setAccessible(true);
result = method.invoke(obj, paramValues);
System.out.println("返回值:"+result);
}
long endTime=System.currentTimeMillis();
System.out.println("耗时:"+(endTime-startTime));
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
2.测试
@Test
public void test07(){
User user=new User("张三",14,"1001");
Object result = MethodUtils.invokeMethod(user, "getName");
//Object result1 = MethodUtils.invokeMethod(user, "setName","李四");
System.out.println(user);
}
3.结果
9、Constructor(用的比较少)
Constructor代表类的构造方法
方法 | 用途 |
---|---|
newInstance(Object… initargs) | 根据传递的参数创建类的对象 |
Constructor类在实际开发中使用极少,几乎不会使用Constructor。因为:Constructor违背了Java的一些思想,比如:私有构造不让用户去new对象;单例模式保证全局只有一个该类的实例。而Constructor则可以破坏这个规则
示例
@Test
public void test08() throws Exception{
Class<User> userClass = User.class;
Constructor<User> constructor = userClass.getDeclaredConstructor(String.class, Integer.class, String.class);
//constructor的newInstance可以通过有参构造创建对象
User user = constructor.newInstance("张三", 13, "1001");
System.out.println(user);
//constructor的newInstance还可以通过私有构造创建对象,但是极不安全
Constructor<User> constructor1 = userClass.getDeclaredConstructor(String.class);
constructor1.setAccessible(true);
User user1 = constructor1.newInstance("李四");
System.out.println(user1);
}
Constructor对单例模式的破坏
1)、新建一个单例设计模式的类
public class Session {
private volatile static Session session=null;
private Session(){}
public static Session getSession(){
if (null!=session){
return session;
}
synchronized (Session.class){
if (null==session){
session=new Session();
}
}
return session;
}
}
2)、测试
@Test
public void test09() throws Exception{
Class<Session> sessionClass = Session.class;
Constructor<Session> constructor = sessionClass.getDeclaredConstructor();
//暴力反射
constructor.setAccessible(true);
Session session1 = Session.getSession();
Session session2 = constructor.newInstance();
System.out.println(session1==session2);
}
//结果发现输出:false
通过反射验证泛型擦除
@Test
public void test10() throws Exception{
List<User> list=new ArrayList<>();
list.add(new User("张三",13,"1001"));
list.add(new User("李四",14,"1002"));
Class<? extends List> listClass = list.getClass();
Method addMethod = listClass.getDeclaredMethod("add", Object.class);
addMethod.invoke(list,123);
addMethod.invoke(list,"哈哈");
//证实:泛型会在运行阶段被擦除,这个时候像集合一类的对象就可以强制往里面插入非指定泛型的数据
//如果一定要这样使用,建议后面不要进行任何操作,直接返回结果
for (User user : list) {
System.out.println(user);
}
}
四、注解
1、概述
注解是一种能被添加到java源代码中的元数据,方法、类、参数和包都可以用注解来修饰。注解可以看作是一种特殊的标记,给计算机看的,注解本身没有任何功能。可以用在方法、类、参数和包上,程序在编译或者运行时可以检测到这些标记而进行一些特殊的处理。
2、注释
用文字描述程序,给程序员看的。
3、定义
也叫作元数据,是一种代码级别的说明。它是JDK1.5引入的一个特性,是一种特殊的接口。它可以声明在类、字段、方法、变量、参数、包等前面,作为一个描述去使用。
4、作用分类
- 编写文档:通过代码中标识的注解生成文档(Swagger)
- 代码分析:通过代码里的注解对代码进行分析(逻辑判断)
- 编译检查:通过代码里对应的注解让编译器实现基本的编译检查(Override,Deprecated,FunctionalInterface)
5、JDK中预定义的一些注解
- Override:检测该注解标识的方法是否继承自父类
- Deprecated:标识方法、类、字段等已经过时,后续的版本可能会将其移除
- SuppressWarnings:压制警告
五、自定义注解
在实际开发中,可能会存在一些代码极其复杂或者复用性很低的业务逻辑,比如:导出Execl、缓存、将返回值转json、事务等等,这个时候就可以使用自定义注解。
1、注解的基本元素
声明注解语法格式:
public @interface 注解名{
// 属性列表
String value() default ""; // 带默认值
String value(); // 不带默认值
}
// 本质:是一个接口,该接口事实上默认继承自Annotation接口
说明:
修饰符:访问修饰符必须为public,不写默认为pubic。
关键字:关键字为@interface。
注解名称:为自定义注解的名称,使用时通过名称来调用。
注解类型元素:注解中内容,可以理解成自定义接口的实现部分。
- 基本数据类型
- String类型
- 枚举类型
- 注解类型
- 以上类型的数组
2、使用规则
- 如果定义了属性,在使用属性的时候需要给属性赋值。
- 如果只有一个属性需要赋值,并且这个属性名称为
value
,使用时则可以省略value
属性。- 数组赋值时,需要使用
{}
包起来。如果数组中只有一个元素,则大括号可以省略。
3、元注解
作用: 用于描述注解的注解。
@Target: 表明该注解可以应用的java元素类型(作用范围)。
Target类型 | 描述 |
---|---|
ElementType.TYPE | 应用于类、接口(包括注解类型)、枚举 |
ElementType.FIELD | 应用于字段、枚举常量 |
ElementType.METHOD | 应用于方法 |
ElementType.PARAMETER | 应用于方法的形参 |
ElementType.CONSTRUCTOR | 应用于构造函数 |
ElementType.LOCAL_VARIABLE | 应用于局部变量 |
ElementType.ANNOTATION_TYPE | 应用于注解类型 |
ElementType.PACKAGE | 应用于包 |
ElementType.TYPE_PARAMETER | 1.8版本新增,应用于类型变量 |
ElementType.TYPE_USE | 1.8版本新增,应用于任何使用类型的语句中(例如声明语句、泛型和强制转换语句中的类型) |
@Retention: 表明该注解的生命周期。
生命周期类型 | 描述 |
---|---|
RetentionPolicy.SOURCE | 编译时被丢弃,不包含在类文件中 |
RetentionPolicy.CLASS | JVM加载时被丢弃,包含在类文件中,默认值 |
RetentionPolicy.RUNTIME | 由JVM 加载,包含在类文件中,在运行时可以被获取到 |
@Document: 表明该注解标记的元素可以被 Javadoc 或类似的工具文档化。
@Inherited: 表明使用了 @Inherited 注解的注解,所标记的类的子类也会拥有这个注解(是否可被继承)
示例:
@Target({ElementType.FIELD,ElementType.Type})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "";
String isDelete();
}
配置注解: 定义一个Person
类
// 为Person类配置刚刚定义的 @Info 注解
@MyAnnotation(isDelete = true) // 没有默认值就必须传入
public class Person{
private String name; // 姓名
private int age; // 年龄
private boolean isDelete; // 是否有效
public Person(String name,int age,boolean isDelete){
this.name = name;
this.age = age;
this.isDelete = isDelete;
}
}
利用反射解析注解:
public class Test {
public static void main(String[] args) {
try {
//获取Person的Class对象
Class clazz = person.getClass();
//判断person对象上是否有Info注解
if (clazz.isAnnotationPresent(Info.class)) {
System.out.println("Person类上配置了MyAnnotation注解!");
//获取该对象上MyAnnotation类型的注解
MyAnnotation infoAnno = (MyAnnotation) clazz.getAnnotation(MyAnnotation.class);
System.out.println("person.name :" + infoAnno.value() + ",person.isDelete:" + infoAnno.isDelete());
} else {
System.out.println("Person类上没有配置MyAnnotation注解!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
说明: 对于一个类或者接口来说,Class类(java.lang包下)中提供了一些方法用于注解,当然对于字段、方法来说反射注解的方式很类似。
getAnnotation():返回指定的注解。
isAnnotation():判断当前元素是否被指定注解修饰。
getAnnotation:返回所有注解。
总结
反射:就是在程序运行中对Class、对象等进行一系列的操作
注解:注解就是个标识,相当于是给程序看的注释。注解本身不存在功能,需要通过反射去进行某些判断,根据判断结果去执行对应的逻辑,这个过程就是给注解赋予功能的过程。