JavaRush /จาวาบล็อก /Random-TH /เครื่องมือเปรียบเทียบใน Java
Viacheslav
ระดับ

เครื่องมือเปรียบเทียบใน Java

เผยแพร่ในกลุ่ม
มีเพียงคนขี้เกียจเท่านั้นที่ไม่ได้เขียนเกี่ยวกับ Comparator และการเปรียบเทียบใน Java ฉันไม่ได้ขี้เกียจ - ฉันขอให้คุณรักและสนับสนุนอีกรูปแบบหนึ่ง ฉันหวังว่ามันจะไม่ฟุ่มเฟือย ใช่แล้ว บทความนี้เป็นคำตอบสำหรับคำถามที่ว่า “คุณเขียนตัวเปรียบเทียบจากความทรงจำได้ไหม” ฉันหวังว่าหลังจากอ่านบทความนี้แล้ว ทุกคนจะสามารถเขียนตัวเปรียบเทียบจากหน่วยความจำได้
เครื่องมือเปรียบเทียบใน Java - 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;
    }
}
มาเพิ่มคลาสนี้ในTutorialspoint java compiler อย่าลืมเพิ่มการนำเข้าด้วย:
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
ในวิธีการหลัก เราจะสร้างข้อความต่างๆ:
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) ของวัตถุเป็นตัวอย่างของคลาส มันหมายความว่าอะไร? ซึ่งหมายความว่าหากคุณสร้างคลาสขึ้นมาสองอินสแตนซ์ที่แตกต่างกัน เนื่องจากอินสแตนซ์นั้นแตกต่างกัน hashCode ของคลาสจึงควรแตกต่างกัน นี่คือสิ่งที่กล่าวไว้ในคำอธิบายของวิธีการ: “เท่าที่ใช้งานได้จริง วิธี hashCode ที่กำหนดโดยคลาส Object จะส่งกลับจำนวนเต็มที่แตกต่างกันสำหรับวัตถุที่แตกต่างกัน” นั่นคือหากสิ่งเหล่านี้เป็นสองกรณีที่แตกต่างกัน ก็ควรจะมีความแตกต่างกัน แฮชโค้ด นั่นคือวิธีนี้ไม่เหมาะกับการเปรียบเทียบของเรา → เท่ากับ วิธีการเท่ากับตอบคำถาม "วัตถุเท่ากัน" และส่งกลับค่าบูลีน วิธีนี้มีรหัสเริ่มต้น:
public boolean equals(Object obj) {
    return (this == obj);
}
นั่นคือ หากไม่มีการแทนที่วิธีนี้บนอ็อบเจ็กต์ เมธอดนี้จะบอกว่าการอ้างอิงไปยังอ็อบเจ็กต์ตรงกันหรือไม่ สิ่งนี้ไม่เหมาะกับข้อความของเรา เนื่องจากเราไม่สนใจลิงก์ไปยังวัตถุ เราจึงสนใจรหัสข้อความ และแม้ว่าเราจะแทนที่วิธีเท่ากับ ค่าสูงสุดที่เราจะได้รับก็คือ: “พวกมันเท่ากัน” หรือ “พวกมันไม่เท่ากัน” แต่นี่ยังไม่เพียงพอสำหรับเราในการกำหนดลำดับ

ตัวเปรียบเทียบและตัวเปรียบเทียบใน Java

อะไรที่เหมาะกับเรา? ถ้าเราแปลคำว่า "เปรียบเทียบ" เป็นภาษาอังกฤษในตัวแปล เราก็จะได้คำแปล "เปรียบเทียบ" เยี่ยมเลย เราต้องการใครสักคนที่จะเปรียบเทียบ ถ้าคุณเปรียบเทียบการเปรียบเทียบนี้ คนที่เปรียบเทียบก็คือผู้เปรียบเทียบ มาเปิดJava Apiแล้วค้นหาComparator ที่ นั่น และแน่นอนว่ามีอินเทอร์เฟซดังกล่าว - java.util.Comparator java.util.Comparator และ java.lang.Comparable อย่างที่คุณเห็นมีอินเทอร์เฟซดังกล่าว คลาสที่นำไปใช้บอกว่า "ฉันกำลังใช้ฟังก์ชันสำหรับการเปรียบเทียบวัตถุ" สิ่งเดียวที่ต้องจำจริงๆ คือสัญญาเปรียบเทียบ ซึ่งแสดงดังนี้:

Comparator возвращает int по следующей схеме: 
  • отрицательный int (первый an object отрицательный, то есть меньше)
  • положительный int (первый an object положительный, хороший, то есть больший)
  • ноль = an objectы равны
ทีนี้ลองเขียนตัวเปรียบเทียบกัน เรา จะต้องนำเข้าjava.util.Comparator หลังจากนำเข้าแล้ว ให้เพิ่มวิธีการไปที่ main: Comparator<Message> comparator = new Comparator<Message>(); โดยปกติแล้ว สิ่งนี้จะไม่ทำงานเพราะว่า ตัวเปรียบเทียบเป็นอินเทอร์เฟซ ดังนั้นหลังจากวงเล็บเราจะเพิ่มอัน หยิก{ }. ในวงเล็บนี้เราจะเขียนวิธีการ:
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
คุณไม่จำเป็นต้องจำที่จะเขียนสิ่งนี้ ผู้เปรียบเทียบคือผู้ทำการเปรียบเทียบ กล่าวคือ ทำการเปรียบเทียบ เพื่อตอบคำถามว่าวัตถุที่เปรียบเทียบอยู่ในลำดับใด เราจะส่งคืน int นั่นคือทั้งหมดจริงๆ อย่างง่ายดายและง่ายดาย ดังที่เราเห็นจากตัวอย่าง นอกเหนือจาก Comparator แล้ว ยังมีอินเทอร์เฟซอื่น - java.lang.Comparable ซึ่งเราต้องกำหนดเมธอดcomparisonTo อินเทอร์เฟซนี้บอกว่า "คลาสที่ใช้อินเทอร์เฟซช่วยให้สามารถเปรียบเทียบอินสแตนซ์ของคลาสได้" ตัวอย่างเช่น การใช้งาน comparisonTo ของ Integer มีลักษณะดังนี้:
(x < y) ? -1 : ((x == y) ? 0 : 1)
จะจำอินเทอร์เฟซเหล่านี้ทั้งหมดได้อย่างไร? เพื่ออะไร? ทุกอย่างมาจากภาษาอังกฤษ เปรียบเทียบ - เพื่อเปรียบเทียบผู้ที่เปรียบเทียบคือ Comparator (ในฐานะนายทะเบียนเป็นต้นนั่นคือผู้ที่ลงทะเบียน) และคำคุณศัพท์ "เปรียบเทียบ" คือ Comparable คำว่า "เปรียบเทียบกับ" ไม่เพียงแต่แปลว่าเปรียบเทียบกับเท่านั้น แต่ยังแปลว่าเปรียบเทียบกับอีกด้วย มันง่ายมาก ภาษา Java เขียนโดยผู้ที่พูดภาษาอังกฤษ และในการตั้งชื่อทุกอย่างในภาษา Java พวกเขาใช้ภาษาอังกฤษเพียงอย่างเดียว และมีเหตุผลบางอย่างในการตั้งชื่อ และเมธอด comparisonTo อธิบายว่าอินสแตนซ์ของคลาสควรถูกเปรียบเทียบกับอินสแตนซ์อื่นอย่างไร ตัวอย่างเช่น สตริงจะถูกเปรียบเทียบตามพจนานุกรมและตัวเลขจะถูกเปรียบเทียบตามค่า
เครื่องมือเปรียบเทียบใน Java - 2
Java 8 นำการเปลี่ยนแปลงที่ดีมาให้ @FunctionalInterfaceหากเราดูอินเทอร์เฟ ซของ Comparator อย่างใกล้ชิด เราจะเห็นว่ามีคำอธิบายประกอบอยู่ด้านบน อันที่จริง คำอธิบายประกอบนี้มีไว้เพื่อเป็นข้อมูลและหมายความว่าอินเทอร์เฟซนี้ใช้งานได้ ซึ่งหมายความว่าอินเทอร์เฟซนี้มีวิธีนามธรรมเพียง 1 วิธีโดยไม่มีการใช้งาน สิ่งนี้ให้อะไรเราบ้าง? เราสามารถเขียนโค้ดตัวเปรียบเทียบได้ดังนี้:
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
ในวงเล็บคือวิธีที่เราตั้งชื่อตัวแปร Java เองจะเห็นว่าเพราะว่า หากมีเพียงวิธีเดียว ก็ชัดเจนว่าจำเป็นต้องใช้พารามิเตอร์อินพุตใด จำนวนเท่าใด และประเภทใด ต่อไปเราพูดด้วยลูกศรว่าเราต้องการโอนไปยังโค้ดส่วนนี้ นอกจากนี้ด้วย Java 8 วิธีการเริ่มต้นจึงปรากฏในอินเทอร์เฟซซึ่งเป็นวิธีการที่ปรากฏตามค่าเริ่มต้น (โดยค่าเริ่มต้น) เมื่อเราใช้อินเทอร์เฟซ มีหลายสิ่งเหล่านี้ในอินเทอร์เฟซตัวเปรียบเทียบ ตัวอย่างเช่น:
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
มีอีกวิธีหนึ่งที่จะทำให้โค้ดของคุณสะอาดขึ้น ลองดูตัวอย่างด้านบนที่เราอธิบายตัวเปรียบเทียบของเรา เขากำลังทำอะไร? มันค่อนข้างดั้งเดิม มันเพียงแค่นำวัตถุมาและดึงค่าบางอย่างที่เทียบเคียงได้ออกมา ตัวอย่างเช่น Integer ใช้การเปรียบเทียบ ดังนั้นเราจึงสามารถดำเนินการเปรียบเทียบTo กับค่ารหัสข้อความได้ ฟังก์ชันตัวเปรียบเทียบอย่างง่ายนี้สามารถเขียนได้ดังนี้:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
นั่นคือแท้จริงแล้ว “เรามีตัวเปรียบเทียบที่เปรียบเทียบดังนี้: มันรับอ็อบเจ็กต์, รับ Comparable จากพวกมันโดยใช้เมธอด getId(), เปรียบเทียบโดยใช้ comparisonTo” และไม่มีการออกแบบที่แย่อีกต่อไป และสุดท้ายนี้ ฉันอยากจะทราบคุณลักษณะอีกอย่างหนึ่ง ตัวเปรียบเทียบสามารถถูกล่ามโซ่ไว้ด้วยกัน ตัวอย่างเช่น:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

แอปพลิเคชัน

การประกาศเปรียบเทียบนั้นค่อนข้างสมเหตุสมผลใช่ไหม ตอนนี้เราต้องดูว่าจะใช้อย่างไรและในสถานที่ใดบ้าง → Collections.sort (java.util.Collections) แน่นอนว่าเราสามารถจัดเรียงคอลเลกชันได้ด้วยวิธีนี้ แต่ไม่ใช่ทุกอย่าง แค่รายการ และที่นี่ก็ไม่มีอะไรผิดปกติเพราะ... เป็นรายการที่ต้องเข้าถึงองค์ประกอบตามดัชนี และช่วยให้สามารถสลับองค์ประกอบหมายเลข 2 กับองค์ประกอบหมายเลข 3 ได้ ดังนั้น การเรียงลำดับด้วยวิธีนี้จึงทำได้เฉพาะกับรายการเท่านั้น:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort (java.util.Arrays) การเรียงลำดับอาร์เรย์ก็สะดวกเช่นกัน อีกครั้งด้วยเหตุผลเดียวกันในการเข้าถึงองค์ประกอบตามดัชนี → ลูกหลานของ java.util.SortedSet และ java.util.SortedMap ตามที่เราจำได้ Set และ Map ไม่รับประกันลำดับการจัดเก็บบันทึก แต่เรามีการดำเนินการพิเศษที่รับประกันการสั่งซื้อ และหากองค์ประกอบการรวบรวมไม่ได้ใช้ java.lang.Comparable เราก็สามารถส่ง 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));
เราหยุดได้ แต่มีปัญหาที่น่าสนใจมากกว่านั้น ตัวอย่างเช่น คุณต้องเตรียมแผนที่ โดยที่รหัสคือรหัสข้อความ ในเวลาเดียวกัน เราต้องการจัดเรียงคีย์เหล่านี้เพื่อให้คีย์เรียงลำดับจากเล็กที่สุดไปใหญ่ที่สุด เริ่มต้นด้วยรหัสนี้:
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));
โค้ดดูน่าขนลุกเล็กน้อย แต่ตอนนี้ปัญหาได้รับการแก้ไขอย่างถูกต้องแล้วด้วยการใช้ TreeMap อย่างชัดเจน คุณสามารถอ่านเพิ่มเติมเกี่ยวกับกลุ่มต่างๆ ได้ที่นี่: คุณสามารถสร้างนักสะสมได้ด้วยตัวเอง คุณสามารถอ่านเพิ่มเติมได้ที่นี่: "การสร้างตัวรวบรวมแบบกำหนดเองใน Java 8 " และมีประโยชน์ในการอ่านการสนทนาที่นี่: "รายการ Java 8 ที่จะแมปด้วยสตรีม "
เครื่องมือเปรียบเทียบใน Java - 3
คราดเปรียบเทียบและ คราด เปรียบเทียบ เป็นสิ่งที่ดี แต่มีความแตกต่างเล็กน้อยที่เกี่ยวข้องกับพวกเขาซึ่งควรค่าแก่การจดจำ เมื่อคลาสทำการเรียงลำดับ มันจะคำนวณว่าคลาสของคุณสามารถปรับเทียบได้ หากไม่เป็นเช่นนั้น คุณจะได้รับข้อผิดพลาดในเวลาดำเนินการ ลองดูตัวอย่าง:
SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
ดูเหมือนว่าไม่มีอะไรผิดปกติที่นี่ แต่ในความเป็นจริง ในตัวอย่างของเรา มันจะผิดพลาดโดยมีข้อผิดพลาด: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable และทั้งหมดเป็นเพราะพยายามเรียงลำดับองค์ประกอบ (ท้ายที่สุดแล้วมันคือ SortedSet) และฉันก็ทำไม่ได้ คุณควรจำสิ่งนี้ไว้เมื่อทำงานกับ SortedMap และ SortedSet แนะนำ เพิ่มเติม สำหรับการดู: Yuri Tkach: HashSet และ TreeSet - Collections #1 - Java ขั้นสูง
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION