栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【JavaSE 16】 反射机制

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【JavaSE 16】 反射机制

类的加载

1.类加载的功能:将类的二进制文本数据(就是java源代码文件经过编译后的.class 字节码文件)加载到JVM内存中,将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象

2.Class对象的作用:作为方法区中类数据的访问入口(即引用地址),所有需要访问和使用类数据只能通过这个Class对象

3.类加载发生时机:第一次使用某个类的时候;

4.类加载器 : 类加载的工作由类加载器完成,类加载器通常由JVM提供,由JVM提供的这些类加载器通常被称为系统类加载器。JVM自带四种类加载器,不同的类加载器,可以加载不同来源的.class文件;除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器

6.类的二进制数据,通常有如下几种来源:
(1)从本地系统直接读取.class文件,这是绝大部分类的加载方法;
(2)从zip,jar等归档文件中加载.class文件,这种方式也是很常见的;
(3)通过网络下载.class文件或数据
(4)从专有数据库中提取.class数据
(5)将Java源文件数据上传到服务器中动态编译为.class数据,并执行加载;

2.类加载器

很多开发人员都遇到过java.lang.ClassNotFoundException或java.lang.NoClassDefError,想要更好的解决这类问题,或者在一些特殊的应用场景,比如需要支持类的动态加载或需要对编译后的字节码文件进行加密解密操作,那么需要你自定义类加载器,因此了解类加载器及其类加载机制也就成了每一个Java开发人员的必备技能之一。

2.1 四种类加载器 2.1.1 引导类加载器(Bootstrap Classloader)
  • 别名:根类加载器
  • 加载内容:负责加载Java的核心库(JAVA_HOME/jre/lib/rt.jar等或sun.boot.class.path路径下的内容)
  • 实现方式:用原生代码(C/C++)来实现的,并不继承自java.lang.ClassLoder,所以通过Java代码获取引导类加载器对象将会得到null

演示

public class _01_ {
    //todo 1.获取某个类的类加载器对象
    @Test
    public void test01(){
        // 普通类的类加载器
        Class clazz = _01_.class;
        ClassLoader loader = clazz.getClassLoader();
        System.out.println(loader);//sun.misc.Launcher$AppClassLoader@18b4aac2


        //rt.jar包下的类的类加载器
        Class stringClass = String.class;
        ClassLoader classLoader = stringClass.getClassLoader();
        System.out.println(classLoader);//null
    }
}
2.1.2 扩展类加载器(Extension ClassLoader)
  • 加载内容:负责加载Java的扩展库(JAVA_HOME/jre/ext/*.jar或java.ext.dirs路径下的内容)。
  • 实现方式: 是java.lang.ClassLoader的子类,sun.misc.Launcher$ExtClassLoader

演示
将java代码导出成jar包,放在JAVA_HOME/jre/ext/目录下面;

打印结果为:sun.misc.Launcher$ExtClassLoader@hash码值

2.1.3 应用程序类加载器(Application Classloader)
  • 实现方式:它由sun.misc.Launcher$AppClassLoader实现,是java.lang.ClassLoader的子类
  • 加载内容:负责加载Java应用程序类路径(classpath、java.class.path)下的内容。
    classPath= 项目路径bin文件夹 或者 配置了的环境变量classPath
2.1.4 自定义类加载器

开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求.
(1)对字节码进行加密来避免class文件被反编译
(2)加载特殊目录下的字节码数据。

2.2 java.lang.ClassLoader API

ClassLoader 类是一个抽象类,ClassLoader的相关方法:
public final ClassLoader getParent():返回委托的父类加载器。一些实现可能使用 null 来表示引导类加载器。

public static ClassLoader getSystemClassLoader():返回委托的系统类加载器。

public Class loadClass(String name):使用指定的二进制名称(类的全限定名)来加载类。例如:java.lang.String,注意内部类的名称:匿名内部类(外部类的全限定名 编 号 ) 、 局 部 内 部 类 ( 外 部 类 的 全 限 定 名 编号)、局部内部类(外部类的全限定名 编号)、局部内部类(外部类的全限定名编号+类名)、成员/静态内部类(外部类的全限定名$+类名)。

protected Class findClass(String name):使用指定的二进制名称(类的全限定名)来查找类。此方法应该被类加载器的实现重写,该实现按照委托模型来加载类。在通过父类加载器检查所请求的类后,此方法将被 loadClass 方法调用。

protected final Class findLoadedClass(String name):返回Class 对象,如果类没有被加载,则返回 null

protected final Class defineClass(String name,byte[] b,int off,int len):将一个 byte 数组转换为 Class 类的实例。

2.3 使用类加载器加载资源文件

(1)ClassLoader类的基本职责
根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个Java类,即java.lang.Class类的一个实例

(2)ClassLoader还负责加载Java应用所需的资源,如图像文件和配置文件等。
加载类路径下(src下)的资源文件 【 src编译后为bin文件】

package _17_反射._02_类加载器;

import org.junit.Test;

import java.io.*;
import java.util.Properties;

public class _02_ClassLoader {
    //在src/resources目录下存放一个jdbc.properties文件
    @Test
    public void test() throws IOException {
        FileReader fr = new FileReader("jdbc.properties");
        BufferedReader br = new BufferedReader(fr);
        String s = br.readLine();
        System.out.println(s);
        br.close();
        fr.close();

        //报错:java.io.FileNotFoundException
        //原因:
        //通过普通IO流读取文件时,
        //相对路径的根路径是项目目录,不是src目录
    }
    
    @Test
    public void test2() throws IOException {
        FileReader fr = new FileReader("src/jdbc.properties");
        BufferedReader br = new BufferedReader(fr);
        String s = br.readLine();
        System.out.println(s);
        br.close();
        fr.close();
        //username=root
        //src目录存放.java 源代码文件,部署的时候部署.class文件而不是.java文件
    }

    @Test
    //src目录是类路径 classpath  和类在一起,用类加载器加载
    public void test3() throws IOException {
        Properties pro = new Properties();
        //todo 获取类加载器
        ClassLoader classLoader = _02_ClassLoader.class.getClassLoader();


        //todo 1. src目录下的文件(source目录都可以直接写文件名)
       InputStream in = classLoader.getResourceAsStream("jdbc.properties");

        //todo 2. src中 包下面的文件 加上包名
        //InputStream in = classLoader.getResourceAsStream("_17_反射/_02_类加载器/Demo.properties");

        //todo 3.项目目录下直接放一个out.properties文件
        //在项目目录中、src外部,此时就不在类路径下了
        //这种情况下只能用直接用FileInputStream了,而不是用类加载器了
        //InputStream in = new FileInputStream("out.properties");

        pro.load(in);
        System.out.println(pro);

        String username = pro.getProperty("username");
        System.out.println(username);


        //todo 类加载器加载的是项目下的source目录中的内容,只要是source Folder都可以直接写文件名
        //source目录编译之后都会在项目目录下的bin目录中
        //sourceFolder是源代码文件夹,一般会单独用一个config这种
        //sourceFolder来装配置文件,等价于src
    }
}

总结:
(1)source folder和普通的folder不同,source folder下的文件可以直接通过类加载器通过文件名获取输入流;而普通的folder不可以。
(2)Source folde下的包中的文件,需要加上包全路径名
(3)如果要通过普通的IO流获取,那么根目录是项目目录

3.通过反射查看类信息

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

例如:我们某些变量或形参的类型是Object类型,但是程序确需要调用该对象运行时类型的方法,该方法不是Object中方法,那么如何解决呢?
为了解决这些问题,程序需要在运行时发现对象和类的真实信息,我们有两种方法:
第一种是在编译和运行时都完全知道类型的具体信息,在这种情况下,我们可以直接先使用instanceof运算符进行判断,再利用强制类型转换符将其转换成运行时类型的变量即可。
第二种是编译时根本无法预知该对象和类的真实信息,程序只能依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射。
因为加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

反射的作用:
1.在运行时获取任意类型的详细信息
2.在运行时能够创建任意引用数据类型的对象
3.在运行时可以为人以对象的任意属性赋值,或者获取任意对象的任意属性的值
4.在运行时可以调用人以对象的任意方法

3.1 java.lang.Class

Class是lang包下的一个类,所有的Java类型,包括基本数据类型、引用数据类型、void 被加载到内存后,或者编译器编译生成的字节码,最终都会用一个Class对象来表示。

(1) 哪些类型有Class对象
所有类型都可以表示为Class的实例对象。
(1)class:外部类,内部类
(2)interface:接口
(3)[]:数组,所有具有相同元素类型和维数的数组共享同一个Class 对象
(4)enum:枚举
(5)annotation:注解@interface
(6)primitive type:8种基本数据类型
(7)void

    @Test
    public void test(){
        //1.8种基本数据类型
        Class integerClass = int.class;
        //2. void
        Class voidClass = void.class;
        //3. String
        Class stringClass = String.class;
        //4. 类、接口
        Class<_01> aClass = _01.class;
        //5. 数组
        Class aClass1 = int[].class;
        //6. 注解
        Class overrideClass = Override.class;
    }

(2)Class对象全局唯一

    @Test
    public void test2(){
        Class c1 = "".getClass();
        Class c2 = String.class;
        System.out.println(c1 == c2 ); //true
    }

结合(1)、(2)得出结论:所有的Java类型,在内存中都标识为唯一的一个Class对象

3.2 获得Class对象(重点)

Java程序中可以通过以下四种方式获得Class对象:
(1)类型名.class
场景:适用于编译期间(写代码的时候)已知的任意类型;
优点:基本数据类型只能通过这种方式获取其Class对象
缺点:编译期间必须知道类型名才能获取其Class对象

(2)调用任意对象的getClass()方法
可以获取该对象的运行时类型的Class对象,getClass()是Object类的方法,用于获取对象的运行时类型。
场景:适用于编译期间已知的类型
这种方式适用前提是可以获取对象,对于基本数据类型来说就无法使用。
(3)使用Class类的forName(String name)静态方法
该方法需要传入一个字符串参数,该值是某个类的全限定名;
该方法适用于除了数组以外的任意引用数据类型;
优点:类型可以在编译期间未知,全类名需要知道,也可以在配置文件中配置全类名或者键盘输入来指定

(4)调用类加载器对象的loadClass(String name)
该方法需要传入一个字符串参数,该值是某个类的全限定名
场景:一般用在自定义类加载器对象去加载指定路径下的类
注意:类加载器只能加载自己管辖范围内的类

方式(1)只适用于编译器间已知的类型,如果某个类型编译期间是已知的,优先考虑这种方式,代码更安全,效率更高;另外基本数据类型和也只能通过该方式获得Class对象;如果某个类型编译期间未知,我们只能通过某种方式获取该类型的全名称的字符串形式,那么就只能选择(3)和(4)了,但是该方法在运行期间仍然无法加载该类的话,会报ClassNotFoundException。对于基本数据类型和数组类型,无法通过第(3)和(4)方式获取。

@Test
public void test3() throws ClassNotFoundException{
    Class c1 = String.class;
    Class c2 = "hello".getClass();
    Class c3 = Class.forName("java.lang.String");
    Class c4 = ClassLoader.getSystemClassLoader().loadClass("java.lang.String");
    System.out.println(c1 == c2);//true
    System.out.println(c1 == c3);//true
    System.out.println(c1 == c4);//true
}
@Test
public void test4(){
    Class c1 = int.class;//基本数据类型
    Class c2 = void.class;//void类型

    Class c3 = String.class;//类
    Class c4 = Object.class;//类
    Class c5 = Class.class;//类
    Class c6 = Comparable.class;//接口

    //只要元素类型与维度一样,就是同一个Class
    Class c7 = int[].class;
    int[] arr1 = new int[5];
    int[] arr2 = new int[10];
    System.out.println(arr1.getClass() == c7);//true
    System.out.println(arr2.getClass() == c7);//true
    Class c9 = String[].class;
    Class c10 = int[][].class;
    System.out.println(c7 == c9);//false
    System.out.println(c7 == c10);//false  维度不一样不是同一个

    Class c11 = Override.class;//注解
    Class c12 = ElementType.class;//枚举
}
3.3 从Class中获取类的元数据信息

反射相关的API主要是java.lang.Class和java.lang.reflect包的内容。

Class类提供了大量实例方法来获取该Class对象所对应类的详细信息,例如:包、修饰符、类名、父类、父接口、注解,及成员(属性、构造器、方法)等

3.3.1 获取某个类的加载器

public ClassLoader getClassLoader()
返回该类的类加载器。
有些实现可能使用 null 来表示引导类加载器,比如此对象表示一个基本类型或 void,则返回 null

3.3.2 获取包名

public Package getPackage():获取此类的包。然后可以通过Package实例对象的getName()获取包名。

    @Test
    public void test2(){
        Package aPackage = _01_.class.getPackage();
        String name = aPackage.getName();
        System.out.println(name); //_17_反射._04_获取类元信息
    }
3.3.3 获取类型名

public String getName():以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。

    public void test3(){
        //java.lang.String
        System.out.println(String.class.getName());
        //int
        System.out.println(int.class.getName());
        //[I
        System.out.println(int[].class.getName());
        //[[[I
        System.out.println(int[][][].class.getName());
        //[Ljava.lang.Object;
        System.out.println(Object[].class.getName());
    }
  • 如果此类对象表示的是非数组类型的引用类型,则返回该类的二进制名称,即包.类名。
  • 如果此类对象表示一个基本类型或 void,则返回该基本类型或 void 所对应的 Java 语言关键字相同的字符串名称。
  • 如果此类对象表示一个数组类,则名字的内部形式为:表示该数组嵌套深度的一个或多个 ‘[’ 字符加元素类型名。元素类型名的编码如下:
3.3.4 获取类型修饰符

修饰符由 Java 虚拟机的 public、protected、private + final + static+ abstract + interface 对应的常量组成;

(1) public int getModifiers():返回此类或接口以整数编码的 Java 语言修饰符
(2) 修饰符和整数编码的关系存储在Modifier类的静态属性中,可以使用 java.lang.reflect包下的Modifier类的静态方法toString(int mod)方法来解码


注意:修饰符的编码为16进制,且某个位置为1,其余为0;这么设计的目的是方便根据修饰符编码获取修饰符,1bit存储数据的思想;只要mod值和某个修饰符的值按位与不为0,则表示有这个修饰符

    @Test
    public void test4(){
        //public final class String
        int modifiers = String.class.getModifiers();
        System.out.println(modifiers);//17
        System.out.println(Modifier.toString(modifiers));//public final
    }
  • 如果底层类是数组类,则其 public、private 和 protected 修饰符与其元素类型的修饰符相同。
  • 如果此 Class 表示一个基本类型或 void,则其 public 修饰符始终为 true,protected 和 private 修饰符始终为 false。
  • 如果此对象表示一个数组类、一个基本类型或 void,则其 final 修饰符始终为 true,其接口修饰符始终为 false。该规范没有给定其他修饰符的值。
3.3.5 获取父类或父接口

public Class getSuperclass()
返回表示此 Class 所表示的实体的超类的 Class。

  • 如果此 Class 表示 Object 类、一个接口、一个基本类型或 void,则返回 null
  • 如果此对象表示一个数组类,则返回 Object 类的 Class 对象
	@Test
	public void test3(){
		System.out.println(Integer.class.getSuperclass());//Number
		System.out.println(int.class.getSuperclass());//null
		System.out.println(Runnable.class.getSuperclass());//null
		System.out.println(int[].class.getSuperclass());//Object
		System.out.println(String[].class.getSuperclass());//Object
	}

public Class[] getInterfaces()

  • 此对象所表示的类或接口实现的接口
  • 如果此对象表示一个类,则返回值是一个数组,它包含了表示该类所实现的所有接口的对象。数组中接口对象顺序与此对象所表示的类的声明的 implements 子句中接口名顺序一致。
  • 如果此对象表示一个接口,则该数组包含表示该接口扩展的所有接口的对象。数组中接口对象顺序与此对象所表示的接口的声明的 extends 子句中接口名顺序一致。
  • 如果此对象表示一个不实现任何接口的类或接口,则此方法返回一个长度为 0 的数组
  • 如果此对象表示一个基本类型或 void,则此方法返回一个长度为 0 的数组。
@Test
public void test4(){
	Class clazz = String.class;
	Class[] interfaces = clazz.getInterfaces();
	for (Class inter : interfaces) {
		System.out.println(inter);
	}
}
3.3.6 获取内部类或外部类信息

(1)当前类是外部类,返回内部的类、接口
public Class[] getClasses() 返回所有公共的内部类和内部接口。包括从超类继承的公共类和接口成员以及该类声明的公共类和接口成员
public Class[] getDeclaredClasses() 返回所有类内部的类和接口。包括该类所声明的公共、保护、默认(包)访问及私有类和接口,但不包括继承的类和接口

(2)当前类是内部类,返回外部类或接口
public Class getDeclaringClass()如果此 Class 对象所表示的类或接口是一个内部类或内部接口,则返回它的外部类或外部接口,否则返回null

    @Test
    public void test5(){
        Class clazz = Map.class;
        Class[] inners = clazz.getDeclaredClasses();
        for (Class inner : inners) {
            System.out.println(inner);
        }
        //interface java.util.Map$Entry

        Class ec = Map.Entry.class; //内部接口
        Class outer = ec.getDeclaringClass();
        System.out.println(outer);
        //interface java.util.Map
    }
3.3.7 获取属性Field

属性是Field对象
所有类型在内存中都是Class对象,那么所有属性都是Field对象

属性的类型:Field类型
类的概念:具有相同特性的食物的抽象描述
所有属性的相同特征: 1.都有修饰符、数据类型、名称
所有属性的相同行为: 1.get操作和set操作
所以将属性抽象为Field类

四个方法用于访问Class对应类所包含的属性(Field):
public Field[] getFields()
获取此 Class 对象所表示的类或接口的所有可访问公共字段,包括继承的公共字段组成的数组。 返回一个包含某些 Field 对象的数组
返回数组中的元素没有排序,也没有任何特定的顺序。

public Field getField(String name)
返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段,包括继承的公共字段。name 参数是一个 String,用于指定所需字段的简称。

public Field[] getDeclaredFields()返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。包括公共、保护、默认(包)访问和私有字段,但不包括继承的字段。返回数组中的元素没有排序,也没有任何特定的顺序。

public Field getDeclaredField(String name) 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。

Field[] fields = clazz.getFields();
int count = 0;
for (Field field : fields) {
    count++;
    System.out.println(count+"属性的修饰符为:"+Modifier.toString(field.getModifiers()));
    System.out.println(count+"属性的数据类型为:"+field.getType().getName());
    System.out.println(count+"属性的名称为:" + field.getName());
}
System.out.println(count);
3.3.8 获取构造器

1.类的成员之一代码块经过编译后,融入了()或者() 因此无法通过Class获取代码块
2.构造器其实也进入了()中,所以这里所说的获取构造器,其实是获取() 实例初始化方法,一个构造器对应一个()

3.所有的构造器都是Constructor类型的对象
构造器都有:修饰符,名称,形参列表
行为: 都能new 对象

4.通过Class获取构造器
public Constructor getDeclaredConstructor(Class... parameterTypes)
获取指定参数类型的某个public,package,default,private的构造器;

public Constructor[] getDeclaredConstructors()

  • 获取所有的public,package,default,private的构造器
  • 如果此 Class 对象表示非静态上下文中声明的内部类,则第一个形参必须为外部类的class实例
public class TestConstructor{
	@Test
	public void test7() throws Exception{
		Class clazz = Outer.class;
		Constructor constructor = clazz.getDeclaredConstructor();
		System.out.println(constructor);//无参构造
		
		Class c = Outer.Inner.class;
		//因为Inner是非静态的内部类,所以它的构造器,默认第一个形参是外部类的实例对象
		Constructor cs = c.getDeclaredConstructor(Outer.class);
		System.out.println(cs);
	}
}
class Outer{
	class Inner{
		
	}
}

public Constructor getConstructor(Class... parameterTypes)
获取指定参数类型的公共构造方法
public Constructor[] getConstructors()
获取所有公共的构造方法

public class TestConstructor{
	@Test
	public void test7() throws Exception{
		Class clazz = Outer.class;
		Constructor[] constructors = clazz.getConstructors();
		for (Constructor constructor : constructors) {
		    System.out.println("构造器的修饰符为:"+Modifier.toString(constructor.getModifiers()));
		    System.out.println("构造器的名称:"+constructor.getName());
		    Parameter[] parameters = constructor.getParameters();
		    System.out.println("构造器的形参列表:" + Arrays.toString(parameters));
		}
}
class Outer{
	class Inner{
		
	}
}
3.3.9 获取方法

所有方法都有的数据信息:修饰符,方法名,返回值类型,形参列表,异常列表
都有的操作:被调用

四个方法用于访问Class对应的类所包含的方法(Method):
public Method getDeclaredMethod(String name,Class... parameterTypes)
name 参数是一个 String,它指定所需方法的简称;
parameterTypes 参数是 Class 对象的一个数组或0~n个Class对象,它按声明顺序标识该方法的形参类型。如果是无参方法,那么parameterTypes 可以不传或者传null。 因为可能存在重载的方法,所以在一个类中唯一确定一个方法,需要方法名和形参类型列表。

public Method[] getDeclaredMethods()
包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法

public Method getMethod(String name,Class... parameterTypes)
指定的公共成员方法。包括继承的公共方法

public Method[] getMethods()
所有公共成员方法。包括继承的公共方法

@Test
	public void test9(){
		Class clazz = String.class;
		Method[] methods = clazz.getMethods();
		for (Method method : methods) {
			int mod = method.getModifiers();
			Class returnType = method.getReturnType();
			String name = method.getName();
			Class[] parameterTypes = method.getParameterTypes();
			System.out.print(Modifier.toString(mod)+"t" + returnType + "t" + name + "(");
			System.out.println(Arrays.toString(parameterTypes)+")");
		}
	}

注意:当重写父类的方法的时候,编译器会将继承的方法编译成两个部分,也就是字节码中有两个版本:编译器是为了兼容老版本,一个是泛型编译后的,一个是没有泛型的

3.3.10 获取泛型父类的泛型实参

JDK1.5引入的泛型,为了通过反射操作这些泛型,新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType几种接口来代表不能被归一到Class中的类型但是又和原始类型齐名的类型。

  • Type代表Java中所有类型
  • Class代表普通的类型,没有泛型信息
  • ParameterizedType:参数化类型 例如:Farther
  • GenericArrayType: 泛型数组类型 比如T[]
  • TypeVariable: 类型变量,例如 T
  • WildcardType:带通配符的泛型的类型,例如ArrayList

在Class类、Field类、Method类等API中增加了很多关于获取泛型信息的方法,例如在Class类中就有很多,其中有一个获取泛型父类的方法:

public Type getGenericSuperclass()返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type。
注意:这里返回的不是Class类型的对象,而是Type类型的,因为Class类型不能表示泛型,而Type相当于Class的父接口,包含了一些泛型类型

示例代码:
package com.atguigu.reflect;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;

public class TestGenericSuperClass {
	public static void main(String[] args) {
		Class c = base.class;
		TypeVariable[] typeParameters = c.getTypeParameters();
		for (TypeVariable typeVariable : typeParameters) {
			System.out.println(typeVariable + ",上限:" + typeVariable.getBounds()[0]);
		}
		
		Class clazz = Sub.class;
		Type gs = clazz.getGenericSuperclass();
		
		ParameterizedType gt = (ParameterizedType)gs;
		Type[] types = gt.getActualTypeArguments();
		for (Type type : types) {
			System.out.println(type);
		}
	}
}
class base{
	
}
class Sub extends base{
	
}
3.3.11 10、获取注解信息

可以通过反射API,获得相关的注解信息。
public Annotation[] getAnnotations()返回此元素上存在的所有注释。
public Annotation[] getDeclaredAnnotations()获取某元素上存在的所有注释。该方法将忽略继承的注释。
public T getAnnotation(Class annotationClass)如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。
示例代码:

package com.atguigu.reflect;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class TestAnnotation {
	public static void main(String[] args) {
		Class clazz = MyClass.class;
		MyAnnotation my = clazz.getAnnotation(MyAnnotation.class);
		System.out.println(my.value());
	}
}
@MyAnnotation
class MyClass{
	
}
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{
	String value() default "尚硅谷";
}

提示:要想通过反射获取到某个注解的信息,该注解声明时必须加@Retention(RetentionPolicy.RUNTIME)元注解,表明滞留注解信息到运行时。

4.反射生成并操作对象 4.1 使用反射创建对象

通过反射来生成对象有如下两种方式:
方式一:使用Class对象的newInstance()方法来创建该Class对象对应类的实例,这种方式要求该Class对象的对应类有默认构造器,而执行newInstance()方法时实际上是利用默认构造器来创建该类的实例。
如果没有无参构造器,报异常:NoSuchMethodException,没有()

方式二:先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance(Object… args)方法来创建该Class对象对应类的实例。通过这种方式可以选择使用某个类的指定构造器来创建实例

第二种方式需要知道形参列表,也就是必须知道类名和形参列表才能创建对象
对于私有构造器的话,需要用getDeclaredConstructor(…),并将获取的Constructor对象设置setAccessible(true),设置为可访问的。
通过第一种方式来创建对象是比较常见的情形,因为在很多JavaEE框架中都需要根据配置文件信息来创建实例对象,从配置文件读取的只是某个类的字符串类名,程序就需要根据该字符串来创建对应的实例,就必须使用反射。

使用两种方式创建实例对象的示例代码:

package com.atguigu.reflect;

import java.lang.reflect.Constructor;

import org.junit.Test;

public class TestNewInstance {
    //方式1
	@Test
	public void test1() throws Exception{
		Class clazz = Class.forName("com.atguigu.reflect.Student");
		Object obj = clazz.newInstance();//obj的编译时类型为Object
		System.out.println(obj);
	}
	
    //方式2
	@Test
	public void test2() throws Exception{
		Class clazz = Class.forName("com.atguigu.reflect.Student");
		Constructor constructor = clazz.getDeclaredConstructor(String.class);
		Object obj = constructor.newInstance("佟刚");
		System.out.println(obj);
	}
}
class Student{
	private String name;

	public Student(String name) {
		super();
		this.name = name;
	}

	public Student() {
		super();
	}

	@Override
	public String toString() {
		return "Student [name=" + name + "]";
	}
}
4.2 动态获取或设置某个对象的属性值

推荐使用类的无参构造:
(1)创建对象方便
(2)继承也方便
(3)反射创建对象也方便
通过Class对象的getFields()等方法可以获取该类所包括的全部Field(属性)或指定Field。而Field类除了提供获取属性的修饰符、属性类型、属性名等方法外,还提供了如下两组方法来访问属性:
public xxx getXxx(Object obj):获取obj对象该Field的属性值。此处的Xxx对应8种基本数据类型,如果该属性的类型是引用数据类型,则直接使用get(Object obj)方法。
public void setXxx(Object obj,Xxx value):设置obj对象该Field的属性值为value。此处的Xxx对应8种基本数据类型,如果该属性的类型是引用数据类型,则直接使用set(Object obj, Object value)方法。
public void setAccessible(boolean flag)启动和禁用访问安全检查的开关。

  • 值为true则指示反射的对象在使用时应该取消Java语言访问检查。
  • 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true.
  • 使得原本无法访问的私有成员也可以访问
  • 值为false则指示反射的对象应该实施Java语言访问检查。

获取或设置某个对象的属性值示例代码:

package com.atguigu.reflect;

import java.lang.reflect.Field;

public class TestField {

	public static void main(String[] args)throws Exception {
         //step1.获取类的Class对象
		Class clazz = Class.forName("com.atguigu.reflect.Circle");
         //step2.获取实例对象 不需要有参数
		Object obj = clazz.newInstance();
         //3.获取属性Field
		Field field = clazz.getDeclaredField("radius");
		field.setAccessible(true);
         //4.给属性赋值  将属性赋给某个对象
		field.set(obj, 1.2);
         //5.获取某个对象该属性的值
		Object value = field.get(obj);
		System.out.println(value);
	}

}
class Circle{
	private double radius;
}
4.3 动态调用对象的任意方法

当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods()等方法获取全部方法或指定方法。每个Method对象对应一个方法,获得Method对象后,程序就可以通过该Method对象的invoke方法来调用对应方法。
示例代码:

package com.atguigu.reflect;

import java.lang.reflect.Method;

public class TestMethod {

	public static void main(String[] args) throws Exception {
         //step1.获取类的Class对象
		Class clazz = Class.forName("com.atguigu.reflect.Utils");
         //step2.获取实例对象 如果调用非静态方法需要用到实例对象
		Object obj = clazz.newInstance();
         //step3.获取方法Method  通过方法名+形参列表
		Method method = clazz.getMethod("check", String.class,String.class);
         //step4.调用方法
         //调用非静态方法:
		Object value = method.invoke(obj, "tong","666");
         //调用静态方法:
         // 1.不需要获取对象实例
         // 2.invoke(null,形参列表)
		System.out.println(value);
	}

}
class Utils{
	public boolean check(String user,String password){
		if("admin".equals(user) && "123".equals(password)){
			return true;
		}else{
			return false;
		}
	}
}
4.5 操作数组

在java.lang.reflect包下还提供了一个Array类,Array对象可以代表所有的数组。程序可以通过使用Array类来动态的创建数组,操作数组元素等。
Array类提供了如下几个方法:
public static Object newInstance(Class componentType, int... dimensions):创建一个具有指定的组件类型和维度的新数组。
public static void setXxx(Object array,int index,xxx value):将array数组中[index]元素的值修改为value。此处的Xxx对应8种基本数据类型,如果该属性的类型是引用数据类型,则直接使用set(Object array,int index, Object value)方法。
public static xxx getXxx(Object array,int index,xxx value):将array数组中[index]元素的值返回。此处的Xxx对应8种基本数据类型,如果该属性的类型是引用数据类型,则直接使用get(Object array,int index)方法。
示例代码:

package com.atguigu.reflect;

import java.lang.reflect.Array;

public class TestArray {
	public static void main(String[] args) {
		Object arr = Array.newInstance(String.class, 5);
		Array.set(arr, 0, "尚硅谷");
		Array.set(arr, 1, "佟刚");
		System.out.println(Array.get(arr, 0));
		System.out.println(Array.get(arr, 1));
		System.out.println(Array.get(arr, 2));
	}
}
5.动态语言和静态语言

动态语言:
Var temp = 10;
Temp = “hello”
动态语言的变量的数据类型是运行时确定的,静态语言是编译的时候就确定的。

Java反射机制使得Java也支持,在运行期间,确定某个变量的类型
静态语言:效率高 安全
动态语言:灵活,不安全 边解释 边执行

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/274566.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号