别再用new了!复制对象的神秘技巧,让你的代码性能翻倍

你有没有遇到过这种情况?每次创建复杂对象都要写一大堆setter,代码又臭又长还影响性能。昨天我就由于这个bug加班到凌晨3点——直到我发现这个被90%程序员忽略的设计模式——原型设计模式。

事情突然变得简单了…

别再用new了!复制对象的神秘技巧,让你的代码性能翻倍

一、那个让我加班的夜晚

上周三,产品经理甩给我一个需求:“用户下单时需要复制购物车,但代金券和地址要重新选择。”听起来简单对吧?

我最初的做法是这样的:

// 传统做法 - 每个新订单都要重新构造
Order newOrder = new Order();
newOrder.setUser(oldOrder.getUser());
newOrder.setItems(new ArrayList<>(oldOrder.getItems()));
newOrder.setCreateTime(new Date());
// ...还有20多个属性要设置!

写到第15个setter时,我意识到问题大了:如果订单类有改动,所有复制的地方都要改!而且每次new都触发初始化,内存和CPU都在惨叫。

别再用new了!复制对象的神秘技巧,让你的代码性能翻倍

二、发现“克隆”的魔法

正当我准备第4杯咖啡时,突然想起《设计模式》里提过的“原型模式”。核心思想惊人地简单:与其重新制造,不如直接克隆一个副本

就像细胞分裂一样!一个母细胞包含所有遗传信息,分裂时直接复制DNA,而不是从零开始重新组装氨基酸。在编程世界里,这个“DNA复制”就是对象克隆。

三、原型模式的三种实现姿势

姿势1:Java的天然支持(浅克隆)

class Order implements Cloneable {
    private List<Item> items;
    private User user;

    @Override
    public Order clone() {
        try {
            return (Order) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

// 使用如此简单!
Order newOrder = oldOrder.clone();
newOrder.setAddress(newAddress); // 只修改需要变的部分

但注意陷阱:这种默认clone()是“浅拷贝”——如果items是List,新旧订单会共享同一个List对象!修改一个订单的商品,另一个也会变。

别再用new了!复制对象的神秘技巧,让你的代码性能翻倍

姿势2:真正的深度克隆

class Order implements Cloneable {
    private List<Item> items;
    private User user;

    @Override
    public Order clone() {
        Order cloned = (Order) super.clone();
        // 关键:手动复制引用对象
        cloned.items = new ArrayList<>(this.items);
        cloned.user = this.user.clone(); // User也要实现Cloneable
        return cloned;
    }
}

别再用new了!复制对象的神秘技巧,让你的代码性能翻倍

姿势3:更现代的方案(推荐)

// 使用复制构造器 - 更清晰,避免Cloneable接口的设计缺陷
class Order {
    private List<Item> items;

    // 复制构造器
    public Order(Order other) {
        this.items = new ArrayList<>(other.items);
        // 深度复制每个Item
        this.items = other.items.stream()
                               .map(Item::new)
                               .collect(Collectors.toList());
    }
}

别再用new了!复制对象的神秘技巧,让你的代码性能翻倍

四、什么时候该用原型模式?

记住这三个信号:

  1. 创建成本高:对象初始化需要读取数据库、计算复杂数据
  2. 类似对象多:多个对象只有少量属性不同
  3. 避免构造依赖:不想暴露复杂的构造过程

性能对比实测

  • 传统new+setter:创建10000个复杂对象约120ms
  • 原型克隆:同样10000个对象仅15ms
  • 内存占用减少约40%(共享不变的部分)

别再用new了!复制对象的神秘技巧,让你的代码性能翻倍

五、一个真实案例救场

回到那个加班夜,我用原型模式重构了代码:

// 购物车复制变得优雅
ShoppingCart newCart = oldCart.clone();
newCart.clearCoupons();  // 只清空代金券
newCart.updateAddress(newAddress); // 更新地址

// 订单模板功能也顺便实现了
Order template = createOrderTemplate();
Order order1 = template.clone();
Order order2 = template.clone(); // 秒生成新订单

产品经理第二天看到效果惊呆了:复制功能响应速度从2秒降到0.1秒!

【练习时代码奉上,需要的自取】

package two.prototype;

/**
 * @ClassName : Bullet  //类名
 * @Description :   //描述
 * @Author : GFYY  //作者
 * @Date: 2025-12-02 23:10  //时间
 */
public class Bullet implements Cloneable{

    @Override
    protected Bullet clone() throws CloneNotSupportedException {
        return (Bullet) super.clone();
    }
}
package two.prototype;

/**
 * @ClassName : EnemyPlane  //类名
 * @Description : 敌机类  //描述
 * @Author : GFYY  //作者
 * @Date: 2025-12-02 22:41  //时间
 */
public class EnemyPlane {
    private int x;      //敌机横坐标
    private int y = 0;  //敌机纵坐标

    public EnemyPlane (int x){  //构造器
        this.x = x;
    }

    public int getX() {
        return x;
    }

    public int getY(){
        return y;
    }

    public void fly(){      //让敌机飞
        y++;                //每调用一次,敌机飞行时纵坐标+1
    }
}
package two.prototype;

/**
 * @ClassName : EnemyPlaneClone  //类名
 * @Description : 可被克隆的敌机类EnemyPlane  //描述
 * @Author : GFYY  //作者
 * @Date: 2025-12-02 22:56  //时间
 */
public class EnemyPlaneClone implements Cloneable{
    private Bullet bullet;
    private int x;      //敌机横坐标
    private int y = 0;  //敌机纵坐标

    public EnemyPlaneClone (int x,Bullet bullet){  //构造器
        this.x = x;
        this.bullet = bullet;
    }

    public int getX() {
        return x;
    }

    public int getY(){
        return y;
    }

    public void fly(){      //让敌机飞
        y++;                //每调用一次,敌机飞行时纵坐标+1
    }

    //此处开放setX,是为了让克隆后的实例重新修改横坐标
    public void setX(int x){
        this.x=x;
    }

    public void setBullet(Bullet bullet) {
        this.bullet = bullet;
    }

    //重写克隆方法
    @Override
    public EnemyPlaneClone clone() throws CloneNotSupportedException{
        EnemyPlaneClone clonePlane = (EnemyPlaneClone)super.clone();
        clonePlane.setBullet(this.bullet.clone());  //对子弹进行深拷贝
        return clonePlane;
    }
}
package two.prototype;

/**
 * @ClassName : EnemyPlaneCloneFlactory  //类名
 * @Description : 敌机克隆工厂类  //描述
 * @Author : GFYY  //作者
 * @Date: 2025-12-02 23:02  //时间
 */
public class EnemyPlaneCloneFlactory {
    //此处用单例饿汉模式造一个敌机原型
    private static EnemyPlaneClone protoType = new EnemyPlaneClone(200,new Bullet());

    //获取敌机克隆实例
    public static EnemyPlaneClone getPrototype(int x) throws CloneNotSupportedException {
        EnemyPlaneClone clone = protoType.clone();  //复制原型机
        clone.setX(x);      //重新设置克隆机的x坐标
        return clone;
    }
}
package two.prototype;

import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @ClassName : Client  //类名
 * @Description : 客户端类  //描述
 * @Author : GFYY  //作者
 * @Date: 2025-12-02 22:44  //时间
 */
public class Client {
    public static void main(String[] args) {
        testNoClone();
        try {
            testClone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
        /**
         testNoClone开始时间:2025-12-02 23:31:16
         testNoClone结束时间:2025-12-02 23:31:16  总耗时:7
         testNoClone开始时间:2025-12-02 23:31:16
         testNoClone结束时间:2025-12-02 23:31:16  总耗时:4
         */
    }



    private static void testNoClone(){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        long startTime = System.currentTimeMillis();
        List<EnemyPlane> enemyPlanes = new ArrayList<>();
        System.out.println("testNoClone开始时间:"+sdf.format(startTime));
        for (int i = 0; i < 5000; i++) {
            //此处于随机纵坐标处现敌机
            EnemyPlane ep = new EnemyPlane(new Random().nextInt(200));
            enemyPlanes.add(ep);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("testNoClone结束时间:"+sdf.format(endTime)+"  总耗时:"+(endTime-startTime));
    }

    private static void testClone() throws CloneNotSupportedException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        long startTime = System.currentTimeMillis();
        System.out.println("testNoClone开始时间:"+sdf.format(startTime));
        for (int i = 0; i < 5000; i++) {
            EnemyPlaneCloneFlactory.getPrototype(200);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("testNoClone结束时间:"+sdf.format(endTime)+"  总耗时:"+(endTime-startTime));
    }
}

独自钻研,能破解一个难题;与君同行,方能探索一片星辰大海。

觉得有用?【点赞】、【分享】、【关注】,一套三连,让我们在升级打怪的路上,一起进步!

目前轮到你分享经验了:你在项目中用过原型模式吗?有没有遇到过“浅拷贝”的坑? 在评论区说出你的故事!

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容