时间 |
---|
4/25 |
克隆
概述
克隆出现的原因:
在实际开发过程中,一个对象obj已经包含一些有用信息,这是我们需要将obj的信息复制到obj2中,使得obj和obj2对象具有两个完全不同的地址,修改一个对象的值,另一个对象不受影响。
实现克隆的方式
实现Cloneable接口,并重写object类中的clone方法,可以实现浅克隆,也可以实现深克隆
1 被克隆对象所属类必须实现Cloneable接口
public class 类名 implements Cloneable{
private 属性类 属性名;
}
1-1 要想实现深克隆的话,类的成员属性 所在的类也需要实现Cloneable接口
public class 属性类 implements Cloneable{}
2 所属类必须重写Object类的clone方法
@Override
public Object clone() throws CloneNotSupportedException{
类名 obj =(类名)super.clone(); // 浅克隆
obj.属性名 = (属性类)属性名.clone(); //深克隆
return obj;
}
实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的克隆
// 要序列化对象所属类 需要实现Serializable接口
public class 类名 implements Serializable{}
// 序列化
1 创建序列化流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("文件路径"));
2 创建对象
类名 obj = new 类名();
3 使用序列化流的writeObject方法将对象写入指定的ObjectOutputStream流中
oos.writeObject(obj);
4 释放资源
oos.close();
// 反序列化
1 创建反序列化流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("文件路径"));
2 使用反序列化流的readObject方法从ObjectInputStream流中读取一个对象
Object obj = ois.readObject();
3 强制转化对象类型
类型 obj1 = (类型)obj;
4 释放资源
ois.close();
利用BeanUtils,apache和spring都提供了bean工具,它是浅克隆
扩展
- BeanUtils 是工具类,用于简化JavaBean的封装,简化数据的封装。(xhj理解:和collections是一样的,同样是工具类)
- apache 是 世界使用排名第一的Web服务器软件,具有跨平台和安全性的优势,是最流行的Web服务器端软件之一。
- Spring框架是一个开放源代码的J2EE应用程序框架,针对bean的生命周期进行管理的轻量级容器。
在Spring中,构成应用程序主干并由Spring IOC容器管理的对象称为bean。bean是一个由Spring IOC容器实例化、组装和管理的对象。
实例的创建不再由调用者管理,而是由Spring容器创建,容器会负责控制程序之间的关系,而不是由程序代码直接控制。
浅克隆(Shallow Clone) 和 深克隆(Deep Clone)
两种克隆方式主要区别在于是否支持引用类型的成员变量的复制。
java中的数据类型分为:基本数据类型(数值型{整型、浮点型、字符型}) 和 引用数据类型(接口、类、数组)
浅克隆(Shallow Clone)
浅克隆中,对象的复制只是复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制。
实现方法
覆盖Object类中的Clone()方法实现。
1 被复制的类需要实现Cloneable接口
public class 类名 implements Cloneable{}
2 覆盖clone()方法,方法调用super.clone()方法得到需要的复制对象,输入clone有选项可以选择,自动生成。 修改为public访问级别
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
实验分析:
1 被复制的类没有实现Cloneable接口,而直接使用 类名 对象2 = (类名)对象1.clone(); 会报错
报clone()方法在java.lang.Object中是protected访问控制。
2 被复制的类实现Cloneable接口,使用 类名 对象2 = (类名)对象1.clone(); 也会报错,
需要抛出异常CloneNotSupportedException异常
案例:
// 320-test1
// 类
public class Student implements Cloneable{
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
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;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// 测试类
public class StudentDemo {
public static void main(String[] args) throws CloneNotSupportedException {
Student s1 = new Student("汪苏泷",35);
Student s2 = (Student)s1.clone();
System.out.println(s1.getName() + ", " + s1.getAge());
System.out.println("--------------");
System.out.println(s2.getName() + ", " + s2.getAge());
System.out.println(s1 == s2);// false 说明对象在堆内存中的地址不同
}
}
深克隆(Deep Clone)
深克隆中,无论原型对象的成员变量的类型是值类型还是引用数据类型,都会复制一份给克隆对象。
实现方法:
方法一:通过覆盖Object类中的clone()方法实现
1 被复制对象的类 需要实现Cloneable接口,并重写Object类中的clone()方法。
public class Outer implements Cloneable{
private Inner inner;
@Override
public Object clone() throws CloneNotSupportedException{
Outer obj = (Outer)super.clone(); //类对象使用clone()方法
obj.inner = (Inner)inner.clone();//类对象属性的类 使用clone()方法
return obj;
}
}
2 Inner类也要实现Cloneable接口,重写Object类中的clone()方法。
public class Inner implements Cloneable{
@Override
public Object clone() throws CloneNotSupportedException{
Inner obj = (Inner)super.clone();
}
* 输入clone有选项可以选择 修改为public访问级别
方法二:使用序列化(Serialization)实现,序列化流ObjectOutputStream、反序列化流ObjectInputStream
// 序列化
1 创建序列化流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("文件路径"));
2 创建对象
类名 obj1 = new 类名();
3 使用序列化方法writeObject(Object obj)
oos.writeObject(obj1);
4 释放资源
oos.close();
// 反序列化
1 创建反序列化对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("文件路径"));
2 使用反序列化方法readObject()
Object obj = ois.readObject();
3 强制类型转换 Object =》 类名
类名 obj2 =(类名)obj;
4 释放资源
ois.close();
方法一案例:
// 320-test2
// Student的属性 的类
public class Address implements Cloneable {
private String address;
public Address() {
}
public Address(String address) {
this.address = address;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// Student 要克隆的对象所属的类
public class Student implements Cloneable{
private String name;
private int age;
private Address addr;
// 创建的是Address类的对象
public Student() {
}
public Student(String name, int age) {
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 Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
@Override
public Object clone() throws CloneNotSupportedException {
Student stu = (Student)super.clone();//浅克隆
stu.addr = (Address)addr.clone();//深克隆
return stu;
}
}
// 测试类
public class StudentDemo {
public static void main(String[] args) throws CloneNotSupportedException {
Address addr = new Address("西安市");
Student s1 = new Student("汪苏泷",35);
s1.setAddr(addr);
Student s2 = (Student)s1.clone();
System.out.println(s1.getName() + ", " + s1.getAge()+", "+s1.getAddr().getAddress());
System.out.println(s2.getName() + ", " + s2.getAge()+", " + s2.getAddr().getAddress());
// System.out.println(s1 == s2);// false 说明对象在堆内存中的地址不同
// 修改前输出: 汪苏泷, 35, 西安市
// 汪苏泷, 35, 西安市
// 两个对象的Addr变量都一样,为了验证原对象和新对象是两个不一样的,这里对s2对象的Addr变量修改值
addr.setAddress("重庆市");
System.out.println("修改之后");
System.out.println("--------------");
System.out.println(s1.getName() + ", " + s1.getAge()+", "+s1.getAddr().getAddress());
System.out.println(s2.getName() + ", " + s2.getAge()+", " + s2.getAddr().getAddress());
// 对s2对象的成员变量addr进行修改,s1对象的addr变量也发生了变化。
// =》 说明 没有实现深克隆,只完成了浅克隆,成员是值类型的复制。
// 要想实现深克隆,需要给Student类中的addr变量所处在类Address设置为浅克隆实现。
// Address实现cloneable、且重写clone方法
// 修改前输出:汪苏泷, 35, 重庆市
// 汪苏泷, 35, 重庆市
// 修改后输出:汪苏泷, 35, 重庆市
// 汪苏泷, 35, 西安市
}
}
细节对比点
new操作符创建对象和clone方法复制对象的区别
具体操作 | new | clone |
---|---|---|
分配内存 | 根据new操作符后面的类型分配对应大小的内存空间 | 分配与源对象相同大小的内存空间 |
填充对象的域 | 调用构造方法,填充对象的各个域(对象的初始化) | 使用源对象中对应的各个域填充新对象的域 |
对象创建完毕 | 构造方法返回创建完毕的对象,并将其引用地址发布到外部(栈内存) | clone()方法返回对象,并将其引用发布到外部(栈内存) |
复制引用 和 复制对象 区别
A 是 源对象,B 是 新对象
复制引用 = A 和 B 地址值 相同,说明是一个对象
复制对象 = A 和 B 地址值 不相同,说明是两个对象
克隆两种实现方式的区别
区别项 | 实现Cloneable接口的方式 | 序列化和反序列化方式 |
---|---|---|
是否可以实现深度克隆 | 可以, 若对象属性嵌套很多引用类型,则需要在类中写多个.clone()方法,使用不是很方便 | 可以,使用方便 |
是否支持编译时检测异常 | 不支持,是在运行时抛出异常的 | 支持,通过泛型限定,可以检查出克隆的对象是否支持序列化,在编译阶段完成 |
java参数传递 = 值传递
当java传递的参数是基本数据类型时,一个方法不能改变基本数据类型参数的值;
当java传递的参数是引用数据类型时,一个方法可以修改引用数据类型参数所指向的对象的值;
值传递的精髓:传递的是存储单元中的内容,而不是存储单元的引用。
基本数据类型参数
// idea_face-test2-TransferTest
public class TransferTest {
public static void main(String[] args) {
int num = 1;
System.out.println("method()方法调用前=" + num);
method(num);
System.out.println("method()方法调用后=" + num);
}
public static void method(int number){
number = 2;
}
//method()方法调用前=1
//method()方法调用后=1
}
分析:
引用数据类型参数
public class TransterTest2 {
public static void main(String[] args) {
Person p = new Person("汪苏泷");
System.out.println("调用方法前= name:" + p.getName());
method(p);
System.out.println("调用方法后= name:" + p.getName());
}
public static void method(Person p){
p.setName("许嵩");
}
// 调用方法前= name:汪苏泷
// 调用方法后= name:许嵩
}
分析:
RMI
- RMI(Remote Method Invocation),远程方法调用,允许运行在一个java虚拟机的对象调用运行在另一个java虚拟机上的对象的方法。两个虚拟机可以是运行在相同计算机上的不同进程,也可以是运行在网络中的不同计算机。
- RPC(Remote Procedure Call),远程过程调用,用于一个进程调用另一个进程。
Java中的动态代理
代理模式
- 代理模式(Proxy或Surrogate),就是一个人或机构代替另一个人或机构采取行动。
一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理就是为其他对象提供一个代理以控制对某个对象的访问。 - 优点:
可以隐藏真实目标类的实现;可以实现客户与真实目标类之间的解耦,在不修改真实目标类代码的情况下能够做一些操作; - 代理模式实现条件:
代理类和被代理类实现共同的接口(或继承);
代理类中存在指向被代理类的索引;( xhj 这个代理类的索引 = 创建的真实类的对象 )
实际执行是通过调用代理类的方法从而实际执行被代理类的方法。 - 代理分类:
静态代理:程序员创建或特定工具自动生成源码,在对其进行编译。运行前,代理类的.class文件就已经存在了;
动态代理:程序运行时,运用反射机制动态创建而成,动态代理类的字节码文件在程序运行时由java反射机制动态生成,无需程序员手工编写它的源代码。- 动态代理分类:
基于接口的代理:代表 = JDK代理
基于继承的代理:代表 = CGlib代理
- 动态代理分类:
静态代理
实现步骤
1 创建一个接口MyInterface,并创建抽象方法
public interface MyInterface{
// 创建抽象方法
public abstract 返回值类型 method(接口MyInterface参数);
}
2 创建MyInterface真实的实现类RealClass,重写接口中的抽象方法
public class RealClass implements MyInterface{
// 重写接口中的抽象方法,此时方法不在带有abstract修饰符
@Override
public 返回值类型 method(接口MyInterface参数){
真实的实现过程;
}
}
3 创建 代理类
public class ProxyClass implements MyInterface{
// 创建接口MyInterface的实现类对象,
// 这里创建的是接口MyInterface的真实类对象,也就是代理类中指向被代理类的索引
private MyInterface myinterface;
// 创建带参构造方法(真实类 真实类对象)
ProxyClass(MyInterface myinterface){
this.myinterface = myinterface;
}
// 重写接口MyInterface中的抽象类方法
@Override
public method(接口MyInterface参数){
调用代理方法前做的事情;
返回值 x = myinterface.method(接口MyInterface参数);
调用代理方法后做的事情;
}
理解
- 好处:
比如每次从磁盘中获取字体库的时候,磁盘的I/O比较耗时,想通过缓存将读到的字体库暂存一份,直接修改代理类ProxyClass,不用修改真实RealClass类。
同样这样的需求直接修改RealClass类也可以实现,但是当多个RealClass2,RealClass3也需要判断缓存是否有字体的时候,那么修改真实类就需要修改多个,而采用代理类的形式,只需要修改一个类即可。
代码
需求:考虑一个字体提供功能,字体库可能源自本地磁盘、网络或者系统。
// 326-test1
// 接口
// 先考虑从本地磁盘中获取字体,采用代理的方式实现,定义一个提供字体的接口FontProvider:
public interface FontProvider {
Font getFont(String name);
}
// 真实类:真正提供获取磁盘字体库的类
public class FontProviderFromDisk implements FontProvider{
@Override
public Font getFont(String name) {
System.out.println("磁盘上的字体库");
return null;
}
// 只是输出文字,返回值类型可以不写为Font类型,而是写为void
}
// 代理类
public class ProxyForFont implements FontProvider{
private FontProvider fontProvider;
// 定义一个接口对象,实际上是被代理类对象
ProxyForFont(FontProvider fontProvider){
this.fontProvider = fontProvider;
}
// 定义带参构造方法,参数为接口对象
@Override
public Font getFont(String name) {
System.out.println("提供代理方法前做的事情");
Font font = fontProvider.getFont(name);
System.out.println("调用代理后做的事情");
return font;
}
}
// 测试类
public class MyFontDemo {
public static void main(String[] args) {
FontProvider fp = new ProxyForFont(new FontProviderFromDisk());
// 多态的形式创建FontProvider接口对象
// 接口 对象名 = new 代理类名(new 真实类名());
fp.getFont("宋体");
}
}
动态代理
出现的原因
当需要获取多种资源,比如字体、图片、音乐等资源,这时就需要多个接口,比如FontProvider、ImageProvider、MusicProvider接口等,需要对应的实现类(比如FontClass、ImageClass、MusicClass等),和其对一个的代理类(比如FontProxy、ImageProxy、MusicProxy等)。当需要给获取字体、图片、音乐等功能上加上缓存功能,就需要对三个代理类进行改动,但是修改的部分逻辑上又相同,就会造成代码的重复和代理类爆炸。
分类: 基于接口的代理(JDK) 和 基于继承的代理(CGlib)
基于接口的代理JDK
InvocationHandler接口
- InvocationHandler接口
每个代理类的实例都会有一个与之相关联的InvocationHandler实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。
public interface InvocationHandler { // 接口
// 唯一的方法invoke
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
参数介绍:
参数名 | 说明 |
---|---|
Object proxy | 代理对象 |
Method method | 代理对象调用的方法 |
Object[] args | 调用方法中的参数 |
Proxy类的静态newProxyInstance方法
返回指定接口的代理实例,该代理实例将方法调用分配给指定的调用处理程序。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
参数介绍:
参数名 | 说明 | 获得 |
---|---|---|
ClassLoader loader | 类加载器 | 测试类.class.getClassLoader() |
Class<?>[] interfaces | 用来代理的接口 | new Class[]{接口.class} 也就是说这里可以是多个接口.class |
InvocationHandler h | 一个InvocationHandler对象 | InvocationHandler handler = new jdkProxyClass(new RealSubject()); jdkProxyClass是代理类,RealSubject是真实类 |
说明
- 测试类.class.getClassLoader()
测试类.class,得到该类对应的Class类对象,通过类的class属性
Class类对象.getClassLoader(),得到Class类对象的类加载器 - Class<?>[] interfaces = new Class[]{接口.class}
2022/4/25 理解:需要创建一个Class数组,成员是接口的Class对象。
具体的实现步骤
1 创建接口MyInterface
public interface MyInterface{
// 定义抽象方法
public abstract 返回值类型 method1();
public abstract 返回值类型 method2();
}
2 创建真实的接口实现类RealClass
public class RealClass{
// 重写抽象方法
@Override
public 返回值类型 method1(){
method1具体的实现;
}
@Override
public 返回值类型 method2(){
method2具体的实现;
}
}
3 创建接口的代理类,需要实现InvocationHandler接口
public class jdkProxyClass implements InvocationHandler{
// 创建接口的真实类对象
private RealClass realObj;
// 创建真实类对象为参数的带参构造方法
public jdkProxyClass(RealClass realObj){
this.realObj = realObj;
}
// 重写Object类中的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// proxy 被代理的对象;method:调用的方法;args:方法所需的参数
执行代理调用之前的操作。
Object result = null;
// 这里的方法是通过反射获取的方法对象
result = method.invoke(realObj,args);
return result;
}
4 创建测试类
public class jdkDemo{
public static void main(String[] args){
// 创建要代理的真实对象,采用多态的形式
MyInterface realObj = new RealClass();
// 创建代理对象
InvocationHandler handler = new jdkProxyClass(realObj);
// 使用Proxy.newProxyInstrace()这个静态方法 获得接口的对象
MyInterface myinterface = (MyInterface)Proxy.newProxyInstance(jdkDemo.class.getClassLoader(),new Class[]{MyInterface.class},handler);
// 通过接口对象调用方法
myinterface.method1();
myinterface.method2();
}
案例:
// 326-test2
// 接口定义
public interface Subject {
void method1();
void method2();
}
// 接口真实的实现类定义
public class RealSubject implements Subject{
@Override
public void method1() {
System.out.println("method1执行");
}
@Override
public void method2() {
System.out.println("method2执行");
}
}
// 接口的代理类定义
public class jdkProxySubject implements InvocationHandler {
// 创建代理类的真实对象,也就是真实类的对象
private Object subject;
// 创建带参构造方法
public jdkProxySubject(Object subject){
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// proxy:被代理的对象
// method:调用的方法
// args:方法所需要的参数
System.out.println("before");
Object result = null;
try{
// 利用反射动态的来反射方法,动态代理和静态代理的区别
result = method.invoke(subject, args);
}catch(Exception e){
System.out.println("ex:" + e.getMessage());
}finally {
System.out.println("after");
}
return result;
}
}
// 测试类
public class Client {
public static void main(String[] args){
// 创建要代理的真实对象
Subject realSubject = new RealSubject();
// 创建代理对象,将真实对象作为参数传入
InvocationHandler handler =new jdkProxySubject(realSubject);
Subject subject = (Subject)Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{Subject.class}, handler);
subject.method1();
subject.method2();
}
}
基于继承的代理 CGlib
概述
- CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,为一个类创建子类,并在子类中采用方法拦截技术 拦截所有对父类方法的调用,并顺势加入横切逻辑。
ASM是一款java字节码层面的代码分析和修改工具。
ASM的目标是生成、转换、分析已编译的java class文件,可使用ASM工具读/写/转换JVM指令集。 - CGlib是针对类来实现代理的,原理是对指定的业务类生成一个子类,并覆盖其中业务方法实现代理,因为采用的是继承,所以不能对final修饰的类进行代理。
由于spring框架中的MethodInterceptor接口,目前没有学习spring框架 ,后期总结。
参考博客