컴파일 타임 값 매개 변수를 사용하여 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;
        }
    }

    이 솔루션은 시간이 많이 걸리고 코드 작성에 지루합니다. 이 예제에서는 그리 나쁘지는 않지만 실제 코드에서는 최대 4 개의 차원 (x, y, z 및 w)으로 각각 여러 구현이있는 벡터를 처리하고 있습니다. 각 벡터에는 실제로 500 개만 필요하지만 현재 2,000 줄 이상의 코드가 있습니다.

  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과 관련하여이 작업을 수행하고 있습니까?
Dan1701

2
DSL은 코드 생성 애플리케이션에 적합합니다. 간단히 말해서 약간의 언어 문법, 해당 언어의 인스턴스 (이 경우 다양한 벡터를 설명) 및 인스턴스가 저장 될 때 실행되는 일부 코드 (Java 코드 생성)를 작성합니다. Xtext 사이트 에는 많은 리소스와 예제가 있습니다 .
Dan1701 2019

2
종속 유형을 사용 하여이 문제에 대한 완벽한 솔루션이 있습니다 (이것은 다소 작습니다).하지만 Java에서는 사용할 수 없습니다. 작은 고정 클래스 수 (1 차원, 2 차원 및 3 차원 벡터 만 사용)와 그 이상을위한 후자 솔루션이있는 경우 첫 번째 솔루션을 사용합니다. 분명히 난 당신의 코드를 실행하지 않고 확실히 말할 수 없다,하지만 난 당신이 걱정하고 성능에 미치는 영향이있을 것이라고 생각하지 않는다
gardenhead

1
이 두 클래스에는 동일한 인터페이스가 없으며 다형성이 아니지만 다형성으로 사용하려고합니다.
Martin Spamer

1
선형 대수 수학을 작성하고 성능에 관심이 있다면 왜 java입니까? 나는 그 안에 문제 외에는 아무것도 볼 수 없습니다.
소펠

답변:


1

이런 경우 코드 생성을 사용합니다.

실제 코드를 생성하는 Java 응용 프로그램을 작성합니다. 그렇게하면 for 루프를 사용하여 다양한 버전을 쉽게 생성 할 수 있습니다. JavaPoet을 사용 하여 실제 코드를 작성하는 것이 매우 간단합니다. 그런 다음 코드 생성 실행을 빌드 시스템에 통합 할 수 있습니다.


0

내 응용 프로그램과 매우 유사한 모델이 있으며 솔루션은 솔루션 2와 비슷한 동적 크기의 맵을 유지하는 것이 었습니다.

당신은 단순히 자바 배열 프리미티브와 같은 성능에 대해 걱정할 필요가 없습니다. 10,000 열 x 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 ++로 행렬을 작성하고 java에서 getter / setter를 호출하면됩니다.


내 주요 관심사는 성능이 아니라 컴파일 타임 검사입니다. 벡터의 크기와 그에 대해 수행 할 수있는 작업이 컴파일 타임에 결정되는 솔루션을 정말로 원합니다 (C ++ 템플릿 사용). 당신이 크기 1000 개 구성 요소까지 될 수 행렬을 처리하는 경우 아마도 솔루션은 가장 좋지만이 경우에 나는 단지 하나의 크기와 벡터를 처리하고있어 - 10
파커 Hoyes

첫 번째 또는 두 번째 솔루션과 같은 것을 사용하면 해당 하위 클래스를 만들 수 있습니다. 이제 Xtend를 읽고 있는데 Kotlin과 같은 것 같습니다. 코 틀린 다음과 같은 작업을 할 수 있습니다 아마도 사용을 data class쉽게 열 개 벡터의 서브 클래스를 생성 할 객체. java를 사용하면 모든 기능을 기본 클래스로 가져올 수 있다고 가정하면 각 하위 클래스는 1-10 줄을 사용합니다. 왜 기본 클래스를 만들지 않습니까?
Groostav

내가 제공 한 예제는 지나치게 단순화되었으며 실제 코드에는 벡터 도트 곱, 구성 요소 추가 및 곱셈 등과 같은 Vector에 대해 정의 된 많은 메소드가 있습니다. 기본 클래스와 asArray메서드를 사용하여 구현할 수는 있지만 컴파일시 이러한 다양한 메서드를 확인하지 못합니다 (스칼라와 데카르트 벡터 사이에 내적을 수행 할 수는 있지만 런타임에는 실패합니다) .
Parker Hoyes

0

배열 (치수 이름 또는 이와 유사한 매개 변수 목록에서 초기화되거나 크기 또는 빈 구성 요소 배열의 정수-디자인에 대한 정수)로 구성된 생성자를 갖는 각 명명 된 Vector가있는 열거 형과 getMagnitude 메소드 열거 형이 setComponents / getComponent (s)에 대한 인터페이스를 구현하고 getX 등을 제거하여 사용중인 구성 요소를 설정하면됩니다. 사용하기 전에 각 구성 요소를 실제 구성 요소 값으로 초기화하여 입력 배열 크기가 차원 이름 또는 크기와 일치하는지 확인해야합니다.

그런 다음 솔루션을 다른 차원으로 확장하면 열거 형과 람다 만 수정하면됩니다.


1
솔루션에 대한 간단한 코드 스 니펫 그림을 제공하십시오.
Tulains Córdova

0

당신의 선택 2에 기초하여, 왜 그렇게합니까? raw base를 사용하지 않으려면 abstract base로 만들 수 있습니다.

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. 최소한의 예에서는 미세 최적화처럼 보이지만 훨씬 더 많은 메타 데이터가 관련되고 해당 유형이 수명이 짧은 훨씬 복잡한 경우를 고려하십시오.
Parker Hoyes

1
맞습니다, 이것은 방법 2를 기반으로합니다. 그의 답변과 관련하여 Groostav와의 토론을 바탕으로, 귀하의 우려가 성과가 아니라는 인상을 받았습니다. 이 오버 헤드를 수량화 했습니까? 즉, 1 대신 2 개의 객체를 생성 했습니까? 수명이 짧은 것과 관련하여 최신 JVM은이 경우에 최적화되어 있으며 수명이 긴 객체보다 GC 비용이 낮습니다 (기본적으로 0). 메타 데이터가 어떻게 작동하는지 잘 모르겠습니다. 이 메타 데이터는 스칼라 또는 차원입니까?
JimmyJames

내가 작업했던 실제 프로젝트는 초 차원 렌더러에서 사용되는 지오메트리 프레임 워크였습니다. 이것은 내가 타원체, 오르토 토프 등의 벡터보다 훨씬 더 복잡한 객체를 만들고 있었고 일반적으로 행렬과 관련된 변환을 의미합니다. 더 높은 차원의 기하학으로 작업하는 복잡성으로 인해 행렬 및 벡터 크기에 대한 유형 안전성이 바람직해졌지만 가능한 한 객체 생성을 피하는 것이 여전히 중요했습니다.
Parker Hoyes

필자가 실제로 찾고있는 것은 표준 Java 또는 Xtend에서는 불가능한 방법 1과 유사한 바이트 코드를 생성하는보다 자동화 된 솔루션이었습니다. 결국이 객체의 크기 매개 변수가 런타임에 동적이어야하는 방법 2를 사용 하고이 매개 변수가 정적 인 경우에 대해보다 효율적이고 특수한 구현을 생성하는 방법 2를 사용했습니다. 구현은 수명이 상대적으로 길면 "동적"수퍼 타입 Vector을보다 특화된 구현 (예 :) 으로 대체합니다 Vector3.
Parker Hoyes

0

한 가지 아이디어 :

  1. getComponent (i) 메소드에 근거한 가변 치수 구현을 제공하는 추상 기본 클래스 Vector입니다.
  2. 개별 서브 클래스 Vector1, Vector2, Vector3은 일반적인 경우를 다루며 Vector 메소드를 대체합니다.
  3. 일반적인 경우의 DynVector 서브 클래스입니다.
  4. 일반적인 경우에 대해 고정 길이 인수 목록을 가진 팩토리 메소드는 Vector1, Vector2 또는 Vector3을 리턴하도록 선언되었습니다.
  5. arglist 길이에 따라 Vector1, Vector2, Vector3 또는 DynVector를 인스턴스화하여 Vector를 반환하도록 선언 된 var-args 팩토리 메서드입니다.

이를 통해 일반적인 경우에는 우수한 성능을 제공하고 일반적인 경우를 희생하지 않고도 일부 컴파일 타임 안전성 (아직 개선 될 수 있음)을 제공합니다.

코드 스켈레톤 :

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.