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();
}
}
所以我创建了自己的 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();
}
}