【黑马程序员】Java基础加强15:反射Reflect

时间:2022-04-07 19:42:37
---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------


一、什么是反射

反射是程序在运行时期,对一个类的class文件进行解析,获取其构造方法、成员变量、成员方法,还能运行这些方法;

简单一句话:反射就是把Java类中的各种成分映射成相应的Java类,出现的目的是增强程序的扩展性。

 

反射的基石:Class

Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class

Class类代表Java类,它的各个实例对象对应各个类在内存中的字节码,比如String是一种类,它加载到内存中会反射为String.class的类。

 

得到Class对象(字节码)的三种方法:

1、对象.getClass(),例如,new Person().getClass()

2、已知类名.class,例如,System.classPerson.class

3Class.forName("完整类名"),例如,Class.forName("java.util.Date")Class.forName("java.lang.String");在反射之中,常用第三种方法,类名可以在需要时换成字符串变量,最后传入。

/*
通过代码示例三种得到Class对象的方法
Person p = new Person();

第一种方法,通过对象获取
Class c1 = p.getClass();
System.out.println(c1);

第二种方法,通过类的静态属性获取
Class c2 = Person.class;
System.out.println(c2);
System.out.println(c2 == c1);//true
System.out.println(c2.equals(c1));//true

第三种方法,通过Class.forName(完整类名)
Person如果定义了包,应该为:包名.类名
Class c3 = Class.forName("Person");
System.out.println(c3);
*/

九种预定义Class实例对象

1在Class类*创建了九个预定义对象,八个基本类型boolean,byte,char,short,int,long,float,double和void(void也有对应的class);

2预定义对象有如下示例:int.class==Integer.TYPEvoid.class==Void.TYPE

3这些对象仅能通过声明为publicstaticfinal的变量访问。

//对class类型的判断
System.out.println(int.class.isPrimitive());//true
System.out.println(int.class == Integer.class);//false
System.out.println(int.class == Integer.TYPE);//true
System.out.println(int[].class.isPrimitive());//false,不是基本类型
System.out.println(int[].class.isArray());//true,是数组类型

二、Java反射的三个重要类

1Constructor类:代表某个类中的构造方法

获取方法:

getConstructor(Class<?>... parameterTypes) 返回公共构造方法

getConstructors() 返回公共构造方法数组

getDeclaredConstructor(Class<?>... parameterTypes) 返回构造方法,包括私有

getDeclaredConstructors()返回构造方法数组,包括私有

代码示例:获得String类中所有的构造方法

package blog.itheima;

import java.lang.reflect.Constructor;

public class ReflectConstructor {

public static void main(String[] args) throws Exception {

//获得String类中的公有构造方法数组,也可以自定义类的构造方法,通过反射获取
Constructor[] construtor = Class.forName("java.lang.String").getConstructors();
//获得某一个构造方法:默认得到的是无参的构造方法
Constructor constructor2 = Class.forName("java.lang.Thread").getConstructor();
System.out.println(constructor2);//打印:public java.lang.Thread()

//得到String类中的String(StringBuffer buffer)的构造方法
//也可以通过可变参数,多传几个class类型,得到多个构造方法
Constructor constructor = String.class.getConstructor(StringBuffer.class);
System.out.println(constructor);
/*打印:public java.lang.String(java.lang.StringBuffer)
说明constructor是对应String类的StringBuffer构造方法的字节码对象

再通过newInstance来创建实例化对象,里面需要传入同类型的对象StringBuffer,可以调用多次*/
String string1 = (String) constructor.newInstance(new StringBuffer("abc"));
System.out.println(string1.charAt(1));

//Class.newInstance()方法等价于使用constructor,newInstance()无参构造器
String string2 = (String) Class.forName("java.lang.String").newInstance();
//该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象
}
}

2Field类:代表某个类中的成员变量

获取方法:

getField(String name) 返回公共成员字段

getFields() 返回公共成员字段数组

getDeclaredField(String nam) 返回成员字段,包括私有

getDeclaredFields()返回成员字段数组,包括私有

//获得某个类上的字节码对象,并取得对应的值
package blog.itheima;
import java.lang.reflect.Field;
import java.util.Date;

public class ReflectFieldGet {
public static void main(String[] args) throws Exception {

ReflectTest rt = new ReflectTest(3,5);
//要得到一个类身上的字段,需要先得到类的字节码
Field fieldY = rt.getClass().getField("y");
System.out.println(fieldY);
/*打印结果:public int blog.itheima.ReflectTest.y
fieldY只是代表类字节码身上的变量,没有对应到对象身上的值
要取得某个对象上对应的值,需要指定相应的对象*/
System.out.println(fieldY.get(rt));

//想要取x字段,用与y相同的方法
//Field fieldX = rt.getClass().getField("x");
//但是运行结果会报错,因为x是私有字段,要用如下方法获取
Field fieldX = rt.getClass().getDeclaredField("x");
//因为是私有 字段,要更改属性为可以访问,称为暴力反射
//注意,Constructor, Field, Method 三个常用类都有暴力反射方法
fieldX.setAccessible(true);
System.out.println(fieldX.get(rt));
}
}

class ReflectTest {

private int x;
public int y;

public ReflectTest(int x, int y) {
super();
this.setX(x);
this.y = y;
}

public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
}

//字段的更改
//需求:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"n"改成"a"。
package blog.itheima;
import java.lang.reflect.Field;
public class ReflectFieldReplace {
public static void main(String[] args) throws Exception {
ReflectTest2 rt = new ReflectTest2();
changeStringValue(rt);
System.out.println(rt);
}

private static void changeStringValue(Object obj) throws Exception {
Field[] fields = obj.getClass().getFields();//得到所有字段的数组
for(Field field : fields){
//if(field.getType().equals(String.class)){
//字节码只有一分,用等号比更加专业
if(field.getType() == String.class){
//得到字节码对应对象的值,强转成String
String oldValue = (String)field.get(obj);
String newValue = oldValue.replace('n', 'a');
/*void set(Object obj, Object value)
将指定对象变量上此 Field 对象表示的字段设置为指定的新值*/
field.set(obj, newValue);
}
}
}//字段反射的作用:可以更改对象里的字段信息,或者更改配置文件的信息
}

class ReflectTest2 {

public String str1 = "baidu";
public String str2 = "Tencent";
public String str3 = "sina";

@Override
public String toString() {
return "ReflectTest2 [str1=" + str1 + ", str2=" + str2 + ", str3="
+ str3 + "]";
}
}

3Method类:代表某个类中的成员方法

获取方法:

getMethod(String name, Class<?>...parameterTypes)公共成员方法

getMethods() 公共成员方法数组

getDeclaredMethod(String name, Class<?>...parameterTypes)所有成员方法

getDeclaredMethods() 所有成员方法数组,包括私有

//获取String中的charAt方法

package blog.itheima;
import java.lang.reflect.Method;
public class ReflectMethod {

public static void main(String[] args) throws Exception {

String s = "abcde";
/*getMethod(String name,Class<?>... parameterTypes)
name - 方法名 parameterTypes - 参数列表,charAt返回的是int型*/
Method methodCharAt = String.class.getMethod("charAt", int.class);
/*Object invoke(Object obj, Object... args)
对带有指定参数的对象调用由此 Method 对象表示的底层方法,即调用charAt方法*/
System.out.println(methodCharAt.invoke(s, 1));//string.charAt(1)
/*注意:如果传递给Method对象的invoke方法的第一个参数为null,说明该Method
对象对应的是一个静态方法*/

//1.4语法调用,把2当作Integer对象,传入一个元素到Object数组中
System.out.println(methodCharAt.invoke(s, new Object[]{2}));
}
}

//用反射方式执行某个类中的main方法
package blog.itheima;
import java.lang.reflect.Method;
public class ReflectMethodMain {

public static void main(String[] args) throws Exception {
//用静态代码方法直接调用,传统的调用方法
//TestArguments.main(new String[]{"111","222","333"});

/*用反射方式去调用main方法的原因?因为有可能开始并不知道类名,
最后才传入类名参数,为了不更改源代码,可以用反射*/
String startingClassName = args[0];
//main方法参数形式是String的数组,只有一个参数
Method mainMethod =
Class.forName(startingClassName).getMethod("main", String[].class);
//main是静态方法,第一个参数为null,
//把new String[]打包成一个Object数组,让系统拆包成一个,只让拆一次
//mainMethod.invoke(null, new Object[]{new String[]{"111","222","333"}});

//也可以把new String[]数组直接强制转成Object,变成一个对象,不让拆包
mainMethod.invoke(null, (Object)new String[]{"111","222","333"});
}
}

class TestArguments{
public static void main(String[] args){
for(String arg : args){
System.out.println(arg);
}
}
}
---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------