注解和反射复习——B站

注解

JDK5引入的

作用:1 可以对程序做出解释 2 可以让编译器读取(通过反射)
格式:@XXXX(xxx=xxx)

内置注解

@Override
@Deprecated
@SuppressWarnings:抑制编译时的警告信息

元注解

作用:负责注解其他注解,java定义了4个元注解

  1. @Target:描述注解的使用范围
  2. @Retention:描述注解的声明周期级别(source<class<runtime),一般都是runtime
  3. @Document:描述注解将被包含在javadoc中
  4. @Inherited:描述子类可以继承父类中的该注解

定义一个注解使用@interface

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Demo05 {
    // 定义注解的参数:参数类型 参数名()
    String name() default "pp";
    String[] age();
}

注意:如果注解只有一个参数,则最好该参数名为value,因为value可以省略

反射

Java、C、C++都是静态语言。
Object-C、C#、JavaScript、PHP、Python等都是动态语言,即运行时的代码可以根据条件改变滋生的结构。

Java虽然不是动态语言,但是可以通过反射机制获得一定的动态特性。 可以实现动态编译和创建对象,但是反射削弱了安全性,性能也较慢于直接执行正向操作。

Reflection:是Java动态行式的关键,可以借助反射API获取任何类的内部信息,并且可以操作其中的方法和属性。

类加载方法:Class c = Class.forName("java.lang.String")
类加载进内存,实际上进了方法区,产生一个字节码对象,然后可以实例化该对象,实例化对象存在堆内存中,可以操作该对象。
* 加载类:方法区
* 实例化:堆内存

需要掌握的反射API:
java.lang.Class
java.lang.reflect.Method|Field|Constructor

注意:对象的getClass方法,返回的是用来new的那个类,如:Person p = new Student();System.out.println(p.getClass());返回的是Student类。

获取class的三种方式

  1. 通过对象.getClass()
  2. 通过静态方法:Class.forName(xxx)
  3. 通过字节码:xxx.class

注意:无论通过哪种方式,同一个类的字节码唯一

获取类的父类类型,通过class对象的该方法:getSuperClass()

class对象的范围

以下类型都有class对象:
在这里插入图片描述

Java内存

  • 方法区:所有线程共享,类字节码的动态运行结构和static变量,方法
  • 堆:所有线程共享,new出来的对象
  • 栈:基本数据类型,引用类型的变量(指向堆内存)

类使用过程

当java程序需要某个类的时候,java虚拟机会确保这个类已经被加载、连接和初始化,而连接这个类的过程分为3个步骤。

  1. 加载:class文件加载进入内存,静态代码转换成方法区的运行时数据结构,生成对应的Class对象(比如:java.lang.Class)

  2. 链接:将类的二进制代码与JVM运行状态合并
    a. 验证:该类符合JVM规范
    b. 准备:将static变量分配内存空间并赋初值,在方法区中
    c. 解析:将虚拟机常量池的符号引用替换成引用地址

  3. 初始化
    a. 执行类构造器:<clinit>(),该构造器由编译期自动收集类中所有类变量赋值静态代码块中的语句合并产生的

    类变量和静态代码块都是在构造方法执行前执行的

    b. 初始化时必须从父类开始
    c. JVM保证类构造器方法在多线程中正确加锁和同步

Demo:

public class A {
	public static void main(String[] args){
	B b = new B();
	System.out.println(b.m);
	}
}
public B{
	static {
		System.out.println("B静态代码块");
		m = 300;
	}
	static int m = 100;
	public B(){
		System.out.println("B无参构造");
	}

分析上面过程:

  1. 启动JVM,将A、B两个类加载进内存行程class对象,放在方法区! 堆空间形成两个引用变量:A和B,并指向方法区class对象
  2. 链接过程:开始执行类的静态属性初始化,如int类型则赋值为0。
  3. 初始化过程:合并static代码(静态代码块和类变量),**按顺序!**执行!
  4. 进入主函数,看到new B()代码,然后进入构造方法执行

类的初始化

该过程是类加载进方法区的第三阶段:初始化

当发生下面几种情况时,类会初始化:

  • 虚拟机启动,main方法所在的类会被初始化
  • new 一个类的对象
  • 调用类静态成员和静态方法(除final常量)
  • 使用反射包下的方法
  • 继承关系会层层初始化

当发生下面关系,类不会初始化:

  • 子类引用父类静态变量,子类不会加载,父类会被初始化
  • 定义对象数组,该对象不会被初始化!X[] x = new X[10]
  • 引用常量,不会初始化类,常量在链接阶段已经存入调用类的常量池中了

类加载器

类被加载器加载后,会维持一段时间缓存,如果没有使用,最终会被GC回收

类加载器有三种:

  1. 引导类:用C++编写,JVM自带的,负责Java核心库(rt.jar),无法直接获取
  2. 扩展类:加载jre/lrt.jar)ib/ext下的包或者-D java.ext.dirs目录下的包加载
  3. 系统类:加载java -classpath或-D java.class.path目录下的类包(最常用)

继承关系:引导(Bootstap)>扩展(extension)->系统(System)->自定义
顶级是引导类加载器,java无法直接通过调用getParent方法获取,是C编写的

判断当前类是哪个加载器加载的:
class.forName("xxx").getClassLoader();

获取类加载器可加载的路径:
System.getProperty("java.class.path")

双亲委派机制:为了保护扩展类和核心类的正确安全加载,如果系统类加载器目录中存在其上两种加载器类中存在的类,则不会被加载。简单地说,引导类和扩展类会覆盖系统类中的同全名类。 再简单点说就是:你不配重写我的类!

class类方法

getName:获取类全名
getSimpleName:获取类名
getFields:获取public属性
getDeclaredFields:获取所有属性
getMethods:获取 本类及父类 所有public方法
getDeclaredMethods:获取 本类 所有方法
getConstructors
getDeclaredConstructors

可以指定获取方法,入参为方法名和入参:getMethod("xxx",XXX.class)

反射创建实例和使用

demo:

// 无参构造创建实例,必须保证类有无参构造器
Class c1=Class.forName("xxx");
XXX xxx=(XXX)c1.newInstance();

// 有参构造实例
Constructor con=c1.getDeclaredConstructor(xx.calss,yy.class);
XXX xxx=(XXX)con.newInstance(a,b);

// 调用类方法,相当于调用实例对象的方法
c1.getDeclaredMethod(方法,参数).invoke(实例对象,参数);

// 调用属性
Field 属性名=c1.getDeclaredField(属性名);
属性名.set(对象名,参数);
// 当属性为私有,则需要打开访问权限,如果频繁使用反射,则建议打开可以提升效率
属性名.setAccessible(true)

性能对比

直接Demo:

package com.xiaopi3;

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

public class Demo07 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        test01();
        test02();
        test03();
    }
    public static void test01(){
        User user = new User();
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("01:"+(endTime-startTime)+"ms");
    }
    public static void test02() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class<?> c1 = user.getClass();
        Method getName = c1.getDeclaredMethod("getName", null);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("02:"+(endTime-startTime)+"ms");
    }
    public static void test03() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class<?> c1 = user.getClass();
        Method getName = c1.getDeclaredMethod("getName", null);
        getName.setAccessible(true);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("03:"+(endTime-startTime)+"ms");
    }
}
class User{
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
// 结果
// 01:4ms
// 02:2792ms
// 03:1514ms

获取泛型参数类型

泛型是给javac做安全检查和避免强制转换的,编译完成就会被擦除。但是为了通过反射操作这些类型,新增了几个class类型外的几个类型:

  • ParameterizedType:参数化类型:Map<String,String>
  • GenericArrayType:数组,存的参数化类型或者类变量
  • TypeVariable:各种类型的父类公共接口
  • WildcardType:通配符类型表达式
    这几种类型不属于class,但是和class同级别

Demo:

public class Demo08 {
    public static void main(String[] args) throws NoSuchMethodException {
        Class<? extends Demo08> aClass = Demo08.class;
        Method mt = aClass.getDeclaredMethod("test", Map.class, List.class,int.class,Demo01.class);
        Type[] genericParameterTypes = mt.getGenericParameterTypes();
        // 打印所有参数类型,如果是泛型,打印内部的类类型
        printActuallyType(genericParameterTypes);
        // 打印返回值类类型
        Type genericReturnType = mt.getGenericReturnType();
        Type[] g={genericReturnType};
        printActuallyType(g);
    }
    public static List<Demo02> test(Map<String, List<Object>> map,List<String> list,int a,Demo01 demo01){
        return null;
    }
    public static void printActuallyType(Type[] t){
        for (Type type : t) {
            if(type instanceof ParameterizedType){
                Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
                printActuallyType(actualTypeArguments);
            }else{
                System.out.println(type);
            }
        }
    }
}
//控制台输出
//class java.lang.String
//class java.lang.Object
//class java.lang.String
//int
//class com.xiaopi3.Demo01
//class com.xiaopi3.Demo02

通过反射获取注解和注解参数值

这部分比较简单,直接上图:

模拟ORM:先使用注解(这里是自定义了两个注解)标识:
在这里插入图片描述
通过反射获取值:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值