JavaSE高级开发之反射(一)
反射指的是对象的反向处理操作。 我们熟知的 “正” 的操作必须要先导入一个包,然后创建于一个类,根据构造方法才能产生类的实例化对象。 而反射是根据对象来取得对象的来源信息。
-
我们先来看看正常的关于对象的处理流程:根据
包名.类名
找到某个类。
package www.tech.java.com;
import java.util.Date;
public class TestReflect {
public static void main(String[] args) {
Date date = new Date() ;
}
}
既然所谓的 “反” 指的是根据对象来取得对象的来源信息,而这个 “反” 的操作核心的处理就在于Object类的一个方法:取得Class
对象。
我们首先来了解一下Class类:
每个类运行时的类型信息就是用Class对象来表示的,它包含了与类有关的信息,其实我们的实例化对象就是通过Class对象来创建的,每一个类都有一个Class对象,每当编译一个新类就会产生一个Class对象。基本类型、数组都有Class对象,就连关键字void也有Class对象(void.class)。Class对象对应着java.lang.Class
类,如果说类是对象抽象和集合的话,那么Class类就是对类的抽象和集合。
1. 认识反射机制
1.1 初识反射
取得Class对象:public final native Class<?> getClass();
,该方法返回的是一个Class类对象,这个Class描述的就是类。
package www.tech.java.com;
import java.util.Date;
public class TestReflect {
public static void main(String[] args) {
//通过类名,调用构造方法创建对象
Date date = new Date();
//通过date对象获取到创建date对象的类的对象
Class classz = date.getClass();
System.out.println(classz);
}
}
java.util.Date -> Class对象,描述Date这个类
java.lang.String -> Class对象,描述String这个类
- 此时就通过对象取得了对象的来源,这就是 “反” 的本质。在反射的世界里面,看重的不再是一个对象,而是对象身后的组成(类、构造、普通、成员等)。
1.2 三种实例化Class对象方式
Class类是描述整个类的概念,也是整个反射的操作源头,在使用Class类的时候需要关注的仍然是这个类的对象。
-
这个Class类的对象的产生方式一共有以下三种方式:
① 通过对象.getclass()
方法获取,比如:obj.getclass()
② 通过类名.class
方法
③ 通过Class.forName(类的全限定名)
获取Class对象
package www.tech.java.com;
import java.util.Date;
public class TestReflect {
public static void main(String[] args) {
Date date = new Date();
//第一种方法
Class class1 = date.getClass();
System.out.println(class1);
//第二种方法
Class class2 = Date.class;
System.out.println(class1 == class2);
//第三种方法
try {
Class class3 = Class.forName("java.util.Date");
System.out.println(class1 == class3);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
- 结果是true也就说明每个类的Class对象只有一个!!
在以上给出的三个方法中我们可以发现,除了第一种方法会产生Date类的实例化对象之外,其他的两种都不会产生Date类的实例化对象。这样看来取得Class类对象有一个最直接的好处:通过Class对象的newInstance
方法可以获取实例化对象,但是要求类中必须要有默认的无参构造。
1.3 通过反射实例化对象
package www.tech.java.com;
import java.util.Date;
public class TestReflect {
public static void main(String[] args) {
Class classz = Date.class;
//Class -> new -> object
//Class对象创建实例化对象
try {
//实例化对象,等价于new java.util.Date();
Object obj = classz.newInstance();
//ClassCastException
if (obj instanceof Date) {
Date date = (Date) obj;
System.out.println(date);
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
- 现在发现除了关键字
new
之外,对于对象的实例化模式有了第二种做法,那就是通过Class对象反射获取。
1.4 反射VS工厂模式
工厂设计模式:如果是自己编写的接口,要想取得本接口的实例化对象,最好使用工厂类来设计。但是传统工厂类在实际开发之中根本用不到,因为每增加一个接口的子类就需要修改工厂类,违背了 “开闭原则” 。
-
要想解决关键字new带来的问题,最好的做法就是通过反射来完成处理,因为Class类可以使用
newInstance()
实例化对象,同时Class.forName()
能够接收类名称。
package www.tech.java.com;
interface Fruit {
void eat();
}
class Apple implements Fruit {
public void eat() {
System.out.println("吃苹果");
}
}
class Orange implements Fruit {
public void eat() {
System.out.println("吃橘子");
}
}
class FruitFactory {
public static Fruit getFruitInstance(String fruitName) {
try {
Class classz = Class.forName(fruitName);
return (Fruit) classz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return null;
}
}
public class TestFactory {
public static void main(String[] args) {
Fruit fruit = FruitFactory.getFruitInstance("www.tech.java.com.Orange");
if (fruit != null) {
fruit.eat();
}
}
}
- 引入反射后,每当新增接口子类,无需去修改工厂类代码就可以很方便的进行接口子类扩容。以上这种工厂类代码我们称之为简单工厂模式。
2. 反射与类操作
2.1 取得父类信息
在java中任何的程序类都一定会有父类,在Class类中就可以通过如下方法来取得父类或者实现的父接口:
-
取得父类信息:
① 取得类的包名称:getPackage()
② 取得父类的Class对象:getSuperclass()
③ 取得实现的父接口:getInterfaces()
package www.tech.java.com;
interface IMessage {}
interface SystemEdition {}
class Father {}
class Son extends Father implements IMessage, SystemEdition {}
public class TestBaseInfo {
public static void main(String[] args) {
Class sonClass = Son.class;
//获取包
Package packages = sonClass.getPackage();
//包的名称
System.out.println(packages.getName());
System.out.println("---------------");
//获取父类
Class superClass = sonClass.getSuperclass();
System.out.println(superClass.getName());//类全限定名
System.out.println(superClass.getSimpleName());//类名
System.out.println("-----------------");
//获取接口
Class[] interfaces = sonClass.getInterfaces();
for (Class i : interfaces) {
System.out.println(i.getName());
}
}
}
5.2 反射调用构造方法
① 取得类中的所有构造:getConstructors()
② 取得指定参数类型的构造:getConstructor(Class<?>... parameterTypes)
package www.tech.java.com;
import java.lang.reflect.Constructor;
import java.util.Arrays;
class Person {
private String name;
private Integer age;
public Person() {
}
public Person(String name) {
this.name = name;
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class TestBaseInfo {
public static void main(String[] args) {
Class personClass = Person.class;
//取得所有构造方法
Constructor[] constructors = personClass.getConstructors();
for (Constructor c : constructors) {
System.out.println(c.getName() + "(" +
Arrays.toString(c.getParameterTypes())
+ ")"
);
}
System.out.println("------------------");
try {
//取得指定的一个参数的构造方法
Constructor c = personClass.getConstructor(String.class);
System.out.println(c.getName() + "(" +
Arrays.toString(c.getParameterTypes())
+ ")"
);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
- 观察Class实例化对象的问题:
- 类中没有无参构造的情况下:
package www.tech.java.com;
class Person {
private String name;
private Integer age;
/*
public Person() {
}
public Person(String name) {
this.name = name;
}
*/
//没有无参构造
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class TestBaseInfo {
public static void main(String[] args) {
Class personClass = Person.class;
try {
System.out.println(personClass.newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
- 类中有无参构造的情况下:
package www.tech.java.com;
class Person {
private String name;
private Integer age;
//有无参构造
public Person() {
}
public Person(String name) {
this.name = name;
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class TestBaseInfo {
public static void main(String[] args) {
Class personClass = Person.class;
try {
System.out.println(personClass.newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
- 结论:Class类通过反射实例化类对象的时候,只能够调用类中的无参构造。如果现在类中没有无参构造则无法使用Class类调用,只能够通过明确的构造调用实例化处理。
- 通过Constructor实例化对象:
package www.tech.java.com;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
class Person {
private String name;
private Integer age;
public Person() {
}
public Person(String name) {
this.name = name;
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class TestBaseInfo {
public static void main(String[] args) {
Class personClass = Person.class;
try {
//调用有参数的构造方法
Constructor c = personClass.getConstructor(String.class);
Object object = c.newInstance("张三");
System.out.println(object);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
5.3 反射调用普通方法
① 取得全部普通方法:getMethods()
② 取得指定普通方法:getMethod(String name, Class<?>... parameterTypes)
package www.tech.java.com;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
class Person {
private String name;
private Integer age;
public Person() {
}
public Person(String name) {
this.name = name;
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class TestBaseInfo {
public static void main(String[] args) {
Person person = new Person("Jack", 20);
Class personClass = Person.class;
//取得所有普通方法
Method[] methods = personClass.getMethods();
System.out.println("Person类中的所有方法");
for (Method m : methods) {
System.out.println(m.getName() + "(" +
Arrays.toString(m.getParameterTypes())
+ ")");
}
System.out.println("----------");
//取得指定的setName方法
Method setNameMethod = null;
try {
setNameMethod = personClass.getMethod("setName", String.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
System.out.println("Person类中的setName方法:");
System.out.println(setNameMethod.getName() + "(" +
Arrays.toString(setNameMethod.getParameterTypes()));
}
}
5.4 反射调用类中属性
在之前已经成功的实现了类的构造调用、普通方法调用,除了这两种模式之外还有类中属性的调用。
前提:类中的所有属性一定在类对象实例化之后才会进行空间分配,所以此时如果要想调用类的属性,必须保证有实例化对象。通过反射的newInstance()
可以直接取得实例化对象(Object类型)。
-
在Class类中提供有四组取得属性的方法:
① 取得父类中公开属性:getFields()
② 取得父类中公开指定名称属性:getField(String name)
③ 取得本类中全部属性:getDeclaredFields()
④ 取得本中指定名称属性:getDeclaredMethod(String name, Class<?>... parameterTypes)
package www.tech.java.com;
import java.lang.reflect.Field;
class Person {
private String name;
private Integer age;
public Person() {
}
public Person(String name) {
this.name = name;
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class Student extends Person {
private String school;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"school='" + school + '\'' +
"} " + super.toString();
}
}
public class TestBaseInfo {
public static void main(String[] args) {
//先要实例化对象
Student student = new Student();
student.setSchool("清华");
student.setName("Jack");
student.setAge(22);
Class studentClass = student.getClass();
//获取父类中的所有属性
Field[] fields = studentClass.getFields();
for(Field f : fields) {
System.out.println(f.getName());
}
//获取父类中的age属性
try {
Field ageField = studentClass.getField("age");
System.out.println(ageField.getName());
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
System.out.println("----------");
//获取本类中的所有属性
Field[] fields1 = studentClass.getDeclaredFields();
for(Field f : fields1) {
System.out.println(f.getName());
}
//获取本类中的school属性
try {
Field schoolField = studentClass.getDeclaredField("school");
System.out.println(schoolField.getName());
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
-
属性的核心描述类
java.lang.reflect.Field
,在这个类之中有两个重要方法:
① 设置属性内容:set(Object obj, Object value)
② 取得属性内容:get(Object obj)
③ 通过反射获取到Constructor/Method/Filed私有访问:setAccessible(true)
package www.tech.java.com;
import java.lang.reflect.Field;
class Emp {
private String name;
}
public class BeanOperation {
public static void main(String[] args) {
Class classz = Emp.class;
Object object = null;
try {
// 实例化本类对象
object = classz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
Field nameField = null;
try {
// 获取name属性
nameField = classz.getDeclaredField("name");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
// 取消封装
nameField.setAccessible(true);
try {
//设置属性值,相当于object.name = "张三";
nameField.set(object, "张三");
} catch (IllegalAccessException e) {
e.printStackTrace();
}
try {
// 取得属性
System.out.println(nameField.get(object));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
3. 反射与单级VO操作
假设现在有一个简单Java类,按照原始的做法就是使用getter/setter
方法对属性值进行操作,那么加入这个类中存在有几十个属性,要是还按照原始做法,就需要调用几十次getter/setter方法,这样操作就太麻烦了。
-
现在希望能对程序做简化,输入字符串
"属性名称:属性值|属性名称:属性值|属性名称:属性值|...."
,就可以将类中的属性设置好。
package www.tech.java.com;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
class Emp {
private String ename;
private String job;
private String skill;
public String getName() {
return ename;
}
public void setName(String ename) {
this.ename = ename;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
@Override
public String toString() {
return "Emp{" +
"ename='" + ename + '\'' +
", job='" + job + '\'' +
", skill='" + skill + '\'' +
'}';
}
}
public class BeanOperation {
public static void main(String[] args) {
String line = "name:Jack|job:SoftDev|skill:Java";
Emp emp = new Emp();
String[] attributes = line.split("\\|");
for(String kv : attributes) {
setXxx(emp, kv);
}
System.out.println(emp);
}
//object:进行赋值的对象 kv:属性和值
public static void setXxx(Object object, String kv) {
//1.获取指定的Class对象
Class classz = object.getClass();
//2.解析kv
String[] segments = kv.split(":");
String attribute = segments[0];
String attributeValue = segments[1];
//3.setXxx方法名称:set+属性名首字母大写
String methodName = "set" + attribute.substring(0, 1).toUpperCase()
+ ((attribute.length() > 1) ? attribute.substring(1) : "");
try {
//4.通过反射获取setter方法
Method method = classz.getDeclaredMethod(methodName, String.class);
//5.通过method对象调用方法
method.invoke(object, attributeValue);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}