JavaSE学习笔记 详解Java反射机制(二)--类加载器加载资源文件、通过反射查看类信息、以及反射的应用

前面我们介绍了Java反射机制的一些预备知识,详细说明了下类的加载过程,Java四大类加载器,以及双亲委托模式。下面我们将继续对Java反射机制的其他知识作以介绍。

1.使用类加载器来加载资源文件

ClassLoader类基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节码文件,然后从字节码中定义一个Java类,即java.lang.Class类的一个实例。
除此之外,ClassLoader还可以加载Java应用(src下)所需的资源,如加载配置文件等。


1.1当配置文件在src下,使用类加载器进行加载

  • 加载类路径下(src下)jdbc.properties配置文件的代码案例。下图为jdbc.properties配置文件中的内容。
    在这里插入图片描述
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class TestLoaderFile {
    public static void main(String[] args) throws IOException {
        Properties properties = new Properties();//创建集合,此集合为map集合(key-value)

        //创建类的对象
        Class<TestLoaderFile> aClass = TestLoaderFile.class;
        //获取类加载器对象
        ClassLoader classLoader = aClass.getClassLoader();
        InputStream in = classLoader.getResourceAsStream("jdbc.properties");
        properties.load(in);
        System.out.println(properties);
    }
}

运行后的结果为:
在这里插入图片描述


1.2 当配置文件在src下具体的包中,使用类加载器进行加载

  • 当配置文件在src下具体的包中,使用类加载器进行加载。下图中config.properties配置文件位于src下org.westos.demo6包中,通过类加载器进行加载。config.properties配置文件具体内容如下图。
    在这里插入图片描述
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class TestLoaderFile2 {
    public static void main(String[] args) throws IOException {
        Properties properties = new Properties();
        //获取类的对象
        Class<TestLoaderFile2> clazz = TestLoaderFile2.class;
        //获取类的加载器对象
        ClassLoader classLoader = clazz.getClassLoader();
        //需要注意的是:当配置文件在src下具体的包下时,在加载时需要加上具体包下的路径
        InputStream in = classLoader.getResourceAsStream("org/westos/demo6/config.properties");
        properties.load(in);
        System.out.println(properties);
    }
}

运行后的结果为:
在这里插入图片描述


1.3 当配置文件在项目根路径下,此时可以使用FileInputStream读取

  • 当配置文件在项目根路径下,可以使用FileInputStream读取配置文件。下图中的out.properties文件在项目根路径下,其内容如下。

在这里插入图片描述

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

public class TestLoaderFile3 {
    public static void main(String[] args) throws IOException {
        Properties properties = new Properties();
        //使用FileInputStream来读取根目录下配置文件
        properties.load(new FileInputStream("out.properties"));
        System.out.println(properties);
    }
}

运行后的结果为:
在这里插入图片描述


2.通过反射来查看类信息

Java程序中,所有的对象都有两种类型:编译时类型和运行时类型,而很多时候对象的编译时类型和运行时类型不一致。

例如:在程序中某些变量或者形参的类型是Object类型,但是在程序却需要调用对象运行时类型的方法,该方法不是Object中方法,该如何解决?

为了解决这些问题,程序需要在运行时发现对象和类的真实信息,常采用两种方法:

  • 第1种是在编译和运行时都完全知道类型的具体信息,我们可以先使用instanceof运算符进行判断,再利用强制类型转换符将其转换成运行时类型的变量即可。
  • 第2种是在编译时根本无法预知该对象和类的真实信息,程序只能依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射。

类加载器加载完类之后,在堆内存的方法区就产生一个Class类型的对象(注意:一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。可以通过这个对象看到类的结构,这个对象就像一面镜子,透过这个镜子就能看到类的结构,我们形象地称之为:反射。


2.1 java.lang.Class(Class类型对象)

所谓的java.lang.Class类型:所有的java类型(包含基本数据类型、引用数据类型、void)被加载到内存后,或者是编译器自动编译成class字节码,最终都会用一个Class对象来表示。即所有的Java类型,在内存中都表示一个Class对象。


2.2 获取Class对象的方式

Java程序中可以通过下面四种方式获得Class对象。

获取Class对象的四种方式
1.类型名.class:适用于编译期间已知的任意类型;
2.调用任意对象的getClass()方法,可以获取该对象的运行时类型的Class对象;
3.使用Class类的forName(String name)静态方法,该方法需要传入一个字符串参数,该值是某个类的全限定名(全限定的意思为完整的包.类型名),该方法适用于除过数组之外的任意引用数据类型;
4.调用类加载对象的loadClass(String name)该方法需要一个字符串参数,该值是某个类的全限定类型。

方式1只适用于编译器期间已知的类型,如果某个类型编译期间是已知的,优先考虑这种方式,另外基本数据类型也只能通过此种方式获得Class对象;如果某个类型编译期间未知,我们只能通过某种方式获取该类型的全名称的字符串形式,那么就需要使用3和4了,当方法在运行期间仍然无法加载该类时,会报ClassNotFoundException。

import java.io.Serializable;
import java.lang.annotation.ElementType;

/***
 * 1.java.lang.Class类型:
 *    所有的java类型(包括基本数据类型、引用数据类型、void)被加载到内存后,
 *    或者是编译器自动编译生成的class字节码,最终都会用一个Class对象来表示。
 *    即,所有的Java类型,在内存中都表示为一个Class对象。
 *
 *
 ****2.如何获取Class对象? 4种   重点
 * (1)类型名.class
 * 优点:最简洁
 * 缺点:要求编译期这个类型就要存在
 * (2)对象.getClass()
 * 这个方法在java.lang.Object类型中声明的,返回对象的运行时类型。
 *适用于:你的先具有对象
 *(3)Class.forName("类型全名称")
 *类型全名称:包.类名
 * 优势:这个类型可以在编译期间未知,这个类名可以在代码中出现,
 * 也可以出现在配置文件中,或者键盘输入等方式来进行指定。
 *
 *(4)使用类加载器对象.loadClass("类型全名称")
 *  一般都是在自定义类加载器对象去加载指定路径下的类
 *
 *
 * 这4种方式:都可以选择,就要看当前的情况,哪种能用,就用哪种,如果都能用,就用就简便的。内存后
 *
 */
public class MyTest {
    public static void main(String[] args) throws ClassNotFoundException {
        test02();
        test03();
        test04();
    }


    public  static  void test01(){
        Class<Integer> c1 = int.class;//基本数据类型
        Class<Void> c2 = void.class;//特殊的空类型
        Class<String> c3 = String.class;//系统定义的类类型
        Class<MyTest> c4 = MyTest.class;//自己定义的类类型

        Class<Serializable> c5 = Serializable.class;//接口类型
        Class<ElementType> c6 = ElementType.class;//枚举类型
        Class<Override> c7 = Override.class;//注解类型
        Class<int[]> c8 = int[].class;//数组类型

        //Class<Student> c9 = Student.class;//错误的,因为编译期间不存在
    }

    public static  void test02(){
        Class c1 = "".getClass();
        Class c2 = String.class;

        System.out.println(c1==c2);//返回true

    }
    public static void test03() throws ClassNotFoundException {
        Class c1 = "".getClass();
        Class c2 = String.class;

        Class c3 = Class.forName("java.lang.String");
        System.out.println(c1 == c2);//true
        System.out.println(c1 == c3);//true

    }
    public static void test04() throws ClassNotFoundException {
        Class c = MyTest.class;
        ClassLoader loader = c.getClassLoader();

        Class c2 = loader.loadClass("org.westos.demo3.User");
        Class c3 = User.class;
        System.out.println(c2 == c3);//true


    }
}


3.反射的应用

关于反射的概念我们前面有所了解,下面我们对反射的应用作以详细阐述。

反射的作用
1.在运行时能够获取任意类型的详细信息
2.在运行时能够创建任意引用数据类型的对象
3.在运行时可以为任意对象的任意属性值赋值,或者获取任意对象的任意属性的值
4.在运行时可以调用任意对象的任意方法
5.在运行时读取某个注解信息
6.在运行时读取某个类的泛型形参

下面对反射的各个应用进行学习。


3.1 应用1:运行期间获取任意类型的详细信息

利用反射在运行期间获取任意类型的详细信息的步骤:

  • (1)获取这个类的Class对象
  • (2)获取类的详细信息,其中包含:包名、类名、修饰符、直接父类、父接口们、属性以及构造器。
/***
 *1.在运行期间去获取任意类型的详细信息
 *
 * 后期的时候,在框架中,例如:spring框架,会帮我们管理很多的类,
 * 而这些类不是spring写的,而是我们写的,然后在运行期间spring去加载获取的。
 *
 *
 * 步骤:
 * (1)获取这个类的Class对象
 * (2)获取类的详细信息:
 * 其中包含:包含包名,类名,修饰符,直接父类,父接口们,属性,构造器
 *
 *
 *
 * 一切皆对象:
 * (1)所有类型在内存中都是Class对象
 * (2)所有的属性都是Field对象
 *     例如:private int age;
 *属性类型:Filed类型
 *
 * 类的概念:一类具有相同特性的事物的抽象描述。
 * 所有的属性,有没有相同特征:
 *         都是有修饰符、数据类型、名称
 *         都有相同的行为操作:get获取值/set设置值
 *
 *所以把属性抽象为Field类,那麽一个属性被加载到内存后,是用一个Filed对象表示的。
 *
 *(3)所有的构造器都是Constructor的对象
 *     所有的构造器都有:
 *          修饰符,构造器名称,形参列表
 *          都能new对象
 *  所有的构造器抽象为Constructor类,那麽一个构造器被加载到内存后,是用一个Constructor对象表示。
 *
 * (4)所有的方法都是Method对象
 *      所有的方法都有:修饰符,返回值类型,方法名,形参列表,抛出的异常列表
 *      方法都能被调用(invoke)
 *
 *
 */
public class MyTest2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //如果这个类名在配置文件中,先获取类名
        Properties pro = new Properties();
        pro.load(MyTest2.class.getClassLoader().getResourceAsStream("config.properties"));
        String className = pro.getProperty("className");//key就是配置文件中=左边的属性名

        //(1)获取这个类的Class对象
        Class aclass = Class.forName(className);
        System.out.println(aclass);

        //(2)获取包的详细信息
        //aClass代表com.test.demo.User这个类
        //获取包名
        Package pkg = aclass.getPackage();
        System.out.println("包名:"+pkg.getName());
        //获取类名
        System.out.println("类名:"+aclass.getName());

        //(3)类的修饰符
        int mod = aclass.getModifiers();
        System.out.println("修饰符的值:"+mod);
        System.out.println("修饰符为:"+ Modifier.toString(mod));

        int mod2 = String.class.getModifiers();
        System.out.println("String类型的修饰符为:"+Modifier.toString(mod2));

        //类的直接父类
        Class superclass = aclass.getSuperclass();
        System.out.println("父类的名称为:"+superclass.getName());

        //类的父接口
        Class[] interfaces = aclass.getInterfaces();
        System.out.println("父接口们:");
        for (Class inter : interfaces) {
            System.out.println(inter);
            
        }

        //类的属性
        /**
         *(1)Fileds[] getFileds() 得到所有的公共的属性
         *(2)Fileds[] getDeclaredFileds() 得到所有的声明的属性
         */
        //Field[] fields = aclass.getFields();
        Field[] fields = aclass.getDeclaredFields();
        int count=0;
        for (Field field : fields) {
            count++;
            int fmod = field.getModifiers();
            System.out.println(count+"属性的修饰符:"+Modifier.toString(mod));

            System.out.println(count+"属性的数据类型:"+field.getType().getName());
            System.out.println(count+"属性的名称:"+field.getName());
        }

        //类的构造器
        /***
         * Constructor[] getConstructors():得到所有的公共构造器
         * Constructor[] getDeclaredConstructors():得到所有声明的构造器
        */
         //Constructor[] constructors = aclass.getConstructors();
        Constructor[] constructors = aclass.getDeclaredConstructors();
        int count1=0;
        for (Constructor constructor : constructors) {
            count1++;
            int cmod = constructor.getModifiers();
            System.out.println(count1+"构造器的修饰符:"+Modifier.toString(cmod));
            System.out.println(count1+"构造器的名称:"+constructor.getName());
            Class[] parameterTypes = constructor.getParameterTypes();
            System.out.println(count1+"构造器的行参列表:"+ Arrays.toString(parameterTypes));
        }


        /***
         * Method[] getMethods():得到所有的公共方法
         *Constructor[] getDeclaredMethods():得到所有声明的方法
         *
         */
        count=0;
        Method[] methods = aclass.getDeclaredMethods();
        for (Method method : methods) {
            count++;
            int mmod = method.getModifiers();
            System.out.println(count+"方法的修饰符:"+Modifier.toString(mod));
            System.out.println(count+"方法的名称:"+method.getName());
            Class[] parameterTypes = method.getParameterTypes();
            System.out.println(count+"方法的行参列表:"+ Arrays.toString(parameterTypes));
            System.out.println(count+"方法的返回值类型:"+method.getReturnType());
            Class<?>[] exceptionTypes = method.getExceptionTypes();
            System.out.println(count+"抛出的异常类型们:"+Arrays.toString(exceptionTypes));

        }

    }
}

运行后的结果为:

class com.test.demo.User
包名:com.test.demo
类名:com.test.demo.User
修饰符的值:1
修饰符为:public
String类型的修饰符为:public final
父类的名称为:java.lang.Object
父接口们:
interface java.io.Serializable
1属性的修饰符:public
1属性的数据类型:java.lang.String
1属性的名称:name
2属性的修饰符:public
2属性的数据类型:int
2属性的名称:age
1构造器的修饰符:public
1构造器的名称:com.test.demo.User
1构造器的行参列表:[]
2构造器的修饰符:public
2构造器的名称:com.test.demo.User
2构造器的行参列表:[class java.lang.String, int]
1方法的修饰符:public
1方法的名称:toString
1方法的行参列表:[]
1方法的返回值类型:class java.lang.String
1抛出的异常类型们:[]
2方法的修饰符:public
2方法的名称:getName
2方法的行参列表:[]
2方法的返回值类型:class java.lang.String
2抛出的异常类型们:[]
3方法的修饰符:public
3方法的名称:setName
3方法的行参列表:[class java.lang.String]
3方法的返回值类型:void
3抛出的异常类型们:[]
4方法的修饰符:public
4方法的名称:setAge
4方法的行参列表:[int]
4方法的返回值类型:void
4抛出的异常类型们:[]
5方法的修饰符:public
5方法的名称:getAge
5方法的行参列表:[]
5方法的返回值类型:int
5抛出的异常类型们:[]

3.2 应用2:运行期间可以创建任意引用数据类型的对象

在运行时可以创建任意引用数据类型的对象,通常有两种方式。

在运行期间可以创建任意引用数据类型的对象方式
方式1:使用Clas对象直接new对象
方式2:通过Class对象先获取有参构造再创建对象
步骤1.获取某个类的Class对象
步骤2.通过Class对象来创建这个Class所代表类型的对象
步骤2.通过Class对象来获取Constructor对象
步骤3.通过Constructor对象来创建这个Class所代表的类型的对象
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/***
 *2.在运行时可以创建任意引用数据类型的对象
 *
 * 方式一:使用Class对象直接new对象
 * 步骤:
 * (1)获取某个类的Class对象
 * (2)通过Class对象来创建这个Class所代表的类型的对象
 *
 * 当UserTest没有无参构造时,会报java.lang.InstantiationException
 *
 *
 * 方式二:通过Class对象先获取有参构造,然后再创建对象
 * 步骤:
 *     (1)获取某个类的Class对象
 *     (2)通过Class对象来获取Constructor对象
 *     (3)通过Constructor对象来创建这个Class所代表的类型的对象。
 */
public class MyTest3 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        //(1)获取某个类的Class对象
        Class<?> aClass = Class.forName("com.test.ext.demo.UserTest");

        //(2)创建对象
        //obj的编译时类型是Object类型
        //obj的运行时类型是UserTest类型
        Object obj = aClass.newInstance();//这里的newInstance()没有参数,因为它是用无参构造创建实例的。
        System.out.println(obj);

        test02();
    }

    public static  void test02() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //(1)获取某个类的对象
        Class<?> aClass = Class.forName("com.test.ext.demo.UserTest2");
        /***
         * (1)Constructor aClass.getConstructor(parameterTypes):某个公共的构造器
         * (2)Constructor aClass.getConstructor(parameterTypes):某个声明的构造器
         *
         * 一个类中可能存在多个构造器,但是多个构造器重载的话,形参列表一定不一样,
         * 所以通过形参列表可以定义带一个唯一的构造器。
         * 如果Class<?>...parameterTypes,一个都不传,即获取无参构造
         *
         *
         */
        //(2)获取有参构造对象
        Constructor<?> constructor = aClass.getDeclaredConstructor(String.class, int.class);

        //当构造器私有时,仍然想通过构造器来实现创建对象,可以设置访问性
        constructor.setAccessible(true);
        //(3)通过Constructor对象来创建实例对象
        Object obj = constructor.newInstance("张三", 23);
        //这里的newInstance(实参列表),因为用的是有参构造来创建对象的
        System.out.println(obj);
    }
}

运行后的结果为:
在这里插入图片描述


3.3 应用3:运行期间可以为任意属性赋值,或者获取任意对象的任意属性的值

我们在平常编写类时注意保存无参构造,原因在于:

  • 1.创建对象方便。
  • 2.继承时比较方便。因为子构造器默认调用父类的无参构造。
  • 3.反射创建对象方便。
运行期间为任意属性赋值或获取任意对象的任意属性的值,操作的步骤
1.获取某个类型的Class对象
2.创建实例对象
3.为某个属性赋值,其中需要先获取某个属性Field对象
public class MyTest4 {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException, NoSuchFieldException {
        test01();

    }

    public static  void test01() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
        //(1)获取某个类的Class对象
        Class<?> aClass = Class.forName("com.test.ext.demo.UserTest");

        //(2)创建对象
        //obj的编译时类型是Object类型
        //obj的运行时类型是UserTest类型
        Object obj = aClass.newInstance();
        System.out.println(obj);

        //(3)获取name属性的Filed对象
        /***
         * Field  aClass.getFiled(name):获取公共的某个属性
         * Filed  Class.getDeclaredField(name):获取声明的某个属性
         *
         *
         */
        Field nameField = aClass.getDeclaredField("name");
        //因为name属性为私有的,所以需要设置下可访问性
        nameField.setAccessible(true);
        /**
         * 之前,如何为属性进行赋值
         *      通过对象.属性名=值;
         * 属性的特点:(1)每一对象都是独立的(2)属性有默认值
         * 所以在为属性赋值时,要说为哪个对象的属性赋值。
         *
         */
       // nameField.set(obj,value);//obj代表UserTest的对象,value代表值
        nameField.set(obj,"张三");

        System.out.println(obj);

        //(4)为年龄属性赋值
        Field ageFiled = aClass.getDeclaredField("age");
        ageFiled.setAccessible(true);
        ageFiled.set(obj,23);
        System.out.println(obj);

        //(5)获取name属性的值
           //先获取name属性的Filed对象
        Field nameFiled2 = aClass.getDeclaredField("name");
        nameFiled2.setAccessible(true);
        /**
         *之前,获取属性的值
         *    变量=对象名.属性名
         */
        Object value = nameFiled2.get(obj);
        System.out.println(value);
    }
}

运行后的结果为:
在这里插入图片描述


3.4 应用4:运行期间可以调用任意对象的任意方法

运行期间可以调用任意对象的任意方法,操作步骤
1.获取某个类的Class对象
2.得到方法Method对象,进而进行调用:invoke(对象,实参列表)
/**
 * 4.在运行时可以调用任意对象的任意方法
 * Method:
 *     invoke(对象,实参列表)
 *
 *
 * 步骤:
 * (1)获取某个类的Class对象
 * (2)得到方法Method对象
 *
 *
 */
public class MyTest5 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
       test01();
    }


    public static void test01() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        //(1)获取某个类的Class对象:四种方式之一
        Class<?> aClass = Class.forName("com.test.ext.demo.UserTest");
        //(2)得到方法Method对象
        //例如:得到setName(String name)方法
        /***
         * Method  aClass.getMethod(name,parameterTypes):得到公共的方法
         * Method  aClass.getDeclaredMethod(name,parameterTypes):得到声明的方法
         *一个类中方法是可能重载的,如何定位到某一个方法?
         * 通过方法名+形参列表
         *
         */
        Method method = aClass.getDeclaredMethod("setName", String.class);

        //调用方法
        /***
         *静态方法:
         *       类名.方法([实参列表])
         *非静态方法
         *       对象名.方法([实参列表])
         */

        //创建对象
        Object obj = aClass.newInstance();
        //调用方法
        method.invoke(obj, "陈奕迅");
        System.out.println(obj);

        //获取public static oid test()方法并进行调用
        Method method1 = aClass.getDeclaredMethod("test", int.class);

        //调用方法
        method1.invoke(null,10);//注意:obj位置上传入null,表示调用静态方法

        
    }
}

运行后的结果为:

在这里插入图片描述


3.5 应用5:运行期间读取某个注解信息

首先一个完整的注解,有三个要素:(1)声明(2)使用(3)读取。

  • @override,@SuppressWarnings,@Deprecated等这些是JRE声明的,也是由编译器读取的
  • @Test,@Before…这些注解是Junit声明和读取的
  • @author,@parm…等这些注解也是JRE中声明的,由javadoc.exe读取的

注意:如果我们自定义的注解,那么声明和读取我们就要自己编写了。

import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.TYPE;

/***
 *5.在运行时读取某个注解信息
 *
 *获取类上面注解步骤:
 * (1)获取Class对象
 * (2)获取注解对象
 * (3)获取注解的配置参数的值
 *
 *获取属性上的注解步骤:
 * (1)获取Class对象
 * (2)获取属性对象Filed对象
 * (2)获取注解对象Annotation对象
 * (3)获取注解的配置参数的值
 *
 */

//声明注解

@Retention(RetentionPolicy.RUNTIME)//注意:只有生命周期是运行时,才可以被反射读取
@Target({TYPE,FIELD})
@interface  MyAnnotation{
    String value();//如果配置参数只有一个,名称是value,在使用时,可以省略赋值"value="
}

//使用注解
@MyAnnotation("陈奕迅")
class MyClass{
    @MyAnnotation("陈奕迅")
    private String info;

}

//读取注解
public class MyTest6 {
    public static void main(String[] args) throws NoSuchFieldException {
        //(1)获取Class对象
        Class<MyClass> clazz = MyClass.class;//四种方式之一即可

        //(2)获取注解对象
        MyAnnotation annotation =clazz.getAnnotation(MyAnnotation.class);

        //(3)获取类上注解的配置参数的值
        String value = annotation.value();
        System.out.println("value="+value);

        test01();

    }

    public static void test01() throws NoSuchFieldException {
        //(1)获取Class对象
        Class<MyClass> clazz = MyClass.class;//四种方式之一即可
        //(2)获取属性对象
        Field info = clazz.getDeclaredField("info");


        //(3)获取注解对象
        Annotation annotation1 = info.getAnnotation(MyAnnotation.class);


        //(3)获取属性上注解的配置参数的值
        System.out.println(annotation1.toString());

    }
}


运行后的结果为:
在这里插入图片描述


3.6 应用6:运行期间读取某个类的泛型实参

/***
 *6.在运行时读取某个类的泛型实参
 * 步骤:
 * (1)获取Class对象
 * (2)获取泛型父类
 * ParameterizedType type = (ParameterizedType)clazz.getGenericSuperclass();
 * (3)获取类型实参
 *
 * Type:代表Java所有类型
 * (1)Class:代表的是普通的类型,没有泛型信息的
 * (2)ParameterizedType:参数化类型  例如:Father<String,Integer>
 * (3)GenericArrayType:泛型数组类型 例如:T[]
 * (4)TypeVariable:类型变量   例如:T
 * (5)WildcardType:带?通配符的泛型的类型  例如:ArrayList<?>
 *  ArrayList<? super 下限>   ArrayList<? extends 上限>

 */
public class MyTest7 {
    @Test
    public void test01(){
        //获取Son类的泛型父类的类型实参
        //(1)获取Class对象
        Class<Son> clazz = Son.class;
        //(2)获取泛型父类

       /*  此种方式是获取普通父类
        Class<? super Son> fu = clazz.getSuperclass();
        System.out.println(fu);*/

        ParameterizedType type = (ParameterizedType)clazz.getGenericSuperclass();

        //(3)获取类型参数
        Type[] types = type.getActualTypeArguments();
        for (Type t : types) {
            System.out.println(t);

        }
    }
    @Test
    public void test02(){
        MyTools myTools = new MyTools();
        myTools.test();
    }
}

//泛型类型形参:<T,U>
class Father<T,U>{

}
//泛型类型实参:
class Son extends Father<String,Integer>{
}

/**
 * 泛型类、接口的类型形参,什么时候才能确定具体的类型
 * (1)创建它的对象
 * (2)继承泛型类
 * (3)实现泛型接口
 * 
 * 
 */

class Tools<T>{
    private Class type;
    //构造方法
    public Tools(){
        //在创建子类对象时,来确定type的代表类型
        //(1)获取正在new的对象的类型的Class对象
        Class<? extends Tools> clazz = this.getClass();

        //(2)获取泛型父类的信息
        ParameterizedType t= (ParameterizedType)clazz.getGenericSuperclass();

        //(3)获取类型实参
        type= (Class) t.getActualTypeArguments()[0];

    }

    public void test(){
        //这个方法中需要用到T的类型对象,即T的Class对象
        System.out.println(type);


    }
    
}

class MyTools extends Tools<String>{
    
}

运行后的结果为:
在这里插入图片描述
在这里插入图片描述


总结

本节首先学习了类加载器加载资源文件,需要注意的是当资源文件放置的位置不同,加载资源文件的方式也有所不同。其次学习了利用反射来查看类信息,最后着重学习反射在我们以后学习工作中的应用。

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值