Java高级-反射
15.1.Java反射机制概述
15.2.理解Class类并获取Class实例(重点)
- 用反射实现类的实例化,调用等操作
public class ReflectionTest {
// 反射之前,对于Person类的操作
@Test
public void test1(){
// 1.创建Person类的对象
Person p1 = new Person("tom", 12);
// 2.通过对象调用内部属性,方法
p1.age = 10;
System.out.println(p1.toString());
p1.show();
// 在Person类外部,不可以通过Person类的对象调用其内部私有结构.
// 比如: name . showNation()以及私有的构造器
}
// 反射之后,对于Person类的操作
// Person类在运行时候,把它称作运行时类,相当
@Test
public void test2() throws Exception {
// 原来可以做到的
// 1.通过反射,创建Person类的对象
// Person类有个属性".class"作为Class的实例
Class clazz = Person.class;
// 获取clazz对象指定的构造器
Constructor cons = clazz.getConstructor(String.class, int.class);
// 通过构造器造Object类型对象
Object obj = cons.newInstance("Tom", 12);// 本质上obj就是Person类型,多态形式
Person p = (Person) obj;// 因此可以强转
System.out.println(obj.toString());// 实际调用Person类重写的toString方法
// 2.通过反射,调用对象指定的属性,方法
// 获取到clazz对应的Person类里age属性
// 调属性
Field age = clazz.getDeclaredField("age");
// 给p对象的age属性赋值为10
age.set(p, 10);// 可以看成 p.setage
System.out.println(p.toString());
// 调方法,其中有可变形参为参数类型,没有也可不写
Method show = clazz.getDeclaredMethod("show");// 空参的
// 调用
show.invoke(p);// 参数为实参
System.out.println("=============================");
//原来做不到的: 通过反射,可以调用Person类(运行时类)的私有结构的。比如:私有的构造器、方法、属性
// 调用私有构造器
// 创建String类型的构造器
Constructor cons1 = clazz.getDeclaredConstructor(String.class);
cons1.setAccessible(true); // 简单的说就是把private封装的变量和方法,强行解锁了,使之可以被访问,被调用
// 通过构造器创建对象,p1就Person类的对象
Person p1 = (Person) cons1.newInstance("Jerry");
System.out.println(p1.toString());
// 调用私有属性,属性名为name
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1, "hanmeimei");// 直接通过对象操作属性
System.out.println(p1.toString());
// 调用私有方法
Method showNation = clazz.getDeclaredMethod("showNation", String.class);
showNation.setAccessible(true);
// showNation是Method类型的对象,Java里永远都是对象调,只不过这个对象恰好是原来的方法,参数p1是原来的对象
// 其实仍然是对象调属性,方法 invoke():默认返回Object类型
String nation = (String) showNation.invoke(p1, "中国");// 相当于 String naton = p1.showNation("中国")
System.out.println(nation.toString());
}
}
- 反射强大之处: 用反射调用类的私有结构
//原来做不到的: 通过反射,可以调用Person类(运行时类)的私有结构的。比如:私有的构造器、方法、属性
// 调用私有构造器
// 创建String类型的构造器
Constructor cons1 = clazz.getDeclaredConstructor(String.class);
cons1.setAccessible(true); // 简单的说就是把private封装的变量和方法,强行解锁了,使之可以被访问,被调用
// 通过构造器创建对象,p1就Person类的对象
Person p1 = (Person) cons1.newInstance("Jerry");
System.out.println(p1.toString());
// 调用私有属性,属性名为name
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1, "hanmeimei");// 直接通过对象操作属性
System.out.println(p1.toString());
// 调用私有方法
Method showNation = clazz.getDeclaredMethod("showNation", String.class);
showNation.setAccessible(true);
// showNation是Method类型的对象,Java里永远都是对象调,只不过这个对象恰好是原来的方法,参数p1是原来的对象
// 其实仍然是对象调属性,方法 invoke():默认返回Object类型
String nation = (String) showNation.invoke(p1, "中国");// 相当于 String naton = p1.showNation("中国")
System.out.println(nation.toString());
- 如何看待反射和封装性两个技术
疑问1:通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用那个?
建议:直接new的方式。
什么时候会使用:编译的时候不确定要new哪个类的对象,就用反射的方式。反射的特征:动态性.因为有不同类型的构造方法,而到运行的时候才能决定需要怎样的对象
疑问2:反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术?
不矛盾; 封装性是有public的结构就不建议调private的结构;反射机制是可以调用private的结构,看具体需求
- Class类的理解
关于java.lang.Class类的理解
1.类的加载过程:
编译过程:程序经过javac.exe(编译)命令以后,会生成一个或多个字节码文件(.cLass结尾)。
解释运行,加载的过程: 接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中,这个过程就称为类的加载。
加载到内存中的类,称之为运行时类,此运行时类,就作为Class类的一个实例
2.换句话说, Class的实例就对应着一个运行时类, 而且该运行时类一旦加载到内存以后,实际上会缓存一段时间,下面的只是通过不同方式获取了内存当中唯一的运行时类
说想提供一个Class的实例而去new一个Class是错误的.Class实例就是用一个运行时类来进行赋值的
3.加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
万事万物皆对象? 对象.xxx,File,URL,反射,前端,数据库操作
- Class实例的四种方式
// 获取Class的实例方式: (前三种方式要掌握)
@Test
public void test3() throws ClassNotFoundException {
// 方式一: 调用运行时类的属性: 这个属性叫做: .class class是Class的属性,用class去描述Class
// 编译时写死了,若类不存在,编译会报错
// Class是对于类的通用描述,此时其实是Person给他赋的值,所以Class具体操作都是Person
Class clazz1 = Person.class;// 可以加上泛型避免后面要强转
System.out.println(clazz1);// 获取到Person类本身
// 方式二: 通过运行时类的对象,调用getClass(); 先创建了类的对象不好用
Person p1 = new Person();
// 获取该对象是哪个类造的方法,任何一个对象都知道在哪个类造的,所以声明在Ojbect类中
Class clazz2 = p1.getClass();// 返回Person类本身赋给另外一个clazz2
System.out.println(clazz2);
// (最常用)方式三: 调用Class的静态方法: forName(String classPath),参数是类的全路径
// 最常用,因为通过反射能做的第一件事就是能创建对应的运行时类的对象,运行时才确定是否存在运行时类
// 这种方式更好的体现了动态性,因为反射主要想体现的就是运行时的动态性,编译的时候先不去确定
// 不管是什么类,这个类本身都作为Class的实例
Class clazz3 = Class.forName("com.senior.Person");// 类的全类名(包含包的完整路径)
System.out.println(clazz3);
// 虽然获取类的方式不同,但时都是获取内存中同一个运行时类,因为用的同一个类加载器,类只加载一次
System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);
// 方式四: 用类的加载器: ClassLoader
// 调用测试类的getClassLoader方法,获得一个类加载器
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
// 通过加载器显式的加载某个类
Class clazz4 = classLoader.loadClass("com.senior.Person");
}
- Class实例对应的结构的说明
// Class实例可以是哪些结构的说明: Class不仅可以表示为运行时类,还可以表示其他结构
@Test
public void test4(){
Class c1 = Object.class;
Class c2 = Comparable.class;
Class c3 = String[].class;
Class c4 = int[][].class;
Class c5 = ElementType.class;// 枚举类
Class c6 = int.class;
Class c7 = Override.class;
Class c8 = void.class;
Class c9 = Class.class;
// 只要数组元素类型和维度一样,就是同一个Class
int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
System.out.println(c10 == c11);// true
}
- 理解类的加载过程
15.3.类的加载与ClassLoader的理解
- ClassLoader的理解
// 自定义加载类是系统类加载器加载的
public class ClassLoaderTest {
@Test
public void test1(){
// 先获得Class类的实例,再获取当前自定义类的类加载器
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader); // AppClassLoader: 系统类加载器
// getParent(): 获取上一级加载器
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);// ExtClassLoader: 扩展类加载器
ClassLoader classLoader2 = classLoader1.getParent();
// 引导类加载器显示null不是没有,而是获取不到,不能主动去加载自定义类,不能直接通过它做一些事
// 引导类加载器主要负责加载java的核心类库,无法加载自定义类的。
System.out.println(classLoader2);// null: 无法获取引导类加载器
ClassLoader classLoader3 = String.class.getClassLoader();
System.out.println(classLoader3);// null
}
}
- 用ClassLoder加载配置文件
Properties: 用来读取配置文件
写web的时候properties文件写在src下,因为部署到Tomcat服务器是module下的配置文件就缺失了
为了保证文件的存在,要写在src下
@Test
public void test2() throws IOException {
// 创建配置文件的对象
Properties pros = new Properties();
// 把设置中File Encodings下的 Transparent native-to-ascii conversion 勾上防止乱码
// 此时的配置文件默认在当前的module下
// 读取配置文件方式一:
// 造一个输入流对象,构造器参数传入一个配置文件,配置文件在当前module下new一个Resource Bundle
FileInputStream fis = new FileInputStream("jdbc.properties");
// 读取的配置文件还是当前module下,只是module下多了一层目录下的文件
// FileInputStream fis = new FileInputStream("src\\jdbc1.properties");
// 通过(pros)读取文件对象去调用加载方法,参数传入输入流对象
pros.load(fis);// pros加载对应的文件输入流对象,其实主要操作对应的jdbc配置文件
// 方式二: 用ClassLoader 类加载器
// 配置文件默认识别为:当前module的src下
// 获取当前自定义加载类的类加载器
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
// 通过系统类加载器以流的形式获取资源文件
// File类型获取的是当前module下的,类加载器获取的是源(src)文件下的
InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
// 通过文件对象读取输入流的资源
//pros.load(is);
// getProperty(): 获取配置文件的键值对中的值: 都是String类型
// 分别获取user和password
String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("user = " + user + " password = " + password);
}
15.4.创建运行时类的对象(重点)
通过反射,创建运行时类的对象
通过反射创建对应的运行时类的对象
Class对应的是哪个运行时类,就只能创建那个类的对象
newInstance(): 调用此方法,创建对应的运行时类(Person)的实例(对象),内部调的是运行时类的空参构造器
造对象,都得是用构造器来造,只是形式上不一样
构造器私有,就是单例模式了,但也是从静态方法里调用构造器的
要想此方法正常的创建运行时类的对象,要求:
1.运行时类必须提供空参构造器
2.提供足够条件的空参构造器的权限修饰符.通常 设置为public
有可能在学框架的时候,框架底层要用到反射,造javabean的对象,通常通过反射造对象,普遍调空参构造器,要是有属性,后面再通过调属性方式给属性赋值
在javabean中要求提供一个public的空参构造器。原因:
1.便于通过反射,创建运行时类的对象
2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
public class NewInstanceTest {
// InstantiationException: 初始化异常, 运行时类没有空参构造器
// IllegalAccessException: 非法访问异常,构造器权限修饰符私有化,访问权限不够
@Test
public void test1() throws InstantiationException, IllegalAccessException {
// 先获取Class类的实例
// clazz实例指向方法区中的Person.class这个类
Class clazz = Person.class;
Person obj = (Person) clazz.newInstance();// 调的是运行时类的空参构造器
// 获取运行时类的对象
// Object obj = clazz.getDeclaredConstructor().newInstance();
System.out.println(obj);
}
}
- 举例体会反射的动态性
// 体会反射的动态性: 运行时才知道到底要造哪个类的对象
@Test
public void test2(){
for (int i = 0; i < 100; i++) {
int num = new Random().nextInt(3); // 0~2
String classPath = "";
switch (num) {
case 0:
classPath = "java.util.Date";
break;
case 1:
classPath = "java.lang.Object";
break;
case 2:
classPath = "com.senior.Person";
break;
}
try {
Object obj = getInstance(classPath);
System.out.println(obj);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
public Object getInstance(String classPath) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}
15.5.获取运行时类的完整结构
- 提供结构丰富的Person类
看看Person能不能拿到父类的泛型,并实现其他接口
把自定义注解加到类的相关结构上,不想用默认值可以自行修改,通过反射方式可以拿到注解
// 丰富结构都在Person里
public class Creature <T> implements Serializable {
private char gender;
public double weight;
public void breath(){
System.out.println("生物呼吸");
}
public void eat(){
System.out.println("生物吃东西");
}
}
public interface MyInterface {
// 定义抽象方法
public abstract void info();
}
// 两个元注解: 需要什么结构,生命周期
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
// 要想通过反射获取当前的注解,要把SOURCE改成RUNTIME,这时会加载到内存当中,只有加载到内存中的结构才能通过反射获取
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
// 创建一个数组 默认值为hello
String value() default "hello";
}
@MyAnnotation(value = "hi")
public class Person extends Creature<String> implements Comparable<String>,MyInterface{
private static String GENDER;
private String name;
int age;
public int id;
public Person(){
}
@MyAnnotation(value = "abc")
private Person(String name) {
this.name = name;
}
Person(String name, int age) {
this.name = name;
this.age = age;
}
private static void showDesc(){
System.out.println("我是个可爱的人");
}
@MyAnnotation
private String show(String nation){
System.out.println("我的国籍是: " + nation);
return nation;
}
public String display(String interests){
return interests;
}
@Override
public void info() {
System.out.println("我是个人");
}
@Override
public int compareTo(String o) {
return 0;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", id=" + id +
'}';
}
}
- 获取运行时类的属性结构及其内部结构
public class FieldTest {
@Test
public void test1(){
Class clazz = Person.class;
// 获取所有属性结构
// getFields():获取当前运行时类及其父类中声明为public访问权限的属性
Field[] fields = clazz.getFields();
for (Field f : fields) {
System.out.println(f);
}
System.out.println();
// 获取声明过的属性
Field[] declaredFields = clazz.getDeclaredFields();
for (Field f : declaredFields) {
System.out.println(f);
}
}
// 获取属性里的具体某个结构(部分)
//权限修饰符 数据类型 变量名
@Test
public void test2(){
// 获取运行时类的对象
Class clazz = Person.class;
// 获取当前类里所有属性
Field[] declaredFields = clazz.getDeclaredFields();
// 遍历数组
for (Field f : declaredFields){
// 1.获取权限修饰符
int modifier = f.getModifiers();
// Modifier类中重写了toString方法,default默认为空
System.out.print(Modifier.toString(modifier) + "\t");
// 2.获取数据类型
Class type = f.getType();
// getName(): 省略class
System.out.println(type.getName() + "\t");
// 3.获取变量名
String fName = f.getName();
System.out.print(fName);
System.out.println();
}
}
}
- 获取运行时类的方法结构
public class MethodTest {
@Test
public void test1(){
Class clazz = Person.class;
// getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
Method[] methods = clazz.getMethods();
for (Method m : methods) {
System.out.println(m);
}
//getDeclaredMethods ():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method m : declaredMethods) {
System.out.println(m);
}
}
}
- 获取运行时类的构造器结构
@Test
public void test1(){
Class clazz = Person.class;
// getConstructors():获取当前运行时类中声明为public的构造器
Constructor[] constructors = clazz.getConstructors();
for (Constructor c : constructors) {
System.out.println(c);
}
System.out.println();
// getDeclaredconstructors():获取当前运行时类中声明的所有的构造器
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor c : declaredConstructors) {
System.out.println(c);
}
}
- 获取运行时类的(带泛型的)父类
/*
获取运行时类的父类
*/
@Test
public void test2(){
Class clazz = Person.class;
// 父类也是Class的实例
Class superClass = clazz.getSuperclass();
System.out.println(superClass);
}
/*
获取运行时类的带泛型的父类
*/
@Test
public void test3(){
Class clazz = Person.class;
// 父类也是Class的实例
// Class类实现了Type接口
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
}
- 获取运行时类的带泛型的父类的泛型
/*
获取运行时类的带泛型的父类的泛型
*/
@Test
public void test4(){
Class clazz = Person.class;
// 父类也是Class的实例
Type genericSuperclass = clazz.getGenericSuperclass();
// 确定类型的参数
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
// 获取实际上的类型参数,实际上就是获取泛型参数
Type[] actualTypeArguments = paramType.getActualTypeArguments();
System.out.println(actualTypeArguments[0]);// 返回泛型类
// System.out.println(actualTypeArguments[0].getTypeName());// 省略class关键字
// System.out.println(((Class)actualTypeArguments[0]).getName());// 同上
System.out.println(paramType);
System.out.println(genericSuperclass);
}
- 获取运行时类实现的接口,包,注解
/*
获取运行时类实现的接口
*/
@Test
public void test5(){
Class clazz = Person.class;
// 因为可以多实现所以返回数组类型
Class[] interfaces = clazz.getInterfaces();
for (Class c : interfaces) {
System.out.println(c);
}
System.out.println();
// 获取运行时类的父类实现的接口
Class[] interface1 = clazz.getSuperclass().getInterfaces();
for (Class c : interface1) {
System.out.println(c);
}
}
/*
获取运行时类所在的包
*/
@Test
public void test6(){
Class clazz = Person.class;
Package pack = clazz.getPackage();
System.out.println(pack);
}
/*
获取运行时类声明的注解
*/
@Test
public void test7(){
Class clazz = Person.class;
// 因为注解可能有多个,所以返回数组类型
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annos : annotations) {
System.out.println(annos);
}
}
15.6.调用运行时类的指定结构(重点)
- 调用运行时类的指定属性
/*
不需要掌握
调用运行时类的指定属性
*/
public class ReflectionTest {
@Test
public void testField() throws Exception{
// 获取Class类的实例
// 一个类只有唯一的一个运行时类,即大Class对象
Class clazz = Person.class;
// 调Person类的(空参)构造器创建运行时类的对象
Person p = (Person) clazz.newInstance();
// 获取运行时类的指定属性: 要求运行时类中属性声明为public
// 通常不采用此方法
Field id = clazz.getField("age");
//id.setAccessible(true); // 不适用于getField()
/*
设置当前属性值
set(): 参数1: 指明设置哪个对象的属性
参数2: 将此属性值设置为多少
*/
id.set(p, 1001);
// 获取当前运行时类对象属性的值
// get(): 参数1: 获取当前对象的属性值
int pId = (int) id.get(p);
System.out.println(pId);
}
/*
如何操作运行时类中的指定的属性 -- 要掌握
*/
@Test
public void testField1() throws Exception {
Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
// 1.getDclaredField(String fieldName): 获取运行时类中指定变量名的属性
Field name = clazz.getDeclaredField("name");
// 2.保证当前属性是可访问的: 如果是私有的情况下
name.setAccessible(true);
// 3.获取,设置指定对象的此属性
name.set(p, "tom");
System.out.println(name.get(p));
}
/*
调用运行时类的静态属性
*/
@Test
public void testField2() throws Exception{
Class clazz = Person.class;
// 获取运行时类的属性
Field gender = clazz.getDeclaredField("GENDER");
// 保证属性可访问
gender.setAccessible(true);
// 调用属性
gender.set(null,"male");// 对象参数也可写当前运行时类
System.out.println(gender.get(null));
}
}
- 调用运行时类的指定方法
/*
如何操作运行时类的指定的方法 -- 要掌握
非静态的方法: 必须要有运行类的对象
*/
@Test
public void testMethod() throws Exception {
// 获取运行时类
Class clazz = Person.class;
// 创建运行时类的对象
Person p = (Person) clazz.newInstance();
// 1.获取指定的某个方法
// getDeclaredMethod(): 参数1: 指明获取的方法名称, 参数2: 指明获取的方法的形参列表
Method show = clazz.getDeclaredMethod("show", String.class);// 因为show是Class类型的方法,所以参数类型也要是class类型
// 2.保证方法可访问
show.setAccessible(true);
/*
3.调用方法的invoke(): 参数1: 方法的调用者, 参数2: 给方法形参赋值的实参
invoke()的返回值即为对应类中调用方法的返回值,默认是Object类型
*/
Object returnValue = show.invoke(p, "CHN");// String nation = p.show("CHN");
System.out.println(returnValue);
/*
如何调用静态方法
*/
// 1.获取运行时类的方法
Method showDesc = clazz.getDeclaredMethod("showDesc");
// 2.保证方法可访问
showDesc.setAccessible(true);
// 3.方法调用invoke(): 如果调用的运行时类中的方法没有返回值,则此invoke()返回null
// Object returnaVal = showDesc.invoke(clazz); 这样也可以
Object returnaVal = showDesc.invoke(Person.class);
// Object returnaVal = showDesc.invoke(null);// 因为静态方法都是共享的,所以调用不需要具体对象所以可以写null
System.out.println(returnaVal);// null
}
- 调用运行时类的指定构造器
@Test
public void testConstructor() throws Exception{
Class clazz = Person.class;
// 获取运行时类指定的构造器
// getDeclaredConstructor(): 参数: 指明构造器的参数列表
Constructor constructor = clazz.getDeclaredConstructor(String.class);
// 保证构造器可访问的
constructor.setAccessible(true);
// 调用构造器的newInstance()造对象
// 构造器调的newinstance()和运行类调的不同,运行时类的是空参的,构造器调的是带参数的重载构造器
Person p = (Person) constructor.newInstance("Tom");// 形参对应运行时类构造器的参数类型
System.out.println(p);
}