1.java.lang.Class 类
万事万物都是对象。我们平常接触到的类,本身也是一种对象,它的类型是 Class,也可以说 Class 是类的类型,即类类型 (Class Type);任何一个类,都是 java.lang.Class 的一个实例对象。
Class 是 Java 的基本类之一,也是反射机制的基础,它的意义是类的抽象,即对“类”进行描述。比如获得类的属性的方法 getField,有获得该类的所有方法、所有公有方法的方法 getMethods, getDeclaredMethods。同时,Class 也是 Java 类型中最重要的一种,表示原始类型(引用类型)及基本类型。
(1) 如何表示这个实例对象?
- 第一种:类名.Class
- 第二种:对象.getClass()
- 第三种:Class.forName("类的全称");
在编译时刻加载类,称为静态加载类,比如通过 new 关键字加载的类。
在运行时刻加载类,称为动态加载类,Class.forName() 方法就是 Java 语言中唯一一种动态加载的方法。动态加载类在编译不会报错,在运行时才会加载,使用接口标准能更方便动态加载类的实现。所以功能性的类尽量使用动态加载,而不用静态加载。
有了类的类类型,就可以获取类的所有信息;具体如下例所示:
例:写一个方法,接受一个对象,然后打印该对象所属类的所有信息;
package homework4_27;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class homework1 {
public static void main(String[] args) {
Object o = new Object();
printClassMsg("hello");
}
// 写一个方法,接受一个对象,然后打印该对象所属类的所有信息; (Reflect.Demo2.java)
// 打印所有方法的返回值类型、方法名,参数类型列表
// 得到所有的声明的成员变量,打印成员变量的类型,名称
public static void printClassMsg(Object obj){
// 获取类类型
Class c = obj.getClass();
System.out.println("类类型名称"+c.getName());
// 获取方法列表,包括方法名,返回值类型,参数列表
System.out.println("======================");
// getMethods(): 获取所有公有的方法;
Method[] ms = c.getMethods();
System.out.println(c.getName() + " 的类方法:");
for(Method m : ms) {
System.out.println("------------");
System.out.println(" 类方法名:" + c.getName() + "." + m.getName());
System.out.println(" 返回值类型:" + m.getReturnType().getName());
System.out.println(" 参数类型:");
Class[] c_params = m.getParameterTypes();
for(Class param : c_params) {
System.out.println(" " + param.getName());
}
}
// 获取类中已经声明的成员变量
System.out.println("======================");
// 获取所有声明的成员变量
// c.getFields();
// 获取该类的所有成员变量
Field[] f = c.getDeclaredFields();
System.out.println(c.getName() + " 的成员变量:");
for(Field fie : f){
System.out.println("------------");
System.out.println(" 变量类型:" + fie.getType().getName());
System.out.println(" 变量名称:" + fie.getName());
}
}
}
(2) 框架的原理
Java 框架就是一些类和接口的集合,通过这些类和接口协调来完成一系列的程序实现。框架又叫做开发中的半成品,它通过了编译,打成了 jar 包,但不能提供整个 WEB 应用程序的所有东西。但是有了框架,我们就可以集中精力进行业务逻辑的开发,同时框架会创建我们写的类的实例对象,并调用我们写的方法;这样我们就不用去关心它的技术实现以及一些辅助的业务逻辑。
说白了 Java 框架就是封装好方便程序员操作的类,在运行时动态加载我们的类(通过 Class.forName(类名) 方法),并创建对象调用方法。这样可以使项目的开发更简单,维护起来也更容易。
2. Java 的反射机制
前面提到框架是开发中的半成品,它可以在运行过程中加载实例,并填充业务。在框架中,反射是框架设计的灵魂。
Java 反射机制是在运行状态中进行的,它把 Java 类中的各种成分映射成一个个的 Java 对象。使用反射的前提条件,是首先必须得到字节码的 Class,它用于表示 .class 文件。通过动态获取信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。具体对于类和对象:
- 任意一个类,都能够知道这个类的所有属性和方法;
- 任意一个对象,都能够调用它的任意一个方法和属性;
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是 Class 类中的方法。所以先要获取到每一个字节码文件 *.class 对应的 Class 类型的对象。在 《Java基础之—反射(非常重要)》 一文中绘制了反射加载的过程:
反射加载流程用好反射,关键在于能够调用任意类或对象的方法和属性。我们可以通过 java.lang.reflect.Method 调用任意的方法,通过 java.lang.reflect.Field 调用任意的属性。
(1) java.lang.reflect.Method 方法的反射
- 获取一个方法对象
- 方法名称
- 参数列表(的类型的类类型)
- 如何用方法的反射操作方法?
- method.invoke(target, 参数列表)
方法本身也可以用对象的形式表现出来,它被封装在 java.lang.reflect.Method 中。调用一个方法对象,关键在于两部分:
- 方法签名
- 参数列表的类型的类类型
举例说明:假设已经定义了方法 methodFunction(int a, double b, boolean c),对于这个方法,methodFunction 是它的方法签名,(int.class, double.class, boolean.class) 是它的参数列表的类型的类类型。
简要介绍几种不同的获取成员方法的方式:
- 批量获取成员方法:
- public Method[] getMethods(): 获取所有"公有方法";该方法包含了父类的方法,也包含 Object 类;
- public Method[] getDeclaredMethods(): 获取所有的成员方法,包括私有的,但不包括继承的;
- 获取单个成员方法:
-
public Method getMethod(String name,Class<?>... parameterTypes):
- 参数:
- String name: 方法名;
- Class<?>...parameterTypes: 形参的Class类型对象
- 参数:
- public Method getDeclaredMethod(String name,Class<?>... parameterTypes):
-
public Method getMethod(String name,Class<?>... parameterTypes):
方法的调用是通过 Method 类的 invoke 方法实现的。它的定义为:
public Object invoke(Object obj,Object... args) {...}
参数说明:
- Object obj: 要调用方法的对象;
- Object... args: 调用方式时所传递的实参;
关于方法的反射,例程如下:
例:写三个参数列表不同的 f 方法,获得其方法的反射:
package reflect;
import java.lang.reflect.Method;
class A{
public void f(){
System.out.println("helloworld");
}
public int f(int a ,int b){
return a+b;
}
public String f(String a,String b,int c){
return a+","+b+","+c;
}
}
public class Demo3 {
public static void main(String[] args) {
A a1 = new A();
Class c = a1.getClass();
try {
// 获取名为 f 的方法,参数列表的类型分别为 (int, int)
Method method1 = c.getMethod("f",int.class,int.class);
System.out.println(a1.f(10, 10));
// 传入参数 (10, 10),输出 f(int, int) 的计算结果
int n = (Integer)method1.invoke(a1, 10,10);
System.out.println(n);
// 获取无参数的 f 方法
System.out.println("================");
Method method2 = c.getMethod("f");
a1.f();
method2.invoke(a1);
// 获取参数列表为 (String, String, int) 类型的 f 方法
System.out.println("================");
Method method3 = c.getMethod("f", String.class,String.class,int.class);
System.out.println(a1.f("hello", "world",100));
String ss = (String)method3.invoke(a1, "hello","world",100);
System.out.println(ss);
} catch (Exception e) {
e.printStackTrace();
}
}
}
该例程中首先定义了一个类 A,其中定义了几个名称为 f 的方法,都有不同的参数列表和返回值。在 getMethod 方法中传入了不同的参数列表,就可以准确的获取我们想要的具体的 f 方法。
(2) java.lang.reflect.Field 成员变量的反射
Class 类中重要的方法:
- Field[] getDeclaredFields(): 返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。
- Field getDeclaredField(String name): 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
Field 类中重要的方法:
- Class<?> getType(): 返回一个 Class 对象,它标识了此 Field 对象所表示字段的声明类型。
- void setAccessible(boolean flag): 将此对象的 accessible 标志设置为指示的布尔值。
下面用例程说明:
- 获取一个成员变量对象
- 如何进行成员变量的反射操作
- field.get(target);
- field.set(target, newValue);
例:
现在有类 User.java 如下:
package reflect;
public class User {
private String name;
private int age;
public User() {}
public User(String name, int age) {
super();
this.name = name;
this.age = age;
}
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 toString() {
return "User [name=" + name + ", age=" + age + "]";
}
}
写一个方法,接受一个对象:
- 如果该对象有字符串属性,把其值改成大写;
- 如果该对象有 int 属性,把值都加 100;
package reflect;
import java.lang.reflect.Field;
public class Demo6 {
public static void main(String[] args) {
User user = new User("zhangsan", 20);
try {
changeValue(user);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(user);
}
public static void changeValue(Object obj) throws Exception{
Class c = obj.getClass();
// 获取成员变量数组
Field[] fs = c.getDeclaredFields();
for (Field field : fs) {
// 当前类型为 String
if(field.getType() == String.class){
// java 中,在类的外面获取此类的私有成员变量的值时,需要先用 setAccessible(true) 对变量进行权限更改设定;
field.setAccessible(true);
// 获取 String field 的值,并将其转为大写;
String oldValue = (String)field.get(obj);
field.set(obj, oldValue.toUpperCase());
}
// 当前类型为 int
if(field.getType()==int.class){
field.setAccessible(true);
// 获取 int 类型 field 的值,并将其加 100
int oldValue = field.getInt(obj);
field.set(obj, oldValue+100);
}
}
}
}
(3) java.lang.reflect.Constructor 构造函数的反射
- constructor.getConstructor:获取某个构造函数
- constructor.newInstance(参数):构建函数传入参数的反射操作
Class 类关于构造函数的重要方法:
- Constructor<?> getConstructor(Class<?>... parameterTypes): 返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法;
- Constuctor<?>[] getDeclaredConstructors(): 返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法;
- T newInstance(): 创建此 Class 对象所表示的类的一个新实例。
下面依旧用上面的中的 User.java 举例说明:
package reflect;
import java.lang.reflect.Constructor;
public class Demo7 {
public static void main(String[] args) {
Class c = User.class;
try {
// 获取 User 类的无参构造函数
Constructor<User> cons = c.getConstructor();
// 构造无参的 User 实例对象
User u = cons.newInstance();
System.out.println(u);
// 获取 User(String, int) 的 User 构造函数
Constructor<User> cons2 = c.getConstructor(String.class,int.class);
// 构造 User(String, int) 的实例对象
User u2 = cons2.newInstance("lisi",20);
System.out.println(u2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
5. 数组的反射
前面说过 Java 中万事万物都是对象,其中也包含数组。数组也是实例对象,而且数组的类类型只和类型和维数相关。数组在反射机制中的类是 java.lang.reflect.Array。
依旧有上例中的 User.java,下面用例程说明:
public class Demo8 {
public static void main(String[] args) {
int[] a = {1,2,3,4,5};
int[] b = {5,6,7,8};
int[][] c = {
{1,2,3},
{4,5,6,7}
};
String[][] str = {
{"aaa","bbb"},
{"ccc","ddd","eee"}
};
Class c1 = a.getClass();
Class c2 = b.getClass();
Class c3 = c.getClass();
System.out.println(c1==c2); // true
System.out.println(c1==int[].class); // true
System.out.println(c1.getName()+","+c2.getName()); // [I,[I
System.out.println(str.getClass().getName()); // [[Ljava.lang.String;
System.out.println(str.getClass()==String[][].class); // true
}
}
输出结果为:
true
true
[I,[I
[[Ljava.lang.String;
true
当然通过反射的方法来创建数组的用法是很少见的,其实也是多余的。为什么不直接通过 new 来创建数组呢?反射创建数组不仅速度没有 new 快,而且写的程序也不易读,还不如 new 来的直接。事实上通过反射创建数组确实很少见。
练习:
写一个函数,签名与参数列表为:public String getSql(Object obj);
- 如果传递的是 User 对象
- User 有 (name, age, sex) 属性
- 写一个数据库操作的命令行:返回 insert into User(name, age, sex) values (?, ?, ?)
public class Demo11 {
public static void main(String[] args) {
String res = getSql(new User());
System.out.println(res);
}
public static String getSql(Object obj) {
StringBuffer s = new StringBuffer();
s.append("insert into ");
Class c = obj.getClass();
// 不包含包名的类名称
String className = c.getSimpleName();
s.append(className).append("(");
//==============
// 获取类的成员变量,并用逗号与括号分隔存储
// 如:(name, age, sex)
//==============
Field[] fs = c.getDeclaredFields();
// 类的所有成员变量用逗号','隔开
for(int i = 0; i < fs.length; i++) {
s = i == 0 ? s.append(fs[i].getName()) : s.append(", ").append(fs[i].getName());
}
// 接上相同个数的 '?' 列表
s.append(") values ").append(getString(fs.length));
return s.toString();
}
public static String getString(int length) {
StringBuilder s = new StringBuilder();
s.append("(");
for(int i = 0; i < length; i++)
s = i == 0 ? s.append("?") : s.append(", ?");
return s.append(")").toString();
}
}
输出为:
insert into User(name, age, sex) values (?, ?, ?)
网友评论