【问题标题】:ArrayList contains case sensitivityArrayList 包含区分大小写
【发布时间】:2012-02-03 19:20:28
【问题描述】:

我目前正在使用属于 ArrayList 类的 contains 方法进行搜索。有没有办法让这个搜索在java中不区分大小写?我发现在 C# 中可以使用 OrdinalIgnoreCase。是否有 java 等价物,或另一种方法来做到这一点? 谢谢。

【问题讨论】:

  • 您需要保持列表中字符串的大小写,还是可以将它们全部设为小写?
  • 我需要保持原样。
  • 如果您还需要原始情况下的字符串,contains() 只会帮助表明它存在于列表中。 HashMap 可以在给定大写或小写字符串的情况下检索原始字符串,并且具有更好的搜索特性(但它不会保留字符串的原始顺序)。
  • 感谢大家的意见,现在我需要决定要采用哪种解决方案。再次感谢。
  • 我简直不敢相信这么多年 Java 没有简单的方法来做到这一点。

标签: java arraylist


【解决方案1】:

contains 方法基于存储在 ArrayList 中的对象的 equals 方法返回的内容。所以是的,如果您使用 equals 使用不区分大小写比较的对象,则有可能。

所以你可以使用这样的类(代码可能仍然包含一些拼写错误)

public class CaseInsensitiveString{
  private final String contents;

  public CaseInsensitiveString( String contents){ this.contents = contents; }

  public boolean equals( Object o ){
    return o != null && o.getClass() == getClass() && o.contents.equalsIgnoreCase( contents);
  }

  public int hashCode(){
    return o.toUpperCase().hashCode();
  }
}

【讨论】:

  • 所有 charSequences(即任何“不区分大小写”有意义的东西)都有一个区分大小写的 equals() 方法,不是吗?
  • 也许添加一个getter并使用o.equalsIgnoreCase(contents)而不是o.toUpperCase().equals(contents.toUpperCase())equalsIgnoreCase() 可以更好地优化,并且可能不需要为两个大写字符串分配对象。
  • @owlstead 当然,吸气剂会很好,但不需要说明我的观点。 equalsIgnoreCase 确实是一个改进,我会相应地编辑我的回复
【解决方案2】:

假设你有一个ArrayList<String>

我能想到的唯一方法是围绕字符串创建一个非常轻量级的包装类,并覆盖等于和哈希码以忽略大小写,尽可能利用equalsIgnoreCase()。然后你会有一个ArrayList<YourString>。不过,这有点丑陋。

【讨论】:

  • +1,但我不同意你的最后一句话:对于应用程序来说,这不是一个丑陋的解决方案。
  • @AaronJLang - 正如我评论你的答案一样。这取决于是否需要原始案例。是的,这可能更容易。取决于 OP 的用例。
  • @Zéychin - 呵呵。在我用自己的代码做之前,我会去 Guava 或 Commons Lang 寻找更好的解决方案,这已经够难看的了。 :)
  • 不要成为一个痛苦的失败者,但扩展列表不会比扩展/包装所有字符串更简单/更有效/更兼容吗?
  • @AaronJLang - 我认为这两种解决方案都能完成工作。这取决于他们最喜欢的OP。正如我最初所说的那样,我什至对自己的解决方案都不满意 :)
【解决方案3】:

通过使用 compareToIgnoreCase,http://docs.oracle.com/javase/1.3/docs/api/java/lang/String.html#compareToIgnoreCase%28java.lang.String%29 你应该可以做你想做的事!

【讨论】:

  • 在调用ArrayList.contains()时可以指定这个比较吗?
  • @AaronJLang 我认为你不能这样做,所以这不是一个真正的解决方案。
【解决方案4】:

您可以像使用任何其他 ArrayList 一样使用它。您可以将此 List 传递给其他代码,外部代码不必理解任何字符串包装类。

public class CustomStringList3 extends ArrayList<String> {
    @Override
    public boolean contains(Object o) {
        String paramStr = (String)o;
        for (String s : this) {
            if (paramStr.equalsIgnoreCase(s)) return true;
        }
        return false;
    }
}

【讨论】:

  • 这假设在插入列表后不需要原始大小写。这是可能的。这取决于 OP 用例。或者,并行维护第二个列表。一个是原始数据,第二个是小写的。
  • @rfeak 非常正确!有点尴尬,我错过了。事后看来,我会使用一个映射,带有小写字符串的键和原始字符串的值
  • 现在只是为了好玩而集思广益,我相信我们对于 OP 有很好的选择。你能像这样子类化,但不是覆盖 add() 和 get(),你能用覆盖 contains() 来利用 equalsIgnoreCase() 吗?理想的情况是,相同的存储使用,保持原始字符串内容/顺序,保持字符串参数(与我的想法相比)并简单地实例化不同的 List 实现。
  • @rfeak 你的天才!我现在就放手
  • Aaron,如果您对同一个问题有不同的答案:请将它们作为单独的答案发布。这使得讨论变得不可能。最后一条评论完全是关于 Map 解决方案的。不要害怕错误的答案,只需将它们标记为这样,以便用户可以从可能的解决方案列表中删除它们。
【解决方案5】:

查看Java API,contains没有这种方法。

但你至少可以做两件事:

  1. 用您自己的方法或 equalsIgnoreCase(str) 覆盖 ArrayList 对象中的 equals 方法
  2. 编写您自己的 contains 方法,该方法应遍历您的 ArrayList 实体,并进行手动检查。

    ArrayList<String> list = new ArrayList<String>();
    ...
    containsIgnoreCase("a", list);
    
    public boolean containsIgnoreCase(String str, ArrayList<String> list){
        for(String i : list){
            if(i.equalsIgnoreCase(str))
                return true;
        }
        return false;
    }
    

【讨论】:

【解决方案6】:

ArrayList 的 contains() 方法通过对您提供的对象(不是数组中的对象)调用 equals() 方法来检查相等性。因此,一个稍微笨拙的方法是围绕 String 对象创建一个包装类,如下所示:

class InsensitiveStringComparable {
    private final String val;

    public InsensitiveStringComparable(String val) {
        this.val = val;
    }

    @Override
    public boolean equals(Object x) {
        if (x == this)
            return true;
        else if (x == null)
            return false;
        else if (x instanceof InsensitiveStringComparable)
            return ((InsensitiveStringComparable) x).val.equalsIgnoreCase(val);
        else if (x instanceof String)
            /* Asymmetric equals condition */
            return val.equalsIgnoreCase((String) x);
        else
            return false;
    }

    @Override
    public int hashCode() {
        return val.toUpperCase().hashCode();
    }
}

然后您可以使用它来执行您的测试。示例“手动”测试用例:

public class Test {
    public static void main(String[] args) {
        ArrayList<Object> a = new ArrayList<Object>();
        a.add("test");
        System.out.println(a.contains(new InsensitiveStringComparable("TEST")));
        System.out.println(a.contains(new InsensitiveStringComparable("tEsT")));
        System.out.println(a.contains(new InsensitiveStringComparable("different")));
    }
}

【讨论】:

  • 如果你覆盖equals你应该覆盖hashCode。
  • @rogelware 是的,但如果您只使用 InsensitiveStringComparable 作为内部“丢弃”类,专门用于执行不区分大小写的 ArrayList 搜索,则无需覆盖 hashCode,因为 ArrayList 不使用它(它如果它应该与例如 HashMap 一起使用,那将是另一回事)
  • 如果列表使用 String 与 InsensitiveStringComparable 进行比较而不是相反,它将不起作用。
  • @Seramme 这可能是真的,但一个非常明显的优化是将对象放入 HashSet 或 HashMap 中,从而破坏代码。许多静态验证器(例如 CheckStyle 和 FindBugs)会很乐意将此标记为问题。最重要的是,将来可以重写 contains() 方法,以在 equals() 之前测试 hashCode() 的相等性,没有什么反对的。只是不要去那里。
  • @owlstead 是的,非常有效的论据。我已经编辑了原始答案以反映这一点。
【解决方案7】:

另一种解决方案:

public class IgnorecaseList extends ArrayList<String>{

    @Override
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    } 

    @Override
    public int indexOf(Object o) {
        if(o instanceof String){
            for (int i = 0; i < this.size(); i++) {
                if(((String)o).equalsIgnoreCase(get(i))){
                    return i;
                }
            }
        }
        return -1;
    }
}

contains() 方法使用 indexOf... 在这个解决方案中,您还可以知道字符串的位置。 list.add("a") -> list.indexOf("A") == 0list.indexOf("a") == 0 ..

您还应该考虑使用 Set 而不是 List。

【讨论】:

  • contains() 没有在 API 中指定它使用 indexOf(),因此您不能为此使用它。如果您采用 contains() 和 indexOf() 的方式,您可能应该重写 both 并希望不会定义其他需要兼容性的方法。在我看来,这排除了它是一个可靠的解决方案。
【解决方案8】:

传统上,您可以开发自己的逻辑来比较 ArrayList 保存的字符串。可能有几种方法可以做到这一点,如下所示。

public boolean containsCaseInsensitive(String strToCompare, ArrayList<String>list)
{
    for(String str:list)
    {
        if(str.equalsIgnoreCase(strToCompare))
        {
            return(true);
        }
    }
    return(false);
}

为什么不应该使用一些直接方便的方式,例如如下所示的 SortedSet 和 case insensitive comparator?。

Set<String> a = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);

a.add("A");
a.add("B");
a.add("C");


Set<String> b = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);

b.add("a");
b.add("b");
b.add("c");

System.out.println(b.equals(a));

将忽略大小写比较两个不同的集合并返回true,在这种特殊情况下,您的比较将毫无问题地进行。

【讨论】:

    【解决方案9】:

    如果您使用的是 Java 8,请尝试:

    List<String> list = ...;
    String searchStr = ...;
    boolean containsSearchStr = list.stream().filter(s -> s.equalsIgnoreCase(searchStr)).findFirst().isPresent();
    

    【讨论】:

    • 我知道它很旧,但你也可以使用 anyMatch() 而不是 filter().findFirst().isPresent()
    • 作为主要的 Android 开发人员,我仍然倾向于避免使用 Java 8 特定的 API,并且更喜欢stackoverflow.com/a/8751546/1815624
    • 查看@shreshtt-bhatt answer below 中的anyMatch 示例
    • 从Android版本N开始,允许使用Stream的anyMatch() API
    【解决方案10】:

    不需要额外的功能,可以通过将比较的字符串转换为大写或小写来实现所需的结果

    (我知道这已在 cmets 中提出,但没有完整地提供作为答案)

    例如:在根据 JTextField 提供的输入过滤 JList 的内容时忽略大小写:

    private ArrayList<String> getFilteredUsers(String filter, ArrayList<User> users) {
        ArrayList<String> filterUsers = new ArrayList<>();
        users.stream().filter((user) -> (user.getUsername().toUpperCase().contains(filter.toUpperCase()))).forEach((user)-> {
            filterUsers.add(user.getUsername());
        });
        this.userList.setListData(filterUsers.toArray());
        return filterUsers;
        /** 
         *  I see the redundancy in returning the object... so even though,
         *  it is passed by reference you could return type void; but because
         *  it's being passed by reference, it's a relatively inexpensive
         *  operation, so providing convenient access with redundancy is just a 
         *  courtesy, much like putting the seat back down. Then, the next
         *  developer with the unlucky assignment of using your code doesn't 
         *  get the proverbially dreaded "wet" seat.
         */
    }
    

    【讨论】:

      【解决方案11】:

      您可以使用来自 collections4 (apache) 的 IterableUtils 和 Predicate。

      List<String> pformats= Arrays.asList("Test","tEst2","tEsT3","TST4");
      
      Predicate<String> predicate  = (s) -> StringUtils.equalsIgnoreCase(s, "TEST");
      if(IterableUtils.matchesAny(pformats, predicate)) {
          // do stuff
      }
      

      IterableUtils(collections4): org.apache.commons.collections4.IterableUtils.html

      【讨论】:

        【解决方案12】:

        在 Java8 中,使用 anyMatch

        List<String> list = Arrays.asList("XYZ", "ABC");
        String matchingText = "xYz";
        
        boolean isMatched = list.stream().anyMatch(matchingText::equalsIgnoreCase);
        

        【讨论】:

        • 注意:stream()anyMatch() 需要 API 24。
        【解决方案13】:

        这是将列表项转换为小写的最佳方式。转换后,您将使用包含方法。喜欢

        List<String> name_list = new ArrayList<>();
        name_list.add("A");
        name_list.add("B");
        

        使用上面创建的name_list创建小写列表

        List<String> name_lowercase_list = new ArrayList<>();
        for(int i =0 ; i<name_list.size(); i++){
         name_lowercase_list.add(name_list.get(i).toLowerCase().toString()); 
        }
        
        for(int i =0 ; i<name_list.size(); i++){
          String lower_case_name =  name_list.get(i).toLowerCase().toString();
          if(name_list.get(i).contains(your compare item) ||
           name_lowercase_list.get(i).contains(your compare item) ){
             //this will return true
           }
        }
        

        【讨论】:

          【解决方案14】:

          对于 java8

          list.stream().anyMatch(s -> s.equalsIgnoreCase(yourString))

          对于

          • 正如上述Aaron J Lang所建议的
          • 或者如果您知道列表的大小写(全部为大写/全部为小写),则在比较之前将搜索字符串转换为适当的大小写

          【讨论】:

            【解决方案15】:

            在我的例子中,因为我在 ArrayList 中的所有字符串都是小写的,所以我只对 contains() 参数执行 String.toLowerCase() 方法。像这样:

            If (yourArrayList.contains (parameterInput.toLowerCase()) {
                // your code here
            }
            

            如你所见,如果你的 arrayList 有大写字符串,你可以做相反的事情:

            If (yourArrayList.contains (parameterInput.toUpperCase ()) {
                // your code here
            }
            

            使用这种方法,您无需覆盖任何内容。例外情况是您的 arrayList 混合了大小写。

            【讨论】:

              【解决方案16】:

              我会这样:

              public boolean isStringInList(final List<String> myList, final String stringToFind) {
                  return myList.stream().anyMatch(s -> s.equalsIgnoreCase(stringToFind));
              }  
              

              【讨论】:

                【解决方案17】:

                如果不想新建函数,可以试试这个方法:

                List<String> myList = new ArrayList<String>(); //The list you're checking
                String wordToFind = "Test"; //or scan.nextLine() or whatever you're checking
                
                //If your list has mix of uppercase and lowercase letters letters create a copy...
                List<String> copyList = new ArrayList<String>();
                for(String copyWord : myList){
                   copyList.add(copyWord.toLowerCase());
                }
                
                for(String myWord : copyList){
                   if(copyList.contains(wordToFind.toLowerCase()){
                      //do something
                   }
                }
                

                【讨论】:

                  【解决方案18】:

                  不要重新发明轮子。使用经过良好测试的 API。为您的目的使用Apache Commons StringUtils

                  来自 Javadoc: 将给定的字符串与 searchStrings 的 CharSequences vararg 进行比较 如果字符串等于任何 searchStrings,则返回 true,忽略大小写。

                  import org.apache.commons.lang3.StringUtils;
                  ...
                  StringUtils.equalsAnyIgnoreCase(null, (CharSequence[]) null) = false
                  StringUtils.equalsAnyIgnoreCase(null, null, null)    = true
                  StringUtils.equalsAnyIgnoreCase(null, "abc", "def")  = false
                  StringUtils.equalsAnyIgnoreCase("abc", null, "def")  = false
                  StringUtils.equalsAnyIgnoreCase("abc", "abc", "def") = true
                  StringUtils.equalsAnyIgnoreCase("abc", "ABC", "DEF") = true
                  

                  【讨论】:

                  • List如何使用?
                  • @kio21 您必须将列表转换为数组: String[] arr = list.toArray(new String[list.size()]);将数组作为第二个参数传递。
                  【解决方案19】:

                  第一种方式

                    List<String> list = Arrays.asList("XYZ", "ABC");
                      String matchingText = "XYZ1";
                          boolean isMatched = list.stream().anyMatch(matchingText::equalsIgnoreCase);
                          System.out.println(isMatched);
                  

                  第二种方式

                  List<String> list1= Arrays.asList("XYZ", "ABC");
                  String searchStr = "abC";
                  boolean containsSearchStr = list1.stream().filter(searchStr::equalsIgnoreCase).findFirst().isPresent();
                  System.out.println(containsSearchStr);
                  

                  【讨论】:

                    【解决方案20】:

                    虽然这个问题是专门针对 Java 提出的,但 Kotlin 中也存在同样的问题。

                    您可以为此使用适用于 List、Array 和 ArrayList 的扩展

                    fun Array<String>.contains(element: String, ignoreCase: Boolean): Boolean {
                      forEach { if (it.equals(element, ignoreCase = ignoreCase)) return true }
                      return false
                    }
                    

                    【讨论】:

                      猜你喜欢
                      • 2012-02-18
                      • 2023-04-02
                      • 1970-01-01
                      • 2011-10-20
                      • 2015-01-22
                      • 2013-01-16
                      • 2014-08-04
                      • 1970-01-01
                      相关资源
                      最近更新 更多