การสร้างคลาส Java ด้วยพารามิเตอร์ค่าเวลาคอมไพล์


10

พิจารณาสถานการณ์ที่คลาสใช้พฤติกรรมพื้นฐานวิธีการและอื่น ๆ แต่คลาสที่แตกต่างกันของคลาสนั้นอาจมีอยู่สำหรับการใช้ที่แตกต่างกัน ในกรณีเฉพาะของฉันฉันมีเวกเตอร์ (เวกเตอร์เรขาคณิตไม่ใช่รายการ) และเวกเตอร์นั้นสามารถใช้ได้กับพื้นที่ยูคลิดมิติ N- มิติใด ๆ (1 มิติ, 2 มิติ, ... ) คลาส / ประเภทนี้สามารถกำหนดได้อย่างไร?

นี่จะเป็นเรื่องง่ายใน C ++ ที่เทมเพลตของคลาสสามารถมีค่าจริงเป็นพารามิเตอร์ได้ แต่เราไม่มีความหรูหราใน Java

ทั้งสองวิธีที่ฉันสามารถคิดได้ว่าสามารถนำมาใช้เพื่อแก้ปัญหานี้คือ:

  1. มีการดำเนินการของแต่ละกรณีที่เป็นไปได้ในเวลารวบรวม

    public interface Vector {
        public double magnitude();
    }
    
    public class Vector1 implements Vector {
        public final double x;
        public Vector1(double x) {
            this.x = x;
        }
        @Override
        public double magnitude() {
            return x;
        }
        public double getX() {
            return x;
        }
    }
    
    public class Vector2 implements Vector {
        public final double x, y;
        public Vector2(double x, double y) {
            this.x = x;
            this.y = y;
        }
        @Override
        public double magnitude() {
            return Math.sqrt(x * x + y * y);
        }
        public double getX() {
            return x;
        }
        public double getY() {
            return y;
        }
    }

    เห็นได้ชัดว่าการแก้ปัญหานี้ใช้เวลานานมากและเป็นที่น่าเบื่ออย่างยิ่งสำหรับรหัส ในตัวอย่างนี้ดูเหมือนจะไม่เลวร้ายเกินไป แต่ในรหัสที่แท้จริงของฉันฉันกำลังติดต่อกับเวกเตอร์ที่มีการใช้งานหลายแบบแต่ละอันมีขนาดสูงสุดถึงสี่มิติ (x, y, z และ w) ขณะนี้ฉันมีโค้ดมากกว่า 2,000 บรรทัดแม้ว่าแต่ละเวคเตอร์จะต้องการเพียง 500 เท่านั้น

  2. การระบุพารามิเตอร์ที่รันไทม์

    public class Vector {
        private final double[] components;
        public Vector(double[] components) {
            this.components = components;
        }
        public int dimensions() {
            return components.length;
        }
        public double magnitude() {
            double sum = 0;
            for (double component : components) {
                sum += component * component;
            }
            return Math.sqrt(sum);
        }
        public double getComponent(int index) {
            return components[index];
        }
    }

    น่าเสียดายที่วิธีนี้ทำให้ประสิทธิภาพของโค้ดแย่ลงส่งผลให้รหัส messier กว่าโซลูชันเดิมและไม่ปลอดภัยในการคอมไพล์เวลา ตัวอย่างเช่น).

ขณะนี้ฉันกำลังพัฒนาจริง ๆ ใน Xtend ดังนั้นหากมีโซลูชัน Xtend ใด ๆ พร้อมใช้งานพวกเขาก็จะยอมรับได้เช่นกัน


เมื่อคุณใช้ Xtend คุณกำลังทำสิ่งนี้ในบริบทของ Xtext DSL หรือไม่
Dan1701

2
DSL นั้นยอดเยี่ยมสำหรับแอปพลิเคชั่นที่ใช้รหัส สั้นคุณสร้างไวยากรณ์ภาษาเล็ก ๆ น้อย ๆ อินสแตนซ์ของภาษานั้น (อธิบายเวกเตอร์ต่างๆในกรณีนี้) และรหัสบางอย่างที่ดำเนินการเมื่อมีการบันทึกอินสแตนซ์ (สร้างรหัส Java ของคุณ) มีความอุดมสมบูรณ์ของทรัพยากรและตัวอย่างอยู่บนเว็บไซต์ Xtext
Dan1701

2
มีวิธีแก้ปัญหาที่สมบูรณ์แบบสำหรับปัญหานี้โดยใช้ชนิดที่พึ่งพา (มันมากหรือน้อยกว่าสิ่งที่พวกเขาสร้างขึ้นมา) แต่อนิจจาที่ไม่สามารถใช้ได้ใน Java ฉันจะใช้วิธีแก้ปัญหาแรกถ้าคุณมีคลาสเล็ก ๆ จำนวนคงที่ (บอกว่าคุณใช้เวกเตอร์ 1, 2, 3 และ 3 มิติ) และวิธีแก้ปัญหาหลังมากกว่านั้น เห็นได้ชัดว่าฉันไม่สามารถพูดได้โดยไม่ต้องเรียกใช้รหัสของคุณ แต่ฉันไม่คิดว่าจะมีผลกระทบต่อประสิทธิภาพการทำงานที่คุณกังวล
Gardenhead

1
คลาสที่สองเหล่านั้นไม่มีอินเทอร์เฟซเดียวกันพวกเขาไม่ใช่ polymorphic แต่คุณกำลังพยายามใช้ polymorphically
Martin Spamer

1
หากคุณกำลังเขียนคณิตศาสตร์พีชคณิตเชิงเส้นและมีความกังวลเกี่ยวกับประสิทธิภาพแล้วทำไมจาวา ฉันไม่เห็นอะไรเลยนอกจากปัญหาในนั้น
Sopel

คำตอบ:


1

ในกรณีเช่นนี้ฉันใช้การสร้างรหัส

ฉันเขียนแอปพลิเคชัน java ที่สร้างรหัสจริง ด้วยวิธีนี้คุณสามารถใช้ for for loop เพื่อสร้างกลุ่มเวอร์ชันที่แตกต่างกัน ฉันใช้JavaPoetซึ่งทำให้ตรงไปตรงมาเพื่อสร้างรหัสจริง จากนั้นคุณสามารถรวมการรันการสร้างรหัสลงในระบบการสร้างของคุณ


0

ฉันมีโมเดลที่คล้ายกันมากในแอปพลิเคชันของฉันและโซลูชันของเราคือเพียงเก็บแผนที่ขนาดไดนามิกเช่นเดียวกับโซลูชันของคุณ 2

คุณก็ไม่จำเป็นต้องกังวลเกี่ยวกับประสิทธิภาพด้วย java array primative แบบนั้น เราสร้างเมทริกซ์ที่มีขนาดขอบบน 100 คอลัมน์ (อ่าน: เวกเตอร์ 100 มิติ) 10,000 แถวและเรามีประสิทธิภาพที่ดีด้วยเวกเตอร์ชนิดที่ซับซ้อนกว่าโซลูชันของคุณ 2 คุณอาจลองปิดผนึกคลาสหรือวิธีการทำเครื่องหมายเป็นขั้นสุดท้าย เพื่อเพิ่มความเร็ว แต่ฉันคิดว่าคุณเพิ่มประสิทธิภาพก่อนกำหนด

คุณสามารถประหยัดรหัสได้ (โดยเสียค่าใช้จ่าย) โดยการสร้างคลาสพื้นฐานเพื่อแชร์รหัสของคุณ:

public interface Vector(){

    abstract class Abstract {           
        protected abstract double[] asArray();

        int dimensions(){ return asArray().length; }

        double magnitude(){ 
            double sum = 0;
            for (double component : asArray()) {
                sum += component * component;
            }
            return Math.sqrt(sum);
        }     

        //any additional behavior here   
    }
}

public class Scalar extends Vector.Abstract {
    private double x;

    public double getX(){
        return x;
    }

    @Override
    public double[] asArray(){
        return new double[]{x};
    }
}

public class Cartesian extends Vector.Abstract {

    public double x, y;

    public double getX(){ return x; }
    public double getY(){ return y; }

    @Override public double[] asArray(){ return new double[]{x, y}; }
}

แน่นอนถ้าคุณใช้ Java-8 + คุณสามารถใช้อินเทอร์เฟซที่เป็นค่าเริ่มต้นเพื่อให้กระชับยิ่งขึ้น

public interface Vector{

    default public double magnitude(){
        double sum = 0;
        for (double component : asArray()) {
            sum += component * component;
        }
        return Math.sqrt(sum);
    }

    default public int dimensions(){
        return asArray().length;
    }

    default double getComponent(int index){
        return asArray()[index];
    }

    double[] asArray();

    // giving up a little bit of static-safety in exchange for 
    // runtime exceptions, we can implement the getX(), getY() 
    // etc methods here, 
    // and simply have them throw if the dimensionality is too low 
    // (you can of course do this on the abstract-class strategy as well)

    //document or use checked-exceptions to indicate that these methods throw IndexOutOfBounds exceptions (or a wrapped version)

    default public getX(){
        return getComponent(0);
    }
    default public getY(){
        return getComponent(1);
    }
    //...


    }

    //as a general rule, defaulted interfaces should assume statelessness, 
    // so you want to avoid putting mutating operations 
    // as defaulted methods on an interface, since they'll only make your life harder
}

ในที่สุดนอกเหนือจากที่คุณหมดตัวเลือกกับ JVM แน่นอนคุณสามารถเขียนพวกเขาใน C ++ และใช้บางอย่างเช่น JNA เพื่อเชื่อมพวกเขาใน - นี่คือทางออกของเราสำหรับการดำเนินการอย่างรวดเร็วของเมทริกซ์ที่เราใช้ Fortran และ MKL ของ Intel - แต่สิ่งนี้จะช้าลงหาก คุณเขียนเมทริกซ์ของคุณใน C ++ และเรียกใช้ getters / setters จาก java


ความกังวลหลักของฉันไม่ใช่ประสิทธิภาพ แต่เป็นการตรวจสอบเวลาแบบคอมไพล์ ฉันต้องการวิธีแก้ปัญหาที่ขนาดของเวกเตอร์และการดำเนินการที่สามารถดำเนินการได้ถูกกำหนดในเวลาคอมไพล์ (เช่นเดียวกับเทมเพลต C ++) บางทีวิธีแก้ปัญหาของคุณดีที่สุดถ้าคุณกำลังจัดการกับเมทริกซ์ที่อาจมีขนาดใหญ่ถึง 1,000 ชิ้น แต่ในกรณีนี้ฉันแค่จัดการเวกเตอร์ด้วยขนาด 1 - 10
Parker Hoyes

หากคุณใช้บางอย่างเช่นโซลูชันที่หนึ่งหรือสองคุณสามารถสร้างคลาสย่อยเหล่านั้นได้ ตอนนี้ฉันกำลังอ่าน Xtend อยู่และดูเหมือนว่าจะค่อนข้างยุติธรรมเช่น Kotlin ด้วย Kotlin คุณสามารถอาจใช้data classวัตถุที่สามารถสร้าง 10 subclasses เวกเตอร์ ด้วย java สมมติว่าคุณสามารถดึงฟังก์ชันการทำงานทั้งหมดของคุณลงในคลาสพื้นฐานแต่ละคลาสย่อยจะใช้เวลา 1-10 บรรทัด ทำไมไม่สร้างคลาสฐาน?
Groostav

ตัวอย่างที่ฉันให้นั้นมีขนาดใหญ่เกินไปรหัสที่แท้จริงของฉันมีวิธีการมากมายที่กำหนดไว้สำหรับ Vector เช่นผลิตภัณฑ์เวกเตอร์ดอทการเพิ่มส่วนประกอบและการคูณที่ชาญฉลาดและอื่น ๆ แม้ว่าฉันจะสามารถใช้สิ่งเหล่านี้ได้โดยใช้คลาสพื้นฐานและasArrayวิธีการของคุณ แต่วิธีการต่าง ๆ เหล่านั้นจะไม่ถูกตรวจสอบในเวลารวบรวม (คุณสามารถแสดงผลแบบดอทระหว่างสเกลาร์กับเวกเตอร์แบบคาร์ทีเซียนได้ .
Parker Hoyes

0

พิจารณา enum กับ Vector แต่ละชื่อที่มีตัวสร้างที่ประกอบด้วยอาร์เรย์ (เริ่มต้นในรายการพารามิเตอร์ด้วยชื่อมิติหรือคล้ายกันหรืออาจเป็นเพียงจำนวนเต็มสำหรับขนาดหรืออาร์เรย์ส่วนประกอบว่างเปล่า - การออกแบบของคุณ) และแลมบ์ดาสำหรับ วิธีการ getMagnitude คุณสามารถให้ enum ใช้อินเทอร์เฟซสำหรับ setComponents / getComponent (s) และเพียงแค่สร้างส่วนประกอบที่ใช้ในการกำจัด getX และอื่น ๆ คุณจะต้องเริ่มต้นแต่ละวัตถุด้วยค่าจริงขององค์ประกอบก่อนการใช้งานอาจตรวจสอบว่าขนาดอาร์เรย์อินพุตตรงกับชื่อมิติหรือขนาด

จากนั้นหากคุณขยายโซลูชันไปยังอีกมิติหนึ่งคุณเพียงแค่ปรับเปลี่ยน enum และแลมบ์ดา


1
โปรดระบุข้อมูลโค้ดสั้น ๆ เพื่อประกอบการแก้ปัญหาของคุณ
Tulains Córdova

0

ตามตัวเลือกที่ 2 ของคุณทำไมไม่ทำเช่นนี้? หากคุณต้องการป้องกันการใช้ฐานดิบคุณสามารถทำให้เป็นนามธรรม:

class Vector2 extends Vector
{
  public Vector2(double x, double y) {
    super(new double[]{x,y});
  }

  public double getX() {
    return getComponent(0);
  }

  public double getY() {
    return getComponent(1);
  }
}

นี่คล้ายกับ "วิธีที่ 2" ในคำถามของฉัน วิธีการแก้ปัญหาของคุณอย่างไรให้วิธีการรับประกันความปลอดภัยชนิดที่รวบรวมเวลา แต่ค่าใช้จ่ายในการสร้างที่double[]ไม่พึงประสงค์เมื่อเทียบกับการใช้งานที่เพียงใช้ 2 ดั้งเดิมdoubles ในตัวอย่างน้อยที่สุดอย่างนี้ดูเหมือนว่า microoptimization แต่ให้พิจารณากรณีที่ซับซ้อนมากขึ้นที่มีข้อมูลเมตามากขึ้นและประเภทของคำถามนั้นมีอายุสั้น
Parker Hoyes

1
ถูกต้องตามที่กล่าวมานี้เป็นไปตามวิธีที่ 2 จากการสนทนากับ Groostav เกี่ยวกับคำตอบของเขาฉันได้รับความประทับใจว่าข้อกังวลของคุณไม่ได้มีประสิทธิภาพ คุณมีปริมาณค่าใช้จ่ายนี้คือการสร้างวัตถุ 2 แทน 1? สำหรับช่วงชีวิตสั้น JVM สมัยใหม่ได้รับการปรับให้เหมาะสมสำหรับกรณีนี้และควรมีต้นทุน GC ต่ำกว่า (โดยทั่วไป 0) มากกว่าวัตถุที่มีอายุยืนกว่า ฉันไม่แน่ใจว่าข้อมูลเมตามีบทบาทอย่างไร สเกลาร์เมทาดาทาหรือมิติข้อมูลนี้หรือไม่
JimmyJames

โครงการจริงที่ฉันกำลังทำอยู่เป็นกรอบรูปเรขาคณิตที่จะใช้ในตัวแสดงผลหลายมิติ นี่หมายความว่าฉันกำลังสร้างวัตถุที่ซับซ้อนกว่าเวกเตอร์เช่น ellipsoids, orthotopes และ cetera และการเปลี่ยนแปลงมักเกี่ยวข้องกับเมทริกซ์ ความซับซ้อนของการทำงานกับเรขาคณิตมิติที่สูงขึ้นทำให้ความปลอดภัยในการพิมพ์สำหรับขนาดเมทริกซ์และเวกเตอร์เป็นที่ต้องการในขณะที่ยังคงมีความต้องการที่สำคัญในการหลีกเลี่ยงการสร้างวัตถุให้มากที่สุด
Parker Hoyes

สิ่งที่ฉันคิดว่าฉันกำลังมองหาคือโซลูชันอัตโนมัติที่สร้างขึ้นโดยโค้ดเทคคล้ายกับวิธีที่ 1 ซึ่งไม่สามารถทำได้ใน Java หรือ Xtend มาตรฐาน เมื่อฉันลงเอยด้วยการใช้วิธีที่ 2 ซึ่งพารามิเตอร์ขนาดของวัตถุเหล่านี้จำเป็นต้องเป็นแบบไดนามิกที่รันไทม์และสร้างการใช้งานที่มีประสิทธิภาพมากขึ้นโดยเฉพาะสำหรับกรณีที่พารามิเตอร์เหล่านี้เป็นแบบคงที่ การใช้งานจะแทนที่ supertype "ไดนามิก" Vectorด้วยการใช้งานที่เชี่ยวชาญมากขึ้น (เช่นVector3) หากอายุการใช้งานของมันค่อนข้างยาว
Parker Hoyes

0

แนวคิดหนึ่ง:

  1. เวกเตอร์คลาสพื้นฐานที่เป็นนามธรรมจัดเตรียมการประยุกต์ใช้มิติตัวแปรตามเมธอด getComponent (i)
  2. คลาสย่อยแต่ละแบบแยกกัน Vector1, Vector2, Vector3 ครอบคลุมกรณีทั่วไปโดยแทนที่เมธอด Vector
  3. คลาสย่อย DynVector สำหรับเคสทั่วไป
  4. เมธอดจากโรงงานที่มีรายการอาร์กิวเมนต์ความยาวคงที่สำหรับกรณีทั่วไปซึ่งประกาศให้ส่งคืน Vector1, Vector2 หรือ Vector3
  5. วิธีการของโรงงาน var-args ประกาศให้ส่งคืน Vector การสร้างอินสแตนซ์ Vector1, Vector2, Vector3 หรือ DynVector ขึ้นอยู่กับความยาวของรายการ

สิ่งนี้ช่วยให้คุณได้รับประสิทธิภาพที่ดีในกรณีทั่วไปและความปลอดภัยเวลาคอมไพล์ (ยังสามารถปรับปรุงได้) โดยไม่ต้องเสียสละกรณีทั่วไป

โครงกระดูกรหัส:

public abstract class Vector {
    protected abstract int dimension();
    protected abstract double getComponent(int i);
    protected abstract void setComponent(int i, double value);

    public double magnitude() {
        double sum = 0.0;
        for (int i=0; i<dimension(); i++) {
            sum += getComponent(i) * getComponent(i);
        }
        return Math.sqrt(sum);
    }

    public void add(Vector other) {
        for (int i=0; i<dimension(); i++) {
            setComponent(i, getComponent(i) + other.getComponent(i));
        }
    }

    public static Vector1 create(double x) {
        return new Vector1(x);
    }

    public static Vector create(double... values) {
        switch(values.length) {
        case 1:
            return new Vector1(values[0]);
        default:
            return new DynVector(values);
        }

    }
}

class Vector1 extends Vector {
    private double x;

    public Vector1(double x) {
        super();
        this.x = x;
    }

    @Override
    public double magnitude() {
        return Math.abs(x);
    }

    @Override
    protected int dimension() {
        return 1;
    }

    @Override
    protected double getComponent(int i) {
        return x;
    }

    @Override
    protected void setComponent(int i, double value) {
        x = value;
    }

    @Override
    public void add(Vector other) {
        x += ((Vector1) other).x;
    }

    public void add(Vector1 other) {
        x += other.x;
    }
}

class DynVector extends Vector {
    private double[] values;
    public DynVector(double[] values) {
        this.values = values;
    }

    @Override
    protected int dimension() {
        return values.length;
    }

    @Override
    protected double getComponent(int i) {
        return values[i];
    }

    @Override
    protected void setComponent(int i, double value) {
        values[i] = value;
    }

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.