Set interface và cách sắp xếp đối tượng trong Java

Dạ em có thắc mắc muốn hỏi ạ.

  1. Trong Java em thường thấy mọi người khai báo một đối tượng dưới dạng interface rồi tham chiếu nó tới class như trong TH cụ thể như:
Set<Integer> s = new TreeSet<Integer>();

Thay vì khai báo là:

TreeSet<Integer> s = new TreeSet<Integer>();

Và lớp Interface Sorted Set là TH riêng của Set và có 1 số phương thức so với Set, nhưng
Sorted Set khi tạo đối tượng cũng phải tạo đối tượng TreeSet và Set cũng có thể làm vậy nên cũng có thể khai triển những phương thức đó nên tại sao cần dùng SortedSet ạ?
Cho em hỏi là 2 cách khai báo trên có khác gì nhau không ạ và khi nào dùng chúng ạ và sinh ra Sorted Set để làm gì ạ, và khi trả về 1 Set sắp xếp như TreeSet thì e vẫn thấy các chương trình trả về kiểu SortedSet<DataType> thay vì trả về TreeSet<DataType> trong khi TreeSet như thế không cần ép kiểu trong 1 số TH.

  1. Cho em hỏi cách sắp xếp mảng giảm dần hay thêm các điều kiện sắp xếp trong Java ạ, và cách sắp xếp 1 list các đối tượng theo 1 trường biến thực thế nào đó thì làm cách nào ạ.

  2. Khi add các đối tượng vào TreeSet hay TreeMap thì chúng sẽ tự động sắp xếp, nhưng chúng sẽ so sánh các đối tượng để xếp chúng trước sau như thế nào ạ !

Em cảm ơn nhiều ạ !

  1. Vấn đề này trên stackoverflow được trả lời khá kỹ.

  2. Ví dụ ta có một class

class Data {
    int age;
    String name;
    ...
}

Và một List<Data> list, bây giờ ta muốn sắp xếp nó theo name thì làm như sau

Collections.sort(list, new Comparator<Data>(){
	@Override
	public int compare(Data d1, Data d2) {
		return d1.name.compareTo(d2.name);
	}
});

Phương thức compare sẽ trả về một số nguyên, nếu là 0 thì name bằng nhau, là số dương thì name của d1 lớn hơn, số âm thì nhỏ hơn.

Đặc biết với Java 8 ta có thể làm gọn thành

Collections.sort(list, (d1, d2) -> d1.name.compareTo(d2.name));

Tương tự cho age, lưu ý kiểu int thì dùng dấu =, >, >= , <, <= để so sánh.

  1. Các TreeSet, TreeMap, … sẽ tự động sắp xếp dữ liệu theo điều kiện tự nhiên, như số thì tăng dần, chuỗi thì bảng chữ cái, … ví dụ đối với TreeSet nó sắp xếp dựa vào bộ so sánh Comparator, chẳng hạn trong constructor của nó có một hàm
public TreeSet(Comparator<? super E> comparator) {
	this(new TreeMap<>(comparator));
}

Bình thường nó sẽ là 1, 2, 3, … bây giờ thêm một Comparator.reverseOrder()

Set<Integer> tree = new TreeSet<>(Comparator.reverseOrder());

thì nó sẽ đảo ngược lại thành 3, 2, 1, …

Như vầy nếu bạn thêm bộ sánh sánh như của List bên trên thì khi chèn mới phần tử vào Set nó sắp sắp xếp theo name, age,…

4 Likes

Để tớ thử trả lời giúp cậu nhé! :slight_smile:


SortedSet là interface, không thể tạo đối tượng được đâu cậu :smiley:
SortedSet hiển nhiên được dùng để khai báo 1 đối tượng Set đã được sắp xếp rồi cậu :smiley:
Trong SortedSet interface có các method không có trong Set interface:

Vậy nên, nếu cậu cần dùng những method đó, cậu buộc phải khai báo với SortedTree.

SortedSet<Integer> sortedSet = new TreeSet<>();
int firstValue = sortedSet.first();
// now do some fancy stuff with firstValue...

Nếu cậu khai báo như vậy và dùng biến s trong 1 method nào đó, thường sẽ không có vấn đề gì cả.
Vấn đề xảy ra khi cậu cần khai báo parameter của method. Ví dụ như cậu phải viết method in ra nội dung của từng phần tử trong Set:

// Declaration using interface
public void printEachElement(Set<Integer> set) {
   // ...
}

// Declaration using concrete class
public void printEachElement(TreeSet<Integer> set) {
   // ...
}

Ở method phía trên, cậu có thể truyền bất cứ thể hiện nào của Set: HashSet, TreeSet,… mà method đều thực hiện được, mà không quan tâm tới cách nó được cài đặt (sử dụng Hash hay Tree). Ở method dưới, đáng tiếc chỉ có TreeSet được chấp nhận thôi.
Điều tương tự xảy ra nếu cậu sử dụng Set trong property. Ví dụ:

// Implementation using generic Set
public class SomeAwesomeMarketplace {
    private Set<Country> sellingCountries;

    public SomeAwesomeMarketplace(Set<Country> sellingCountries) {
       this.sellingCountries = sellingCountries;
   }
}

// Implementation using concrete TreeSet
public class SomeAwesomeMarketplace {
    private TreeSet<Country> sellingCountries;

    public SomeAwesomeMarketplace(TreeSet<Country> sellingCountries) {
       this.sellingCountries = sellingCountries;
   }
}

Ở implementation trên, cậu nhận mọi thể loại set, không quan tâm tới cách nó được cài đặt, nhưng ở dưới, chỉ có TreeSet được nhận. Giờ, trong tương lai, nếu cậu nhận ra HashSet sẽ tốt hơn cho việc lưu trữ, so với việc phải build ra 1 cây, cậu sẽ phải đổi toàn bộ TreeSet thành HashSet hoặc Set, thay vì cứ truyền HashSet vào constructor.

Cậu có thể đã nhận ra, từ 2 ví dụ trên, với 1 object, chúng ta quan tâm tới hành vi của nó (behavior) hơn là cách nó được cài đặt (how it’s implemented). Hành vi của đối tượng, được định nghĩa bằng các method (các dịch vụ / hành vi) mà object đó có, và nó luôn có ở trong interface (nếu code của cậu design tuân theo nguyên tắc Liskov).
Đó là lý do tại sao chúng ta khai báo mà sử dụng interface :wink:


Trong 1 số trường hợp, cậu cần cụ thể SortedSet. Ví dụ: cậu muốn tìm 1 danh sách tất cả các unique custome ID đã được sắp xếp trong 1 loạt các giao dịch ngân hàng, cách hiệu quả nhất hiển nhiên là dùng SortedSet, thay vì cậu dùng generic Set, hoặc sử dụng list + check contains + sort.


Khi sắp xếp, ngoài giải thuật sắp xếp, cậu cần cả tiêu chí so sánh.
Giải thuật sắp xếp thường do thư viện lo. Còn tiêu chí so sánh được tự định nghĩa theo các cách:

  • Đối tượng cần sắp xếp cài đặt Comparable interface.
  • Định nghĩa cách so sánh dùng Comparator interface.

Ví dụ dưới đây demo cách sắp xếp People theo tên và tuổi (nếu cùng tên, thì sắp xếp theo tuổi):

  @Value
  public class Person implements Comparable<Person> {
    int age;
    String name;
    
    @Override
    public int compareTo(Person that) {
      if(that == null) {
        return 1;
      }
      
      if(this.getName() == null && that.getName() == null) {
        return 0;
      }
      
      if(this.getName() == null) {
        return -1;
      }

      int nameComparisionResult = this.getName().compareTo(that.getName());
      if(nameComparisionResult != 0) {
        return nameComparisionResult;
      }
      
      return this.getAge() - that.getAge();
    }
  }
  
  @Test
  public void testSort() throws Exception {
    List<Person> people = asList(new Person(22, "Alan Smith"), new Person(12, "Alan Smith"), new Person(100, "Harry Potter"));
    
    // Using Collections class with Comparable interface implementation
    List<Person> peopleClone = new ArrayList<>(people);
    Collections.sort(peopleClone);
    System.out.println(peopleClone);
    
    // Using Collections class with Comparator
    peopleClone = new ArrayList<>(people);
    Collections.sort(peopleClone, (a, b) -> a.compareTo(b));
    System.out.println(peopleClone);
    
    // Use List's sort method
    peopleClone = new ArrayList<>(people);
    // sort in the reverse way
    peopleClone.sort((a, b) -> - a.compareTo(b));
    System.out.println(peopleClone);
    
    // Result:
    // [SomeTest.Person(age=12, name=Alan Smith), SomeTest.Person(age=22, name=Alan Smith), SomeTest.Person(age=100, name=Harry Potter)]
    // [SomeTest.Person(age=12, name=Alan Smith), SomeTest.Person(age=22, name=Alan Smith), SomeTest.Person(age=100, name=Harry Potter)]
    // [SomeTest.Person(age=100, name=Harry Potter), SomeTest.Person(age=22, name=Alan Smith), SomeTest.Person(age=12, name=Alan Smith)]
  }

Như tớ có đề cập ở trên, cậu có 2 phương pháp định nghĩa chiến lược sắp xếp, tương ứng với 2 loại constructor dưới đây của TreeSet/TreeMap

Đây là javadoc của TreeSet nullary constructor:

Constructs a new, empty tree set, sorted according to the natural ordering of its elements. All elements inserted into the set must implement the Comparable interface…

Trong đó có đề cập các phần tử được insert vào set này phải implement Comparable interface.

Còn đây là javadoc của constructor public TreeSet(Comparator<? super E> comparator)

Constructs a new, empty tree set, sorted according to the specified comparator. All elements inserted into the set must be mutually comparable by the specified comparator: comparator.compare(e1, e2) must not throw a ClassCastException for any elements e1 and e2 in the set. If the user attempts to add an element to the set that violates this constraint, the add call will throw a ClassCastException .

Ở đây, cậu cần truyền Comparator instance vào để định nghĩa chiến lược sắp xếp.

Dưới đây là ví dụ sử dụng:

  @Test
  public void testName() throws Exception {
    SortedSet<Person> people = new TreeSet<>();
    people.add(new Person(22, "Alan Smith"));
    people.add(new Person(12, "Alan Smith"));
    people.add(new Person(100, "Harry Potter"));
    System.out.println(people);
    
    // Result:
    // [SomeTest.Person(age=12, name=Alan Smith), SomeTest.Person(age=22, name=Alan Smith), SomeTest.Person(age=100, name=Harry Potter)]
  }

Hope it helps!

7 Likes

Mình cảm ơn nhiều nhé !

1 Like

em cảm ơn nhiều ạ !!!

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?