初识反射

前言

Java的反射机制是Java特性之一,反射机制是构建框架技术的基础所在.所以复习一下反射的知识,这篇中主要介绍什么是反射,Class类,简单工厂模式,以及反射对一个类的一些基本操作

什么是反射?

反射指的是对象的反向处理操做,和反向处理操作相对应的就是正向处理操作,也就是我们平时对一个类的对象进行的操作。
如果想要使用一个类那么必须知道这个类在哪个包并且导入,然后才能使用这个类进行类对象的实例化

1
2
3
4
5
6
7
8
9
import java.util.Date; //1.导入类所在的包

public class Test {

public static void main(String[] args) {
Date date = new Date(); //2.实例化类对象
System.out.println(date);
}
}

这就是使用一个类的正向操作.那什么是反向操作呢
反向操作就是我们先知道某一个类的对象.但是不知道这个对象是属于哪一个类,他有什么属性,成员,然后我们通过某种方法来得到他的类型,获取他的属性,这个方法就做就反射.简单点来说就是通过对象来取得对象的来源信息,他的核心是Object类提供的getClass方法
举一个简单的反射例子:

1
2
3
4
5
6
7
8
9
import java.util.Date;

public class Test {

public static void main(String[] args) {
Date date = new Date();
System.out.println(date.getClass());
}
}

通过date对象我们能获取到他的来源是Date
在反射中看重的不再是一个对象本身,而是他身后的组成(类,构造,属性,方法)

Class类

Class类用于Java反射机制,一切Java类都有一个对应的Class对象,他是一个final类.Class类的实例表示正在运行的Java应用程序中的类和接口
也就是说在我们编写一个类,完成编译之后,在生成的.class文件中,就会产生一个Class对象,专门用于表示这个类的类型信息(类,属性…),
Class类是描述整个类的概念,也是整个反射的操作源头,我们想要通过对象反向的拿到对象的类信息就要操作这个对象相对应的Class类,但Class类毕竟一时一个类,所以在操作这个类的时候关注点还是在这个类的实例化对象上

Class类的三种对象实例化的方法

Class类是不允许用户在外部显式的进行对象的实例化的,因为他的构造器是一个私有的

1
2
3
4
5
6
7
8
/*
* Private constructor.
* Only the Java Virtual Machine
* creates Class objects.
*/
private Class(ClassLoader loader) {
classLoader = loader;
}

下面有三种方法可以用来取得Class类的对象

1.类型.class
不论是在我们自己写的类中还是JDK提供的类中,都会有一个默认的隐含的静态成员class,这个静态成员表示的就是该类对应的class对象
使用方法:

1
2
3
4
5
6
7
8
9
10
public class Test {

public static void main(String[] args) {
//获取Student类的Class对象
Class c = Student.class;
System.out.println(c);
}
}

class Student{}

2.getClass()方法
在Object类中有一个getClass方法,任何类的实例化对象都可以通过这个方法来取得他的Class对象

1
2
3
4
5
6
7
8
9
10
public class Test {

public static void main(String[] args) {
Student student = new Student();
Class c = student.getClass();
System.out.println(c);
}
}

class Student{}

3.Class提供的forName()方法

1
2
//括号中的参数为类名   
public static Class<?> forName(String className)

(他还有其他两种重载方式)

1
2
3
4
5
6
7
8
9
public class Test {

public static void main(String[] args) throws ClassNotFoundException {
Class c = Class.forName("io.github.seevae.reflect1.Student");
System.out.println(c);
}
}

class Student{}

以上三种方式都可以拿到类的Class对象,运行结果都相同

1
class io.github.seevae.reflect1.Student

  • 1.以上三个方法中除了第二个方式中实例化了类的对象从而拿到他的Class对象,其他两种方法都是不需要具体接触到类的实例化的对象的.
  • 2.使用反射机制创建目标类对象,使用方法,Class类中的newInstance方法,这也将是第二种实例化对象的方式(第一种是通过关键字new来实例化对象)

首先准备一个简单的Student类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Student{

private String name;
private int id;


public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
}

通过反射机制实例化Student对象

1
2
3
4
5
6
7
8
9
//通过反射实例化对象
public class Test {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//通过反射实例化对象
Class c = Class.forName("io.github.seevae.reflect1.Student");
Student student =(Student) c.newInstance();
System.out.println(student.toString());
}
}

和使用new关键字的效果相同,反射也可以获得Student类的实例化对象,并且能够调用Student中的方法
所以取得一个类的Class对象便意味着我们取得了对这个类的操作权

运用反射机制的简单工厂模式

先来回顾一下简单工厂
产品接口定义不同产品的相同共性
工厂内部按照不同的需求生产不同的商品,返回实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class SimpleFactoryTest {

public static void main(String[] args) {
IFruit fruit = Factory.getFruit("apple");
fruit.eat();
}
}

interface IFruit{
void eat();
}

class Apple implements IFruit{

public void eat() {
System.out.println("吃苹果");
}
}

class Factory{
private Factory(){ }

public static IFruit getFruit(String fruitName){
if("apple".equals(fruitName)){
return new Apple();
}
return null;
}
}

简单工厂的缺陷在于如果要再加入产品就需要修改工厂
此时加入新的产品类梨

1
2
3
4
5
6
class Pear implements IFruit{

public void eat() {
System.out.println("吃梨");
}
}

于是就需要修改工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Factory{
private Factory(){ }

public static IFruit getFruit(String fruitName){
if("apple".equals(fruitName)){
return new Apple();
}

if("pear".equals(fruitName)){
return new Pear();
}

return null;
}
}

造成这种问题的原因就在于关键字new.因为工厂中必须通过new来获取实例对象,而一个new关键字只能实例化一个类的对象,解决这种问题就可以通过反射,因为反射通过传入的类的全路径来实例化对象,代码改造如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class SimpleFactoryTest {

public static void main(String[] args) {
String fruitClassPath = "io.github.seevae.simplefactory.Apple";
IFruit resultFruit = Factory2.getFruitInstance(fruitClassPath);
resultFruit.eat();
}
}

interface IFruit{
void eat();
}

class Apple implements IFruit{

public void eat() {
System.out.println("吃苹果");
}
}

class Pear implements IFruit{
public void eat() {
System.out.println("吃梨");
}
}

class Factory2{
private Factory2(){ }

public static IFruit getFruitInstance(String fruitClassPath){
IFruit fruit = null;
try {
//获取目标类的class对象
Class c = Class.forName(fruitClassPath);
//通过class对象实例化具体类对象
fruit = (IFruit) c.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return fruit;
}
}

这就是最终版的简单工厂模式,现在如果再加入新的产品类型,在工厂中制造过程中只需要传入不同的产品类路径就行了

反射与类操作

总结了Class类中的一些常用方法

取得父类信息

1.取得当前Class对象的父类名称:getSuperClass()
定义如下类关系

1
2
public class Person { }
class Student extends Person{ }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestDemo {

public static void main(String[] args) {
try {
Class c = Class.forName("io.github.seevae.basicop.Student");
//获取该类所继承的父类
System.out.println(c.getSuperclass());
} catch (Exception e) {
e.printStackTrace();
}
}
}

运行结果:class io.github.seevae.basicop.Person

2.取得当前Class对象所在的包名称:getPackage

1
2
3
//获取该类所在的包的名称
System.out.println(c.getPackage());
运行结果: package io.github.seevae.basicop

3.获取当前类实现的所有的接口名称:getInterfaces,此方法返回的是一个Class类的集合,因为接口的实现可以是多个
类关系的定义

1
2
3
4
5
6
7
interface Ball{

}

interface Price{}

class FootBall implements Ball,Price{}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TestDemo {

public static void main(String[] args) {
try {
Class c = Class.forName("io.github.seevae.basicop.Student");
//取得该类实现的所有父接口的信息
Class c2 = Class.forName("io.github.seevae.basicop.FootBall");
Class<?>[] cc = c2.getInterfaces();
for(Class<?> i:cc){
System.out.println(i);
}

} catch (Exception e) {
e.printStackTrace();
}
}
}

运行结果: interface io.github.seevae.basicop.Ball
interface io.github.seevae.basicop.Price

反射调用构造

定义一个具有三种不同重载方式的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Child{

private String name;
private int age;

public Child(){

}

public Child(String name){
this.name=name;
}

public Child(String name,int age){
this.name = name;
this.age=age;
}
}

此处有两个方法都能够返回指定类的构造方法
1.getConstructors(),此方法将返回该Class类对应类的所有构造方法

1
2
3
4
5
6
7
8
9
10
11
//获取三种不同的构造方法
public class TestDemo2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class c = Child.class;
//返回的是一个数组
Constructor<?>[] constructors = c.getConstructors();
for(Constructor<?> i:constructors){
System.out.println(i);
}
}
}

运行结果:

1
2
3
public io.github.seevae.basicop.Child()
public io.github.seevae.basicop.Child(java.lang.String)
public io.github.seevae.basicop.Child(java.lang.String,int)

2.getConstructor(Class<?>… 构造方法中参数类型),此方法会根据参数返回指定的构造方法

1
2
Class c = Child.class;
System.out.println(c.getConstructor(String.class));

此时结果:

1
public io.github.seevae.basicop.Child(java.lang.String)

注意:在定义Java简单类的时候要有一个默认的无参的构造方法以便能更加方便的调用反射机制
为什么一定要有一个无参的构造方法呢?
因为在调用Class类中的newInstance方法时默认调用的是类的无参的构造方法,如果此时目标类没有一个无参的构造方法是会出错的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class TestDemo2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException {
Class c = Child.class;
Child child = (Child)c.newInstance();
}
}

class Child{

private String name;
private int age;

// public Child(){
//
// }

public Child(String name){
this.name=name;
}

public Child(String name,int age){
this.name = name;
this.age=age;
}
}

此时报错:

1
java.lang.InstantiationException

这种情况下就只能先通过getConstructor获取到指定的构造器后在实例化对象

1
2
3
4
5
6
7
8
public class TestDemo2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
Class c = Child.class;
Constructor constructor = c.getConstructor(String.class,int.class);
Child child1 = (Child)constructor.newInstance("Alice",22);
System.out.println(child1.toString());
}
}

这样就能获取到目标对象了
但是这样是很不方便的,所以在以后的简单Java对象中都应该带上一个默认的构造方法

反射调用普通方法(核心)

类中肯定会定义各种的普通方法,那么如何调用普通方法呢?
Class类提供了两种调用普通方法的方法
1.取得全部普通方法

1
public Method[] getMethods() throws SecurityException

2.取得指定的普通方法

1
public Method getMethod(String name, Class<?>... parameterTypes)

以上两个类的返回类型都是java.lang.reflect.Method类的对象,在此类中提供有一个调用该对象中的具体方法的支持方法:

1
2
public Object invoke(Object obj, Object... args)throws IllegalAccessException,
IllegalArgumentException,InvocationTargetException

这些方法具体都怎么用将在下面说明
首先定义一个普通类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Teacher{
private String name;
private int id;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
}

取得Teacher类中的全部普通方法

1
2
3
4
5
6
7
8
9
public class TestDome3 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("io.github.seevae.basicop.Teacher");
Method[] methods = c.getMethods();
for(Method i:methods){
System.out.println(i);
}
}
}

运行后会发现他不光把我们自己定义的方法拿到了,还把默认继承Object的所有方法也拿到了
拿到方法之后怎么调用这些方法?这时就要使用Method类中的invoke()方法
在不使用反射机制时如果想要调用某个类的方法,必须明确指定这个类的实例化对象,现在有了反射机制,可以不用明确指定某个类的实例化对象,而是使用Object类的对象来代替这个实例化对象
3.反射机制调用普通方法
还是使用上面那个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestDome3 {
public static void main(String[] args) throws Exception {
//1.拿到指定类的Class对象
Class c = Class.forName("io.github.seevae.basicop.Teacher");
//2.获取指定类的实例化对象(Object接收就好)
Object obj = c.newInstance();
//3.通过class对象拿到目标方法(第一个参数是方法名称,第二个参数是方法中参数类型.class)
Method setMethod = c.getMethod("setName", String.class);
//4.调用目标方法,如果调用的目标方法有返回值则使用Object接收
Object ong2 = setMethod.invoke(obj,"大宝");
Method getMethod = c.getMethod("getName");
Object result = getMethod.invoke(obj);
System.out.println(result);
}
}

反射调用类中属性

属性的获取

上面讲解了调用类中的构造方法,普通方法.除此之外还要说说调用类中属性.调用类中属性有一个前提:必须先有类的实例化对象,因为一个类中的属性只有在实例化对象之后才会分配空间,通过反射的newInstance()可以直接取得实例化对象(Object类型)
获得属性的方法总共有四个分为两类
第一类:针对父类属性的获取,其中有两个方法
1.getFields(); 获取父类中的所有public修饰的属性
2.getField(String name); 获取父类中指定名称的属性,也必须是public修饰
第二类:针对当前类属性的获取,其中也有两个方法
1.getDeclarations(); 获取当前类中的所有属性,所有权限修饰符的属性都可以
2.getDeclaration(String name); 获取当前类中指定名称的属性
代码演示:

1
2
3
4
5
6
7
8
9
10
//定义类关系  
class Animal{
private String name;
private String age;
public String tail;
}

class Dog extends Animal{
private String id;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//属性的获取
public class TestDemo4 {

public static void main(String[] args) throws Exception {
Class c =Class.forName("io.github.seevae.basicop.Dog");
Object obj = c.newInstance();

/**
*
* 注意: 获取父类中的属性只能获取由public修饰的
* 其他权限修饰的是拿不到的
*/

//获取父类中的所有属性 getFields
Field[] fields = c.getFields();
for(Field i:fields){
System.out.println(i);
}

//指定获取父类中的某个属性 getField(String 属性名称)
Field f = c.getField("tail");
System.out.println(f);


//获取当前类中的所有属性
Field[] fields1 = c.getDeclaredFields();
for(Field field:fields1){
System.out.println(field);
}

//获取当前类中指定的属性
Field ff = c.getDeclaredField("id");
System.out.println(ff);

}
}

获取属性时的注意点:通过反射只能获取到父类中被public修饰的属性,其他更小权限的属性是获取不到的;获取当前类中的属性没有权限修饰符的限制,只要是声明的属性都可以获取到

对属性进行操作

获取到类中的属性之后就是对他们的操作了(set,get).这里只说明一下获取当前类属性后对其的操作,不介绍对父类属性的操作
使用反射获取到的属性都是Field类,在Field类中有两个重要方法

1
2
3
4
1. 设置属性内容 : public void set(Object obj, Object value) throws IllegalArgumentException,
IllegalAccessException
2. 取得属性内容 : public Object get(Object obj) throws IllegalArgumentException,
IllegalAccessException

这两个方法便是用来对属性进行设置和获取的,下面是具体使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestDemo4 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("io.github.seevae.basicop.Dog");
Object obj = c.newInstance();
//获取属性
Field field = c.getDeclaredField("id");
//设置对象的访问权限,保证对private的属性的访问
field.setAccessible(true);
//设置属性值相当于setXxx
field.set(obj,"021");
//获取属性值并打印相当于getXxx
System.out.println(field.get(obj));
}
}

这里要注意点就是Field对象的属性默认访问权限是不包括private的,所以要使用setAccessible来设置访问权限,保证对private的属性的访问,如果不设置,默认属性是一个封装状态是不能对他进行修改操作的

获取属性类型

如果我们不知道一个属性是什么类型的还可以通过Field类中的getType()方法来获取

1
2
3
4
5
6
7
8
Class c = Class.forName("io.github.seevae.basicop.Dog");
Object obj = c.newInstance();
//获取属性
Field field = c.getDeclaredField("id");
//获取属性全名称:包.类名
System.out.println(field.getType().getName());
//获取属性名称:类名
System.out.println(field.getType().getSimpleName());