我们在编程的过程中,为了更好地描述现实世界的事物,会将现实中的事物看做一个一个的对象,那在程序设计中是用什么来描述对象会有哪些属性或者方法呢?那就是用类了,其实在现实社会中也一样,一个一个的人是属于人类这一个类别的,那在程序设计中,有没有什么东西是用来描述类的呢?有的,Java中就将每个类看做是Class这个类的一个对象,也就是字节码对象,通过字节码对象,我们就能够知道这个类拥有哪些成员了。
1.字节码对象的获取
字节码对象的获取方式一共有三种,第一种是直接通过类名,每个类都会有一个静态成员属性 class
,直接通过这个属性就可以得到这个类的字节码对象;第二种就是要通过一个类的对象,每个对象都会有一个 getClass()
方法,这个方法是继承 Object
这个类得到的,通过对象调用这个方法就可以得到该对象所属类的字节码对象;第三种方法是通过 Class
这个类的静态方法 forName(String className)
来得到字节码对象,传入的字符串参数是这个类的全类名,就是包含完整包名的类名。这三种方式第三种只需要字符串参数就可以了,因此使用的最多。
现在假设有一个 Student
类,那获取这个类的字节码文件就有以下三种方式:
a:Class clazz = Student.class;
b:Student s = new Student(); Class clazz = s.getClass();
c:Class clazz = Class.forName("party.laucloud.Student");
使用一个类的字节码对象主要是为了获取这个类的各个组成部分,并使用它们,而一个类的组成部分主要就是构造器,成员变量和成员方法,下面就依次演示如何得到这各个部分。
2.获取构造器
为了演示获取构造器和下面所有的代码,先在这里把要操作的类放上来
package day13;
public class Student {
private String name;
int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Student() {
super();
}
public String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
那下面就直接演示获取构造器了
public class ClassDemo2 {
public static void main(String[] args) throws ClassNotFoundException,
NoSuchMethodException, SecurityException, InstantiationException,
IllegalAccessException, IllegalArgumentException,
InvocationTargetException {
Class clazz = Class.forName("day13.Student");
Constructor[] cs = clazz.getConstructors();
for (int i = 0; i < cs.length; i++) {
System.out.println(cs[i]);
}
Constructor c = clazz.getConstructor();
Object s = c.newInstance();
System.out.println(s);
Constructor c2 = clazz.getConstructor(String.class, int.class);
Object s2 = c2.newInstance("小樱", 17);
System.out.println(s2);
}
}
上面的程序演示了获取 Student
的空参构造和有参构造,并对它们进行了调用
结果如下:
public day13.Student(java.lang.String,int)
public day13.Student()
Student [name=null, age=0]
Student [name=小樱, age=17]
3.获取成员变量
先放代码:
public class FieldDemo {
public static void main(String[] args) throws ClassNotFoundException,
NoSuchFieldException, SecurityException, IllegalArgumentException,
IllegalAccessException, InstantiationException {
Class<?> clazz = Class.forName("day13.Student");
Object stu = clazz.newInstance();
Field[] fs = clazz.getDeclaredFields();
for (int i = 0; i < fs.length; i++) {
System.out.println(fs[i]);
}
Field fAge = clazz.getDeclaredField("age");
Object age = fAge.get(stu);
System.out.println(age);
fAge.set(stu, 18);
System.out.println(stu);
}
}
这次字节码对象获取成员属性的时候是使用的 getDeclaredFields()
方法,而上面获取构造器是直接使用的 getConstructors()
方法,其实获取成员变量也可以使用 getFields()
方法,也是用来获取类的成员变量的,其实还不止获取成员变量如此,获取类的其它组成部分都会有这样的差别,就是一个方法中间带有 Declared
,一个没有,那这两者的区别是什么呢?就以获取成员变量为例来说明, getFields()
是获取本类以及父类还有超类中所有以 public
修饰的成员变量,而 getDeclaredFields()
而是获取本类中声明的所有成员变量,这就是两者的差别。
其实获取成员变量还有一个方法,那就是获取指定的成员变量,通过 getDeclaredField(String name)
来实现,当然这和上面也是一样,public
修饰的成员变量可以通过 getField(String name)
获得,而其它的就只能通过 getDeclaredField(String name)
来访问了。
4.获取成员方法
先把代码放上来:
public class MethodDemo {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("day13.Student");
Object stu = clazz.newInstance();
Method method = clazz.getMethod("getName");
System.out.println(method.invoke(stu));
Method m2 = clazz.getDeclaredMethod("setName", String.class);
m2.setAccessible(true);
m2.invoke(stu, "李四");
System.out.println(stu);
}
}
这里通过反射调用 setName()
方法的时候,其中有一句代码是 m2.setAccessible(true);
,如果不加这一句的话程序会报错,报错提示是说不能访问一个以 private
修饰的成员方法,这就是Java中的访问检查机制,因为我们这个程序是在 Student
类的外面,因此是不能访问 Student
类中的私有成员方法的,这和我们之前编程的逻辑也是一样的,那如果想在 Student
类的外面访问这个私有方法怎么办呢?这里就要看API文档了,成员方法所属的类是 java.lang.reflect.Method
,而它的直接父类就是 java.lang.reflect.AccessibleObject
,没错,这里面就有取消Java中访问检查的方法,那就是 setAccessible(boolean flag)
,这是一个普通的成员方法,由于 Method
是这个类的子类,所以也就继承了这一个方法,因此要想访问 Student
类中的私有成员方法,就应该使用对应的 Method
对象调用上面这个方法,那也就是 m2.setAccessible(true);
了,其实像这样暴力访问的方式并不好,因为破坏了 Studnet
类的封装性。
其实这里主要是为了演示这种现象才把 setName()
方法设置为私有的,像上面的 getter()
和 setter()
方法主要就是为了让外部程序访问类中的成员变量的,因此是不应该设置为私有的。
5.BeanUtils的使用
BeanUtils
是 Apache
这个组织发布的操作 JavaBean
的工具组件,里面封装了很多实用方法,可以提高我们的开发效率,说起来 Apache
这个组织真的挺强大的,发布了好多关于 Java
的组件和应用,比如 Tomcat
服务器,DBUtils
操作数据库的组件。
我使用的 BeanUtils
版本号是 commons-beanutils-1.8.3.jar
,而使用它的话还需要另一个组件 commons-logging-1.1.1.jar
,这两个都可以在网上搜索然后在官网进行下载的,下载工具的话我还是比较喜欢到官网,就算下载 JDK
或者 MySQL
需要先注册一下,还是感觉东西把握在自己手中的感觉,其实如果第一次下载某种东西感觉很陌生的话,多尝试一下就好了,然后多下载两遍就熟悉了,就会发觉获取工具其实蛮简单的。
5.1 导入jar包
使用上面所说的工具,必须导入相应的jar包,直接在项目中新建一个文件夹,比如 lib
,然后将上面两个 jar
包拷贝到里面,打开 lib
文件夹,选中刚拷贝进来的两个 jar
包,点击右键,选择 Build Path
,再选择 Add to Build Path
,这样 jar
就导入成功了。
5.2 使用BeanUtils
这里主要介绍 BeanUtils
类中的三个方法:
1.public static String getProperty(Object bean, String name)
2.public static void setProperty(Object bean, String name, Object value)
3.public static void populate(Object bean, Map properties)
很明显可以看出,第一个方法是获取 bean
对象中某个属性的值,第二个就是为 bean
对象中的某个属性赋值,第三个方法就是使用 Map
集合中的对应的键值对为 bean
对象赋值。
首先自己先定义一个人物类:
public class Person {
private String name;
private Integer 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;
}
public Person() {
super();
}
public Person(String name, Integer age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
下面再演示 BeanUtils
类的相关方法:
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.beanutils.BeanUtils;
public class BeanUtilsDemo {
public static void main(String[] args) throws ReflectiveOperationException {
// mathod();
Person p2 = new Person();
Map<String, Object> map = new HashMap<String, Object>();
map.put("name", "小雪");
map.put("age", 17);
map.put("number", 14526);
BeanUtils.populate(p2, map);
System.out.println(p2);
}
public static void mathod() throws IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
Person p = new Person();
System.out.println(p);
BeanUtils.setProperty(p, "name", "青");
BeanUtils.setProperty(p, "age", 18);
System.out.println(p);
String name = BeanUtils.getProperty(p, "name");
String age = BeanUtils.getProperty(p, "age");
System.out.println("name:" + name);
System.out.println("age:" + age);
}
}
使用 BeanUtils
确实可以提高开发效率,使用起来也比较简单。
6.自己创建BeanUtils相似工具类
其实 BeanUtils
中获取对象属性值和为属性赋值也还是走的 getter
和 setter
方法,可以做个试验,就是将 Person
类中的某些 getter
和 setter
方法设置为 private
,程序就会出现异常,并且获取到的值就会是 null
或者 0
,这就说明 BeanUtils
绝对走的不是上面我们说过的暴力访问的方法,那样也确实不好,破坏了类的封装性,那就完全背离了 JavaBean
的设计初衷了,知道这些。我们也就可以自己尝试着写一下类似 BeanUtils
的程序了。
直接放上代码,工具类:
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
public class MyBeanUtils {
public static void setProperty(Object bean, String name, Object value)
throws ReflectiveOperationException {
Class clazz = bean.getClass();
String methodName = "set" + name.substring(0, 1).toUpperCase()
+ name.substring(1);
try {
Method method = clazz.getDeclaredMethod(methodName,
value.getClass());
method.invoke(bean, value);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public static String getProperty(Object bean, String name)
throws ReflectiveOperationException {
Class clazz = bean.getClass();
String methodName = "get" + name.substring(0, 1).toUpperCase()
+ name.substring(1);
Object value = null;
try {
Method method = clazz.getDeclaredMethod(methodName);
value = method.invoke(bean);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return "" + value;
}
public static void populate(Object bean, Map map) throws ReflectiveOperationException {
Set<String> keySet = map.keySet();
Class clazz = bean.getClass();
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
String name = fields[i].getName();
if(keySet.contains(name)){
Object value = map.get(name);
setProperty(bean,name,value);
}
}
}
}
测试类:
import java.util.HashMap;
import java.util.Map;
public class MyBeanUtilsDemo {
public static void main(String[] args) throws ReflectiveOperationException {
Person p2 = new Person();
System.out.println(p2);
Map<String, Object> map = new HashMap<String, Object>();
map.put("name", "小雪");
map.put("age", 17);
map.put("number", 14526);
MyBeanUtils.populate(p2,map);
System.out.println(p2);
}
public static Person method() throws ReflectiveOperationException {
Person p = new Person();
System.out.println(p);
MyBeanUtils.setProperty(p, "name", "Lau Cloud");
MyBeanUtils.setProperty(p, "age", 17);
System.out.println(p);
String name = MyBeanUtils.getProperty(p, "name");
String age = MyBeanUtils.getProperty(p, "age");
System.out.println("name:" + name);
System.out.println("age:" + age);
return p;
}
}
把这个小工具类写出来还是有点小激动的,毕竟自己用所学的知识实现了自己所想的东西,而且还不是那么简单粗暴的方法,这说明自己还是费了心思的。
7.总结
反射在一般的应用开发中用的比较少,但是编写一些工具还是可以的,就我现在的理解而言,字节码对象就是用来描述一个类的,而类的组成大概就是构造器、成员变量和成员方法,因此最重要的就是使用反射获取这些组成部分,其它的就是要注意一些细节吧,把握大的,留意小的,就差不多了吧。