Java基础-小白成长复习笔记-反射机制

反射(Reflection)机制

因为有了反射机制的存在才使得Java有了动态的特性,Java是静态语言。

  • 反射机制允许程序在执行期间借助Reflection API 取得任何类的内部信息,并能直接操作任何对象的内部属性(包括私有变量和方法)和方法
  • 在加载类完成之后,在堆内存的方法区就产生了一个Class对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息(包括属性、方法、构造器、私有属性等)。

正常方式:

在这里插入图片描述

反射方式:

在这里插入图片描述

public class Test4 {

    public static void main(String[] args) throws ClassNotFoundException {
        Class c1 = Class.forName("com.chen.User");
        Class c2 = Class.forName("com.chen.User");
        System.out.println(c1.hashCode());
        System.out.println(c2.hashCode());

    }
}

Class c2 = Class.forName(“com.chen.User”);

在使用时forName()的参数字符串,指向要获取的实体类的 全限定名 。

获取Class类的实例

  • 若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序型最高

    Class clazz = 类名.class;
    
  • 已知某个类的实例,调用该实例的getClass()方法获取该类的Class对象

    Class clazz = 该类的实例对象.getClass();
    
  • 已知类的全限定名,且该类在该路径下,可通过forName()获取Class对象

    Class clazz = Class.forName("全限定名");
    
  • 内置基本数据类型可以直接通过 类名.TYPE 直接获取Class对象

  • 还可以利用ClassLoader。

//定义一个Person类
class Person{
    public String name;
    public int id;

    public Person() {
    }

    public Person(String name, int id) {
        this.name = name;
        this.id = id;
    }
}
//定义一个Student 类 继承Person类
class Student extends Person{
    public Student(String name){
        this.name = name;
    }
}
//定义一个Teacher类,继承Person类
class Teacher extends Person{
    public Teacher(String name){
        this.name = name;
    }
}
//测试获取Class对象的几种方法
public class Test5 {
    public static void main(String[] args) throws ClassNotFoundException {
        Person student = new Student("学生");
        Person teacher = new Teacher("老师");

        //在知道是具体类情况下,使用 类名.class   获取Class 对象
        Class c1 = Student.class;
        //通过类的具体实例对象,获取Class 对象
        Class c2 = student.getClass();
        //通过全限定名 来获取Class对象
        Class c3 = Class.forName("com.chen.Student");
        //输出Class对象的哈希值
        System.out.println(c1.hashCode());
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ynZwiMBw-1605454002252)(Java%E5%9F%BA%E7%A1%80.assets/image-20201114202233425.png)]

  • 可见一个类只有一个Class对象

所有类型的Class对象

类、接口、一维数组、二维数组、注解、枚举、基本数据类型、void、Class

这些类型都有他们的Class对象

public class Test6 {
    public static void main(String[] args) {
        Class c1 = Object.class;        //类
        Class c2 = Comparable.class;    //接口
        Class c3 = int[].class;         //一维数组
        Class c4 = int[][].class;       //二维数组
        Class c5 = Override.class;      //注解
        Class c6 = ElementType.class;   //枚举
        Class c7 = String.class;        //基本数据类型
        Class c8 = void.class;          //void
        Class c9 = Class.class;         //Class

        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);
        System.out.println(c4);
        System.out.println(c5);
        System.out.println(c6);
        System.out.println(c7);
        System.out.println(c8);
        System.out.println(c9);
        
        //一维数组中虽然有不同的长度和形式,只要数组的维度是一样的,他们的Class对象都是同一个
        int[] n= new int[2];
        int[] n2 = new int[6];
        
        int[][] m = new int[1][2];
        
        System.out.println(n.getClass().hashCode());
        System.out.println(n2.getClass().hashCode());
        System.out.println(m.getClass().hashCode());
    }
}

在这里插入图片描述

类加载(ClassLoader)内存分析

类加载大体分为三步流程:加载、链接、初始化

加载

将生成的Class字节码文件内容加载到到内存中,并将这些静态数据转换成方法区中的运行时数据结构,然后在堆中生成这个类的java.lang.Class 对象

链接

将Java类的二进制代码合并到JVM的运行状态中的过程

验证:确保加载的类信息符合JVM规范,没有安全方面的问题

准备:正式为静态类变量分配内存并设置类变量的默认初始值

解析:将虚拟机常量池的符号引用转换为直接引用

初始化
  • 类构造器() 方法是由编译期间自动收集类中的所有变量赋值动作和静态代码块的语句合并产生的。(类构造器是构造类的信息的,不是构造里对象的构造器)
  • 当初始化一个类的时候,如果发现其夫雷还没有进行初始化,则需要先出发父类的初始化
  • 虚拟机会保证一个类的() 方法在多线程环境中被正确的枷锁和同步
public class Test7 {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(A.m);  //m = 100  
    }
}

class A{
    static {
        System.out.println("A类中的静态代码块初始化");
        m = 300;
    }
    static int m = 100;
    public A(){
        System.out.println("A类构造函数初始化");
    }
}

m = 100 为什么不是300?下面将就此问题进行分析:

在这里插入图片描述

类加载器(重点,面试会被问到)

类加载器的作用

将生成的Class字节码文件内容加载到到内存中,并将这些静态数据转换成方法区中的运行时数据结构,然后在堆中生成这个类的java.lang.Class 对象

类缓存

标准的JavaSE类加载器可以按照要求查找类,一旦某个类被加载到类加载器中,他将维持加载(缓存)一段时间。不过Java中的JVM垃圾回收机制后将这些Class对象回收

在这里插入图片描述

类的初始化

类的主动引用(一定会发生类的初始化)

  • 当虚拟机启动,先初始化main方法所在的类时
  • new一个类的对象时
  • 调用类的静态成员时(除了final常量)和静态方法时
  • 使用Java.lang.reflect包的方法对类进行反射调用时
  • 当初始化一个类,如果其父类没有被初始化,则先会初始化器父类
public class Test08 {
    static{
        System.out.println("main类被加载");
    }
    public static void main(String[] args) throws ClassNotFoundException {
        //创建一个类的对象会主动引用    运行结果对应图一
//        Son son = new Son();
//        System.out.println(son.m);
        
        //使用反射方法会自动调用   运行结果对应图二
//        Class.forName("com.chen.Son");
        //调用一个类的静态变量和静态方法会自动调用(除了final常量)
//        System.out.println(Son.m);    运行结果对应图三
    }
}
class Father{
    static int b =2;
    static {
        System.out.println("父类被加载");
    }
}

class Son extends Father{
    static {
        System.out.println("子类被加载");
        m = 300;
    }
    static int m = 100;
    static final int M = 1;
}

在这里插入图片描述
图一

在这里插入图片描述
图二

在这里插入图片描述
图三

类的被动调用

  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如当通过子类引用父类的静态变量,不会导致子类的初始化
  • 通过数组定义类的引用,不会触发此类的初始化(数组定义只是引用了该类的名称而已,实质上不会是使用到该类的内部内容)
  • 引用常量不会触发此类的初始化(常量在连接阶段就存入调用类的常量池中了)
        //不会产生类的引用
        //t通过子类访问父类的静态常量不会导致子类的初始化  运行结果对应图一
//        System.out.println(Son.b);

       //通过数组定义的子类对象不对对子类和父类产生影响    运行结果对应图二
//        Son[] son = new Son[5];
        //调用类中的final常量不会影响类的加载            运行结果对应图三
//        System.out.println(Son.M);

在这里插入图片描述
图一

在这里插入图片描述
图二

在这里插入图片描述
图三

类加载器

在这里插入图片描述

public class Test8 {
    public static void main(String[] args) throws ClassNotFoundException {
        //获取系统类的加载器 ---->系统类加载器
        ClassLoader  classLoader = ClassLoader.getSystemClassLoader();
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
       
        //获取系统加载器的父类----->扩展类加载器
        ClassLoader parent1=classLoader.getParent();
        System.out.println(parent1); //sun.misc.Launcher$ExtClassLoader@1b6d3586
       
        //获取扩展类加载器的父类加载器----> 引导类加载器
        ClassLoader parent2 = parent1.getParent();
        System.out.println(parent2); //null

        //获取当前类时那个类加载器加载的
        classLoader = Class.forName("com.chen.Test8").getClassLoader();
        System.out.println(classLoader); //sun.misc.Launcher$AppClassLoader@18b4aac2
        
        //获取JDK内置的类时谁加载的
        classLoader =Class.forName("java.lang.Object").getClassLoader();
        System.out.println(classLoader); //null
        
        
        //获取类加载器可以加载的路径
        System.out.println(System.getProperty("java.class.path"));
        
        //C:\Program Files\Java\jdk1.8.0_201\jre\lib\charsets.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\deploy.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\access-bridge-64.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\cldrdata.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\dnsns.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\jaccess.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\jfxrt.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\localedata.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\nashorn.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunec.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunjce_provider.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunmscapi.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunpkcs11.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\zipfs.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\javaws.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\jce.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\jfr.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\jfxswt.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\jsse.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\management-agent.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\plugin.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\resources.jar;
        // C:\Program Files\Java\jdk1.8.0_201\jre\lib\rt.jar;
        // E:\IdeaWorkSpace\JavaSE\project-01\out\production\chapter-01;
        // C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.5\lib\idea_rt.jar
    }
}

Java中的双亲委派机制

什么是双亲委派机制:

当类加载器加载某个.class文件时,会从CustomClassLoader (用户自定义类加载器)逐级的向上级询问是否已经加载过该字节码文件直至最后,如果都不能加载该字节码文件则包错

流程图:

img

获取类的运行时状态(主要掌握方法的使用)

package com.chen;

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

//获取类的相关信息
public class Test09 {
    public static  void  main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class c1 = Class.forName("com.chen.User");
        
        //获取类的名字  包名+类名   getName()
        System.out.println(c1.getName());
       
        //获取类的简单名字   类名   getSimpleName()
        System.out.println(c1.getSimpleName());
        System.out.println("----------------------------------");
        
        //获取类的属性  获取类的public属性   getFields()
        Field[] fields= c1.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }
 
        //获取类的  全部属性                getDeclaredFields()
        fields =c1.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field);
        }
        //获取类的指定属性
        Field name = c1.getDeclaredField("name");
        System.out.println(name);
        System.out.println("----------------------------");

        //获取类的方法  getMethods() 获取该类的所有public方法和父类所有public方法
        Method[] methods = c1.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println("----------------------------");
        
        //获取该类本身所有的方法    getDeclaredMethods()
        for (Method declaredMethod : c1.getDeclaredMethods()) {
            System.out.println(declaredMethod);
        }
        System.out.println("----------------------------");

        //获得指定的方法            getMethod()   getDeclaredMethod()
        Method method = c1.getDeclaredMethod("setName", String.class);
        Method method2 = c1.getDeclaredMethod("getName");
        System.out.println("*****"+method);
        System.out.println(method2);

        System.out.println("----------------------------");

        //获取public构造器         getConstructors()
        Constructor[] constructors =c1.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        System.out.println("----------------------------");

        //获得该类的全部构造器      getDeclaredConstructors()
        constructors = c1.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        System.out.println("----------------------------");

        //获得指定的构造器          getDeclaredConstructor(String.class,int.class,int.class)
        Constructor constructor =c1.getDeclaredConstructor(String.class,int.class,int.class);
        System.out.println(constructor);

        //获取无参构造器            getDeclaredConstructor()
        Constructor constructor2 =c1.getDeclaredConstructor();
        System.out.println(constructor2);
    }
}

运行结果:

com.chen.User
User
----------------------------------
private java.lang.String com.chen.User.name
private int com.chen.User.id
private int com.chen.User.age
private java.lang.String com.chen.User.name
----------------------------
public java.lang.String com.chen.User.toString()
public java.lang.String com.chen.User.getName()
public int com.chen.User.getId()
public void com.chen.User.setName(java.lang.String)
public int com.chen.User.getAge()
public void com.chen.User.setId(int)
public void com.chen.User.setAge(int)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
----------------------------
public java.lang.String com.chen.User.toString()
public java.lang.String com.chen.User.getName()
public int com.chen.User.getId()
public void com.chen.User.setName(java.lang.String)
public int com.chen.User.getAge()
public void com.chen.User.setId(int)
public void com.chen.User.setAge(int)
----------------------------
*****public void com.chen.User.setName(java.lang.String)
public java.lang.String com.chen.User.getName()
----------------------------
public com.chen.User()
public com.chen.User(java.lang.String,int,int)
----------------------------
public com.chen.User()
public com.chen.User(java.lang.String,int,int)
----------------------------
public com.chen.User(java.lang.String,int,int)
public com.chen.User()

创建的 Class 对象能够干什么?

创建Class 类的对象

  • 创建类的对象:调用 Class 对象的 newInstance() 方法 创建类的对象 newInstance()方法默认使用类的无参构造器创建对象
    • 类必须有一个无参数的构造器
    • 类的构造器的访问权限必须需要足够
  • 也可以通过有参构造器也可以创建 Class 对象对应类的对象
    • 通过 Class 类的getDeclaredConstructor() 获取指定的构造器
    • 向构造器的形参中传递一个对象数组,里面包括构造器所需的各个参数
    • 通过Construcor 实例化对象

通过Class类创建的对象调用指定的方法

通过反射调用类中的方法,通过Method类完成

  • 通过 Class 类的 getDeclaredMethod() 方法取得一个Method对象,并设置此方法操作时所需要的参数类型
  • 之后使用Object invoke(Object obj,args)进行调用,冰箱方法中传入要设置的 obj 对象的参数信息
image-20201115173505149

Object invoke(Object obj,args)

  • Object 对原方法中的返回值,若原方法没有返回值,将返回null
  • 若原方法为静态方法,此时形参Object obj 可谓null
  • 若原方法的形参列表为空,args为null
  • 若原方法声明为private,则需要在调用此invoke()方法前显式调用对象的方法setAccessible(true)方法,将可访问private的方

setAccessible()

  • Method、Field、Constructor对象都有setAccessible方法
  • setAccessible()是启动和关闭访问安全检查的开关
  • 参数值设置为true时,表示反射的对象在使用时取消访问安全检查(参数默认是false,开启访问全权限检查)
  • 如果经常调用反射机制的话建议关闭Java访问检查权限,这样会提高反射的效率
package com.chen;

import javax.jws.soap.SOAPBinding;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

//动态创建对象、 反射过程
public class Test10 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //获得Class对象
        Class c1 = Class.forName("com.chen.User");

        //通过Class对象构造一个对象   newInstance() 默认调用无参构造器创建对象
        User user = (User) c1.newInstance();
        System.out.println(user);

        //通过Construtor 构造器创建Class对象
        Constructor constructor = c1.getDeclaredConstructor(String.class,int.class,int.class);
        User user1 = (User) constructor.newInstance("史久辰",1828424041,20);
        System.out.println(user1);

        //通过反射调用普通方法
        User user2 = (User) c1.newInstance();
        Method setName =c1.getDeclaredMethod("setName", String.class);
        setName.invoke(user2,"史久辰1");
        System.out.println(user2.getName());

        //通过反射操作属性
        User user3 = (User) c1.newInstance();
        Field name =c1.getDeclaredField("name");
        
        //取消Java访问安全检测   
        name.setAccessible(true);
        name.set(user3,"十九策划");
        System.out.println(user3.getName());
    }
}

name.setAccessible(true);

如果没有取消安全检测则会报错:

Exception in thread “main” java.lang.IllegalAccessException: Class com.chen.Test10 can not access a member of class com.chen.User with modifiers “private”

反射操作泛型

  • Java采用反省察除机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的问题,但是一旦编译完成,所有和泛性有关的类型就会全部擦除
  • 为了通过反射机制操作这些类型,Java新增了ParameterizedType、GenericArrayType、TypeVariable、WidcardType 几种类型 来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型
  • ParameteriedType:表示参数化的类型,比如Collection<String>
  • GenericArryType:表示一种元素类型是参数化类型或者类型变量的数组类型
  • TypeVariable:是各种变量类型的公共父接口
  • WilcardType:代表一种通配符类型的表达式
package com.chen;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

public class Test12 {

    public static void test01(Map<String,User> map, List<User> list){
        System.out.println("test01");
    }

    public Map<String,User> test02(){
        System.out.println("test02");
        return null;
    }
    public static void main(String[] args) throws NoSuchMethodException {


        Method method = Test12.class.getDeclaredMethod("test01", Map.class, List.class);
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println("¥通用参数类型" + genericParameterType);
            if (genericParameterType instanceof ParameterizedType) {
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println("#实际参数类型" + actualTypeArgument);
                }
            }
        }
        System.out.println("+++++++++++++++++++++++++++++++++++++++++++");

        method = Test12.class.getMethod("test02", null);
        Type genericReturnType = method.getGenericReturnType();

        if (genericReturnType instanceof ParameterizedType) {
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }
    }
}

在这里插入图片描述

代码分析:

test01方法:

判断参数类型是否是泛型类型的

在这里插入图片描述

test02方法:

判断方法返回值的参数是否是泛型类型的

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值