【问题标题】:These Sets allow null. Why can't I add null elements?这些集合允许为空。为什么我不能添加空元素?
【发布时间】:2013-01-24 08:18:05
【问题描述】:

我想知道为什么HashSet、LinkedHashSet 和TreeSet 的实现不允许空元素? 每当我尝试运行以下代码时,它都会引发空指针异常。

public static void main(String[] args) {

    HashSet<Integer> hashSet = new HashSet<Integer>();

    hashSet.add(2);
    hashSet.add(5);
    hashSet.add(1);
//  hashSet.add(null);  will throw null pointer 
    hashSet.add(999);
    hashSet.add(10);
    hashSet.add(10);
    hashSet.add(11);
    hashSet.add(9);
    hashSet.add(10);
    hashSet.add(000);
    hashSet.add(999);
    hashSet.add(0);

    Iterator<Integer> it = hashSet.iterator();
    while(it.hasNext()){
        int i = it.next();
        System.out.print(i+" ");
    }
    }

请指导我。

【问题讨论】:

  • 误导性标题。例如。 HashSet 文档明确声明 此类允许 null 元素。
  • HashSet 和 LinkedHashSet 允许空值。只是,TreeSet 不允许。

标签: java null hashset


【解决方案1】:

这就是我不喜欢依赖自动装箱的原因。 Java Collections 不能存储原语(为此您需要第三方 API,例如 Trove)。所以,真的,当你执行这样的代码时:

hashSet.add(2);
hashSet.add(5);

真正发生的是:

hashSet.add(new Integer(2));
hashSet.add(new Integer(5));

向哈希集添加空值是不是的问题,这部分工作正常。当您尝试将您的值拆箱为原始 int 时,您的 NPE 会稍后出现:

while(it.hasNext()){
    int i = it.next();
    System.out.print(i+" ");
}

当遇到null 值时,JVM 会尝试将其拆箱为 int 原语,这会导致 NPE。您应该更改代码以避免这种情况:

while(it.hasNext()){
    final Integer i = it.next();
    System.out.print(i+" ");
}

【讨论】:

  • 其实2是通过调用Integer.valueOf(2)自动装箱的
  • 我将 null 添加到 ArrayList 并且它没有抛出 NPE。{ ArrayList list = new ArrayList(); System.out.println("数组的初始大小为:"+list.size()); list.add(4); list.add(2); list.add(8); list.add(9); list.add(3); list.add(7); list.add(1); list.add(null); list.add(0,12);迭代器 iterator = list.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); }
  • @nakul - ArrayList 允许空元素,就像 HashSet。您的代码的问题与添加到 HashSet 无关,它与将元素从 HashSet 检索到原语中有关。
  • 是的,java 中不允许将 null 存储到原语中。
  • "这就是为什么我不喜欢依赖自动装箱",为什么 null 应该自动装箱为 0 ?空!= 0
【解决方案2】:

1) 你确定你得到 compile time 错误吗?我不这么认为,我猜代码在运行时会抛出 NPE

int i = it.next();

2)事实上java.util.Set接口并没有禁止空元素,一些JCF Set实现也允许空元素:

设置 API - A collection that contains no duplicate elements. More formally, sets contain no pair of elements e1 and e2 such that e1.equals(e2), and at most one null element.

HashSet API - This class permits the null element

LinkedHashSet API - This class provides all of the optional Set operations, and permits null elements

TreeSet.add API - throws NullPointerException - if the specified element is null and this set uses natural ordering, or its comparator does not permit null elements

【讨论】:

    【解决方案3】:

    不,设置接口确实允许空值只有它的实现,即TreeSet不允许空值。

    即使您没有编写迭代代码并且代码中只有oTreeSet.add(null),它也会编译并在运行时抛出 NullPointerException。

    TreeSet类的add()方法内部调用了TreeMap类的put()方法。 null 值在 put() 方法中是不允许的,如下代码所示

    if (key == null)
         throw new NullPointerException();
    

    【讨论】:

      【解决方案4】:

      Set 接口的要点是使用有关元素的信息(哈希码或比较)来加快实现速度。

      null 没有该信息。

      【讨论】:

      【解决方案5】:

      Set 接口不允许 null,因为在 TreeSet 中,它按排序顺序存储元素,所以每次我们添加新元素时,它都会比较值然后排序。所以在内部发生的事情是将新添加的 null 值与现有值进行比较,因此它将抛出 NullPointerException。

      String str=null;
      if(str.equals("abc"))
      {
      }
      //it will throw null pointer exception
      

      这就是它不允许空值的原因。

      【讨论】:

        【解决方案6】:

        Set 允许添加 null,所以这不是问题。其次Java程序需要先编译成字节码再执行。 NullPointerException 是在运行时抛出的异常。编译时应该不是问题。现在让我们分析一下为什么会得到 NPE。

        Iterator 这里应该输出Integer 类型的对象,我们希望将结果存储在primitive type int 变量中。 Integer 是一个类,它可以具有其类型的引用来引用 null,但原语不能保存 null 值。

        Iterator<Integer> it = hashSet.iterator(); // Iterator of Type Integer
        while(it.hasNext()){
            int i = it.next(); // it.next outputs Integer, but result is tried to be held in a primitive type variable
            System.out.print(i+" ");
        }
        

        int i = it.next(); 被执行时,public int intValue() 被调用以将 Integer 的 Object 转换为原始 int。当it.next() 返回 null 时,null.intValue() 被执行,结果为NullPointerException

        如果用Integer代替int则不会有异常

        Integer i = it.next();
        

        【讨论】:

          【解决方案7】:

          你提到过:

          // hashSet.add(null);会抛出空指针

          这是不正确的,因为HashSet 确实允许将null 添加到其中,因此,您不会遇到此语句的任何编译失败或运行时异常。但是,什么时候会执行下面的语句

          int i = it.next();
          

          你会得到如下所示的运行时异常:

          线程“主”java.lang.NullPointerException 中的异常:不能 调用“java.lang.Integer.intValue()”,因为返回值 “java.util.Iterator.next()”在 Main.main(Main.java:24) 处为空

          这个例外是不言自明的。 JLS §5.1.8 指定如下:

          如果 r 为 null,拆箱转换会抛出 NullPointerException

          下面是理解这个概念的另一个例子:

          public class Main {
              public static void main(String[] args) {
                  Integer x = null;
                  print(x);
              }
          
              static void print(int x) {
                  System.out.println(x);
              }
          }
          

          输出:

          线程“主”java.lang.NullPointerException 中的异常:不能 调用“java.lang.Integer.intValue()”,因为“x”在 Main.main(Main.java:7)

          其实编译器会提前警告你:

          空指针访问:这个整数类型的表达式为空,但 需要自动拆箱

          【讨论】:

            【解决方案8】:
            public class JavaHashSetTest {
            
            
                public static void main(String[] args) {
                    Set<Integer> hashset= new HashSet<Integer>();
            
                    hashset.add(null);
                    hashset.add(22);
                    hashset.add(222);
                    hashset.add(null);
                    hashset.add(11);
                    hashset.add(233);
                    //  TreeSet<String> tset=hashset;
            
                  Iterator<Integer> it = hashset.iterator();
                    while(it.hasNext()){
                        Integer i = it.next();
                        System.out.print(i+" ");
                    }
                }
            
            }
            

            我的代码正在运行,为什么它会给你 Nullpointer 我试图对包含 Null 值的哈希集进行排序,然后它会给你异常,否则它工作正常。

            【讨论】:

            • 您的代码有效,因为您使用了 'Integer i = it.next();'如果你使用'int i = it.next();'那么你会得到异常。 Iterator 应该从其 next() 方法中提供 Integer 类型对象,当我们尝试将其存储为原始类型时,将调用 Integer.java 中定义的 intValue() 。由于引用为 null,因此它在 null 上调用并导致 NPE。
            【解决方案9】:

            只有TreeSet 将获得运行时Null pointer ExceptionHashSet 在编译时不会出现任何错误,并且运行时它将只允许一个null 值,如果我们输入的值超过此值,它将覆盖HashSet。见这里:

            public class HasSetDemo {
                public static void main(String[] args) {
                    // TODO Auto-generated method stub
                    //no error it will print only one null and 2,5,8
                    Set<Integer> s = new HashSet<Integer>();
                    s.add(2);
                    s.add(5);
                    s.add(8);
                    s.add(null);
                    s.add(null);//it will override and hashset will allow one null value
                    Iterator i=s.iterator();        
                    while(i.hasNext()){
                        Object in=(Object)i.next();
                        System.out.println(" hi "+in);
                    }
                }
            }
            

            输出

            hi null
            hi 2
            hi 5
            hi 8
            

            案例2:

            public class SetDemo {
            
                public static void main(String[] args) {
                    // either its may be string or integer it will give NullPointerException in run time
                    Set<String> s2 = new TreeSet<>();
                    s2.add("ramana");
                    s2.add(null);
                    s2.add("4");
                    s2.add("5");
                    s2.add(null);
                    Iterator<String> i=s2.iterator();       
                    while(i.hasNext()){
                        System.out.println(i.next());
                    }
                }
            }
            

            输出:

            Exception in thread "main" java.lang.NullPointerException
                at java.util.TreeMap.put(Unknown Source)
                at java.util.TreeSet.add(Unknown Source)
                at com.sagili.set.SetDemo.main(SetDemo.java:13)
            

            【讨论】:

              【解决方案10】:

              只有在使用该集合进行排序时,向 HashSet Arraylist 等集合添加 null 才会产生问题。除此之外,null 将适用于迭代器和正常场景以显示列表或集合的内容。

              【讨论】:

                【解决方案11】:

                Set 接口内部使用 HashMap 实现类。每当我们使用 add() 时,我们提供的值作为值的键存储到 Map 中,它会创建一个空对象。

                因此,地图不允许重复。

                【讨论】:

                • “设置接口内部使用HashMap”。不,Set 的一些标准实现可能使用 HashMap。但界面不会强迫你这样做。
                猜你喜欢
                • 2022-01-09
                • 1970-01-01
                • 1970-01-01
                • 2012-08-12
                • 1970-01-01
                • 1970-01-01
                • 2022-06-13
                • 2018-07-19
                • 2023-03-16
                相关资源
                最近更新 更多