Мне нужно создать Set
с начальными значениями.
Set<String> h = new HashSet<String>();
h.add("a");
h.add("b");
Есть ли способ сделать это в одной строке кода? Например, это полезно для окончательного статического поля.
Мне нужно создать Set
с начальными значениями.
Set<String> h = new HashSet<String>();
h.add("a");
h.add("b");
Есть ли способ сделать это в одной строке кода? Например, это полезно для окончательного статического поля.
У меня есть сокращение, которое я использую, это не очень эффективно, но подходит для одной строки:
Set<String> h = new HashSet<>(Arrays.asList("a", "b"));
Опять же, это не очень эффективно, поскольку вы создаете массив, конвертируете его в список и используете этот список для создания набора.
При инициализации статических окончательных наборов я обычно пишу это следующим образом:
public static final String[] SET_VALUES = new String[] { "a", "b" };
public static final Set<String> MY_SET = new HashSet<>(Arrays.asList(SET_VALUES));
Чуть менее уродливое и эффективность не имеет значения для статической инициализации.
Литералы коллекции были запланированы для Java 7, но не сделали этого. Так что ничего не было автоматическим.
Вы можете использовать guava Sets
:
Sets.newHashSet("a", "b", "c")
Или вы можете использовать следующий синтаксис, который будет создавать анонимный класс, но он взломан:
Set<String> h = new HashSet<String>() {{
add("a");
add("b");
}};
В Java 8 я бы использовал:
Set<String> set = Stream.of("a", "b").collect(Collectors.toSet());
Это дает вам переменную Set
, предварительно инициализированную символами "a" и "b". Обратите внимание, что, хотя в JDK 8 это возвращает a HashSet
, спецификация не гарантирует этого, и это может измениться в будущем. Если вы специально хотите HashSet
, сделайте это вместо этого:
Set<String> set = Stream.of("a", "b")
.collect(Collectors.toCollection(HashSet::new));
Set<String> strSet1 = Stream.of("A", "B", "C", "D")
.collect(Collectors.toUnmodifiableSet());
Здесь сборщик на самом деле возвращает немодифицируемый набор, введенный в Java 9, как видно из инструкции set → (Set<T>)Set.of(set.toArray())
в исходном коде.
Следует отметить, что метод Collections.unmodifiableSet()
возвращает неизменяемое представление указанного набора (согласно документации). Неизменяемая коллекция представлений - это коллекция, которая не может быть изменена, а также является представлением резервной коллекции. Обратите внимание, что изменения в резервной коллекции все еще возможны, и если они происходят, они видны через неизменяемое представление. Но метод Collectors.toUnmodifiableSet()
возвращает действительно неизменный набор в Java 10.
Ниже представлен наиболее компактный способ инициализации набора:
Set<String> strSet6 = Set.of("Apple", "Ball", "Cat", "Dog");
У нас есть 12 перегруженных версий этого метода фабричного удобства:
static <E> Set<E> of()
static <E> Set<E> of(E e1)
static <E> Set<E> of(E e1, E e2)
//....и так далее
static <E> Set<E> of(E... elems)
Тогда возникает естественный вопрос: зачем нам перегруженные версии, когда у нас есть var-args? Ответ таков: каждый метод var-arg создает массив внутри, а наличие перегруженных версий позволит избежать ненужного создания объекта, а также избавит нас от затрат на сборку мусора.
Использование Stream в Java 8.
Set<String> strSet1 = Stream.of("A", "B", "C", "D")
.collect(Collectors.toCollection(HashSet::new));
// stream from an array (String[] stringArray)
Set<String> strSet2 = Arrays.stream(stringArray)
.collect(Collectors.toCollection(HashSet::new));
// stream from a list (List<String> stringList)
Set<String> strSet3 = stringList.stream()
.collect(Collectors.toCollection(HashSet::new));
Использование Collections.unmodifiableSet() - Мы можем использовать Collections.unmodifiableSet()
как:
Set<String> strSet4 = Collections.unmodifiableSet(strSet1);
Но это выглядит немного неловко, и мы можем написать наш собственный сборщик, например:
class ImmutableCollector {
public static <T> Collector<T, Set<T>, Set<T>> toImmutableSet() {
return Collector.of(HashSet::new, Set::add, (l, r) -> {
l.addAll(r);
return l;
}, Collections::unmodifiablSet);
}
}
И затем используйте это как:
Set<String> strSet4 = Stream.of("A", "B", "C", "D")
.collect(ImmutableCollector.toImmutableSet());
Другой подход заключается в использовании метода Collectors.collectingAndThen()
который позволяет нам выполнять дополнительные завершающие преобразования:
import static java.util.stream.Collectors.*;
Set<String> strSet5 = Stream.of("A", "B", "C", "D").collect(collectingAndThen(
toCollection(HashSet::new),Collections::unmodifiableSet));
Если мы заботимся только о Set
то мы также можем использовать Collectors.toSet()
вместо Collectors.toCollection(HashSet::new)
.
Существует несколько способов:
Инициализация двойной скобки
Это метод, который создает анонимный внутренний класс, который имеет инициализатор экземпляра, который добавляет String
к себе при создании экземпляра:
Set<String> s = new HashSet<String>() {{
add("a");
add("b");
}}
Имейте в виду, что это фактически создаст новый подкласс HashSet
каждый раз, когда он будет использоваться, хотя для него не требуется явно написать новый подкласс.
Утилитный метод
Написание метода, возвращающего Set
, который инициализируется нужными элементами, не так сложно записать:
public static Set<String> newHashSet(String... strings) {
HashSet<String> set = new HashSet<String>();
for (String s : strings) {
set.add(s);
}
return set;
}
В приведенном выше коде используется только String
, но не должно быть слишком сложно разрешить использование любого типа с использованием дженериков.
Использовать библиотеку
Во многих библиотеках есть метод удобства инициализации объектов коллекций.
Например, Google Collections имеет Sets.newHashSet(T...)
, который заполнит элемент HashSet
элементами определенного типа.
Если у вас есть только одно значение и вы хотите получить неизменный набор, этого будет достаточно:
Set<String> immutableSet = Collections.singleton("a");
Вы можете сделать это на Java 6:
Set<String> h = new HashSet<String>(Arrays.asList("a", "b", "c"));
Но почему? Я не считаю его более читаемым, чем явное добавление элементов.
Одним из наиболее удобных способов является использование универсального метода Collections.addAll(), который принимает коллекцию и varargs:
Set<String> h = new HashSet<String>();
Collections.addAll(h, "a", "b");
Я считаю, что наиболее читаемым является просто использовать google Guava:
Set<String> StringSet = Sets.newSet("a", "b", "c");
С Java 9 вы можете сделать следующее:
Set.of("a", "b");
и вы получите неизменный набор, содержащий элементы. Подробности см. В документации Oracle интерфейса Set.
Обобщение функции утилиты coobird answer для создания нового HashSet
s:
public static <T> Set<T> newHashSet(T... objs) {
Set<T> set = new HashSet<T>();
for (T o : objs) {
set.add(o);
}
return set;
}
Если содержащийся тип Set является перечислением, тогда существует Java-метод factory (начиная с версии 1.5):
Set<MY_ENUM> MY_SET = EnumSet.of( MY_ENUM.value1, MY_ENUM.value2, ... );
С Eclipse Collections существует несколько различных способов инициализации Set
, содержащего символы 'a' и 'b' в одном выражении, Eclipse Collections имеет контейнеры как для объектных, так и для примитивных типов, поэтому я проиллюстрировал, как вы можете использовать Set<String>
или CharSet
в дополнение к изменяемым, неизменяемые, синхронизированные и неизменяемые версии обоих.
Set<String> set =
Sets.mutable.with("a", "b");
HashSet<String> hashSet =
Sets.mutable.with("a", "b").asLazy().into(new HashSet<String>());
Set<String> synchronizedSet =
Sets.mutable.with("a", "b").asSynchronized();
Set<String> unmodifiableSet =
Sets.mutable.with("a", "b").asUnmodifiable();
MutableSet<String> mutableSet =
Sets.mutable.with("a", "b");
MutableSet<String> synchronizedMutableSet =
Sets.mutable.with("a", "b").asSynchronized();
MutableSet<String> unmodifiableMutableSet =
Sets.mutable.with("a", "b").asUnmodifiable();
ImmutableSet<String> immutableSet =
Sets.immutable.with("a", "b");
ImmutableSet<String> immutableSet2 =
Sets.mutable.with("a", "b").toImmutable();
CharSet charSet =
CharSets.mutable.with('a', 'b');
CharSet synchronizedCharSet =
CharSets.mutable.with('a', 'b').asSynchronized();
CharSet unmodifiableCharSet =
CharSets.mutable.with('a', 'b').asUnmodifiable();
MutableCharSet mutableCharSet =
CharSets.mutable.with('a', 'b');
ImmutableCharSet immutableCharSet =
CharSets.immutable.with('a', 'b');
ImmutableCharSet immutableCharSet2 =
CharSets.mutable.with('a', 'b').toImmutable();
Eclipse Collections совместим с Java 5-8.
Примечание. Я являюсь коммиттером для коллекций Eclipse.
import com.google.common.collect.Sets;
Sets.newHashSet("a", "b");
или
import com.google.common.collect.ImmutableSet;
ImmutableSet.of("a", "b");
Немного свернуто, но работает с Java 5:
Set<String> h = new HashSet<String>(Arrays.asList(new String[] {
"a", "b"
}))
Используйте вспомогательный метод, чтобы сделать его доступным для чтения:
Set<String> h = asSet ("a", "b");
public Set<String> asSet(String... values) {
return new HashSet<String>(java.util.Arrays.asList(values));
}
(уродливый) Инициализация двойного брака без побочных эффектов:
Set<String> a = new HashSet<>(new HashSet<String>() {{
add("1");
add("2");
}})
Но в некоторых случаях, если бы мы упоминали, что это хороший запах, чтобы сделать окончательные коллекции неизменяемыми, это может быть действительно полезно:
final Set<String> a = Collections.unmodifiableSet(new HashSet<String>(){{
add("1");
add("2");
}})
Просто небольшая заметка, независимо от того, какой из прекрасных подходов упоминается здесь, вы в конечном итоге, если это по умолчанию, которое обычно немодифицировано (например, значение по умолчанию в создаваемой библиотеке), это хорошая идея следуйте этому шаблону:
// Initialize default values with the method you prefer, even in a static block
// It a good idea to make sure these defaults aren't modifiable
private final static Set<String> DEFAULT_VALUES = Collections.unmodifiableSet(...);
private Set<String> values = DEFAULT_VALUES;
Преимущество зависит от количества экземпляров, созданных вами в этом классе, и того, насколько вероятно, что значения по умолчанию будут изменены.
Если вы решите следовать этому шаблону, вы также можете выбрать способ инициализации набора, который наиболее читабельен. Поскольку микроразличия в эффективности между различными методами, вероятно, не будут иметь большого значения, поскольку вы будете инициализировать набор только один раз.
Используя Java 8, мы можем создать HashSet
как:
Stream.of("A", "B", "C", "D").collect(Collectors.toCollection(HashSet::new));
И если мы хотим немодифицируемый набор, мы можем создать вспомогательный метод как:
public static <T, A extends Set<T>> Collector<T, A, Set<T>> toImmutableSet(Supplier<A> supplier) {
return Collector.of(
supplier,
Set::add, (left, right) -> {
left.addAll(right);
return left;
}, Collections::unmodifiableSet);
}
Этот метод может быть использован как:
Stream.of("A", "B", "C", "D").collect(toImmutableSet(HashSet::new));
С выпуском java9 и удобными фабричными методами это стало более понятным:
Set set = Set.of("a", "b", "c");
Может использовать статический блок для инициализации:
private static Set<Integer> codes1=
new HashSet<Integer>(Arrays.asList(1, 2, 3, 4));
private static Set<Integer> codes2 =
new HashSet<Integer>(Arrays.asList(5, 6, 7, 8));
private static Set<Integer> h = new HashSet<Integer>();
static{
h.add(codes1);
h.add(codes2);
}
Это элегантное решение:
public static final <T> Set<T> makeSet(@SuppressWarnings("unchecked") T... o) {
return new HashSet<T>() {
private static final long serialVersionUID = -3634958843858172518L;
{
for (T x : o)
add(x);
}
};
}
Здесь может быть полезен шаблон Builder. Сегодня у меня была такая же проблема. где мне нужно, чтобы операции мутации Set возвращали мне ссылку на объект Set, чтобы я мог передать его конструктору суперкласса, чтобы они тоже могли продолжить добавление к тому же набору, в свою очередь создавая новый StringSetBuilder из Set, который является дочерним классом только что построен Класс построителя, который я написал, выглядит следующим образом (в моем случае это статический внутренний класс внешнего класса, но он также может быть и своим независимым классом):
public interface Builder<T> {
T build();
}
static class StringSetBuilder implements Builder<Set<String>> {
private final Set<String> set = new HashSet<>();
StringSetBuilder add(String pStr) {
set.add(pStr);
return this;
}
StringSetBuilder addAll(Set<String> pSet) {
set.addAll(pSet);
return this;
}
@Override
public Set<String> build() {
return set;
}
}
Обратите внимание на addAll()
и add()
, которые устанавливают возвращающие аналоги Set.add()
и Set.addAll()
. Наконец обратите внимание на метод build()
, который возвращает ссылку на Set, который инкапсулирует построитель. Ниже показано, как использовать этот конструктор множеств:
class SomeChildClass extends ParentClass {
public SomeChildClass(String pStr) {
super(new StringSetBuilder().add(pStr).build());
}
}
class ParentClass {
public ParentClass(Set<String> pSet) {
super(new StringSetBuilder().addAll(pSet).add("my own str").build());
}
}
Комбинируя ответ Майкл Бердышев с Дженерики и с помощью конструктора с initialCapacity, по сравнению с Arrays.asList
варианта:
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@SafeVarargs
public static <T> Set<T> buildSetModif(final T... values) {
final Set<T> modifiableSet = new HashSet<T>(values.length);
Collections.addAll(modifiableSet, values);
return modifiableSet;
}
@SafeVarargs
public static <T> Set<T> buildSetModifTypeSafe(final T... values) {
return new HashSet<T>(Arrays.asList(values));
}
@SafeVarargs
public static <T> Set<T> buildeSetUnmodif(final T... values) {
return Collections.unmodifiableSet(buildSetModifTypeSafe(values));
// Or use Set.of("a", "b", "c") if you use Java 9
}
buildSetModif
типы с buildSetModif
ли T ? extends Object
? extends Object
, что, вероятно, не то, что вам нужно, это не может произойти с вариантом buildSetModifTypeSafe
, что означает, что buildSetModifTypeSafe(1, 2, "a");
не будет компилироваться