CodeGym /จาวาบล็อก /สุ่ม /อินเทอร์เฟซตัวเปรียบเทียบของ Java
John Squirrels
ระดับ
San Francisco

อินเทอร์เฟซตัวเปรียบเทียบของ Java

เผยแพร่ในกลุ่ม
ไม่ใช่คนเดียวที่ขี้เกียจเขียนเกี่ยวกับ Comparators และการเปรียบเทียบใน Java ฉันไม่เกียจคร้าน ดังนั้น โปรดรักและเข้าใจเกี่ยวกับคำอธิบายอื่น ฉันหวังว่ามันจะไม่ฟุ่มเฟือย และใช่ บทความนี้คือคำตอบสำหรับคำถาม: " คุณสามารถเขียนตัวเปรียบเทียบจากหน่วยความจำได้หรือไม่ " ฉันหวังว่าทุกคนจะสามารถเขียนตัวเปรียบเทียบจากหน่วยความจำหลังจากอ่านบทความนี้ อินเตอร์เฟส Javas Comparator - 1

การแนะนำ

ดังที่คุณทราบ Java เป็นภาษาเชิงวัตถุ ด้วยเหตุนี้ จึงเป็นเรื่องปกติที่จะจัดการกับออบเจกต์ใน Java แต่ไม่ช้าก็เร็ว คุณต้องเผชิญกับงานเปรียบเทียบวัตถุตามคุณลักษณะบางอย่าง ตัวอย่างเช่น : สมมติว่าเรามีข้อความอธิบายโดยMessageชั้นเรียน:

public static class Message {
    private String message;
    private int id;
        
    public Message(String message) {
        this.message = message;
        this.id = new Random().nextInt(1000);
    }
    public String getMessage() {
        return message;
    }
    public Integer getId() {
        return id;
    }
    public String toString() {
        return "[" + id + "] " + message;
    }
}
ใส่คลาสนี้ในคอมไพเลอร์ Java Tutorialspoint อย่าลืมเพิ่มคำสั่งการนำเข้าด้วย:

import java.util.Random;
import java.util.ArrayList;
import java.util.List;
ในmainเมธอด ให้สร้างข้อความหลายข้อความ:

public static void main(String[] args){
    List<Message> messages = new ArrayList();
    messages.add(new Message("Hello, World!"));
    messages.add(new Message("Hello, Sun!"));
    System.out.println(messages);
}
ลองคิดดูว่าถ้าเราอยากจะเปรียบเทียบกันจะทำอย่างไร? เช่น เราต้องการจัดเรียงตามรหัส และในการสร้างคำสั่งซื้อ เราจำเป็นต้องเปรียบเทียบวัตถุเพื่อที่จะเข้าใจว่าวัตถุใดควรมาก่อน (เช่น วัตถุที่เล็กกว่า) และวัตถุใดควรตามมา (เช่น วัตถุที่ใหญ่กว่า) เรามาเริ่มกันที่คลาสอย่างjava.lang.Object เรารู้ว่าทุกคลาสสืบทอดObjectคลาส โดยปริยาย และนี่ก็สมเหตุสมผลเพราะมันสะท้อนแนวคิดที่ว่า "ทุกอย่างเป็นวัตถุ" และแสดงพฤติกรรมทั่วไปสำหรับทุกชั้นเรียน คลาสนี้กำหนดว่าทุกคลาสมีสองเมธอด: → hashCode เมธอดhashCodeส่งคืนตัวเลข (int) การเป็นตัวแทนของวัตถุ นั่นหมายความว่าอย่างไร? หมายความว่าถ้าคุณสร้างอินสแตนซ์ที่แตกต่างกันสองคลาส มันควรจะมีhashCodes ที่แตกต่างกัน คำอธิบายของเมธอดระบุว่า: "มากที่สุดเท่าที่จะเป็นไปได้จริง เมธอด hashCode ที่กำหนดโดย class Object จะส่งคืนจำนวนเต็มที่แตกต่างกันสำหรับออบเจกต์ที่แตกต่างกัน" กล่าวอีกนัยหนึ่ง สำหรับสองinstances ที่แตกต่างกัน ควรมีhashCodes ต่างกัน นั่นคือวิธีนี้ไม่เหมาะสำหรับการเปรียบเทียบของเรา → equals. วิธีequalsการตอบคำถาม "วัตถุเหล่านี้เท่ากันหรือไม่" และส่งกลับboolean" ตามค่าเริ่มต้นวิธีนี้มีรหัสต่อไปนี้:

public boolean equals(Object obj) {
    return (this == obj);
}
นั่นคือ ถ้าเมธอดนี้ไม่ถูกแทนที่ มันจะบอกว่าการอ้างอิงอ็อบเจกต์ตรงกันหรือไม่ นี่ไม่ใช่สิ่งที่เราต้องการสำหรับข้อความของเรา เนื่องจากเราสนใจรหัสข้อความ ไม่ใช่การอ้างอิงวัตถุ และแม้ว่าเราจะลบล้างequalsวิธีการนี้ สิ่งที่เราคาดหวังได้มากที่สุดก็คือการเรียนรู้ว่ามันเท่ากันหรือไม่ และนี่ไม่เพียงพอที่เราจะกำหนดลำดับ แล้วเราต้องการอะไร? เราต้องการสิ่งที่เปรียบเทียบ คนที่เปรียบเทียบเป็นComparator. เปิดJava APIและค้นหาComparator แน่นอนมีjava.util.Comparatorส่วนต่อประสาน java.util.Comparator and java.util.Comparable อย่างที่คุณเห็นมีอินเทอร์เฟซดังกล่าวอยู่ คลาสที่ใช้มันบอกว่า "ฉันใช้วิธีที่เปรียบเทียบวัตถุ" สิ่งเดียวที่คุณต้องจำไว้คือสัญญาเปรียบเทียบ ซึ่งแสดงไว้ดังนี้:

Comparator returns an int according to the following rules: 
  • It returns a negative int if the first object is smaller
  • It returns a positive int if the first object is larger
  • It returns zero if the objects are equal
ทีนี้มาเขียนตัวเปรียบเทียบกัน เราจะต้องนำเข้าjava.util.Comparator. หลังจากคำสั่งนำเข้า ให้เพิ่มวิธีการต่อไปนี้main: Comparator<Message> comparator = new Comparator<Message>(); แน่นอนว่าวิธีนี้ใช้ไม่ได้เพราะComparatorเป็นส่วนต่อประสาน ดังนั้นเราจึงเพิ่มวงเล็บปีกกา{}หลังวงเล็บ เขียนวิธีการต่อไปนี้ไว้ในวงเล็บปีกกา:

public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
คุณไม่จำเป็นต้องจำตัวสะกดด้วยซ้ำ ผู้เปรียบเทียบคือผู้ที่ทำการเปรียบเทียบ นั่นคือ เปรียบเทียบ เพื่อระบุลำดับสัมพัทธ์ของออบเจกต์ เราจะส่งคืนไฟล์int. นั่นคือโดยพื้นฐานแล้ว ดีและง่าย ดังที่คุณเห็นจากตัวอย่าง นอกเหนือจาก Comparator แล้ว ยังมีอินเทอร์เฟซอื่น — java.lang.Comparableซึ่งต้องการให้เราใช้compareToเมธอด อินเทอร์เฟซนี้กล่าวว่า "คลาสที่ใช้ฉันทำให้สามารถเปรียบเทียบอินสแตนซ์ของคลาสได้" ตัวอย่างเช่นIntegerการใช้ To ของ ' compareเป็นดังนี้:

(x < y) ? -1 : ((x == y) ? 0 : 1)
Java 8 แนะนำการเปลี่ยนแปลงที่ดี หากคุณพิจารณาComparatorอินเทอร์เฟซโดยละเอียด คุณจะเห็น@FunctionalInterfaceคำอธิบายประกอบด้านบน คำอธิบายประกอบนี้มีวัตถุประสงค์เพื่อให้ข้อมูลและบอกเราว่าอินเทอร์เฟซนี้ใช้งานได้ ซึ่งหมายความว่าอินเทอร์เฟซนี้มีวิธีการนามธรรมเพียง 1 วิธีซึ่งเป็นวิธีการที่ไม่มีการนำไปใช้ สิ่งนี้ให้อะไรเราบ้าง? ตอนนี้เราสามารถเขียนโค้ดของตัวเปรียบเทียบได้ดังนี้:

Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
เราตั้งชื่อตัวแปรในวงเล็บ Java จะเห็นว่าเนื่องจากมีเพียงวิธีเดียว ดังนั้นจำนวนและประเภทของพารามิเตอร์อินพุตที่ต้องการจึงชัดเจน จากนั้นเราใช้ตัวดำเนินการลูกศรเพื่อส่งไปยังส่วนนี้ของรหัส ยิ่งไปกว่านั้น ขอบคุณ Java 8 ตอนนี้เรามีเมธอดเริ่มต้นในอินเทอร์เฟซ วิธีการเหล่านี้ปรากฏขึ้นตามค่าเริ่มต้นเมื่อเราใช้อินเทอร์เฟซ อินComparatorเทอร์เฟซมีหลายอย่าง ตัวอย่างเช่น:

Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
มีอีกวิธีที่จะทำให้โค้ดของคุณสะอาดขึ้น ดูตัวอย่างด้านบนที่เรากำหนดตัวเปรียบเทียบของเรา มันทำอะไร? มันค่อนข้างดั้งเดิม เพียงแค่ใช้วัตถุและแยกค่าบางอย่างที่ "เปรียบเทียบได้" ตัวอย่างเช่นIntegerใช้comparableดังนั้นเราจึงสามารถดำเนินการเปรียบเทียบกับค่าของช่องรหัสข้อความได้ ฟังก์ชันเปรียบเทียบอย่างง่ายนี้สามารถเขียนได้ดังนี้:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
กล่าวอีกนัยหนึ่ง เรามี a Comparatorที่เปรียบเทียบแบบนี้: ใช้วัตถุ ใช้getId()วิธีรับ a Comparableจากสิ่งเหล่านั้น แล้วใช้compareToเปรียบเทียบ และไม่มีโครงสร้างที่น่ากลัวอีกต่อไป และสุดท้าย ฉันต้องการทราบคุณลักษณะอื่น ตัวเปรียบเทียบสามารถถูกล่ามโซ่ได้ ตัวอย่างเช่น:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

แอปพลิเคชัน

การประกาศตัวเปรียบเทียบนั้นค่อนข้างสมเหตุสมผลคุณว่าไหม? ตอนนี้เราต้องดูว่าจะใช้อย่างไรและที่ไหน → Collections.sort(java.util.Collections) เราสามารถจัดเรียงคอลเลกชันด้วยวิธีนี้ได้ แต่ไม่ใช่ทุกคอลเลคชัน รายการเท่านั้น ไม่มีอะไรผิดปกติที่นี่ เนื่องจากรายการเป็นประเภทของคอลเล็กชันที่คุณเข้าถึงองค์ประกอบด้วยดัชนี สิ่งนี้ทำให้สามารถสลับองค์ประกอบที่สองกับองค์ประกอบที่สามได้ นั่นเป็นสาเหตุที่วิธีการเรียงลำดับต่อไปนี้ใช้สำหรับรายการเท่านั้น:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort(java.util.Arrays) อาร์เรย์ยังง่ายต่อการจัดเรียง อีกครั้ง ด้วยเหตุผลเดียวกัน — องค์ประกอบของพวกเขาเข้าถึงได้โดยดัชนี → Descendants of java.util.SortedSet and java.util.SortedMap คุณจะเรียกคืนได้SetและMapไม่รับประกันลำดับการจัดเก็บองค์ประกอบ แต่เรามีการใช้งานพิเศษที่รับประกันการสั่งซื้อ และถ้าองค์ประกอบของคอลเลกชันไม่ได้ใช้งานjava.util.Comparableเราสามารถส่ง a Comparatorไปยังตัวสร้างได้:

Set<Message> msgSet = new TreeSet(comparator);
Stream API ใน Stream API ซึ่งปรากฏใน Java 8 ตัวเปรียบเทียบช่วยให้คุณลดความซับซ้อนในการทำงานกับองค์ประกอบสตรีม ตัวอย่างเช่น สมมติว่าเราต้องการลำดับของตัวเลขสุ่มตั้งแต่ 0 ถึง 999 รวมถึง:

Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
เราสามารถหยุดที่นี่ได้ แต่มีปัญหาที่น่าสนใจยิ่งกว่านั้น ตัวอย่างเช่น สมมติว่าคุณต้องเตรียม a Mapโดยที่คีย์คือรหัสข้อความ นอกจากนี้ เราต้องการจัดเรียงคีย์เหล่านี้ ดังนั้นเราจะเริ่มด้วยรหัสต่อไปนี้:

Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
ที่จริงเราได้ที่HashMapนี่ และอย่างที่เราทราบ มันไม่รับประกันคำสั่งซื้อใดๆ เป็นผลให้องค์ประกอบของเราซึ่งจัดเรียงตาม id สูญเสียลำดับไป ไม่ดี. เราจะต้องเปลี่ยนตัวสะสมของเราเล็กน้อย:

Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg, (oldValue, newValue) -> oldValue, TreeMap::new));
รหัสเริ่มดูน่ากลัวขึ้นเล็กน้อย แต่ตอนนี้ปัญหาได้รับการแก้ไขอย่างถูกต้องแล้ว อ่านเพิ่มเติมเกี่ยวกับการจัดกลุ่มต่างๆ ได้ที่นี่: คุณสามารถสร้างนักสะสมของคุณเองได้ อ่านเพิ่มเติมที่นี่: "การสร้างตัวรวบรวมแบบกำหนดเองใน Java 8 " และคุณ จะ ได้ประโยชน์จากการอ่านการสนทนาที่นี่: "รายการ Java 8 เพื่อแมปกับสตรีม"

กับดักตก

ComparatorและComparableเป็นสิ่งที่ดี แต่มีข้อแม้อย่างหนึ่งที่คุณควรจำไว้ เมื่อคลาสทำการเรียงลำดับ คลาสของคุณจะถูกแปลงเป็นไฟล์Comparable. หากไม่เป็นเช่นนั้น คุณจะได้รับข้อผิดพลาดในขณะดำเนินการ ลองดูตัวอย่าง:

SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
ดูเหมือนว่าไม่มีอะไรผิดปกติที่นี่ แต่ในความเป็นจริง ในตัวอย่างของเรา มันจะล้มเหลวโดยมีข้อผิดพลาด: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable และทั้งหมดเป็นเพราะพยายามจัดเรียงองค์ประกอบ ( SortedSetหลังจากทั้งหมดมันคือ a)...แต่ทำไม่ได้ อย่าลืมสิ่งนี้เมื่อทำงานกับ SortedMapและSortedSet
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION