Streams 的奇怪错误

Strange bug with Streams

所以我创建了自己的 Set,它只是一个普通的 set,但是有额外的功能(例如我的 set 只存储绝对值)。

这是我的代码:

import java.util.*;

public class SortedByAbsoluteValueIntegerSet<E> extends HashSet<E> {

private Set<Integer> mySet;

public SortedByAbsoluteValueIntegerSet() {
    mySet = new HashSet<Integer>();
}

@Override
public int size() {
    return mySet.size();
}
@Override
public boolean add(E e){
    return mySet.add(Math.abs((Integer) e));
}


@Override
public boolean remove(Object o) {
    return mySet.remove(o);
}

@Override
public boolean contains(Object o){
    return mySet.contains(o);
}

@Override
public boolean addAll(Collection<? extends E> c) {
    List<Integer> myList = new ArrayList<>();
    for (Object e: c) {
        myList.add(Math.abs((Integer) e));
    }
    return mySet.addAll(myList);
}

@Override
public String toString(){
    return mySet.toString();
}

}

我在 JUnit 中有一个测试用例,但失败了。因为我的代码有问题。出于演示目的,为了更好地解释我的问题,我创建了两个函数,可以很好地显示问题。

这是问题所在:

public static void testSortedByAbsoluteValueIntegerSet() {
    Set<Integer> set1 = new SortedByAbsoluteValueIntegerSet();
    Set<Integer> set2 = new HashSet<>();
    set1.add(5);
    set1.add(3);
    set2.add(5);
    set2.add(3);
    String x = toString(set1); //x is  ""
    String t = toString(set2); //t is "3 5"
}

public static String toString(final Collection<Integer> collection) {
    return String.join(" ", collection.stream()
            .map(i -> Integer.toString(i))
            .toArray(String[]::new));
}

所以问题出现在这一行:

String x = toString(set1); //x is always an empty string
String t = toString(set2); //t works correctly

当我通过调试器时,我发现字符串 x 始终是一个空字符串,而字符串 t 工作正常。顺便说一句,set1 代表我创建的集合,而 set2 只是一个常规哈希集。

问题是:如何修复我的 SortedByAbsoluteValueIntegerSet class 以便 toString() 方法也能很好地处理我自己创建的集合。

P.S我是流的新手,我不太明白这个问题,为什么会这样。

这是因为您在扩展 HashSet 的同时还使用了内部 Set。

添加时,您是在向内部集合添加内容,但在使用 collection.stream() 时,它会调用继承的 HashSet(为空)。

我相信对你来说最简单的方法是删除内部 'mySet' 并在重写的方法中调用继承的方法。

例如,您的添加方法是

@Override
public boolean add(E e){
    return super.add(Math.abs((Integer) e));
}

(然后您不需要覆盖 toString 或 spliterator 的大小、删除、包含)

完整示例:

import java.util.*;

public class SortedByAbsoluteValueIntegerSet extends HashSet<Integer> {


    @Override
    public boolean add(Integer e){
        return super.add(Math.abs(e));
    }

    @Override
    public boolean addAll(Collection<? extends Integer> c) {
        List<Integer> myList = new ArrayList<>();
        for (Integer e: c) {
            myList.add(Math.abs(e));
        }
        return super.addAll(myList);
    }

}

我认为 Tomas F 表现更好 answer

你的集合中的主要问题是使用 HashSet mySet 作为字段并扩展 HashSet。在 java 中,最好使用(字段)组合而不是扩展以向 class 添加一些功能。您在这里尝试同时使用两者 - 这不是一个好主意。 最好的决定是只使用组合并扩展更一般的 class,例如 AbstractSet<Integer>Set<Integer>:

import java.util.*;

public class SortedByAbsoluteValueIntegerSet extends AbstractSet<Integer> 
        implements Set<Integer>, java.io.Serializable {

    private final Set<Integer> mySet;

    public SortedByAbsoluteValueIntegerSet() {
        mySet = new HashSet<>();
    }

    @Override
    public Iterator<Integer> iterator() {
        return mySet.iterator();
    }

    @Override
    public int size() {
        return mySet.size();
    }

    @Override
    public boolean add(Integer e) {
        return mySet.add(Math.abs(e));
    }


    @Override
    public boolean remove(Object o) {
        return mySet.remove(o);
    }

    @Override
    public boolean contains(Object o) {
        return mySet.contains(o);
    }

    @Override
    public boolean addAll(Collection<? extends Integer> c) {
        List<Integer> myList = new ArrayList<>();
        for (Integer e : c) {
            myList.add(Math.abs(e));
        }
        return mySet.addAll(myList);
    }

    @Override
    public String toString() {
        return mySet.toString();
    }
}

在这种情况下,您不必实施 spliterator,因为 Set 具有使用 this 关键字的默认实施(将您的集合称为 Collection)

但你也可以在你的 class 中实现 spliterator(但是使用这样的 extends 和内部 Set 字段是不好的做法。另外,最好去掉类型参数E 并将元素转换为整数:

import java.util.*;

public class SortedByAbsoluteValueIntegerSet extends HashSet<Integer> {

    private Set<Integer> mySet;

    public SortedByAbsoluteValueIntegerSet() {
        mySet = new HashSet<>();
    }

    @Override
    public int size() {
        return mySet.size();
    }
    @Override
    public boolean add(Integer e){
        return mySet.add(Math.abs(e));
    }


    @Override
    public boolean remove(Object o) {
        return mySet.remove(o);
    }

    @Override
    public boolean contains(Object o){
        return mySet.contains(o);
    }

    @Override
    public boolean addAll(Collection<? extends Integer> c) {
        List<Integer> myList = new ArrayList<>();
        for (Integer e: c) {
            myList.add(Math.abs(e));
        }
        return mySet.addAll(myList);
    }

    @Override
    public String toString(){
        return mySet.toString();
    }

    @Override
    public Spliterator<Integer> spliterator() {
        return mySet.spliterator();
    }

}