解决List的add方法错使前面的元素被覆盖成相同值

来源:互联网 发布:做淘宝直播如何赚钱 编辑:程序博客网 时间:2024/04/30 01:03

今天数据结构讲到栈和队列,无意中灵感突发,想到了给之前开发的2048小游戏加一个反悔的功能,并且可以反悔任意多步数(内存允许的情况下)。
反悔其实就类似于编辑类软件的撤销功能,不过这个撤销队列在数据操作上有自己的特征。
首先,这个队列大小是固定的,如下图,在这里它的长度是6,按照0~5的顺序进队,队满之后,若要再进队,则必须移除队首的元素(即0),我们可以看成是后来者挤掉最前者。
这里写图片描述

然后在取出元素的时候,和栈有点类似,是从队尾的元素开始取(因为撤销是返回最近的一次操作)。

明确思路后,我便开始写逻辑。
在GameView(继承自LinearLayout)中定义了一个卡片数组:

private int[][] cards_map = new int[Config.LINES][Config.LINES];

然后在滑动事件当中使用事先写好的set方法存储card_map对象进队:

private void swipeLeft() {        // 循环获取卡片移动之前的状态        for (int x = 0; x < Config.LINES; ++x) {            for (int y = 0; y < Config.LINES; ++y) {                cards_map[x][y] = cardsMap[x][y].getNum();            }        }        cardsMapManager.setCards_map(cards_map);……}

当然,这个存储是Application级别的临时存储,退出应用后将消失。那么,我们就能在MainActivity当中的反悔按钮的监听里去调用事先写好的get方法:

backGame.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    int temp_score = cardsMapManager.getScore();                    if (temp_score != -1) {                        score = temp_score;                        tvScore.setText(score + "");                    }                    int[][] temp_maps = cardsMapManager.getCards_map();                    // 此处必须先定义临时变量来存储get到的数组,否则在if判断和resumeGame的参数里直接写get方法会造成get内部语句操作多次,这显然是不允许的                    if (temp_maps != null)                        gameView.resumeGame(temp_maps);                    else {                        backGame.setEnabled(false);                        Toast.makeText(MainActivity.this, "反悔太多啦!", Toast.LENGTH_SHORT).show();                    }                }            });

下面,我们就来看set和get方法的实现:在CardsMapManager(继承自Application)中,默认实例化list(这里是关键的必须的步骤),

private List<int[][]> cards_map_list = new ArrayList<>();
public int[][] getCards_map() {        if (cards_map_list.size() == 0)            return null;        else {            int last = cards_map_list.size() - 1;            int[][] temp_maps = cards_map_list.get(last);            cards_map_list.remove(last);            return temp_maps;        }    }
public void setCards_map(int[][] cards_map) {        if (cards_map_list.size() < Config.LIST_SIZE)            cards_map_list.add(cards_map);        else {            for (int i = 0; i < cards_map_list.size() - 1; ++i)                cards_map_list.set(i , cards_map_list.get(i + 1));            cards_map_list.set(cards_map_list.size() - 1, cards_map);        }    }

其实set和get的逻辑都是没有问题的,毕竟逻辑处理我还打了草稿。
但最后会发现一个百思不得其解的问题,那就是cards_map_list.add(cards_map)方法始终会覆盖之前add的数据,造成整个list的每一项元素都一样。

经过几番调试后,我发现了不是我逻辑的问题,而是所传对象cards_map实例化的问题,我们回过头去看最开始那两块代码,一个是在GameView里初始化定义的二维数组,一个是滑动事件里的循环赋值,关键就在这。
查阅到了一段资料:

很有可能是你加入的元素是某个bean,且这个bean被你在调用的类中定义成域成员,并实例化了。这时候你再循环改变bean里的内容add到list中,那么根据java的对象存的是地址的原理,你的list里全是那个实例化bean的地址,最后元素值就全都一样,并且取的是最后一次改变的值。

意思就是,我之前只在初始化那里定义了这个二维数组,在swipe内部方法里并没有进行新的定义,而是反复使用初始化定义,所以造成了静态值覆盖的错误,没有达到预期效果。
所以,解决办法就是在每一个swipe(left、right、up、down)里都实例化新的数组:

private void swipeLeft() {        // 此处非常关键,如果不重新实例化会造成每次List的add方法覆盖前面全部元素        cards_map = new int[Config.LINES][Config.LINES];        for (int x = 0; x < Config.LINES; ++x) {            for (int y = 0; y < Config.LINES; ++y) {                cards_map[x][y] = cardsMap[x][y].getNum();            }        }        cardsMapManager.setCards_map(cards_map);……}

如此一来,什么都搞定了。

1 0
原创粉丝点击