Lấy một phần tử từ Tập hợp


323

Tại sao không Setcung cấp một hoạt động để có được một yếu tố bằng với một yếu tố khác?

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

Tôi có thể hỏi liệu Setphần tử có chứa phần tử bằng không bar, vậy tại sao tôi không thể có phần tử đó? :

Để làm rõ, equalsphương thức được ghi đè, nhưng nó chỉ kiểm tra một trong các trường, không phải tất cả. Vì vậy, hai Foođối tượng được coi là bằng nhau thực sự có thể có các giá trị khác nhau, đó là lý do tại sao tôi không thể sử dụng foo.


2
Bài đăng này đã được thảo luận rộng rãi, và câu trả lời tốt đã được đề xuất. Tuy nhiên, nếu bạn chỉ tìm kiếm một bộ được đặt hàng, chỉ cần sử dụng SortedSetvà triển khai nó, dựa trên bản đồ (ví dụ: TreeSetcho phép truy cập first()).
Eliran Malka

3
Tôi cũng nhớ phương pháp đó, chính xác cho trường hợp tương tự như bạn đã mô tả ở trên. Objective-C ( NSSet) có phương thức như vậy. Nó được gọi membervà nó trả về đối tượng trong tập so sánh "bằng" với tham số của memberphương thức (tất nhiên có thể là một đối tượng khác và cũng có các thuộc tính khác nhau, có thể không kiểm tra bằng nhau).
Mecki

Câu trả lời:


118

Sẽ không có điểm nào nhận được phần tử nếu nó bằng nhau. A Maplà phù hợp hơn cho usecase này.


Nếu bạn vẫn muốn tìm phần tử, bạn không có tùy chọn nào khác ngoài sử dụng iterator:

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);
    }
}

234
Hoàn toàn có thể có một điểm để có được yếu tố. Điều gì xảy ra nếu bạn muốn cập nhật một số giá trị của thành phần sau khi nó đã được thêm vào Tập hợp? Ví dụ: khi .equals () không sử dụng tất cả các trường, như OP đã chỉ định. Một giải pháp kém hiệu quả hơn là loại bỏ phần tử và thêm lại nó với các giá trị được cập nhật.
KyleM

14
Tôi vẫn sẽ tranh luận rằng một Mapphù hợp hơn ( Map<Foo, Foo>trong trường hợp này.)
dacwe

22
@dacwe, tôi đến đây vì tôi bắt đầu tìm cách tránh chính xác điều đó! Một đối tượng hoạt động, đồng thời, cả dưới dạng khóa và giá trị tương ứng là chính xác những gì một tập hợp nên có. Trong trường hợp của tôi, tôi muốn lấy một số đối tượng phức tạp từ một tập hợp theo khóa (Chuỗi). Chuỗi này được gói gọn (và duy nhất) cho đối tượng được ánh xạ. Trên thực tế, toàn bộ đối tượng 'xoay quanh' phím đã nói. Hơn nữa, người gọi biết String, nhưng không phải chính đối tượng; đó chính xác là lý do tại sao nó muốn lấy nó bằng chìa khóa. Hiện tại tôi đang sử dụng Bản đồ, nhưng nó vẫn là hành vi kỳ quặc.
pauluss86

4
@KyleM Tôi hiểu trường hợp sử dụng, nhưng tôi muốn nhấn mạnh tầm quan trọng của việc không chạm vào các thuộc tính là một phần của hashCode / bằng. Từ Tập hợp Javadoc: "Lưu ý: Phải hết sức cẩn thận nếu các đối tượng có thể thay đổi được sử dụng làm phần tử tập hợp. Hành vi của tập hợp không được chỉ định nếu giá trị của đối tượng bị thay đổi theo cách ảnh hưởng đến so sánh trong khi đối tượng là một yếu tố trong tập hợp. " - Tôi khuyên các đối tượng đó là bất biến, hoặc ít nhất là có các thuộc tính khóa bất biến.
stivlo

5
Tôi đồng ý rằng bạn có thể sử dụng Map<Foo, Foo>thay thế, nhược điểm là bản đồ luôn phải lưu trữ ít nhất một khóa và giá trị (và để thực hiện, nó cũng nên lưu trữ hàm băm), trong khi một bộ có thể thoát khỏi việc lưu trữ giá trị (và có thể băm cho hiệu suất). Vì vậy, một cài đặt tốt có thể nhanh như nhau Map<Foo, Foo>nhưng sử dụng bộ nhớ ít hơn tới 50%. Trong trường hợp Java, điều đó không thành vấn đề, vì Hashset được dựa trên nội bộ dựa trên HashMap.
Mecki

372

Để trả lời câu hỏi chính xác " Tại sao không Setcung cấp thao tác để lấy phần tử bằng với phần tử khác?", Câu trả lời sẽ là: bởi vì các nhà thiết kế của bộ sưu tập không mong muốn lắm. Họ đã không lường trước trường hợp sử dụng rất hợp pháp của bạn, đã cố gắng "mô hình hóa sự trừu tượng hóa toán học" (từ javadoc) và chỉ đơn giản là quên thêm get()phương thức hữu ích .

Bây giờ với câu hỏi ngụ ý " làm thế nào để bạn có được phần tử sau đó": Tôi nghĩ giải pháp tốt nhất là sử dụng Map<E,E>thay vì a Set<E>, để ánh xạ các phần tử cho chính chúng. Theo cách đó, bạn có thể truy xuất một phần tử một cách hiệu quả từ "set", bởi vì phương thức get () của Mapphần tử sẽ tìm phần tử bằng cách sử dụng bảng băm hoặc thuật toán cây hiệu quả. Nếu bạn muốn, bạn có thể viết triển khai của riêng bạn Setcung cấp get()phương thức bổ sung , đóng gói Map.

Các câu trả lời sau đây theo tôi là xấu hay sai:

"Bạn không cần phải lấy phần tử, vì bạn đã có một đối tượng bằng nhau": khẳng định đó là sai, như bạn đã thể hiện trong câu hỏi. Hai đối tượng bằng nhau vẫn có thể có trạng thái khác nhau không liên quan đến đẳng thức đối tượng. Mục tiêu là để có quyền truy cập vào trạng thái này của phần tử có trong Set, chứ không phải trạng thái của đối tượng được sử dụng làm "truy vấn".

"Bạn không có lựa chọn nào khác ngoài sử dụng trình lặp": đó là một tìm kiếm tuyến tính trên một bộ sưu tập hoàn toàn không hiệu quả đối với các tập hợp lớn (trớ trêu thay, bên trong Setđược tổ chức dưới dạng bản đồ băm hoặc cây có thể được truy vấn hiệu quả). Đừng làm thế! Tôi đã thấy các vấn đề hiệu suất nghiêm trọng trong các hệ thống thực tế bằng cách sử dụng phương pháp đó. Theo tôi, điều tồi tệ về get()phương pháp còn thiếu không phải là quá phức tạp khi làm việc xung quanh nó, nhưng hầu hết các lập trình viên sẽ sử dụng phương pháp tìm kiếm tuyến tính mà không nghĩ đến các hàm ý.


27
ồ Ghi đè việc thực hiện bằng để các đối tượng không bằng nhau là "bằng nhau" là vấn đề ở đây. Yêu cầu một phương thức có nội dung "đưa tôi đối tượng giống hệt với đối tượng này", và sau đó mong đợi một đối tượng không giống nhau được trả về có vẻ điên rồ và dễ gây ra sự cố bảo trì. Như những người khác đã đề xuất, sử dụng bản đồ sẽ giải quyết tất cả những vấn đề này: và nó làm cho những gì bạn đang tự giải thích. Thật dễ hiểu khi hai đối tượng không bằng nhau có thể có cùng một khóa trong bản đồ và có cùng một khóa sẽ cho thấy mối quan hệ giữa chúng.
David Ogren

20
Những từ mạnh mẽ, @David Ogren. Hả? Khùng? Nhưng trong nhận xét của bạn, bạn đang sử dụng các từ "giống hệt" và "bằng nhau" như thể chúng có cùng một ý nghĩa. Họ không. Cụ thể, trong Java, danh tính được biểu thị bằng toán tử "==" và đẳng thức được biểu thị bằng phương thức equals (). Nếu chúng có cùng ý nghĩa, thì sẽ không cần phương thức equals () nào cả. Trong các ngôn ngữ khác, điều này tất nhiên có thể khác nhau. Ví dụ: trong Groovy, danh tính là phương thức is () và đẳng thức là "==". Buồn cười phải không?
jschreiner

15
Sự chỉ trích của bạn về việc tôi sử dụng từ giống hệt nhau khi tôi nên sử dụng từ tương đương là rất hợp lệ. Nhưng việc xác định bằng nhau trên một đối tượng sao cho Foo và Bar "bằng nhau" nhưng không "đủ bằng nhau" để anh ta sử dụng chúng một cách tương đương sẽ tạo ra tất cả các loại vấn đề cả về chức năng và khả năng đọc / bảo trì. Vấn đề này với Set chỉ là phần nổi của tảng băng cho các vấn đề tiềm ẩn. Ví dụ, các đối tượng bằng nhau phải có mã băm bằng nhau. Vì vậy, anh ấy sẽ có va chạm băm tiềm năng. Có phải thật điên rồ khi đối tượng gọi .get (foo) đặc biệt để có được thứ gì đó ngoài foo?
David Ogren

12
Có lẽ đáng lưu ý rằng, ví dụ, Hashset được triển khai như một trình bao bọc xung quanh HashMap (ánh xạ các khóa thành một giá trị giả). Vì vậy, sử dụng HashMap một cách rõ ràng thay vì Hashset sẽ không gây ra chi phí sử dụng bộ nhớ.
Alexey B.

4
@ user686249 Tôi cảm thấy như điều này đã biến thành một cuộc tranh luận học thuật. Tôi thừa nhận rằng tôi có thể đã quá nhiệt tình trong việc phản đối việc ghi đè bằng. Đặc biệt là trong việc sử dụng như của bạn. Nhưng, tôi vẫn phản đối ý tưởng gọi phương thức này get(). Trong ví dụ của bạn, tôi sẽ rất bối rối bởi khách hàng Set.get (thisCustomer). (Trong khi đó, một Bản đồ, như được đề xuất bởi nhiều câu trả lời) sẽ rất ổn với canonicalCustomerMap.get (khách hàng này). Tôi cũng sẽ ổn với một phương thức được đặt tên rõ ràng hơn (như phương thức thành viên của Objective-C trên NSSet).
David Ogren

19

Nếu bạn có một đối tượng bằng nhau, tại sao bạn cần một đối tượng từ tập hợp? Nếu nó "bằng" chỉ bằng một phím, Mapthì đó sẽ là lựa chọn tốt hơn.

Dù sao, sau đây sẽ làm điều đó:

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

Với Java 8, điều này có thể trở thành một lớp lót:

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

Tôi thích câu trả lời này tốt hơn, tôi sẽ chỉ tránh sử dụng hai câu lệnh return, vì điều đó chống lại OOP và nó làm cho giá trị Độ phức tạp Cyclomatic cao hơn.
Leo

8
@Leo cảm ơn, nhưng mô hình thoát đơn không chống lại OOP và hầu như không hợp lệ đối với các ngôn ngữ hiện đại hơn Fortran hoặc COBOL, xem thêm phần
Arne Burmeister

1
Sử dụng Bản đồ thay vì Tập hợp có vẻ như là một lựa chọn tốt hơn: lặp lại các yếu tố của Tập hợp sẽ hiệu quả hơn là nhận một giá trị duy nhất từ ​​Bản đồ. (O (N) vs O (1))
Jamie Flournoy

@JamieFlournoy đúng, nếu bạn phải kiểm tra cùng một bộ cho các yếu tố khác nhau nhiều lần thì tốt hơn nhiều. Đối với việc sử dụng duy nhất thì không, vì nó đòi hỏi nhiều nỗ lực hơn để xây dựng bản đồ trước.
Arne Burmeister

18

Chuyển đổi thiết lập thành danh sách, và sau đó sử dụng getphương pháp danh sách

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

37
Tôi không hiểu điều này. Điều này sẽ lấy một đối tượng tùy ý của tập hợp. Không những đối tượng.
aioobe

14

Thật không may, Set trong Java không được thiết kế để cung cấp thao tác "get", như jschreiner đã giải thích chính xác.

Các giải pháp sử dụng một trình vòng lặp để tìm phần tử quan tâm (được đề xuất bởi dacwe ) hoặc loại bỏ phần tử đó và thêm lại nó với các giá trị được cập nhật (được đề xuất bởi KyleM ), có thể hoạt động, nhưng có thể rất không hiệu quả.

Ghi đè việc thực hiện bằng nhau sao cho các đối tượng không bằng nhau là "bằng nhau", như David Ogren đã nêu chính xác , có thể dễ dàng gây ra sự cố bảo trì.

Và sử dụng Bản đồ như một sự thay thế rõ ràng (như được đề xuất bởi nhiều người), imho, làm cho mã kém thanh lịch.

Nếu mục tiêu là để có quyền truy cập vào thể hiện ban đầu của phần tử có trong tập hợp (hy vọng tôi hiểu chính xác trường hợp sử dụng của bạn), thì đây là một giải pháp khả thi khác.


Cá nhân tôi cũng có nhu cầu tương tự của bạn trong khi phát triển trò chơi điện tử máy khách-máy chủ với Java. Trong trường hợp của tôi, mỗi máy khách có các bản sao của các thành phần được lưu trữ trong máy chủ và vấn đề là bất cứ khi nào khách hàng cần sửa đổi một đối tượng của máy chủ.

Truyền một đối tượng qua internet có nghĩa là khách hàng có các phiên bản khác nhau của đối tượng đó. Để khớp với cá thể "được sao chép" này với cá thể gốc, tôi đã quyết định sử dụng các UUID Java.

Vì vậy, tôi đã tạo một lớp trừu tượng UniqueItem, nó tự động cung cấp một id duy nhất ngẫu nhiên cho mỗi phiên bản của các lớp con của nó.

UUID này được chia sẻ giữa máy khách và phiên bản máy chủ, vì vậy cách này có thể dễ dàng khớp chúng bằng cách sử dụng Bản đồ.

Tuy nhiên, trực tiếp sử dụng Bản đồ trong một usecase tương tự vẫn không phù hợp. Ai đó có thể lập luận rằng việc sử dụng Bản đồ có thể phức tạp hơn để xử lý và xử lý.

Vì những lý do này, tôi đã triển khai một thư viện có tên MagicSet, điều đó làm cho việc sử dụng Bản đồ trở nên "minh bạch" đối với nhà phát triển.

https://github.com/ricpacca/magicset


Giống như Java Hashset ban đầu, MagicHashset (là một trong những triển khai của MagicSet được cung cấp trong thư viện) sử dụng HashMap sao lưu, nhưng thay vì có các phần tử làm khóa và giá trị giả làm giá trị, nó sử dụng UUID của phần tử làm khóa và chính phần tử là giá trị. Điều này không gây ra chi phí sử dụng bộ nhớ so với Hashset thông thường.

Hơn nữa, Magicset có thể được sử dụng chính xác như một Tập hợp, nhưng với một số phương thức khác cung cấp các chức năng bổ sung, như getFromId (), popFromId (), removeFromId (), v.v.

Yêu cầu duy nhất để sử dụng nó là bất kỳ phần tử nào bạn muốn lưu trữ trong Magicset đều cần mở rộng lớp trừu tượng UniqueItem.


Dưới đây là một ví dụ mã, tưởng tượng để truy xuất thể hiện ban đầu của một thành phố từ Magicset, đưa ra một phiên bản khác của thành phố đó có cùng UUID (hoặc thậm chí chỉ là UUID của nó).

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
}

11

Nếu bộ của bạn thực tế là một NavigableSet<Foo>(chẳng hạn như a TreeSet), và Foo implements Comparable<Foo>, bạn có thể sử dụng

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

(Cảm ơn bình luận của @ eliran-malka về gợi ý.)


5
Nếu tôi không phiền bất cứ ai đọc mã của tôi có suy nghĩ ban đầu rằng tôi hoàn toàn mất trí, đây sẽ là một giải pháp tuyệt vời.
Adam

10

Với Java 8, bạn có thể làm:

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

Nhưng hãy cẩn thận, .get () ném NoSuchEuityException hoặc bạn có thể thao tác một mục Tùy chọn.


5
item->item.equals(theItemYouAreLookingFor)có thể rút ngắn thànhtheItemYouAreLookingFor::equals
Henno Vermeulen

5
Object objectToGet = ...
Map<Object, Object> map = new HashMap<Object, Object>(set.size());
for (Object o : set) {
    map.put(o, o);
}
Object objectFromSet = map.get(objectToGet);

Nếu bạn chỉ thực hiện một lần thì điều này sẽ không thực hiện được vì bạn sẽ lặp qua tất cả các yếu tố của mình nhưng khi thực hiện nhiều lần truy xuất trên một tập hợp lớn, bạn sẽ nhận thấy sự khác biệt.


5

Tại sao:

Có vẻ như Set đóng vai trò hữu ích trong việc cung cấp phương tiện so sánh. Nó được thiết kế để không lưu trữ các yếu tố trùng lặp.

Do ý định / thiết kế này, nếu ai đó có được () một tham chiếu đến đối tượng được lưu trữ, sau đó biến đổi nó, có thể ý định thiết kế của Set có thể bị cản trở và có thể gây ra hành vi không mong muốn.

Từ JavaDocs

Phải hết sức cẩn thận nếu các đối tượng có thể thay đổi được sử dụng làm yếu tố tập hợp. Hành vi của một tập hợp không được chỉ định nếu giá trị của một đối tượng bị thay đổi theo cách ảnh hưởng đến sự so sánh bằng trong khi đối tượng là một thành phần trong tập hợp.

Làm sao:

Bây giờ Streams đã được giới thiệu, người ta có thể làm như sau

mySet.stream()
.filter(object -> object.property.equals(myProperty))
.findFirst().get();

2

Còn việc sử dụng lớp Mảng thì sao?

import java.util.Arrays;
import java.util.List;
import java.util.HashSet;
import java.util.Arrays;

public class MyClass {
    public static void main(String args[]) {
        Set mySet = new HashSet();
        mySet.add("one");
        mySet.add("two");
        List list = Arrays.asList(mySet.toArray());
        Object o0 = list.get(0);
        Object o1 = list.get(1);
        System.out.println("items " + o0+","+o1);
    }
}

đầu ra:
mục một, hai



1

Tôi biết, điều này đã được hỏi và trả lời từ lâu, tuy nhiên nếu có ai quan tâm, đây là giải pháp của tôi - lớp tập tùy chỉnh được hỗ trợ bởi HashMap:

http://pastebin.com/Qv6S91n9

Bạn có thể dễ dàng thực hiện tất cả các phương thức Set khác.


7
Bạn nên bao gồm ví dụ thay vì chỉ liên kết với một ví dụ.
Tất cả công nhân là cần thiết

1

Đã từng trải qua rồi!! Nếu bạn đang sử dụng Guava, một cách nhanh chóng để chuyển đổi nó thành bản đồ là:

Map<Integer,Foo> map = Maps.uniqueIndex(fooSet, Foo::getKey);

1

bạn có thể sử dụng lớp Iterator

import java.util.Iterator;
import java.util.HashSet;

public class MyClass {
 public static void main(String[ ] args) {
 HashSet<String> animals = new HashSet<String>();
animals.add("fox");
animals.add("cat");
animals.add("dog");
animals.add("rabbit");

Iterator<String> it = animals.iterator();
while(it.hasNext()) {
  String value = it.next();
  System.out.println(value);   
 }
 }
}

1

Nếu bạn muốn phần tử thứ n từ Hashset, bạn có thể đi với giải pháp bên dưới, ở đây tôi đã thêm đối tượng của ModelClass trong Hashset.

ModelClass m1 = null;
int nth=scanner.nextInt();
for(int index=0;index<hashset1.size();index++){
    m1 = (ModelClass) itr.next();
    if(nth == index) {
        System.out.println(m1);
        break;
    }
}

1

Nếu bạn nhìn vào một vài dòng đầu tiên của việc thực hiện, java.util.HashSetbạn sẽ thấy:

public class HashSet<E>
    ....
    private transient HashMap<E,Object> map;

Vì vậy, dù sao cũng HashSetsử dụng nội bộ HashMap, điều đó có nghĩa là nếu bạn chỉ sử dụng HashMaptrực tiếp và sử dụng cùng một giá trị với khóa và giá trị bạn sẽ nhận được hiệu ứng bạn muốn và tiết kiệm cho mình một số bộ nhớ.


1

nó trông giống như các đối tượng thích hợp để sử dụng là Interner từ ổi:

Cung cấp hành vi tương đương với String.i INTERN () cho các loại bất biến khác. Triển khai phổ biến có sẵn từ Interners lớp.

Nó cũng có một vài đòn bẩy rất thú vị, như concurrencyLevel hoặc loại tham chiếu được sử dụng (có thể đáng chú ý là nó không cung cấp SoftI INTERNer mà tôi có thể thấy hữu ích hơn WeakI INTERNer).


0

Bởi vì bất kỳ triển khai cụ thể nào của Set có thể hoặc không thể truy cập ngẫu nhiên .

Bạn luôn có thể nhận được một trình vòng lặp và bước qua Tập hợp, sử dụng next()phương thức của trình vòng lặp để trả về kết quả bạn muốn khi bạn tìm thấy phần tử bằng nhau. Điều này hoạt động bất kể việc thực hiện. Nếu việc triển khai KHÔNG phải là truy cập ngẫu nhiên (hình ảnh Bộ được hỗ trợ danh sách liên kết), một get(E element)phương thức trong giao diện sẽ bị đánh lừa, vì nó sẽ phải lặp lại bộ sưu tập để tìm phần tử trả về và get(E element)dường như ngụ ý điều này sẽ là cần thiết, Bộ có thể nhảy trực tiếp đến phần tử để lấy.

contains() Tất nhiên, có thể hoặc không phải làm điều tương tự, tùy thuộc vào việc triển khai, nhưng cái tên dường như không cho vay chính nó với cùng một loại hiểu lầm.


2
Bất cứ điều gì phương thức get () sẽ làm đều đã được thực hiện bởi phương thức chứa (). Bạn không thể triển khai chứa () mà không nhận được đối tượng chứa và gọi phương thức .equals () của nó. Các nhà thiết kế API dường như không có ý định gì về việc thêm get () vào java.util.List mặc dù nó sẽ chậm trong một số triển khai.
Bryan Rink

Tôi không nghĩ điều này là đúng. Hai đối tượng có thể bằng nhau thông qua bằng nhau, nhưng không giống nhau thông qua ==. Nếu bạn có đối tượng A và một tập hợp S chứa đối tượng B và A.equals (B) nhưng A! = B và bạn muốn có một tham chiếu đến B, bạn có thể gọi S.get (A) để tham chiếu đến B, giả sử bạn đã có một phương thức get với ngữ nghĩa của phương thức get, đây là trường hợp sử dụng khác với kiểm tra xem S.contains (A) (mà nó sẽ). Đây thậm chí không phải là trường hợp sử dụng hiếm hoi cho các bộ sưu tập.
Tom Tresansky

0

Có, sử dụng HashMap... nhưng theo một cách chuyên biệt: cái bẫy mà tôi thấy trước khi cố gắng sử dụng HashMapnhư một giả - Setlà sự nhầm lẫn có thể có giữa các yếu tố "thực tế" của các yếu tố Map/Setvà "ứng cử viên", tức là các yếu tố được sử dụng để kiểm tra xem equalyếu tố đã có mặt. Điều này là xa vời, nhưng đẩy bạn ra khỏi cái bẫy:

class SelfMappingHashMap<V> extends HashMap<V, V>{
    @Override
    public String toString(){
        // otherwise you get lots of "... object1=object1, object2=object2..." stuff
        return keySet().toString();
    }

    @Override
    public V get( Object key ){
        throw new UnsupportedOperationException( "use tryToGetRealFromCandidate()");
    }

    @Override
    public V put( V key, V value ){
       // thorny issue here: if you were indavertently to `put`
       // a "candidate instance" with the element already in the `Map/Set`: 
       // these will obviously be considered equivalent 
       assert key.equals( value );
       return super.put( key, value );
    }

    public V tryToGetRealFromCandidate( V key ){
        return super.get(key);
    }
}

Sau đó, làm điều này:

SelfMappingHashMap<SomeClass> selfMap = new SelfMappingHashMap<SomeClass>();
...
SomeClass candidate = new SomeClass();
if( selfMap.contains( candidate ) ){
    SomeClass realThing = selfMap.tryToGetRealFromCandidate( candidate );
    ...
    realThing.useInSomeWay()...
}

Nhưng ... bây giờ bạn muốn candidatetự hủy theo một cách nào đó trừ khi lập trình viên thực sự ngay lập tức đưa nó vào Map/Set... bạn muốn contains"làm mờ" candidateđể bất kỳ việc sử dụng nào trừ khi nó tham gia vào Map"anathema" ". Có lẽ bạn có thể thực SomeClasshiện một Taintablegiao diện mới .

Một giải pháp thỏa đáng hơn là một Gfortset , như dưới đây. Tuy nhiên, để làm việc này, bạn phải chịu trách nhiệm thiết kế SomeClassđể làm cho tất cả các nhà xây dựng không nhìn thấy được (hoặc ... có thể và sẵn sàng thiết kế và sử dụng lớp trình bao bọc cho nó):

public interface NoVisibleConstructor {
    // again, this is a "nudge" technique, in the sense that there is no known method of 
    // making an interface enforce "no visible constructor" in its implementing classes 
    // - of course when Java finally implements full multiple inheritance some reflection 
    // technique might be used...
    NoVisibleConstructor addOrGetExisting( GettableSet<? extends NoVisibleConstructor> gettableSet );
};

public interface GettableSet<V extends NoVisibleConstructor> extends Set<V> {
    V getGenuineFromImpostor( V impostor ); // see below for naming
}

Thực hiện:

public class GettableHashSet<V extends NoVisibleConstructor> implements GettableSet<V> {
    private Map<V, V> map = new HashMap<V, V>();

    @Override
    public V getGenuineFromImpostor(V impostor ) {
        return map.get( impostor );
    }

    @Override
    public int size() {
        return map.size();
    }

    @Override
    public boolean contains(Object o) {
        return map.containsKey( o );
    }

    @Override
    public boolean add(V e) {
        assert e != null;
        V result = map.put( e,  e );
        return result != null;
    }

    @Override
    public boolean remove(Object o) {
        V result = map.remove( o );
        return result != null;
    }

    @Override
    public boolean addAll(Collection<? extends V> c) {
        // for example:
        throw new UnsupportedOperationException();
    }

    @Override
    public void clear() {
        map.clear();
    }

    // implement the other methods from Set ...
}

Các NoVisibleConstructorlớp học của bạn sau đó trông như thế này:

class SomeClass implements NoVisibleConstructor {

    private SomeClass( Object param1, Object param2 ){
        // ...
    }

    static SomeClass getOrCreate( GettableSet<SomeClass> gettableSet, Object param1, Object param2 ) {
        SomeClass candidate = new SomeClass( param1, param2 );
        if (gettableSet.contains(candidate)) {
            // obviously this then means that the candidate "fails" (or is revealed
            // to be an "impostor" if you will).  Return the existing element:
            return gettableSet.getGenuineFromImpostor(candidate);
        }
        gettableSet.add( candidate );
        return candidate;
    }

    @Override
    public NoVisibleConstructor addOrGetExisting( GettableSet<? extends NoVisibleConstructor> gettableSet ){
       // more elegant implementation-hiding: see below
    }
}

PS một vấn đề kỹ thuật với một NoVisibleConstructorlớp như vậy : có thể bị phản đối rằng một lớp như vậy vốn có final, điều này có thể là không mong muốn. Trên thực tế, bạn luôn có thể thêm một hàm tạo không tham số giả protected:

protected SomeClass(){
    throw new UnsupportedOperationException();
}

... mà ít nhất sẽ để một lớp con biên dịch. Sau đó, bạn phải suy nghĩ về việc bạn có cần đưa getOrCreate()phương thức xuất xưởng khác vào lớp con hay không.

Bước cuối cùng là một lớp cơ sở trừu tượng (NB "phần tử" cho một danh sách, "thành viên" cho một tập hợp) như thế này cho các thành viên tập hợp của bạn (khi có thể - một lần nữa, phạm vi sử dụng lớp trình bao trong đó lớp không nằm trong tầm kiểm soát của bạn, hoặc đã có một lớp cơ sở, v.v.), để ẩn thực hiện tối đa:

public abstract class AbstractSetMember implements NoVisibleConstructor {
    @Override
    public NoVisibleConstructor
            addOrGetExisting(GettableSet<? extends NoVisibleConstructor> gettableSet) {
        AbstractSetMember member = this;
        @SuppressWarnings("unchecked") // unavoidable!
        GettableSet<AbstractSetMembers> set = (GettableSet<AbstractSetMember>) gettableSet;
        if (gettableSet.contains( member )) {
            member = set.getGenuineFromImpostor( member );
            cleanUpAfterFindingGenuine( set );
        } else {
            addNewToSet( set );
        }
        return member;
    }

    abstract public void addNewToSet(GettableSet<? extends AbstractSetMember> gettableSet );
    abstract public void cleanUpAfterFindingGenuine(GettableSet<? extends AbstractSetMember> gettableSet );
}

... sử dụng là khá rõ ràng (bên trong của bạn SomeClass's staticphương pháp nhà máy):

SomeClass setMember = new SomeClass( param1, param2 ).addOrGetExisting( set );

0

Hợp đồng của mã băm làm rõ rằng:

"Nếu hai đối tượng bằng nhau theo phương thức Object, thì việc gọi phương thức hashCode trên mỗi hai đối tượng phải tạo ra cùng một kết quả số nguyên."

Vì vậy, giả định của bạn:

"Để làm rõ, phương thức bằng bị ghi đè, nhưng nó chỉ kiểm tra một trong các trường chứ không phải tất cả. Vì vậy, hai đối tượng Foo được coi là bằng nhau thực sự có thể có các giá trị khác nhau, đó là lý do tại sao tôi không thể sử dụng foo."

là sai và bạn đang phá vỡ hợp đồng. Nếu chúng ta xem phương thức "chứa" của giao diện Set, chúng ta có:

boolean chứa (Object o);
Trả về true nếu tập hợp này chứa phần tử đã chỉ định. Chính thức hơn, trả về true khi và chỉ khi tập hợp này chứa một phần tử "e" sao cho o == null? e == null: o.equals (e)

Để thực hiện những gì bạn muốn, bạn có thể sử dụng Bản đồ nơi bạn xác định khóa và lưu trữ phần tử của bạn với khóa xác định cách các đối tượng khác nhau hoặc bằng nhau.


-2

Phương pháp trợ giúp nhanh có thể giải quyết tình huống này:

<T> T onlyItem(Collection<T> items) {
    if (items.size() != 1)
        throw new IllegalArgumentException("Collection must have single item; instead it has " + items.size());

    return items.iterator().next();
}

8
Điều rất lạ là câu trả lời này đã nhận được rất nhiều sự ủng hộ vì nó không trả lời câu hỏi hoặc thậm chí cố gắng giải quyết nó theo bất kỳ cách nào.
David Conrad

-2

Sau đây có thể là một cách tiếp cận

   SharedPreferences se_get = getSharedPreferences("points",MODE_PRIVATE);
   Set<String> main = se_get.getStringSet("mydata",null);
   for(int jk = 0 ; jk < main.size();jk++)
   {
      Log.i("data",String.valueOf(main.toArray()[jk]));
   }

-2

Hãy thử sử dụng một mảng:

ObjectClass[] arrayName = SetOfObjects.toArray(new ObjectClass[setOfObjects.size()]);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.