一、反射:
1、什么是java反射机制;
Java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取信息及动态调用对象方法的机制就是反射。
2、反射的作用
反射机制允许在运行时取得任何一个已知名称的类的内部信息,包括属性、方法等。运用反射可以使编写的代码更加灵活,同时还可以降低代码的耦合度。有利就有弊,反射可以获取任意一个类的属性和方法,破坏了类的封装性。
3、Class类对象的三种产生方式:
- 任何类的实例化对象,都可通过Object类的 getClass() 方法取得Class对象:
public final native Class<?> getClass();
- “类 . class“:直接根据某个具体的类来取得 Class 类的实例化对象。
- 使用Class类提供的 forName() 静态方法:
public static Class<?> forName(String className) throws ClassNotFoundException;
来看具体的例子:
import java.util.Date;
public class Test {
public static void main(String[] args) {
Date date = new Date() ;
//1、getClass()获取对象的类信息:如传过来的是Object对象不知道具体类型就可通过这种方法获取
Object obj1 = date.getClass();
//2、类.class:这种方法获取对象方便可靠
Object obj2 = Date.class;
//3、Class.forName():知道一个类的全限定名,就可获得该类对象,可能抛出ClassNotFoundException异常
try {
Object obj3 = Class.forName("java.util.Date");
//三者的关系
System.out.println(obj1.equals(obj2) && obj2.equals(obj3));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
那么上面三种方式产生的对象之间有什么关系呢?程序运行结果如下:
结果是true,说明上面obj1、obj2、obj3都是同一个对象。因此需要注意:在JVM中一个类只有一个Class实例。
二、反射与类操作
(1)取得父类信息
java中的任何类都一定有父类,通过以下方法可以得到父类的信息:
- 取得类的包名称: public Package getPackage()
- 取得父类的Class对象: public native Class<? super T> getSuperclass();
class Message{...}
class MessageImpl extends Message{...}
public class Demo{
public static void main(String[] args) {
//取得Class对象
Class<?> cls = MessageImpl.class;
System.out.println(cls.getName());
//取得包名
System.out.println(cls.getPackage().getName());
//取得父类对象
Class superclass = cls.getSuperclass();
System.out.println(superclass.getName());
}
}
(2)反射调用构造
- 取得指定参数类型的构造:
public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException;
- 取得类中的所有构造:
public Constructor<?>[] getConstructors() throws SecurityException;
这两个方法返回的都是java.lang.reflect.Constructor类的实例化对象,在Constructor类中重点关注newInstance()这个方法:
public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException;
举个例子:
package com.mytest;
import java.lang.reflect.Constructor;
class Person {
public Person() {}
public Person(String name) {}
public Person(String name,int age) {}
}
public class Demo {
public static void main(String[] args) {
Class<?> cls = Person.class ;
// 取得类中的全部构造
Constructor<?>[] constructors = cls.getConstructors() ;
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
}
}
运行后成功拿到了Person类的所有构造方法:
上面信息打印调用了Constructor类中的toString()方法,因此取得了构造方法的完整信息(包含权限方法权限和参数列表),如果使用getName()方法,只能拿到构造方法的包名.类名。
另外需要注意:在定义简单java类的时候一定要有无参构造,Class类通过反射实例化对象的时候,只能调用无参构造,如果没有无参构造会出现InstantiationException(实例化异常)。
出现这个问题的解决的办法有两种:
- 一种就是加一个无参构造
- 另一种方法就是明确调用指定参数的构造方法
class Person {
private String name ;
private int age ;
//方法一:在这里添加无参构造
public Person(String name,int age) {
this.name = name ;
this.age = age ;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
public class Demo {
public static void main(String[] args) throws InstantiationException,
IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class<?> cls = Person.class ;
//方法二:调用具体的构造方法
Constructor<?> constructor = cls.getConstructor(String.class,int.class);
System.out.println(constructor.newInstance("Tony",20));
}
}
(3)反射调用普通方法
- 取得全部普通方法
public Method[] getMethods() throws SecurityException;
- 取得指定普通方法
public Method getMethod(String name, Class<?>... parameterTypes);
这两个方法返回类型都是 java.lang.reflect.Method 类的对象,在Method类中有一个支持调用的方法invoke()
public Object invoke(Object obj, Object... args)throws IllegalAccessException,IllegalArgumentException,InvocationTargetException;
还是以上面的例子来说明:
public class Demo {
public static void main(String[] args) throws InstantiationException,
IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class<?> cls = Person.class ;
//调用类中的普通方法必须有实例化对象
Object obj = cls.newInstance();
//取得setName()方法的实例化对象
Method setNameMethod = cls.getMethod("setName",String.class);
Method setAgeMethod = cls.getMethod("setAge",int.class);
//Method类对象调用指定方法
setNameMethod.invoke(obj,"Jack");
setAgeMethod.invoke(obj,20);
Method getNameMethod = cls.getMethod("getName");
Method getAgeMethod = cls.getMethod("getAge");
//获取结果
Object nameResult = getNameMethod.invoke(obj);
Object ageResult = getAgeMethod.invoke(obj);
System.out.println(nameResult + " " + ageResult);
}
}
上面这种调用方式,可以通过Object类型实现所有类的方法调用,而不局限于某一具体类型对象。
(4)反射调用类中的属性
类中的所有属性是在类对象实例化之后才会分配空间,因此想要取得类中的属性,就必须有类的实例化对象。通过反射的newInstance()可以直接取得实例化对象(Object类型)。
- 取得本类中的全部属性:
public Field[] getDeclaredFields() throws SecurityException;
- 取得本类中的指定属性:
public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException;
上面两个方法返回的是java.lang.reflect.Field类型,在Filed类中有两个重要的方法set()和get():
//1、设置属性内容 :
public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException;
//2、取得属性内容 :
public Object get(Object obj) throws IllegalArgumentException,IllegalAccessException;
如果要修改类中的私有属性,则要使用到一个AccessibleObject类的setAccessible()方法,来动态设置是否封装:
public void setAccessible(boolean flag) throws SecurityException;
来看例子:
public class Demo {
public static void main(String[] args) throws InstantiationException,
IllegalAccessException, NoSuchFieldException, ClassNotFoundException {
Class<?> cls = Class.forName("com.mytest.Person") ;
//调用类中的普通方法必须有实例化对象
Object obj = cls.newInstance();
Field nameField = cls.getDeclaredField("name");
Field ageField = cls.getDeclaredField("age");
//取消封装:取消后可以修改私有属性
nameField.setAccessible(true);
ageField.setAccessible(true);
//设置属性值
nameField.set(obj,"Marry");
ageField.set(obj,18);
//取出
System.out.println(nameField.get(obj) + " " + ageField.get(obj));
}
}