【问题标题】:Iterating through a Collection, avoiding ConcurrentModificationException when removing objects in a loop遍历集合,在循环中删除对象时避免 ConcurrentModificationException
【发布时间】:2010-09-18 10:39:50
【问题描述】:

我们都知道因为ConcurrentModificationException,您不能执行以下操作:

for (Object i : l) {
    if (condition(i)) {
        l.remove(i);
    }
}

但这显然有时有效,但并非总是如此。下面是一些具体的代码:

public static void main(String[] args) {
    Collection<Integer> l = new ArrayList<>();

    for (int i = 0; i < 10; ++i) {
        l.add(4);
        l.add(5);
        l.add(6);
    }

    for (int i : l) {
        if (i == 5) {
            l.remove(i);
        }
    }

    System.out.println(l);
}

当然,这会导致:

Exception in thread "main" java.util.ConcurrentModificationException

即使多个线程没有这样做。无论如何。

这个问题的最佳解决方案是什么?如何在不引发此异常的情况下循环从集合中删除项目?

我这里也使用了任意的Collection,不一定是ArrayList,所以你不能依赖get

【问题讨论】:

标签: java collections iteration


【解决方案1】:

如果使用 HashMap,在较新版本的 Java (8+) 中,您可以选择 3 个选项中的每一个:

public class UserProfileEntity {
    private String Code;
    private String mobileNumber;
    private LocalDateTime inputDT;
    // getters and setters here
}
HashMap<String, UserProfileEntity> upMap = new HashMap<>();


// remove by value
upMap.values().removeIf(value -> !value.getCode().contains("0005"));

// remove by key
upMap.keySet().removeIf(key -> key.contentEquals("testUser"));

// remove by entry / key + value
upMap.entrySet().removeIf(entry -> (entry.getKey().endsWith("admin") || entry.getValue().getInputDT().isBefore(LocalDateTime.now().minusMinutes(3)));

【讨论】:

    【解决方案2】:

    我最终得到了这个ConcurrentModificationException,同时使用stream().map() 方法迭代列表。但是for(:) 在迭代和修改列表时没有抛出异常。

    这里是代码 sn-p ,如果它对任何人有帮助: 在这里,我正在迭代 ArrayList&lt;BuildEntity&gt; ,并使用 list.remove(obj)

    对其进行修改
     for(BuildEntity build : uniqueBuildEntities){
                if(build!=null){
                    if(isBuildCrashedWithErrors(build)){
                        log.info("The following build crashed with errors ,  will not be persisted -> \n{}"
                                ,build.getBuildUrl());
                        uniqueBuildEntities.remove(build);
                        if (uniqueBuildEntities.isEmpty()) return  EMPTY_LIST;
                    }
                }
            }
            if(uniqueBuildEntities.size()>0) {
                dbEntries.addAll(uniqueBuildEntities);
            }
    

    【讨论】:

      【解决方案3】:

      并发修改异常

      1. 单线程
      Iterator<String> iterator = list.iterator();
      while (iterator.hasNext()) {
          String value = iter.next()
          if (value == "A") {
              //throws ConcurrentModificationException
              list.remove(it.next());
          }
      }
      

      解决方案:迭代器remove()方法

      Iterator<String> iterator = list.iterator();
      while (iterator.hasNext()) {
          String value = iter.next()
          if (value == "A") {
              it.remove()
          }
      }
      
      1. 多线程
      • 复制/转换并迭代另一个集合。对于小型收藏
      • synchronize[About]
      • 线程安全集合[About]

      【讨论】:

        【解决方案4】:

        您可以使用 while 循环。

        Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
        while(iterator.hasNext()){
            Map.Entry<String, String> entry = iterator.next();
            if(entry.getKey().equals("test")) {
                iterator.remove();
            } 
        }
        

        【讨论】:

          【解决方案5】:

          另一种方法是使用 arrayList 的副本仅用于迭代:

          List<Object> l = ...
              
          List<Object> iterationList = ImmutableList.copyOf(l);
              
          for (Object curr : iterationList) {
              if (condition(curr)) {
                  l.remove(curr);
              }
          }
          

          【讨论】:

          • 注意:i 不是index,而是对象。也许称它为obj 会更合适。
          • 早在 2012 年就已经在上面提出了建议:stackoverflow.com/a/11201224/3969362 复制列表是他们通常对 Android 上的侦听器所做的事情。对于小型列表来说,这是一个有效的解决方案。
          【解决方案6】:

          现在,您可以使用以下代码删除

          l.removeIf(current -> current == 5);
          

          【讨论】:

            【解决方案7】:

            你也可以使用递归

            Java 中的递归是一个方法不断调用自身的过程。 java中调用自身的方法称为递归方法。

            【讨论】:

              【解决方案8】:

              使用传统的 for 循环

              ArrayList<String> myArray = new ArrayList<>();
              
              for (int i = 0; i < myArray.size(); ) {
                  String text = myArray.get(i);
                  if (someCondition(text))
                      myArray.remove(i);
                  else
                      i++;   
              }
              

              【讨论】:

              • 啊,所以它真的只是抛出异常的增强-for-loop。
              • FWIW - 相同的代码在修改为在循环保护而不是循环主体中增加 i++ 后仍然可以工作。
              • 更正^:如果i++ 递增不是有条件的——我现在明白这就是你在正文中这样做的原因:)
              【解决方案9】:

              您可以像您提到的那样直接使用迭代器,也可以保留第二个集合并将要删除的每个项目添加到新集合中,然后在最后 removeAll。这允许您以增加内存使用和 cpu 时间为代价继续使用 for-each 循环的类型安全性(除非您有非常非常大的列表或非常旧的计算机,否则这应该不是一个大问题)

              public static void main(String[] args)
              {
                  Collection<Integer> l = new ArrayList<Integer>();
                  Collection<Integer> itemsToRemove = new ArrayList<>();
                  for (int i=0; i < 10; i++) {
                      l.add(Integer.of(4));
                      l.add(Integer.of(5));
                      l.add(Integer.of(6));
                  }
                  for (Integer i : l)
                  {
                      if (i.intValue() == 5) {
                          itemsToRemove.add(i);
                      }
                  }
              
                  l.removeAll(itemsToRemove);
                  System.out.println(l);
              }
              

              【讨论】:

              • 这是我通常做的,但显式迭代器是我觉得更优雅的解决方案。
              • 很公平,只要您不对迭代器做任何其他事情 - 将它暴露出来可以更容易地执行诸如每次循环调用 .next() 两次等操作。这不是一个大问题,但如果您执行的操作比仅通过列表删除条目更复杂,则可能会导致问题。
              • @RodeoClown:在最初的问题中,Claudiu 正在从集合中移除,而不是从迭代器中移除。
              • 从迭代器中删除从底层集合中删除......但我在最后一条评论中所说的是,如果你正在做任何比在循环中查找删除更复杂的事情(比如处理正确data) 使用迭代器可以使一些错误更容易产生。
              • 如果它是一个简单的删除不需要的值并且循环只做一件事,直接使用迭代器并调用 .remove() 绝对没问题。
              【解决方案10】:

              人们断言一个不能从被 foreach 循环迭代的 Collection 中删除。我只是想指出 技术上 不正确并准确描述(我知道 OP 的问题是如此先进以至于避免知道这一点)该假设背后的代码:

              for (TouchableObj obj : untouchedSet) {  // <--- This is where ConcurrentModificationException strikes
                  if (obj.isTouched()) {
                      untouchedSet.remove(obj);
                      touchedSt.add(obj);
                      break;  // this is key to avoiding returning to the foreach
                  }
              }
              

              并不是说您不能从迭代的Colletion 中删除,而是您一旦这样做就不能继续迭代。因此上面代码中的break

              抱歉,如果这个答案是一个有点专业的用例,并且更适合我从这里到达的原始 thread,那一个被标记为重复(尽管这个线程看起来更细微)并被锁定。

              【讨论】:

                【解决方案11】:

                最好的方法(推荐)是使用 java.util.Concurrent 包。经过 使用这个包你可以很容易地避免这个 Exception 。参考 修改代码

                public static void main(String[] args) {
                    Collection<Integer> l = new CopyOnWriteArrayList<Integer>();
                
                    for (int i=0; i < 10; ++i) {
                        l.add(new Integer(4));
                        l.add(new Integer(5));
                        l.add(new Integer(6));
                    }
                
                    for (Integer i : l) {
                        if (i.intValue() == 5) {
                            l.remove(i);
                        }
                    }
                
                    System.out.println(l);
                }
                

                【讨论】:

                • 您是否考虑了性能损失?每次你“写入”这个结构时,它的内容都会被复制到一个新对象中。所有这些都对性能不利。
                【解决方案12】:

                试试这个(删除列表中等于i的所有元素):

                for (Object i : l) {
                    if (condition(i)) {
                        l = (l.stream().filter((a) -> a != i)).collect(Collectors.toList());
                    }
                }
                

                【讨论】:

                  【解决方案13】:

                  使用Eclipse Collections,在MutableCollection 上定义的方法removeIf 将起作用:

                  MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
                  list.removeIf(Predicates.lessThan(3));
                  Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);
                  

                  使用 Java 8 Lambda 语法可以这样写:

                  MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
                  list.removeIf(Predicates.cast(integer -> integer < 3));
                  Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);
                  

                  这里需要调用Predicates.cast(),因为在Java 8 的java.util.Collection 接口上添加了默认的removeIf 方法。

                  注意:我是Eclipse Collections 的提交者。

                  【讨论】:

                    【解决方案14】:

                    一种解决方案可能是旋转列表并删除第一个元素以避免 ConcurrentModificationException 或 IndexOutOfBoundsException

                    int n = list.size();
                    for(int j=0;j<n;j++){
                        //you can also put a condition before remove
                        list.remove(0);
                        Collections.rotate(list, 1);
                    }
                    Collections.rotate(list, -1);
                    

                    【讨论】:

                      【解决方案15】:

                      在 Java 8 中,您可以使用 the new removeIf method。应用于您的示例:

                      Collection<Integer> coll = new ArrayList<>();
                      //populate
                      
                      coll.removeIf(i -> i == 5);
                      

                      【讨论】:

                      • 噢噢噢!我希望 Java 8 或 9 中的某些东西可能会有所帮助。这对我来说似乎仍然很冗长,但我仍然喜欢它。
                      • 在这种情况下是否也推荐实施equals()?
                      • 顺便说一下removeIf 使用Iteratorwhile 循环。你可以在 java 8 java.util.Collection.java 看到它
                      • @omerhakanbilici 出于性能原因,ArrayList 等一些实现会覆盖它。您所指的只是默认实现。
                      • @AnmolGupta:不,equals 在这里根本没有使用,因此不必实现。 (当然,如果你在测试中使用equals,那么它必须按照你想要的方式实现。)
                      【解决方案16】:

                      这行得通:

                      Iterator<Integer> iter = l.iterator();
                      while (iter.hasNext()) {
                          if (iter.next() == 5) {
                              iter.remove();
                          }
                      }
                      

                      我认为由于 foreach 循环是用于迭代的语法糖,因此使用迭代器无济于事……但它为您提供了 .remove() 功能。

                      【讨论】:

                      • foreach 循环 is 用于迭代的语法糖。但是,正如您所指出的,您需要在迭代器上调用 remove - foreach 不允许您访问。因此,您无法在 foreach 循环中删除的原因(即使您 实际上在引擎盖下使用了迭代器)
                      • +1 例如在上下文中使用 iter.remove() 的代码,Bill K 的回答[直接]没有。
                      【解决方案17】:

                      我知道这个问题只假设Collection,而不是更具体的List。但是对于那些阅读这个问题并且确实使用List 参考的人,如果你想避免Iterator,则可以使用while-loop(在其中修改时)避免ConcurrentModificationException(如果您想在一般情况下避免它,或者专门避免它以实现与在每个元素处从头到尾停止不同的循环顺序[我相信这是Iterator 本身可以做的唯一顺序]):

                      *更新:请参阅下面的 cmets,阐明类似的情况也可以通过 传统-for-loop 实现。

                      final List<Integer> list = new ArrayList<>();
                      for(int i = 0; i < 10; ++i){
                          list.add(i);
                      }
                      
                      int i = 1;
                      while(i < list.size()){
                          if(list.get(i) % 2 == 0){
                              list.remove(i++);
                      
                          } else {
                              i += 2;
                          }
                      }
                      

                      该代码没有 ConcurrentModificationException。

                      我们看到循环不是从一开始就开始,也不是在每个元素处停止(我相信Iterator本身不能这样做)。

                      FWIW 我们还看到 getlist 上被调用,如果它的引用只是 Collection(而不是更具体的 List 类型的 Collection),则无法完成 - List接口包含get,但Collection 接口不包含。如果不是因为这种差异,那么 list 引用可以改为 Collection [因此从技术上讲,此答案将是直接答案,而不是切线答案]。

                      FWIWW 相同的代码在修改为从每个元素开始到停止后仍然有效(就像Iterator order):

                      final List<Integer> list = new ArrayList<>();
                      for(int i = 0; i < 10; ++i){
                          list.add(i);
                      }
                      
                      int i = 0;
                      while(i < list.size()){
                          if(list.get(i) % 2 == 0){
                              list.remove(i);
                      
                          } else {
                              ++i;
                          }
                      }
                      

                      【讨论】:

                      • 然而,这仍然需要非常仔细地计算要删除的指标。
                      • 另外,这只是对这个答案的更详细的解释stackoverflow.com/a/43441822/2308683
                      • 很高兴知道 - 谢谢!另一个答案帮助我理解这是 enhanced-for-loop 会抛出 ConcurrentModificationException,但 不是 traditional-for-loop (另一个答案使用) - 没有意识到之前是我有动力写这个答案的原因(当时我错误地认为是 all for-loops 会抛出异常)。
                      【解决方案18】:

                      Iterator.remove() 是安全的,你可以这样使用它:

                      List<String> list = new ArrayList<>();
                      
                      // This is a clever way to create the iterator and call iterator.hasNext() like
                      // you would do in a while-loop. It would be the same as doing:
                      //     Iterator<String> iterator = list.iterator();
                      //     while (iterator.hasNext()) {
                      for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
                          String string = iterator.next();
                          if (string.isEmpty()) {
                              // Remove the current element from the iterator and the list.
                              iterator.remove();
                          }
                      }
                      

                      请注意,Iterator.remove() 是在迭代期间修改集合的唯一安全方法;如果在迭代过程中以任何其他方式修改了底层集合,则行为未指定。

                      来源: docs.oracle > The Collection Interface


                      同样,如果您有 ListIterator 并且想要添加项,您可以使用 ListIterator#add,出于同样的原因,您可以使用 Iterator#remove - 它旨在允许它。


                      在您的情况下,您尝试从列表中删除,但如果在迭代其内容时尝试将 put 变为 Map,则同样的限制适用。

                      【讨论】:

                      • 如果要删除当前迭代中返回的元素以外的元素怎么办?
                      • 你必须在迭代器中使用 .remove 并且只能删除当前元素,所以没有:)
                      • 请注意,这比使用 ConcurrentLinkedDeque 或 CopyOnWriteArrayList 慢(至少在我的情况下)
                      • 不能将iterator.next() 调用放在for循环中吗?如果不是,有人可以解释原因吗?
                      • @GonenI 它是为来自不可变集合的所有迭代器实现的。 List.add 在同样的意义上也是“可选的”,但你不会说添加到列表中是“不安全的”。
                      【解决方案19】:

                      我知道这个问题对于 Java 8 来说太老了,但是对于那些使用 Java 8 的人来说,您可以轻松地使用 removeIf():

                      Collection<Integer> l = new ArrayList<Integer>();
                      
                      for (int i=0; i < 10; ++i) {
                          l.add(new Integer(4));
                          l.add(new Integer(5));
                          l.add(new Integer(6));
                      }
                      
                      l.removeIf(i -> i.intValue() == 5);
                      

                      【讨论】:

                        【解决方案20】:

                        线程安全集合修改示例:

                        public class Example {
                            private final List<String> queue = Collections.synchronizedList(new ArrayList<String>());
                        
                            public void removeFromQueue() {
                                synchronized (queue) {
                                    Iterator<String> iterator = queue.iterator();
                                    String string = iterator.next();
                                    if (string.isEmpty()) {
                                        iterator.remove();
                                    }
                                }
                            }
                        }
                        

                        【讨论】:

                          【解决方案21】:

                          ListIterator 允许您添加或删除列表中的项目。假设您有一个Car 对象列表:

                          List<Car> cars = ArrayList<>();
                          // add cars here...
                          
                          for (ListIterator<Car> carIterator = cars.listIterator();  carIterator.hasNext(); )
                          {
                             if (<some-condition>)
                             { 
                                carIterator().remove()
                             }
                             else if (<some-other-condition>)
                             { 
                                carIterator().add(aNewCar);
                             }
                          }
                          

                          【讨论】:

                          • ListIterator 接口(Iterator 的扩展)中的附加方法很有趣——尤其是它的previous 方法。
                          【解决方案22】:
                          for (Integer i : l)
                          {
                              if (i.intValue() == 5){
                                      itemsToRemove.add(i);
                                      break;
                              }
                          }
                          

                          如果您跳过内部 iterator.next() 调用,则捕获是从列表中删除元素之后。它仍然有效!虽然我不建议编写这样的代码,但有助于理解其背后的概念:-)

                          干杯!

                          【讨论】:

                            【解决方案23】:

                            ConcurrentHashMapConcurrentLinkedQueueConcurrentSkipListMap 可能是另一种选择,因为它们永远不会抛出任何 ConcurrentModificationException,即使您删除或添加项目。

                            【讨论】:

                            • 是的,请注意这些都在java.util.concurrent 包中。该包中的其他一些类似/常见用例类是 CopyOnWriteArrayList CopyOnWriteArraySet [但不限于这些]。
                            • 其实,我刚刚了解到,虽然那些数据结构 Objects avoid ConcurrentModificationException,在 enhanced-for-loop 中使用它们仍然会导致索引问题(即:仍然跳过元素,或IndexOutOfBoundsException...)
                            【解决方案24】:

                            如果 ArrayList:remove(int index)- if(index is last element's position) 它会避免没有System.arraycopy() 并且不需要时间。

                            如果(索引减少),数组复制时间会增加,顺便说一下列表的元素也会减少!

                            最有效的移除方式是 - 按降序移除其元素: while(list.size()&gt;0)list.remove(list.size()-1);//需要 O(1) while(list.size()&gt;0)list.remove(0);//需要 O(factorial(n))

                            //region prepare data
                            ArrayList<Integer> ints = new ArrayList<Integer>();
                            ArrayList<Integer> toRemove = new ArrayList<Integer>();
                            Random rdm = new Random();
                            long millis;
                            for (int i = 0; i < 100000; i++) {
                                Integer integer = rdm.nextInt();
                                ints.add(integer);
                            }
                            ArrayList<Integer> intsForIndex = new ArrayList<Integer>(ints);
                            ArrayList<Integer> intsDescIndex = new ArrayList<Integer>(ints);
                            ArrayList<Integer> intsIterator = new ArrayList<Integer>(ints);
                            //endregion
                            
                            // region for index
                            millis = System.currentTimeMillis();
                            for (int i = 0; i < intsForIndex.size(); i++) 
                               if (intsForIndex.get(i) % 2 == 0) intsForIndex.remove(i--);
                            System.out.println(System.currentTimeMillis() - millis);
                            // endregion
                            
                            // region for index desc
                            millis = System.currentTimeMillis();
                            for (int i = intsDescIndex.size() - 1; i >= 0; i--) 
                               if (intsDescIndex.get(i) % 2 == 0) intsDescIndex.remove(i);
                            System.out.println(System.currentTimeMillis() - millis);
                            //endregion
                            
                            // region iterator
                            millis = System.currentTimeMillis();
                            for (Iterator<Integer> iterator = intsIterator.iterator(); iterator.hasNext(); )
                                if (iterator.next() % 2 == 0) iterator.remove();
                            System.out.println(System.currentTimeMillis() - millis);
                            //endregion
                            
                            • 索引循环:1090 毫秒
                            • 对于 desc 索引:519 毫秒---最好的
                            • 对于迭代器:1043 毫秒

                            【讨论】:

                              【解决方案25】:

                              由于问题已经得到解答,即最好的方法是使用迭代器对象的 remove 方法,我将详细介绍引发错误 "java.util.ConcurrentModificationException" 的地方。

                              每个集合类都有一个私有类,它实现了 Iterator 接口并提供了next()remove()hasNext() 等方法。

                              next 的代码看起来像这样......

                              public E next() {
                                  checkForComodification();
                                  try {
                                      E next = get(cursor);
                                      lastRet = cursor++;
                                      return next;
                                  } catch(IndexOutOfBoundsException e) {
                                      checkForComodification();
                                      throw new NoSuchElementException();
                                  }
                              }
                              

                              这里的方法checkForComodification被实现为

                              final void checkForComodification() {
                                  if (modCount != expectedModCount)
                                      throw new ConcurrentModificationException();
                              }
                              

                              因此,如您所见,如果您明确尝试从集合中删除一个元素。它导致modCountexpectedModCount 不同,从而导致异常ConcurrentModificationException

                              【讨论】:

                              • 非常有趣。谢谢!我自己经常不调用 remove() ,而是喜欢在迭代后清除集合。并不是说这是一个很好的模式,只是我最近一直在做的事情。
                              【解决方案26】:

                              我对上述问题有一个建议。不需要二级名单或任何额外的时间。请找到一个例子,它可以做同样的事情,但以不同的方式。

                              //"list" is ArrayList<Object>
                              //"state" is some boolean variable, which when set to true, Object will be removed from the list
                              int index = 0;
                              while(index < list.size()) {
                                  Object r = list.get(index);
                                  if( state ) {
                                      list.remove(index);
                                      index = 0;
                                      continue;
                                  }
                                  index += 1;
                              }
                              

                              这样可以避免并发异常。

                              【讨论】:

                              • 问题明确指出,使用ArrayList 不需要OP,因此不能依赖get()。不过,否则可能是一个好方法。
                              • (澄清 ^)OP 正在使用任意Collection - Collection 接口不包括 get。 (虽然 FWIW List 接口确实包含“get”)。
                              • 我刚刚添加了一个单独的、更详细的答案在这里也为while-循环List。但是为这个答案 +1,因为它排在第一位。
                              【解决方案27】:

                              在这种情况下,一个常见的技巧是(曾经?)倒退:

                              for(int i = l.size() - 1; i >= 0; i --) {
                                if (l.get(i) == 5) {
                                  l.remove(i);
                                }
                              }
                              

                              也就是说,我很高兴您在 Java 8 中有更好的方法,例如removeIffilter 在流上。

                              【讨论】:

                              • 这是个好技巧。但它不适用于像集合这样的非索引集合,并且在链表上会非常慢。
                              • @Claudiu 是的,这绝对只适用于ArrayLists 或类似的集合。
                              • 我正在使用一个 ArrayList,这很好用,谢谢。
                              • 索引很棒。如果它很常见,为什么不使用for(int i = l.size(); i--&gt;0;) {
                              【解决方案28】:

                              Claudius 相同的答案,带有 for 循环:

                              for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
                                  Object object = it.next();
                                  if (test) {
                                      it.remove();
                                  }
                              }
                              

                              【讨论】:

                                【解决方案29】:

                                这可能不是最好的方法,但对于大多数小情况来说,这应该是可以接受的:

                                “创建第二个空数组并仅添加您想要保留的那些”

                                我不记得我是从哪里读到这篇文章的……为了公正起见,我会制作这个维基,希望有人能找到它,或者只是为了不赢得我不应该得到的代表。

                                【讨论】:

                                  【解决方案30】:

                                  复制现有列表并迭代新副本。

                                  for (String str : new ArrayList<String>(listOfStr))     
                                  {
                                      listOfStr.remove(/* object reference or index */);
                                  }
                                  

                                  【讨论】:

                                  • 复制听起来像是在浪费资源。
                                  • @Antzi 这取决于列表的大小和其中对象的密度。仍然是一个有价值且有效的解决方案。
                                  • 我一直在用这个方法。它需要更多资源,但更加灵活和清晰。
                                  • 当您不打算删除循环本身内的对象时,这是一个很好的解决方案,但是它们是从其他线程中“随机”删除的(例如,网络操作更新数据)。如果您发现自己经常做这些副本,甚至有一个 java 实现就是这样做的:docs.oracle.com/javase/8/docs/api/java/util/concurrent/…
                                  • 复制列表是他们通常对 Android 上的侦听器所做的事情。对于小型列表,这是一个有效的解决方案。
                                  猜你喜欢
                                  相关资源
                                  最近更新 更多