注解和反射
1.什么是注解
注解 (英文名Annotation)
是从JDK5.0开始引入的一个新的技术.
注解的作用:
不是程序本身,可以对程序作出解释
可以被其他程序读取(比如:编辑器)
注解的格式:
注解是以"@注解名"在代码中存在的,还可以添加一些参数值
例如;`@SuppressWarnings(value="unchecked")
注解适用于哪里?
可以附加在package , class, method , field等上面,相当于给他们添加了额外的辅助信息
我们可以通过反射机制编程实现对这些元数据的访问.
2. 内置注解
-
@Override
注解定义在java.lang.Override 中, 此注解用于修辞方法, 表示一个方法声明打算重写超类(父类)中的方法 -
@Deprecated
注解定义在java.lang.Deprecated 中, 此注解用于修辞方法, 属性, 类. 表示不鼓励程序员去使用, 通常是因为它可能存在危险或者存在更好的选择 -
@SuppressWarnings
定义在java.lang.SuppressWarnings 中, 用来抑制编译时的警告信息
3. 元注解
元注解的作用就是负责注释其他注解,java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型作说明.
//测试元注解
public class Test02 {
public void test(){
}
}
//定义一个注解
//Target表示我们的注解可以用在哪些地方
value={ElementType.METHOD,ElementType.TYPE}) (
//Retention表示我们的注解在什么地方还有效
//runntime > class > sources
value = RetentionPolicy.RUNTIME) (
//Documented表示我们的注解是否生成在doc中
//Inherited 子类可以继承父类的注解
@interface Liq{
}
4. 自定义注解
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
格式:
public @interface 注解名{
定义内容
}
4.1 注解的参数
自定义注解时,可以设置参数. 参数格式为 参数类型 参数名();
例如: String name();
在使用时如果没有默认值,则必须传递参数
// 自定义注解
ElementType.TYPE,ElementType.METHOD}) ({
RetentionPolicy.RUNTIME) (
@interface liq{
// 注解的参数: 参数类型 + 参数名();
String name();
}
//使用注解
public class test{
name = "liq") (
public void test(){}
}
4.2 注解的默认值
如果注解已经自定义了默认值, 参数可以不用写.如下:
//自定义注解
ElementType.TYPE,ElementType.METHOD}) ({
RetentionPolicy.RUNTIME) (
@interface liq{
//注解的参数:参数类型+参数名(); default为默认值
String name() default "";
int age() default 0;
int id() default -1;//如果默认值为-1,代表不存在,
String[] school() default { "河工大","北大"};
}
// 使用注解
public class Test03 {
// 因为注解里面 已经 有了默认值 default 0; 所以age的属性可写可不写
//注解可以显示赋值,如果没有默认值,我们就必须给注解赋值
name = "李强",school = "河工大") (
public void test(){ }
4.3 当注解只有一个参数时
如果说注解里只有一个值,建议使用value做参数名, 因为使用value做参数名, 写参数的时候就可以省略参数名
例如:
// 定义注解
ElementType.TYPE,ElementType.METHOD}) ({
RetentionPolicy.RUNTIME) (
@interface liq2{
String value();
}
//使用注解
public class test(){
"李强") (
public void test1(){ }
}
反射
1. 反射概述
Reflection
(反射) 是java被视为动态语言的关键, 反射机制允许程序在执行时期间借助Reflection
的API获取任何类的内部信息, 并直接操作任意对象的内部属性及方法. 例如:
Class c = Class.forName("java.lang.String");
加载完类之后, 在堆内存的方法去就产生了一个class类型的对象(一个类只有一个Class对象, 这个对象就包含了完整的类的结构信息, 我们可以通过这个对象看到类的结构. 这个对象就像一面镜子, 透过这个镜子,可以看待类的结构,所以我们形象的称之为: 反射
1.1 Java反射机制提供的功能
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时获取泛型信息
在运行时调用任意一个对象的成员变量和方法
在运行时处理注解
生成动态代理
2. 获得反射对象
package com.tedu.reflection;
//什么叫反射
public class Test1 extends Object{
public static void main(String[] args) throws ClassNotFoundException {
//通过反射获取类的Class对象
Class c1 = Class.forName("com.tedu.reflection.User");
System.out.println(c1);//class com.tedu.reflection.User
Class c2 = Class.forName("com.tedu.reflection.User");
Class c3 = Class.forName("com.tedu.reflection.User");
Class c4 = Class.forName("com.tedu.reflection.User");
//一个类在内存中只有一个Class对象
//一个类被加载后,类的整个结构都会被封装在Class对象中
System.out.println(c2.hashCode());//460141958
System.out.println(c3.hashCode());//460141958
System.out.println(c4.hashCode());//460141958
}
}
//实体类 :pojo
class User{
private String name;
private int id ;
private int age;
//... 省略有参无参构造方法,get-set方法,toString方法
}
3. Class类获取对象的几种方式
3.1 对象照镜子后可以获得那些信息?
-
某个类的属性
-
方法和构造器
-
某个类实现了那些接口等
对于每个类而言,JRE都为其保留了一个不变的Class对象.一个对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息
方法名 | 功能说明 |
---|---|
static ClassforName(String name) | 返回指定类名的Class对象 |
Obiect ne'wInstance() | 调用缺省构造函数, 返回Class对象的一个实例 |
getName() | 返回此Class对象所表示的实体 (类,接口,数组类或void) 的名称 |
Class getSupperClass() | 返回当前对象的父类Class对象 |
Class[] getInterfaces() | 返回当前类的所有接口 |
ClassLoader getClassLoader() | 返回该类的加载器 |
Constructor[] getConstructor() | 返回一个包含Constructor对象的数组 |
Method getMethod(String name,Class.. T) | 返回一个Method对象, 此对象的形参类型为paramType |
Field[] getDelaredFielda() | 返回Field对象的一个数组 |
3.2 获取Class实例 (5种)
-
若已知具体类, 通过类的class属性可以获取,这种方法最安全最可靠,程序性能最高
Class clazz = Person.class;
-
已知某个类的实例可以调用实力的
getClass()
方法获取Class clazz = person.getClass();
-
已知一个类的全名 ,且在该类路径下,可通过Class类的静态方法,forName()获取,但是可能抛出ClassNotFoundException
Class clazz = Class.forName("com.tedu.reflection.Student");
-
8种基本类型的封装类都有一个Type属性
Class clazz = Integer.Type;
-
通过子类获取
// 例如Student对象继承了Person对象
//所以可以使用Student对象获取父类Person的Class对象
Class clazz = Student.class.getSuperClass();
4. 那些类型可以有Class对象
注意: 数组在同一维度的才是同一个Class对象
-
class: 外部类, 成员(成员内部类,静态内部类), 局部内部类, 匿名内部类
-
interface: 接口
-
[]: 数组
-
enum: 枚举
-
annotation: 注解 @interface
-
primitive type: 基本数据类型
-
void
//所有类型的class
public class Test3 {
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
//只要元素类型和维度一样,就是同一个Class
int[] a = new int[10];
int[] a1 = new int[100];
System.out.println(a.getClass().hashCode());
System.out.println(a1.getClass().hashCode());
}
}
5. 类的加载与内存分析
5.1 类的加载分为三部分
(加载,链接,初始化)
-
-加载: 将class字节码内容加载到内存中, 并将这些静态数据转换成方法运行时的数据结构,然后生成一个代表这个类的数据结构,然后生成一个java.lang.Class对象.
-
链接: 将java类的二进制代码合并到JVM的运行状态之中的过程.
-
验证: 确保加载类的信息符合JVM的规范, 没有安全方面的问题
-
准备; 正式为变量 (static) 分配内存并设置默认初始值的阶段, 这些内存将在方法区进行分配
-
解析: 虚拟机常量池的符号引用, (常量名) 替换为直接引用的( 地址) 的过程
-
-
初始化
-
执行类构造器的<clinit>() 方法的过程. 类构造器<clinit>()方法由编译期间自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生的.(类构造器是构造类的信息, 不是构造该类对象的构造器)
-
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发父类的初始化.
-
虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步.
-
5.2 代码示例:
public class Test4 {
public static void main(String[] args) {
A a = new A();
System.out.println(A.m);
}
}
/*
* 1.加载到内存,会产生一个类对应Class对象
* 2.链接, 链接结束后m获得一个默认值 m = 0;
* 3.初始化
* <clinit>(){
* System.sout.println("A类的静态代码初始化");
* m = 300;
* m = 100;
* }
* 最终结果: m = 100;
*/
class A{
static {
System.out.println("A类的静态代码初始化");
m = 300;
}
static int m = 100;
public A(){
System.out.println("A类的无参构造方法");
}
}
代码运行结果:
A类的静态代码初始化
A类的无参构造方法
100
6. 类加载器
6.1 什么时候会发生类初始化
-
类的主动引用 (一定会发生类的初始化)
-
当虚拟机启动时,先初始化main方法所在的类
-
new 一个类的对象
-
调用类的静态成员(除了final常量)和静态方法
-
使用java.lang.reflect包的方法对类进行反射调用
-
当初始化一个类, 如果其父类没有被初始化,则会先初始化其父类
-
-
类的被动调用 (不会发生类的初始化)
-
当访问一个静态域时,只有真正声明这个域的类才会被初始化,如: 当通过子类引用父类的静态变量,不会导致子类被初始化
-
通过数组定义类引用,不会触发此类的初始化
-
引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
-
6.2 类加载器的作用
-
类加载的作用: 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然会在堆中生成一个代表这个类的java.lang.Class对象,作为此方法区中类数据的访问入口,类加载器是用来把类(class)装载进内存的,JVM定义了如下类型加加载器
-
引导类加载器: 用c++编写的,是JVM自带的类加载器,负责JVM平台的核心库(java核心库: rt.jar),用来装在核心类库,该加载器无法直接获取
-
扩展类加载器: 负责
jre/lib/ext
目录下的jar包或 -D java.ext.dirs 指定目录下的jar包装入工作库 -
系统类加载器: 负责java -classpath 或 -D java.class.path所指的目录下的类与jar包,装入工作库, 是最常用的加载器
检查类是否已经装在顺序:
系统->扩展->引导
尝试加载类顺序:
引导->扩展->系统
-
-
类缓存: 标砖的javaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间. 不过JVM垃圾回收机制可以回收这些Class对象
代码示例 (引导类加载器(核心类库)使用c/c++所编写,输出为null)
package com.tedu.reflection;
public class Test6 {
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类加载器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
System.out.println(classLoader);
//获取系统类加载器的父类加载器-->扩展类加载器
ClassLoader parent = classLoader.getParent();
System.out.println(parent);
//获取扩展类加载器的父类加载器-->根加载器(c/c++)
ClassLoader parent1 =parent.getParent();
System.out.println(parent1);
//测试当前类是哪个加载器加载的
ClassLoader classLoader1 = Class.forName("com.tedu.reflection.Test6").getClassLoader();
System.out.println(classLoader1);
//测试JDK内置的类是谁加载的
classLoader1 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader1);
//如何获取系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));
//双亲委派机制
/*
C:\Program Files\Java\jdk1.8.0_251\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\deploy.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\access-bridge-64.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\cldrdata.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\dnsns.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jaccess.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jfxrt.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\localedata.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\nashorn.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunec.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunjce_provider.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunmscapi.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunpkcs11.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\zipfs.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\javaws.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfxswt.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\management-agent.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\plugin.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\rt.jar;
D:\tedu\workspace2\注解和反射\out\production\注解和反射;
C:\Program Files\ideaIU-2020.1\lib\idea_rt.jar
*/
}
}
7. 获取类运行时的结构
代码演示:
//获取类的信息
public class Test7 {
public static void main(String[] args) throws Exception {
Class c1 = Class.forName("com.tedu.reflection.User");
//获得类的名字
System.out.println(c1.getName());//获得包名+类名
System.out.println(c1.getSimpleName());//获得类名
//获得类的属性
Field[] fields = c1.getFields();//只能找到public属性
fields = c1.getDeclaredFields();//找到全部属性
for (Field f :fields
) {
System.out.println(f);
}
//获得指定属性的值
Field name = c1.getDeclaredField("name");
System.out.println(name);
//获得类的方法
System.out.println("==========");
Method[] methods = c1.getMethods();//获得本类以及父类的全部public方法
for (Method m:methods
) {
System.out.println("正常的:"+m);
}
methods = c1.getDeclaredMethods();//获得本类的全部方法
for (Method m1:methods
) {
System.out.println("getDeclaredMethods:"+m1);
}
//获得指定的方法
//重载的原因,所以需要参数null 或者其他类型
Method getName = c1.getMethod("getName", null);
Method setName = c1.getMethod("setName", String.class);
System.out.println(getName);
System.out.println(setName);
//获得构造器
System.out.println("=============");
Constructor[] constructors = c1.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
constructors = c1.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println("##"+constructor);
}
//获得指定的构造器
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
System.out.println("指定"+declaredConstructor);
}
}
8. 动态创建对象的执行方法
实例代码:
//动态的创建对象,通过反射
public class Test8 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//获得Class对象
Class c1 = Class.forName("com.tedu.reflection.User");
//构造一个对象
// User user = (User) c1.newInstance();//本质调用了一个无参构造器
//通过构造器创建对象
// Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
// User user1 = (User) constructor.newInstance("李强", 002, 18);
// System.out.println(user1);
//通过反射调用普通方法
User user2 = (User) c1.newInstance();
//通过反射获取一个方法
Method setName = c1.getDeclaredMethod("setName", String.class);
//invoke:激活的意思
//(对象,"方法的值")
setName.invoke(user2,"强哥");
System.out.println(user2.getName());
System.out.println("=================");
//通过反射操作属性
User user3 = (User) c1.newInstance();
Field name = c1.getDeclaredField("name");
//不能直接操作私有属性,我们需要关闭程序的安全检测,
name.setAccessible(true);
name.set(user3,"李强1");
System.out.println(user3.getName());
}
}
9. 创建对象方式的性能问题
以执行一个对象的方法十亿次的时间差对比结果
-
1, 普通方式 new 一个对象最快 -> 7ms
-
2.反射方式关闭安全检测速度一般 -> 1414ms (name.setAccessible(true);)
-
3.反射不关闭安全检测速度最慢 -> 3763ms
10. 反射操作泛型
➢Java采用泛型擦除的机制来弓|入泛型, Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,但是,-旦编译完成,所有和泛型有关的类型全部擦除
➢为了通过反射操作这些类型, Java新增了ParameterizedTyp,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是 又和原始类型齐名的类型.
➢ParameterizedType :表示一种参数化类型,比如Collection<String>
➢GenericArrayType :表示一-种元素类型是参数化类型或者类型变量的数组类型
➢TypeVariable :是各种类型变量的公共父接口
➢WildcardType :代表一种通配符类型表达式
实例代码:
//通过反射获取泛型
public class Test10 {
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 = Test10.class.getMethod("test01", Map.class, List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("#"+genericParameterType);
if (genericParameterType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("actual::== "+actualTypeArgument);
}
}
}
Method method1 = Test10.class.getMethod("test02", null);
Type genericReturnType = method1.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("a::== "+actualTypeArgument);
}
}
}
}
#java.util.Map<java.lang.String, com.tedu.reflection.User>
actual::== class java.lang.String
actual::== class com.tedu.reflection.User
#java.util.List<com.tedu.reflection.User>
actual::== class com.tedu.reflection.User
a::== class java.lang.String
a::== class com.tedu.reflection.User
11. 练习反射操作注解
//练习反射操作注解
public class Test11 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("com.tedu.reflection.Stu");
//通过反射获得注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获得注解的value值
Tableli tableli = (Tableli) c1.getAnnotation(Tableli.class);
String value = tableli.value();
System.out.println(value);
//获得类指定的注解
Field f = c1.getDeclaredField("id");
Fieldli annotation = f.getAnnotation(Fieldli.class);
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}
"db_student") (
class Stu{
columnName = "db_id",type = "int",length = 11) (
private int id;
columnName = "db_age",type = "int",length = 11) (
private int age;
columnName = "db_name",type = "varchar",length = 20) (
private String name;
public Stu(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public Stu() {
}
public String toString() {
return "Stu{" +
"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;
}
}
//类名的注解
ElementType.TYPE) (
RetentionPolicy.RUNTIME) (
@interface Tableli{
String value();
}
//属性的注解
ElementType.FIELD) (
RetentionPolicy.RUNTIME) (
@interface Fieldli{
String columnName();
String type();
int length();
}
1.什么是注解
注解 (英文名Annotation)
是从JDK5.0开始引入的一个新的技术.
注解的作用:
不是程序本身,可以对程序作出解释
可以被其他程序读取(比如:编辑器)
注解的格式:
注解是以"@注解名"在代码中存在的,还可以添加一些参数值
例如;`@SuppressWarnings(value="unchecked")
注解适用于哪里?
可以附加在package , class, method , field等上面,相当于给他们添加了额外的辅助信息
我们可以通过反射机制编程实现对这些元数据的访问.
2. 内置注解
-
@Override
注解定义在java.lang.Override 中, 此注解用于修辞方法, 表示一个方法声明打算重写超类(父类)中的方法 -
@Deprecated
注解定义在java.lang.Deprecated 中, 此注解用于修辞方法, 属性, 类. 表示不鼓励程序员去使用, 通常是因为它可能存在危险或者存在更好的选择 -
@SuppressWarnings
定义在java.lang.SuppressWarnings 中, 用来抑制编译时的警告信息
3. 元注解
元注解的作用就是负责注释其他注解,java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型作说明.
//测试元注解
public class Test02 {
public void test(){
}
}
//定义一个注解
//Target表示我们的注解可以用在哪些地方
value={ElementType.METHOD,ElementType.TYPE}) (
//Retention表示我们的注解在什么地方还有效
//runntime > class > sources
value = RetentionPolicy.RUNTIME) (
//Documented表示我们的注解是否生成在doc中
//Inherited 子类可以继承父类的注解
@interface Liq{
}
4. 自定义注解
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
格式:
public @interface 注解名{
定义内容
}
4.1 注解的参数
自定义注解时,可以设置参数. 参数格式为 参数类型 参数名();
例如: String name();
在使用时如果没有默认值,则必须传递参数
// 自定义注解
ElementType.TYPE,ElementType.METHOD}) ({
RetentionPolicy.RUNTIME) (
@interface liq{
// 注解的参数: 参数类型 + 参数名();
String name();
}
//使用注解
public class test{
name = "liq") (
public void test(){}
}
4.2 注解的默认值
如果注解已经自定义了默认值, 参数可以不用写.如下:
//自定义注解
ElementType.TYPE,ElementType.METHOD}) ({
RetentionPolicy.RUNTIME) (
@interface liq{
//注解的参数:参数类型+参数名(); default为默认值
String name() default "";
int age() default 0;
int id() default -1;//如果默认值为-1,代表不存在,
String[] school() default { "河工大","北大"};
}
// 使用注解
public class Test03 {
// 因为注解里面 已经 有了默认值 default 0; 所以age的属性可写可不写
//注解可以显示赋值,如果没有默认值,我们就必须给注解赋值
name = "李强",school = "河工大") (
public void test(){ }
4.3 当注解只有一个参数时
如果说注解里只有一个值,建议使用value做参数名, 因为使用value做参数名, 写参数的时候就可以省略参数名
例如:
// 定义注解
ElementType.TYPE,ElementType.METHOD}) ({
RetentionPolicy.RUNTIME) (
@interface liq2{
String value();
}
//使用注解
public class test(){
"李强") (
public void test1(){ }
}
反射
1. 反射概述
Reflection
(反射) 是java被视为动态语言的关键, 反射机制允许程序在执行时期间借助Reflection
的API获取任何类的内部信息, 并直接操作任意对象的内部属性及方法. 例如:
Class c = Class.forName("java.lang.String");
加载完类之后, 在堆内存的方法去就产生了一个class类型的对象(一个类只有一个Class对象, 这个对象就包含了完整的类的结构信息, 我们可以通过这个对象看到类的结构. 这个对象就像一面镜子, 透过这个镜子,可以看待类的结构,所以我们形象的称之为: 反射
1.1 Java反射机制提供的功能
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时获取泛型信息
在运行时调用任意一个对象的成员变量和方法
在运行时处理注解
生成动态代理
2. 获得反射对象
package com.tedu.reflection;
//什么叫反射
public class Test1 extends Object{
public static void main(String[] args) throws ClassNotFoundException {
//通过反射获取类的Class对象
Class c1 = Class.forName("com.tedu.reflection.User");
System.out.println(c1);//class com.tedu.reflection.User
Class c2 = Class.forName("com.tedu.reflection.User");
Class c3 = Class.forName("com.tedu.reflection.User");
Class c4 = Class.forName("com.tedu.reflection.User");
//一个类在内存中只有一个Class对象
//一个类被加载后,类的整个结构都会被封装在Class对象中
System.out.println(c2.hashCode());//460141958
System.out.println(c3.hashCode());//460141958
System.out.println(c4.hashCode());//460141958
}
}
//实体类 :pojo
class User{
private String name;
private int id ;
private int age;
//... 省略有参无参构造方法,get-set方法,toString方法
}
3. Class类获取对象的几种方式
3.1 对象照镜子后可以获得那些信息?
-
某个类的属性
-
方法和构造器
-
某个类实现了那些接口等
对于每个类而言,JRE都为其保留了一个不变的Class对象.一个对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息
方法名 | 功能说明 |
---|---|
static ClassforName(String name) | 返回指定类名的Class对象 |
Obiect ne'wInstance() | 调用缺省构造函数, 返回Class对象的一个实例 |
getName() | 返回此Class对象所表示的实体 (类,接口,数组类或void) 的名称 |
Class getSupperClass() | 返回当前对象的父类Class对象 |
Class[] getInterfaces() | 返回当前类的所有接口 |
ClassLoader getClassLoader() | 返回该类的加载器 |
Constructor[] getConstructor() | 返回一个包含Constructor对象的数组 |
Method getMethod(String name,Class.. T) | 返回一个Method对象, 此对象的形参类型为paramType |
Field[] getDelaredFielda() | 返回Field对象的一个数组 |
3.2 获取Class实例 (5种)
-
若已知具体类, 通过类的class属性可以获取,这种方法最安全最可靠,程序性能最高
Class clazz = Person.class;
-
已知某个类的实例可以调用实力的
getClass()
方法获取Class clazz = person.getClass();
-
已知一个类的全名 ,且在该类路径下,可通过Class类的静态方法,forName()获取,但是可能抛出ClassNotFoundException
Class clazz = Class.forName("com.tedu.reflection.Student");
-
8种基本类型的封装类都有一个Type属性
Class clazz = Integer.Type;
-
通过子类获取
// 例如Student对象继承了Person对象
//所以可以使用Student对象获取父类Person的Class对象
Class clazz = Student.class.getSuperClass();
4. 那些类型可以有Class对象
注意: 数组在同一维度的才是同一个Class对象
-
class: 外部类, 成员(成员内部类,静态内部类), 局部内部类, 匿名内部类
-
interface: 接口
-
[]: 数组
-
enum: 枚举
-
annotation: 注解 @interface
-
primitive type: 基本数据类型
-
void
//所有类型的class
public class Test3 {
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
//只要元素类型和维度一样,就是同一个Class
int[] a = new int[10];
int[] a1 = new int[100];
System.out.println(a.getClass().hashCode());
System.out.println(a1.getClass().hashCode());
}
}
5. 类的加载与内存分析
5.1 类的加载分为三部分
(加载,链接,初始化)
-
-加载: 将class字节码内容加载到内存中, 并将这些静态数据转换成方法运行时的数据结构,然后生成一个代表这个类的数据结构,然后生成一个java.lang.Class对象.
-
链接: 将java类的二进制代码合并到JVM的运行状态之中的过程.
-
验证: 确保加载类的信息符合JVM的规范, 没有安全方面的问题
-
准备; 正式为变量 (static) 分配内存并设置默认初始值的阶段, 这些内存将在方法区进行分配
-
解析: 虚拟机常量池的符号引用, (常量名) 替换为直接引用的( 地址) 的过程
-
-
初始化
-
执行类构造器的<clinit>() 方法的过程. 类构造器<clinit>()方法由编译期间自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生的.(类构造器是构造类的信息, 不是构造该类对象的构造器)
-
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发父类的初始化.
-
虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步.
-
5.2 代码示例:
public class Test4 {
public static void main(String[] args) {
A a = new A();
System.out.println(A.m);
}
}
/*
* 1.加载到内存,会产生一个类对应Class对象
* 2.链接, 链接结束后m获得一个默认值 m = 0;
* 3.初始化
* <clinit>(){
* System.sout.println("A类的静态代码初始化");
* m = 300;
* m = 100;
* }
* 最终结果: m = 100;
*/
class A{
static {
System.out.println("A类的静态代码初始化");
m = 300;
}
static int m = 100;
public A(){
System.out.println("A类的无参构造方法");
}
}
代码运行结果:
A类的静态代码初始化
A类的无参构造方法
100
6. 类加载器
6.1 什么时候会发生类初始化
-
类的主动引用 (一定会发生类的初始化)
-
当虚拟机启动时,先初始化main方法所在的类
-
new 一个类的对象
-
调用类的静态成员(除了final常量)和静态方法
-
使用java.lang.reflect包的方法对类进行反射调用
-
当初始化一个类, 如果其父类没有被初始化,则会先初始化其父类
-
-
类的被动调用 (不会发生类的初始化)
-
当访问一个静态域时,只有真正声明这个域的类才会被初始化,如: 当通过子类引用父类的静态变量,不会导致子类被初始化
-
通过数组定义类引用,不会触发此类的初始化
-
引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
-
6.2 类加载器的作用
-
类加载的作用: 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然会在堆中生成一个代表这个类的java.lang.Class对象,作为此方法区中类数据的访问入口,类加载器是用来把类(class)装载进内存的,JVM定义了如下类型加加载器
-
引导类加载器: 用c++编写的,是JVM自带的类加载器,负责JVM平台的核心库(java核心库: rt.jar),用来装在核心类库,该加载器无法直接获取
-
扩展类加载器: 负责
jre/lib/ext
目录下的jar包或 -D java.ext.dirs 指定目录下的jar包装入工作库 -
系统类加载器: 负责java -classpath 或 -D java.class.path所指的目录下的类与jar包,装入工作库, 是最常用的加载器
检查类是否已经装在顺序:
系统->扩展->引导
尝试加载类顺序:
引导->扩展->系统
-
-
类缓存: 标砖的javaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间. 不过JVM垃圾回收机制可以回收这些Class对象
代码示例 (引导类加载器(核心类库)使用c/c++所编写,输出为null)
package com.tedu.reflection;
public class Test6 {
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类加载器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
System.out.println(classLoader);
//获取系统类加载器的父类加载器-->扩展类加载器
ClassLoader parent = classLoader.getParent();
System.out.println(parent);
//获取扩展类加载器的父类加载器-->根加载器(c/c++)
ClassLoader parent1 =parent.getParent();
System.out.println(parent1);
//测试当前类是哪个加载器加载的
ClassLoader classLoader1 = Class.forName("com.tedu.reflection.Test6").getClassLoader();
System.out.println(classLoader1);
//测试JDK内置的类是谁加载的
classLoader1 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader1);
//如何获取系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));
//双亲委派机制
/*
C:\Program Files\Java\jdk1.8.0_251\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\deploy.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\access-bridge-64.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\cldrdata.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\dnsns.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jaccess.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jfxrt.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\localedata.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\nashorn.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunec.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunjce_provider.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunmscapi.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunpkcs11.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\zipfs.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\javaws.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfxswt.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\management-agent.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\plugin.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_251\jre\lib\rt.jar;
D:\tedu\workspace2\注解和反射\out\production\注解和反射;
C:\Program Files\ideaIU-2020.1\lib\idea_rt.jar
*/
}
}
7. 获取类运行时的结构
代码演示:
//获取类的信息
public class Test7 {
public static void main(String[] args) throws Exception {
Class c1 = Class.forName("com.tedu.reflection.User");
//获得类的名字
System.out.println(c1.getName());//获得包名+类名
System.out.println(c1.getSimpleName());//获得类名
//获得类的属性
Field[] fields = c1.getFields();//只能找到public属性
fields = c1.getDeclaredFields();//找到全部属性
for (Field f :fields
) {
System.out.println(f);
}
//获得指定属性的值
Field name = c1.getDeclaredField("name");
System.out.println(name);
//获得类的方法
System.out.println("==========");
Method[] methods = c1.getMethods();//获得本类以及父类的全部public方法
for (Method m:methods
) {
System.out.println("正常的:"+m);
}
methods = c1.getDeclaredMethods();//获得本类的全部方法
for (Method m1:methods
) {
System.out.println("getDeclaredMethods:"+m1);
}
//获得指定的方法
//重载的原因,所以需要参数null 或者其他类型
Method getName = c1.getMethod("getName", null);
Method setName = c1.getMethod("setName", String.class);
System.out.println(getName);
System.out.println(setName);
//获得构造器
System.out.println("=============");
Constructor[] constructors = c1.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
constructors = c1.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println("##"+constructor);
}
//获得指定的构造器
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
System.out.println("指定"+declaredConstructor);
}
}
8. 动态创建对象的执行方法
实例代码:
//动态的创建对象,通过反射
public class Test8 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//获得Class对象
Class c1 = Class.forName("com.tedu.reflection.User");
//构造一个对象
// User user = (User) c1.newInstance();//本质调用了一个无参构造器
//通过构造器创建对象
// Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
// User user1 = (User) constructor.newInstance("李强", 002, 18);
// System.out.println(user1);
//通过反射调用普通方法
User user2 = (User) c1.newInstance();
//通过反射获取一个方法
Method setName = c1.getDeclaredMethod("setName", String.class);
//invoke:激活的意思
//(对象,"方法的值")
setName.invoke(user2,"强哥");
System.out.println(user2.getName());
System.out.println("=================");
//通过反射操作属性
User user3 = (User) c1.newInstance();
Field name = c1.getDeclaredField("name");
//不能直接操作私有属性,我们需要关闭程序的安全检测,
name.setAccessible(true);
name.set(user3,"李强1");
System.out.println(user3.getName());
}
}
9. 创建对象方式的性能问题
以执行一个对象的方法十亿次的时间差对比结果
-
1, 普通方式 new 一个对象最快 -> 7ms
-
2.反射方式关闭安全检测速度一般 -> 1414ms (name.setAccessible(true);)
-
3.反射不关闭安全检测速度最慢 -> 3763ms
10. 反射操作泛型
➢Java采用泛型擦除的机制来弓|入泛型, Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,但是,-旦编译完成,所有和泛型有关的类型全部擦除
➢为了通过反射操作这些类型, Java新增了ParameterizedTyp,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是 又和原始类型齐名的类型.
➢ParameterizedType :表示一种参数化类型,比如Collection<String>
➢GenericArrayType :表示一-种元素类型是参数化类型或者类型变量的数组类型
➢TypeVariable :是各种类型变量的公共父接口
➢WildcardType :代表一种通配符类型表达式
实例代码:
//通过反射获取泛型
public class Test10 {
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 = Test10.class.getMethod("test01", Map.class, List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("#"+genericParameterType);
if (genericParameterType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("actual::== "+actualTypeArgument);
}
}
}
Method method1 = Test10.class.getMethod("test02", null);
Type genericReturnType = method1.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("a::== "+actualTypeArgument);
}
}
}
}
#java.util.Map<java.lang.String, com.tedu.reflection.User>
actual::== class java.lang.String
actual::== class com.tedu.reflection.User
#java.util.List<com.tedu.reflection.User>
actual::== class com.tedu.reflection.User
a::== class java.lang.String
a::== class com.tedu.reflection.User
11. 练习反射操作注解
//练习反射操作注解
public class Test11 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("com.tedu.reflection.Stu");
//通过反射获得注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获得注解的value值
Tableli tableli = (Tableli) c1.getAnnotation(Tableli.class);
String value = tableli.value();
System.out.println(value);
//获得类指定的注解
Field f = c1.getDeclaredField("id");
Fieldli annotation = f.getAnnotation(Fieldli.class);
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}
"db_student") (
class Stu{
columnName = "db_id",type = "int",length = 11) (
private int id;
columnName = "db_age",type = "int",length = 11) (
private int age;
columnName = "db_name",type = "varchar",length = 20) (
private String name;
public Stu(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public Stu() {
}
public String toString() {
return "Stu{" +
"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;
}
}
//类名的注解
ElementType.TYPE) (
RetentionPolicy.RUNTIME) (
@interface Tableli{
String value();
}
//属性的注解
ElementType.FIELD) (
RetentionPolicy.RUNTIME) (
@interface Fieldli{
String columnName();
String type();
int length();
}