利用 Map 集合的 containsKey 方法,实现对象数组的去重以及重复对象的字段值累加

来源:互联网 发布:sql大于某个时间 编辑:程序博客网 时间:2024/06/11 13:46

1. 前言

公司的项目又加了一个新需求,打印发票增加详细收费方式以及每种收费收费金额。一开始没把它当回事,想着服务端返回的支付信息里包含着各种支付记录,在打印模块里将接收到的支付信息 List 遍历一下,然后打印出来就好了。

后来做的时候发现,是我想得简单了。

因为服务端返回的支付信息是按照每笔交易记录返回的,即如果支付总额为20元,如果使用者支付了两次10元完成的支付,那么服务端存储的这笔交易的支付信息就为两次10元的支付记录。具体返回的 json 串示例如下:

    "***INFOS\": [        {            \"***NAME\": \"现金收费\",            \"***ACCOUNT\": \"7.0\",            \"***NUM\": \"1000000576\"        },        {            \"***NAME\": \"现金收费\",            \"***ACCOUNT\": \"3.0\",            \"***NUM\": \"1000000576\"        },        {            \"***NAME\": \"现金收费\",            \"***ACCOUNT\": \"5.0\",            \"***NUM\": \"1000000576\"        },        {            \"***NAME\": \"微信收费\",            \"***ACCOUNT\": \"15.0\",            \"***NUM\": \"1000000576\"        },        {            \"***NAME\": \"微信收费\",            \"***ACCOUNT\": \"8.0\",            \"***NUM\": \"1000000576\"        }    ]

可以看到,此次交易总额为38元,然后使用者分了5次完成的支付。如果我什么也不处理就开始遍历打印的话,打出的发票信息上显示的详细支付信息就是3条现金支付记录,和2条微信支付记录,只是钱数不同而已。这样是肯定不行的,所以这里要处理一下数据。

2. 思路

思路其实很简单,就是把 List 中的相同支付方式去重,然后将每笔支付金额相加,得出的总额算作是这种支付方式的支付钱数。即前文中那笔示例交易,处理后应该为:

    "***INFOS\": [        {            \"***NAME\": \"现金收费\",            \"***ACCOUNT\": \"15.0\",            \"***NUM\": \"1000000576\"        },        {            \"***NAME\": \"微信收费\",            \"***ACCOUNT\": \"23.0\",            \"***NUM\": \"1000000576\"        }    ]

好了,思路清晰了,就去完成它吧。

3. 实现

实现的部分在新建的示例工程中完成。

创建一个实体类对象:

    public class Person {        private String mName;        private String mMoney;        public Person(String pName, String pMoney) {            mName = pName;            mMoney = pMoney;        }        public String getName() {            return mName;        }        public void setName(String pName) {            mName = pName;        }        public String getMoney() {            return mMoney;        }        public void setMoney(String pMoney) {            mMoney = pMoney;        }        @Override        public String toString() {            return "Person{" +                    "mName='" + mName + '\'' +                    ", mMoney='" + mMoney + '\'' +                    '}';        }    }

然后在代码中给实体类赋值,并新建一个该实体类的对象数组,将赋值后的实体类 add 到数组中:

     private ArrayList<Person> mPersons;        @Override        protected void onCreate(Bundle savedInstanceState) {            super.onCreate(savedInstanceState);            setContentView(R.layout.activity_main);            Person lPerson1 = new Person("王", "100");            Person lPerson2 = new Person("张", "200");            Person lPerson3 = new Person("李", "300");            Person lPerson4 = new Person("张", "400");            Person lPerson5 = new Person("王", "500");            Person lPerson6 = new Person("王", "800");            mPersons = new ArrayList<>();            mPersons.add(lPerson1);            mPersons.add(lPerson2);            mPersons.add(lPerson3);            mPersons.add(lPerson4);            mPersons.add(lPerson5);            mPersons.add(lPerson6);        }

到这里,初始化工作已经完成。相信你也已经看明白了,此时数组中存在6条数据,3条姓王的,2条姓张的,1条姓李的,并且每个人拥有的钱数都不一样。我们要做的,就是将此数组经过一系列处理后,数组内只让它留存3条数据,分别为1条姓王的,钱数1400;一条姓张的,钱数600,;一条姓李的,钱数300。

看起来很简单,但是我在实践的过程中还是走了一段弯路的。

3.1 弯路

一开始我是想这样处理的,先把数组中相同姓氏的去重,然后 copy 一份原数组,遍历此数组,将相同姓氏的钱数累加,累加后得出的新数组再根据姓氏替换去重数组中的数据。这样就完成了数组去重并且相同字段值累加的工作。

看起来有点乱,一会一个去重数组,一会一个累加数组的。所以我画了一个图,看图说话:
弯路思路

虽然步骤多了点,但如果能得出正确结果也没毛病。(聪明的你一定猜到最后这种做法没走通,要不怎么叫弯路呢。)

数组去重难度不大,利用 Set 集合的不重复性,即可完成数组的去重工作:

    Set<Person> lPersonSet = new HashSet<Person>();    lPersonSet.addAll(mPersons);    mPersons.clear();    mPersons.addAll(lPersonSet);

这里要注意,如果只这么写,去重肯定是失败的。因为本身每个人的钱数就不同,所以它们就不是相同的对象。即便是姓名和钱数都相同,那每个对象在系统中的内存地址都不同,所以也不是相同对象,这样写一样会失败。

所以,这里要重写一下 Person 类的 equals() 与 hashCode() 方法。在 equals() 方法中,让它只比较姓名是否相同就可以了。

        @Override        public boolean equals(Object obj) {            if (obj == null)                return false;            if (this == obj)                return true;            if (obj instanceof Person) {                Person lPerson = (Person) obj;                if (lPerson.mName.equals(this.mName)) {                    return true;                }            }            return false;        }        @Override        public int hashCode() {            return mName.hashCode();        }

这样,去重工作就成功了。

问题就出在钱数累加上面了。我是这样写的:

    ArrayList<Person> mPersonsRecombine = new ArrayList<Person>();    for (int i = 0; i < mPersons.size(); i++) {        for (int j = 0; j < mPersons.size() - i; j++) {            if(mPersons.get(i).getName().equals(mPersons.get(j).getName()) && i != j) {                Person lPerson = new Person(mPersons.get(i).getName(),String.valueOf(Integer.parseInt(    mPersons.get(i).getMoney()) + Integer.parseInt(mPersons.get(j).getMoney())));                mPersonsRecombine.add(lPerson);            }        }    }

这段代码可以说是漏洞百出,首先,虽然通过 i != j 排除了相同 item 的累加情况,但是不同 item 相等的情况会出现两次。其次,超过两个相等姓氏的对象(比如数组中有3个姓王的),钱数会加不全。

之后思路就断在了这里,并且还围绕这这个问题做了很多修补工作,都是不行。我就准备换方法去实现了,总卡在这里也不行。

这里要说一下,也可能按照这种思路会有正确的打开方式,但是我实在是没有找到解。如果有解决出来的同学,一定要和我联系,一同讨论下。

3.2 解决

树挪死,人挪活。

在经历前文所叙的弯路后,我就不打算在 ArrayList 中进行数据操作了,我打算把数据放在 Map 集合中试一下。然后我就去翻了下 Map 的 API,发现了这样一个方法—— containsKey。该方法判断 Map 集合对象中是否包含指定的键名。如果 Map 集合中包含指定的键名,则返回 true,否则返回 false。

看起来和 ArrayList 的 contains 方法差不多,但是这个方法可以直接对字段名进行判断。看起来可以搞。

    Map<String, Person> lPersonMap = new HashMap<String, Person>();    for (Person lPerson : mPersons) {        String personName = lPerson.getName();        if (lPersonMap.containsKey(personName)) {            double personMoney = Double.valueOf(lPersonMap.get(personName).getMoney());            personMoney += Double.valueOf(lPerson.getMoney());              lPersonMap.get(personName).setMoney(Double.toString(personMoney));        } else {             lPersonMap.put(personName, lPerson);        }    }    mPersons.clear();    mPersons.addAll(lPersonMap.values());

逻辑十分简单,先建立了一个 Map 集合,然后遍历数组,获取数组内每个字段的名称,接着使用了 Map 的 containsKey 方法,判断 Map 集合中是否已经存在当前名称的数据。如果存在,就将钱数累加,如不存在,就将此数据存入 Map 集合中。最后将 Map 集合再转换成 List 数组。

然后,验证一下:
重组数据

结果证明,是正确的。

然后此刻我幡然醒悟,其实用 ArrayList 的 contains 方法也是可以的,因为我都已经重写了 Person 类的 equals() 与 hashCode() 方法,在 equals() 中我只要对姓名进行判断就好了。这样和 Map 的 containsKey 方法所完成的结果是一致的。当然 Map 的 containsKey 方法比 ArrayList 的 contains 效率提升的不是一星半点。

4. 总结

所幸,最终正确的答案被我找到了,也接触并学会了一个新知识 —— containsKey 方法。但是也要认清自己的不足,

一是受错误思路所困,现在回头看那个弯路思路是有多复杂和繁乱。另外,既已知道 ArrayList 的 contains 方法的含义和重写了 Person 类的 equals() 与 hashCode() 方法,也不知道将二者结合,去变通一下思路。

二是知识点掌握不全,如果一早知道 Map 集合的 containsKey 方法,也就不必费这些口舌与精力了。

还得练呐!~~~

阅读全文
0 0