Java SE 学习笔记-Day 09

1 注解基本概念

  • 注释(comment):给人看的
  • 注解(Annotation):不是程序本身,可以对程序做出解释,给程序看的,同时可以被其他程序读取。
  • 以"@注释名"存在的,如@Override
  • 注解可以附加在package,class,method,filed等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制实现对这些元数据的访问

2 内置注解

@Override:重写的注解

@Deprecated:不推荐使用,但是可以使用,或许存在更好的方式

@SuppressWarnings:用来抑制编译时的警告信息。这个注释需要添加一个已经定义好了的参数。image-20210319190222317

元注解:负责注解其他注解的注解

  • @Target: 用于描述被注解的注解的使用范围
  • @Retention: 表示需要在什么级别保存该注释信息,描述注解的生命周期
    • source < class <runtime,一般都使用runtime,让运行时,注解也存在
  • @Documented: 说明该注解包含在javadoc中
  • @Inherited: 说明子类可以继承父类中的该注解

自定义注解:使用@interface自定义一个注解,其将自动继承java.lang.annotation.Annotation接口

  • 注解参数定义格式:参数类型 + 参数名();

  • 可以使用default指定一个默认值,当有默认值时,使用注解可以不用显示传参,没有时,一定要给定参数

  • 只有一个参数时,参数命名为value,就可以在使用时,不用写value = ,直接写实参即可

@Explain(a = 3)
@Another("可以省略写value =")
public class MyAnnotation {
}


// 定义一个注解
// Target 表示我们的注解可以用在哪些范围,通过枚举类型
@Target(value = {ElementType.TYPE,ElementType.METHOD})
// Retention 表示注解的生命周期,一般使用runtime,可以被反射读取
@Retention(value = RetentionPolicy.RUNTIME)
// Documented 表示注解可以被生成javadoc
@Documented
// Inherited 表示注解可以被子类继承
@Inherited
@interface Explain{
    String name() default "";
    int a();
    String[] mans() default {"Bob","Peter"};
}

@Target(value = {ElementType.TYPE,ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
@interface Another{
    String value();
}

3 反射概述

动态语言:在运行过程中也能改变程序结构,例如新的函数、对象、数据类型

静态语言:程序编译之后不可改变,如C,C++,Java

Java不是动态语言,是“准动态语言”。由于有了反射机制Java程序可以获得类似动态语言的特性。

image-20210319201228862

Class类

Java中的每一个类在加载后都存在一个且只有一个Class类型的对象,一个Class对象包含了某些特定的结构,如属性,方法,构造器,父类等信息。

image-20210319202410941

image-20210319202725755

创建Class的方式
Person person = new Student();

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

// 方法二:通过Class.forname(),传入包名+类名获得
Class c2 = Class.forName("reflection.Student");

// 方法三:通过类名.class获得
Class c3 = Student.class;

// 方法四:通过基本内置类型的包装类的TYPE属性获得
Class c4 = Integer.TYPE;

// 方法五:获得父类类型
Class c5 = c1.getSuperclass();

image-20210319215406819

Java内存模型

方法区是一个特殊的

image-20210319215528351

注意:这里的class是指类型信息,不是Class类对象,Class类对象在堆中。

类型信息是一个java类的描述信息(class mate),classloader加载一个类时从class文件中提取出来并存储在方法区。它包括以下信息:

  1. 类型的完整有效名,类型的修饰符(public,abstract, final的某个子集),类型直接接口的一个有序列表及继承的父类。

类型名称在java类文件和jvm中都以完整有效名出现。在java源代码中,完整有效名由类的所属包名称加一个”.”,再加上类名组成。例如,类Object的所属包为java.lang,那它的完整名称为java.lang.Object,但在类文件里,所有的”.”都被斜杠“/”代替,就成为java/lang/Object。完整有效名在方法区中的表示根据不同的实现而不同。

  1. 类型的常量池( constant pool):常量池里面包含了实际的常量,也含有一些方法、类的符号引用

  2. 域(Field)信息

  3. 方法(Method)信息

  4. 除了常量外的所有静态(static)变量

  5. classloader的引用

Classloader加载一个类并把类型信息保存到方法区后,会创建一个Class对象,存放在堆区的,不是方法区

类的加载与ClassLoader的理解

我们的实例对象是通过 Class对象 来创建的, Class 对象被创建在堆中。

如果说类是对象抽象和集合的话,那么Class类就是对类的抽象和集合。Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。

image-20210319220422535

image-20210319220439200

1)Loading(载入)

JVM 在该阶段的主要目的是将字节码从不同的数据源(可能是 class 文件、也可能是 jar 包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的 java.lang.Class 对象()。

2)Verification(验证)

JVM 会在该阶段对二进制字节流进行校验,只有符合 JVM 字节码规范的才能被 JVM 正确执行。该阶段是保证 JVM 安全的重要屏障,下面是一些主要的检查。

  • 确保二进制字节流格式符合预期(比如说是否以 cafe bene 开头)。
  • 是否所有方法都遵守访问控制关键字的限定。
  • 方法调用的参数个数和类型是否正确。
  • 确保变量在使用之前被正确初始化了。wwww
  • 检查变量是否被赋予恰当类型的值。w

3)Preparation(准备)

JVM 会在该阶段对类变量(也称为静态变量,static 关键字修饰的)分配内存并初始化(对应数据类型的默认初始值,如 0、0L、null、false 等)。

也就是说,假如有这样一段代码:

public String chenmo = "沉默";
public static String wanger = "王二";
public static final String cmower = "沉默王二";

chenmo 不会被分配内存,而 wanger 会;但 wanger 的初始值不是“王二”而是 null

需要注意的是,static final 修饰的变量被称作为常量,和类变量不同。常量一旦赋值就不会改变了,所以 cmower 在准备阶段的值为“沉默王二”而不是 null

4)Resolution(解析)

该阶段将常量池中的符号引用转化为直接引用。

what?符号引用,直接引用?

符号引用以一组符号(任何形式的字面量,只要在使用时能够无歧义的定位到目标即可)来描述所引用的目标。

在编译时,Java 类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如 com.Wanger 类引用了 com.Chenmo 类,编译时 Wanger 类并不知道 Chenmo 类的实际内存地址,因此只能使用符号 com.Chenmo

直接引用通过对符号引用进行解析,找到引用的实际内存地址。

5)Initialization(初始化)

到了此阶段,才真正开始执行类中定义的Java程序代码。用于执行该类的静态初始器和静态初始块,如果该类有父类的话,则优先对其父类进行初始化。

什么时候会发生类初始化

image-20210319225742442

public class TestClass {
    static {
        System.out.println("main类被加载");
    }
    
    // 虚拟机启动,就会加载main方法所在类
    public static void main(String[] args) {
        System.out.println("进入main函数");
        // 当访问类变量时,所在类初始化
        System.out.println(Hello.m);
    }
}

class Hello{
    // 类变量(static)的赋值顺序会影响最后的值,在类加载的初始化阶段,clint会将这些静态赋值语句按顺序抽出。
    static {
        System.out.println("加载子类的static代码块");
        m = 32;
    }
    static int m = 20;
}

执行结果

image-20210319230848037

而常量(static final)在常量池中,所以使用它不会触发类加载。

public class TestClass {
    static {
        System.out.println("main类被加载");
    }
    public static void main(String[] args) {
        System.out.println("进入main函数");
        System.out.println(Hello.m);
    }
}

class Hello{
    static {
        System.out.println("加载子类的static代码块");
    }
    final static int m = 20;
}

运行结果

image-20210319231242287

使用类声明数组也不会加载,如下。因为数组声明,此时只是声明了一个引用变量(存放对象的地址)的数组,没有使用到类。

public class TestClass {
    static {
        System.out.println("main类被加载");
    }
    public static void main(String[] args) {
        System.out.println("进入main函数");
        Hello [] array = new Hello[10];
    }
}

class Hello{
    static {
        System.out.println("加载子类的static代码块");
    }
}
类加载器

image-20210319232451038

Java平台核心库在 rt.jar 中

JVM中提供了三层的ClassLoader:

Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。

ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。

AppClassLoader:主要负责加载应用程序的主函数类

双亲委派机制

image-20210319234840908

首先,检查是否已经被系统类加载器(AppClassLoader)加载过, 存在父加载器,递归的交由父加载器, 判断是否加载过;若加载就不再加载,若一直没有加载,从引导类加载器开始,逐级向下判断是否可以加载,若可以加载,就加载,若一直到最底层都没有加载器可加载,将跑出ClassNotFoundException

为什么要设计这种机制?

这种机制的作用:

  • 防止类重复加载
  • 保护程序安全,防止核心的API类被篡改,自己定义一个java.lang.String不能被加载

4 动态创建对象

通过Class对象来使用它的构造器来创建实例对象,并可以操纵方法与属性等。

// 获得一个Class对象
Class c = Class.forName("reflection.Person");

// 通过Class对象构造一个实例
Person person = (Person) c.newInstance(); // 调用了Person的无参构造器
System.out.println(person);

//先通过Class对象的getDeclaredConstructor()返回一个构造器,再通过构造器来创建一个对象
Constructor constructor = c.getDeclaredConstructor(String.class, String.class); //指定参数列表匹配构造器
Person p2  = (Person) constructor.newInstance("lpf", "man");
System.out.println(p2.getName());

// 通过Class对象获取该类的一个方法
Method setName = c.getDeclaredMethod("setName", String.class);
// 通过方法的invoke,指定执行这个方法的对象与传入方法的参数
setName.invoke(p2,"lpf");
System.out.println(p2.getName());

// 通过Class对象获取该类的一个属性
Field name = c.getDeclaredField("name");
// 通过设置访问检查是否开启,可以访问类的私有属性,方法也是同理
name.setAccessible(true); // 传入true时,关闭访问权限检查。默认是开启权限检查的,所以要显式使用
name.set(p2,"属性");
  • 通过反射调用方法的效率比正常调用方法慢得多,但关闭访问权限检查,能提高一些。

5 反射操作泛型

public class Generic {
    public void test1(Map<String, Integer> student, List<String> course){

    }

    public Map<String, Integer> test2(){
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        // 1.获取包含泛型参数的方法
        Method method = Generic.class.getMethod("test1", Map.class, List.class);
        // 2.使用Type类型的数组接受所有泛型参数
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        for (Type ParameterTypes : genericParameterTypes) {
            System.out.println(ParameterTypes);
            // 3.对每个泛型参数判断是否为参数化类型
            if (ParameterTypes instanceof ParameterizedType)
            {
                // 4.类型转换为参数化类型后,返回所有当作参数的类型
                Type [] actualtype = ((ParameterizedType) ParameterTypes).getActualTypeArguments();
                for (Type type :
                        actualtype) {
                    System.out.println(type);
                }
            }
        }

        Method test2 = Generic.class.getMethod("test2", null);
        // 获取方法的返回值泛型类型
        Type genericReturnType = test2.getGenericReturnType();
        System.out.println(genericReturnType);
        if (genericReturnType instanceof ParameterizedType)
        {
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }
    }
}

6 反射操作注解

ORM(Object Relationship Mapping) 对象关系映射

image-20210324181653660

通过反射可以读取类的所有信息,包括注解,而在注解中可以对类、属性、方法进行给定参数描述,通过反射机制读出注解的参数信息,可以交由其他框架处理或生成数据库。

@MyAtonotion(name = "a class",length = "1222")
public class Testall  {

    @FiledAnnotation(name = "a word for description")
    private int m;

    public static void main(String[] args) throws NoSuchFieldException {
        MyAtonotion annotation = Testall.class.getAnnotation(MyAtonotion.class);
        System.out.println(annotation.name());
        System.out.println(annotation.length());

        Field m = Testall.class.getDeclaredField("m");
        FiledAnnotation filedAnnotation = m.getAnnotation(FiledAnnotation.class);
        System.out.println(filedAnnotation.name());
    }
}


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAtonotion{
    String name();
    String length();
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FiledAnnotation{
    String name();
}

运行结果:

image-20210324184504766

posted @ 2021-03-27 10:27  蓬飞  阅读(55)  评论(0)    收藏  举报