Java教程

JavaSE——注解、反射、Java内存分析

本文主要是介绍JavaSE——注解、反射、Java内存分析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

注解、反射、Java内存分析

    • 一、注解
        • 什么是注解?
        • Java语言自带的几个基本注解
        • 注解的使用
        • 元注解
        • 自定义注解
    • 二、反射
        • 什么是反射机制?
        • Class类
          • 1. 哪些类型可以有Class对象
          • 2. 获取Class类的实例
          • 3. Class类常用方法
        • 通过反射机制获取类的信息
        • 通过反射机制调用构造器、方法、属性
        • 通过反射机制操作泛型
        • 通过反射操作注解
    • 三、Java内存分析
          • 简易JVM内存模型
          • 深入理解JVM内存模型
          • 类加载过程
          • 什么时候一定会发生类的初始化?(主动引用)
          • 什么时候不会发生类的初始化?(被动引用)
          • 类加载器
          • 双亲委派机制

一、注解

什么是注解?

注解(Annotation)和注释(Comment)很类似,只不过注释是给程序员看的,帮助程序员理解程序代表的意思和逻辑,而注解既是给程序员看的,又是给程序看的,如编译器,程序可以对不同的注解做出与之相应的动作。

Java语言自带的几个基本注解

@Override:修饰方法,表示该方法是对父类方法的重写,编译器会检查该方法的定义方式,保证和父类的方法相同。

@Deprecated:修饰属性,方法,类,表示这些东西已经过时了,有更好的替代方式或者很危险,虽然不推荐但还是可以编译并执行。

@SuppressWarnings:用于抑制编译时的警告信息,后面可以接一些参数:

​ 比如:

​ @SuppressWarnings(“all”)

​ @SuppressWarnings(“unchecked”)

​ @SuppressWarnings({value=“unchecked”,"deprecation“})

注解的使用

//无参数的
@Override
public void test(){}

//有参数的
@SuppressWarnings("all")
class Test{
    int i;
    ...
}

元注解

元注解(meta-anotation)就是对注解进行注解,为注解提供一些说明。

​ 比如:

​ @Target:描述注解的使用范围

​ 需要的参数:

​ ElementType.TYPE、ElementType.FIELD、ElementType.METHOD、ElementType.PARAMETER等等。

​ @Retention:描述注解的生命周期。编译时存在,还是类加载存在,还是运行时存在。(SOURCE<CLASS<RUNTIME)

​ 需要的参数:

​ RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME

​ @Document:说明该注解将包含在javadoc中。

​ @Inherited:说明子类可以继承父类的该注解。

自定义注解

//注解的使用
@MyAnnotation01(name = "ace")
public class Test01{
    
}

//元注解
@Target({ElementType.METHOD,ElementType.TYPE})  //表示我们的注解只能用在方法、类型上,类也是一个类型。
@Retention(RetentionPolicy.RUNTIME) //表示我们的注解运行时任然有效,通常都是使用这个。
//自定义注解
@interface MyAnnotation01{
    //注解的参数,参数的数据类型只能是基本数据类型,Class,String,Enum
    String name();

    //参数可以有默认值
    int sex() default 1;
}
//注解的使用
@MyAnnotation02({1,2,3})
public class Test01{

}

//元注解
@Target({ElementType.METHOD,ElementType.TYPE})  //表示我们的注解只能用在方法、类型上,类也是一个类型。
@Retention(RetentionPolicy.RUNTIME) //表示我们的注解运行时任然有效,通常都是使用这个。
//自定义注解
@interface MyAnnotation02{
    //当参数只有一个的时候,推荐使用value做参数名,这样使用的时候可以省略参数名。
    int[] value();
}

二、反射

什么是反射机制?

反射(Reflection)就如同照镜子一样,照镜子能得到我们清晰、完整的模样,反射机制能得到一个类的完整结构,我们就可以操作任意该类对象的所有属性和方法。

Class类

​ a. 我们通过反射机制获得的是一个Class类对象,该类保存了一个类的完整结构。

​ b. Class在类被加载的时候创建,且一个类只有一个Class对象,所有类实例的Class对象都是相同的。

注意是Class,而不是class,小写的class是关键字

1. 哪些类型可以有Class对象
public class Test {
    public static void main(String[] args) {
        Class c1 = Object.class;        //类
        Class c2 = Comparable.class;    //接口
        Class c3 = int[].class;         //一维数组
        Class c4 = String[][].class;    //二维数组
        Class c5 = Override.class;      //注解
        Class c6 = ElementType.class;   //枚举
        Class c7 = Integer.class;       //基本数据类型
        Class c8 = void.class;          //void
        Class c9 = Class.class;         //Class
    }
}
2. 获取Class类的实例
public class Test02 {
    public static void main(String[] args) throws ClassNotFoundException {
        //通过类的实例获取该类的Class实例
        A a = new A();
        Class classA01 = a.getClass();
        System.out.println(classA01.hashCode());

        //若知道类名,则直接通过类属性class获取该类的Class实例
        Class classA02 = A.class;
        System.out.println(classA02.hashCode());

        //若知道类的全名(包名+类名)则可以通过Class的类方法forName获取该类的Class实例
        Class classA03 = Class.forName("com.ace.test.A");
        System.out.println(classA03.hashCode());

        //基本数据类型的包装类的Class实例的获取可以通过包装类中的TYPE属性获取
        Class classInt = Integer.class;
        System.out.println(classInt.hashCode());

        //通过子类的Class实例获取父类的Class实例
        Class classB = classA01.getSuperclass();
    }
}

class A extends B{
    private int a;
    private void test(){
    }
}
class B {
    int b;
}
3. Class类常用方法

在这里插入图片描述

通过反射机制获取类的信息

//获取类的信息
public class Test06 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        
        Class class01 = Class.forName("com.ace.test.Person");

        //获取类的名字
        System.out.println(class01.getName());  //包名 + 类名 com.ace.test.Person
        System.out.println(class01.getSimpleName());    //类名 Person

        //获取类的属性
        Field[] fields01 = class01.getFields();   //只能得到public,其他权限都不可以
        Field[] fields02 = class01.getDeclaredFields();   //得到全部权限的属性
        //获得指定的属性
        Field field01 = class01.getField("id");
        Field field02 = class01.getDeclaredField("age");

        //获得方法
        Method[] methods01 = class01.getMethods();
        Method[] methods02 = class01.getDeclaredMethods();
        //获得指定的方法,需要传入方法名和形参类型的Class实例对象
        Method method01 = class01.getMethod("test01", int.class, int.class);
        Method method02 = class01.getDeclaredMethod("test02",null); //无参数可以填null

        //获得构造器
        Constructor[] constructors = class01.getConstructors();
        Constructor[] constructors1 = class01.getDeclaredConstructors();
        //获得指定的构造器
        Constructor constructor = class01.getConstructor(null);
        Constructor constructor1 = class01.getDeclaredConstructor(int.class, String.class);
    }
}

class Person{
    public int id;
    private String name;
    protected int age;

    public Person() {
    }

    private Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    private void test01(int x, int y){
    }
    void test02(){
    }
}

通过反射机制调用构造器、方法、属性

public class Test07 {
    //通过反射动态的创建对象
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class classUser = Class.forName("com.ace.test.User");

        //调用newInstance()方法,相当于调用了类的无参构造器
        User user1 = (User) classUser.newInstance();
        //通过反射机制获取本类构造方法,用这个方法去创建对象
        Constructor constructorUser = classUser.getDeclaredConstructor(String.class, String.class);
        User user2 = (User) constructorUser.newInstance("ace123","123");

        //调用普通的方法
        Method method1 = classUser.getDeclaredMethod("test02",null);
        //当调用的方法是私有的时候,需要关闭程序的访问安全检查,Method、Constructor、Field都有这个方法,关闭安全检查可以提高		  程序的运行效率
        method1.setAccessible(true);
        method1.invoke(user1,null); //将该方法授权给user1对象使用,并传入参数的Class对象

        //操作属性
        Field field = classUser.getDeclaredField("name");
        field.setAccessible(true);
        field.set(user2,"ace");     //设置属性的值
        String userName = (String) field.get(user2);    //获得属性的值
        System.out.println(userName);
    }
}

class User{
    private String name;
    private  String passWord;

    public User() {
    }

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

    public void test01(){
        System.out.println("调用了test01()");
    }
    private void test02(){
        System.out.println("调用了test02()");
    }
}

通过反射机制操作泛型

Java采用泛型擦除机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据安全性和免去强制类型转换问题,但是一旦编译完成,所有和泛型相关的信息全部擦除。

为了通过反射操作这些类型,Java新增了几种类型来代表不能归到Class类中的类型但是又和原始类型齐名的类型。

  1. ParameterizedType:表示一种参数化的类型。
  2. GenericArrayType:表示一种元素类型是参数化类型或者类型变量的类型。
  3. TypeVarible:各种类型变量的公共父接口。
  4. WildcardType:表示一种通配符类型表达式。
public class Test08 {
    public Set<Integer> test01(Map<Integer,String> map, List<String> list){
        System.out.println("调用了test01()");
        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) {
            System.out.println(genericParameterType);
            输出:
                java.util.Map<java.lang.Integer, java.lang.String>
                java.util.List<java.lang.String>
        }*/
        for (Type genericParameterType : genericParameterTypes) {
            if (genericParameterType instanceof ParameterizedType) {
                Type[] actualTypeArguments = ((ParameterizedType)  	genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                    /*
                        输出:
                            class java.lang.Integer
                            class java.lang.String
                            class java.lang.String
                     */
                }
            }
        }

        //获得返回值类型
        Type genericReturnType = method.getGenericReturnType();
        System.out.println(genericReturnType);  //输出java.util.Set<java.lang.Integer>
        if(genericReturnType instanceof ParameterizedType){
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument); //输出class java.lang.Intege
            }
        }
    }
}

通过反射操作注解

什么是对象映射关系?

在这里插入图片描述

public class Test09 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class c = Class.forName("com.ace.test.Student");

        //直接通过反射获得注解,只能获得类外部的注解
        Annotation[] annotations = c.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation); //@com.ace.test.TableAnno(value=t_student)
        }

        //指定注解的类型,获得注解的参数值,也只能获取到类外部的注解
        TableAnno TableAnno = (TableAnno) c.getAnnotation(TableAnno.class);

        //获取注解的参数值
        String value = TableAnno.value();
        System.out.println(value);  //t_student

        //获取类内部的注解
        //先获取到被注解的元素,如Field、Method等等,再获取该元素的某个注解,就可以获取到注解的参数值了
        Field[] Fields = c.getDeclaredFields();
        ColumnAnno columnAnno;
        String name , type;
        int length;
        for (Field field : Fields) {
            //获取所有属性指定的注解的参数值
            columnAnno = field.getAnnotation(ColumnAnno.class); //指定获取该属性的哪个注解
            name = columnAnno.columnName();
            type = columnAnno.type();
            length = columnAnno.length();
            System.out.println(name + type + length);
            /*
                学号int10
                姓名varchar255
                年龄int10
             */
        }

    }
}

@TableAnno("t_student")
class Student{
    @ColumnAnno(columnName = "学号",type = "int",length = 10)
    int num;
    @ColumnAnno(columnName = "姓名",type = "varchar",length = 255)
    String name;
    @ColumnAnno(columnName = "年龄",type = "int",length = 10)
    int age;
}
//类名的注解,类名和表名对应
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface TableAnno{
    String value();
}

//属性的注解,属性和字段对应
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@interface ColumnAnno{
    String columnName();    //列名
    String type();          //字段类型
    int length();        //字段长度
}

三、Java内存分析

简易JVM内存模型

在这里插入图片描述

深入理解JVM内存模型

深入理解JVM—JVM内存模型

类加载过程

​ I. 加载:将类的字节码文件.class读入内存,并为之创建一个Class对象,此过程由类加载器ClassLoader完成。

​ II. 链接:验证(保证安全)–>准备(为静态变量分配内存并为其设置默认值)–>解析(将虚拟机常量池中的符号引用替换为直接 引用)

​ II. 初始化:执行类构造器<clinit>()方法。如果其父类还未初始化,则触发父类的初始化。

注意:类构造器是构造类信息的,不是构造该类对象的构造器,是编译器在编译阶段自动收集类中的类变量赋值语句,和静态代 码块中的语句合成而来的。

public class Test03 {
    public static void main(String[] args) {
        C c = new C();
        System.out.println(C.m);
        /**
         * 初始化过程:
         *  1.加载:加载到内存,产生一个Class对象。
         *  2.链接:链接结束后,m = 0;
         *  3.初始化:
         *      相当于
         *      <clinit>(){
         *          System.out.println("C类静态代码块初始化");
         *          m =10;
         *          m=30
         *      }
         *   所以最后m = 30
         */
    }
}

class C{
    static {
        System.out.println("C类静态代码块初始化");
        m =10;
    }
    static int m = 30;

    public C() {
        System.out.println("C类无参构造初始化");
    }
}
什么时候一定会发生类的初始化?(主动引用)

I. main方法所在类

II. new一个类的对象

III. 调用类的静态属性(static final常量除外)和静态方法

VI. 对类进行反射

V. 当初始化一个类的时候,若父类未初始化,子类会先初始化父类。

什么时候不会发生类的初始化?(被动引用)

I. 通过数组定义类引用

II. 调用类中的常量,因为常量在链接阶段就存入调用类中的常量池中了。

III. 调用静态属性和静态方法的时候,只有真正声明这个静态成员的类才会被初始化。比如通过子类使用父类的静态属性,子类不会初始化。

public class Test04 {
    static {
        System.out.println("main方法所在类被加载!");
    }
    public static void main(String[] args) {
        //一定会初始化,一个类只会被加载一次
        Father father = new Father();
        Class c1 = Father.class;

        //不会初始化
        Son[] sons = new Son[5];
        System.out.println(Son.a);
    }
}
class Father{
    static int a = 5;
    static {
        System.out.println("父类被加载!");
    }
}

class Son extends Father{
    static {
        System.out.println("子类被加载!");
    }
}

/**
* 结果:
*	main方法所在类被加载!
*	父类被加载!
*	5
*/
类加载器

引导类加载器(BootstapClassLoader):负责加载Java核心类库。该加载器无法直接获取。

扩展类加载器(ExtensionClassLoader或者ExtClassLoader):负责jre/lib/ext目录下的jar包。

系统类加载器(SystemClassLoader或者AppClassLoader):负责java -classpath或-D java.class.path所指目录下的jar包,是最常见的类加载器。

public class Test05 {
    public static void main(String[] args) throws ClassNotFoundException {
        //获取系统类加载器
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        System.out.println(classLoader);

        //获取系统类加载器的父类加载器-->扩展类加载器
        ClassLoader classLoader1 = classLoader.getParent();
        System.out.println(classLoader1);

        //获取扩展类加载器的父类加载器-->根类加载器
        ClassLoader classLoader2 = classLoader1.getParent();
        System.out.println(classLoader2);   //获取不到

        //测试当前类是由哪个类加载器加载的
        ClassLoader classLoader3 = Class.forName("com.ace.test.Test05").getClassLoader();
        System.out.println(classLoader3);

        //测试jdk的内置类是由哪个类加载器加载的
        ClassLoader classLoader4 = Class.forName("java.sql.Connection").getClassLoader();
        System.out.println(classLoader4);   //根加载器,获取不到
        
        //获取系统加载器可以加载到的路径
        System.out.println(System.getProperty("java.class.path"));
    }
}
双亲委派机制

java.lang.ClassLoader中的loadClass方法源码:

public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

示意图:

这样设计的好处?

如果有人想替换系统级别的类:String、Object等等。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap ClassLoader加载过了,因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader,所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。

这篇关于JavaSE——注解、反射、Java内存分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!