狂神笔记—注解与反射

注解

介绍

  • 注解Annotation是从JDK5.0开始引入的新技术。

  • 作用:

    1. 不影响程序本身,但是可以对程序作出解释
    2. 可以被其他程序(如编译器等)读取
  • Annotation的格式:注解是以"@注释名"再代码中存在,还可以添加一些参数值,如@SuppressWarnings(value = “unchecked”)

  • 注解的使用场景:可以附加在package,class,method,field等上面,相当于是添加了辅助信息,也可以通过反射机制编程实现对元数据的访问

内置注解

  • @Override:定义在java.lang包中,此注释只适用于修饰方法,表示一个方法声明打算重写超类中的另外一个方法声明
  • @Depercated:定义在java.lang包中,此注释用于修饰方法、属性、类,表示所定义的东西不鼓励使用,因为其很危险或者有其他更好的选,当使用所修饰的这个方法时,方法中间会带有一条划掉的横线
  • @SuppressWarnings:定义在java.lang包中,用来抑制编译时的警告信息,与前两个不同,这个需要在括号中添加参数才能正确使用,如:@SuppressWarnings(“all”);

元注解

  • 元注解的作用就是负责注解其他注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型说明
  • 这些类型和他们所支持的类在java.lang包中可以找到,如@Taget,@Retention,@Documented,@Inherited
    • @Taget:用于描述注解的使用范围(即:被描述的注解可以用在说明地方)
    • @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(SOURCE(源码里面)<CLASS(类里面)<RUNTIME(运行时))
    • @Documented:说明该注解将被包含在javadoc中
    • @Inherited:说明子类可以继承父类中的该注解

自定义注解

  • 使用@interface自定义注解时,自动继承java.lang.annotation.Annotation接口

  • 分析:

    • @interface用来声明一个注解,格式:public @interface 注解名{定义内容}

    • 其中的每个方法实际上是声明一个配置参数

    • 方法名称就是参数名称

    • 返回值类型就是参数的类型(返回值只能是基本类型、Class、String、enum)

    • 可以通过default来声明参数的默认值

    • 如果只有一个参数成员,一般参数名为value

    • 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值

测试代码如下:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//自定义注解测试
public class Test02 {
    //没有赋予默认值的参数要在使用注解时给注解赋值
    @MyAnnotation02(name = "你好",a = 20)
    public void method01(){}

    @MyAnnotation03(value = "你好")
    public void method02(){}
    
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation02{
    //注解的参数:参数类型 + 参数名();
    String name();
    int a();

    //注解也可赋予默认值。则在使用的时候就不用输入默认值
    int id() default 100;//默认值为100
    String[] arr() default  {"小明","小冷"};//默认值为"小明","小冷"

}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation03{
    //注解的参数:参数类型 + 参数名();
    String value();//只有一个参数时,参数名最好用value
}

反射

概念

  • Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并且直接操作任意对象的内部属性和方法:Class c = Class.forName(“java.lang.String”)
  • 加载完类之后,在堆内存的方法区中产生了一个Class类型的对象(一个类只有Class对象),这个对象就包含了完整类的结构信息,通过这个对象来看到类的结构,所以称之为“反射”。

优点:可以实现动态创建对象编译,体现出很大的灵活性

缺点:对性能有影响,使用反射基本上是一种解释操作,可以告诉jvm我们的需求,但是这类操作总是慢于直接执行相同的操作。

主要api

  • java.lang.Class:代表一个类
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Constructor:代表类的构造器

以上方法返回值类型都是一个Class类,此类是java反射的源头,,所以也可以看作是通过对象求出类的名称

public class Test01 {
    public static void main(String[] args) throws ClassNotFoundException {
        //通过反射来获取类的class对象
        Class c1 = Class.forName("注解和反射.反射.User");
        System.out.println(c1);

        System.out.println(c1.getAnnotations());//获取类的注解
        System.out.println(c1.getConstructors());//获取类的方法

        //创建多个对象,通过hashcode方法判断是否是同一个类
        Class c2 = Class.forName("注解和反射.反射.User");
        Class c3 = Class.forName("注解和反射.反射.User");
        //一个类在内存中只有一个Class对象
        //一个类被加载之后,整个类都会被封装在Class对象之中
        System.out.println(c1.hashCode());
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());
        //发现三个对象的类都是一样的
    }
}
//测试类
class User{
}

结果如下:

在这里插入图片描述

获取Class类的方式:

  1. 方式一:若已知具体的类,通过类的class属性获取,该方法最可靠。程序性能更高
    Class c3 = Student.class;
  2. 方式二:已知某个类的实例,调用该实例的getClass()方法获取Class对象
    Class c1 = person.getClass();
  3. 方式三:已知一个类的全类名。且该类在类的路径,可通过Class类的静态方法forName()获取,可以抛出ClassNotFoundException异常
    Class c2 = Class.forName(“注解和反射.反射.Test02”);
  4. 方式四:内置基本数据类型可以直接类名.Type
    Class c4 = Integer.TYPE;
//测试Class类的创建方式有哪些
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("注解和反射.反射.Test02");
        //方式三:通过类名.class获取
        Class c3 = Student.class;

        //判断是否是同一个类
        System.out.println(c1.hashCode());
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());

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

        //获取父类类型的方法
        Class c5 = c1.getSuperclass();
        System.out.println(c5);
    }
}
//测试类
class Person{
}

class Student extends Person{
    public Student (){
        this.name = "学生";
    }
}

运行出的结果如下:

在这里插入图片描述

所有类型的Class对象

import java.lang.annotation.ElementType;

//所有类型的Class
public class Test03 {
    public static void main(String[] args) {
        Class c1 = Object.class;//类
        Class c2 = Comparable.class;//接口
        Class c3 = String[].class;//一维数组
        Class c4 = Override.class;//注解
        Class c5 = ElementType.class;//枚举
        Class c6 = void.class;//void空类型
        Class c7 = 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);
    }

}

运行结果如下:

在这里插入图片描述

类加载内存分析

java内存分析

堆内存:

  • 存放new的对象和数组
  • 可以被所有线程共享,不会存放别的对象的引用

栈内存:

  • 存放基本变量类型(会包含这个基本类型的具体数值)

  • 引用对象的变量(会存放这个引用在堆内存的具体地址)

方法区:

  • 可以被所有线程共享
  • 包含所有的class和static变量

类的加载过程

当程序使用某类时,如果该类还未被加载到内存中,则系统会通过如下三步来对类进行初始化

  1. 类的加载(Load):将类的class文件读入内存,并为之创建一个java.lang.Class对象,此过程由类加载器完成。
  2. 类的链接(Link):将类的二进制数据合并到JRE中。
    • 验证:确保加载的类的信息符合JVM规范,没有安全方面的问题
    • 准备:正式为类变量(static)分配内存并设置类变量的默认初始值的阶段,这些内存都将在方法区中进行分配
    • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
  3. 类的初始化(Initialize):JVM负责对类进行初始化。
    • 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类的所有类变量的赋值动作静态代码块中的语句合并产生的(类构造器是构造类信息的,表示构造该类对象的构造器)
    • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
    • 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步

eg:

public class Test04 {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(A.m);//输出m是100
    }
}
/*
* 1.加载内存,会产生一个类对应的class对象
* 2.链接,链接结束后m=0
* 3.初始化
* <clinit>(){
* //将静态代码块合并
*   System.out.println("A类静态代码块初始化");
     m = 300;
     int m = 100;
     //所以m是先等于300再等于100,最后是100
* }*/
class A{


    static {
        System.out.println("A类静态代码块初始化");
        m = 300;
    }
    static int m = 100;


    public A(){
        System.out.println("A类的构造器初始化");
    }
}

结果如下:

在这里插入图片描述

类什么时候发生初始化

  1. 类的主动引用(一定会发生类的初始化)
    1. 当虚拟机启动时,先初始化main方法所在的类
    2. new一个类对象
    3. 调用类的静态成员(除了final常量)和静态方法
    4. 使用java.lang.reflect包的方法对类进行反射调用
    5. 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
  2. 类的被动引用(不会发生类的初始化)
    1. 当访问一个静态域时,只有真正声明这个域的类才会被初始化,如:当通过子类引用父类的静态变量,不会导致子类初始化
    2. 通过数组定义类的引用,不会触发此类的初始化
    3. 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
public class Test05 {
    //1.1当虚拟机启动时,先初始化main方法所在的类
    static {
        System.out.println("主类被加载");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        //1.2new一个类对象
        //Son son = new Son();

        //1.3调用类的静态成员(除了final常量)和静态方法
        //System.out.println(Son.m);

        //1.4使用java.lang.reflect包的方法对类进行反射调用
        //Class.forName("注解和反射.反射.Test05");

        //2.1当通过子类引用父类的静态变量,不会导致子类初始化
        //System.out.println(Son.b);

        //2.2通过数组定义类的引用,不会触发此类的初始化
        //Son[] arr = new Son[5];

        //2.3引用常量不会触发此类的初始化
        //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;

}

获取类运行时的结构

类似于获取类的属性:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Test06 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        //第一种方式
        Class c1 = Class.forName("注解和反射.反射.User");

        //第二种方式
        User user = new User();
        c1 = user.getClass();

        //获取类的名字
        System.out.println(c1.getName());//获得包名加类名
        System.out.println(c1.getSimpleName());//获得类名

        System.out.println("=============================");
        //获得类的属性
        //只能获取类的public属性
        Field[] fields = c1.getFields();
        for (int i = 0; i < fields.length; i++) {
            System.out.println(fields[i]);
        }
        //可以获取类的全部属性
        fields = c1.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            System.out.println(fields[i]);
        }
        //获取指定属性
        Field name = c1.getField("name");
        System.out.println(name);

        System.out.println("=============================");
        //获取类的方法
        //获取本类以及父类的全部public方法
        Method[] methods = c1.getMethods();
        for (Method method : methods) {
            System.out.println("第一种方法" + method);
        }
        //只获得本类的所有方法
        Method[] declaredMethods = c1.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("第二种方法" + declaredMethod);
        }
        //获得指定方法
        Method getName = c1.getMethod("getName", null);
        System.out.println(getName);
        Method setName = c1.getMethod("setName", String.class);
        System.out.println(setName);

        System.out.println("=============================");
        //获得指定构造器
        //只能获取类的public构造器
        Constructor[] constructors = c1.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        //能获取类的全部构造器
        Constructor[] declaredConstructors = c1.getDeclaredConstructors();
        for (Constructor declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }
        //获取指定的构造器
        Constructor declaredConstructor = c1.getDeclaredConstructor(String.class);
        System.out.println(declaredConstructor);
    }
}

动态创建对象

1.创建类的对象:调用Class对象的**newInstance()**方法

  1. 类必须有一个无参数的构造器
  2. 类的构造器的访问权限需要足够

2.若没有无参构造器如何创建对象

  1. 通过Class类的getDeclaredConstructor(参数列表)取得本类的指定形参类型的构造器
  2. 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数
  3. 通过Constructor实例化对象

3.调用指定的方法

  1. 通过Class类的getDeclaredMethod(参数列表)方法获取一个Method对象,并设置此方法操作时所需要的参数类型
  2. 之后使用Object invoke(类的对象,传进去的参数)进行调用,并向方法中传递要设置的obj对象的参数信息
  3. 如果方法为private,则需要在invoke方法调用之前,先用方法对象调用setAccessible(true)方法再进行激活

4.调用指定的属性

  1. 通过Class类的getField(参数列表)方法获取一个Field对象,并设置此属性名称
  2. 之后使用Object set(类的对象,传进去的参数)进行调用,并向方法中传递要设置的obj对象的参数信息
  3. 如果方法为private,则需要在set方法调用之前,先用方法对象调用setAccessible(true)方法再进行设置

代码实现:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

//通过反射,动态创建对象
public class Test07 {
    public static void main(String[] args) throws Exception{
        //获取Class对象
        Class c1 = Class.forName("注解和反射.反射.User");

        //1.构造一个对象(通过无参构造器)
        User user = (User) c1.newInstance();//本质上是调用类的无参构造器
        System.out.println(user);
        System.out.println("==============================");

        //2.通过构造器构造对象
        Constructor constructor = c1.getDeclaredConstructor(String.class);
        User user2 = (User)constructor.newInstance("你好");//括号里是参数
        System.out.println(user2);
        System.out.println("==============================");

        //3.通过反射调用普通方法
        User user3 = (User) c1.newInstance();
        //3.1获取一个方法
        Method setName = c1.getDeclaredMethod("setName", String.class);
        //3.2invoke:激活的意思
        //参数列表:(对象,"方法的值")
        setName.invoke(user3,"小明");//参数要包括user类的对象

        //4.通过反射操作属性
        System.out.println("==============================");
        User user4 = (User) c1.newInstance();
        Field name = c1.getField("name");//获取指定属性
        //不能直接操作私有属性,可以选择关闭程序的安全检测,属性或方法的setAccessible(true)。若为true则属性和方法可以被随意调用,相反则不可以
        name.setAccessible(true);
        //参数列表:(对象,"方法的值")
        name.set(user4,"小王");
        System.out.println(user4.getName());


    }
}

反射操作泛型

  • ParameterizedType:表示一种参数化类型,比如Collect

  • GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型

  • TypeVariable:是各种类型变量的公共的接口

  • WildcardType:代表一种通配符类型的表达式

代码实现:

import java.lang.reflect.*;
import java.util.List;
import java.util.Map;

public class Test08 {
    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 = Test08.class.getMethod("test01", Map.class, List.class);
        //获取泛型的参数类型
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            //打印出类型,一个map一个list
            System.out.println("#" + genericParameterType);
            //判断泛型是否为结构化的参数类型
            if(genericParameterType instanceof ParameterizedType){
                //是的话就进行强制转换,转化出来再通过getActualTypeArguments获得真实参数信息
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                //获取完再打印出来
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }
        }

        //原理同上
        method = Test08.class.getMethod("test02",null);
        Type genericReturnType = method.getGenericReturnType();
        if(genericReturnType instanceof ParameterizedType){
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }

    }
}

运行结果如下:

在这里插入图片描述

通过反射来操作注解

代码实现:

import java.lang.annotation.*;
import java.lang.reflect.Field;

//练习反射操作注解
public class Test09 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("注解和反射.反射.Student2");

        //通过反射获取注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        //获取TableJava注解中的value的值
        //通过强制转换来获取注解对象
        TableJava tablejava = (TableJava)c1.getAnnotation(TableJava.class);
        String value = tablejava.value();
        System.out.println(value);

        //获取类的指定注解
        //1,先获取类的属性
        Field name = c1.getDeclaredField("name");
        //2,再获取属性里面注解的对象
        FieldJava annotation = name.getAnnotation(FieldJava.class);
        //3,通过获取的注解对象调用出其中的元素
        System.out.println(annotation.columname());
        System.out.println(annotation.length());
        System.out.println(annotation.type());
    }
}

@TableJava("学生")
class Student2{
    @FieldJava(columname = "卡号",type = "类型",length = 10)
    private int id;
    @FieldJava(columname = "年龄",type = "类型",length = 10)
    private String age;
    @FieldJava(columname = "姓名",type = "类型",length = 10)
    private String name;

    public Student2() {
    }

    public Student2(int id, String age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student2{" +
                "id=" + id +
                ", age='" + age + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableJava{
    String value();
}

//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldJava{
    String columname();
    String type();
    int length();
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值