目录
1、不可变类 String对象是否真的不可变?
不可变类的特性:
-
(1). String类被 final修饰,不可继承;
-
(2). String内部所有成员都设置为不可变 final和变为 私有变量private修饰;
-
(3). 不存在value的 setter;
-
(4). 当传入可变数组value[]时,进行深拷贝copy而不是直接将 value[]复制给内部变量.
-
(5). 获取value时不是直接返回对象引用,而是返回 对象的copy.
虽然String对象通过各种机制保证其成员变量不可改变,但也不是说一定就不可变,我们依然可以使用
反射来改变,看一个实例:
public class StringTestDemo {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
//创建字符串"Hello World", 并赋给引用s
String s = "Hello World";
System.out.println("修改前:s = " + s); //Hello World
//获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");
//改变value属性的访问权限
valueFieldOfString.setAccessible(true);
//获取s对象上的value属性的值
char[] value = (char[]) valueFieldOfString.get(s);
//改变value所引用的数组中的第6个字符
value[5] = '_';
System.out.println("反射修改后:s = " + s); //Hello_World
}
}
打印结果为:
修改前:s = Hello World
反射修改后:s = Hello_World
发现String的值已经发生了改变。也就是说,可以通过反射是可以修改所谓的“不可变”对象的。
2、什么是反射?
java的反射机制是在运行状态中,对于任意一个类首先获取其字节码,然后通过构造函数生成一个java对象实例,对于任意一个对象,都能够调用它的任意方法和属性。这种动态获取信息以及动态调用对象方法的功能称为
java
语言的反射机制,这也是Java被视为动态语言的关键特性,源码中用的非常多。
3、反射能做什么?
我们知道反射机制允许程序在运行时获取class字节码文件,进而访问或者改变fields值或执行调用method方法。
-
class字节码文件:得到任何一个已知名称的class类的内部信息;
-
constructor:得到构造函数;
-
instance:生成类的实例对象;
-
fields(属性);包括包括其modifiers(修饰符);
-
methods(方法);
-
method.invoke() 执行目标方法;
4、反射的使用步骤
-
(1). 获得Class对象,就是获取到指定的名称的字节码文件对象;
-
(2). 实例化对象,获得类的属性、方法及方法参数或构造函数;
-
(3). 访问改变属性值、执行调用目标方法;
4.1、获取Class对象的三种方式
当通过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪个特定的类。因此,类的.class信息对于JVM来说必须是可获取的,可以是本地机器上,网络或者磁盘。
(1)、
getClass() 通过每个对象都具备的方法getClass来获取。弊端:必须要创建该类对象,才可以调用getClass方法。
Class<?> class = object.getClass();
(2)、Object.class 每一个数据类型(基本数据类型和引用数据类型)都有一个静态的属性class。弊端:必须要先明确该类。
Class<?> class = Student.class;
ps: 前两种方式不利于程序的扩展,因为都需要在程序使用具体的类来完成。
(3)、
forName() 使用的Class类中的方法,静态的
forName方法。指定类名,获取类字节码文件对象,这种方式的扩展性最强,只要将类名的字符串传入即可。
Class clazz = Class.forName("com.makaruina.reflect.Person");
4.2、实例化实例对象
获取了字节码文件对象后,然后就需要创建类的实例对象。
4.2.1. 调用指定带参数的构造函数,然后通过该构造器对象的newInstance(实际参数) 进行对象的初始化。
Constructor constructor = clazz.getDeclaredConstructor(String.class,int.class, String.class);
Object obj = constructor.newInstance("king",1,"java");
4.2.2. 直接使用了Class类调用无参构造函数生成实例对象;
//1、获取字节码;
Class clazz = Class.forName("com.king.Reflection.Person");
//2、生成实例对象;
Object obj = clazz.newInstance();
4.3、访问或执行
获取类中的方法,属性,构造函数,进行函数的调用。
classType.getName() //获取类的完成名字,全路径名:
getFields(): //获得类的public类型的属性。
getDeclaredFields(): //获得类的所有属性。
getMethods():获得类的public类型的方法。
getDeclaredMethods(): //获得类的所有方法
getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes参数指定方法的参数类型。
5、反射的api应用实例
/*
* 反射的基本步骤:
* 1. 获得Class对象,就是获取到指定的名称的字节码文件对象。
* 2. 实例化对象,获得类的属性、方法或构造函数。
* 3. 访问属性、调用方法、调用构造函数创建对象。
*/
public class ReflectionTest {
//getClass
public static void getClassTest() throws Exception {
Son son = new Son("king", 29, "hust");
//todo 得到class的三种方式
//1、通过每个对象都具备的方法getClass来获取。通过对象调用来getClass来获取,通常用于传过来的是一个Object,而我不知道你具体的是什么类
//弊端:必须要创建该类对象,才可以调用getClass方法。
Class aClass = son.getClass();
//2、直接通过类名来获取class,任何一个类都有一个隐含的静态成员变量class,该方法安全可靠,程序性能更高;
// 弊端:必须先明确类
Class bClass = Son.class;
//3、使用Class类中的方法,forName(),指定什么类名,就获取什么类字节码文件对象,这种方式的扩展性最强,只要将类名传入即可。
Class cClass = Class.forName("com.beijing.king.springtest.service.Reflection.Son");
//todo 一个jvm只有一个class实例,我们对上面的aclass ,bclass, 和cClass进行equal对比,其地址都是一样的
System.out.println(aClass.equals(bClass) ? bClass.equals(cClass) : "not equal"); //true
//获得类完整的名字
System.out.println(cClass.getName());
}
//获取类中所有的方法。
public static void getMethodAll() throws Exception {
Class clazz = Class.forName("com.beijing.king.springtest.service.Reflection.Son");
//获取的是该类中所有的公有方法,静态的,包含继承和实现的方法,不包含私有的。
Method[] methodsAll = clazz.getMethods();
for (Method method : methodsAll) {
System.out.println("所有的方法,包含继承和实现的方法:" + method.getName());
}
//获取的是该类中的所有方法,包含私有方法,但不包含继承的方法。
Method[] methodsNotContainExtent = clazz.getDeclaredMethods();
methodsNotContainExtent = clazz.getDeclaredMethods();
for (Method method : methodsNotContainExtent) {
System.out.println("所有的方法,不包含继承的方法:" + method.getName());
}
}
//获取指定方法;
public static void getMethodAssign() throws Exception {
//1、加载类;
Class clazz = Class.forName("com.beijing.king.springtest.service.Reflection.Son");
//2、获取指定名称的方法。
Method method = clazz.getMethod("goToSchool", String.class);
//想要运行指定方法,当然是方法对象最清楚,为了让方法运行,调用方法对象的invoke方法即可,但是方法运行必须要明确所属的对象和具体的实际参数。
//3、 创建实例
Son obj = (Son) clazz.newInstance();
//4、调用
method.invoke(obj, "#############king");//执行一个方法
}
//想要运行私有方法。
public static void getMethodPrivate() throws Exception {
//1、获取字节码
Class clazz = Class.forName("com.beijing.king.springtest.service.Reflection.Son");
//todo 想要获取私有方法。必须用getDeclearMethod();
//2、获取指定方法
Method method = clazz.getDeclaredMethod("play", String.class);
// 私有方法不能直接访问,因为权限不够。非要访问,可以通过暴力的方式。
method.setAccessible(true);//一般很少用,因为私有就是隐藏起来,所以尽量不要访问。
//3、创建实例,调用默认的构造函数
//Object obj = clazz.newInstance();
//TODO 3、调用带参数的构造函数
Constructor constructor = clazz.getDeclaredConstructor(String.class,int.class, String.class);
Object obj = constructor.newInstance("kongyin",30,"HUST");
//4、调用方法
method.invoke(obj, "带参数的构造函数 #############king");//执行私有方法
}
//反射静态方法。
public static void getMethodStatic() throws Exception {
//1、获取字节码
Class clazz = Class.forName("com.beijing.king.springtest.service.Reflection.Son");
//2、得到方法
Method method = clazz.getMethod("staticMethod", null);
//3、获取实例
Object obj = clazz.newInstance();
//4、执行方法
method.invoke(obj);//执行静态方法
}
//todo 获取字段属性
public static void getField() throws Exception {
Class c2 = Class.forName("com.beijing.king.springtest.service.Reflection.Son");
//获得指定的属性
Field f1 = c2.getField("course");
System.out.println("指定的son public属性:" + f1);
//获得指定的私有属性
Field f2 = c2.getDeclaredField("school");
//启用和禁用访问安全检查的开关,值为 true,则表示反射的对象在使用时应该取消 java 语言的访问检查;反之不取消
f2.setAccessible(true);
System.out.println("指定的私有属性school: " + f2);
//获得类的public类型的属性。
Field[] fields = c2.getFields();
for (Field field : fields) {
System.out.println("public 的属性字段:" + field.getName());//course
}
//获得类的所有属性。包括私有的
Field[] allFields = c2.getDeclaredFields();
for (Field field : allFields) {
System.out.println("包括私有的字段:" + field.getName());//course school
}
}
//todo 获取构造方法
public static void getConstructor() throws Exception {
Class c2 = Class.forName("com.beijing.king.springtest.service.Reflection.Son");
Constructor[] constructors = c2.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("包含共有的构造函数:" + constructor.toString());//public com.ys.reflex.Person()
}
//获取私有的构造方法
Constructor[] constructorAlls = c2.getDeclaredConstructors();
for (Constructor constructor : constructorAlls) {
System.out.println("包含私有的构造函数:" + constructor.toString());//public com.ys.reflex.Person()
}
}
水滴石穿,积少成多。学习笔记,内容简单,用于复习,梳理巩固。