• 通过与C++程序对比,彻底搞清楚JAVA的对象拷贝


    一、背景

    JAVA编程中的对象一般都是通过new进行创建的,新创建的对象通常是初始化的状态,但当这个对象某些属性产生变更,且要求用一个对象副本来保存当前对象的“状态”,这时候就需要用到对象拷贝的功能,以便封装对象之间的快速克隆。

    二、JAVA对象拷贝的实现

    2.1 浅拷贝
    • 被复制的类需要实现Clonenable接口;
    • 覆盖clone()方法,调用super.clone()方法得到需要的复制对象;
    • 浅拷贝对基本类型(boolean,char,byte,short,float,double.long)能完成自身的复制,但对于引用类型只对引用地址进行拷贝。
      -- 下面我们用一个实例进行验证:
      在这里插入图片描述
    /**
     * 单只牌
     *
     * @author zhuhuix
     * @date 2020-06-10
     */
    public class Card implements Comparable, Serializable,Cloneable {
    
        // 花色
        private String color = "";
        //数字
        private String number = "";
    
        public Card() {
        }
    
        public Card(String color, String number) {
            this.color = color;
            this.number = number;
        }
    
        public String getColor() {
            return this.color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
    
        public String getNumber() {
            return this.number;
        }
    
        public void setNumber(String number) {
            this.number = number;
        }
    
        @Override
        public String toString() {
            return this.color + this.number;
        }
    
        @Override
        public int compareTo(Object o) {
            if (o instanceof Card) {
                int thisColorIndex = Constant.COLORS.indexOf(this.getColor());
                int anotherColorIndex = Constant.COLORS.indexOf(((Card) o).getColor());
                int thisNumberIndex = Constant.NUMBERS.indexOf(this.getNumber());
                int anotherNumberIndex = Constant.NUMBERS.indexOf(((Card) o).getNumber());
    
                // 大小王之间相互比较: 大王大于小王
                if ("JOKER".equals(this.color) && "JOKER".equals(((Card) o).getColor())) {
                        return thisColorIndex > anotherColorIndex ? 1 : -1;
                }
    
                // 大小王与数字牌之间相互比较:大小王大于数字牌
                if ("JOKER".equals(this.color) && !"JOKER".equals(((Card) o).getColor())) {
                    return 1;
                }
                if (!"JOKER".equals(this.color) && "JOKER".equals(((Card) o).getColor())) {
                    return -1;
                }
    
                // 数字牌之间相互比较: 数字不相等,数字大则牌面大;数字相等 ,花色大则牌面大
                if (thisNumberIndex == anotherNumberIndex) {
                    return thisColorIndex > anotherColorIndex ? 1 : -1;
                } else {
                    return thisNumberIndex > anotherNumberIndex ? 1 : -1;
                }
    
            } else {
                return -1;
            }
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    /**
     * 扑克牌常量定义
     *
     * @author zhuhuix
     * @date 2020-06-10
     */
    public class Constant {
    
        // 纸牌花色:黑桃,红心,梅花,方块
        final static List<String> COLORS = new ArrayList<>(
                Arrays.asList(new String[]{"♢", "♣", "♡", "♠"}));
        // 纸牌数字
        final static List<String> NUMBERS = new ArrayList<>(
                Arrays.asList(new String[]{"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"}));
        // 大王小王
        final static List<String> JOKER = new ArrayList<>(
                Arrays.asList(new String[]{"小王","大王"}));
    }
    
    /**
     * 整副副扑克牌
     *
     * @author zhuhuix
     * @date 2020-06-10
     */
    public class Poker implements Cloneable, Serializable {
    
        private List<Card> cards;
    
        public Poker() {
            List<Card> cardList = new ArrayList<>();
            // 按花色与数字组合生成52张扑克牌
            for (int i = 0; i < Constant.COLORS.size(); i++) {
                for (int j = 0; j < Constant.NUMBERS.size(); j++) {
                    cardList.add(new Card(Constant.COLORS.get(i), Constant.NUMBERS.get(j)));
                }
            }
            // 生成大小王
            for (int i = 0; i < Constant.JOKER.size(); i++) {
                cardList.add(new Card("JOKER", Constant.JOKER.get(i)));
            }
    
            this.cards = cardList;
        }
    
       
        // 从整副扑克牌中抽走大小王
        public void removeJoker() {
            Iterator<Card> iterator = this.cards.iterator();
            while (iterator.hasNext()) {
                Card cardJoker = iterator.next();
                if (cardJoker.getColor() == "JOKER") {
                    iterator.remove();
                }
            }
        }
    
        public List<Card> getCards() {
            return cards;
        }
    
        public void setCards(List<Card> cards) {
            this.cards = cards;
        }
    
        public Integer getCardCount() {
            return this.cards.size();
        }
    
        @Override
        public String toString() {
            StringBuilder poker = new StringBuilder("[");
            Iterator<Card> iterator = this.cards.iterator();
            while (iterator.hasNext()) {
                poker.append(iterator.next().toString() + ",");
            }
            poker.setCharAt(poker.length() - 1, ']');
            return poker.toString();
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    
    /**
     * 测试程序
     *
     * @author zhuhuix
     * @date 2020-6-10
     */
    public class PlayDemo {
    
        public static void main(String[] args) throws CloneNotSupportedException {
    
            // 生成一副扑克牌并洗好牌
            Poker poker1 = new Poker();
            System.out.println("新建:第一副牌共 "+poker1.getCardCount()+" 张:"+poker1.toString());
    
            Poker poker2= (Poker) poker1.clone();
            System.out.println("第一副牌拷页生成第二副牌,共 "+poker2.getCardCount()+" 张:"+poker2.toString());
    
            poker1.removeJoker();
    
            System.out.println("====第一副牌抽走大小王后====");
            System.out.println("第一副牌还有 "+poker1.getCardCount()+" 张:"+poker1.toString());
            System.out.println("第二副牌还有 "+poker2.getCardCount()+" 张:"+poker2.toString());
    
        }
    
    }
    
    • 运行结果:
      -- 在第一副的对象中抽走了“大小王”,克隆的第二副的对象的“大小王”竟然也被“抽走了”
      在这里插入图片描述
      在这里插入图片描述
    2.2 深拷贝的实现方法一
    • 被复制的类需要实现Clonenable接口;
    • 覆盖clone()方法,自主实现引用类型成员的拷贝复制。
      -- 我们只要改写一下Poker类中的clone方法,让引用类型成员实现复制:
    /**
     * 整副副扑克牌--自主实现引用变量的复制
     *
     * @author zhuhuix
     * @date 2020-06-10
     */
    public class Poker implements Cloneable, Serializable {
    
        private List<Card> cards;
    
        public Poker() {
            List<Card> cardList = new ArrayList<>();
            // 按花色与数字组合生成52张扑克牌
            for (int i = 0; i < Constant.COLORS.size(); i++) {
                for (int j = 0; j < Constant.NUMBERS.size(); j++) {
                    cardList.add(new Card(Constant.COLORS.get(i), Constant.NUMBERS.get(j)));
                }
            }
            // 生成大小王
            for (int i = 0; i < Constant.JOKER.size(); i++) {
                cardList.add(new Card("JOKER", Constant.JOKER.get(i)));
            }
    
            this.cards = cardList;
        }
    
        // 从整副扑克牌中抽走大小王
        public void removeJoker() {
            Iterator<Card> iterator = this.cards.iterator();
            while (iterator.hasNext()) {
                Card cardJoker = iterator.next();
                if (cardJoker.getColor() == "JOKER") {
                    iterator.remove();
                }
            }
        }
    
        public List<Card> getCards() {
            return cards;
        }
    
        public void setCards(List<Card> cards) {
            this.cards = cards;
        }
    
        public Integer getCardCount() {
            return this.cards.size();
        }
    
        @Override
        public String toString() {
            StringBuilder poker = new StringBuilder("[");
            Iterator<Card> iterator = this.cards.iterator();
            while (iterator.hasNext()) {
                poker.append(iterator.next().toString() + ",");
            }
            poker.setCharAt(poker.length() - 1, ']');
            return poker.toString();
        }
    
    	// 遍历原始对象的集合,对生成的对象进行集合复制
        @Override
        protected Object clone() throws CloneNotSupportedException {
            Poker newPoker = (Poker)super.clone();
            newPoker.cards = new ArrayList<>();
            newPoker.cards.addAll(this.cards);
            return newPoker;
        }
    }
    
    
    • 输出结果:
      -- 通过自主实现引用类型的复制,原对象与对象的拷贝的引用类型成员地址不再关联
      在这里插入图片描述
    2.3 深拷贝的实现方法二
    • 在用第二种方式实现JAVA深拷贝之前,我们首先对C++程序的对象拷贝做个了解:
    2.3.1 C++拷贝构造函数

    C++拷贝构造函数,它只有一个参数,参数类型是本类的引用,且一般用const修饰
    在这里插入图片描述

    2.3.2 C++源码
    // 单只牌的类定义
    // Created by Administrator on 2020-06-10.
    //
    
    #ifndef _CARD_H
    #define _CARD_H
    
    #include <string>
    
    using namespace std;
    
    class Card {
    private :
        string color;
        string number;
    public:
        Card();
    
        Card(const string &color, const string &number);
    
        const string &getColor() const;
    
        void setColor(const string &color);
    
        const string &getNumber() const;
    
        void setNumber(const string &number);
    
        string toString();
    
    };
    
    
    #endif //_CARD_H
    
    // 单只牌类的实现
    // Created by Administrator on 2020-06-10.
    //
    
    #include "card.h"
    
    Card::Card(){}
    
    Card::Card(const string &color, const string &number) : color(color), number(number) {}
    
    const string &Card::getColor() const {
        return color;
    }
    
    void Card::setColor(const string &color) {
        Card::color = color;
    }
    
    const string &Card::getNumber() const {
        return number;
    }
    
    void Card::setNumber(const string &number) {
        Card::number = number;
    }
    
    
    string Card::toString() {
        return getColor()+getNumber();
    }
    
    
    
    
    
    // 扑克牌类的定义
    // Created by Administrator on 2020-06-10.
    //
    
    #ifndef _POKER_H
    #define _POKER_H
    
    #include <vector>
    #include "card.h"
    
    using namespace std;
    
    const int COLOR_COUNT=4;
    const int NUMBER_COUNT=13;
    const int JOKER_COUNT=2;
    
    const string COLORS[COLOR_COUNT] = {"♢", "♣", "♡", "♠"};
    const string NUMBERS[NUMBER_COUNT]={"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
    const string JOKER[JOKER_COUNT] ={"小王","大王"};
    
    class Poker {
    private:
        vector<Card> cards;
    public:
        Poker();
    
        Poker(const Poker &poker);
    
        const vector<Card> &getCards() const;
    
        void setCards(const vector<Card> &cards);
    
        int getCardCount();
    
        void toString();
    
        void clear();
    };
    
    
    #endif //_POKER_H
    
    // 扑克牌类的实现
    // Created by zhuhuix on 2020-06-10.
    //
    
    #include "Poker.h"
    #include <iostream>
    
    const vector<Card> &Poker::getCards() const {
        return this->cards;
    }
    
    void Poker::setCards(const vector<Card> &cards) {
        Poker::cards = cards;
    }
    
    // 构造函数
    Poker::Poker() {
        for (int i = 0; i < NUMBER_COUNT; i++) {
            for (int j = 0; j < COLOR_COUNT; j++) {
                this->cards.emplace_back(COLORS[j], NUMBERS[i]);
            }
        }
        for (int i = 0; i < JOKER_COUNT; i++) {
            this->cards.emplace_back("JOKER", JOKER[i]);
        }
    }
    
    // 拷贝构造函数
    Poker::Poker(const Poker &poker) {
        for (int i = 0; i < poker.getCards().size(); i++) {
            this->cards.emplace_back(poker.cards[i].getColor(), poker.cards[i].getNumber());
        }
    }
    
    int Poker::getCardCount() {
        return this->cards.size();
    }
    
    void Poker::toString() {
        cout << "共" << getCardCount() << "张牌:";
        cout << "[";
        for (int i = 0; i < this->cards.size(); i++) {
            cout << this->cards[i].toString();
            if (i != getCardCount() - 1) {
                cout << ",";
            }
        }
        cout << "]" << endl;
    
    }
    
    void Poker::clear() {
        this->cards.clear();
    }
    
    
    // 主测试程序
    // Created by Administrator on 2020-06-10.
    //
    
    #include "Poker.h"
    #include <iostream>
    
    using namespace std;
    
    int main() {
        Poker poker1;
        cout << "第一副牌:";
        poker1.toString();
        // 通过拷贝构造函数生成第二副牌
        Poker poker2(poker1);
        cout << "第二副牌:";
        poker2.toString();
        // 清除扑克牌1
        poker1.clear();
        cout << "清空后,第一副牌:";
        poker1.toString();
        cout << "第二副牌:";
        poker2.toString();
        return 0;
    }
    
    • 输出:
      在这里插入图片描述
    2.3.3 JAVA通过拷贝构造方法实现深拷贝
    • JAVA拷贝构造方法与C++的拷贝构造函数相同,被复制对象的类需要实现拷贝构造方法:
      --首先需要声明带有和本类相同类型的参数构造方法
      --其次拷贝构造方法可以通过序列化实现快速复制
    • 拷贝对象通过调用拷贝构造方法进行创建。
      -- 我们再改写一下Poker类,实现拷贝构造方法:
    /**
     * 整副副扑克牌--实现拷贝构造方法
     *
     * @author zhuhuix
     * @date 2020-06-10
     */
    public class Poker implements Serializable {
    
        private List<Card> cards;
    
        public Poker() {
            List<Card> cardList = new ArrayList<>();
            // 按花色与数字组合生成52张扑克牌
            for (int i = 0; i < Constant.COLORS.size(); i++) {
                for (int j = 0; j < Constant.NUMBERS.size(); j++) {
                    cardList.add(new Card(Constant.COLORS.get(i), Constant.NUMBERS.get(j)));
                }
            }
            // 生成大小王
            for (int i = 0; i < Constant.JOKER.size(); i++) {
                cardList.add(new Card("JOKER", Constant.JOKER.get(i)));
            }
    
            this.cards = cardList;
        }
    
        // 拷贝构造方法:利用序列化实现深拷贝
        public Poker(Poker poker) {
    
            try {
    
                ByteArrayOutputStream os = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(os);
                oos.writeObject(poker);
    
                ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
                ObjectInputStream ois = new ObjectInputStream(is);
                this.cards = ((Poker) ois.readObject()).getCards();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
        }
    
        // 从整副扑克牌中抽走大小王
        public void removeJoker() {
            Iterator<Card> iterator = this.cards.iterator();
            while (iterator.hasNext()) {
                Card cardJoker = iterator.next();
                if (cardJoker.getColor() == "JOKER") {
                    iterator.remove();
                }
            }
        }
    
        public List<Card> getCards() {
            return cards;
        }
    
        public void setCards(List<Card> cards) {
            this.cards = cards;
        }
    
        public Integer getCardCount() {
            return this.cards.size();
        }
    
        @Override
        public String toString() {
            StringBuilder poker = new StringBuilder("[");
            Iterator<Card> iterator = this.cards.iterator();
            while (iterator.hasNext()) {
                poker.append(iterator.next().toString() + ",");
            }
            poker.setCharAt(poker.length() - 1, ']');
            return poker.toString();
        }
    }
    
    
    • 对测试主程序进行修改:
    /**
     * 测试程序
     *
     * @author zhuhuix
     * @date 2020-6-10
     */
    public class PlayDemo {
    
        public static void main(String[] args) throws CloneNotSupportedException {
    
            // 生成一副扑克牌并洗好牌
            Poker poker1 = new Poker();
            System.out.println("新建:第一副牌共 "+poker1.getCardCount()+" 张:"+poker1.toString());
    
            Poker poker2 = new Poker(poker1);
            System.out.println("第一副牌拷页生成第二副牌,共 "+poker2.getCardCount()+" 张:"+poker2.toString());
    
            poker1.removeJoker();
    
            System.out.println("====第一副牌抽走大小王后====");
            System.out.println("第一副牌还有 "+poker1.getCardCount()+" 张:"+poker1.toString());
            System.out.println("第二副牌还有 "+poker2.getCardCount()+" 张:"+poker2.toString());
    
    
            Poker poker3 = new Poker(poker1);
            System.out.println("第三副牌还有 "+poker3.getCardCount()+" 张:"+poker3.toString());
        }
    
    }
    
    • 输出结果:
      --通过序列化的有手段,同样也能实现对象的深拷贝
      在这里插入图片描述

    四、总结

    • java程序进行对象拷贝时,如果对象的类中存在引用类型时,需进行深拷贝
    • 对象拷贝可以通过实现Cloneable接口完成
    • java编程也可仿照 C++程序的拷贝构造函数,实现拷贝构造方法进行对象的复制
    • 通过序列化与反序化手段可实现对象的深拷贝
  • 相关阅读:
    Mongoexport导出数据,Mongoimport导入数据,mongodump备份数据,mongorestore恢复恢复
    php7中使用mongodb的驱动
    windows(X64)+apche2.4+php7.2下安装mongodb
    windows(X64)下安装apche2.4+php7.2+mysql5.7
    Django2.0 path与Django1.x版本url正则匹配问题
    Django: ImportError: No module named 'corsheaders'
    linux开启端口
    MySQL 存储过程传参数实现where id in(1,2,3,...)示例
    ubuntu安装pip3
    在Ubuntu 16.04 安装python3.6 环境并设置为默认
  • 原文地址:https://www.cnblogs.com/zhuhuix/p/13091177.html
Copyright © 2020-2023  润新知