संकलन-समय मान पैरामीटर के साथ जावा कक्षाएं उत्पन्न करना


10

ऐसी स्थिति पर विचार करें जहां एक वर्ग एक ही मूल व्यवहार, विधियों, एट वगैरह को लागू करता है, लेकिन उस वर्ग के कई अलग-अलग संस्करण विभिन्न उपयोगों के लिए मौजूद हो सकते हैं। मेरे विशेष मामले में, मेरे पास एक वेक्टर (एक ज्यामितीय वेक्टर, एक सूची नहीं) है और वह वेक्टर किसी भी एन-आयामी यूक्लिडियन स्पेस (1 आयामी, 2 आयामी, ...) पर लागू हो सकता है। इस वर्ग / प्रकार को कैसे परिभाषित किया जा सकता है?

यह C ++ में आसान होगा जहां क्लास टेम्प्लेट में पैरामीटर के रूप में वास्तविक मान हो सकते हैं, लेकिन हमारे पास जावा में वह लक्जरी नहीं है।

इस समस्या को हल करने के लिए जिन दो तरीकों के बारे में मैं सोच सकता हूँ, वे हैं:

  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];
        }
    }
    

    दुर्भाग्य से यह समाधान कोड प्रदर्शन को नुकसान पहुंचाता है, पूर्व समाधान की तुलना में गड़बड़ कोड में परिणाम होता है, और संकलन-समय पर उतना सुरक्षित नहीं है (यह संकलन-समय पर गारंटी नहीं दी जा सकती है कि आप जिस वेक्टर के साथ काम कर रहे हैं वह 2-आयामी है, उदाहरण के लिए)।

मैं वर्तमान में Xtend में वास्तव में विकसित कर रहा हूं, इसलिए यदि कोई भी Xtend समाधान उपलब्ध है, तो वे भी स्वीकार्य होंगे।


जब से आप Xtend का उपयोग कर रहे हैं, क्या आप Xtext DSL के संदर्भ में ऐसा कर रहे हैं?
दान 1701 23

2
कोड-जीन अनुप्रयोगों के लिए डीएसएल महान हैं। संक्षेप में, आप थोड़ी भाषा व्याकरण, उस भाषा का एक उदाहरण (विभिन्न वैक्टर का वर्णन करते हुए, इस मामले में) बनाते हैं, और कुछ कोड जो निष्पादित होने पर सहेजे जाते हैं (उदाहरण के लिए आपका जावा कोड)। Xtext साइट पर बहुत सारे संसाधन और उदाहरण हैं ।
दान 1701

2
आश्रित प्रकारों का उपयोग करके इस समस्या का एक सही समाधान है (यह कमोबेश वे इसके लिए बनाए गए थे), लेकिन अफसोस कि जावा में उपलब्ध नहीं है। मैं पहले समाधान के साथ जाऊंगा यदि आपके पास केवल छोटी, निश्चित संख्या में कक्षाएं हैं (जैसे कि आप केवल 1-, 2-, और 3-आयामी वैक्टर का उपयोग करते हैं), और उससे अधिक के लिए बाद वाला समाधान। जाहिर है मैं आपके कोड को चलाने के बिना कुछ के लिए नहीं कह सकता, लेकिन मुझे नहीं लगता कि प्रदर्शन प्रभाव होगा
जिसके

1
उन दो वर्गों में समान इंटरफ़ेस नहीं है, वे बहुरूपता नहीं हैं, लेकिन आप उन्हें बहुरूपिए उपयोग करने की कोशिश कर रहे हैं।
मार्टिन स्पेमर

1
यदि आप रैखिक बीजगणित गणित लिख रहे हैं और प्रदर्शन के बारे में चिंतित हैं तो जावा क्यों। मैं इसमें कुछ भी नहीं देख सकता लेकिन समस्याएँ।
सोपेल

जवाबों:


1

इस तरह के मामलों में, मैं कोड पीढ़ी का उपयोग करता हूं।

मैं एक जावा एप्लिकेशन लिखता हूं जो वास्तविक कोड उत्पन्न करता है। इस तरह आप विभिन्न संस्करणों का एक गुच्छा उत्पन्न करने के लिए लूप के लिए आसानी से उपयोग कर सकते हैं। मैं JavaPoet का उपयोग करता हूं , जो वास्तविक कोड बनाने के लिए बहुत सरल बनाता है। फिर आप कोड निर्माण को अपनी बिल्ड सिस्टम में चला सकते हैं।


0

मेरे पास मेरे आवेदन पर एक समान मॉडल है और हमारा समाधान केवल आपके समाधान 2 के समान एक गतिशील आकार का एक नक्शा रखना था।

आप बस इस तरह एक जावा सरणी के साथ प्रदर्शन के बारे में चिंता करने की जरूरत नहीं जा रहे हैं। हम 10,000 पंक्तियों द्वारा 100 कॉलम (पढ़ें: 100 आयामी वैक्टर) के ऊपरी-बाउंड आकार के साथ मैट्रिक्स उत्पन्न करते हैं, और हमारे पास आपके समाधान की तुलना में बहुत अधिक जटिल वेक्टर प्रकारों के साथ अच्छा प्रदर्शन है। आप वर्ग या अंतिम तरीकों को चिह्नित करने का प्रयास कर सकते हैं। इसे गति देने के लिए, लेकिन मुझे लगता है कि आप समय से पहले अनुकूलन कर रहे हैं।

अपना कोड साझा करने के लिए एक आधार वर्ग बनाकर आप कुछ कोड-बचत (प्रदर्शन की लागत पर) प्राप्त कर सकते हैं:

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 की तरह कुछ का उपयोग करके उन्हें पा सकते हैं - यह हमारे लिए कुछ तेज़ मैट्रिक्स ऑपरेशन का समाधान है, जहाँ हम फोरट्रान और इंटेल के MKL-- का उपयोग करते हैं, लेकिन यह केवल चीजों को धीमा करने वाला है आप बस C ++ में अपना मैट्रिक्स लिखते हैं और जावा से इसके गेटर्स / सेटर को कॉल करते हैं।


मेरी मुख्य चिंता प्रदर्शन नहीं है, यह संकलन-समय की जाँच है। मैं वास्तव में एक समाधान चाहूंगा जहां वेक्टर का आकार और उस पर किए जाने वाले संचालन को संकलन-समय पर निर्धारित किया जा सकता है (जैसे सी ++ टेम्पलेट के साथ)। शायद आपका समाधान सबसे अच्छा है यदि आप मेट्रिक्स के साथ काम कर रहे हैं जो आकार में 1000 घटकों तक हो सकता है, लेकिन इस मामले में मैं केवल 1 - 10. के आकार वाले वैक्टर के साथ काम कर रहा हूं
पार्कर होयस

यदि आप पहले या दूसरे समाधान की तरह कुछ का उपयोग करते हैं, तो आप उन उपवर्गों का निर्माण कर सकते हैं। अब मैं भी केवल Xtend पर पढ़ रहा हूं, और यह कोटलिन की तरह एक उचित सा लगता है। कोटलिन के साथ, आप संभवतःdata class 10 वेक्टर उपवर्ग बनाने के लिए वस्तुओं का उपयोग कर सकते हैं । जावा के साथ, यह मानते हुए कि आप अपनी सभी कार्यक्षमता को आधार वर्ग में खींच सकते हैं, प्रत्येक उपवर्ग में 1-10 रेखाएँ होंगी। बेस क्लास क्यों नहीं बनाया?
ग्रोस्तव

मैंने जो उदाहरण दिया है वह ओवरसाइम्प्लीफाइड है, मेरे वास्तविक कोड में वेक्टर के लिए परिभाषित कई विधियाँ हैं जैसे कि वेक्टर डॉट उत्पाद, घटक-वार जोड़ और गुणन, वगैरह। हालांकि मैं एक बेस क्लास और आपकी asArrayविधि का उपयोग करके इन्हें लागू कर सकता था , उन विभिन्न तरीकों को संकलन समय पर चेक नहीं किया जाएगा (आप एक स्केलर और कार्टेशियन वेक्टर के बीच एक डॉट-उत्पाद प्रदर्शन कर सकते हैं और यह ठीक संकलन करेगा, लेकिन रनटाइम में विफल हो सकता है) ।
पार्कर होयस

0

एक नामित वेक्टर के साथ एक एनम पर विचार करें जिसमें एक रचनाकार होता है जिसमें एक सरणी होती है (आयाम नामों या इसी तरह के साथ पैरामीटर सूची में आरंभीकृत, या शायद आकार या एक खाली घटक सरणी के लिए एक पूर्णांक - आपका डिज़ाइन), और एक लैम्ब्डा के लिए getMagnitude मेथड। आपके पास Enum भी setCompords / getComponent (s) के लिए एक इंटरफ़ेस लागू कर सकता है, और बस स्थापित करें कि कौन सा घटक इसके उपयोग में था, गेटएक्स को खत्म कर, एट अल। आपको प्रत्येक ऑब्जेक्ट को उपयोग करने से पहले उसके वास्तविक घटक मूल्यों के साथ आरंभीकृत करने की आवश्यकता होगी, संभवतः यह जांच कर कि इनपुट सरणी आकार आयाम नामों या आकार से मेल खाता है।

फिर यदि आप समाधान को किसी अन्य आयाम तक बढ़ाते हैं, तो आप बस एनम और लैम्ब्डा को संशोधित करते हैं।


1
कृपया, अपने समाधान का एक संक्षिप्त कोड स्निपेट चित्रण प्रदान करें।
ट्यूलेंस कोरडोवा

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 आदिम doubleएस का उपयोग करता है । एक उदाहरण के रूप में कम से कम के रूप में यह एक microoptimization की तरह लगता है, लेकिन एक बहुत अधिक जटिल मामले पर विचार करें जहां बहुत अधिक मेटाडेटा शामिल है और प्रश्न में प्रकार एक छोटा जीवनकाल है।
पार्कर होयस

1
ठीक है, जैसा कि यह कहता है, यह विधि 2 पर आधारित है। ग्रोस्तव के साथ उनके जवाब के संबंध में आपकी चर्चा के आधार पर, मुझे आभास हुआ कि आपकी चिंता प्रदर्शन से नहीं थी। क्या आपने इस ओवरहेड को परिमाणित किया है अर्थात 1 के बजाय 2 ऑब्जेक्ट बनाए जा रहे हैं? छोटे जीवन काल के लिए, आधुनिक जेवीएम इस मामले के लिए अनुकूलित हैं और इसमें जीसी लागत (मूल रूप से 0) अधिक जीवित वस्तुओं की तुलना में कम होनी चाहिए। मुझे यकीन नहीं है कि मेटाडेटा इसमें कैसे खेलता है। क्या यह मेटाडेटा स्केलर या आयामी है?
जिमीजम्स

जिस वास्तविक परियोजना पर मैं काम कर रहा था, वह एक हाइपरडिमेंशियल रेंडरर में उपयोग की जाने वाली एक ज्यामिति रूपरेखा थी। इसका मतलब है कि मैं वैक्टर की तुलना में बहुत अधिक जटिल वस्तुओं का निर्माण कर रहा था, जैसे कि दीर्घवृत्त, ऑर्थोटोप्स एट सिटेरा और परिवर्तनों में आमतौर पर मैट्रिस शामिल थे। उच्च आयामी ज्यामिति के साथ काम करने की जटिलता ने मैट्रिक्स और वेक्टर आकार के लिए टाइप-सेफ्टी को वांछनीय बना दिया, जबकि वस्तुओं के निर्माण से यथासंभव बचने की अभी भी महत्वपूर्ण इच्छा थी।
पार्कर होयस

मुझे लगता है कि मैं वास्तव में तलाश कर रहा था एक अधिक स्वचालित समाधान था जो कि विधि 1 के समान बायटेकोड का उत्पादन करता था, जो वास्तव में मानक जावा या Xtend में संभव नहीं है। जब मैंने समाप्त कर दिया तो विधि 2 का उपयोग कर रहा था, जहां रनटाइम पर इन ऑब्जेक्ट्स के आकार के मापदंडों को गतिशील होने की आवश्यकता थी, और उन मामलों के लिए अधिक कुशल, विशेष कार्यान्वयन तैयार करना जहां ये पैरामीटर स्थिर थे। कार्यान्वयन "डायनेमिक" सुपरपाइप Vectorको अधिक विशिष्ट कार्यान्वयन (जैसे Vector3) से बदल देगा, यदि इसका जीवनकाल अपेक्षाकृत लंबा होना था।
पार्कर होयस

0

एक विचार:

  1. एक सार आधार वर्ग वेक्टर एक getComponent (i) विधि के आधार पर चर-आयाम कार्यान्वयन प्रदान करता है।
  2. Indiviual उपवर्ग वेक्टर 1, वेक्टर 2, वेक्टर 3, विशिष्ट मामलों को कवर करते हुए, वेक्टर विधियों को ओवरराइड करते हैं।
  3. सामान्य मामले के लिए एक DynVector उपवर्ग।
  4. वेक्टर मामलों, वेक्टर 2 या वेक्टर 3 को वापस करने के लिए घोषित विशिष्ट मामलों के लिए निश्चित लंबाई तर्क सूचियों के साथ कारखाने के तरीके।
  5. एक var-args फ़ैक्टरी विधि, arglist की लंबाई के आधार पर, वेक्टर को लौटाने के लिए, वेक्टर 1, वेक्टर 2, वेक्टर 3, या डायनेक्टर को इंस्टेंट करने की घोषणा की।

यह आपको सामान्य मामलों का त्याग किए बिना विशिष्ट मामलों में अच्छा प्रदर्शन और कुछ संकलन-समय सुरक्षा (अभी भी सुधार किया जा सकता है) देता है।

कोड कंकाल:

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;
    }

}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.