[java] 세트에서 요소 얻기

Set다른 요소와 동일한 요소를 가져 오는 작업을 제공 하지 않는 이유는 무엇 입니까?

Set<Foo> set = ...;
...
Foo foo = new Foo(1, 2, 3);
Foo bar = set.get(foo);   // get the Foo element from the Set that equals foo

Set같은 요소 가 포함되어 있는지 물어볼 수 있는데 bar왜 해당 요소를 얻을 수 없습니까? 🙁

명확히하기 위해 equals메서드가 재정의되지만 필드 중 하나만 검사하지는 않습니다. 따라서 Foo동일한 것으로 간주되는 두 객체는 실제로 다른 값을 가질 수 있으므로을 사용할 수 없습니다 foo.



답변

요소가 같으면 요소를 가져올 필요가 없습니다. Map이 사용 사례에 A 가 더 적합합니다.


여전히 요소를 찾으려면 반복자를 사용하는 것 외에 다른 옵션이 없습니다.

public static void main(String[] args) {

    Set<Foo> set = new HashSet<Foo>();
    set.add(new Foo("Hello"));

    for (Iterator<Foo> it = set.iterator(); it.hasNext(); ) {
        Foo f = it.next();
        if (f.equals(new Foo("Hello")))
            System.out.println("foo found");
    }
}

static class Foo {
    String string;
    Foo(String string) {
        this.string = string;
    }
    @Override
    public int hashCode() {
        return string.hashCode();
    }
    @Override
    public boolean equals(Object obj) {
        return string.equals(((Foo) obj).string);
    }
}


답변

” 다른 요소와 같은 요소를 얻기 위해 작업을 제공 하지 않는 이유무엇입니까? “라는 정확한 질문에 대답 Set하기 위해, 컬렉션 프레임 워크의 디자이너가 매우 기대하지 않았기 때문에 대답은 다음과 같습니다. 그들은 매우 합법적 인 사용 사례를 예상하지 않았고, 순진하게 “수학적 집합 추상화”(javadoc에서)를 모델링하려고 시도했으며 단순히 유용한 get()방법 을 추가하는 것을 잊었습니다 .

이제 ” 어떻게 요소를 얻는가? “라는 묵시적 질문으로 , 가장 좋은 해결책은 요소 Map<E,E>대신에 Set<E>요소를 매핑하는 것입니다. 이런 식으로 “set”에서 요소를 효율적으로 검색 할 수 있습니다 . 왜냐하면 get () 메소드 Map는 효율적인 해시 테이블 또는 트리 알고리즘을 사용하여 요소를 찾기 때문입니다. 원하는 경우을 캡슐화하여 Set추가 get()메소드 를 제공하는 자체 구현을 작성할 수 있습니다 Map.

다음 대답은 내 의견으로는 나쁘거나 잘못되었습니다.

“이미 같은 객체를 가지고 있기 때문에 요소를 얻을 필요가 없습니다”: 질문에서 이미 보여 주듯이 주장이 잘못되었습니다. 동일한 두 개체는 여전히 개체 동등성과 관련이없는 다른 상태를 가질 수 있습니다. 목표는 Set“쿼리”로 사용되는 객체의 상태가 아니라에 포함 된 요소의이 상태에 액세스하는 것 입니다.

“반복자를 사용하는 것 외에는 다른 옵션이 없습니다.”: 대규모 집합에 대해 전혀 비효율적 인 컬렉션에 대한 선형 검색 Set입니다. 하지마! 이 방법을 사용하여 실제 시스템에서 심각한 성능 문제가 발생했습니다. 내 의견으로는 누락 된 get()방법 에 대해 끔찍한 것은 해결하기가 다소 번거롭지 않지만 대부분의 프로그래머는 의미를 생각하지 않고 선형 검색 방법을 사용한다는 것입니다.


답변

같은 물건을 가지고 있다면 왜 세트의 물건이 필요합니까? 키만으로 “동일” Map하면 더 나은 선택이 될 것입니다.

어쨌든 다음과 같은 작업이 수행됩니다.

Foo getEqual(Foo sample, Set<Foo> all) {
  for (Foo one : all) {
    if (one.equals(sample)) {
      return one;
    }
  }
  return null;
}

Java 8에서는 이것이 하나의 라이너가 될 수 있습니다.

return all.stream().filter(sample::equals).findAny().orElse(null);


답변

세트를리스트로 변환 한 후 get리스트의 방법 을 사용

Set<Foo> set = ...;
List<Foo> list = new ArrayList<Foo>(set);
Foo obj = list.get(0);


답변

불행히도 Java의 기본 설정은 jschreiner가 정확하게 설명했듯이 “get”작업을 제공하도록 설계되지 않았습니다 .

반복자를 사용하여 관심있는 요소를 찾 거나 ( dacwe제안한 ) 요소를 제거하고 업데이트 된 값으로 Kyle 요소가 제안한 값을 다시 추가하는 솔루션 이 작동 할 수는 있지만 매우 비효율적 일 수 있습니다.

등호 구현을 재정의하면 David Ogren이 올바르게 언급 한 것처럼 동일하지 않은 객체가 “동일”하므로 유지 관리 문제가 쉽게 발생할 수 있습니다.

imho는 Map을 명시적인 대체물로 사용하여 코드를 덜 우아하게 만듭니다.

목표가 세트에 포함 된 요소의 원래 인스턴스에 액세스하는 것입니다 (유스 케이스를 올바르게 이해했으면합니다). 또 다른 가능한 해결책이 있습니다.


Java로 클라이언트 서버 비디오 게임을 개발하는 동안 개인적으로 동일한 요구가있었습니다. 필자의 경우 각 클라이언트에는 서버에 저장된 구성 요소의 사본이 있었으며 문제는 클라이언트가 서버의 객체를 수정해야 할 때마다 발생했습니다.

인터넷을 통해 객체를 전달한다는 것은 클라이언트가 해당 객체의 다른 인스턴스를 가지고 있음을 의미했습니다. 이 “복사 된”인스턴스를 원래 인스턴스와 일치시키기 위해 Java UUID를 사용하기로 결정했습니다.

그래서 추상 클래스 UniqueItem을 만들었습니다.이 클래스는 하위 클래스의 각 인스턴스에 임의의 고유 ID를 자동으로 부여합니다.

이 UUID는 클라이언트와 서버 인스턴스간에 공유되므로 맵을 사용하여 쉽게 일치시킬 수 있습니다.

그러나 유사한 유스 케이스에서 맵을 직접 사용하는 것은 여전히 ​​우아하지 않았습니다. 누군가지도를 사용하는 것은 관리하고 다루기가 더 복잡 할 수 있다고 주장 할 수 있습니다.

이러한 이유로 저는 MagicSet이라는 라이브러리를 구현하여 개발자에게 맵을 “투명하게”만듭니다.

https://github.com/ricpacca/magicset


원래 Java HashSet과 마찬가지로 MagicHashSet (라이브러리에서 제공되는 MagicSet의 구현 중 하나임)은 지원 HashMap을 사용하지만 요소를 키로, 더미 값을 값으로 사용하는 대신 요소의 UUID를 키로 사용합니다. 그리고 요소 자체는 가치입니다. 일반적인 HashSet에 비해 메모리 사용에 오버 헤드가 발생하지 않습니다.

또한 MagicSet을 Set으로 정확하게 사용할 수 있지만 getFromId (), popFromId (), removeFromId () 등과 같은 추가 기능을 제공하는 몇 가지 메소드가 있습니다.

이를 사용하기위한 유일한 요구 사항은 MagicSet에 저장하려는 모든 요소가 추상 클래스 UniqueItem을 확장해야한다는 것입니다.


다음은 동일한 UUID (또는 UUID)를 가진 해당 도시의 다른 인스턴스가 제공된 경우 MagicSet에서 도시의 원래 인스턴스를 검색하는 코드 예제입니다.

class City extends UniqueItem {

    // Somewhere in this class

    public void doSomething() {
        // Whatever
    }
}

public class GameMap {
    private MagicSet<City> cities;

    public GameMap(Collection<City> cities) {
        cities = new MagicHashSet<>(cities);
    }

    /*
     * cityId is the UUID of the city you want to retrieve.
     * If you have a copied instance of that city, you can simply
     * call copiedCity.getId() and pass the return value to this method.
     */
    public void doSomethingInCity(UUID cityId) {
        City city = cities.getFromId(cityId);
        city.doSomething();
    }

    // Other methods can be called on a MagicSet too
}


답변

세트가 실제로 a NavigableSet<Foo>(예 : a TreeSet) 인 Foo implements Comparable<Foo>경우

Foo bar = set.floor(foo); // or .ceiling
if (foo.equals(bar)) {
    // use bar…
}

(힌트에 대한 @ eliran-malka의 의견에 감사드립니다.)


답변

Java 8을 사용하면 다음을 수행 할 수 있습니다.

Foo foo = set.stream().filter(item->item.equals(theItemYouAreLookingFor)).findFirst().get();

그러나 .get ()은 NoSuchElementException을 발생 시키거나 Optional 항목을 조작 할 수 있습니다.