javaSE.注解和反射
注解和反射
注解(Annotation)
注解是所有框架的底层;
1.注解入门
什么是注解
Annotation是JDK5.0开始引入的技术;
Annotation的作用:
不是程序本身,可以对程序做出解释(这点和注释comment)没有区别;
可以被其他程序(比如编译器等)读取;
Annotation的格式:
注解是以@注释名
在代码中存在,还可以添加一些参数值,例如`@SuppressWarnings(value="unchecked");
Annotation在哪里使用
可以附加在package,class,method,field等上面,相当于给他们添加了额外的辅助信息,可以通过反射机制编程实现对这些元素的访问;
2.内置注解
@Override: 定义在java.lang.Override中,只适用于修饰方法,表示一个方法声明打算重写超类中的另一个方法声明;
@Deprecated: 定义在java.lang.Deprecated中,可用于修饰方法、属性、类,表示不鼓励程序员使用这些类,通常是因为它很危险或存在更好的选择;
@SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息;
与前两个注释不同,需要添加一个参数才能使用,这些参数都是已经定义好的:
@SuppressWarnings("all")
@SuppressWarnings("unchecked")
@SuppressWarnings(value={"unchecked","deprecation"})
...
3.元注解(meta-annotation)
元注解的作用就是负责注解其他注解,Java定义了4个标准的元注解类型,它们被用来对其他注解类型作说明;
这些类型和它们所支持的类在java.lang.annotation包中可以找到:
@Target:用于描述注解的使用范围(即被注解的注解可以用在什么地方:方法、属性、类);
@Retention:表示需要在什么级别保存该注释信息(有效),用于描述注解的声明周期(SOURCE < CLASS < RUNTIME);
@Document:说明该注解将被包含在javadoc中;
@Inherited:说明子类可以继承父类中的该注解;
4.自定义注解
使用@interface 自定义注解,自动继承了java.lang.annotation.Annotation接口;
- @interface用来声明一个注解,格式:
@interface 注解名{定义类容}
; - 其中的每个方法实际上是声明了一个配置参数;
- 方法名称就是参数名称;
- 返回值类型就是参数的类型,返回值只能是基本类型(Class、String、enum);
- 可以通过default来声明参数的默认值;
- 如果只有一个参数成员,一般参数名为value,可以不写;
- 注解元素必须要有值,定义注解时,经常使用空字符串、0作为默认值;
/**
* Annotion 注解
*/
public class D01Annotation {
@MyAnnotation1(name = "zs",age=20,cars={"ford","bens"})
public void test1(){
}
//只有1个参数,且是value,可以省略
@MyAnnotation2("ls")
public void tests(){
}
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation1{
//注解的参数:参数类型 参数名();
String name();
int age();
int id() default -1;//默认值-1,代表不存在
String[] cars();
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
//注解的参数:只有一个参数,推荐用value;
String value();
}
反射(Reflection)
1.Java反射机制概述
动态语言
是一类在运行时可以改变其结构的语言,例如新的函数、对象,甚至代码可被引进,已有的函数可以被删除或其他结构上的变化;通俗的说就是在运行时代码可以根据某些条件改变自身结构;
主要动态语言:Object-C、C#、JavaScript、PHP、Python等;
静态语言
与动态语言相对应,运行时结构不可改变的语言,如:Java、C、C++等;
Java不是动态语言,但Java可以称为“准动态语言”;即Java具有一定的动态性,可以利用反射机制获得类似动态语言的特性;Java的动态性让编程更加灵活;
反射
是Java被视为动态语言的关键,反射机制允许程序在执行期借助Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法;
Class c = Class.forName("java.lang.String");
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了完整的类的结构信息;我们可以通过这个对象看到类的结构,这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射;
反射机制提供的功能
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法;
- 在运行时获取泛型信息;
- 在运行时调用任意一个对象的成员变量和方法;
- 在运行时处理注解;
- 生成动态代理;
- ...
反射的优缺点
优点:可以实现动态创建对象和编译,体现出很大的灵活性;
缺点:对性能有影响,使用反射基本上是一种解释操作,我们可以告诉JVM希望做什么并且满足我们的要求;这类操作总是慢于直接执行的相同的操作;
2.理解Class类并获取Class实例
Class类
对象照镜子后可以得到的信息:某个类的属性、方法、构造器,某个类实现的接口;对于每个类而言,JRE都为其保留一个不变的Class对象,一个Class对象包含了特定的某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息;
- Class 本身也是一个类;
- Class 对象只能由系统建立对象;
- 一个加载的类在JVM中只有一个Class实例;
- 一个Class对象对应的是一个加载到JVM中个一个.class文件;
- 每个类的实例都会记得自己是由哪个Class实例所生成;
- 通过Class可以完整地得到一个类中的所有被加载的结构;
- Class类是Reflection的根源,针对任何想动态加载、运行的类,唯有先获得相应的Class对象;
Class类的常用方法
- static Class forName(String name) //返回类名name的Class对象,name必须包括包名;
- Object newInstance() //调用缺省构造函数,返回Class对象的一个实例;
- Object getName() //返回此Class对象所表示的实体(类、接口、数组类或void)的名称;
- Class getSuperclass() //返回当前Class对象的父类的Class对象;
- Class[] getInterfaces() //返回当前Class对象的接口;
- ClassLoader getClassLoader() //返回该类的类加载器;
- Constructor[] getConstructors() //返回一个包含某些Constructor对象的数组;
- Method getMethod(String name,Class.. T) //返回一个Method对象,此对象的形参类型为paramType;
- Field[] getDeclaredFields() //返回Field对象的数组
- Class CanonicalName()//返回不包含包名的全限定名;
- Class getSimpleName()//返回不包含包名的类名;
获取Class实例
在Object类中定义了以下方法,此方法将被所有的子类继承:
public final Class getClass()
以上方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓的反射从程序运行的结果来看也很好理解,即可以通过对象发射求出类的名称;
【问题?】和instanceOf的区别?
- 若已知具体的类,通过“class属性”获取,该方法最安全可靠,性能最高:
Class clazz = Person.class;
- 若已知某个类的实例,调用该实例的“getClass()”方法获取Class对象:
Class clazz = person.getclass();
- 若已知某个类的全类名,且该类在类路径下,可通过Class类的静态方法“forName()”获取,可能抛出ClassNotFoundException异常:
Class clazz = Class.forName("demo.Person");
- 内置基本数据类型可以直接用“类名.Type”;
- 还可以利用ClassLoader获取
;
/**
* Reflection类
* 获取Class类的方式
*/
public class D02Reflect {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Student();
System.out.println("This is "+person.name);
//方式1:对象.getClass
Class c1 = person.getClass();
//方式2:类名.forName
Class c2 = Class.forName("AnnoReflect.Student");
//方式3:类名.class
Class c3 = Student.class;
System.out.println("c1="+c1.hashCode()+" ? "+"c2="+c2.hashCode());
//方式4:基本内置类型.Type属性
Class c4 = Integer.TYPE;
System.out.println(c4);
//获得父类类型
Class c5 = c1.getSuperclass();
System.out.println(c5);
}
}
class Person{
public String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person[" +
"name='" + name + '\'' +
']';
}
}
class Student extends Person{
public Student() {
this.name="student";
}
}
class Teacher extends Person{
public Teacher() {
this.name="teacher";
}
}
哪些类型可以有Class对象
- Class:外部类,成员(成员内部类,静态内部类),局部内部-
类,匿名内部类; - interface:接口;
- []:数组;
- enum:枚举;
- annotation:注解@interface;
- primitive type:基本数据类型;
- void
【注意】只要元素类型、维度一样,就是同一个Class
3.类的加载
内存结构
类的加载过程
当程序主动使用某个类时,如果该类未被加载到内存中,则系统会通过以下三个步骤来对该类进行初始化:
- 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表该类的java.lang.Class对象;
- 连接:将Java类的二进制代码合并到JVM的运行状态之中的过程;
- 验证:确保加载的类的信息符合JVM规范,没有安全方面的问题;
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配;
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程;
- 初始化:执行类构造器
()方法的过程,类构造器 ()方法是由编译期自动手机类中所有类变量的赋值动作和静态代码块中的语句合并产生的;(类构造器是构造类的信息的,不是构造该类对象的构造器) - 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化;
- 虚拟机会保证一个类的
()方法在多线程环境中被正确加锁和同步;
/**
* Class加载过程
* 加载、链接、初始化
*/
public class D03ClassLoad {
public static void main(String[] args) {
A a = new A();
System.out.println(A.m);
/*
1.加载:产生一个类对应的Class对象
2.链接:链接结束后 m=0;
3.初始化
<clinit>(){
System.out.println("A类静态代码初始化化");
m=200;
m=100;
}
*/
}
}
class A{
static {
System.out.println("A类静态代码块初始化");
m = 200;
}
static int m=100;
public A(){
System.out.println("A类的无参构造初始化");
}
}
什么时候会发生类的初始化
- 类的主动引用(一定会发送类的初始化)
- 当虚拟机启动,先初始化main方法所在的类;
- new一个类的对象;
- 调用类的静态成员(除final常量)和静态方法;
- 使用java.lang.reflect包的方法对类进行反射调用;
- 当初始化一个类,如果其父类没有被初始化,则会先初始化其父类;
- 类的被动引用(不会发生类的初始化)
- 当访问一个静态域,只有真正声明这个域的类才会被初始化;如,当通过子类引用父类的静态变量,不会导致子类初始化;
- 通过数组定义类引用,不会触发此类的初始化(只是分配一个内存区而已);
- 引用常量不会触发此类的初始化(常量在连接阶段就存入了用类的常量池中了);
类加载器(ClassLoader)
类加载器的作用:
类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口;
类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间,不过JVM垃圾回收机制可以回收这些Class对象;
类加载器的类型
/**
* ClassLoader
* 加载器的类型,获取方法
*/
public class D04ClassLoader {
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类的加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//获取系统类的父类加载器->扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);
//获取扩展类的父类加载器->根加载器
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
//测试当前类的类加载器
ClassLoader classLoader = Class.forName("AnnoReflect.D04ClassLoader").getClassLoader();
System.out.println(classLoader);
//测试内置类的类加载器
classLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader);
//获取系统类加载器可加载的路径
System.out.println(System.getProperty("java.class.path"));
}
}
4.创建运行时类的对象
Person p=new Student();
p.getClass();
5.获取运行时类的完整结构
通过反射获取Field,Method,Constructor,SuperClass,Interface,Annotation;
- 实现的全部接口;
- 所继承的父类;
- 全部的构造器;
- 全部的方法
- 全部的Field
- 注解...
/**
* 获取类的信息的方法
*/
public class D05ClassInfo {
public static void main(String[] args) throws Exception {
Class c = Class.forName("AnnoReflect.Person");
//类的名字
System.out.println(c.getName());//包名+类名
System.out.println(c.getSimpleName());//类名
//获取类的属性
Field[] fields=c.getFields();//public属性
fields=c.getDeclaredFields();//所有属性
for(Field field:fields){
System.out.println(field);
}
//获取指定属性的值
System.out.println(c.getField("name"));
System.out.println(c.getDeclaredField("name"));
//获取类的方法
Method[] methods = c.getMethods();//本类及父类所有方法
methods=c.getDeclaredMethods();//本类的所有方法
for (Method method:methods){
System.out.println(("getDeclaredMethods: " + method));
}
System.out.println(c.getMethod("setName", String.class));//获取指定的方法
//获取构造器
Constructor[] constructors=c.getConstructors();//public构造器
constructors=c.getDeclaredConstructors();//全部构造器?
for(Constructor constructor:constructors){
System.out.println(("构造器:" + constructor));
}
//获得指定的构造器
c.getConstructor(String.class,int.class);
}
}
【小结】
- 在实际操作中,取得类的信息的操作代码,并不会经常开发;
- 一定要熟悉java.lang.reflect包的作用,反射机制;
- 如果取得属性、方法、构造器的名称,修饰符等
6.调用运行时类的指定结构
有了Class对象,能做什么?
- 创建类的对象:调用Class对象的newInstance()方法
- 类必须有一个无参的构造器;
- 类的构造器的访问权限要足够;
【问题?】没有无参构造器就不能创建对象吗?只要在操作的时候明确的调用类中的构造器,并将参数传递进去后,才可以实例化操作;
- 步骤如下:
- 通过Class类的getDeclaredConstructor(Class...parameterTypes)取得本类指定形参类型的构造器;
- 向构造器的形参中传递一个对象数组进去,里面包含了构造器中需要的各个参数;
- 通过Constructor实例化对象;
/**
* 通过反射创建对象
*/
public class D06ReflectObject {
public static void main(String[] args) throws Exception {
//获取Class对象
Class c = Class.forName("AnnoReflect.Person");
//构造一个对象
Person p = (Person)c.newInstance();//本质上调用的无参构造器
System.out.println(p);//null
//通过构造器创建对象
Constructor constructor = c.getDeclaredConstructor(String.class, int.class);
Person person = (Person)constructor.newInstance("tom", 10);
System.out.println(person);
//通过反射调用普通方法
Person p1=(Person)c.newInstance();
Method setId=c.getDeclaredMethod("setId", int.class);
//invoke(对象,方法的值)激活
setId.invoke(p1,1);
System.out.println(p1.getId());
//通过反射操作属性
Field name = c.getDeclaredField("name");
p1.name="jack";
System.out.println(p1.name);
Field id = c.getDeclaredField("id");
//不能直接操作私有属性、方法,需要关闭安全检测
id.setAccessible(true);
p1.setId(100);
System.out.println(p1.getId());
}
}
setAccessible
- Method和Field、Constructor对象都有setAccessible()方法;
- setAccessible作用是启动和禁止访问安全检查的开关;
- 参数值为true则指示反射的对象在使用时应该取消java语言访问检查;
- 提高反射的效率,如果代码中必须用反射,而该句代码需要频繁的被调用,则宜设置为true;
- 使得原本无法访问的私有成员也可以访问;
- 参数值为false则指示反射的对象应该实施java语言的访问检查;
反射操作泛型
- Java采用泛型擦除的机制引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题;但是,一旦编译完成,所有和泛型有关的类型全部擦除;
- 为了通过反射操作这些类型,Java新增了几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型:
- ParameterizedType:表示一种参数化类型,比如Collection
; - GenericArrayType:表示一种元素类型是参数化类型或类型变量的数组类型;
- TypeVariable:是各种类变量的公共父结构;
- WildcardType:代表一种通配符类型表达式;
- ParameterizedType:表示一种参数化类型,比如Collection
/**
* 获取泛型信息
*/
public class D07GenericInfo {
public void test1(Map<String,Person> map, List<Person> list){
System.out.println("test1");
}
public Map<String,Person> test2(){
System.out.println("test2");
return null;
}
public static void main(String[] args) throws Exception {
Method method = D07GenericInfo.class.getMethod("test1", Map.class, List.class);
Type[] parameterTypes = method.getGenericParameterTypes();
for (Type parameterType : parameterTypes) {
System.out.println("->"+parameterType);
if (parameterType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) parameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("--"+actualTypeArgument);
}
}
}
System.out.println("=======方法返回参数类型======");
method = D07GenericInfo.class.getMethod("test2",null);
Type parameterType = method.getGenericReturnType();
if (parameterType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) parameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("--"+actualTypeArgument);
}
}
}
}
反射操作注解
getAnnotations;
getAnnotation
ORM(Object ralationship Mapping,对象关系映射)
/**
* 反射操作注解
* 模拟数据库信息读取
*/
public class D08AnnoInfo {
public static void main(String[] args) throws Exception {
Class c = Class.forName("AnnoReflect.Stud");
//通过反射获得注解
Annotation[] annotations = c.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获得指定注解的value
TableStu annoTable = (TableStu)c.getAnnotation(TableStu.class);
String value = annoTable.value();
System.out.println(value);
//获得类指定的注解
Field name = c.getDeclaredField("id");
FieldStu annoName = name.getAnnotation(FieldStu.class);
System.out.println(annoName.col_name());
System.out.println(annoName.type());
System.out.println(annoName.length());
}
}
@TableStu("db_stu")
class Stud {
@FieldStu(col_name = "db_id",type = "int",length = 10)
private int id;
@FieldStu(col_name = "db_age",type = "int",length = 10)
int age;
@FieldStu(col_name = "db_name",type = "varchar",length = 10)
private String name;
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableStu{
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldStu{
String col_name();
String type();
int length();
}
本文来自博客园,作者:老李学Java,转载请注明原文链接:https://www.cnblogs.com/JasonPro/p/16096203.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构