框架进阶.

Spring

BeanFactory和ApplicationContext

BeanFactory 接口是 IOC 容器要实现的最基础的接口,定义了管理 bean 的最基本的方法,例如获取实例、类型、基本的判断等

ApplicationContext也间接继承了BeanFactory接口,在此基础上扩展了功能

  • BeanFactory是Spring基础设施,面向Spring框架本身的,在使用对象时才创建
  • ApplicationContext是面向应用的,增加了功能(支持AOP和事务),适用与Web应用程序,在服务器启动时就创建

ApplicationContext的具体实现类有ClassPathXmlApplicationContext:它可以加载类路径的配置文件,要求配置文件必须在类路径下,如果不在则加载不了

FactoryBean

实现了FactoryBean的Bean是一类叫做factory的bean。

Spring会在使用getBean()获取bean时,调用其通过getObject()重写的方法来创建bean

SpringBean的生命周期

spring Bean的作用域:scope的值

  • singleton:单例(默认),对象在工厂初始化时创建
  • prototype:原型(多例),对象在工厂初始化后创建即获取对象时创建
  • request:在Web环境下,同一次请求创建一个实例
  • session:在Web环境下,同一次会话创建一个实例
  • globalSession

Spring中的Bean可能经过5个阶段

  1. 实例化:创建一个原始的对象,例如new对象,通过反射机制实现的(框架可以读到类名)
  2. 属性赋值:为对象的属性进行赋值 在UserService中注入UserDao userDao
  3. 初始化
    1. 如果实现了BeanNameAwareBeanFactoryAwareApplicationContextAware这些接口,就要去执行这些接口的函数,来初始化我们的对象例如接口,或配置了自定义的初始化方法< bean init-method="init">
    2. 对类进行功能的提升:如果此类需要事务的增强(aop),就要在此处为bean添加功能
  4. 将完整 bean 对象创建好,放入到容器中,可以使用
  5. 销毁 如果 Bean 实现 DisposableBean 执行 destroy

在这里插入图片描述
在这里插入图片描述

Spring中的bean是线程安全的吗?

要看Spring 的 bean 作用域(scope)类型

  • singleton(单例):多个线程共享一个对象,不一定是线程安全的
  • prototype(原型):每次使用时都会创建一个对象,是线程安全的

有状态 / 无状态

  • 有状态bean(可存储数据):线程不安全
  • 无状态bean(不会保存数据):线程安全

在这里插入图片描述

servlet是线程安全的吗? 不是
servlet对象是在服务器启动时由服务器创建,只会有一个,所以是单例的

Bean循环依赖

就是 A 对象的创建依赖了 B 对象,B 对象的创建依赖了 A 对象
在这里插入图片描述

public class Admin{
	@Autowired
	Menu menu;    //此时menu对象还可能没有被创建
}

public class Menu{
	@Autowired
	Admin admin;
}

如果不考虑 Spring,问题不存在。但是,在 Spring 中循环依赖就是一个问题了,为什么

因为spring在创建对象时要为属性自动注入值,注入时就需要查找所依赖的对象,如果此时依赖的对象还可能没有被创建,就会出现问题
在这里插入图片描述

解决方法

spring提供了一个三级缓存机制

每一个缓存可以理解为一个map容器,把不同的对象做一个临时存储

  1. 一级缓存,用于保存实例化、注入、初始化完成的 bean 实例
  2. 二级缓存,用于保存实例化完成的原始对象(不需要注入值)
  3. 三级缓存,假如这个类有需要增强的功能,就要把这个把成品对象继续放在三级缓存中去增强功能。(用于保存 bean 创建工厂,以便于后面扩展有机会创建代理对象)

解决过程
在这里插入图片描述

  1. A,B 循环依赖,先初始化 A,先暴露一个半成品 A(二级缓存)
  2. 再去初始化依赖的 B,初始化 B 时如果发现 B 依赖 A,也就是循环依赖,就注入半成品 A,之后初始化完毕 B
  3. 再回到 A 的初始化过程时就解决了循环依赖

在这里只需要一个 Map能缓存半成品 A 就行了,也就是二级缓存就够了,但是这个二级缓存存的是 Bean对象,如果这个对象存在代理,那应该注入的是代理,而不是 Bean,此时二级缓存无法及缓存 Bean,又缓存代理,因此三级缓存做到了缓存工厂 ,也就是生成代理,这我的理解:总结起来:二级缓存就能解决缓存依赖,三级缓存解决的是代理

Servlet 的过滤器与 Spring 拦截器区别

过滤器实现依赖于tomcat,请求会先到达过滤器,然后进入servlet

拦截器是spring框架内部封装的,请求先到达servlet,根据映射地址,去匹配拦截器,最终找到controller控制器

底层的实现原理不同

  • 过滤器 是基于函数回调的
  • 拦截器 则是基于 Java 的反射机制(动态代理)实现的

使用范围不同

  • 过滤器 Filter 的使用要依赖于 Tomcat 等容器,导致它只能在 web 程序中使用

触发时机不同

  • 过滤器 是在请求进入容器后,但在进入 servlet 之前进行预处理
  • 拦截器 是在请求进入 servlet 后,在进入 Controller 之前进行预处理的

拦截的请求范围不同

  • 过滤器几乎可以对所有进入容器的请求起作用
  • 拦截器只会对 Controller 中请求或访问 static 目录下的资源请求起作用

在这里插入图片描述

spring中用到了哪些设计模式

  • 单例模式:spring提供了一个获取对象的全局访问点BeanFactory,这个BeanFactory就是单例的
  • 简单工厂:根据传入一个唯一的标识来获得Bean对象,这个标识可以是类型或者名称
  • 动态代理:spring的AOP;在织入切面也就是增强方法时,AOP容器会为目标对象创建动态的创建一个代理 对象
  • 工厂方法:实现了FactoryBean接口的bean是一类叫做factory的bean;其特点是,spring会在使用getBean()调 用获得该bean时,会自动调用该bean的getObject()方法,所以返回的不是factory这个bean,而是这个 bean.getOjbect()方法的返回值

建模语言UML

类图:以可视化图形方式来表示类的结构类与类的关系,方便理解

  • +:公有的 Public
  • -:私有的 Private
  • #:受保护的 Protected
  • ~:朋友 Friendly

在这里插入图片描述

接口

在 UML 中,接口使用一个带有名称的小圆圈来进行表示。图形类接口的 UML 表示

在这里插入图片描述

类图

简化对软件系统的理解,它是系统分析与设计阶段的重要产物,也是系统编码与测试的重要模型依据

在这里插入图片描述

类和类之间的关系

依赖关系

在一个类中某个方法把另一个类当参数使用,临时性的

在这里插入图片描述

关联关系

在一个类中把另一个类当做属性

在这里插入图片描述

聚合关系

强关联关系,是整体和部分之间的关系

在这里插入图片描述

组合关系

更强烈的关联关系,一旦整体对象不存在,部分对象也将不存在

在这里插入图片描述

泛化关系

类继承类

在这里插入图片描述

实现关系

类实现接口

在这里插入图片描述

面向对象的设计原则

时提高一个软件系统的可维护性和可复用性

在这里插入图片描述
开闭原则

对扩展开放,对修改关闭

在程序需要扩展的时候,不去修改原有的代码,实现一个热插拔效果(USB接口)。

比如将功能抽象,使用抽象类和接口,具体的实现可以扩展子类,提高复用性和可维护性

里氏替换原则

在任何父类出现的地方都可以用它的子类来替换,且不影响功能(多态)

子类可以扩展父类的功能,但不去改变父类原有的功能;也就是子类继承父类时,可以新添加功能,尽力不去重写父类方法。

如果重写父类的方法,继承体系的可用性就会变差,特别是多态使用频繁时程序出错概率大

依赖倒置

高展模块不应该依赖底层模块,两者都应该依赖其抽象;
抽象不应该依赖细节,细节应该依赖抽象

比如组装电脑需要cpu、内存、硬盘等配件;我们在实现电脑类的时候,依赖是的抽象(CPU),而不是具体的类(intelcpu);

  • 在顶层不适合定义太多的功能,底层实现有可能用不到
  • 在中间设计接口,抽象类去扩展功能,底层按自己的需要去实现即可

在这里插入图片描述

单一职责

一个类只负责一个功能领域中的相应职责

便于理解,提高代码可读性

接口隔离原则

使用多个专门的接口,一个接口只做一件事,而不使用单一的总接口,客户端不应该被迫依赖它不使用的方法

迪米特原则

一个对象应当对其他对象尽可能少的了解,降低耦合

使用代理的思想,不直接去对其他类进行访问;例如明星、经纪人、公司粉丝关系;

组合/聚合复用原则

优先使用组合,使系统更灵话,其次才考虑继承,达到复用的目的

电脑类,cpu类,内存类,硬盘类,电脑需要包含其他三个,就把三个类加入到自己的属性中,而不是继承

Java设计模式

前辈们在长期开发过程中,为解决某一类问题,总结出的一种较为优化的代码设计结构,提高程序代码的复用性、扩展性、稳定性

设计模式分类

  • 创建型模式:主要描述如何创建对象(单例、原型、工厂方法、抽象工厂、建造者)
  • 结构型模式:按照某种布局建造复杂的结构(代理、适配器、桥接、装饰、外观、享元、组合)
  • 行为模式:多对象协作完成任务(模板方法、职责链、观察者、中介者、访问者、迭代器、解释器、备忘录、策略、命令、状态)

单例模式

在程序中只允许有一个对象

  • 节省内存资源
  • 保证数据内容的一致性

要创建对象

  1. 将构造方法私有化
  2. 只能在本类中创建,这样我们就可以控制数量
  3. 向外界提供一个公共的访问方法
饿汉式单例

在类加载时就会创建单例对象,不会出现线程安全问题

public class Singleton {
	   //创建 Singleton 的一个对象
	   private static Singleton instance = new Singleton();
	   //让构造函数为 private
	   private Singleton(){}
	   //获取唯一可用的对象
	   public static Singleton getInstance(){
	      return instance;
	   }
}
懒汉式单例

在类加载的时候不会去创建对象,在第一次访问时才去创建,避免资源浪费

懒汉式单例有线程安全问题,要加锁处理;

对于直接在方法加锁,所有使用该对象的时候都需要加锁获取,而大多数判断结果都是这个对象已经生成,直接那去用就行,所以给方法加锁极大影响效率

volatile + 双重检索:先通过不加锁判断是否存在实例,如果有就直接拿;如果没有,加锁去生成实例,但是拿到锁后不能直接生成对象,需要在判断一次,这个成员又是被volatile修饰的具有可见性和有序性。就能保证仅第一个线程生成对象

可见性:保证第一个线程生成实例后,后面的线程能够可见,再第二重检索判断的时候就不会再生成实例

有序性:new 这个单例实例需要三条指令。1 申请内存空间 2 调用构造方法 3 把对象的地址赋给引用变量。。但是操作系统的指令重排会 将23反过来,把半成品对象赋给了引用;其他的新来的线程可能拿到这个半成品对象;所以应该禁止指令重排

public class Singleton {
	//因为大多数都是判断完发现不为null直接返回对象的,直接给方法加锁影响性能;双重检索锁的区域小
    private static volatile Singleton singleton;
    private Singleton() {}
    public static Singleton getInstance() {
        //先判断是否有实例,有就不用加锁直接获取该实例;没有再加锁去生成实例保证唯一
        if (singleton == null) {
            //加锁让一个线程进入
            synchronized (Singleton.class) {
                //第二次检索
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

工厂方法模式

批量使用对象,将创建与使用分离

一类工厂只负责一类产品;

三种角色

  • 抽象产品:接口/抽象类 定义
  • 具体产品:实现接口/抽象类的具体实现类
  • 工厂:负责生产对象

以抽象表示具体(多态)
在这里插入图片描述

/**
 * 抽象汽车接口
 */
public interface Car {
    void show();
}

/**
 * 具体汽车类
 */
public class DZCar implements Car{
    @Override
    public void show() {
        System.out.println("DZCar");
    }
}

/**
 * 具体汽车类
 */
public class FTCar implements Car{
    @Override
    public void show() {
        System.out.println("FTCar");
    }
}

/**
 * 汽车工厂:复制生产汽车对象
 */
public class CarFactory {

    public static Car createCar(String className) {
        if(className==null){
            return null;
        }
        try {
            return (Car) Class.forName(className).newInstance();
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }
}


public class Test {
    public static void main(String[] args) {

        DZCar dzCar = (DZCar) CarFactory.createCar("com.ff.advance.test.DZCar");
        dzCar.show();

        FTCar ftCar = (FTCar) CarFactory.createCar("com.ff.advance.test.FTCar");
        ftCar.show();
    }
}

---------------------------------------
DZCar
FTCar

抽象工厂模式

围绕一个超级工厂创建其他工厂,超级工厂是其他工厂的工厂

产品族:一个品牌下的所有产品类型(华为品牌下的电脑、手机)
产品等级:多个品牌下的同种产品(华为、小米品牌下的手机)

  1. 定义电脑工产品口和手机产品接口
  2. 超级工厂接口聚合这两个接口
  3. 定义华为工厂和小米工厂的实现类分别实现超级工厂接口
  4. 客户端通过工厂接口创建各品牌的工厂(华为工厂),通过华为工厂类创建自己的产品

拓展产品族:添加一个Pad产品种类,要写一个Pad接口,让超级工厂集成他,然后再创建华为Pad和小米Pad两个具体产品类。很不友好

拓展产品等级:添加一个苹果工厂,再加苹果电脑和苹果手机实现类。友好
在这里插入图片描述

代理模式

当用户不能或不想直接去访问目标对象,可以通过一个中间代理商在用户和目标之间起到了一个中间作用

例如我去买车,依照迪米特原则,我应该更少的直接去了解汽车厂,而是去找4S店,由中介来了解双方的信息,做到牵线的作用

  • 防止直接去访问目标对象,保护了目标对象
  • 可以对目标的功能进行扩展
  • 将用户和目标对象分离,降低耦合度

结构

  • 抽象主题类:通过接口定义真实主题的规范 (汽车接口)
  • 真实主题类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。(奥迪汽车)
  • 代理类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能(4S店)

在这里插入图片描述

静态代理

一般使用于关系是固定的,代理某类事务就必须实现其接口

如果要代理多个目标对象,就需要实现更多的接口,后期维护比较麻烦,违背开闭原则

/**
  Dao接口,定义保存功能
 */
public interface BaseDao {
	void save();
}
---------------------------------------------------
/**
  实际功能实现类
 */
public class UserDaoImpl implements BaseDao {
	    @Override
	    public void save() {
	        System.out.println("UserDaoImpl:save()");
	    }
}
-----------------------------------------------------
/**
 * 静态代理类
 */
public class StaticDaoProxy implements BaseDao {
	//接收所有实现BaseDao接口的实现类对象
	private BaseDao baseDao;
	//将被代理者的实例传进动态代理类的构造函数中
	public StaticDaoProxy(BaseDao baseDao) {
        this.baseDao = baseDao;
    }
    //代理他们实现功能,可以在调用前,调用后额外添加功能
	@Override
	public void save() {
		System.out.println("before");   //额外的扩展功能
		baseDao.save();					//调用真实目标中的方法
		System.out.println("after");	//额外的扩展功能
	}
}
--------------------------------------------------
public class Test {
	 public static void main(String[] args) {
          //把实际执行者交给代理对象管理即可
		  StaticDaoProxy subject = new StaticDaoProxy(new UserDaoImpl());
	      subject.save();
     }
}
--------------------------------------------------
before
UserDaoImpl:save()
after
动态代理

动态代理中,代理类并不是在 Java 代码中实现,而是在运行时期生成,相比静态代理,动态代理可以很方便的对委托类的方法进行统一处理,如添加方法调用次数、添加日志功能等等

jdk 代理

实现原理:java反射机制,动态获取目标类中代理的方法,动态生成代理对象,jdk代理要求目标类必须实现其接口,而代理类不需要实现那些接口,代理扩展性好

  1. 必须定义一个功能接口(抽象主题类)
  2. 写一个真实主题类(被代理的类)
  3. 创建一个动态代理类,生成这个类要传入被代理的类的实例,实现InvocationHandler接口,并重写该 invoke(),这个invoke方法会获取我们要执行的方法,并对其进行增强
  4. 生成代理对象,然后执行一个方法的时候就去执行invoke方法
/**
 * 抽象主题类
 */
public interface Car {
    void show();
}
----------------------------------------------
/**
 * 真实主题类(被代理的类)
 */
public class BZCar implements Car {
    @Override
    public void show() {
        System.out.println("BZCar show");
    }
}
----------------------------------------------
/**
 * 代理类
 */
public class DtProxy implements InvocationHandler {
    //被代理类实例
    Object object;
    public DtProxy(Object object) {
        this.object = object;
    }
    /**
     * 通过传入被代理对象 需要执行的方法,在此方法上进行增强
     * @param method 获取需要代理的方法
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("pre show");
        Object invoke = method.invoke(object);
        System.out.println("over show");
        return invoke;
    }
}
---------------------------------------------
public class Test {
    public static void main(String[] args) {
        BZCar bzCar = new BZCar();
        //获取我们写的代理对象
        InvocationHandler dtProxy = new DtProxy(bzCar);
        //真正生成动态代理对象
        Car car = (Car) Proxy.newProxyInstance(DtProxy.class.getClassLoader(),bzCar.getClass().getInterfaces(), dtProxy);
        //实际执行的是代理类的invoke方法
        car.show();
    }
}
Cglib 代理

使用动态字节码技术

可以在运行中为目标类动态的生成一个子类,进行方法拦截,从而增强功能

不能代理final修饰的类,目标类可以不实现任何接口

其他设计模式

原型模式

使用复制(克隆)的方式来创建对象,省去了对象创建的一系列流程,提高了效率

模板方法模式

基于继承进行代码复用

做几件事情的步骤差不多,只有细节有所不同;在一个方法中定义一个骨架,将一些步骤由子类实现;可以在不改变算法结构的情况下重新定义某些步骤。

AQS提供模板方法,具体的实现由子类继承重写

责任链模式

请求要访问资源要经过多个对象组成的责任链

Sentinel内部创建了一个责任链,也就是不同的Slot对象,每个Slot对象负责不同的功能,只有通过这些对象的校验,才能真正访问资源

观察者模式

对象之间有依赖关系,一个对象的状态改变,它的所有依赖者都会收到通知

Spring中的ApplicationListener事件监听,实现ApplicationContext的事件处理

注解

Java 注解(Annotation)又称 Java 标注,为类,方法,属性,包进行标注

可以被编译到字节码文件中 ,运行时可以通过反射机制获取到注解标签.

内置的注解

java中已经定义好的注解标记

  • @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
  • @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
  • @SuppressWarnings - 指示编译器去忽略注解中声明的警告
元注解 (注解的注解)
  • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入 class 文件中,或者是在 运行时可以通过反射访问
    1. SOURCE:在源文件中有效(即源文件保留)
    2. CLASS:在 class 文件中有效(即 class 保留)
    3. RUNTIME:在运行时有效(即运行时保留)
  • @Target - 标记这个注解应该是哪种 Java 成员
    1. ElementType.TYPE 可以应用于类的任何元素
    2. ElementType.CONSTRUCTOR 可以应用于构造函数
    3. ElementType.FIELD 可以应用于字段或属性
    4. ElementType.LOCAL_VARIABLE 可以应用于局部变量
    5. ElementType.METHOD 可以应用于方法级注释
    6. ElementType.PACKAGE 可以应用于包声明
    7. ElementType.PARAMETER 可以应用于方法的参数

自定义注解标签

@Target(ElementType.FIELD)//用在属性上
@Retention(RetentionPolicy.RUNTIME)//运行时检测
public @interface NotNull {

	String message() default "";  //注解属性
	
	int length() default 0;
	
	String lengthmessage() default "";
}


public class User {
	
	private int num;

	@NotNull(message="姓名不能为空",length=3,lengthmessage="长度不能小于3")
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getNum() {
		return num;
	}

	public void setNum(int num) {
		this.num = num;
	}

}


public class Test {

	  public static void main(String[] args) throws NoSuchMethodException, SecurityException, Exception {
		  User user = new User();
		      user.setName("ji");

		  //通过反射机制获取类的信息
		  Field[] fields = user.getClass().getDeclaredFields();
		  for (Field field : fields) {
		  		//获得此属性上 名为NotNull 的注解标签
	            NotNull notNull = field.getAnnotation(NotNull.class);
	            if (notNull != null) {
	            	//通过属性名获取到属性的get方法
	                Method m = user.getClass().getMethod("get" + getMethodName(field.getName()));
	                //获得属性的值
	                Object obj=m.invoke(user);
	                if (obj==null) {   //值为空,获取注解信息
	                    System.err.println(field.getName() +notNull.message());
	                    throw new NullPointerException(notNull.message());
	                }else{
	                	if(String.valueOf(obj).length()<(notNull.length())){
	                		 System.err.println(field.getName() +notNull.lengthmessage());
	                	}
	                }
	            }
	        }
	   }

		/**
		 * 把一个字符串的第一个字母大写
		 */
	    private static String getMethodName(String fildeName) throws Exception {
	        byte[] items = fildeName.getBytes();
	        items[0] = (byte) ((char) items[0] - 'a' + 'A');
	        return new String(items);
	    }
}
---------------------------------------------------------
name长度不能小于3

对象克隆

复制对象,将一个对象的数据复制到另一个对象中去

实现方式

1.在 Java 语言中,通过覆盖 Object 类的 clone()方法可以实现浅克隆。

2.在 spring 框架中提供 BeanUtils.copyProperties(source,target)

浅克隆

​ 如果一个对象中关联了其他的引用变量, 浅克隆时,只会将关联的对象的引用地址复制出来,并没有创建一个新的对象.

深克隆

​ 如果一个对象中关联了其他的引用变量, 深克隆时,将此对象中所关联的对象也会进行克隆操作,也就是会创建一个新的关联对象

如何实现深克隆

  • 多层次克隆 在关联的类中继续克隆

    public class Address  implements Cloneable{
    
         String  address;
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    
        @Override
        public String toString() {
            return "Address{" +
                    "address='" + address + '\'' +
                    '}';
        }
    
        @Override
        protected Address clone() throws CloneNotSupportedException {
            return (Address)super.clone();
        }
    }
    
    
    
    public class Person implements  Cloneable{
    
         int num;
         String name;
         Address address;
    
        public Person() {
        }
    
        public Person(int num, String name) {
            this.num = num;
            this.name = name;
        }
    
        public int getNum() {
            return num;
        }
    
        public void setNum(int num) {
            this.num = num;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Address getAddress() {
            return address;
        }
    
        public void setAddress(Address address) {
            this.address = address;
        }
    
        @Override
        protected Person clone() throws CloneNotSupportedException {
            Person person = (Person)super.clone();
            person.address = (Address)address.clone();   //深度复制  联同person中关联的对象也一同克隆.
            return person;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "num=" + num +
                    ", name='" + name + '\'' +
                    ", address=" + address +
                    '}';
        }
    }
    
    
    
    public class Test {
    
        public static void main(String[] args) throws CloneNotSupportedException {
    
            Address address = new Address();
                    address.setAddress("汉中");
    
            Person p1 = new  Person(100,"jim");
                   p1.setAddress(address);
    
            Person p2 =p1.clone();
                   p2.setName("tom");
                   address.setAddress("西安");
    
            System.out.println(p1);
            System.out.println(p2);
        }
    }
    -----------------------------------------
    Person{num=100, name='jim', address=Address{address='西安'}}
    Person{num=100, name='tom', address=Address{address='汉中'}}
    
  • 使用序列化和反序列化的方式实现(反序列化也是生成对象的一种方式)

    public class Address  implements Serializable {
    
         String  address;
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    
        @Override
        public String toString() {
            return "Address{" +
                    "address='" + address + '\'' +
                    '}';
        }
    }
    
    
    
    public class Person implements Serializable {
    
         int num;
         String name;
         Address address;
    
        public Person() {
        }
    
        public Person(int num, String name) {
            this.num = num;
            this.name = name;
        }
    
        public int getNum() {
            return num;
        }
    
        public void setNum(int num) {
            this.num = num;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Address getAddress() {
            return address;
        }
    
        public void setAddress(Address address) {
            this.address = address;
        }
    
        /**
         * 自定义克隆方法
         * @return
         */
        public Person myclone() {
                Person person = null;
                  try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
                         ByteArrayOutputStream baos = new ByteArrayOutputStream();
                          ObjectOutputStream oos = new ObjectOutputStream(baos);
                          oos.writeObject(this);
                // 将流序列化成对象
                        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                         ObjectInputStream ois = new ObjectInputStream(bais);
                         person = (Person) ois.readObject();
                      } catch (IOException e) {
                         e.printStackTrace();
                      } catch (ClassNotFoundException e) {
                         e.printStackTrace();
                     }
                 return person;
              }
    
    
        @Override
        public String toString() {
            return "Person{" +
                    "num=" + num +
                    ", name='" + name + '\'' +
                    ", address=" + address +
                    '}';
        }
    }
    
    
    
    public class Test {
    
        public static void main(String[] args) throws CloneNotSupportedException {
    
            Address address = new Address();
                    address.setAddress("汉中");
    
            Person p1 = new  Person(100,"jim");
            p1.setAddress(address);
    
            Person p2 =p1.myclone();
                   p2.setName("tom");
                   address.setAddress("西安");
    
            System.out.println(p1);
            System.out.println(p2);
        }
    }
    ------------------------------------------------------------------
    Person{num=100, name='jim', address=Address{address='西安'}}
    Person{num=100, name='tom', address=Address{address='汉中'}}
    
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EnndmeRedis

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值