反射(面试必问)

反射

为什么要用反射?

因为Java是静态的强类型语言,在编译阶段就需要确定类型

  Java为了实现“动态性“特征,引入了反射机制

    变量可以使用Object声明,然后在运行时确定某个对象的运行时类型

    或者在运行时动态的”注入“某个类型的对象,动态的创建某个类型的对象

      例如:用这个类型的Class对象,然后创建它的实例

例如:JS等是动态的弱类型的语言,在运行时确定变量的类型,根据赋的值确定变量的类型

反射的根源

java.lang.Class

  Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。 

复制代码
//示例代码
    @Test
    public void test() {
        Class c1 = int.class;
        Class c2 = void.class;
        Class c3 = String.class;
        Class c4 = Comparable.class;
        Class c5 = ElementType.class;
        Class c6 = Override.class;
        Class c7 = int[].class;
        
        int[] arr1 = new int[5];
        int[] arr2 = new int[10];
        
        System.out.println(arr1.getClass() == arr2.getClass());
        System.out.println(int[].class == arr2.getClass());
        
        int[][] arr3 = new int[5][10];
        System.out.println(arr1.getClass());
        System.out.println(arr3.getClass());
    }
复制代码

四种获取Class对象的方式

  (1)如果类型已知:  类型名.class

  (2)如果对象存在  对象.getClass()

  (3)如果在编译阶段未知,但是运行阶段可以获取它的类型全名称  Class.forName("类型全名称")

  (4)如果在编译阶段未知,但是运行阶段可以获取它的类型全名称  类加载对象.loadClass("类型全名称")

相关API

java.lang.Class 方法

  (1)获取类型名:getName()

  (2)创建实例对象  newInstance()

    这个类型必须有无参构造

    Class对象.newInstance()

  (3)获取包的信息:getPackage()

  (4)获取父类

    Class  getSuperClass()  不带泛型

    Type  getGenericSuperClass()  可以带泛型

  (5)获取父接口

    Class[]  getInterfaces()  不带泛型

    Type[]  getGenericInterfaces()  可以带泛型

  (6)获取该类型的属性

    获取全部可访问的公共的属性  Field[]   getFields()

    获取全部已声明的属性  Field[]  getDeclaredFields()

    获取某一个公共的属性  Field  getField("属性名")

    获取某一个声明过的属性,可能是私有的等

      Field  getDeclaredField("属性名")

      通过属性名就可以唯一确定一个属性

  (7)获取该类的构造器

    获取全部的公共的构造器  

    获取全部已声明的构造器

    获取某一个公共的构造器

    获取某一个已声明的构造器

      Constructor  getDeclaredConstructor(形参列表的类型Class列表...  )

      通过构造器的形参列表就可以唯一确定一个构造器

  (8)获取该类的方法

    获取全部的公共的方法

    获取全部已声明的方法

    获取某一个公共的方法

    获取某一个已声明的方法

      Method getDeclaredMethod("方法名", 形参列表的类型Class列表 ....)

      通过方法的名称+形参列表才能唯一确定一个方法  

  (9)获取类上的注解

    获取所有的注解/注释  Annotation[] getAnnotations() 

    获取指定的注解  <A extends Annotation>  A   getAnnotation(Class<A> annotationClass) 

java.lang.reflect

  Package

    获取包名  getName()

  Modifier

    Modifier.toString(mod)

  Constructor

    创建实例对象  newInstance(Object ...)

      如果无参,那么就直接“构造器对象.newInstance()”

      如果有参:构造器对象.newInstance(给构造器的实参列表)

  Field

    (1)setAccessible(true)

    (2)Object  get(实例对象)

      Object  属性对象.get(实例对象)

      原来是:  实例对象.get属性名();

    (3)set(实例对象, 属性的新值)

      属性对象.set(实例对象,属性值)

      原来是:实例对象.set属性名(属性值)

  Method

    (1)setAccessible(true)  如果方法不是public才需要

    (2)Object invoke(实例对象, 传给被调用方法的实参列表)

      Object  returnValue = 方法对象.invoke(实例对象,实参列表...)

        如果原来的方法对象是没有返回值,即是void,那么returnValue是null

      原来:

        有返回值  变量 = 实例对象.方法名(实参列表)

        无返回值  实例对象.方法名(实参列表);

复制代码
//示例代码
package com.atguigu.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
import java.util.Arrays;
/*
 * 有了Class对象后,都可以做什么事?你想干啥干啥
 * 
 * 1、获取类的详细信息
 * 2、创建实例对象
 * 3、获取属性,设置属性
 * 4、获取方法,设置方法
 * ...
 */
public class TestReflectAPI {
    public static void main(String[] args) throws Exception {
        Object obj = "hello";
        
        Class clazz = obj.getClass();
        
        //1、获取类名
        System.out.println("类名:" + clazz.getName());
        
        //2、获取包信息
        /*
         * 所有的包有共同点-->Package
         */
        Package pack = clazz.getPackage();
        System.out.println("包名:" + pack.getName());
        
        //3、获取类的修饰符
        int mod = clazz.getModifiers();
        //每一种修饰符,有一个常量表示
        //这个常量在Modifier类型声明
        System.out.println(Modifier.toString(mod));
        
        //4、父类
        Class superclass = clazz.getSuperclass();
        System.out.println("父类:" + superclass);
        
        //5、接口
        Class[] interfaces = clazz.getInterfaces();
        System.out.println("接口们:");
        for (Class class1 : interfaces) {
            System.out.println(class1);
        }
        
        //6、属性:Field
        /*
         * 属性共同点:  修饰符   数据类型   属性名      属性对应set值,get值的操作
         * 任意类型的一个属性对应Field对象
         * 
         * 一切皆对象
         */
//        Field[] fields = clazz.getFields();//返回公共的属性
/*        Field[] fields = clazz.getDeclaredFields();
        System.out.println("属性们:");
        for (Field field : fields) {
            System.out.println("属性的类型:"+field.getType());
            System.out.println("属性的名称:"+field.getName());
            System.out.println("属性的所有信息:"+field);
        }*/
        
        //单独获取某个属性对象,例如:获取value属性
        //假设从配置文件中知晓属性名是value
//        Field field = clazz.getField("value");//得到公共的
        Field field = clazz.getDeclaredField("value");//得到已声明的
        System.out.println(field);
        //设置属性值,获取属性值
        //先有对象,才能有属性值
        
//        获取"hello"对象的value属性值
        field.setAccessible(true);//设置可访问
        
        Object object = field.get(obj);
        char[] v = (char[]) object;
        System.out.println(Arrays.toString(v));
        v[0] = 'w';
        v[1] = 'o';
        v[2] = 'r';
        v[3] = 'l';
        v[4] = 'd';
        
        //参数一:哪个对象的field属性,第二个参数:设置为xx新值
//        field.set("hello", "world");//因为是final
        
        System.out.println(obj);
        
        //7、创建对象    创建Class对应的类型的对象
//        Object obj = clazz.newInstance();
//        System.out.println(obj);
        
        //8、构造器
//        clazz.getConstructors()//获取所有公共的构造器
//        clazz.getDeclaredConstructors();//获取所有该类拥有的构造器
        
        /*
         * 构造器的共同特点:修饰符   构造器名   形参列表      可以创建对象的操作
         */
        
        /*
         * 构造器可以重载,构造器的名称都一样
         * 如何在类中唯一确定一个构造器:靠形参列表(个数和类型)
         */
//         Constructor c = clazz.getDeclaredConstructor();//获取无参构造
//         Object newInstance = c.newInstance();//用无参构造创建对象
//         System.out.println("对象:"+newInstance);
         
        //public String(char value[])
         Constructor c = clazz.getDeclaredConstructor(char[].class);//char[]数组类型
         //用有参构造创建对象,需要实参列表
         char[] params= {'c','h','a','i'};
         Object newInstance = c.newInstance(params);
        System.out.println("对象:"+newInstance);
        
        
        //9、方法
        /*
         * 所有方法共同特点:
         * 修饰符  返回值类型  方法名(形参列表)抛出的异常列表
         * 方法可以被调用
         */
//        clazz.getMethods()//获取所有公共的方法
//        clazz.getDeclaredMethods();//获取所有方法
        /*
         * 方法可以重载,如何在一个类中,唯一确定方法:方法名+形参列表(个数和类型)
         * 
         * toString()
         */
        Method m = clazz.getDeclaredMethod("toString");//获取无参的方法
        System.out.println(m);
        
        //调用方法
        //参数一:那个实例对象调用m方法,参数二:传给m方法的实参列表
        Object returnValue = m.invoke(obj);
        System.out.println(returnValue);
        
        // public byte[] getBytes(Charset charset) 
        Method m2 = clazz.getDeclaredMethod("getBytes", Charset.class);
        Object returnValue2 = m2.invoke(obj, Charset.forName("GBK"));
        System.out.println(returnValue2);
        byte[] data = (byte[]) returnValue2;
        System.out.println(Arrays.toString(data));
    }
}
复制代码

如何获取类上的泛型

步骤

  (1)先得到类的Class对象

  (2)获取它的父类  Type  getGenericSuperClass() 可以带泛型

  (3)类型转换

    如果是父类是这样的类型    父类名<泛型实参>

    ParameterizedType  p = (ParameterizedType )type;

  (4)获取泛型实参  Type[] getActualTypeArguments()  

复制代码
//示例代码
package com.atguigu.reflect;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Date;
public class TestGenericType {
    public static void main(String[] args) {
        GenericSub g = new GenericSub();
        System.out.println(g.getType1());
        System.out.println(g.getType2());
        
        GenericSub2 g2 = new GenericSub2();
        System.out.println(g2.getType1());
        System.out.println(g2.getType2());
    }
}
//T叫做类型形参
abstract class GenericSuper<T,U>{
    private Class<T> type1;
    private Class<U> type2;
    
    public GenericSuper(){
        Class clazz = this.getClass();//this是当前对象,在构造器中,就是代表那个正在创建的对象
        
        //Type是包含Class等的所有类型
        Type gs = clazz.getGenericSuperclass();
        //GenericSuper<String>:参数化的类型
        ParameterizedType p  = (ParameterizedType) gs;
        //获取类型实参
        Type[] arr = p.getActualTypeArguments();
                
        type1 = (Class<T>) arr[0];
        type2 = (Class<U>) arr[1];
    }
    public Class<T> getType1() {
        return type1;
    }
    public Class<U> getType2() {
        return type2;
    }
}
//String是类型实参
class GenericSub extends GenericSuper<String,Integer>{
}
class GenericSub2 extends GenericSuper<Date,Double>{
}

//核心代码
        Class clazz = GenericSub.class;
//        Class sup = clazz.getSuperclass();//得不到泛型的信息
//        System.out.println(sup);
        
        //Type是包含Class等的所有类型
        Type gs = clazz.getGenericSuperclass();
        //GenericSuper<String>:参数化的类型
        ParameterizedType p  = (ParameterizedType) gs;
        //获取类型实参
        Type[] arr = p.getActualTypeArguments();
        System.out.println(arr[0]);
        System.out.println(arr[1]);
复制代码

获取注解

获取类上的注解

  步骤

    (1)先得到类的Class对象

    (2)获取指定的注解

      <A extends Annotation>  A   getAnnotation(Class<A> annotationClass) 

      可以获取到的注解,必须声明周期是RUNTIME

    (3)获取注解的配置参数的值

复制代码
//示例代码
package com.atguigu.reflect;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.junit.Test;
@MyAnnoation
public class TestAnnotatio {
    @MyAnnoation(value = "尚硅谷")
    private String info;
    //获取类上的注解信息
    @Test
    public void test() {
        // 1、先得到Class对象
        Class clazz = TestAnnotatio.class;
        // 2、获取类上的注解信息:得到MyAnnoation注解对象
        MyAnnoation m = (MyAnnoation) clazz.getAnnotation(MyAnnoation.class);
        // 3、得到注解的配置参数的值
        String value = m.value();
        System.out.println(value);
    }
}
@Retention(RetentionPolicy.RUNTIME) // 为了在反射阶段可以读取到该注解的信息,生命周期一定要在RUNTIME
@interface MyAnnoation {
    String value() default "atguigu";
}
复制代码

获取属性上的注解

  步骤

    (1)先得到类的Class对象

    (2)获取属性对象

    (3)获取指定的注解

      <A extends Annotation>  A   getAnnotation(Class<A> annotationClass) 

      可以获取到的注解,必须声明周期是RUNTIME

    (4)获取注解的配置参数的值

复制代码
//示例代码
package com.atguigu.reflect;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import org.junit.Test;
public class TestAnnotatio {
    @MyAnnoation(value = "尚硅谷")
    private String info;
    //获取属性上的注解信息
    @Test
    public void test2() throws Exception {
        // 1、获取Class对象
        Class clazz = TestAnnotatio.class;
        // 2、先获取属性对象
        Field field = clazz.getDeclaredField("info");
        // 3、得到注解对象
        MyAnnoation m = (MyAnnoation) field.getAnnotation(MyAnnoation.class);
        // 4、得到属性值
        System.out.println(m.value());
    }
}
@Retention(RetentionPolicy.RUNTIME) // 为了在反射阶段可以读取到该注解的信息,生命周期一定要在RUNTIME
@interface MyAnnoation {
    String value() default "atguigu";
}
复制代码

类加载器

类加载的过程(了解)

  双亲委托模式/机制

    某个类加载器接到加载任务,先把加载任务交给“父”加载器,层层往上,一直到引导类加载器,如果“父”加载器可以加载,那么就由“父”加载器加载,如果不可以,传回它的“子”加载器,“子”加载器尝试加载,如果可以,那么就加载,如果不可以,再往回传,一到回到最初接到任务的那个加载器,如果它可以,也正常加载,如果它也不能加载,报异常:ClassNotFoundException

    作用:安全

类加载器的体系结构

  1、引导类加载器BootStrap

    非Java语言实现的  获取不到它的对象,只能得到null

    加载核心类库rt.jar

    加载sun.boot.class.path路径下的内容

  2、扩展类加载器ExtClassLoader

    加载jre/ext目录

    java.ext.dirs路径下的内容

  3、应用程序类加载器,系统类加载器AppClassLoader

    加载用户自定义的类型

    加载src目录下的内容(bin)

  4、自定义类加载器

类加载器的作用

  (1)加载类

  (2)加载资源文件

复制代码
    @Test
    public void test8()throws Exception{
        Properties pro = new Properties();
        //JavaSE和Web项目
        //在web项目中,因为项目部署到tomcat中运行,又因为tomcat用自己的类加载器的
        //把配置文件放在了src中,最终代码在WEB-INFO的classes目录
        //可以用类加载器加载这个配置文件,但是不是系统类加载器
        //this.getClass().getClassLoader()目的是得到tomcat的自定义类加载器对象
        pro.load(this.getClass().getClassLoader().getResourceAsStream("3.properties"));
    
        System.out.println(pro.getProperty("user3"));
    }
    
    @Test
    public void test7()throws Exception{
        Properties pro = new Properties();
        //JavaSE和Web都可以
        //把配置文件放在了项目根目录下,在src外面
        //不可以用类加载器加载这个配置文件
        //可以使用FileInputStream获取
        pro.load(new FileInputStream("2.properties"));
    
        System.out.println(pro.getProperty("user2"));
    }
复制代码

 

posted @   LuckySnail  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术
点击右上角即可分享
微信分享提示