文章目录
- 一、 注解
- 1.简介
- 2. 元注解
- 3. 自定义注解
- 二、 反射
- 1. 简介
- 2. 理解Class类并获取Class实例
- 3. 类的加载与初始化
- 4. 类加载器ClassLoader
- 5. 获取运行时类的完整结构
- 6. 动态创建对象执行方法
- 7. 反射操作泛型
- 8. 反射操作注解
一、 注解
1.简介
Annotation是JDK5.0开始引入的新技术,Annotation不是程序本身,它可以对程序作出解释(这一点和注释没什么区别),但它可以被其他程序读取。注解(Annotations)是一种用于提供元数据的机制,可以被用于类、方法、变量、参数和包等。这些元数据本身对代码的逻辑没有直接影响,但可以被用来影响编译、运行时行为,或者被用来做代码生成、测试和API文档等。Java 中的注解可以分为三类:
- 标准注解:Java 提供的标准注解,比如 @Override, @Deprecated, @SuppressWarnings。
- 元注解:用于定义注解的注解,比如 @Retention, @Target, @Inherited, @Documented 和 Java 8 引入的 @Repeatable。
- 自定义注解:用户根据需要自定义的注解。
下面介绍了一个简单的自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String value() default "";
}
然后你就可以在方法上使用这个注解了:
public class MyClass {
@MyAnnotation(value = "Hello")
public void myMethod() {
//...
}
}
最后你可以通过反射获取到这个注解和它的元数据
Method method = MyClass.class.getMethod("myMethod");
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
System.out.println(annotation.value()); // Prints "Hello"
请注意,不是所有的注解都可以在运行时获取。只有 @Retention(RetentionPolicy.RUNTIME) 的注解才能在运行时获取,如果注解的 @Retention 是 SOURCE 或 CLASS,那么这个注解在编译后就不会包含在 class 文件中,因此无法在运行时获取
2. 元注解
元注解的作用就是负责注解其他注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他注解类型作声明,这些类型和它们所支持的类在java.lang.annotation包中可以找到(@Target、@Retention、@Documented和@Inherited)
- @Target:用于描述注解的使用范围(即被描述的注解可以用到什么地方)
- @Retention:表示需要在什么级别保存该注解信息,用于描述注解的生命周期(SOURCE<CLASS<RUNTIME)
- @Document:说明该注解将被包含在javadoc中
- @Inherited:说明子类可以继承父类中的该注解
@Target
@Target 是 Java 提供的一种元注解,它被用于标识其他自定义注解可以应用的位置(也就是说,可以标记在哪些程序元素上)。这些位置由 ElementType 枚举的值定义,包括:
- ElementType.TYPE: 类、接口(包括注解类型)或枚举的声明
- ElementType.FIELD: 字段的声明,包括枚举常量
- ElementType.METHOD: 方法的声明
- ElementType.PARAMETER: 方法参数的声明
- ElementType.CONSTRUCTOR: 构造器的声明
- ElementType.LOCAL_VARIABLE: 局部变量的声明
- ElementType.ANNOTATION_TYPE: 注解类型声明
- ElementType.PACKAGE: 包的声明
- ElementType.TYPE_PARAMETER: 类型参数的声明(Java 8新增)
- ElementType.TYPE_USE: 任何使用类型的地方(Java 8新增)
如果没有使用 @Target 来标注一个注解,那么该注解可以用在任何元素上。
@Retention
@Retention 是 Java 提供的一种元注解,它用于指定被标记的注解在哪个阶段保留。@Retention 可以接收一个 RetentionPolicy 参数,这是一个枚举,其三个可能的值包括:
- RetentionPolicy.SOURCE:注解仅保留在源码中,编译器处理源代码时就会被丢弃。这种类型的注解只用于检查代码或者生成文档。例如,@Override 和 @SuppressWarnings 就是这种类型的注解。
- RetentionPolicy.CLASS:注解在编译时会被保留在字节码(.class文件)中,但在运行时 JVM 不会认识它们,所以运行时无法使用反射获取这些注解。这是默认的保留策略。
- RetentionPolicy.RUNTIME:注解在编译时会被保留在字节码中,并且在运行时 JVM 也会认识它们,所以可以在运行时通过反射获取这些注解的信息。这种类型的注解主要用于框架,如 Spring、Hibernate 等
@Document
在 Java 中,@Documented 是一种元注解,用于指定被标注的注解是否应该被 javadoc 工具记录在默认的文档中。换句话说,如果一个注解被 @Documented 标注,那么任何使用了该注解的公共 API(例如类,方法等),在生成 javadoc 文档时,都会包含这个注解的信息。请注意,@Documented 是一种标记注解,它没有成员元素。它只是一个标志,告诉 javadoc 工具应该记录使用了被 @Documented 标注的注解的 API
javadoc:
Javadoc 是 Java 语言中用于生成 API 文档的一个工具,它可以从 Java 代码中的注释生成 HTML 格式的文档。Javadoc 工具是 Oracle 的 Java 开发工具包(JDK)的一部分。Javadoc 的主要用途是为编写的代码提供 API 文档。这些文档可以包括类的描述、方法的描述、参数的描述、返回值的描述、异常的描述等。同时,Javadoc 还能根据包结构自动生成导航和索引,使得文档更加易于查阅。使用 Javadoc 的关键是编写良好的文档注释。在 Java 代码中,文档注释以 /** 开头,以 */ 结尾,位于任何类、接口、构造方法、方法或字段声明的前面。例如:
/**
* 这是一个示例类的描述。
*/
public class ApTtest {
/**
* 这是一个示例方法的描述。
*
* @param param 这是参数的描述。
* @return 这是返回值的描述。
* @throws IllegalArgumentException 当参数非法时抛出此异常
*/
public int exampleMethod(String param) throws IllegalArgumentException {
//...
}
}
在这个例子中,Example 类和 exampleMethod 方法都有 Javadoc 注释。注释中的 @param、@return 和 @throws 是 Javadoc 的标签,用于指定参数、返回值和抛出的异常的文档。在你写好 Javadoc 注释后,可以使用 javadoc 命令行工具来生成文档。在命令行中,切换到代码的目录,然后运行 javadoc 命令,例如:javadoc Example.java。这将在当前目录下生成一个名为 index.html 的文档入口文件,以及其他相关的 HTML 文件。总的来说,Javadoc 是一个强大的工具,能够帮助你生成详尽的 API 文档,对于维护大型代码库或者创建公开 API 非常有用。下面就是我使用javadoc命令生成的API文档。
3. 自定义注解
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,@interface用来声明一个注解,格式:public @interface 注解名{定义内容}
,其中的每一个方法实际上是声明了一个配置参数,方法的名称就是参数的名字,返回值类型就是参数的类型(返回值只能是基本类型,Class、String和enum)。可以通过default来声明参数的默认值,如果只有一个参数成员,一般参数名为value,注解元素必须要有值,我们定义注解原属时,经常使用空字符串,0作为默认值。
public class ApiTest {
//如果没有默认值,我们必须赋值
@MyAnnocation2(name="chailong",age = 18)
public void test(){
}
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnocation2{
//注解的参数:参数类型+参数名()
//默认值给的空
String name() default "";
int age() default 0;
int id() default -1;
String[] schoos() default {"清华大学","背景大学"};
}
二、 反射
1. 简介
在 Java 中,反射是一种强大且高级的功能,它允许运行中的 Java 程序对自身进行自省,包括检查类、接口、字段和方法的信息。更进一步,反射还允许创建和操作对象,即使你在编写代码时不知道它们的具体类型。Java 反射的主要组成部分是 java.lang.Class 类和 java.lang.reflect 包。Class 类代表类和接口的元数据,并提供了获取这些信息的方法。java.lang.reflect 包中的类(如 Field, Method, Constructor 等)允许你访问和操作类的字段、方法、构造器等信息。以下是反射的一些常见用途:
- 动态创建对象和调用方法:你可以使用 Class.forName 来加载一个类,然后调用 newInstance 方法来创建这个类的实例。然后,你可以获取这个类的方法并调用它们。
- 访问私有成员:通常,你不能直接访问一个类的私有字段和方法。但是,通过反射,你可以设置 setAccessible(true) 来访问和修改私有字段或调用私有方法。
- 实现通用的数组操作:反射 API 提供了 java.lang.reflect.Array 类,它有很多用于创建和操作数组的方法,这些方法可以处理所有类型的数组,包括基本类型的数组。
- 动态代理:java.lang.reflect.Proxy 类允许你动态地创建接口的实现。这被广泛用于各种框架中,例如 Spring 和 Hibernate。
尽管反射非常强大,但它也有一些缺点。最重要的是,反射操作相对于直接的 Java 方法调用来说非常慢,因为反射涉及到动态解析类的信息。另外,反射代码通常难以理解和维护。因此,你应该只在必要的时候使用反射,而在可能的情况下优先考虑其他的解决方案。
@Inherited
@Inherited 是 Java 提供的一种元注解,用于指示被标记的注解类型会被自动继承。如果一个使用了 @Inherited 修饰的注解被用于一个类上,那么这个注解会被这个类的所有子类继承。请注意,这种继承只对类有效,不对类成员有效。也就是说,如果一个注解标记在一个类的方法或字段上,那么这个注解并不会被该类的子类的对应方法或字段继承。另外,接口的注解不会被实现接口的类继承,类的注解也不会被实现的接口继承。
静态语言VS动态语言
动态语言:是一类运行时可以改变结构的语言(例如新的函数、对象、甚至代码可以被引进,已有的函数可以删除或是其他结构上的变化),通俗点说就是在运行时代码可以根据某些条件改变自身结构。(Object-c、C#、Javascript、PHP、Python等)
静态语言:与动态语言相对应,运行时结构不可改变的就是静态语言。如Java、C和C++。Java不是动态语言,但Java可以称为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制获得类似动态的特性。Java的动态性让编程更加灵活。
Refletction(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助Reflection的API取得任何类的内部信息,并能直接操作任意对象内部属性及方法。Class c= Class.forName("java.lang.String")
。加载完类之后,在堆方法区内就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们形象的称之为反射。
反射机制提供的功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
反射虽然实现了动态创建和编译对象,具有很大的灵活性,但反射对性能有影响,使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作。
反射相关的主要API
- java.lang.Class:代表一个类
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造器
- …
2. 理解Class类并获取Class实例
class类的使用案例
public class ApiTest {
public static void main(String[] args) throws ClassNotFoundException {
//通过反射获取类的Class对象
Class<?> name1 = Class.forName("springframework.user");
Class<?> name2 = Class.forName("springframework.user");
//判断Class对象是否唯一,一个类在内存中只有一个Class对象,一个类被加载后,类的整个结构都会被封装到class对象中
System.out.println("name1:"+name1.hashCode());
System.out.println("name2:"+name2.hashCode());
}
}
//实体类
class user{
private String name;
private int id;
private int age;
public user(){};
public user(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
@Override
public String toString() {
return "user{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
}
Class类:对象照镜子后可以得到的信息,某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于某个类而言,JRE都为其保留了一个不变的Class类型的对象。一个Class对象包含了特定的某些结构的有关信息。
- Class类本身也是一个类
- Class对象只能由系统建立
- 一个加载的类在JVM中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己由哪个Class实例所生成
- 每个Class对象可以完整地得到一个类的所有被加载的结构
- Class类是Reflection的根源,针对任何你想动态加载的类、运行时的类,唯有先获得Class对象
Class部分重要源码如下:
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
//构造函数,参数为一个类加载其
private Class(ClassLoader loader) {
classLoader = loader;
}
//这句话的意思是@CallerSensitive注解用于告诉Java虚拟机,这个方法的调用者的安全性取决于它的上下文环境。这个注解可以帮助Java虚拟机确定调用者的身份和权限,从而确保代码的安全性。
@CallerSensitive
//这个方法的作用是根据给定的类名加载并返回一个Class对象。它使用了Reflection.getCallerClass()方法来获取调用者的类,然后使用ClassLoader.getClassLoader()方法来获取调用者的类加载器,最后调用forName0()方法来加载并返回指定类的Class对象。如果指定的类名无法找到,则会抛出ClassNotFoundException异常
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
//forname0是一个Native方法,所有的forname方法底层都是调用这个forName0方法
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
}
//它会调用一个类的默认构造函数,返回一个class对象的实例
@CallerSensitive
public T newInstance()
throws InstantiationException, IllegalAccessException
{
//这个if语句用于检查是否存在安全管理器,并在存在时检查调用者的访问权限。
if (System.getSecurityManager() != null) {
//这个方法用于检查调用者是否具有访问公共成员的权限
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
}
//这个if语句用于检查是否已经缓存了构造函数
if (cachedConstructor == null) {
//这个if语句用于检查是否正在尝试创建java.lang.Class类的实例,如果是,则抛出IllegalAccessException异常
if (this == Class.class) {
throw new IllegalAccessException(
"Can not call newInstance() on the Class for java.lang.Class"
);
}
try {
//这个语句定义了一个空的Class数组
Class<?>[] empty = {};
//这个语句用于获取声明的构造函数
final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
//:这个语句用于执行特权操作,以便将构造函数的可访问性设置为true
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
c.setAccessible(true);
return null;
}
});
//这个语句用于将构造函数缓存起来,以便以后使用
cachedConstructor = c;
} catch (NoSuchMethodException e) {
throw (InstantiationException)
new InstantiationException(getName()).initCause(e);
}
}
//这个语句将缓存的构造函数赋值给一个临时变量
Constructor<T> tmpConstructor = cachedConstructor;
这个语句用于获取构造函数的修饰符
int modifiers = tmpConstructor.getModifiers();
//这个if语句用于检查调用者是否具有访问构造函数的权限
if (!Reflection.quickCheckMemberAccess(this, modifiers)) {
//这个语句用于获取调用者的类
Class<?> caller = Reflection.getCallerClass();
if (newInstanceCallerCache != caller) {
//这个语句用于确保调用者具有访问构造函数的权限
Reflection.ensureMemberAccess(caller, this, null, modifiers);
newInstanceCallerCache = caller;
}
}
// Run constructor
try {
//这个语句用于创建一个新的类实例,并返回一个泛型T类型的对象
return tmpConstructor.newInstance((Object[])null);
} catch (InvocationTargetException e) {
Unsafe.getUnsafe().throwException(e.getTargetException());
// Not reached
return null;
}
}
//返回此Class对象所表示的实体(类、接口、数组类或void)的名称
public String getName() {
String name = this.name;
if (name == null)
this.name = name = getName0();
return name;
}
//返回当前Class对象的父类Class对象
public Type getGenericSuperclass() {
ClassRepository info = getGenericInfo();
if (info == null) {
return getSuperclass();
}
if (isInterface()) {
return null;
}
return info.getSuperclass();
}
//返回当前Class对象的接口
public Class<?>[] getInterfaces() {
// 获取反射数据
ReflectionData<T> rd = reflectionData();
if (rd == null) {
// 不需要克隆,直接获取接口
return getInterfaces0();
} else {
// 获取缓存的接口
Class<?>[] interfaces = rd.interfaces;
if (interfaces == null) {
// 如果缓存为空,则获取接口并缓存
interfaces = getInterfaces0();
rd.interfaces = interfaces;
}
// 防御性地复制一份后返回给用户使用
return interfaces.clone();
}
}
//获得该类的类加载器
@CallerSensitive
public ClassLoader getClassLoader() {
ClassLoader cl = getClassLoader0();
if (cl == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
}
return cl;
}
//返回一个Method对象,此对象的形参类型为paramType
@CallerSensitive
public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
Method method = getMethod0(name, parameterTypes, true);
if (method == null) {
throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
}
return method;
}
//返回Field对象的一个数组
@CallerSensitive
public Field[] getDeclaredFields() throws SecurityException {
checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
return copyFields(privateGetDeclaredFields(false));
}
}
获取Class类实例的几种方法
- 若已经知道具体的类,通过类的class属性获取,该方法是最为安全可靠的,程序性能最高。
Class class=Person.class
- 已知某个类的实例,调用该实例的getClass()方法获取Class对象
Class class=person.getClass()
- 已知一个类的全类名,且调用类在类路径下,可以通过Class类的静态方法forName()获取
Class class=Class.forName("demo1.Student")
- 内置基本数据类型可以直接用类名.Type获取
- 还可以使用类加载器获取
public class ApiTest {
public static void main(String[] args) throws ClassNotFoundException {
Person person=new Student();
//方式一:通过对象获取
Class<? extends Person> aClass = person.getClass();
System.out.println("方式一:"+aClass.toString()+" hash值:"+aClass.hashCode());
//方式二:使用forname获取
Class<?> bClass = Class.forName("springframework.Student");
System.out.println("方式二:"+bClass.toString()+" hash值:"+bClass.hashCode());
//方式三:通过类名获取
Class<Student> studentClass = Student.class;
System.out.println("方式三:"+studentClass.toString()+" hash值:"+studentClass.hashCode());
//方式四:基本内置类型的包装类都有一个Type属性可以获取
Class<Integer> type = Integer.TYPE;
System.out.println("方式四:"+type.toString()+" hash值:"+type.hashCode());
//获取父类的类型
Class<?> superclass = aClass.getSuperclass();
System.out.println("父类的类型:"+superclass.toString());
}
}
//实体类
class Person{
String name;
int id;
int age;
public Person(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
public Person(){
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
}
class Student extends Person{
public Student(){
this.name="学生";
}
}
class Teacher extends Person{
public Teacher(){
this.name="老师";
}
}
哪些类型可以有Class对象
- class:外部类,成员(成员内部类、静态内部类),局部内部类,匿名内部类
- interface:接口
- []:数组
- enum:枚举
- annotation:注解@interface
- primitve type:基本数据类型
- void
public static void main(String[] args) throws ClassNotFoundException {
//外部类
Class<Object> objectClass = Object.class;
System.out.println("外部类:"+objectClass.toString());
//接口
Class<Comparable> comparableClass = Comparable.class;
System.out.println("接口:"+comparableClass.toString());
//数组
Class<String[]> aClass = String[].class;
System.out.println("一维数组:"+aClass.toString());
Class<int[][]> aClass1 = int[][].class;
System.out.println("二维数组:"+aClass1.toString());
//注解
Class<Override> overrideClass = Override.class;
System.out.println("注解:"+overrideClass.toString());
//枚举
Class<ElementType> elementTypeClass = ElementType.class;
System.out.println("枚举:"+elementTypeClass.toString());
//基本数据类型
Class<Integer> integerClass = Integer.class;
System.out.println("基本数据类型:"+integerClass.toString());
//void
Class<Void> voidClass = void.class;
System.out.println("void:"+voidClass.toString());
}
3. 类的加载与初始化
java内存分析
类的加载过程
- 加载:将Class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象
- 链接:将java类的二进制代码合并到JVM的运行状态之中的过程
-
- 验证:确保加载的类符合JVM规范,没有安全方面的问题
-
- 准备:正式为类变量(static)分配内存并设置类变量的默认初始值的阶段,这些内存都将在方法区中进行分配
-
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用地址的过程
- 初始化:
-
- 执行类构造器<clinit>()方法的过程。该构造器<clinit>()的方法是由编译期自动收集类中的所有类变量的赋值和静态代码块中的语句合并产生的(类构造器是构造类信息的,不是构造该类对象的构造器)
-
- 当初始化一个类时,如果发现其父类还没有初始化,则需要先触发父类的初始化
-
- 虚拟机保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步
类的初始化
- 类的主动引用(一定会发生类的初始化)
-
- 当虚拟机启动,先初始化main()方法所在的类
-
- new一个类的对象
-
- 调用类的静态成员(除了final常量)和静态方法
-
- 使用java.lang.reflect包的方法对类进行反射调用
-
- 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
- 类的被动引用(不会发生类的初始化)
-
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
-
- 通过数组定义类引用,不会触发此类的初始化
-
- 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
public class ApiTest {
static {
System.out.println("Main被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
//主动引用
Son son=new Son();
//反射也会加载主动引用
//Class<?> aClass = Class.forName("springframework.Son");
}
}
//实体类
class Father{
static int b=2;
static {
System.out.println("父类被加载");
}
}
class Son extends Father{
static {
System.out.println("子类被加载");
m=300;
}
static int m=100;
static final int k=1;
}
public static void main(String[] args) throws ClassNotFoundException {
//被动引用
int b = Son.b;
}
4. 类加载器ClassLoader
类加载器用于将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后再堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。标准的JavaSE类加载可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓冲)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
类加载器的作用时把类装载进内存的。JVM规范定义类如下类型的类的加载器
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类的加载器
ClassLoader systemClassLoader=ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//获取系统类加载器的父类加载器->扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);
//获取扩展类的父类加载器->根加载器(由于是c++写的所以获取不到)
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
//测试当前类是谁加载的
Class<?> aClass = Class.forName("springframework.ApiTest");
System.out.println(aClass);
//测试JDK内置的类是谁加载的->获取不到因为是根加载器加载的
Class<?> aClass1 = Class.forName("java.lang.Object");
System.out.println(aClass1);
//如何获得系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));
}
5. 获取运行时类的完整结构
通过反射获取运行类的完整结构:Field、Method、Constructor、Superclass、Interface、Annotation
- 实现全部的接口
- 所继承的父类
- 全部的构造器
- 全部的方法
- 全部的Field
- 注解
public class ApiTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class<?> aClass = Class.forName("springframework.Student");
System.out.println("获得类的名字:"+aClass.getName());
System.out.println("获得类的简单名字:"+aClass.getSimpleName());
//getFields只能获取public属性
System.out.println("获得类的所有属性:"+ Arrays.toString(aClass.getDeclaredFields()));
//getField只能获取public属性
System.out.println("获得类的指定属性:"+aClass.getDeclaredField("id"));
System.out.println("获得所有方法(包括本类和父类):"+Arrays.toString(aClass.getMethods()));
System.out.println("获得所有方法(只获得本类):"+Arrays.toString(aClass.getDeclaredMethods()));
//获得指定的方法吗
Method getId=aClass.getMethod("getId",null);
Method setId=aClass.getMethod("setId", int.class);
System.out.println("获得指定的方法:"+getId);
System.out.println("获得指定的方法:"+setId);
Constructor<?>[] constructor = aClass.getConstructors();
System.out.println("获得public构造函数:"+Arrays.toString(constructor));
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
System.out.println("获得本类全部构造函数:"+Arrays.toString(declaredConstructors));
//获得指定的构造函数
Constructor<?> constructor1 = aClass.getConstructor();
System.out.println("无参构造函数:"+constructor1);
Constructor<?> constructor2 = aClass.getConstructor(int.class);
System.out.println("有参构造函数:"+constructor2);
}
}
class Person{
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class Student extends Person{
int id;
public Student(int id) {
this.id = id;
}
public Student(){
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
6. 动态创建对象执行方法
使用newInstance方法创建类的对象:
- 类必须有一个无参构造函数(如果没有无参构造函数,只要在操作的时候明确调用类中的构造求,并将参数传递进去之后,才能进行实例化操作,步骤如下:)
- 通过Class类的getDeclardConstructor(Class … parameterTypes)取得本类的指定行参类型的构造函数
- 向构造函数的行参中传递一个对象数组进去,里面包含了构造器中所需的各个参数
- 通过Constructor实例化对象
- 类的构造器的访问权限必须足够
public class ApiTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
//获得class对等
Class<?> aClass = Class.forName("springframework.Student");
Student student = (Student) aClass.newInstance();
System.out.println("调用了无参构造器:"+student);
Constructor<?> constructor = aClass.getConstructor(String.class, int.class, int.class);
Student student1 = (Student) constructor.newInstance("jack", 23, 12);
System.out.println(student1);
//通过反射调用普通方法(其实上面创建对象后就可以正常使用类中定义的成员方法了)
Method setId = aClass.getMethod("setId", int.class);
setId.invoke(student,1233);
System.out.println(student);
//通过反射创建属性
Field id = aClass.getDeclaredField("id");
//private属性不能直接设置,可以使用id.setAccessible(true)来关闭安全检查
id.set(student,213);
System.out.println(student);
}
}
class Person{
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class Student extends Person{
int id;
public Student(String name , int age,int id) {
super(name,age);
this.id = id;
}
public Student(){
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
Method和Field、Constructor对象都有setAccessible()方法,setAccessible的作用是启动和禁止访问安全检查开关。参数值为true则指示反射的对象在使用时应该取消Java语言访问检查(这样可以提高反射的效率,如果代码中必须用反射,而该句代码需要频繁的被调用,那么可以设置为true,这样原来私有的方法和成员也可以访问了)
性能分析
public class ApiTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
//普通方法调用
Student simple=new Student();
long startTime=System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
simple.getId();
}
long endTime=System.currentTimeMillis();
System.out.println("普通方法用时:"+(endTime-startTime));
//反射方法
Student simple2=new Student();
Class<? extends Student> aClass = simple2.getClass();
Method getId = aClass.getMethod("getId");
startTime=System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getId.invoke(simple2);
}
endTime=System.currentTimeMillis();
System.out.println("反射方法:"+(endTime-startTime));
//关闭检测的反射方法
Student simple3=new Student();
Class<? extends Student> bClass = simple2.getClass();
Method getId2 = bClass.getMethod("getId");
getId2.setAccessible(true);
startTime=System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getId2.invoke(simple3);
}
endTime=System.currentTimeMillis();
System.out.println("关闭检测反射方法:"+(endTime-startTime));
}
}
class Person{
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class Student extends Person{
int id;
public Student(String name , int age,int id) {
super(name,age);
this.id = id;
}
public Student(){
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
所有关闭检测可以提高反射效率
7. 反射操作泛型
Java采用泛型擦除机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的问题。但是,一旦编译完成,所有的泛型有关的类型全部擦除。为了通过反射操作这些类型,Java新增加了ParameterizedType,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能归到Class类中的类型,但是又和原始类型齐名的类型。
泛型是 Java 5.0 中引入的一种新特性,它允许在定义类和接口的时候使用类型参数。比如,你可以定义一个 List,其中 E 是一个类型参数,表示 List 中元素的类型。然后,你在使用的时候可以指定 E 的具体类型,比如 List 表示一个整数列表。那么,什么是泛型擦除呢?在 Java 中,泛型实际上是在编译阶段实现的。也就是说,当你编写了使用泛型的代码之后,编译器在编译的时候会把所有的类型参数(比如上面的 E)都替换成实际的类型(或者如果没有指定具体类型,就用上界,即 extends 后面的类型)。这个过程就叫做泛型擦除。因此,运行时的 Java 代码实际上是没有泛型信息的。泛型擦除的作用:
- 向后兼容性:由于泛型是在 Java 5.0 之后引入的,为了保证新代码能够和老代码无缝对接,Java 泛型采用了类型擦除的策略。这样,使用泛型的代码在编译后,对于 JVM 来说,就和没有使用泛型的代码没有区别,确保了兼容性
- 类型安全:在编译时期,编译器会检查我们是否正确使用了泛型,这样可以在编译时期就发现可能在运行时出现的类型转换错误,增强了 Java 程序的类型安全性
但泛型擦除也有一些限制
- 由于运行时没有泛型信息,所以不能用 new T() 这样的代码创建一个泛型类型的实例。
- 不能使用 instanceof 操作符检查一个对象是否是一个泛型类型。比如,不能写 “if (o instanceof List)”。
- 不能创建泛型数组。比如,不能写 “new List<String>[10]”
ParameterizedType
ParameterizedType 是 Java 反射中的一个接口,它代表了一种参数化的类型,比如 List。在 Java 泛型中,参数化类型是一种重要的类型,这是因为在运行时,Java 泛型信息被擦除了,无法直接从对象中获取其泛型信息。而 ParameterizedType 可以帮助我们获取到这些信息。下面是 ParameterizedType 的一些主要方法:
Type[] getActualTypeArguments()
:返回表示此类型实际类型参数的 Type 对象的数组。例如,在 Map<String, Integer> 中,调用 getActualTypeArguments() 返回的数组包含 String 和 Integer。Type getRawType()
:返回表示此类型的原始类型的 Type 对象。例如,在 Map<String, Integer> 中,调用 getRawType() 返回的是 Map。Type getOwnerType()
:如果此类型是其它类型的成员,则返回其它类型;如果此类型表示顶级类型,则返回 null。
public class Test {
public static void main(String[] args) {
Field field = ClassA.class.getDeclaredField("map");
Type genericType = field.getGenericType();
if(genericType instanceof ParameterizedType){
ParameterizedType parameterizedType = (ParameterizedType) genericType;
System.out.println("Raw type: " + parameterizedType.getRawType());
System.out.println("Owner type: " + parameterizedType.getOwnerType());
System.out.println("Actual type arguments: " + Arrays.toString(parameterizedType.getActualTypeArguments()));
}
}
static class ClassA {
Map<String, Integer> map;
}
}
GenericArrayType
GenericArrayType 是 Java 的反射 API 中的一个接口,它表示泛型数组的类型。当你需要反射地处理数组的类型,并且这个数组可能是一个泛型数组时,你会使用到这个接口。如果一个数组的元素是参数化类型或类型变量,那么这就是一个泛型数组。例如,List[] 和 T[] 都是泛型数组。GenericArrayType 接口定义了一个方法:
- Type getGenericComponentType():返回泛型数组中元素的类型。
下面是一个使用 GenericArrayType 的例子:
public class Test {
public static void main(String[] args) throws Exception {
Field field = ClassA.class.getDeclaredField("listArray");
Type genericType = field.getGenericType();
if (genericType instanceof GenericArrayType) {
GenericArrayType genericArrayType = (GenericArrayType) genericType;
Type componentType = genericArrayType.getGenericComponentType();
System.out.println(componentType);
}
}
static class ClassA {
List<String>[] listArray;
}
}
TypeVariable
- TypeVariable 是 Java 反射 API 中的一个接口,它代表了类型变量,也就是在定义泛型类、接口、方法时用到的类型参数。例如,在 List 中,E 就是一个类型变量。下面是 TypeVariable 的一些主要方法:
- Type[] getBounds():返回表示此类型变量上边界的 Type 对象数组。如果没有显式声明上界,那么默认上界就是 Object。
- D getGenericDeclaration():返回 GenericDeclaration 对象,它是此类型变量声明的。D 是 GenericDeclaration 的类型参数,可以是 Class、Interface 或 Method 等类型。
- String getName():返回类型变量的名称。这通常是在声明泛型时使用的单个大写字母,如 “E”、“T” 等。
下面是一个使用 TypeVariable 的例子:
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
public class Test {
public static void main(String[] args) {
TypeVariable<?>[] typeVars = ArrayList.class.getTypeParameters();
for (TypeVariable<?> typeVar : typeVars) {
System.out.println("Type variable name: " + typeVar.getName());
System.out.println("Generic declaration: " + typeVar.getGenericDeclaration());
System.out.println("Bounds: " + Arrays.toString(typeVar.getBounds()));
}
}
}
WildcardType
WildcardType 是 Java 反射 API 中的一个接口,它代表了通配符类型表达式,如 ?,? extends Number,? super Integer。在 Java 泛型中,通配符类型是一种特殊的类型参数,它用来表示一种未知的类型。而 WildcardType 则用来在反射中表示这种类型。WildcardType 主要有两个方法:
- Type[] getUpperBounds():返回一个 Type 数组,表示通配符类型的上界。如果没有明确指定上界(比如 ?),那么上界默认为 Object(即返回 Object.class)。
- Type[] getLowerBounds():返回一个 Type 数组,表示通配符类型的下界。如果没有明确指定下界(如 ? 或 ? extends Number),那么返回一个空数组。
下面是一个使用 WildcardType 的例子:
public class Test {
public static void main(String[] args) throws Exception {
Field field = ClassA.class.getDeclaredField("list");
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericType;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type typeArg : actualTypeArguments) {
if (typeArg instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) typeArg;
System.out.println("Upper bounds: " + Arrays.toString(wildcardType.getUpperBounds()));
System.out.println("Lower bounds: " + Arrays.toString(wildcardType.getLowerBounds()));
}
}
}
}
static class ClassA {
List<? extends Number> list;
}
}
8. 反射操作注解
反射操作注解有两个方法:
- getAnnotations()
- getAnnotation()
ORM(Object-Relational Mapping)是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。在 Java 中,ORM 技术主要用于将关系数据库和 Java 对象模型之间进行映射,从而使得我们在处理数据库时,可以使用面向对象的思想和技术,而不用去关心 SQL 语句的书写。下面是 ORM 技术的一些核心概念和功能:
- 实体(Entity):实体是数据库中的一个表在 Java 代码中的对应,通常表现为一个 Java 类。这个类的每个实例对应数据库中的一行数据。
- 映射(Mapping):映射是指如何将数据库中的表和字段与 Java 类和属性进行对应。这通常通过注解或 XML 配置文件来进行定义。
- 会话(Session):会话代表了 Java 程序和数据库之间的一次对话,是进行数据库操作的一个重要接口。在会话中,我们可以进行数据的增、删、改、查等操作。
- 事务(Transaction):事务是一系列操作的集合,这些操作要么全部成功,要么全部失败。ORM 技术通常会提供对事务的支持。
在 Java 中,最著名的 ORM 框架是 Hibernate,它提供了一套完整的 ORM 解决方案,包括数据映射、事务管理、查询语言等。另外,MyBatis 也是一个非常流行的 ORM 框架,它更倾向于 SQL 映射,提供了更强大的 SQL 支持和灵活性
下面利用注解和反射完成类和表结构的映射关系
public class Test {
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("springframework.student");
//通过反射获得注解
Annotation[] annotations = aClass.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获得注解中的值
Table annotation = aClass.getAnnotation(Table.class);
System.out.println(annotation.value());
Field name = aClass.getDeclaredField("name");
Filed annotation1 = name.getAnnotation(Filed.class);
System.out.println(annotation1.columnName());
System.out.println(annotation1.type());
System.out.println(annotation1.length());
}
}
//创建数据库表的实体类
@Table("Student")
class student {
@Filed(columnName = "db_id",type = "int",length = 10)
private int id;
@Filed(columnName = "db_age",type = "int",length = 10)
private int age;
@Filed(columnName = "db_name",type = "vachar",length = 10)
private String name;
public student(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public student() {
}
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 "student{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table{
String value();
}
//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Filed{
String columnName();
String type();
int length();
}