目录
对象创建的5种方式:
静态编码&编译
克隆(clon)
序列化及反序列化
反射创建
对象
反射关键类——拿到一个对象的详细信息
反射的应用
编译器API
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。
——百度百科对反射的定义
Java本身是一个强类型的语言,但是位于java.lang.reflect包下的反射为Java增添了动态语言的特性,有效的弥补了强类型语言的不足。通过反射我们可以在程序运行的时候加载、探知、以及使用编译期间完全未知的类,那么这些未知的类又是如何创建的呢?
对象创建的5种方式:
静态编码&编译
//这也是我们最常见的一种方式——new关键字创建对象
CreateClassNew createClassNew = new CreateClassNew();
克隆(clon)
对于克隆我们可以分为浅克隆、深克隆,浅克隆是一种较为简单的实现,深克隆的实现除过通过Cloneable接口外还可以通过序列化以及反序列化实现。PersionClone内含两个普通类型的成员变量以及一个自定义类型的成员变量Address,Address类型内部又含有一个普通类型的成员以及一个自定义类型CityPosition。深克隆与浅克隆的差异主要是在自定义的类型上,亦或是说引用类型上。
public class PersionClone implements Cloneable{
private String name;
private int age;
private Address address;
public PersionClone(String name,int age,Address address){
this.name =name;
this.age =age;
this.address = address;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
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;
}
@Override
public String toString() {
return "PersionClone{" +
"name='" + name + '\'' +
", age=" + age +
", address=" + address +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Address {
private String city;
private CityPosition cityPosition;
public Address(String city,CityPosition cityPosition){
this.city = city;
this.cityPosition = cityPosition;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public CityPosition getCityPosition() {
return cityPosition;
}
public void setCityPosition(CityPosition cityPosition) {
this.cityPosition = cityPosition;
}
@Override
public String toString() {
return "Address{" +
"city='" + city + '\'' +
", cityPosition=" + cityPosition +
'}';
}
}
public class CityPosition {
private float x;
private float y;
public CityPosition(int x,int y){
this.x =x;
this.y =y;
}
public float getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
public float getY() {
return y;
}
public void setY(float y) {
this.y = y;
}
@Override
public String toString() {
return "CityPosition{" +
"x=" + x +
", y=" + y +
'}';
}
}
测试代码:
public class ReflectionTestMain {
public static void main(String[] args) throws CloneNotSupportedException {
CreateClassNew createClassNew = new CreateClassNew();
CityPosition cityPosition =new CityPosition(1,1);
Address address =new Address("LuoYang",cityPosition);
PersionClone persion =new PersionClone("ZhangSan",12,address);
PersionClone clonePersion = (PersionClone) persion.clone();
// 对克隆对象的一个改变
clonePersion.setName("LiSi");
clonePersion.getAddress().setCity("XiAn");
clonePersion.getAddress().getCityPosition().setX(2);
// 对两个对象的一个输出检测
System.out.println("persion:"+persion.toString());
System.out.println("clonePerdsion:"+clonePersion.toString());
}
}
上述展示的浅克隆,运行的结果我们可以看到,两个对象的值类型相互独立互补干扰,但是引用类型则是改变一个则两个同时都有改变:
产生上述的情况的原因是因为:浅克隆的时候如果被克隆对象的变量是值类型那么就将值复制一份给新的对象,如果变量是引用对象那就将引用的地址给一份给信的对象。这就造成被克隆对象与新的对象的引用型变量指向的是内存中的通一块地址,引用型成员变量的值实际上并没有被克隆。如果我们想要实现引用类型的成员变量也被成功的克隆,那么我们可以通过重写被克隆对象的clone方法,同时给引用对象类型实现Cloneable接口。
被克隆对象中代码的改动:
@Override
protected Object clone() throws CloneNotSupportedException {
PersionClone temp = null;
temp = (PersionClone) super.clone();
temp.setAddress((Address) address.clone());
return temp;
}
引用类型的中实现Cloneable接口并且重写Clone方法:
public class Address implements Cloneable{
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
这时候我们发现运行的结果依旧是有问题的,城市的坐标依旧是两个对象同时更改。这是因为我们发生了多层克隆,我们可以继续以上边解决PersionClone、与Address之间浅克隆的问题解决Address、与CityPosition之间发生的浅克隆问题。但是如果我们的引入对象被嵌套了好多层的话,那我们就需要重写多次clone()方法,明显是不理智的,这时候我们通过序列化的方法来实现多层克隆问题。
序列化及反序列化
找一个中间文件作为载体,先将要被克隆对象的class文件写入一个中间文件,克隆的时候我们再将这个文件读出成一个对象,这时候我们就实现了序列化及反序列化创建对象,同时也实现了上边所说的多层克隆。一个对象如果想要通过序列化及反序列化创建应该实现Serializable接口,并且定义一个静态实例域:private static final long servialVersionUID =1L;
ObjectOutputStream out =new ObjectOutputStream(new FileOutputStream("data.obj"));
out.writeObject(persion);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));
PersionClone clonePersion = (PersionClone) in.readObject();
in.close();
如果我们实现序列化的方法中有引用类型变量,那么引用类型的也是需要实现序列化的。就如同我们的PersionClone实现了实例化,那么类中有一个引用类型的变量Address,这个Address就业需要实现序列化,然而Address中又有一个CityPosition的引用类型,那这个引用类型就也是需要实现序列化的。实现序列化就是实现Serializable接口并且定义一个servialVersionUID的静态long型常量。序列化看似无比强大,但是因为有中间文件的存在,所以带来了诸多的不安全因素,现在JDK并不推荐使用,已经将这个语法标记为简要废除的语法,所以我们在使用的时候应当注意。
反射创建对象
这两种不同的创建形式主要对应着两种不同的方法调用,个人感觉两种不同的方法调用分别于代理中的动态代理与静态代理中的一部分相对应
// 通过newInstance来创建对象
Object createClassNew1 = Class.forName("reflectio.CreateClassNew").newInstance();
Method m =Class.forName("reflectio.CreateClassNew").getMethod("hello");
m.invoke(createClassNew1);
// 方法的不同调用形式
CreateClassNew createClassNew2 = (CreateClassNew) Class.forName("reflectio.CreateClassNew").newInstance();
createClassNew2.hello();
反射关键类——拿到一个对象的详细信息
如果想要拿到一个对象的关键信息,首先我们应该拿到这个对象的类型表示信息,JVM中为每个对象都保留其类型的信息,获取的方式有三种。
- 已知一个对象(createClassNew3是我们已经创建好的好的对象,内有一个hello方法),通过getClass方法拿到:Class c1 =createClassNew3.getClass();
- 已知对象所属类型的全限定类名,通过forName的方法拿到:Class c2 =Class.forName("reflectio.CreateClassNew");
- 已知对象所说类型通过后缀".class"拿到:Class c3 =CreateClassNew.class;
获得了对象的类型信息之后我们便可以通过如下的方法类获得相应的信息,同时通过所获得相应信息的相应类型接受,例如getFields()获得的就通过Field接受,getPackage()获得的我们就通过Package接受.........
setAccessible(true)的方法来将这个属性的可访问修饰符改为public,不然的话就会出现报错。这里访问修饰符属性的修改并不会对类本身的造成影响。
Class persionClass =persion.getClass();
Field[] fileds= persionClass.getFields();
for (Field field:fileds){
System.out.println(field.getName()+":"+field.get(persion));
}
Field [] fields2 =persionClass.getDeclaredFields();
for (Field field:fields2){
field.setAccessible(true);
System.out.println(field.getName()+":"+field.get(persion));
}
Package packages =persionClass.getPackage();
System.out.println(packages);
反射的应用
总所周知Java是一门面向对象的语言,几乎程序的所到之处皆需要对象的创建。上边的过程中我们也讲到了了对象创建的几种方式,我们大概的可以分为这几种:通过new关键字,通过复制粘贴的方式(克隆、序列化以及反序列化)、通过反射创建对象。在我们对象创建时候前两种对象的创建我们很容易出来看到的,所以说除此之外几乎都是通过反射穿件对象,通过反射穿件对象最常见的一种表现形式就是我们通过配置文件来配置我们的程序。
数组扩充器:
int[] a={1,2,3,4,5};
for (int i : a = (int[]) arrayExpansion(a, 10)) {
System.out.println(i);
}
public static Object arrayExpansion (Object oldArray,int newLength){
// 获取数组类型
Class c =oldArray.getClass();
// 获取数组中元素的类型
Class componentType =c.getComponentType();
// 获取旧数组的长度
int oldLength = Array.getLength(oldArray);
// 通过反射创建新数组
Object newArray =Array.newInstance(componentType,newLength);
// 对旧数组的数据进行拷贝
System.arraycopy(oldArray,0,newArray,0,oldLength);
return newArray;
}
定时器任务的执行:
public class MyTask extends TimerTask {
@Override
public void run() {
try{
Method method=Class.forName("reflectio.Worker").getMethod("hello");
method.invoke(null);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 创建一个定时对象
AtomicReference<Timer> timer = new AtomicReference<>(new Timer());
// 获取当前时间并加1
Calendar now =Calendar.getInstance();
now.set(Calendar.SECOND,now.get(Calendar.SECOND)+1);
// 创建运行开始时刻
Date runDate =now.getTime();
// 创建目标任务对象
MyTask task2 =new MyTask();
// 开始运行
timer.get().scheduleAtFixedRate(task2,runDate,3000);
在Json与对象之间的相互转换(第三方实现库Gson)、Tomcat中Servlet的创建、MyBatis的ORM(Object-Relation Mapping)实现、Spring框架的IOC容实现,都有发射的应用。
编译器API
在上面我们说到的一些创建方法起码都要要求有类的class文件,才可已经对象的创建,那么如果我们没有class文件呢?这时候我们就可以通过jdk1.6提出的编译器API来在程序中实现将Java文件编译为class文件。