Java 14 บันทึกและอาร์เรย์


11

รับรหัสต่อไปนี้:

public static void main(String[] args) {
    record Foo(int[] ints){}

    var ints = new int[]{1, 2};
    var foo = new Foo(ints);
    System.out.println(foo); // Foo[ints=[I@6433a2]
    System.out.println(new Foo(new int[]{1,2}).equals(new Foo(new int[]{1,2}))); // false
    System.out.println(new Foo(ints).equals(new Foo(ints))); //true
    System.out.println(foo.equals(foo)); // true
}

ดูเหมือนว่าเห็นได้ชัดของอาร์เรย์ที่toString, equalsวิธีการใช้ (แทนวิธีการแบบคงที่Arrays::equals, Arrays::deepEquals หรือArray::toString)

ดังนั้นฉันจึงเดาว่า Java 14 Records ( JEP 359 ) ทำงานได้ไม่ดีนักกับอาร์เรย์วิธีการนั้นต้องสร้างด้วย IDE (อย่างน้อยใน IntelliJ โดยค่าเริ่มต้นจะสร้างวิธีการ "มีประโยชน์" นั่นคือพวกเขาใช้วิธีการคงที่ ในArrays)

หรือมีวิธีการแก้ปัญหาอื่น ๆ ?


3
วิธีการเกี่ยวกับการใช้Listแทนอาร์เรย์?
Oleg

ฉันไม่เข้าใจว่าทำไมต้องสร้างวิธีดังกล่าวด้วย IDE วิธีการทั้งหมดควรสามารถสร้างได้ด้วยมือ
NomadMaker

1
toString(), equals()และhashCode()วิธีการของการบันทึกจะดำเนินการโดยใช้การอ้างอิง invokedynamic . ถ้ามีเพียงคลาสที่คอมไพล์แล้วเท่านั้นที่สามารถเข้าใกล้กับวิธีที่Arrays.deepToStringใช้ในเมธอดโอเวอร์โหลดส่วนตัวในวันนี้มันอาจจะถูกแก้ไขในกรณีดั้งเดิม
Naman

1
ในการที่สองทางเลือกการออกแบบสำหรับการนำไปใช้งานสำหรับบางสิ่งที่ไม่ได้นำเสนอการใช้งานแบบแทนที่ของวิธีการเหล่านี้จึงไม่ใช่ความคิดที่ดีที่จะถอยกลับไปObjectเนื่องจากสิ่งนี้อาจเกิดขึ้นกับคลาสที่ผู้ใช้กำหนด เช่นเท่ากับไม่ถูกต้อง
Naman

1
@Naman ตัวเลือกที่ใช้invokedynamicไม่มีอะไรเกี่ยวข้องกับการเลือกซีแมนทิกส์ indy เป็นรายละเอียดการใช้งานจริงที่นี่ คอมไพเลอร์อาจปล่อยสัญญาณ bytecode ให้ทำสิ่งเดียวกัน; นี่เป็นวิธีที่มีประสิทธิภาพและยืดหยุ่นกว่าในการไปที่นั่น มันถูกถกเถียงกันอย่างกว้างขวางในระหว่างการออกแบบของเร็กคอร์ดว่าจะใช้ความหมายที่เท่าเทียมกันมากขึ้น (เช่นความเสมอภาคลึกสำหรับอาร์เรย์) แต่สิ่งนี้กลับกลายเป็นทำให้เกิดปัญหามากขึ้นกว่าที่ควรจะเป็น
Brian Goetz

คำตอบ:


18

Java อาร์เรย์ก่อให้เกิดความท้าทายหลายประการสำหรับระเบียนและสิ่งเหล่านี้เพิ่มข้อ จำกัด จำนวนมากในการออกแบบ อาร์เรย์มีความไม่แน่นอนและความหมายของความเท่าเทียมกัน (สืบทอดมาจาก Object) คือโดยเอกลักษณ์ไม่ใช่เนื้อหา

ปัญหาพื้นฐานกับตัวอย่างของคุณคือคุณต้องการให้equals()ในอาร์เรย์หมายถึงความเท่าเทียมกันของเนื้อหาไม่ใช่การอ้างอิงความเท่าเทียมกัน ซีแมนทิกส์ (ค่าเริ่มต้น) equals()สำหรับบันทึกจะขึ้นอยู่กับความเสมอภาคของส่วนประกอบ ในตัวอย่างของคุณทั้งสองFooระเบียนที่มีอาร์เรย์ที่แตกต่างกันมีความแตกต่างกันและการบันทึกมีพฤติกรรมที่ถูกต้อง ปัญหาคือคุณเพียงต้องการให้การเปรียบเทียบความเท่าเทียมกันแตกต่างกัน

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

record Foo(String[] ss) {
    Foo { ss = ss.clone(); }
    String[] ss() { return ss.clone(); }
    boolean equals(Object o) { 
        return o instanceof Foo 
            && Arrays.equals(((Foo) o).ss, ss);
    }
    int hashCode() { return Objects.hash(Arrays.hashCode(ss)); }
}

สิ่งนี้ทำคือการป้องกันการคัดลอกในทางใน (ในตัวสร้าง) และวิธีการออก (ในการเข้าถึง) เช่นเดียวกับการปรับความหมายความเท่าเทียมกันที่จะใช้เนื้อหาของอาร์เรย์ สิ่งนี้สนับสนุนค่าคงที่ที่จำเป็นในซูเปอร์คลาสjava.lang.Recordว่า "การแยกเร็กคอร์ดออกเป็นส่วนประกอบของมันและการสร้างส่วนประกอบใหม่ในเร็กคอร์ดใหม่ให้ผลการบันทึกที่เท่าเทียมกัน"

คุณอาจพูดว่า "แต่มันใช้งานได้มากเกินไปฉันต้องการใช้บันทึกดังนั้นฉันจึงไม่ต้องพิมพ์ทุกอย่าง" แต่ไม่ได้บันทึกหลักเครื่องมือประโยค (แม้ว่าพวกเขาจะ syntactically สบายมากขึ้น) พวกเขาเป็นเครื่องมือที่มีความหมาย: บันทึกเป็นtuples เล็กน้อย ส่วนใหญ่แล้วซินแท็กซ์คอมแพคจะให้ความหมายที่ต้องการ แต่ถ้าคุณต้องการซีแมนทิกส์ต่างกันคุณต้องทำงานพิเศษบางอย่าง


6
นอกจากนี้ยังเป็นข้อผิดพลาดทั่วไปโดยผู้ที่ต้องการความเท่าเทียมกันของอาเรย์คือโดยเนื้อหาที่จะสมมติว่าไม่มีใครต้องการความเท่าเทียมกันของอาเรย์โดยอ้างอิง แต่นั่นไม่ใช่ความจริง เป็นเพียงว่าไม่มีคำตอบเดียวที่เหมาะกับทุกกรณี บางครั้งการอ้างอิงความเท่าเทียมกันเป็นสิ่งที่คุณต้องการ
Brian Goetz

9

List< Integer > วิธีแก้ปัญหา

การแก้ปัญหา: ใช้ListของIntegerวัตถุ ( List< Integer >) มากกว่าอาร์เรย์ของ primitives ( int[])

ในตัวอย่างนี้ฉันยกตัวอย่างรายการที่ไม่สามารถระบุได้ของคลาสที่ไม่ระบุโดยใช้List.ofคุณลักษณะที่เพิ่มเข้ากับ Java 9 คุณสามารถใช้ได้เช่นกันArrayListสำหรับรายการที่แก้ไขได้ซึ่งสนับสนุนโดยอาร์เรย์

package work.basil.example;

import java.util.List;

public class RecordsDemo
{
    public static void main ( String[] args )
    {
        RecordsDemo app = new RecordsDemo();
        app.doIt();
    }

    private void doIt ( )
    {

        record Foo(List < Integer >integers)
        {
        }

        List< Integer > integers = List.of( 1 , 2 );
        var foo = new Foo( integers );

        System.out.println( foo ); // Foo[integers=[1, 2]]
        System.out.println( new Foo( List.of( 1 , 2 ) ).equals( new Foo( List.of( 1 , 2 ) ) ) ); // true
        System.out.println( new Foo( integers ).equals( new Foo( integers ) ) ); // true
        System.out.println( foo.equals( foo ) ); // true
    }
}

ไม่ใช่ความคิดที่ดีเสมอไปที่จะเลือก List over a array และเราก็คุยกันอยู่ดี
Naman

@Naman ฉันไม่เคยอ้างสิทธิ์ว่าเป็น "ความคิดที่ดี" คำถามถามหาทางออกอย่างแท้จริง ฉันให้หนึ่งอัน และฉันก็ทำไปหนึ่งชั่วโมงก่อนที่ความคิดเห็นของคุณจะเชื่อมโยง
Basil Bourque

5

การแก้ปัญหา:การสร้างการเรียนและห่อIntArrayint[]

record Foo(IntArray ints) {
    public Foo(int... ints) { this(new IntArray(ints)); }
    public int[] getInts() { return this.ints.get(); }
}

ไม่สมบูรณ์เพราะตอนนี้คุณต้องโทรfoo.getInts()แทนfoo.ints()แต่ทุกอย่างอื่นก็ทำงานตามที่คุณต้องการ

public final class IntArray {
    private final int[] array;
    public IntArray(int[] array) {
        this.array = Objects.requireNonNull(array);
    }
    public int[] get() {
        return this.array;
    }
    @Override
    public int hashCode() {
        return Arrays.hashCode(this.array);
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null || getClass() != obj.getClass())
            return false;
        IntArray that = (IntArray) obj;
        return Arrays.equals(this.array, that.array);
    }
    @Override
    public String toString() {
        return Arrays.toString(this.array);
    }
}

เอาท์พุต

Foo[ints=[1, 2]]
true
true
true

1
นั่นไม่เทียบเท่ากับการบอกให้ใช้คลาสและไม่บันทึกในกรณีเช่นนี้หรือ
Naman

1
@Naman ไม่เลยเพราะคุณrecordอาจมีหลายสาขาและมีเพียงฟิลด์อาร์เรย์เท่านั้นที่ถูกห่อเช่นนี้
Andreas

ฉันได้รับจุดที่คุณพยายามทำในแง่ของความสามารถนำกลับมาใช้ใหม่ได้ แต่มีคลาส inbuilt เช่นListที่ให้การห่อหุ้มแบบนั้นอาจค้นหาด้วยวิธีแก้ปัญหาตามที่เสนอไว้ที่นี่ หรือคุณพิจารณาว่าอาจเป็นค่าใช้จ่ายสำหรับกรณีการใช้งานดังกล่าว?
Naman

3
@Naman หากคุณต้องการเก็บไว้int[]a List<Integer>จะไม่เหมือนกันด้วยเหตุผลอย่างน้อยสองประการ: 1) รายการใช้หน่วยความจำมากขึ้นและ 2) ไม่มีการแปลงในตัวระหว่างกัน Integer[]🡘 List<Integer>ค่อนข้างง่าย ( toArray(...)และArrays.asList(...)) แต่int[]🡘 List<Integer>ทำงานได้มากขึ้นและการแปลงต้องใช้เวลาจึงเป็นสิ่งที่คุณไม่ต้องการทำตลอดเวลา หากคุณมีint[]และต้องการเก็บไว้ในบันทึก (กับสิ่งอื่นมิฉะนั้นทำไมต้องใช้บันทึก) และต้องการมันเป็นint[]แล้วแปลงทุกครั้งที่คุณต้องการมันผิด
Andreas

จุดดีแน่นอน ฉันจะได้ แต่ถ้าคุณให้ nitpicking ขอให้สิ่งที่เป็นประโยชน์เราจะยังคงเห็นด้วยArrays.equals, Arrays.toStringในช่วงListการดำเนินการใช้ในขณะที่การเปลี่ยนint[]เป็นข้อเสนอแนะในคำตอบอื่น ๆ (สมมติว่าทั้งสองสิ่งนี้เป็นวิธีแก้ปัญหาอยู่แล้ว)
Naman
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.