Java学习笔记 反射与注解

前言

反射是框架(如Spring等)的灵魂,掌握java反射的知识可以让你更好的理解框架的底层实现。而我们经常使用的一些Spring注解就是通过反射来获取的。

反射

  • 动态语言VS静态语言
    • 动态语言:是一类可以在运行时可以改变其结构的语言(引进新的对象,函数或者删除已有的函数),通俗的点说就是在运行代码的时候可以根据某些条件改变自身结构,如C#,JavaScript,PHP,Python等
    • 静态语言:运行时结构不可变的语言就是静态语言,如Java,C,C++. Java虽然不是动态语言,但是可以通过反射机制获取类似动态语言的特性
  • java反射机制:
    • 作用:允许程序在执行期间借助反射API取得任何类的内部信息,并且可以直接操作任意对象的内部属性及其方法
    • 优点:可以实现动态对象的创建和编译,体现了很大的灵活性.
    • 缺点:对性能有比较大的影响,反射是一种解释性的操作,总是比直接操作慢很多
类加载的过程
  • 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在java堆中生成一个代表这个类的java.lang.Class对象
  • 链接:将java类的二进制代码合并到JVM的运行状态之中的过程
    • 验证:确保加载的类信息符合jvm规范,没有安全方面的问题
    • 准备:正式为类变量(static)分配内存并设置初始值的阶段,这些内存都将在方法区中进行分配
    • 解析: 虚拟即常量池的符号引用(常量名)替换成直接引用(地址)的过程
  • 初始化
    • 执行类构造器< clinit >()方法的过程, < clinit >()方法时由编译期自动收集类中的所有类变量的赋值动作和静态代码块中的语句产生的(类构造器时构造类消息的,不是构造该类对象的构造器)
    • 当初始化一个类的时候,如果发现其父类还没有进行初始初始化,则需要先触发其父类的初始化
    • 虚拟机会保证一个类的< clinit >()方法在多线程环境中被正确执行
三种类加器器
  • 启动类加载器(Bootstrap classLoader):又称为引导类加载器,由C++编写,无法通过程序得到。主要负责加载JAVA中的一些核心类库,主要是位于<JAVA_HOME>/lib/rt.jar中。

  • 拓展类加载器(Extension classLoader):主要加载JAVA中的一些拓展类,位于<JAVA_HOME>/lib/ext中,是启动类加载器的子类。

  • 应用类加载器(System classLoader): 又称为系统类加载器,主要用于加载CLASSPATH路径下我们自己写的类,是拓展类加载器的子类。

  • 类加载器的三大特性:委托性、可见性、单一性

    • 委托性:每个类中都有一个自己的类加载器的属性,这也就是为什么可以通过Student.class.getClassLoader()来 获取自己的类加载器。当一个类加载器要加载一个类时,它会先委托自己的父类加载器来加载,只有当父加载器无法加载类时,才会自己去加载。例如我们写了一个类Student,它的类加载器是System ClassLoader,它首先会委托给它的父加载器即Extension ClassLoader,然后Extension ClassLoader又会委托给它的父加载器BootStrap ClassLoader,启动类加载器无法加载这个类,交给拓展类加载器,拓展类加载器也无法加载,然后才轮到系统类加载器进行加载。

    • 可见性:可见性指的是父加载器无法利用子加载器加载的类,而子加载器可以利用父加载器加载的类。

    • 单一性:一个类只会被一个类加载器加载一次,不会被重复加载。

获取一个Class对象的方法

Class 对象是单例的,即在java虚拟机中,每一个类都有且只有一个对应的Class对象

package annotation;

/**
 * Created by IntelliJ IDEA
 *
 * @author manzuo
 * @date 2020/9/30 2:42
 */
public class Test01 {


    public static void main(String[] args) throws ClassNotFoundException {
        // 方式一 通过对象获得
        Person person  = new Person();
        Class c1 = person.getClass();
        System.out.println("c1:"+c1.hashCode());
        //方式二 通过forName 方法获得(该方法会抛出一个ClassNotFoundException异常
        Class c2 = Class.forName("annotation.Person");
        System.out.println("c2:"+c2.hashCode());
        //方式三 通过类名.class得到
        Class c3 = Person.class;
        System.out.println("c3:"+c3.hashCode());
        // 方式四:基本内置类型的包装类独有 通过Type属性获取
        Class c4 = Integer.TYPE;

    }
}
class  Person{
    private int age;
    private String name;

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

    public Person() {
    }
}

动态创建对象执行方法

package com.manzuo.reflection;

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

/**
 * Created by IntelliJ IDEA
 *
 * @author manzuo
 * @date 2020/10/9 10:36
 */
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //获取Class<User> 对象
        Class c1 = Class.forName("com.manzuo.reflection.User");
        //调用默认的无参构造方法生成一个user对象
        //类必须有一个无参的构造方法,且非私有
        //User user =(User)c1.newInstance();
        //System.out.println(user);

        //通过指定的构造器创建对象
        Constructor constructor = c1.getConstructor(int.class, String.class);
        User user2 = (User) constructor.newInstance(10,"哈哈");
        System.out.println(user2);
        //通过反射调用对象的普通方法
        //1.通过反射获取方法,参数为方法名称和方法参数类型
        Method setName = c1.getDeclaredMethod("setName", String.class);
        //2.调用invoke方法通过一个具体的对象、参数来调用方法
        setName.invoke(user2,"反射调用setName");
        System.out.println(user2.getName());

        //通过反射操作属性
        User user3 = (User)c1.newInstance();

        Field name = c1.getDeclaredField("name");
        //因为name是私有类型的,需要先关闭安全检测
        name.setAccessible(true);
        name.set(user3,"aaa");
        System.out.println(user3);

    }

}
class User {
    private int age;
    private String name;

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

    public User() {
    }

    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;
    }
    private void test(){
        System.out.println("私有方法");
    }

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

注解

  • 引入: 从jdk1.5 开始引入的技术
  • 作用:
    • 不是程序本身,可以对程序做出解释
    • 可以被其他程序(比如编译器等)读取
  • 格式:
    • 注解是以“@注释名”在代码中存在的,还可以添加一些参数值,如@GetMapping(value = “index”);
  • 使用范围
    • 可以附加在package、calss、method、filed等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问
常用的内置注解
  • @Override : 只适用于修饰方法,表示该方法重写了父类的方法.
  • @Deprecated: 可以修饰方法,属性,类;表示不鼓励程序猿使用这样的元素,通常是因为他很危险或者存在更好的选择
  • @SuppressWarnings,可以修饰类,方法,属性 用来抑制编译时的警告信息
元注解
  • 作用:负责注解其他注解,java定义四个标准的meta-annotation类型,他们被用来提供对其他annotation类型作说明.
  • 所在包: java.lang.annotation
  • 具体注解:
    • @Target :用于描述注解的使用范围(即被描述的注解可以用在什么地方)
    • @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(SOURCE< CLASS < RUNTIME,分别指源码时有效、class文件时有效、运行时有效)
    • @Document:说明该注解将被包含子啊javadoc中(是否生成文档注释)
    • @Inherited:说明子类可以继承父类中的该注解
关于Retention三个参数的区别
  • RetentionPolicy.SOURCE : 注解只在源码级别有效,编译时不会被写入Class文件中.此类注解一般作为标记使用,提高代码的可读性,比如@Override注解
  • RetentionPolicy.Class :在编译的过程中保留并且会写入Class文件中,但是JVM在加载类的时候不需要将其加载为运行时可见的(反射可见)的注解
  • RetentionPolicy.RUNTIME: 该注解一直到运行时也有效,也可以被反射获取到
自定义注解
  • 总结
    • @interface 用来声明一个注解,格式: public @interface 注解名 {…}
    • 其中的每一个方法实际上是声明了一个配置参数,方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型,Class,String,enum)
    • 可以通过default来声明参数的默认值
import java.lang.annotation.*;

/**
 * Created by IntelliJ IDEA
 *
 * @author manzuo
 * @date 2020/9/30 2:43
 */
//定义一个注解
// Target定义该注解可以用在哪些地方
// ElementType.TYPE表示可以标注在类上
@Target(value = {ElementType.TYPE,ElementType.METHOD})
// Retention 定义该注解在什么地方还有效
@Retention(RetentionPolicy.RUNTIME)
//Documented 定义是否将该注解生成在Javadoc中
@Documented
// 子类可以继承父类的注解
@Inherited
public @interface MyAnnotation {
    //注解的参数 : 类型参数 + 参数名 ()
    // 可以用default设置默认值
    //如果只有一个参数,推荐参数名设置为value,使用注解的时候可以省略参数名
    String name();
    int age() default 0;
    int id() default -1;
    String[] schools() default {""};

}

使用自定义注解以及通过反射获取注解内容

  • 定义注解
import java.lang.annotation.*;
/**
 * Created by IntelliJ IDEA
 *
 * @author manzuo
 * @date 2020/10/10 20:01
 */


//只能标注在变量域中
@Target(value = {ElementType.FIELD})
//运行时有效
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SQL{
    String name();
    String db_type();
    int length();
}
  • 使用注解及通过反射获取注解
package annotation;

import javax.xml.ws.soap.Addressing;
import java.lang.annotation.*;
import java.lang.reflect.Field;

/**
 * Created by IntelliJ IDEA
 *
 * @author manzuo
 * @date 2020/9/30 2:42
 */

public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("annotation.Person");
        //获取标注在Person类上的所有注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        //获取指定注解的里的value
        MyAnnotation myAnnotation = (MyAnnotation)c1.getAnnotation(MyAnnotation.class);
        System.out.println("MyAnnotation注解里的属性为: name="+myAnnotation.name()+" id="+myAnnotation.id());
        //获取标注在属性上的注解
        Field name = c1.getDeclaredField("name");
        SQL sql = (SQL) name.getAnnotation(SQL.class);
        System.out.println("SQL注解里的属性为: name="+sql.name()+" db_type="+sql.db_type()+" length="+sql.length());
    }
}
@MyAnnotation(name = "test",id = 111)
class  Person{
    @SQL(name = "age",db_type = "int",length = 10)
    private int age;
    @SQL(name = "name",db_type = "varchar",length = 20)
    private String name;

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

    public Person() {
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值