Множество
Set — это набор элементов, в котором любой данный элемент в Set появляется только один раз.
Более формально, множество не содержит пары элементов e1 и e2 , для которых e1.equals(e2) .
Мы можем легко создать Set в Java 9 следующим образом:
|
1
2
|
final Set<Integer> s = Set.of(1, 2, 3);System.out.println(s); |
Это может привести к следующему выводу:
|
1
|
[2, 3, 1] |
Произведенное выше Set является неизменным, то есть оно не может изменяться, и оно также конечно, потому что в Set есть различное количество элементов, а именно три. Порядок, в котором элементы возвращаются через его методы чтения (такие как stream() , iterator() и forEach() ), не указан.
Бесконечный набор
Бесконечный набор содержит неограниченное количество элементов. Одним примером бесконечного набора является набор всех целых чисел […, -1, 0, 1, 2,…], где целое число не является классом Java Integer а является целым числом в соответствии с математическим определением целого числа, посредством которого существует всегда большее целое число n + 1 для любого заданного целого числа n.
Существует множество бесконечных множеств, таких как множество всех простых чисел, множество четных целых чисел, множество чисел Фибоначчи и т. Д.
По понятным причинам мы не можем предварительно вычислить и сохранить все элементы бесконечного Set Java. Если мы попробуем, у нас в конечном итоге не хватит памяти
Фундаментальный вопрос, который мы должны задать себе: есть ли на самом деле бесконечные множества для типов Java, которые у нас есть? Если у нас есть Set<Byte> то в Set<Byte> не более 256 элементов, и это далеко не бесконечно, то же самое относится и к Short и даже к Integer . В конце концов, существует всего около четырех миллиардов различных объектов Integer и если бы мы использовали битовую карту для представления членства, мы могли бы поместить Set<Integer> всего в 0,5 ГБ. Хоть и большой, но не бесконечный.
Но если мы говорим об элементах Long или String , мы приближаемся, по крайней мере, к практически бесконечным множествам. Для хранения растрового изображения всех длинных потребуется количество ПБ внутренней памяти. Истинный бесконечный Set — это Set String со всеми возможными комбинациями символов [az] любой длины.
Прежде чем мы продолжим, я хотел бы отметить, что код в этом посте также доступен на GitHub, как описано в самом конце поста.
ImmutableStreamSet
Чтобы отойти от парадигмы, в которой мы храним элементы Set , мы могли бы создать ImmutableStreamSet который определяет элементы Set только через его метод stream() . ImmutableStreamSet может быть определен как FunctionalInterface следующим образом:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
@FunctionalInterfacepublic interface ImmutableStreamSet<E> extends Set<E> { // This is the only method we need to implements @Override public Stream<E> stream(); @Override default int size() { return (int) stream().limit(Integer.MAX_VALUE).count(); } @Override default boolean contains(Object o) { return stream().anyMatch(e -> Objects.equals(e, o)); } @Override default boolean containsAll(Collection<?> c) { return (this == c) ? true : c.stream().allMatch(this::contains); } @Override default boolean isEmpty() { return !stream().findAny().isPresent(); } @Override default <T> T[] toArray(T[] a) { return stream().collect(toList()).toArray(a); } @Override default Object[] toArray() { return stream().toArray(); } @Override default Spliterator<E> spliterator() { return stream().spliterator(); } @Override default Iterator<E> iterator() { return stream().iterator(); } @Override default Stream<E> parallelStream() { return stream().parallel(); } @Override default void forEach(Consumer<? super E> action) { stream().forEach(action); } // We are immutable @Override default boolean removeIf(Predicate<? super E> filter) { throw new UnsupportedOperationException(); } @Override default void clear() { throw new UnsupportedOperationException(); } @Override default boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override default boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override default boolean addAll(Collection<? extends E> c) { throw new UnsupportedOperationException(); } @Override default boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override default 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 implemented return new ImmutableStreamSetImpl<>(supplier); }} |
Круто, теперь мы можем создавать бесконечные множества, просто предоставив поставщика потоков, например так:
|
1
2
|
ImmutableStreamSet<Long> setOfAllLong = LongStream.rangeClosed(Long.MIN_VALUE, Long.MAX_VALUE)::boxed; |
Это создаст Set всех Long значений (например, с 2 ^ 64 элементами). При предоставлении поставщика потока обязательно убедитесь, что он придерживается свойства Set уникальности элемента. Рассмотрим следующий незаконный набор:
|
1
2
|
final ImmutableStreamSet<Long> illegalSet = () -> Stream.of(1l, 2l, 1l); |
Ясно, что 11 встречается два раза в наборе, что заставляет этот объект нарушать требования набора.
Как мы увидим, было бы лучше создать конкретные классы бесконечных множеств, которые мы рассматриваем. Одна конкретная проблема, связанная с реализацией по умолчанию выше, заключается в том, что метод contains() может быть очень медленным. Прочитайте следующие главы и узнайте, почему и как это решить.
PositiveLongSet
Давайте предположим, что мы хотим создать Set со всеми положительными длинными значениями и хотим, чтобы мы могли эффективно использовать Set с другими наборами и объектами. Вот как мы можем это сделать:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
public final class PositiveLongSet implements ImmutableStreamSet<Long> { public static final PositiveLongSet INSTANCE = new PositiveLongSet(); private PositiveLongSet() { } @Override public Stream<Long> stream() { return LongStream.rangeClosed(1, Long.MAX_VALUE).boxed(); } @Override public int size() { return Integer.MAX_VALUE; } @Override public boolean contains(Object o) { return SetUtil.contains(this, Long.class, other -> other > 0, o); } @Override public boolean isEmpty() { return false; } @Override public String toString() { return SetUtil.toString(this); }} |
Обратите внимание, как мы соблюдаем формальное требование в методе size() где мы возвращаем Integer.MAX_VALUE хотя Set намного больше. Если бы Set был определен сегодня, вполне вероятно, что size() вернул бы long вместо int . Но в начале 90-х годов объем внутренней оперативной памяти обычно составлял менее 1 ГБ. Мы используем два служебных метода в классе:
SetUtil.toString() принимает Set , перебирает первые восемь элементов и возвращает String представление этих элементов.
Метод SetUtil.contains() принимает Set , класс типа Element (здесь Long.class ) и Predicate который вызывается, если объект, который мы сравниваем, имеет заданный класс типа Element (если объект, с которым мы сравниваем, является null или другого типа, тогда тривиально Set не содержит объект).
Вот как выглядит SetUtil :
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
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); }} |
Вооружившись классами SetUtil и SetUtil мы теперь можем легко создавать другие бесконечные наборы, такие как PostitiveEvenLongSet (не показан ниже, попробуйте написать его самостоятельно), PrimeLongSet (содержащий все простые числа, которые могут быть представлены Long ) и даже FibonacciLongSet (содержащий все числа FibonacciLongSet что может быть представлено Long ). Вот как могут выглядеть эти классы:
PrimeLongSet
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
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); @Override public Stream<Long> stream() { return LongStream.rangeClosed(2, Long.MAX_VALUE) .filter(IS_PRIME) .boxed(); } @Override public int size() { return Integer.MAX_VALUE; } @Override public boolean contains(Object o) { return SetUtil.contains(this, Long.class, IS_PRIME::test, o); } @Override public boolean isEmpty() { return false; } @Override public String toString() { return SetUtil.toString(this); }} |
FibonacciLongSet
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
public final class FibonacciLongSet implements ImmutableStreamSet<Long> { public static final FibonacciLongSet INSTANCE = new FibonacciLongSet(); private FibonacciLongSet() { } @Override public 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() ); } @Override public int size() { return 92; } @Override public boolean contains(Object o) { return SetUtil.contains( this, Long.class, other -> stream().anyMatch(fib -> Objects.equals(fib, other)), o ); } @Override public boolean isEmpty() { return false; } @Override public 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 чтобы разорвать поток, когда long оборачивается в отрицательное значение. Возможно, мы «обманываем», когда выполняем предварительные вычисления и предоставляем размер 92, но в противном случае size() был бы немного медленнее.
Сшить все это
Предоставляя интерфейс со статическими поставщиками для экземпляров этих классов, мы можем инкапсулировать наши предопределенные наборы и убедиться, что в JVM есть только один их экземпляр:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
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» (который не выставляется ).
Вот module-info.java может выглядеть наш module-info.java при условии, что два открытых класса находятся в com.speedment.infinite_sets а классы реализации — в пакете, подобном com.speedment.infinite_sets.internal :
module-info.java
|
1
2
3
|
module com.speedment.infinite_sets { exports com.speedment.infinite_sets;} |
Пробовать
Теперь мы можем создать еще один модуль, который использует наши бесконечные множества, сначала объявив об использовании нашего существующего модуля следующим образом:
module-info.java
|
1
2
3
|
module Infinite_sets_app { requires com.speedment.infinite_sets;} |
И тогда у нас есть доступ к открытым частям модуля. Вот один из способов опробовать бесконечные множества:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
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 equality positiveLongSet().containsAll(positiveLongSet()); }} |
Это может привести к следующему выводу:
|
1
2
3
4
5
|
[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 здесь .
Игра, сет и матч …
| Смотрите оригинальную статью здесь: Бесконечные множества в Java 9
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |