一套
甲Set
是元素的集合,从而在任何给定的元件Set
只出现一次。
更正式地说,集合不包含元素e1
和e2
对,因此e1.equals(e2)
。
我们可以像这样在Java 9中轻松创建Set
:
final Set<Integer> s = Set.of(1, 2, 3);
System.out.println(s);
这可能会产生以下输出:
[2, 3, 1]
上面产生的Set
是不可变的,即它不能更改,并且也是有限的,因为Set
有不同数量的元素,即三个。 未指定通过元素的读取方法(例如stream()
, iterator()
和forEach()
)返回元素的顺序。
无限集
无限集包含无限数量的元素。 无限集的一个示例是所有整数的集合[…,-1,0,1,2,…],其中整数不是Java Integer
类的整数,而是根据整数的数学定义得出的整数,其中存在对于任何给定的整数n,始终为更大的整数n + 1。
有许多无限集,例如所有质数的集合,偶数整数的集合,斐波那契数的集合等。
由于明显的原因,我们无法预先计算和存储无限Java Set
所有元素。 如果尝试,最终将耗尽内存。
我们必须问自己的一个基本问题是:对于我们拥有的Java类型,实际上是否存在无限集? 如果我们有一个Set<Byte>
中有最多256个元素的Set
,这是远离无限的,同样的道理也适用于Short
,甚至Integer
。 毕竟,只有大约40亿个不同的Integer
对象,如果使用位图表示成员身份,则可以将Set<Integer>
装入0.5 GB。 尽管很大,但不是无限的。
但是,如果我们谈论的是Long
或String
元素,那么至少实际上是在接近无限集。 要存储所有Long的位图,将需要许多PB内部存储空间。 真正的无限Set
将是具有任意长度的字符[az]的所有可能组合的String
Set
。
在继续之前,我想提一下,这篇文章中的代码也可以在GitHub上获得,如文章结尾处所述。
ImmutableStreamSet
为了摆脱存储Set
元素的范式,我们可以创建一个ImmutableStreamSet
,仅通过其stream()
方法定义Set
的元素。 ImmutableStreamSet
可以这样定义为FunctionalInterface
:
@FunctionalInterface
public interface ImmutableStreamSet<E> extends Set<E> {// This is the only method we need to implements@Overridepublic Stream<E> stream(); @Overridedefault int size() {return (int) stream().limit(Integer.MAX_VALUE).count();}@Overridedefault boolean contains(Object o) {return stream().anyMatch(e -> Objects.equals(e, o));}@Overridedefault boolean containsAll(Collection<?> c) {return (this == c) ? true : c.stream().allMatch(this::contains);}@Overridedefault boolean isEmpty() {return !stream().findAny().isPresent();}@Overridedefault <T> T[] toArray(T[] a) {return stream().collect(toList()).toArray(a);}@Overridedefault Object[] toArray() {return stream().toArray();}@Overridedefault Spliterator<E> spliterator() {return stream().spliterator();}@Overridedefault Iterator<E> iterator() {return stream().iterator();}@Overridedefault Stream<E> parallelStream() {return stream().parallel();}@Overridedefault void forEach(Consumer<? super E> action) {stream().forEach(action);}// We are immutable@Overridedefault boolean removeIf(Predicate<? super E> filter) {throw new UnsupportedOperationException();}@Overridedefault void clear() {throw new UnsupportedOperationException();}@Overridedefault boolean removeAll(Collection<?> c) {throw new UnsupportedOperationException();}@Overridedefault boolean retainAll(Collection<?> c) {throw new UnsupportedOperationException();}@Overridedefault boolean addAll(Collection<? extends E> c) {throw new UnsupportedOperationException();}@Overridedefault boolean remove(Object o) {throw new UnsupportedOperationException();}@Overridedefault boolean add(E e) {throw new UnsupportedOperationException();}static <E> ImmutableStreamSet<E> of(Supplier<Stream<E>> supplier) {// Check out GitHub to see how this Impl class is implementedreturn new ImmutableStreamSetImpl<>(supplier);}}
太棒了,现在我们只需提供像这样的流提供者就可以创建无限集:
ImmutableStreamSet<Long> setOfAllLong= LongStream.rangeClosed(Long.MIN_VALUE, Long.MAX_VALUE)::boxed;
这将创建一Set
所有Long
值(例如,具有2 ^ 64个元素)。 提供流提供程序时,必须确保遵守元素唯一性的Set属性。 考虑以下非法集:
final ImmutableStreamSet<Long> illegalSet = () -> Stream.of(1l, 2l, 1l);
显然,集合中发生11次两次,这使该对象违反了集合要求。
正如我们将看到的,最好创建我们正在考虑的无限集的具体类。 上面的默认实现的一个特殊问题是contains()
方法可能非常慢。 阅读下一章,找出原因以及解决方法。
正长集
让我们假设我们要创建一个包含所有正长值的Set
,并且希望能够与其他Set和Object有效地使用Set
。 我们可以这样去做:
public final class PositiveLongSet implements ImmutableStreamSet<Long> {public static final PositiveLongSet INSTANCE = new PositiveLongSet();private PositiveLongSet() {}@Overridepublic Stream<Long> stream() {return LongStream.rangeClosed(1, Long.MAX_VALUE).boxed();}@Overridepublic int size() {return Integer.MAX_VALUE;}@Overridepublic boolean contains(Object o) {return SetUtil.contains(this, Long.class, other -> other > 0, o);}@Overridepublic boolean isEmpty() {return false;}@Overridepublic String toString() {return SetUtil.toString(this);}}
注意我们如何遵守方法size()
中的形式要求,即使Set
很大,我们也会在其中返回Integer.MAX_VALUE
。 如果Set
已在今天定义,则size()
可能会返回long
而不是int
。 但是在90年代初,内部RAM通常小于1 GB。 我们在类中使用了两种实用程序方法:
SetUtil.toString()
采用Set
,对前八个元素进行迭代,并返回这些元素的String
表示形式。
SetUtil.contains()
方法采用Set
,Element类型类(此处为Long.class
)和一个Predicate
,如果我们Long.class
比较的对象属于给定Element类型类(如果我们要比较的对象是null
或其他类型,则该Set
不包含该对象)。
这是SetUtil
样子:
final class SetUtil {private static final int TO_STRING_MAX_ELEMENTS = 8;static <E> String toString(Set<E> set) {final List<String> first = set.stream().limit(TO_STRING_MAX_ELEMENTS + 1).map(Object::toString).collect(toList());final String endMarker = first.size() > TO_STRING_MAX_ELEMENTS ? ", ...]" : "]";return first.stream().limit(TO_STRING_MAX_ELEMENTS).collect(joining(", ", "[", endMarker));}static <E> boolean contains(final Set<E> set,final Class<E> clazz,final Predicate<E> predicate,final Object o) {if (o == null) {return false;}if (!(clazz.isAssignableFrom(o.getClass()))) {return false;}final E other = clazz.cast(o);return predicate.test(other);}}
有了类ImmutableStreamSet
和SetUtil
我们现在可以轻松地创建其他无限集,例如PostitiveEvenLongSet
(下面未显示,请PrimeLongSet
编写), PrimeLongSet
(包含可以用Long
表示的所有素数)甚至FibonacciLongSet
(包含所有fibonacci数字)可以用Long
表示)。 这些类如下所示:
PrimeLongSet
public final class PrimeLongSet implements ImmutableStreamSet<Long> {public static final PrimeLongSet INSTANCE = new PrimeLongSet();private PrimeLongSet() {}private static final LongPredicate IS_PRIME =x -> LongStream.rangeClosed(2, (long) Math.sqrt(x)).allMatch(n -> x % n != 0);@Overridepublic Stream<Long> stream() {return LongStream.rangeClosed(2, Long.MAX_VALUE).filter(IS_PRIME).boxed();}@Overridepublic int size() {return Integer.MAX_VALUE; }@Overridepublic boolean contains(Object o) {return SetUtil.contains(this, Long.class, IS_PRIME::test, o);}@Overridepublic boolean isEmpty() {return false;}@Overridepublic String toString() {return SetUtil.toString(this);}}
斐波那契长集
public final class FibonacciLongSet implements ImmutableStreamSet<Long> {public static final FibonacciLongSet INSTANCE = new FibonacciLongSet();private FibonacciLongSet() {}@Overridepublic Stream<Long> stream() {return Stream.concat(Stream.of(0l),Stream.iterate(new Fibonacci(0, 1), Fibonacci::next).mapToLong(Fibonacci::getAsLong).takeWhile(fib -> fib > 0).boxed());}@Overridepublic int size() {return 92;}@Overridepublic boolean contains(Object o) {return SetUtil.contains(this,Long.class,other -> stream().anyMatch(fib -> Objects.equals(fib, other)),o);}@Overridepublic boolean isEmpty() {return false;}@Overridepublic String toString() {return SetUtil.toString(this);}private static class Fibonacci {final long beforeLast;final long last;public Fibonacci(long beforeLast, long last) {this.beforeLast = beforeLast;this.last = last;}public Fibonacci next() {return new Fibonacci(last, last + beforeLast);}public long getAsLong() {return beforeLast + last;}}}
注意当长回绕为负值时,我们如何使用Stream::takeWhile
中断流。 可以说,当我们进行预计算并提供92的大小时,我们正在“作弊”,但否则size()
会慢一些。
缝合全部
通过为这些类的实例提供带有静态提供程序的接口,我们可以封装我们的预定义集,并确保在JVM中只有这样的一个实例,如下所示:
public interface Sets {static Set<Long> positiveLongSet() {return PositiveLongSet.INSTANCE;}static Set<Long> positiveEvenLongSet() {return PositiveEvenLongSet.INSTANCE;}static Set<Long> primeLongSet() {return PrimeLongSet.INSTANCE;}static Set<Long> fibonacciLongSet() {return FibonacciLongSet.INSTANCE;}}
我们还可以将代码封装在Java 9模块中,通过将Sets
和ImmutableStreamSet
类暴露在项目的顶级包中,并将所有其他类放在名为“ internal”的包中(不公开),以确保仅可见Sets
和ImmutableStreamSet
类。 )。
这是我们如何module-info.java
可能看起来像规定,两个外露类在com.speedment.infinite_sets
在包等,并且实现类com.speedment.infinite_sets.internal
:
module-info.java
module com.speedment.infinite_sets {exports com.speedment.infinite_sets;
}
尝试一下
现在,我们可以通过首先声明现有模块的用法来创建另一个使用无限集的模块,如下所示:
module-info.java
module Infinite_sets_app {requires com.speedment.infinite_sets;
}
然后,我们可以访问模块的裸露部分。 这是尝试无限集的一种方法:
import static com.speedment.infinite_sets.Sets.*;
public class Main {public static void main(String[] args) {Stream.of(Set.of(1, 2, 3),positiveLongSet(),positiveEvenLongSet(),primeLongSet(),fibonacciLongSet()).forEachOrdered(System.out::println);// This actually completes fast due to identity equalitypositiveLongSet().containsAll(positiveLongSet());}}
这可能会产生以下输出:
[3, 2, 1]
[1, 2, 3, 4, 5, 6, 7, 8, ...]
[2, 4, 6, 8, 10, 12, 14, 16, ...]
[2, 3, 5, 7, 11, 13, 17, 19, ...]
[0, 1, 2, 3, 5, 8, 13, 21, ...]
参与GitHub
这篇文章中的源代码可以在GitHub上找到 。
游戏,设定和比赛…
翻译自: https://www.javacodegeeks.com/2018/06/infinite-sets-java.html