Derleme zamanı değeri parametreleriyle Java sınıfları oluşturma


10

Bir sınıfın aynı temel davranışı, yöntemleri, vb. Uyguladığı bir durumu düşünün, ancak bu sınıfın farklı kullanımlar için birden çok farklı sürümü mevcut olabilir. Benim özel durumumda, bir vektörüm var (liste değil geometrik bir vektör) ve bu vektör herhangi bir N-boyutlu Öklid uzayına (1 boyutlu, 2 boyutlu, ...) uygulanabilir. Bu sınıf / tür nasıl tanımlanabilir?

Sınıf şablonlarının parametreler olarak gerçek değerlere sahip olabileceği C ++ 'da bu kolay olurdu, ancak Java'da bu lüksümüz yok.

Bu sorunu çözmek için kullanılabileceğini düşünebileceğim iki yaklaşım şunlardır:

  1. Derleme zamanında olası her bir vakanın uygulanması.

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

    Bu çözüm açıkça çok zaman alıcıdır ve kodlanması son derece sıkıcıdır. Bu örnekte çok kötü görünmüyor, ancak gerçek kodumda, her biri dört boyuta kadar (x, y, z ve w) birden çok uygulamaya sahip vektörlerle uğraşıyorum. Her bir vektörün sadece 500'e ihtiyacı olmasına rağmen şu anda 2.000'den fazla kod satırım var.

  2. Çalışma zamanında parametrelerin belirtilmesi.

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

    Ne yazık ki bu çözüm, kod performansını incitir, önceki çözümden daha karışık kodla sonuçlanır ve derleme zamanında güvenli değildir (derleme zamanında ele aldığınız vektörün aslında 2 boyutlu olduğu garanti edilemez, Örneğin).

Şu anda Xtend'de gelişiyorum, bu nedenle herhangi bir Xtend çözümü varsa, bunlar da kabul edilebilir.


Xtend kullandığınız için bunu bir Xtext DSL bağlamında mı yapıyorsunuz?
Dan1701

2
DSL'ler kod-gen uygulamaları için mükemmeldir. Özetle, küçük bir dil grameri, o dilin bir örneği (bu durumda çeşitli vektörleri açıklar) ve örnek kaydedildiğinde (Java kodunuzu üreterek) çalışan bir kod oluşturursunuz. Xtext sitesinde çok sayıda kaynak ve örnek var .
Dan1701

2
Bağımlı türleri kullanarak bu soruna mükemmel bir çözüm var (ne için oluşturuldukları az çok), ancak Java'da mevcut olmayan ne yazık ki. Sadece küçük, sabit sayıda sınıfınız varsa (ilk olarak yalnızca 1-, 2- ve 3 boyutlu vektörler kullanıyorsunuz) ve ikinci çözümden daha fazlası için ilk çözüm ile giderdim. Açıkçası kodunuzu çalıştırmadan kesin olarak söyleyemem, ama endişelendiğiniz performans etkisi olacağını düşünmüyorum
gardenhead

1
Bu iki sınıf aynı arayüze sahip değildir, polimorfik değildir, ancak bunları polimorfik olarak kullanmaya çalışıyorsunuz.
Martin Spamer

1
Doğrusal cebir matematiği yazıyorsanız ve performans konusunda endişeleriniz varsa neden java. Buradaki problemlerden başka bir şey göremiyorum.
Sopel

Yanıtlar:


1

Bu gibi durumlarda, kod üretimi kullanıyorum.

Gerçek kodu üreten bir java uygulaması yazıyorum. Bu şekilde, bir grup farklı sürümü kolayca oluşturmak için bir for döngüsü kullanabilirsiniz. Kullandığım JavaPoet oldukça basit gerçek kodu oluşturmak için yapar. Daha sonra kod oluşturma çalışmasını derleme sisteminize entegre edebilirsiniz.


0

Uygulamamda çok benzer bir modelim var ve çözümümüz, çözümünüze benzeyen dinamik boyutta bir harita tutmaktı.

Sadece böyle bir java dizi ilkel ile performans hakkında endişelenmenize gerek yok. 10.000 satıra kadar 100 sütun üst sınır boyutlarında (okuma: 100 boyutlu vektör) matrisler üretiyoruz ve çözüm 2'den çok daha karmaşık vektör türleriyle iyi performans elde ettik. Sınıfı veya işaretleme yöntemlerini son olarak mühürlemeyi deneyebilirsiniz hızlandırmak için, ancak bence erken optimizasyon yapıyorsunuz.

Kodunuzu paylaşmak için bir temel sınıf oluşturarak bazı kod tasarrufları elde edebilirsiniz (performans pahasına):

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

Tabii ki, Java-8 + kullanıyorsanız, bunu daha da daraltmak için varsayılan arayüzleri kullanabilirsiniz:

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
}

Nihayetinde JVM ile seçeneklerin dışındasınız. Elbette bunları C ++ 'da yazabilir ve JNA gibi bir şeyleri köprülemek için kullanabilirsiniz - bu, fortran ve intel'in MKL'sini kullandığımız bazı hızlı matris işlemleri için çözümümüzdür - ancak bu sadece işleri yavaşlatacaksa matrisinizi C ++ ile yazmanız ve java'dan getters / setters olarak adlandırmanız yeterlidir.


Temel kaygım performans değil, derleme zamanı kontrolü. Gerçekten vektör boyutu ve üzerinde gerçekleştirilebilecek işlemleri derleme zamanında (C ++ şablonları gibi) belirlendiği bir çözüm istiyorum. Belki de çözümünüz, 1000 bileşene kadar boyutta olabilen matrislerle uğraşıyorsanız en iyisidir, ancak bu durumda sadece 1 - 10 büyüklüğündeki vektörlerle ilgileniyorum.
Parker Hoyes

Birinci veya ikinci çözüm gibi bir şey kullanırsanız, bu alt sınıfları oluşturabilirsiniz. Şimdi sadece Xtend'i okuyorum ve Kotlin gibi oldukça adil görünüyor. KOTLIN ile şunları yapabilirsiniz muhtemelen kullanmak data classkolayca 10 vektör alt sınıflarını oluşturmak için nesneleri. Java ile tüm işlevlerinizi temel sınıfa çekebileceğiniz varsayılarak, her alt sınıf 1-10 satır alacaktır. Neden bir temel sınıf oluşturmuyorsunuz?
Groostav

Sağladığım örnek aşırı basitleştirildi, gerçek kodum Vector için tanımlanmış vektör nokta ürünü, bileşen bazında toplama ve çarpma, vb. Gibi birçok yönteme sahip. Bunları bir temel sınıf ve asArrayyönteminizi kullanarak uygulayabilsem de , bu çeşitli yöntemler derleme zamanında denetlenmeyecekti (skaler ve kartezyen vektör arasında bir nokta ürünü gerçekleştirebilirsiniz ve iyi derlenir, ancak çalışma zamanında başarısız olur) .
Parker Hoyes

0

Vector adında her biri, bir dizi (parametre listesinde boyut adları veya benzeri ile başlatılan veya belki de yalnızca boyut veya boş bir bileşenler dizisi - tasarımınız için bir tam sayı) ve bir lambdadan oluşan bir yapıcıya sahip bir enum düşünün getMagnitude yöntemi. Enum ayrıca setComponents / getComponent (ler) için bir arabirim uygulayabilir ve sadece hangi bileşenin kullanımında olduğunu belirleyerek getX, et al. Her nesneyi kullanmadan önce gerçek bileşen değerleriyle başlatmanız, muhtemelen giriş dizisi boyutunun boyut adları veya boyutuyla eşleşip eşleşmediğini kontrol etmeniz gerekir.

Daha sonra çözümü başka bir boyuta genişletirseniz, enum ve lambda'yı değiştirirsiniz.


1
Lütfen çözümünüzün kısa kod parçacığını göstermesini sağlayın.
Tulains Córdova

0

Seçenek 2'nize dayanarak, neden sadece bunu yapmıyorsunuz? Ham bazın kullanılmasını önlemek istiyorsanız, soyut yapabilirsiniz:

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

Bu benim sorum "yöntem 2" benzer. Ancak çözümünüz, derleme zamanında tür güvenliğini garanti etmenin bir yolunu sağlar, ancak a oluşturma yükü, double[]yalnızca 2 ilkel doubles kullanan bir uygulamaya kıyasla istenmeyen bir durumdur . Bu kadar minimal bir örnekte bir mikrooptimizasyon gibi görünüyor, ancak çok daha fazla meta verinin dahil olduğu ve söz konusu türün kısa bir ömre sahip olduğu çok daha karmaşık bir durumu düşünün.
Parker Hoyes

1
Doğru, söylediği gibi, bu yöntem 2'ye dayanıyor. Groostav ile cevabı ile ilgili tartışmanıza dayanarak, endişenizin performansla ilgili olmadığı izlenimini edindim. Bu yükü, yani 1 yerine 2 nesne oluşturmayı ölçtünüz mü? Kısa ömürlere gelince, modern JVM'ler bu durum için optimize edilmiştir ve daha uzun ömürlü nesnelerden daha düşük GC maliyetine (temel olarak 0) sahip olmalıdır. Meta verilerin bunu nasıl oynadığından emin değilim. Bu meta veri skaler mi yoksa boyutsal mı?
JimmyJames

Üzerinde çalıştığım gerçek proje, hiper boyutlu bir oluşturucuda kullanılacak bir geometri çerçevesiydi. Bu, elipsoidler, orthotopes et cetera ve genellikle matrisleri içeren dönüşümler gibi vektörlerden çok daha karmaşık nesneler oluşturduğum anlamına geliyor. Daha yüksek boyutlu geometri ile çalışmanın karmaşıklığı, matris ve vektör boyutu için tip güvenliğini arzu edilir hale getirirken, mümkün olduğunca nesnelerin oluşturulmasını önlemek için hala önemli bir istek vardı.
Parker Hoyes

Gerçekten aradığımı düşündüğüm, standart Java veya Xtend'de gerçekten mümkün olmayan, yöntem 1'e benzer bayt kodu üreten daha otomatik bir çözümdü. Sonunda, bu nesnelerin boyut parametrelerinin çalışma zamanında dinamik olması gereken yöntem 2'yi kullanmak ve bu parametrelerin statik olduğu durumlar için yorucu bir şekilde daha verimli, özel uygulamalar oluşturmaktı. Uygulama , kullanım ömrü nispeten uzun olacaksa "dinamik" üst türün yerini Vectordaha özel bir uygulama ile değiştirecektir (örneğin Vector3).
Parker Hoyes

0

Bir fikir:

  1. GetComponent (i) yöntemine dayalı değişken boyutlu uygulamalar sağlayan soyut temel sınıf Vector.
  2. Bireysel alt sınıflar Vector1, Vector2, Vector3, tipik durumları kapsayan, Vector yöntemlerini geçersiz kılar.
  3. Genel durum için bir DynVector alt sınıfı.
  4. Vector1, Vector2 veya Vector3'ü döndürdüğü bildirilen tipik durumlar için sabit uzunluklu argüman listelerine sahip fabrika yöntemleri.
  5. Arglist uzunluğuna bağlı olarak Vector1, Vector2, Vector3 veya DynVector örneğini başlatan Vector öğesini döndürdüğünü bildiren bir var-args fabrika yöntemi.

Bu, tipik durumlarda iyi performans ve genel durumdan ödün vermeden derleme zamanı güvenliği (yine de geliştirilebilir) sağlar.

Kod iskeleti:

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

}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.