Java(注解和反射)

本文详细介绍了Java中的注解(Annotation)及其应用,包括内置注解、元注解和自定义注解的创建与使用。注解主要用于提供代码解释、编译时检查和运行时行为控制等功能。此外,还探讨了反射机制,它是Java语言的重要特性,允许在运行时检查和修改类的行为。反射机制通过Class对象获取类的成员信息,如构造器、方法和字段,并能动态创建对象。文章还讨论了反射的优缺点及应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


注解(java.Annotation)

1.什么是注解

注解的作用

  • 不是程序本身,可以对程序作出解释(这一点和注释(comment),没什么区别)
  • 可以被其他程序(如编译器等)读取
  • 生成帮助文档。这是最常见的,也是 Java 最早提供的注解。常用的有 @see、@param 和 @return 等;
  • 跟踪代码依赖性,实现替代配置文件功能。比较常见的是 Spring 2.5 开始的基于注解配置。作用就是减少配置。现在的框架基本都使用了这种配置来减少配置文件的数量;
  • 在编译时进行格式检查。如把 @Override 注解放在方法前,如果这个方法并不是重写了父类方法,则编译时就能检查出。

注解的格式

  • 以"@注释名"在代码中存在,还可以添加一些参数,如@SuppressWarnings(value=“unchecked”)

注解在哪里使用

  • 可以附加在package,class,method,filed等上面,相当于添加了额外的辅助信息,可通过反射机制编程实现对这些元数据的访问

2.内置注解

内置注解作用
@Override用来指定方法重写的,只能修饰方法并且只能用于方法重写,不能修饰其它的元素。它可以强制一个子类必须重写父类方法或者实现接口的方法。
@Deprecated可以用来注解类、接口、成员方法和成员变量等,用于表示某个元素(类、方法等)已过时或不安全。当其他程序使用这些元素时,编译器将会给出警告。
@SuppressWarnings抑制编译时的警告信息,且会一直作用于该程序元素的所有子元素。需要添加关键字才能使用。关键字
@FunctionalIterface(Java 8新增)修饰函数式接口。Java8规定:如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法),该接口称为函数式接口
@SafeVarargsDK 7 专门为抑制“堆污染”警告提供的

3.元注解

元注解是负责对其它注解进行说明的注解,自定义注解时可以使用元注解。

元注解(meta-annotation)作用:

  • @Documented 用该注解修饰的注解类会被 JavaDoc 工具提取成文档(默认情况下,JavaDoc 是不包括注解的)
  • @Target 指定一个注解的用于什么地方。有一个成员变量(value)用来设置适用目标,value 是 java.lang.annotation.ElementType 枚举类型的数组。格式为@Target({ ElementType.METHOD })
    • CONSTRUCT 用于构造方法
    • FIELD 用于成员变量(包括枚举常量)
    • LOCAL_VARIABLE 用于局部变量
    • METHOD 用于方法
    • PACKAGE 用于包
    • PARAMETER 用于类型参数(JDK 1.8新增)
    • TYPE 用于类、接口(包括注解类型)或 enum 声明
  • @Retention 用于描述该注解被保留的时间长短(也可理解为该注解在哪一级别可用)。成员变量(value)用来设置保留策略,value 是 java.lang.annotation.RetentionPolicy 枚举类型。如@Retention(RetentionPolicy.SOURCE )
    • SOURCE 在源文件中有效(即源文件保留),所以该注解会被编译器抛弃
    • CLASS 在 class 文件中有效(即 class 保留),所以该注解会被VM抛弃
    • RUNTIME 在运行时有效(即运行时保留),所以可以通过反射机制读取注解信息
    • 生命周期大小排序为 SOURCE < CLASS < RUNTIME,前者能使用的地方后者一定也能使用
  • @Inherited 指定该注解可以被继承
  • @Repeatable 允许在相同的程序元素中重复注解(Java 8新增)。在需要对同一种注解多次使用时,往往需要借助 @Repeatable 注解
  • @Native 修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。对于 @Native 注解不常使用,了解即可

没有元素的注解称为标记注解

3.自定义注解

声明自定义注解使用 @interface 关键字,此时自动继承了java.lang.Annoation接口。注解也会被编译为class文件

自定义一个注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
    public int id(); //此处没有给定默认值,所以在使用该注解时必须提供id值
    public String description() default "no description"; 
    //无论是定义注解还是使用注解,元素值都不能为null,可以用-1、空字符串""代替
}

在方法上使用刚定义的注解

public class PasswordUtils {
    @UseCase(id = 47, description = "Password must contain at least one numeric")
    public boolean validatePassword(String password){
        return password.matches("\\w*\\d\\w*");
    }
    @UseCase(id = 48)
    public String encryptPassword(String password){
        return new StringBuilder(password).reverse().toString();
    }
    @UseCase(id = 49, description = "New password can't equal previously used ones")
    public boolean checkForNewPassword(
            List<String> prevPassword, String newPassword){
        return !prevPassword.contains(newPassword);
    }
}

利用反射获取方法中的注解信息

public class UseCaseTracker {
    public static void trackUseCases(List<Integer> useCases, Class<?> cl){
        for(Method m : cl.getDeclaredMethods()){ //这里cl指PasswordUtils类,通过反射api获得类中的方法
            UseCase uc = m.getAnnotation(UseCase.class); //通过反射中的getAnnotation方法得到方法m的注解信息
            if(uc != null){
                System.out.println("Found UseCase" + uc.id() + " " + uc.description());
                useCases.remove(new Integer(uc.id()));
            }
        }
        for (Integer id : useCases) {
            System.out.println("Warning: Missing UseCase:" + id);
        }
    }

    public static void main(String[] args) {
        List<Integer> useCases = new ArrayList<Integer>();
        Collections.addAll(useCases, 47, 48, 49, 50);
        trackUseCases(useCases, PasswordUtils.class);
    }
}
/
/*output:
Found UseCase48 no description
Found UseCase47 Password must contain at least one numeric
Found UseCase49 New password can't equal previously used ones
Warning: Missing UseCase:50
*/

反射(java.Reflection)

反射机制是Java语言一个重要的特性,在学习之前先了解一下编译期和运行期。

  • 编译期:在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如检查错误。
  • 运行期:是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。

在java中,所有的类型转换都是在运行时进行正确性检查的,这也是**RTTI(Run Time Type Identification)**的含义:即在运行时,识别一个对象的类型。

那么一个对象的类型信息是如何在运行时表示的呢?
在Java中,执行RTTI这项工作是借助Class对象的特殊对象来完成的,它包含了与类有关的所有信息,也可以理解为Class对象是用来创建类的所有”常规“对象的。

每当编写并编译了一个新类,就会产生一个Class对象(或者说是被保存到同名的.class文件中),而产生这个对象借助的便是运行此程序的JVM中的类加载器子系统。有关JVM以及类加载机制会在以后学习JVM时总结。下面先简单理解一下:

首先,Java程序在它开始运行之前并非完全被加载,其各个部分是在必须时才加载的,称为动态加载。所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时(比如类中有使用static关键字,或new一个对象,因为构造方法也是静态方法),就会加载这个类。

如何得到一个Class对象的引用:
1.Class.forName(“类的全限定名”):如果类没被加载JVM便会加载它;如果没找到这个类,便会抛出ClassNotFoundException。
此方法的好处是:不需要获得某个类的对象就可以获得该类的Class引用

try{
	Class<? extends ...> c = Class.forName("...")
}catch(ClassNotFoundException){
	...
}

Class<? extends ...>可以将Class引用限定为某种类型,为了提供编译期类型检查。

2.Object类中的getClass()方法:如果已经拥有了某个类的对象,可以直接调用Class<? extends ...>c = 对象.getClass();来获得Class引用

3.类字面常量 Class<? extends ...> c = 类名.class;,这样做更加简单且安全,因为此语句在编译时就会收到检查 (不需要t放入ry语句) 。与Class.forName不同的是,使用.class来创建Class对象的引用不会自动初始化改Class对象
并且此方法还可以用于接口、数组以及基本数据类型(如int.class)。对于基本数据类型及其包装器类,int.class等价于Integer.TYPE,TYPE字段是一个引用,指向基本数据类型的Class对象。

需要注意:只要元素类型与维度一样,就是同一个Class引用
获得类的Class对象

得到Class引用后可以做什么?

1.通过引用可以获得类的成员信息
java.lang.reflect包

  • Constructor 类:提供类的构造方法信息。
  • Field 类:提供类或接口中成员变量信息。
  • Method 类:提供类或接口成员方法信息。
  • Array 类:提供了动态创建和访问 Java 数组的方法。
  • Modifier 类:提供类和成员访问修饰符信息。
    类里面具体的方法
方法作用
Package对象 getPackage()获取该类的存放路径
String getName()获取该类的名称
Class getSuperclass()获取该类的父类
Class[] getlnterfaces()获取该类实现的所有接口
ClassLoader getClassLoader()返回类的类加载器
Constructor[] getConstructors()获取所有权限为public的构造方法
Constructor<?>[] getDeclaredContruectors()获取当前对象的所有构造方法
Method getMethod(String name,类<?>… parameterTypes)返回一个指定方法对象,name指所需方法的简单名称,此对象的形参为parameterTypes
Method[] getMethods()获取所有权限为 public 的方法,包括继承的方法
Method[] getDeclaredMethods()获取当前对象的所有方法,不包括继承的方法
Filed getField(String name)返回一个指定的成员变量
Field[] getFields()获取所有权限为 public 的成员变量,包括继承的成员变量
Field[] getDeclareFileds()获取当前对象的所有成员变量,不包括继承的成员变量
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
    Class c1 = Class.forName("com.kk.reflection.User");
    //获得类的信息的基本操作
    //获得指定属性的值
    Field[] fields = c1.getDeclaredFields();
    for (Field field : fields) {
        System.out.println(field);
    }
    System.out.println(c1.getDeclaredField("name")); //private java.lang.String com.kk.reflection.User.name
    System.out.println(c1.getDeclaredMethod("getAge", null)); //public int com.kk.reflection.User.getAge()
    System.out.println(c1.getDeclaredMethod("setName", String.class)); //public void com.kk.reflection.User.setName(java.lang.String)
	//其余方法的调用都类似。
}

2.newInstance()
使用newInstance()可以创建一个确切类型的对象。

Class< A> aClass = A.class;
A a = aClass.newInstance();

3.instanceof、isInstance() & ==、equals() 这位两组方法在比较Class对象的等价形式有一定的区别。

class Base{}
class Derived extends Base{}

Base base = new Base();
Derived derived  = new Derived();

base instanceof Base; // true 等价于Base.class.isInstance(base)
base instanceof Derived; // false 
derived instanceof Base; //true

base.getClass() == Base.class; //true 等价于 base.getClass().equals(Base.class)
derived.getClass() == Base.class; //false,与上面的derived instanceof Base结果不同

可以看出instanceof、isInstance() 是等效的,它会判断对象是否为该类或是该类派生类的对象;而==、equals()不会考虑继承关系

反射的优点

  • 能够运行时动态获取或创建类的实例,大大提高系统的灵活性和扩展性。
  • 与 Java 动态编译相结合,可以实现无比强大的功能。
  • 对于 Java 这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

反射的缺点

  • 反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射
  • 反射调用方法时可以忽略权限检查,获取这个类的私有方法和属性,因此可能会破坏类的封装性而导致安全问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值