一篇文章了解Java反射和应用

2019 Java 开发者跳槽指南.pdf (吐血整理)….>>>

什么是反射

反射就是指程序在运行的时候可以知道一个类的自身信息。

对于任何一个:可以知道这个类的属性和方法。

对于任何一个对象:可以调用这个对象的任何一个方法和属性。

反射就是把java类中的各种成分映射成一个个的Java对象

例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行 解剖,把个个 组成部分映射成一个个对象。

(其实:一个类中这些成员方法、构造方法、在加入类中都有一个类来描述)

反射的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行的时候构造任意一个类的对象
  • 在运行时判断一个类所具有的成员变量和方法
  • 在运行时调用任何一个对象的方法
  • 生成动态代理(会有一篇关于动态代理的文章,在这里挖坑)

反射的优点和缺点

动态编译和静态编译

反射用到的是动态编译,既然有动态编译,就会有静态编译

那么动态编译和静态编译又有什么区别?

静态编译:在编译的时候进确定类型,如果绑定对象成功,new 是静态加载类,就编译通过。

1 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Phone{
  	public static void main(String[] args){
  		if("iphone".equals(args[0])){
  			Iphone iphone = new Iphone();
  			iphone.call();
  		}
  		if("xiaomi".equals(args[0])){
  			Xiaomi xiaomi = new Xiaomi();
  			xiaomi.call();
  		}
  	}
  }
  class Xiaomi{
  	public void call(){
  		System.out.println("xiaomi is calling");
  	}
  }
  class Iphone{
  	public void call(){
  		System.out.println("iphone is calling");
  	}
  }

2 解释

当在Phone.java里面写好代码的时候,如果需要添加新的类,则需要直接在文件里面修改代码。假如需要添加一个华为手机,则我需要在Phone.java文件里面加个if语句判断传进来的参数是不是"huawei",这样增加了类之间的耦合性。

 

当删除一个类的时候Phone.java编译可能会出现错误。  假如我删除了小米手机这个类,phone.java文件没有删除if判断语句,那么phone.java在编译的时候则会失败。

没删除Xiaomi.java编译的时候是成功并且成功运行

 

 

删除Xiaomi.java编译的时候就会失败了,因为Xiaomi.java不存在

 

动态编译:在运行的时候确定类型,绑定对象。最大发挥了Java的多态,降低类之间的耦合性。

1 代码示例

Phone.java

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
public static void main(String[] args){
  		try{
  			Class c = Class.forName("Huawei");
  			PhoneInterface cellPhone = (PhoneInterface)c.newInstance();
  			cellPhone.ring();
  		}catch (Exception  e){
  			e.printStackTrace();   
  		}
  	}
 PhoneInterface.java
  interface PhoneInterface{
  	void ring();
  }
 Huawei.java
  public class Huawei implements PhoneInterface{
  	@Override
  	public void ring(){
  		System.out.println("huawei is ringing...");
  	}
  }
 OnePlus.java
  public class OnePlus implements PhoneInterface{
  	@Override
  	public void ring(){
  		System.out.println("OnePlus is ringing...");
  	}
  }

2 解释

(1)对比静态编译,当我们需要往Phone.java里面传递新的类参数的时候,根本不需要修改Phone.java的代码,因为这里应用了Java的多态。只要新建一个新的类实现了PhoneInterface的接口,把类名传进去就可以调用。这里体现了 需要哪个类的对象就动态的创建哪个类的对象,也就是说动态的实现了类的加载。

(2)当删除一个类的时候,Phone.java文件不会编译失败。

比如说删除OnePlus.java

区别:这里说明了动态加载的在不修改Phone.java的前提下不会因为其它类的不存在而导致整个文件不能编译,而静态加载则会编译的时候绑定对象,从而导致编译失败。

优点

  • 以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。

缺点

  • 对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。

Class类和类类型

Class类

所有的类是java.lang.Class类的对象,Class类是所有类的类,反射的基础。

Class对象(类类型)

普通类构造对象是:Student s = new Student();

但Class对象则不是,看Class类的源码,构造器是私有的,则用户无法直接像普通类那样new一个Class的对象,只有JVM才可以构造Class对象。

1
2
3
4
5
private Class(ClassLoader loader) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
    }

但是我们可以通过一个已知的类获得Class对象

有以下三种方式:

1
2
3
Class s = Student.class;
Class s1 = new Student().getClass();
Class s2 = Class.forName("Student");

Class对象就是类类型,在这里表示的是Student类的类型,下面看一个图了解Class对象是什么和类在JVM中加载的过程

由图中可以看出,一个具体的Class对象就保存了具体类的基本属性和方法,并且可以调用。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package General;
 
import java.lang.reflect.Method;
 
public class Student {
    private String name;
    private int age;
    private String msg = "I am a student";
 
    public void fun() {
        System.out.println("fun");
    }
 
    public void fun(String name,int age) {
        System.out.println("我叫"+name+",今年"+age+"岁");
    }
 
    public Student(){
 
    }
 
    private Student(String name){
 
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    public String getMsg() {
        return msg;
    }
 
    public void setMsg(String msg) {
        this.msg = msg;
    }
 
}

反射相关操作

文章开始说过,反射会把一个类的成分(成员变量,方法,构造器)各自映射成一个个对象(Field对象,Method对象,Construct对象),

一个类中这些成员方法、构造方法、在加入类中都有一个类来描述。

java.lang.reflect.Constructor; java.lang.reflect.Field;  java.lang.reflect.Method;  java.lang.reflect.Modifier;

通过Class对象我们可以做什么呢?

  • 获取成员方法Method
  • 获取成员变量Field
  • 获取构造函数Construct

 

获取成员方法

用法

1
2
3
4
5
6
7
8
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 得到该类所有的方法,不包括父类的 
public Method getMethod(String name, Class<?>... parameterTypes) // 得到该类所有的public方法,包括父类的
 
//具体使用
Method[] methods = class1.getDeclaredMethods();//获取class对象的所有声明方法 
Method[] allMethods = class1.getMethods();//获取class对象的所有public方法 包括父类的方法 
Method method = class1.getMethod("info", String.class);//返回次Class对象对应类的、带指定形参列表的public方法 
Method declaredMethod = class1.getDeclaredMethod("info", String.class);//返回次Class对象对应类的、带指定形参列表的方法

例子

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
        try {
            Class c = Class.forName("General.Student");
            Object o = c.newInstance();
            Method method = c.getMethod("fun",String.class,int.class);
            method.invoke(o,"jieMing",21);
        } catch (Exception  e) {
            e.printStackTrace();
        }
    }

结果

只要知道包的限定名,就可以对Student这个类进行所有操作

 

获取成员变量信息

成员变量 = 成员类型+变量名

 

用法

获取成员变量,通过Class类的以下方法,变量是 成员变量名

1
2
3
4
5
6
7
8
public Field getDeclaredField(String name) // 获得该类自身声明的所有变量,不包括其父类的变量
public Field getField(String name) // 获得该类自所有的public成员变量,包括其父类变量
 
//具体实现
Field[] allFields = class1.getDeclaredFields();//获取class对象的所有属性 
Field[] publicFields = class1.getFields();//获取class对象的public属性 
Field ageField = class1.getDeclaredField("age");//获取class指定属性 
Field desField = class1.getField("des");//获取class指定的public属性

例子

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
        try {
            Class c = Class.forName("General.Student");
            Object o = c.newInstance();
            Field field = c.getDeclaredField("msg");//msg在例子中是私有变量,如果没设置之前,用c.getField()是会报错的
            field.setAccessible(true); //private设置为public
            System.out.println(c.getField("msg")); //用getFiled()则可以直接访问
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

获取构造函数信息

获取构造函数,Class的方法如下

用法

1
2
3
4
5
6
7
8
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) //  获得该类所有的构造器,不包括其父类的构造器
public Constructor<T> getConstructor(Class<?>... parameterTypes) // 获得该类所以public构造器,包括父类
 
//具体
Constructor<?>[] allConstructors = class1.getDeclaredConstructors();//获取class对象的所有声明构造函数 
Constructor<?>[] publicConstructors = class1.getConstructors();//获取class对象public构造函数 
Constructor<?> constructor = class1.getDeclaredConstructor(String.class);//获取指定声明构造函数 
Constructor publicConstructor = class1.getConstructor(String.class);//获取指定声明的public构造函数

例子

Student类的私有构造函数

1
2
3
private Student(String name){
     System.out.println(name);
 }

获取私有的构造函数,并且设置为public从而可以创建对象

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
        try {
            Class c = Class.forName("General.Student");
            Constructor constructor = c.getDeclaredConstructor(String.class); 
            constructor.setAccessible(true); //如果把这行注释掉,调用private的构造函数则会报错
            constructor.newInstance("JieMingLi");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

结果

实现对数据库增,查。

原理

  • 保存数据时:把pojo类的属性取出来,拼凑sql语句
  • 查询数据的时:把查询到的数据包装成一个Java对象
  • 一张数据表对应java的一个pojo对象,表中的每一个字段(column)对应pojo的每一个属性
  • 数据表名和Pojo的类名相等,column和pojo的属性相等,不区分大小写(数据库中不区分大小写)
  • pojo的每一个属性的get和set方法,都是为了后续的操作

实例

  • 数据表User

  • pojo User类
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
43
44
45
46
47
48
49
50
 	package dbtest;
 
public class User {
    private int id;
    private String name;
    private String pwd;
    private int age;
 
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                ", age=" + age +
                '}';
    }
 
    public int getId() {
        return id;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getPwd() {
        return pwd;
    }
 
    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
}
  • 数据库连接的工厂类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 	package dbtest;
 
import java.sql.Connection;
import java.sql.DriverManager;
 
public class ConnectDBFactory {
    public static  Connection getDBConnection(){
        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            String url = "jdbc:mysql://localhost:3306/sm";
            String user = "root";
            String password = "123456";
            conn = DriverManager.getConnection(url,user,password);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return conn;
    }
}
  • 操作数据库的dao
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
 	package dbtest;
 
import org.springframework.web.bind.annotation.ResponseBody;
 
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
 
public class SqlSession {
 
    public static String getSaveObjectSql(Object o) throws InvocationTargetException, IllegalAccessException {
 
        String sql = "insert into ";
        /*获取Class对象*/
        Class c = o.getClass();
        /*获取pojo所有的方法*/
        Method[] methods = c.getDeclaredMethods();
        /*获取全类名*/
        String cName = c.getName();
        /*通过全类名获取数据库名称*/
        String tableName = cName.substring(cName.lastIndexOf(".")+1,cName.length());
        sql+= tableName + "(";
        /*字段名字*/
        List<String> fieldList = new ArrayList<>();
        /*字段对应的值*/
        List valueList = new ArrayList();
 
        /*遍历Class对象的Method对象,就可以执行相对于的方法了*/
        for (Method method :
                methods) {
            String methodName = method.getName();
            /*找出get方法,并设置值*/
            if(methodName.startsWith("get") && !method.equals("getClass")){
                String fieldName = methodName.substring(3,methodName.length());
                fieldList.add(fieldName);
                Object res = method.invoke(o,null);
                if(res instanceof String){
                    valueList.add("\""+res+"\"");
                }else{
                    valueList.add(res);
                }
            }
        }
 
        /*拼接sql语句的字段*/
        for (int i = 0; i <fieldList.size() ; i++) {
            if(i < fieldList.size() - 1){
                sql += fieldList.get(i) + ",";
            }else{
                sql += fieldList.get(i) + ") values (";
            }
        }
 
        /*拼接sql语句的值*/
        for (int i = 0; i <valueList.size() ; i++) {
            if(i < valueList.size()-1){
                sql += valueList.get(i) + ",";
            }else{
                sql += valueList.get(i) + ")";
            }
        }
 
        return sql;
    }
 
 
    /*保存数据的操作*/
    public int saveObject(Object o) throws InvocationTargetException, IllegalAccessException, SQLException {
        Connection connection = ConnectDBFactory.getDBConnection();
        String sql = getSaveObjectSql(o);
        PreparedStatement statement = connection.prepareStatement(sql);
        int i = 0;
        i = statement.executeUpdate();
        return i;
    }
 
    /*
    * 查询数据,查询出来的数据映射到pojo的每一个属性上
    * */
    public Object getObject(String pname,int id) throws ClassNotFoundException {
        /*通过包名获取数据表名*/
        String tableName =  pname.substring(pname.lastIndexOf(".")+1,pname.length());
        String sql = "select * from " + tableName + " where Id = " + id;
        Connection conn = ConnectDBFactory.getDBConnection();
        Class c = Class.forName(pname);
        Object obj = null;
        try{
            Statement statement = conn.createStatement();
            ResultSet resultSet = statement.executeQuery(sql);
            Method[] methods = c.getDeclaredMethods();
 
            while(resultSet.next()){
                obj = c.newInstance();
                for (Method method :methods
                        ) {
                    String methodName = method.getName();
                    if(methodName.startsWith("set")){
                        /*通过方法名获取数据库的列名*/
                        String columnName = methodName.substring(3,methodName.length());
                        /*获取参数的类型*/
                        Class[] params = method.getParameterTypes();
                        /*判断参数的类型*/
                        if(params[0] == String.class){
                            method.invoke(obj,resultSet.getString(columnName));
                        }
                        if(params[0] == int.class){
                            method.invoke(obj,resultSet.getInt(columnName));
                        }
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return obj;
    }
 
    public static void main(String[] args) {
        try{
            SqlSession session = new SqlSession();
            User user = new User();
            user.setAge(22);
            user.setName("JiemingLi");
            user.setId(44);
            user.setPwd("123456");
            int resNum  = session.saveObject(user);
            if(resNum > 0){
                System.out.println("成功");
            }else{
                System.out.println("插入失败");
            }
            User res = (User)session.getObject("dbtest.User",44);
            System.out.println(res);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
 
}

结果

总结

Java反射非常好用,灵活性非常大,不用花费太多的时间去写操作数据库的代码,让重点在开发者的业务逻辑上。现在很多和数据库操作的框架都用到反射,只要配置文件,按照框架的规则就可以对数据库进行相对应的操作了。

 

参考