java 反射 注解

1. 反射

  1. 定义:
    将类的各个组成部分封装成对象,这就是反射机制
    好处:可以在程序运行时操控这些对象;可以解耦,提高程序的可扩展性。
  2. 过程:
    如下图所示:java代码在计算机中所经历的三个阶段
    一个类经过 java.c 的编译形成 .class结尾的字节码文件,此时字节码文件存储在硬盘中,由类加载器 class.loader加载进内存,形成一个class类对象,将里面的成员变量,构造方法和成员方法分别封装为field数组,constructor数组和method数组对象,由class类对象创建我们需要的对象。

我们都知道java程序写好以后是以.java(文本文件)的文件存在磁盘上,然后,我们通过(bin/javac.exe)编译命令把.java文件编译成.class文件(字节码文件),并存在磁盘上。
但是程序要运行,首先一定要把.class文件加载到JVM内存中才能使用的,我们所讲的classLoader,就是负责把磁盘上的.class文件加载到JVM内存中

在这里插入图片描述

  1. class对象的获取方法
    1. 通过 Class.forName(“全类名”) 获取 :一般用于资源的加载(可以在一个类还没有被加载时加载它)
    2. 通过类名 . class 获取 (这样不用创建对象也可以获得class引用)
    3. 通过对象 . getClass() 方法获取
        Class aClass = Class.forName("反射.com.explam.Person");
        
        Class personClass = Person.class;
        
        Person person = new Person();
        Class aClass1 = person.getClass();

类字面常量(类.class)详解:

  1. 它更简单也更安全,因为在编译期就会受到检查。
  2. 不仅可以应用于普通的类,也可以应用于接口,数组以及基本数据类型。另外对于基本数据类型的包装类,还有一个标准字段TYPE(int.class == Integer.TYPE)
  3. 仅使用.class语法来获得对类的引用不会引发初始化,由下代码可见,编写Initable.class后并未初始化Initable类,再调用了“编译器常量”staticFinal(不需要初始化就可以读取)任未初始化,调用了staticFinal2变量后才开始类的初始化(因为staticFinal2不是“编译期常量”)
class Initable{
  static final int staticFinal = 47;
  static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
  static{
    System.out.println("Initializing Initable");
  }
}

public class test(){
  Class initable = Initable.class;
  System.out.println("Initable.staticFinal");
  System.out.println("lalala");
  System.out.println("Initable.staticFinal2");
}

输出:

47
lalala
Initializing Initable
439(随机数)
  1. 应用
    JDBC中:用于加载驱动
Class.forName("com.mysql.jdbc.Driver");

BaseDao.class.getClassLoader().getResourceAsStream():通过类加载器在classPath目录下获取资源db.properties.并且是以流的形式。

Properties properties = new Properties();
InputStream resource = BaseDao.class.getClassLoader().getResourceAsStream("db.properties");
properties.load(resource);

2. 注解

  1. 内置注解:
    @Override:表示当前的方法定义将覆盖父类中的方法。(重写)
    @Deprecated:如果使用了注解为它的元素,编译器会发出警告。
    @SuppressWarnings:关闭编译器警告信息。
  2. 元注解:
    @Target:表明该注解可以用于什么地方。可选的ElementType参数:CONSTRUCTOR,FIELD(域声明),LOCAL_VARIABLE,METHOD,PACKAGE,PARAMETER(参数声明),TYPE(类,接口或enum声明),ANNOTATION_TYPE(能给注解使用)
    @Retention:表示需要在什么级别保存该注解信息。可选的RententionPolicy参数:SOURCE:注解将被编译器丢失 ;CLASS:注解在class文件中可用,但会被VM丢弃; RUNTIME:vm将在运行期也保存注解,可以通过反射机制读取到注解信息。
    @Documented:将此注解包含在javadoc中。
    @Inherited:允许子类继承父类中的注解。
    有专门的类型编写注解,因为一个java文件中只能有一个public修饰的类。
    在这里插入图片描述
    自定义注解的方法:
    元注解就是自定时注解的属性,内部定义使用注解时传入的参数(id,name),其中为name设置了默认值。
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface test {
    public int id();
    public String name() default "woo" ;
}

3. 重学反射:

此处学习的是韩顺平老师的反射课程。

3.1 应用场景:

我们有这样的一个需求:想根据文件中的指定信息,创建一个dog的对象并调用其的“hi”方法。

classfullpath = com.w.demo.dog
method = hi

方法1:
我们第一个想到的方法一定是用new对象的方法实现,但是使用new方法创建对象可能会导致代码的耦合性增高。
方法2:
通过io流获得配置文件,进而获得配置文件中的信息:

public void getProperties() throws IOException {
        Properties properties = new Properties();
        properties.load(new FileInputStream("src//re.properties"));
        String classfullpath = properties.get("classfullpath").toString();
        String method = properties.get("method").toString();
    }

配置信息是获取到了,但是我们发现无法往下操作,因为我们获取到的是String类型的配置信息。

在很多框架结构中(比如spring),很多地方都用到了这种思想(OCP开闭原则:即通过外部的配置文件,在不改变代码本身的情况下,对程序功能进行扩展。就是功能是开放的,代码是“闭上”的)此时我们就需要使用反射

3.2 反射入门及原理:

public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Properties properties = new Properties();
        properties.load(new FileInputStream("src//re.properties"));
        String classfullpath = properties.get("classfullpath").toString();
        String methodName = properties.get("method").toString();

//        加载类,返回class类型的对象 aclass
        Class<?> aClass = Class.forName(classfullpath);
//        通过 aclass 获得你想加载的类的实例对象
        Object o = aClass.newInstance();
//        虽然o的类型是object,但 getClass方法获得的是运行时的变量,所以获得的是我们想要的变量
        System.out.println(o.getClass());
//        根据 aclass 获得 你指定的类中的指定的方法对象
//        此处获得的是一个方法的对象(万物皆对象)
        Method method1 = aClass.getMethod(methodName);
        System.out.println("======================================");
        method1.invoke(o);//一般是通过对象调用方法,这里是通过方法的对象调用类的对象
    }

运行结果:
在这里插入图片描述
反射原理:
加载完类后,就在堆中产生了一个class类型的对象(一个类只有一个class对象),这个对象包含了类的所有信息,就像照镜子一样可以通过这个class对象获得类的全部结构。
在这里插入图片描述
反射相关类:
Class:Class对象表示某个类加载进内存后在堆中出现的对象。
Constructor:代表类的构造方法,注意无参和有参构造的写法区别
Field:代表成员变量,不能直接获得私有的成员变量
Method:代表成员方法

//        获得构造方法
        Constructor<?> constructor = aClass.getConstructor(); //无参构造
        System.out.println(constructor);
        Constructor<?> constructor2 = aClass.getConstructor(String.class);//有参构造
        System.out.println(constructor2);

//        获得成员变量,因为name是私有变量,所以会报错,无法获得私有变量
//        Field name = aClass.getField("name");
        Field ageField = aClass.getField("age");
        System.out.println(ageField.get(o));//这里和method一样都是反过来用成员变量的对象掉class实例对象

代码的运行结果:在这里插入图片描述
反射优化:
反射缺点:基本是解释执行,对执行速度有影响。
解决方法:反射的一些类对象有setAccessible()方法,作用是启动和禁用访问安全检查的开关。如果我们将参数值设置为true,则表示反射对象在使用是取消访问检查,会加快一点速度,

3.2 Class类分析:

  1. Class就是一个普通的类。
    在这里插入图片描述
  2. Class类的对象不是new出来的,是系统创建的
本质上是通过ClassLoader类中的loadClass方法
public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
  1. 一个类的Class类对象,在内存中只有一份,因为类只加载一次。
  2. 每个类的实例会记得自己是由哪个Class实例所生成。
  3. 通过Class的一系列API可以获得一个类的完整结构。
  4. Class类对象是存在堆中的。
  5. 类的字节码二进制数据是放在方法区的。

获得Class的方法:
java程序在计算机中有三个阶段,每个阶段对应了一个获得class的方法
第一个编译阶段: Class.forName()方法:适用于根据配置文件加载类的方式,知道了指定类的全路径

String classPath = "com.w.demo.dog";
Class<?> aClass1 = Class.forName(classPath);

第二个类加载阶段:类.Class方法:适用于参数传递

Class<dog> aClass2 = dog.class;

第三个运行阶段:对象.getClass方法

dog dog = new dog();
Class<? extends com.w.demo.dog> aClass3 = dog.getClass();

其他还有:
通过类加载器获得Class对象:

ClassLoader classLoader = dog.getClass().getClassLoader();
Class<?> aClass4 = classLoader.loadClass(classPath);

通过基本数据类型获得Class对象:

Class<Integer> aClass5 = int.class;

通过包装类获得Class对象:

Class<Integer> aClass6 = Integer.TYPE;

3.3 类加载:

  1. 类加载分为:
    静态加载:在编译期加载需要的类,如果类不存在或者找不到就会报错,依赖性强。(普通通过new创建对象
    动态加载:在运行时加载需要的类,只有执行到这段代码时才会进行类加载,运行不到就不加载,降低了依赖性。(反射
    如下面代码所示:
    此代码会直接报错,因为1的情况没有导入cat的包,也没有cat这个类,把1中的代码注释掉则不会报错了,因为2中是动态加载,只有输入2的情况才会报classNotFound的错。
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Scanner scanner = new Scanner(System.in);
        int i = scanner.nextInt();
        switch (i){
            case 1:
            //静态加载
                cat cat = new cat();
                cat.hi();
                break;
                
            case 2:
            //动态加载
                Class<?> cat1 = Class.forName("cat");
                Object o = cat1.newInstance();
                Method hi = cat1.getMethod("hi");
                hi.invoke(o);
                break;
            case 3:
                System.out.println("hi");
        }
    }

类加载的三个阶段:

在这里插入图片描述

  • 加载:将字节码从不同的数据源(Class文件、也可能是jar包,甚至是网络)转换为二进制字节流加载到内存中,并生成一个代表该类的Class对象。
  • 连接:
    1.校验:进行安全校验(确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并不会危害虚拟机的自身安全)
    注:正是这一步耗时相对较长,可以考虑使用 -Xverify:none来关闭大部分的类验证措施,缩短虚拟机类加载的时间。
    2.准备:给静态变量,分配内存并初始化(根据数据类型的默认初始值给予默认值),这些变量所使用的内存都在方法区中进行分配。
    3.解析:JVM将常量池内的符号引用替换为直接引用的过程。
//i1是实例属性,不在准备阶段赋值,不会分配内存
public int i1 = 10;
//i2是静态变量,在准备阶段被赋值为0;
public static int i2 = 20;
//i3是final修饰的静态变量,根据final的特性,这里的i3在准备阶段被赋值为30
public static final int i3 =30;
  • 初始化:
    1.在这个阶段才开始真正执行java代码,这个阶段会有一个clinit方法,会依次的收集程序中的静态变量和静态代码块中的语句,并对其进行自定义的赋值的初始化和合并。
    2 虚拟机会保证一个类的clinit方法在多线程环境中被正确的加锁、同步,如果有多个线程同时的去初始化一个类,那么只有一个线程会去执行这个类的clinit方法,其他线程会阻塞等待,直到活动线程执行完clinit方法(对应了一个类只会有一个Class对象)
public class B {
//clinit方法内:
//	System.out.println("静态代码块");
//	num = 300(被覆盖)
//	num = 100 ,所以最后的num值为100(因为是按顺序的)
    static {
        System.out.println("静态代码块");
        //为什么这里不会报错呢,因为在连接阶段,num已经被加载进内存了
        num = 300;
    }
    public static int num = 100;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值