1 面试原题
“String 可不可变?”
若回答不可变则需要根据不可变性(后续文章进行分析)进行回答若回答可变则需要进入本文引入的话题通过反射改变 String 对象的内容
2 代码解释
String str
= new String("Hello the World !");
System
.out
.println("改变前的值:" + str
);
System
.out
.println("改变前的hashCode:" + str
.hashCode());
Field value
= str
.getClass().getDeclaredField("value");
value
.setAccessible(true);
char[] field
= (char[]) value
.get(str
);
field
[6] = ' ';
field
[7] = 'm';
field
[8] = 'y';
System
.out
.println("改变后的值:" + str
);
System
.out
.println("改变后的hashCode:" + str
.hashCode());
运行上面代码,在不改变字符串对象 str 的前提下,修改了其属性 value 的内容。主要的核心就是用到了 Class 中的 public Field getDeclaredField(String name)方法,该方法就是根据属性名获取类本身的属性成员并对其进行修改。
3 反射
就个人理解来说,反射就是在 Java 运行环境中提供的一种机制,即在运行时可以判断对象所属类以及类具有的成员属性和方法、构造任意一个类的对象、和调用对象的方法等。 本文不阐述反射的原理定义,主要以代码应用为主。
3.1 获取 Class
通过 Class.forName(全类名)获取
System
.out
.println("1、获取Class");
Class
<?> c1
= Class
.forName("java.lang.Integer");
通过对象的 getClass()方法获取
Integer i1
= 1;
Class
<?> c2
= i1
.getClass();
通过静态数据 class 获取
Class
<?> c3
= Integer
.class;
3.2 创建实例
通过 Class 的 newInstance()方法创建
Class
<?> class1
= String
.class;
Object s1
= class1
.newInstance();
通过 getConstructor 构造器创建
Class
<?> class2
= String
.class;
Constructor
<?> constructor
= class2
.getConstructor(String
.class);
Object s2
= constructor
.newInstance("Java");
3.3 判断是否为某个类的实例
通过 instanceof 判断
Object obj
= "Java";
System
.out
.println(obj
instanceof String);
通过 Class 对象的 isInstance()判断
Object obj
= "Java";
System
.out
.println(String
.class.isInstance(obj
));
3.4 获取对象的属性
测试类 User
public class User {
private String name
;
public Integer age
;
}
通过 getField()或 getFields()获取公有属性及其父类的公有属性
Field age
= User
.class.getField("age");
Field name
= User
.class.getField("name");
Field
[] fields
= User
.class.getFields();
for (Field field
: fields
) {
System
.out
.println(field
.toString());
}
通过 getDeclaredField()或 获取属性及其父类属性,不区分公有私有
Field userName
= User
.class.getDeclaredField("name");
Field userAge
= User
.class.getDeclaredField("age");
Field
[] declaredFields
= User
.class.getDeclaredFields();
for (Field declaredField
: declaredFields
) {
System
.out
.println(declaredField
);
}
3.5 获取对象属性值
通过 get()方法获取,如果知道属性类型可以使用指定类型进行获取,例如:getInt()等,需要强调的是要在调用时传入的是已经实例化的对象
User user
= new User("zs",
18);
Field userName
= user
.getClass().getDeclaredField("name");
Field userAge
= user
.getClass().getDeclaredField("age");
userName
.setAccessible(true);
System
.out
.println(user
);
System
.out
.println(userName
.get(user
));
System
.out
.println(userAge
.get(user
));
补充:Accessable 属性是继承自 AccessibleObject 类,功能是默认为(false)启用,如需禁用安全检查则设置为(true),此处并发本文重点,读者只需记住如需访问私有属性则需要禁用安全检查即可(后续文章进行分析)
3.6 获取方法
getMethods(),返回该类所有的公共的方法以及继承的类公用方法通过 getDeclaredMethods()获取,返回类或接口声明的所有方法,包括公共、保护、默认访问和私有方法,但不包括继承的方法
Class
<?> clazz
= User
.class;
Method
[] methods
= clazz
.getMethods();
for (Method method
: methods
) {
int modifiers
= method
.getModifiers();
System
.out
.print(Modifier
.toString(modifiers
) + " ");
Class
<?> returnType
= method
.getReturnType();
System
.out
.print(returnType
.getName() + " " + method
.getName() + "( ");
Parameter
[] parameters
= method
.getParameters();
for (Parameter parameter
: parameters
) {
System
.out
.print(parameter
.getType().getName() + " " + parameter
.getName() + ",");
}
Class
<?>[] exceptionTypes
= method
.getExceptionTypes();
if (exceptionTypes
.length
== 0) {
System
.out
.println(" )");
} else {
for (Class
<?> exceptionType
: exceptionTypes
) {
System
.out
.println(") throws " + exceptionType
.getName());
}
}
}
通过 getMethod()获取,返回一个特定的方法,第一个参数为方法名称,后面的参数是方法参数对应的 Class 对象通过 getDeclaredMethod()获取,与 getMethod()区别在于可以获取私有方法
Method setName
= clazz
.getMethod("setName", String
.class);
System
.out
.println(user
);
setName
.invoke(user,
"张三");
System
.out
.println(user
);
3.7 调用方法
ReduceDemo 测试类
public class ReduceDemo {
public int reduce(int x
, int y
) {
return x
- y
;
}
}
invoke()调用执行实例对象的方法,注第一个参数为实例对象,私有方法需要 setAccessible(true)
Class
<ReduceDemo> reduceDemoClass
= ReduceDemo
.class;
ReduceDemo reduceDemo
= reduceDemoClass
.newInstance();
Method reduce
= reduceDemoClass
.getMethod("reduce",
int.class,
int.class);
Object invoke
= reduce
.invoke(reduceDemo,
10,
8);
System
.out
.println("执行方法 " + reduce
.getName());
System
.out
.println(invoke
);
4 小结
反射是 Java 学习中不可避免的一个难题,又由于工作中很少使用到,对于大部分开发来说都很陌生,但这对于 Java 开发工程来说这又是很重要的知识点,尤其是在源码的学习上,如 Spring、Hadoop 等框架源码学习中都可以看到反射的身影。本文主要讲述 Java 反射的基本操作,并未深入细讲化具体使用方向等,如有需要后续将引入源码进行分析反射在源码框架中的使用。 重要的事情说三遍:代码跟着手敲一遍、代码跟着手敲一遍、代码跟着手敲一遍。