一、注解
1. 注解
引入:注解是从jdk5.0引入的新技术。
作用:注解可以被其他程序读取。
格式:@注解名(value=””)
使用范围:可以附加在package、class、method、field上,相当于为其添加了额外的辅助信息,可以通过反射机制编程实现对这些元数据的访问。
2. 元注解
元注解:用于注解注解的注解。
Java定义了四个标准的meta-annotation。
@Target:描述注解的使用范围,如class、method、field
@Retention:表示在什么级别保存该注解信息,用于描述注解的声明周期,如source、class、runtime
@Documented:说明该注解将被包含在javadoc中
@Inherited:说明子类可以继承父类中的该注解
3. 自定义注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnnotation {
String name();
int age() default 0;
}
二、反射
1. 反射机制概述
动态语言&静态语言
Reflection使Java具有了动态语言的特性,反射机制允许程序在执行期间借助于reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性和方法。
正常方式:import package —> 实例化 —>取得实例化对象
反射方式:实例化对象 —> getClass方法 —> 得到完整的类信息
// 通过Object类的getClass方法得到实例的runtime class。
public final native Class<?> getClass();
反射机制提供的功能
优缺点
优点:可以实现动态创建对象和编译,灵活性高
缺点:对性能有一定影响
相关API
java.lang.Class
java.lang.reflect.Method
java.lang.reflect.Field
java.lang.reflect.Constructor
2. 理解class类并获取class实例
java.lang.class类
对于每个类而言,JRE都为其保留且仅保留一个不变的class类型的对象,class对象中包含了所有信息。
class类是反射的根源,只有先获得class对象,才能做动态加载、运行等操作。
有Class对象的数据类型:class、interface、enum、annotation、array、primitive type、void
Class<Object> c1 = Object.class; //Object
Class<Class> c2 = Class.class; //Class
Class<Override> c3 = Override.class; //annotation
Class<Comparable> c4 = Comparable.class; //interface
Class<MemoryType> c5 = MemoryType.class; //enum
Class<String[]> c6 = String[].class; //array
Class<int[][]> c7 = int[][].class; //array
Class<Integer> c8 = int.class; //int
Class<Void> c9 = void.class; //void
获取Class对象的方式:
// method 1: Object.class
Class<Student> s1 = Student.class;
// method 2: Object().getClass()
Person person = new Student();
Class<? extends Person> s2 = person.getClass();
// method 3: Class.forName()
Class<?> s3 = Class.forName("com.bai.reflection.Student");
// method 4: 对于基本类型的包装类来说,通过TYPE属性获取
Class<Integer> s4 = Integer.TYPE;
3. 类的加载与ClassLoader
Java内存分析
- 堆
- 存放new的对象和数组
- 可以被所有的线程共享,不会存放别的对象引用
- 栈
- 存放基本变量类型(会存放这个基本类型的具体数值)
- 引用对象的变量(会存放这个引用在堆里面的具体地址)
- 方法区
- 类加载器ClassLoader将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象。
- 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
- 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
- 准备:为类变量(static)在方法区中分配内存并设置初始值
- 解析:将虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
初始化:
- 执行类构造器
()的过程。类构造器 ()方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句完成的。(这里的类构造器不是构造该类对象的构造器,而是用于构造类信息的) - 当初始化一个类的时候,如果其父类还没有初始化,需要先初始化其父类
- 虚拟机会保证一个类的
()方法在多线程环境中被正确加锁和同步 类加载器
类加载器的作用:将javac编译后的class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
类缓存:标准的JavaSE类加载器可以按要求查找类,但是一旦某个类被加载到类加载器中,它将缓存一段时间。不过JVM垃圾回收机制可以回收这些对象。
JVM定义了下面三种类加载器:
- 执行类构造器
bootstrap ClassLoader:引导类加载器,使用C++编写,是JVM自带的类加载器,负责加载Java平台核心库(rt.jar),该加载器无法直接获取。
- extension ClassLoader:扩展类加载器,负责加载jre/lib/ext目录下的jar包或-D java.ext.dirs指定目录下的jar包
system ClassLoader:系统加载器,负责java -classpath或-D java.class.path所指的目录下的类,是最常用的装载器
// 获取系统类的加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println("systemClassLoader = " + systemClassLoader);
// 获取系统类加载器的父类加载器 ————> 扩展类加载器
ClassLoader extensionClassLoader = systemClassLoader.getParent();
System.out.println("extendedClassLoader = " + extendedClassLoader);
// 获取扩展类加载器的父类加载器 ————> 根加载器(C++/C)
ClassLoader bootstrapClassLoader = extendedClassLoader.getParent();
System.out.println("rootClassLoader = " + rootClassLoader);
什么时候会发生类初始化(构造类信息而非创造类对象)
- 类的主动引用(一定会发生类的初始化)
- 当虚拟机启动,先初始化main方法所在的类
- new一个类的对象会初始化
- 调用类的静态成员(除了final常量)和静态方法会初始化
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类时,如果其父类没有被初始化,则会先初始化它的父类
- 类的被动引用(不会发生类的初始化)
- 当访问一个类的静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类访问父类的静态域时,父类会被初始化,子类不会被初始化。
- 使用数组定义类引用,不会触发类的初始化
- 访问类的常量不会导致类的初始化(常量在链接阶段就已经存到调用类的常量池中了)
package com.bai.reflection;
public class Test05 {
static {
System.out.println("main类初始化");
}
public static void main(String[] args) throws ClassNotFoundException {
// 当虚拟机启动时,先初始化main方法所在的类
// 主动引用(会发生类的初始化)
// 1. new新的对象:main类、father类、son类初始化
Son son = new Son();
// 2. reflection:main类、father类、son类初始化
Class<?> son2 = Class.forName("com.bai.reflection.Son");
// 3. 调用类的静态变量:main类、father类、son类初始化
System.out.println("Son.m = " + Son.sonStaticVariable);
// 4. 调用类的静态方法:main类、father类、son类初始化
Son.print();
// 被动引用(不会发生类的初始化)
// 1. 通过子类访问父类的静态域或者静态方法,子类不会被初始化,只有main类和父类会被初始化
System.out.println("Son.n = " + Son.fatherStaticVariable);
// 2. 通过数组定义类引用,不会触发类的初始化,只有main类初始化
Son[] sons = new Son[5];
// 3. 引用常量不会触发类的初初始化(常量在链接阶段就存入调用类的常量池中了),只有main类初始化
System.out.println("Son.CONSTANT = " + Son.CONSTANT);
}
}
class Father {
static {
System.out.println("父类静态代码块初始化");
}
public static int fatherStaticVariable;
static void print() {
System.out.println("父类静态方法");
}
}
class Son extends Father {
static {
System.out.println("子类静态代码块初始化");
}
public static int sonStaticVariable;
public static final double CONSTANT = Math.PI;
static void print() {
System.out.println("子类静态方法");
}
}
4. 反射用法
class类的常用方法:
Class c1 = Class.forName("com.bai.reflection.User");
System.out.println(c1.getName()); // 包名+类名
System.out.println(c1.getTypeName()); // 包名+类名
System.out.println(c1.getCanonicalName()); // 包名+类名
System.out.println(c1.getSimpleName()); // 类名
System.out.println(c1.getPackage()); // 包名
System.out.println(c1.getClassLoader()); // 类加载器
System.out.println(Arrays.toString(c1.getConstructors())); // 构造器
System.out.println(Arrays.toString(c1.getInterfaces())); // 接口
System.out.println(Arrays.toString(c1.getAnnotations())); // 注解
System.out.println(c1.getSuperclass()); //父类
System.out.println(c1.newInstance()); // 新的实例
System.out.println(Arrays.toString(c1.getFields())); // only public fields
System.out.println(Arrays.toString(c1.getDeclaredFields())); // all fields
System.out.println(c1.getField("name")); // 指定的public field
System.out.println(c1.getDeclaredField("age")); // 指定的field
System.out.println(Arrays.toString(c1.getMethods())); // 本类和父类的所有public methods
System.out.println(Arrays.toString(c1.getDeclaredMethods())); // 本类的所有methods
System.out.println(c1.getMethod("toString")); // 本类和父类指定的public method
System.out.println(c1.getDeclaredMethod("setAge", int.class)); // 本类的指定method
通过反射创建对象、操作属性和方法
// 通过Class.newInstance()创建对象
Class<?> c1 = Class.forName("com.bai.reflection.User");
User user1 = (User) c1.newInstance(); // 本质上是调用了无参构造器
System.out.println(user1);
// 通过指定构造器创建对象
Constructor<?> constructor = c1.getDeclaredConstructor(String.class, int.class);
User user2 = (User) constructor.newInstance("bai", 27);
System.out.println(user2);
// 通过反射调用普通方法,通过invoke调用方法
Method setName = c1.getDeclaredMethod("setName", String.class);
setName.invoke(user2, "slsefe");
System.out.println(user2);
// 通过反射操作属性:不能直接操作私有属性
Field age = c1.getDeclaredField("age");
age.setAccessible(true); // 关闭指定属性的安全检查
age.set(user2, 26);
System.out.println(user2);
通过反射获取泛型变量实际的数据类型
public class Test10 {
public void test1(Map<String, User> userMap, List<User> userList) {
System.out.println("test1");
}
public List<User> test2() {
System.out.println("test2");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
System.out.println("test1 method");
Method method1 = Test10.class.getDeclaredMethod("test1", Map.class, List.class); // 获取方法
Type[] genericParameterTypes = method1.getGenericParameterTypes(); // 获取泛型参数类型
for (Type type : genericParameterTypes) {
if (type instanceof ParameterizedType) {
Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments(); // 获取真实参数类型
for (Type actualType : actualTypes) {
System.out.println(actualType);
}
}
}
System.out.println("test2 method");
Method method2 = Test10.class.getDeclaredMethod("test2"); // 获取方法
Type returnType = method2.getGenericReturnType();// 获取泛型返回类型
if (returnType instanceof ParameterizedType) {
Type[] actualTypes = ((ParameterizedType) returnType).getActualTypeArguments(); // 获取真实参数类型
for (Type actualType : actualTypes) {
System.out.println(actualType);
}
}
}
}
使用反射操作注解
ORM:object relationship mapping,对象关系映射。类与表结构对应,属性与表字段对应,对象与记录对应。
public class Test {
public static void main(String[] args) throws NoSuchFieldException {
Class<User2> user = User2.class;
MyTable annotation = user.getDeclaredAnnotation(MyTable.class); // 通过反射获取指定注解
System.out.println("table name is: " + annotation.value());
Field age = user.getDeclaredField("age");
MyField ageAnnotation = age.getAnnotation(MyField.class); // 通过反射获取指定注解
System.out.println("age filed name in table is: " + ageAnnotation.name());
System.out.println("age filed type in table is: " + ageAnnotation.type());
System.out.println("age filed length in table is: " + ageAnnotation.length());
}
}
@MyTable("db_user2")
class User2 {
@MyField(name = "db_id", type = "bigint", length = 10)
private long id;
@MyField(name = "db_age", type = "int", length = 3)
private int age;
@MyField(name = "db_name", type = "varchar", length = 10)
private String name;
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyTable {
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyField {
String name();
String type();
int length();
}