Рассмотрим ситуацию, когда класс реализует одно и то же базовое поведение, методы и так далее, но для разных целей может существовать несколько разных версий этого класса. В моем конкретном случае у меня есть вектор (геометрический вектор, а не список), и этот вектор может применяться к любому N-мерному евклидову пространству (1-мерное, 2-мерное, ...). Как можно определить этот класс / тип?
Это было бы легко в C ++, где шаблоны классов могут иметь фактические значения в качестве параметров, но у нас нет такой роскоши в Java.
Я могу придумать два подхода для решения этой проблемы:
Наличие реализации каждого возможного случая во время компиляции.
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). В настоящее время у меня более 2000 строк кода, хотя каждому вектору нужно только 500.
Задание параметров во время выполнения.
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]; } }
К сожалению, это решение снижает производительность кода, приводит к более сложному коду, чем прежнее решение, и не так безопасно во время компиляции (во время компиляции нельзя гарантировать, что вектор, с которым вы имеете дело, фактически является двумерным, например).
В настоящее время я фактически занимаюсь разработкой в Xtend, поэтому, если будут доступны какие-либо решения Xtend, они также будут приемлемы.
источник
Ответы:
В таких случаях я использую генерацию кода.
Я пишу Java-приложение, которое генерирует реальный код. Таким образом, вы можете легко использовать цикл for для генерации множества разных версий. Я использую JavaPoet , что делает его довольно простым для создания реального кода. Затем вы можете интегрировать запуск генерации кода в вашу систему сборки.
источник
У меня очень похожая модель в моем приложении, и наше решение состояло в том, чтобы просто сохранить карту динамического размера, похожую на ваше решение 2.
Вам просто не нужно беспокоиться о производительности с таким примитивом Java-массива. Мы генерируем матрицы с размерами верхней границы 100 столбцов (читай: 100 пространственных векторов) на 10 000 строк, и у нас была хорошая производительность с гораздо более сложными векторными типами, чем в вашем решении 2. Вы можете попробовать заклеить класс или методы маркировки как финальные чтобы ускорить это, но я думаю, что вы оптимизируете преждевременно.
Вы можете получить некоторую экономию кода (за счет производительности), создав базовый класс для совместного использования вашего кода:
Тогда, конечно, если вы используете Java-8 +, вы можете использовать интерфейсы по умолчанию, чтобы сделать это еще труднее:
В конечном счете, у вас нет выбора с JVM. Конечно, вы можете написать их на C ++ и использовать что-то вроде JNA для их соединения - это наше решение для некоторых быстрых матричных операций, где мы используем MKL от Fortran и Intel, но это только замедлит процесс, если Вы просто пишете свою матрицу на C ++ и вызываете ее получатели / установщики из Java.
источник
data class
объекты, чтобы легко создать 10 векторных подклассов. С Java, если вы можете перенести все свои функции в базовый класс, каждый подкласс займет 1-10 строк. Почему бы не создать базовый класс?asArray
метод, эти различные методы не были бы проверены во время компиляции (вы могли бы выполнить скалярное произведение между скалярным и декартовым вектором, и он скомпилировался бы нормально, но потерпел неудачу во время выполнения) ,Рассмотрим перечисление с каждым именованным вектором, имеющим конструктор, который состоит из массива (инициализированного в списке параметров с именами измерений или аналогичными, или, возможно, просто целого числа для размера или пустого массива компонентов - вашего дизайна) и лямбда-выражения для метод getMagnitude. Вы можете сделать так, чтобы перечисление также реализовало интерфейс для setComponents / getComponent (s) и просто установило, какой компонент был каким при его использовании, исключив getX, и др. Вы должны будете инициализировать каждый объект с его фактическими значениями компонентов перед использованием, возможно, проверяя, что размер входного массива соответствует именам или размеру измерений.
Затем, если вы расширите решение до другого измерения, вы просто измените enum и lambda.
источник
Исходя из вашего варианта 2, почему бы просто не сделать это? Если вы хотите предотвратить использование необработанной базы, вы можете сделать ее абстрактной:
источник
double[]
нежелательны по сравнению с реализацией, которая просто использует 2 примитиваdouble
. В таком минимальном примере это похоже на микрооптимизацию, но рассмотрим гораздо более сложный случай, когда задействовано гораздо больше метаданных, а рассматриваемый тип имеет короткое время жизни.Vector
на более специализированную реализацию (напримерVector3
), если его время жизни будет относительно большим.Одна идея:
Это дает вам хорошую производительность в типичных случаях и некоторую безопасность во время компиляции (все еще можно улучшить) без ущерба для общего случая.
Код скелета:
источник