java注解与反射机制

注解与反射

## 一,注解

注解(Annotation):不是程序本身,可以对程序做出解释,也可以被其他程序读取。

注解格式:@注释名,也可以添加一些参数,如:@SuppressWarnings(value="unchecked")

1.内置注解

常用的三种内置注解:
① @Override : 重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。只能修饰方法。
② @Deprecated : 标记过时方法。所标注内容,不再被建议使用。可以修饰方法、属性、类。
③ @SuppressWarnings : 让编译器对"它所标注的内容"的某些警告保持静默。可以修饰类、方法。

2.元注解

元注解的作用就是负责注解其他注解,即修饰其他的注解。
常用的四种元注解:
① @Retention : 标识这个注解怎么保存,是只在代码中(SOURSE),还是编入class文件中(CLASS),
或者是在运行时可以通过反射访问(RUNTIME)。如@Retention(RetentionPolicy.SOURCE)。
	SOURSE<CLASS<RUNTIME
② @Documented : 标记这些注解将被包含在用户文档(JavaDoc)中。
③ @Inherited : 标记子类可以继承父类的注解。
④ @Target : 标记这个注解的使用范围。如@Target(value=ElementType.METHOD)。 用来约束注解可以应用的地方(如方法、类或字段),其中ElementType是枚举类型,其定义如下,也代表可能的取值范围
	TYPE 意味着,它能标注"类、接口(包括注释类型)或枚举声明"。
	FIELD 意味着,它能标注"字段声明"。
	METHOD 意味着,它能标注"方法"。
	PARAMETER 意味着,它能标注"参数"。
	CONSTRUCTOR 意味着,它能标注"构造方法"。
	LOCAL_VARIABLE 意味着,它能标注"局部变量"。

3.自定义注解

使用@Interface自定义注解,自动继承了java.lang.annotation.Annotation接口。
其中的每一个方法实际上是声明了一个配置参数,方法的名称就是参数的名称,返回值类型就是参数的类型。
可以通过default来声明参数的默认值
如果只有一个参数成员,一般声明参数名为value
注解元素必须有值

举例:
public class Demo1DefineAnnotation {
    @MyAnnotation1(name = "张三") // 每个参数如果没有默认值必须赋值
    public void test1() {}

    @MyAnnotation2("张三") // 当注解中只有一个参数且参数名为value,写的时候可以省略参数名
    public void test2(){}
}

@Inherited // 表示被这个注解修饰的类的子类可以继承父类的注解
@Documented // 表示注解可以被保存到JavaDoc中
@Target({ElementType.TYPE,ElementType.METHOD}) // 表示这个注解MyAnnotation1可以修饰类和方法
@Retention(RetentionPolicy.RUNTIME) // 表示这个注解MyAnnotation1可以保存到程序运行时
@interface MyAnnotation1{  // 自定义注解
    String name(); // 注解的参数,不是方法
    int age() default 0; // 设置age的默认值为0
}

@interface MyAnnotation2{  // 自定义注解
    String value(); // 当只有一个参数成员,一般声明参数名为value
}

## 二,反射

Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

加载完类之后,在堆内存的方法区中就产生了一个不变的Class类型(类)的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构,这个对象就像一面镜子,通过这个镜子看到类的结构,所以我们形象的称之为反射。

Class对象是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象。

### 1.获取Class对象的方式

① 通过对象获得:Class c1 = person.getClass()

② 通过forName获得:Class c2 = Class.forName(“Annotation.Student”)

③ 通过类名.class获得:Class c3 = Student.class

④ 通过基本内置类型的包装类的Type属性:Class c4 = Integer.TYPE

⑤获得父类类型:Class c5 = c1.getSuperclass();

public class test02 {
    public static void main(String[] args) throws ClassNotFoundException {
        Person person = new Student();
        System.out.println(person.name);


        //方式一: 通过对象获得
        Class c1  =person.getClass();

        //方式二: forName获得
        Class c2 = Class.forName("com.tedu.reflection.Student");
        System.out.println(c2.hashCode());

        //方式三: 通过类名.Class获得
        Class c3 = Student.class;
        System.out.println(c3.hashCode());

        //方式四: 基本内置类型那个的包装类都有一个Type属性
        Class c4 = Integer.TYPE;
        System.out.println(c4.hashCode());

        //获得父类类型
        Class c5 = c1.getSuperclass();
        System.out.println(c5);


    }
}

2. Class类的常用方法

static Class forName(String name):返回指定类名name的Class对象
Object newInstance():返回一个Class对象的实例
String getName():返回此Class对象所表示的类的名称(包名+类名)
String getSimpleName():返回此Class对象所表示的类的名称(类名)
Class getSuperClass():返回当前Class对象的父类的Class对象
Class[] getInterfaces():获取被当前Class对象实现的接口
ClassLoader getClassLoader():获取该类的类加载器
Constructor[] getConstructors():获得本类的所有构造函数的对象数组
Method getMethod(String name, Class<?>... parameterTypes):返回指定名称和参数类型对象的public方法对象(包括继承父类的,但不包括构造方法)
Method[] getMethods():获得类中的所有public方法(包括继承父类的,但不包括构造方法)
Method getDeclaredMethod(String name, Class<?>... parameterTypes):返回本类指定名称和参数类型对象的方法对象(不包括继承的和构造方法)
Method[] getDeclaredMethods():获得本类的所有方法(不包括继承父类的和构造方法)
Field getField(String name):返回这个类中指定的属性(包括继承的),这个属性必须是public
Field[] getFields():返回这个类中所有(包括继承的)声明的属性对象数组,这个字段必须是public
Field getDeclaredField(String name):返回这个类中指定的属性(不包括继承的)
Field[] getDeclaredFields():返回这个类中所有(不包括继承的)声明的属性的对象数组

3.其它类型的Class对象

class类、interface接口、[]数组、enum枚举、annotation注解、基本数据类型、void 等

public class test03 {
    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
        int[] a= new  int[10];
        int[] b= new  int[100];
        System.out.println(a.getClass().hashCode());
        System.out.println(b.getClass().hashCode());
    }
}
结果:
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
356573597
356573597

4.类加载器的过程

​ ① 编译:代码首先被编译成二进制字节码文件(.class文件)
​ ② 加载:将class文件字节码内容加载到内存中,并将静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的Class对象
​ ③ 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程

  • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
  • 准备:为类变量(static)分配内存并设置类变量默认初始值

  • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)

    ④ 初始化:执行类构造器方法的过程,类构造器方法是由编译期自动收集类中的所有类变量(static)的赋值动作和静态代码块的语句合并产生的(类构造器是构造类信息的,不是构造该类对象的构造方法)。初始化一个类的时候,当发现其父类还没有初始化,需要先初始化其父类。

5.有Class对象能做什么

5.1通过反射动态的创建对象和调用方法与属性
// 通过反射动态的创建对象
// 通过反射调用方法和属性
public class Demo7 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class c1 = Class.forName("Annotation.User"); // 获得Class对象

        // 通过反射创建一个实例对象
        User user = (User) c1.newInstance(); // 相当于调用无参构造器
        System.out.println(user.toString());

        // 通过构造器创建实例对象
        Constructor constructor = c1.getDeclaredConstructor(int.class,String.class,int.class);
        user = (User) constructor.newInstance(10101,"张三",18);
        System.out.println(user.toString());

        // 通过反射调用方法
        Method setName = c1.getDeclaredMethod("setName", String.class); // 获得本类的指定方法setName
        setName.invoke(user,"李四"); // invoke:激活
        System.out.println(user);

        // 通过反射操作属性
        Field name = c1.getDeclaredField("name");
        // 不能直接操作private属性,需要关闭程序的安全检测
        name.setAccessible(true); // 关闭安全检测
        name.set(user,"王五");  // 相当于 user.name = "王五"
        System.out.println(user.getName());
    }
}
class User{
    private int id;
    private String name;
    private int age;

    public User() {
    }
    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "User{id="+this.id+",name="+this.name+",age="+this.age+"}";
    }
}

结果:
User{id=0,name=null,age=0}
User{id=10101,name=张三,age=18}
User{id=10101,name=李四,age=18}
王五

三.反射机制

原理

​ 经过反编译后,Java所有注解都继承了Annotation接口,也就是Java使用Annotation接口代表注解元素,该接口是所有Annotation类型的父接口。同时为了运行时能准确获取到注解的相关信息,Java在java.lang.reflect 反射包下新增了AnnotatedElement接口,它主要用于表示目前正在JVM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,如反射包的Constructor类、Field类、Method类、Package类和Class类都实现了AnnotatedElement接口,它简要含义如下(更多详细介绍可以看 深入理解Java类型信息(Class对象)与反射机制)

 Class:类的Class对象定义   
 Constructor:代表类的构造器定义   
 Field:代表类的成员变量定义 
 Method:代表类的方法定义   
 Package:代表类的包定义

AnnotatedElement中相关的API方法

返回值方法名称说明
getAnnotation(Class annotationClass)该元素如果存在指定类型的注解,则返回这些注解,否则返回 null。
Annotation[]getAnnotations()返回此元素上存在的所有注解,包括从父类继承的
booleanisAnnotationPresent(Class<? extends Annotation> annotationClass)如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。
Annotation[]getDeclaredAnnotations()返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组

Java8中注解增强

元注解@Repeatable

​ 元注解@Repeatable是JDK1.8新加入的,它表示在同一个位置重复相同的注解。在没有该注解前,一般是无法在同一个类型上使用相同的注解的

新的两种ElementType

​ 在Java8中 ElementType 新增两个枚举成员,TYPE_PARAMETER 和 TYPE_USE ,在Java8前注解只能标注在一个声明(如字段、类、方法)上,Java8后,新增的TYPE_PARAMETER可以用于标注类型参数,而TYPE_USE则可以用于标注任意类型(不包括class)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值