SortedSet ใน Android: จัดเก็บข้อมูลไม่ซ้ำและเรียงลำดับอัตโนมัติ
SortedSet เป็น Interface ใน Java Collections Framework ที่ขยายจาก Set โดยรับประกันว่า: (1) ไม่มีค่าซ้ำ (Unique) และ (2) จัดเรียงตามลำดับธรรมชาติ (Natural Ordering) หรือ Comparator ที่กำหนด ใช้งานผ่าน TreeSet ซึ่งเป็น Implementation หลัก
ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่
Android Studio → File → New Project → Empty Activity
ขั้นตอนที่ 2: Layout - activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
android:padding="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SortedSet (TreeSet) Demo"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="16dp" />
<EditText
android:id="@+id/etName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="กรอกชื่อ (จะเรียง A-Z อัตโนมัติ)"
android:layout_marginBottom="16dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="16dp">
<Button
android:id="@+id/btnAdd"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="4dp"
android:text="เพิ่ม" />
<Button
android:id="@+id/btnClear"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="4dp"
android:text="ล้างทั้งหมด" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvNames"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
ขั้นตอนที่ 3: MainActivity.java
package com.example.sortedsetdemo;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
public class MainActivity extends AppCompatActivity {
private EditText etName;
private Button btnAdd, btnClear;
private RecyclerView rvNames;
private NameAdapter adapter;
private SortedSet<String> sortedNames;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// TreeSet เรียงตาม Natural Ordering (String = Alphabetical)
sortedNames = new TreeSet<>();
etName = findViewById(R.id.etName);
btnAdd = findViewById(R.id.btnAdd);
btnClear = findViewById(R.id.btnClear);
rvNames = findViewById(R.id.rvNames);
rvNames.setLayoutManager(new LinearLayoutManager(this));
adapter = new NameAdapter(new ArrayList<>());
rvNames.setAdapter(adapter);
btnAdd.setOnClickListener(v -> addName());
btnClear.setOnClickListener(v -> {
sortedNames.clear();
adapter.setData(new ArrayList<>());
Toast.makeText(this, "ล้างข้อมูลแล้ว", Toast.LENGTH_SHORT).show();
});
}
private void addName() {
String name = etName.getText().toString().trim();
if (name.isEmpty()) {
Toast.makeText(this, "กรุณากรอกชื่อ", Toast.LENGTH_SHORT).show();
return;
}
// TreeSet.add() คืนค่า true ถ้าเพิ่มสำเร็จ (ไม่ซ้ำ), false ถ้ามีอยู่แล้ว
boolean added = sortedNames.add(name);
if (added) {
Toast.makeText(this, "เพิ่ม '" + name + "' เรียบร้อย", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "'" + name + "' มีอยู่แล้ว (ไม่ซ้ำ)", Toast.LENGTH_SHORT).show();
}
// อัปเดต RecyclerView (TreeSet เรียงลำดับอัตโนมัติ)
adapter.setData(new ArrayList<>(sortedNames));
etName.setText("");
}
}
NameAdapter.java
package com.example.sortedsetdemo;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class NameAdapter extends RecyclerView.Adapter<NameAdapter.ViewHolder> {
private List<String> items;
public NameAdapter(List<String> items) {
this.items = items;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(android.R.layout.simple_list_item_1, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.textView.setText(items.get(position));
}
@Override
public int getItemCount() {
return items.size();
}
public void setData(List<String> newData) {
this.items = newData;
notifyDataSetChanged();
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView textView;
ViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(android.R.id.text1);
}
}
}
คุณสมบัติหลักของ SortedSet / TreeSet
| คุณสมบัติ | คำอธิบาย |
|---|---|
| Unique Elements | ไม่รับค่าซ้ำ (ใช้ equals() ตรวจสอบ) |
| Sorted Order | เรียงตาม Natural Order (Comparable) หรือ Comparator |
| Null Values | ไม่รองรับ null (จะโยน NullPointerException) |
| Thread Safety | ไม่ Thread-Safe (ต้อง Synchronize เองหรือใช้ ConcurrentSkipListSet) |
| Performance | O(log n) สำหรับ add, remove, contains |
SortedSet Methods ที่น่าสนใจ
SortedSet<String> set = new TreeSet<>();
set.add("Charlie");
set.add("Alpha");
set.add("Beta");
// set = [Alpha, Beta, Charlie] (เรียงอัตโนมัติ)
// Navigation Methods
set.first(); // "Alpha" - ค่าน้อยที่สุด
set.last(); // "Charlie" - ค่ามากที่สุด
set.headSet("Beta"); // [Alpha] - น้อยกว่า "Beta" (Exclusive)
set.tailSet("Beta"); // [Beta, Charlie] - มากกว่าหรือเท่า "Beta" (Inclusive)
set.subSet("Beta", "Charlie"); // [Beta] - ระหว่าง Beta ถึง Charlie
// Poll (ดึงและลบ)
set.pollFirst(); // ลบและคืนค่าน้อยที่สุด
set.pollLast(); // ลบและคืนค่ามากที่สุด
// Descending View
NavigableSet<String> descending = set.descendingSet(); // [Charlie, Beta, Alpha]
Custom Comparator (เรียงแบบกำหนดเอง)
// เรียงตามความยาวสตริง (สั้นไปยาว)
SortedSet<String> byLength = new TreeSet<>((a, b) -> {
int lenCompare = Integer.compare(a.length(), b.length());
if (lenCompare != 0) return lenCompare;
return a.compareTo(b); // ถ้ายาวเท่ากัน เรียงตามอักษร
});
byLength.add("A");
byLength.add("BBB");
byLength.add("CC");
byLength.add("DDDD");
// ผล: [A, CC, BBB, DDDD]
Thread-Safe Alternative: ConcurrentSkipListSet
import java.util.concurrent.ConcurrentSkipListSet;
// Thread-safe SortedSet (Lock-free, Scalable)
SortedSet<String> concurrentSortedSet = new ConcurrentSkipListSet<>();
// ใช้ได้ใน Multi-thread Environment โดยไม่ต้อง Synchronize
การใช้งานจริงใน Android
- Auto-complete/Suggestions: เก็บคำที่ผู้ใช้พิมพ์ ไม่ซ้ำ เรียง A-Z
- Tag/Category Management: จัดการ Tags เรียงลำดับ
- Leaderboard/Ranking: ใช้ TreeMap<Score, List<User>> หรือ TreeSet กับ Custom Comparator
- Deduplication + Sorting: กรองข้อมูลซ้ำจาก API แล้วแสดงเรียงลำดับ
สรุป
TreeSet (SortedSet) เหมาะสำหรับข้อมูลที่ต้องการ Unique + Sorted พร้อมกัน ใช้งานง่าย แค่ add() แล้วมันจัดเรียงให้อัตโนมัติ รองรับ Navigation Methods (first, last, headSet, tailSet) และ Custom Comparator สำหรับการเรียงแบบกำหนดเอง