本篇学习笔记总结自bilibiliup主【狂神说】系列视频:【狂神说Java】注解和反射
视频作者公众号:狂神说
Annotation
是Java5.0开始引入的新技术
Annotation
的作用:
Annotation
格式
注解是以@注解名
的方式在代码中实现的,可以添加一些参数值
如:@SuppressWarnings(value="unchecked")
在哪里使用?
package
,class
,method
,field
等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制变成实现对这些元数据的访问meta-annotation
类型
@Target
:用来描述注解的使用范围(被描述的注解可以使用在什么地方)@Retention
:表示需要在什么级别保存该注解信息,用于描述注解生命周期(Source < class < Runtime)@Documented
:说明该注解被包含在JavaDoc中@Inherited
:说明子类可以继承父类中的该注解用来生命一个注解,格式为:
@public @interface 注解名{定义内容}`Class
,String
,Enum
)default
来声明参数的默认值value
public class AnnotationTest01 {
//注解可以显示复制,如果没有默认值,就必须要赋值
@MyAnnotation( name = "Neil",schools = {"幼儿园A班", "学前班"})
public void test(){
}
//只有一个参数的时候可以省略value
@MyAnnotation1("Neil")
public void test2(){
}
}
//定义一个注解
//定义作用域只在方法上
@Target(value = {ElementType.METHOD,ElementType.TYPE})
//表示注解在什么地方还有效(一般是RUNTIME)
// RUNTIME > CLASS > SOURCE
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation{
//注解的参数:参数类型 + 参数名()
String name() default ""; //默认值为空
int age() default 0;
int id() default -1; //如果默认值为-1,代表不存在
String[] schools();
}
@Target(value = {ElementType.METHOD,ElementType.TYPE})
//表示注解在什么地方还有效(一般是RUNTIME)
// RUNTIME > CLASS > SOURCE
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation1{
String value(); //如果只有一个参数最好用value命名
}
Reflection是Java被视为动态语言的关键,允许程序在执行器借助Reflection API取得任何类的内部信息,并且能够直接操作任意对象的内部属性及方法
例如:Class c = Class.forName("java.lang.String")
加载完类之后,在堆内存的方法就产生了一个Class
类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象对象就像一面镜子,透过镜子看到类的结果,所以称之为反射:
new
进行实例话 -->取得实例化对象getClass()
方法 --> 得到完整的“包类”名称java.lang.Class
:代表一个类java.lang.reflect.Method
:代表类的方法java.lang.reflect.Field
:代表类的成员变量java.lang.reflect.Constructor
:代表类的构造器对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE都为其保留了一个不变的Class类型的对象。一个Class对象包含了特定的结构有关的信息
若已知具体的类,可以通过类的.class
属性获取,该方法安全性能最高,程序性能最强
Class clazz = Person.class;
已知某个类的实例,调用该实例的getClass()
方法获取Class对象
Class clazz = person.getClass()
已知一个类的全类名,并且该类在类路径下,可通过Class类的静态方法forName()
获取,可能抛出异常
Class clazz =. Class.forName("com.reflection.person")
内置基本类型可以直接使用类名.Type
利用ClassLoader
对于所有实例来说,只要元素类型和维度一样,就是同一个class
//所有类型的class
public class TestReflection03 {
public static void main(String[] args) {
Class c1 = Object.class; //类
Class c2 = Comparable.class; //接口
Class c3 = String[].class; //一维数组
Class c4 = int[][].class; //二维数组
Class c5 = Override.class; //注解
Class c6 = ElementType.class; //枚举
Class c7 = Integer.class; //基本数据类型
Class c8 = void.class; //void
Class c9 = Class.class; //class
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
System.out.println(c9);
}
}
结果:
class java.lang.Object
interface java.lang.Comparable
class [Ljava.lang.String;
class [[I
interface java.lang.Override
class java.lang.annotation.ElementType
class java.lang.Integer
void
class java.lang.Class
new
的对象和数组当程序主动使用某个类的时候,如果该类还没有被加载在内存中,则系统会通过如下三个步骤来对该类进行初始化
java.lang.Class
对象。此过程由类加载器(ClassLoader)完成加载:将.class
文件字节码内容加载到内存中,并将这些静态数据转换成方法区运行时的数据结构,然后生成一个代表这个类的java.lang.Class
对象
链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
初始化:
()
方法的过程。类构造器()
方法是由编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)()
方法在多线程环境中被正确的加锁和同步案例:
public class TestReflection04 {
public static void main(String[] args) {
A a = new A();
System.out.println(A.m);
/**
* 1、加载到内存,会产生一个类对应的Class对象(从方法区到堆)
* 2、链接,链接结束后 m = 0 (从堆到栈)
* 3、初始化化 (从堆到栈)
* (){
* System.out.println("a类静态代码块初始化");
* m = 300;
* int m = 100;
* }
* m = 100
*/
}
}
class A{
//首先初始化了静态代码块 m = 300
//之后初始化了 m = 100
static {
System.out.println("a类静态代码块初始化");
m = 300;
}
static int m = 100;
public A() {
System.out.println("A类的与参构造初始化");
}
}
结果:
a类静态代码块初始化
A类的与参构造初始化
100
类的主动引用(一定会发生类的初始化)
main
方法所在的类new
一个类的对象final
常量)和静态方法java.lang.reflect
包的方法对类进行反射调用类的被动引用(不会发生类的初始化)
案例:
//类什么时候会被初始化
public class TestReflection06 {
static{
System.out.println("Main类被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
//1、主动引用:new一个对象(会发生类的初始化)
Son son = new Son();
//2、主动引用:反射 (会发生类的初始化)
Class c1 = Class.forName("com.reflection.Son");
//不会产生类的引用的方法,因为是子类调用父类的static常量
//在这里父类会被初始化,但是子类不会被初始化
System.out.println(Son.b);
//数组只开辟了空间,也被命名了,但是没有加载任何类,不会发生类的初始化
Son[] array = new Son[5];
//常量,静态变量在链接阶段就被赋值并且调用到了常量池中了,所以调用时不会发生类的初始化
System.out.println(Son.M);
}
}
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 M = 1;
}
将class文件字节码内容加载到内存中,并将这些静态数据转化成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class
对象,作为方法区中类数据的访问入口
类加载器包含:
jre/lib/ext
目录下的jar
包或-D java.ext.dirs
指定目录下的jar
包装入工作库java -classpath
或 -D java.class.path
所指的目录下的类与jar
包装入工作,是最常用的加载器流程如下:
类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象
案例:
public class TestReflection07 {
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类加载器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
System.out.println(classLoader);
//获取系统类加载器的父类加载器 --> 扩展类加载器
ClassLoader parent = classLoader.getParent();
System.out.println(parent);
//无法扩展类加载器的父类 --> 引导类加载器
//测试当前类是哪个加载器加载的(系统类加载器,也就是App加载器)
ClassLoader classLoader1 = Class.forName("com.reflection.TestReflection07").getClassLoader();
System.out.println(classLoader1);
//测试Object类是哪个加载器加载的(引导类加载器)
classLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader);
//如何获得系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));
//双亲委派机制
//自定义一个包java.lang.string --> 先推系统类加载器是否有这个包,再往上推扩展加载器有没有这个包,最后是引导类加载器—
//如果发现了同样名字的包,就不会使用自定义的包
}
}
结果:
jdk.internal.loader.ClassLoaders$AppClassLoader@3d4eac69
jdk.internal.loader.ClassLoaders$PlatformClassLoader@77459877
jdk.internal.loader.ClassLoaders$AppClassLoader@3d4eac69
null
/Users/kuifengyuan/Desktop/JavaAnnotation/out/production/JavaAnnotation
//获得类的信息
public class TestReflection08 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
//此处在com.reflection下面有一个User类,获取Class
Class<?> c1 = Class.forName("com.reflection.User");
//也可以用实例去获取Class
User user = new User();
c1 = user.getClass();
//获取类的名字(包名 + 类名)
System.out.println(c1.getName());
//获得简单名字 (类名)
System.out.println(c1.getSimpleName());
//获得类的属性
System.out.println("================");
Field[] fields = c1.getFields(); //只能找到public 属性
fields = c1.getDeclaredFields(); //可以找到全部的属性
for (Field field : fields) {
System.out.println(field);
}
//获得指定属性的值
Field name = c1.getDeclaredField("name");
System.out.println(name);
//获得类的方法,可以获得本类和父类的全部方法(不包括私有方法)
System.out.println("================");
Method[] methods = c1.getMethods();
for (Method method : methods) {
System.out.println("正常的 " + method);
}
//获得本类的所有方法(包括私有方法)
methods = c1.getDeclaredMethods();
for (Method method : methods) {
System.out.println("declaredMethods " + method);
}
//第一个为想要获取的方法名,后面为参数类型
//重载
System.out.println("================");
Method getName = c1.getMethod("getName", null);
Method setName = c1.getMethod("setName", String.class);
System.out.println(getName);
System.out.println(setName);
//获得的构造器(public构造器)
System.out.println("================");
Constructor<?>[] constructors = c1.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
//获得全部的构造器
Constructor<?>[] declaredConstructors = c1.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
}
}
小结:
java.lang.reflect
包的作用,反射机制创建类的对象:调用Class
对象的newInstance()
方法
类必须有一个无参数构造器
类的构造器的访问权限需要足够
如果没有无参数构造器,需要明确调用类中的构造器并且传递对应的参数进行实例化
步骤如下:
Class
类的getDeclaredConstructor(Class ...parameterTypes)
获得本类的指定形参类型的构造器调用指定的方法
通过反射,调用类中的方法,通过Method
类完成
Class
类的getMethod(String name,Class ...parameterTypes)
方法获取一个Method对象,并且设置此方法操作时所需要的参数类型。Object invoke(Object ojb, Object[] args)
进行调用,并向该方法中传递要设置的obj对象的参数信息案例:
//动态创建对象
public class TestReflection09 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//获得Class对象
Class c1 = Class.forName("com.reflection.User");
//构造一个对象
User user = (User)c1.getDeclaredConstructor().newInstance();
//输出结构都为null,调用无参构造器
System.out.println(user);
//通过构造器创建对象
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
User user2 = (User) declaredConstructor.newInstance("Neil",200,333);
System.out.println(user2);
//通过反射调用普通方法
User user3 = (User)c1.newInstance();
Method setName = c1.getDeclaredMethod("setName", String.class);
//invoke()激活这个方法,第一个参数为调用的实例,第二个为想调用方法传入的参数
setName.invoke(user3,"Neil");
System.out.println(user3.getName());
//通过反射操作属性(不可直接操作私有属性)
User user4 = (User)c1.getDeclaredConstructor().newInstance();
Field name = c1.getDeclaredField("name");
//由于权限不够(name属性为private),需要设置安全检测改为可以访问
//关闭权限检查可以提高程序性能
name.setAccessible(true);
name.set(user4,"Neilllll");
System.out.println(user4.getName());
}
}
结果:
User{name='null', id=0, age=0}
User{name='Neil', id=200, age=333}
Neil
Neilllll
Java采用泛型擦出机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免除强制类型转换问题,但是一旦编译完成,所有和泛型相关的类型全部擦除
为了通过反射操作这些类型,Java新增了ParameterizedType
,GenericArrayType
,TypeVariable
和WildcardType
几种类型来代表不能归一到Class类的中的类型但是又和原始类型齐名的类型
ParameterizedType
:表示一种参数化的泛型,比如Collection
GenericArrayType
:表示一种元素类型是参数化类型活着类型变量的数组类型TypeVariable
:是各种类型变量的公共父接口WildcardType
:代表一种通配符类型表达式案例:
//通过反射获取泛型
public class TestReflection10 {
public void test01(Map<String,User> map, List<User> list){
System.out.println("test01");
}
public Map<String,User> test02(){
System.out.println("test02");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Method method = TestReflection10.class.getMethod("test01", Map.class, List.class);
//获得参数重泛型信息
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println(genericParameterType);
System.out.println("=============");
//获得真实的泛型信息
if(genericParameterType instanceof ParameterizedType){
for (Type actualTypeArgument : ((ParameterizedType) genericParameterType).getActualTypeArguments()) {
System.out.println(actualTypeArgument);
}
}
}
//获得返回值泛型信息
System.out.println("--------------------------" );
Method method2 = TestReflection10.class.getMethod("test02");
Type genericReturnType = method2.getGenericReturnType();
if(genericReturnType instanceof ParameterizedType){
for (Type actualTypeArgument : ((ParameterizedType) genericReturnType).getActualTypeArguments()) {
System.out.println(actualTypeArgument);
}
}
}
}
练习:ORM
什么叫ORM?
Object relationship Mapping --> 对象关系映射
如:
class Student{
int id;
String name;
int age;
};
映射到表中:
id | name | age |
---|---|---|
001 | Neil | 24 |
002 | 张三 | 55 |
类和表结构对应
属性和字段对应
对象和记录对应
要求利用注解和反射完成类和表结构的映射关系
案例:
//练习反射操作注解
public class TestReflection11 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("com.reflection.Student2");
Annotation[] annotations = c1.getAnnotations();
//获得注解
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获得注解Value值
TableNeil tableNeil = (TableNeil)c1.getAnnotation(TableNeil.class);
System.out.println(tableNeil.value());
//获得类指定的注解和值
FieldNeil name = c1.getDeclaredField("id").getAnnotation(FieldNeil.class);
System.out.println(name);
System.out.println(name.colName());
System.out.println(name.length());
System.out.println(name.type());
}
}
@TableNeil("db_student2")
class Student2 {
//通过注解可以操作表
@FieldNeil(colName = "db_id",type = "int",length = 10)
private int id;
@FieldNeil(colName = "db_age",type = "int",length = 10)
private int age;
@FieldNeil(colName = "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;
}
@Override
public String toString() {
return "Student2{" +
"id=" + id +
", age=" + age +
", 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;
}
}
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableNeil{
String value();
}
//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldNeil{
String colName();
String type();
int length();
}
输出结果:
@com.reflection.TableNeil("db_student2")
db_student2
@com.reflection.FieldNeil(colName="db_id", type="int", length=10)
db_id
10
int
参考文献:本篇学习笔记主要总结于bilibili up主 “狂神学Java” 的系列视频— 【狂神说Java】注解与反射