美文网首页Android知识Android开发经验谈Android开发
利用 Map 集合的 containsKey 方法,实现对象数组

利用 Map 集合的 containsKey 方法,实现对象数组

作者: GinkWang | 来源:发表于2017-06-17 16:12 被阅读859次

    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 方法,也就不必费这些口舌与精力了。

    还得练呐!~~~


    相关文章

      网友评论

        本文标题:利用 Map 集合的 containsKey 方法,实现对象数组

        本文链接:https://www.haomeiwen.com/subject/peqbqxtx.html