目录
注解
什么是注解?
自定义注解
元注解
反射
什么是反射
静态语言和动态语言
动态语言
静态语言
对比
Class类
Java内存分析
类加载过程
类加载器
获取运行时类的完整结构
通过Class对象实例化对象
1.调用Class对象的newInstance
2.Constructor类的newInstance
调用对象的方法和属性
调用指定的方法
调用指定的属性
反射操纵注解
注解
什么是注解?
- Annotation是从JDK1.5开始引入的技术
- Annotation的作用:
- 不是程序本身,可以对程序做出解释。(和注释差不多)
- 可以被其他程序读取
- Annotation的格式:
- 注解是以“@参数名”在代码中存在的,还可以添加一些参数值,比如元注解:@SuppressWarnings(value="unchecked").
- Annotation在哪里使用?
- 可以附加在package、class、method、field等上面,相当于给他们添加了额外的辅助信息,我们可以同反射机制来实现对这些元数据的访问
自定义注解
- 使用 @interface 自定义注解时,自动继承了java.lang.annotation.Annotation 接口。
- 分析
- @interface 用来声明一个注解,格式:public @interface 注解名 { 方法名()... }
- 其中每一个方法实际上是一个配置参数
- 方法名称就是参数名称
- 返回值类型就是参数类型
- 可以通过default来声明参数的默认值
- 如果只有一个参数成员,一般参数名用value
- 注解必须要有值,我们定义注解时,经常使用空字符串、0作为默认值
元注解
- 元注解的作用就是负责注解其他注解,Java定义了4个标准的 meta-annotation类型,它们被用来提供对其他annotation类型作说明
- 这些类型和它们所支持的类在 java.lang.annotation中可以找到(@Target、#Documented、@Inherrited、@Retention)
- @Target:用于描述注解的使用范围(即注解可以标注在什么地方,类上面或者方法、属性等)
- @Retention:表示在什么级别保存该注释信息,用于描述注解的声明周期
- SOURCE:源码阶段-就是写代码的时候]
- CLASS :表示注解将在编译时被保留,并且会被包含在类文件中,但在运行时不可用。但这意味着,当程序运行时,无法访问该注解。
- RUNTINUE :程序运行时,可以访问该注解(通过反射)。
- @Documented:说明该注解将被包含在javadoc中。(javadoc是Java语言中自带的一种工具,用于生成API文档。)
- @Inherited:说明子类可以继承父类的注解
反射
什么是反射
- Java中的反射是指程序在运行时动态地获取类的信息以及操作类的成员变量、方法和构造方法的能力。
- 通过反射,可以在运行时检查类的属性和方法,获取类的构造函数并实例化对象,调用类的成员变量和方法,甚至可以在运行时动态地生成新的类,这使得Java程序具有更大的灵活性和动态性。
- 但是,反射机制也会导致一些性能上的问题,因为反射调用的速度通常比直接调用要慢得多。
Java中的反射主要API:
- Class类:用于表示Java类的信息,包括类的名称、父类、接口、构造函数、成员变量和方法等。
- Constructor类:用于表示Java类的构造函数信息。
- Method类:用于表示Java类的方法信息。
- Field类:代表类的成员变量
反射机制的核心是在运行时动态地获取类的信息,并通过这些信息来调用类的成员变量和方法,这种能力使得Java程序可以在运行时动态地加载和执行代码,从而实现更加灵活和动态的功能。
静态语言和动态语言
动态语言
- 动态语言是指在运行时进行类型检查的语言。在编写程序时,不需要明确指定变量的数据类型,变量的类型会在运行时根据赋值的内容自动推断。因此,动态语言往往比静态语言更灵活,更容易编写和修改。
- 常见的动态语言包括Python、JavaScript和Ruby等。
静态语言
- 静态语言是指在编译时进行类型检查的语言。在编写程序时,需要明确指定变量的数据类型,并在编译时检查所有变量和表达式的类型是否匹配。如果存在类型不匹配的情况,编译器会报错并终止编译。
- Java、C++和C#等语言都是静态语言。
对比
- 总之,静态语言在编写时需要明确指定类型,编译时会进行类型检查,更加严格和安全,但也更加繁琐;动态语言在编写时不需要指定类型,运行时会自动推断类型,更加灵活和易用,但也更加容易出错。
Class类
在Java中,Class类是代表类的实体,它是Java反射机制的核心,用于获取类的信息、创建对象和调用方法等。Class类提供了以下常用的方法:
- getName():获取类的完整名称;
- newInstance():创建类的实例,等同于使用new关键字构造对象;
- getConstructors():获取类的所有公共构造器;
- getMethods():获取类的所有公共方法,包括继承的方法;
- getDeclaredFields():获取类的所有成员变量,不包括继承的变量;
- getDeclaredMethods():获取类的所有方法,不包括继承的方法;
- getSuperclass():获取类的父类;
- isAssignableFrom(Class c):判断当前类是否可以赋值给参数类c;
- isInstance(Object obj):判断当前对象是否为指定类的实例;
- isArray():判断当前类是否为数组类型。
Class类的一个重要应用是Java反射机制。通过Class类获取类的信息,可以实现在运行时动态创建对象、调用方法和访问成员变量等功能,增加了程序的灵活性和动态性。同时,反射机制也带来了一定的性能开销,因此应该慎重使用。
无论一个.java文件中有多少个类,最终都只有一个Class对象。
Java内存分析
在Java应用程序中,内存主要分为三个区域:堆、栈和方法区。
- 堆(Heap):用于存储对象实例,堆是Java中最大的内存分配区域。堆内存的大小可以通过-Xmx和-Xms参数来指定,-Xmx表示最大堆内存,-Xms表示初始堆内存。
- 栈(Stack):用于存储方法调用的信息,每个线程都有自己的栈空间。栈内存的大小是固定的,并且在线程创建时分配。栈中存储着局部变量、方法参数、方法返回值和方法调用的状态等信息。
- 方法区(Method Area):用于存储类的信息、静态变量、常量等数据。方法区属于堆的一部分,但是它的作用和用途与堆不同。
类加载过程
Java的类加载过程分为三个步骤:加载、链接和初始化。其中,加载和链接是在程序运行时进行的,而初始化在类被首次使用时进行。
- 加载:将类的字节码文件加载到内存中,并在内存中创建一个Class对象,用于表示该类的信息。ClassLoader负责查找和加载类的字节码文件,将字节码文件读入内存,并创建Class对象,并将其保存在方法区中。
- 链接:在链接阶段,虚拟机会对类进行验证、准备和解析。
- 验证:验证字节码文件的正确性,包括验证文件格式、元数据、字节码和符号引用等是否符合规范。
- 准备:为类的静态变量分配内存,并设置默认初始值,例如int类型的默认值为0,对象类型的默认值为null。这些内存都在方法区中进行分配。
- 解析:将符号引用解析为直接引用,例如将方法调用的符号引用解析为实际的方法地址。
- 初始化:在类被首次使用时进行,虚拟机会执行类的初始化操作,包括执行类的静态代码块和静态变量的初始化。
- 执行静态代码块:静态代码块是在类被初始化时执行的,它可以用来进行一些静态资源的初始化工作。
- 静态变量初始化:静态变量的初始化也是在类被初始化时进行的,它可以通过赋初值或静态代码块来进行初始化。
类的加载过程是Java虚拟机实现动态性的重要基础,也是Java的重要特性之一。通过自定义ClassLoader,可以实现类的动态加载和替换,这为Java应用程序带来了更大的灵活性和动态性。
类加载器
获取运行时类的完整结构
通过Java反射可以获取类的运行时完整结构,包括类的构造器、方法、字段、注解和泛型等信息,具体步骤如下:
- 获取Class对象:使用 lass.forName() 方法或者类名.class 获取需要反射的类的Class对象。
- 获取构造器:使用 getConstructors() 方法获取类的所有公共构造器,使用getDeclaredConstructors() 方法获取类的所有构造器(包括私有构造器)。
- 获取方法:使用 getMethods() 方法获取类的所有公共方法,使用 getDeclaredMethods() 方法获取类的所有方法(包括私有方法)。
- 获取字段:使用 getFields() 方法获取类的所有公共字段,使用 getDeclaredFields() 方法获取类的所有字段(包括私有字段)。
- 获取注解:使用 getAnnotations() 方法获取类的所有注解,使用 getDeclaredAnnotations()方法获取类的所有注解(包括私有注解)。
- 获取泛型:使用 getGenericSuperclass() 方法获取类的带有泛型的父类,使用getGenericInterfaces() 方法获取实现的所有接口(包括带有泛型的接口)。
通过反射获取类的运行时完整结构可以实现动态调用、动态代理和动态生成代码等功能,但是反射的性能较低,应该尽量避免在性能要求高的场景中使用。
通过Class对象实例化对象
1.调用Class对象的newInstance
使用newInstance()方法创建类的实例,该方法会调用类的默认构造器来创建对象。
需要注意的是,newInstance()方法只能调用类的默认构造器来创建对象,如果类没有默认构造器或者默认构造器不可访问,则会抛出InstantiationException异常。如果需要调用其他构造器来创建对象,则需要使用Constructor类的newInstance()方法。
2.Constructor类的newInstance
Class<?> c1 = Class.forName("com.reflection.test.User");
//用含参的构造器创建对象
Constructor<?> constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
User user1 = (User)constructor.newInstance("张三", 1, 20);
调用对象的方法和属性
调用指定的方法
通过反射,调用类中的方法,通过Method类完成。
通过Class对象的 getMethod() 或者 getDeclaredMethod() 方法来获得一个Method对象,并设置此方法操作时需要的参数类型。
然后使用 invoke 进行调用
Class<?> c1 = Class.forName("com.refelection.test.User");//获得Class类对象
User user = (User) c1.newInstance();//初始化对象
Method setName = c1.getDeclaredMethod("setName", String.class);
setName.invoke(user,"张三"); //调用user的setName方法将user对象的name属性设置为"张三"
如果方法为private,需要关闭安全检测setAccessible(true)
setName.setAccessible(true);
调用指定的属性
//获得属性
Field name = c1.getDeclaredField("name");
name.set("name","张三");//设置属性
同样如果属性为private,需要关闭安全检测setAccessible(true)
name.setAccessible(true);//关闭权限检测
反射操纵注解
在Java中,通过反射可以操作注解,具体步骤如下:
- 获取注解信息:使用Class、Method、Field等类的getAnnotation()方法获取注解信息,例如使用getAnnotation()方法获取类、方法或属性上的注解信息,使用getAnnotations()方法获取类、方法或属性上的所有注解信息。
- 解析注解信息:使用Annotation类的相关方法解析注解信息,例如使用annotationType()方法获取注解的类型,使用value()方法获取注解的属性值等。
import java.lang.annotation.*;
import java.lang.reflect.Field;
/**
* 反射操作注解
*/
public class Test12 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
//获得Class对象
Class<?> c1 = Class.forName("reflection.Student2");
//通过反射获得注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);//@reflection.TableInfo(value=db_student)
}
//获得注解的 value 的值
TableInfo tableInfo = c1.getAnnotation(TableInfo.class);
String value = tableInfo.value();
System.out.println(value);//db_student
//获得类指定的注解
Field f = c1.getDeclaredField("name");
FieldInfo fieldInfo = f.getAnnotation(FieldInfo.class);
System.out.println(fieldInfo.columnName());//db_name
System.out.println(fieldInfo.type());//varchar
System.out.println(fieldInfo.length());//10
}
}
@TableInfo("db_student")
class Student2{
@FieldInfo(columnName = "db_id",type = "int",length = 10)
private int id;
@FieldInfo(columnName = "db_age",type = "int",length = 10)
private int age;
@FieldInfo(columnName = "db_name",type = "varchar",length = 10)
private String name;
public Student2(){
}
public Student2(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student2{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
//类名的注解
@Target(ElementType.TYPE)//作用范围 类
@Retention(RetentionPolicy.RUNTIME)//作用的生命周期 运行时
@interface TableInfo{
String value();
}
//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldInfo{
String columnName();
String type();
int length();
}