Подтвердить что ты не робот

Дублирование кода, вызванное примитивными типами: как избежать безумия?

В одном из моих проектов Java меня преследует повторение кода из-за того, как Java обрабатывает (not) примитивы. После того, как вы снова вручную скопировали одно и то же изменение в четыре разных местоположения (int, long, float, double), в третий раз, снова и снова, я действительно очень близко (?) К привязке.

В разных формах эта проблема была поднята сейчас и затем в StackOverflow:

Консенсус, казалось, сходился к двум возможным альтернативам:

  • Используйте генератор кода.
  • Что вы можете сделать? C'est la vie!

Хорошо, второе решение - это то, что я делаю сейчас, и это медленно становится опасным для моего здравомыслия, подобно хорошо известный метод пыток.

Прошло два года с тех пор, как были заданы эти вопросы, и появился Java 7. Поэтому я надеюсь на более легкое и/или более стандартное решение.

  • Есть ли в Java 7 какие-либо изменения, которые могут облегчить нагрузку в таких случаях? Я не мог найти ничего в сокращенных сводках изменений, но, возможно, есть какая-то неясная новая функция где-то?

  • Хотя генерация исходного кода является альтернативой, я бы предпочел решение, поддерживаемое с помощью стандартного набора функций JDK. Конечно, используя cpp или другой генератор кода будет работать, но он добавляет больше зависимостей и требует изменений в системе сборки.

    Единственная система генерации кода, которая, по-видимому, поддерживается JDK, - это механизм аннотаций. Я предвижу процессор, который расширяет исходный код следующим образом:

    @Primitives({ "int", "long", "float", "double" })
    @PrimitiveVariable
    int max(@PrimitiveVariable int a, @PrimitiveVariable int b) {
        return (a > b)?a:b;
    }
    

    Идеальный выходной файл будет содержать четыре запрошенных варианта этого метода, предпочтительно с соответствующими комментариями Javadoc e.t.c. Есть ли где-нибудь обработчик аннотации для обработки этого случая? Если нет, что нужно, чтобы построить один?

  • Возможно, какой-то другой трюк, который появился недавно?

EDIT:

Важное замечание: я бы не использовал примитивные типы, если у меня не было причины. Даже сейчас в некоторых приложениях наблюдается очень высокая производительность и память при использовании типов в штучной упаковке.

ИЗМЕНИТЬ 2:

Использование max() в качестве примера позволяет использовать метод compareTo(), который доступен во всех числовых коробчатых типах. Это немного сложнее:

int sum(int a, int b) {
    return a + b;
}

Как можно поддерживать этот метод для всех числовых типов в штучной упаковке без фактического написания его шесть или семь раз?

4b9b3361

Ответ 1

Я обычно использую "супер-тип", например long или double, если я все еще хочу примитив. Производительность обычно очень близка, и она позволяет избежать множества вариаций. BTW: регистры в 64-разрядной JVM все равно будут 64-разрядными.

Ответ 2

Почему вы повесили на примитивах? Обертки очень легкие, а авто-бокс и дженерики делают остальные:

public static <T extends Number & Comparable<T>> T max(T a, T b) {
    return a.compareTo(b) > 0 ? a : b;
}

Все это выполняется и выполняется правильно:

public static void main(String[] args) {
    int i = max(1, 3);
    long l = max(6,7);
    float f = max(5f, 4f);
    double d = max(2d, 4d);
    byte b = max((byte)1, (byte)2);
    short s = max((short)1, (short)2);
}

Edited

OP задал вопрос об общем, автоматическом коробке для sum() и будет здесь.

public static <T extends Number> T sum(T... numbers) throws Exception {
    double total = 0;
    for (Number number : numbers) {
        total += number.doubleValue();
    }
    if (numbers[0] instanceof Float || numbers[0] instanceof Double) {
        return (T) numbers[0].getClass().getConstructor(String.class).newInstance(total + "");
    }
    return (T) numbers[0].getClass().getConstructor(String.class).newInstance((total + "").split("\\.")[0]);
}

Это немного хромает, но не так хромает, как большая серия instanceof и делегируется полностью типизированному методу. instanceof требуется, потому что, хотя все Numbers имеют конструктор String, Numbers кроме Float и Double могут анализировать только целое число (без десятичной точки); хотя общее число будет целым числом, мы должны удалить десятичную точку из Double.toString() перед отправкой в ​​конструктор для этих других типов.

Ответ 3

Есть ли в Java 7 какие-либо изменения, которые могут облегчить нагрузку в таких случаях?

Нет.

Есть ли какой-нибудь обработчик аннотации для обработки этого случая?

Не то, что я знаю.

Если нет, что нужно, чтобы построить один?

Время или деньги.: -)

Мне кажется, что это проблема-пространство, где трудно найти общее решение, которое хорошо работает... за тривиальными случаями. Обычное генерация исходного кода или (текстовый) препроцессор представляется мне более перспективным. (Хотя я не эксперт по процессору аннотации).

Ответ 4

Если экстраординарная многословие Java подходит вам, изучите некоторые новые языки более высокого уровня, которые запускаются на JVM и могут взаимодействовать с Java, например Clojure, JRuby, Scala и т.д., Ваше неконтролируемое примитивное повторение станет беспроблемным. Но преимущества будут намного больше, чем это - есть всевозможные способы, которые только что упомянутые языки позволяют вам делать больше с менее подробным, повторяющимся, подверженным ошибкам кодом (по сравнению с Java).

Если производительность является проблемой, вы можете вернуться в Java для критически важных бит (используя примитивные типы). Но вы можете быть удивлены тем, насколько часто вы можете получить хороший уровень производительности на языке более высокого уровня.

Я лично использую JRuby и Clojure; если вы работаете на фоне Java/C/С#/С++, у обоих есть возможность изменить способ программирования.

Ответ 5

Хех. Почему бы не стать подлым? С отражением вы можете вытягивать аннотации для метода (аннотации, похожие на приведенный вами пример). Затем вы можете использовать отражение, чтобы получить имена участников, и поместить соответствующие типы... В инструкции system.out.println.

Вы запускаете это один раз или каждый раз, когда вы модифицируете класс. Результат можно было бы скопировать в. Это, вероятно, сэкономит вам значительное время, и не будет слишком сложно разработать.

Hm, как и для содержимого методов... Я имею в виду, что если все ваши методы тривиальны, вы можете жестко закодировать стиль (т.е. если методName.equals( "max" ) print return a > b: a: b и т.д. Где методName определяется посредством отражения), или вы могли бы, мммм... Hm. Я представляю, что содержимое можно легко скопировать, но это просто кажется более полезным.

О! Внесите еще одну аннотацию, называемую "содержимое", дайте ей строковое значение содержимого метода, добавьте это члену, и теперь вы также можете распечатать содержимое.

По крайней мере, время, затрачиваемое на кодирование этого помощника, даже если до тех пор, пока он выполняет утомительную работу, было бы интереснее, riiiight?

Ответ 6

Ваш вопрос довольно сложный, поскольку вы уже, кажется, знаете все "хорошие" ответы. Поскольку из-за языкового дизайна нам не разрешено использовать примитивы в качестве типичных типов параметров, лучшим практическим ответом является то, где @PeterLawrey заголовок.

public class PrimitiveGenerics {

    public static double genericMax( double a, double b) {
        return (a > b) ?a:b;
    }


    public int max( int a, int b) {
        return (int) genericMax(a, b);
    }
    public long max( long a, long b) {
        return (long) genericMax(a, b);
    }
    public float max( float a, float b) {
        return (float) genericMax(a, b);
    }
    public double max( double a, double b) {
        return (double) genericMax(a, b);
    }


}

Список примитивных типов невелик и, надеюсь, постоянный в будущей эволюции языка, а двойной тип - самый широкий/самый общий.

В худшем случае вы вычисляете с использованием 64-битных переменных, где достаточно 32 бит. Для конверсии (крошечный) и для передачи по значению есть еще один способ (маленький), но нет объектов, потому что это основной (и действительно огромный) штраф за использование примитивных оберток.

Я также использовал статический метод, поэтому он привязан рано, а не во время выполнения, хотя он всего лишь один, и это то, что обычно оптимизирует оптимизация JVM, но в любом случае это не повредит. Может зависеть от реального сценария.

Было бы здорово, если бы кто-то его протестировал, но я считаю, что это лучшее решение.

UPDATE: Основываясь на комментарии @thkala, double может представлять только long-s до определенной величины, поскольку он теряет точность (становится неточным при работе с long-s) после этого:

public class Asdf2 {

    public static void main(String[] args) {
        System.out.println(Double.MAX_VALUE); //1.7976931348623157E308
        System.out.println( Long.MAX_VALUE); //9223372036854775807
        System.out.println((double) Long.MAX_VALUE); //9.223372036854776E18
    }
}

Ответ 7

С точки зрения производительности (я делаю много алгоритмов, связанных с процессором), я использую свои собственные боксы, которые не являются неизменными. Это позволяет использовать изменяемые числа в наборах, таких как ArrayList и HashMap, для работы с высокой производительностью.

Требуется один длительный этап подготовки, чтобы все примитивные контейнеры с их повторяющимся кодом, а затем вы просто используете их. Поскольку я также занимаюсь двумерными трехмерными значениями и т.д., Я также создал их для себя. Выбор за вами.

как:
Vector1i - 1 целое число, заменяет Integer
Vector2i - 2 целых числа, заменяет Point и Dimension
Vector2d - 2 удваивается, заменяет Point2D.Double
Vector4i - 4 целых числа, может заменить Rectangle
Vector2f - двумерный вектор с плавающей точкой
Vector3f - трехмерный вектор с плавающей точкой
... и т.д...
Все они представляют собой обобщенный "вектор" в математике, отсюда и название всех этих примитивов.

Один недостаток заключается в том, что вы не можете сделать a+b, у вас есть методы вроде a.add(b), а для a=a+b я решил назвать методы типа a.addSelf(b). Если это вас беспокоит, посмотрите Ceylon, который я обнаружил совсем недавно. Это слой поверх Java (JVM/Eclispe compatbile), созданный специально для устранения его ограничений (например, перегрузки операторов).

Еще одна вещь, следите за тем, чтобы использовать эти классы в качестве ключа в Map, поскольку сортировка/хеширование/сравнение будут ускользать при изменении значения.

Ответ 8

Я бы согласился с предыдущими ответами/комментариями, которые говорят, что нет способа сделать именно то, что вы хотите ", используя стандартный набор функций JDK". Таким образом, вам нужно будет сделать некоторое генерирование кода, хотя это не обязательно потребует изменений в системе сборки. Поскольку вы спрашиваете:

... Если нет, что нужно, чтобы построить один?

... Для простого случая, не слишком много, я думаю. Предположим, что я применил примитивные операции в классе util:

public class NumberUtils {

    // @PrimitiveMethodsStart
    /** Find maximum of int inputs */
    public static int max(int a, int b) {
        return (a > b) ? a : b;
    }

    /** Sum the int inputs */
    public static int sum(int a, int b) {
        return a + b;
    }
    // @PrimitiveMethodsEnd

    // @GeneratedPrimitiveMethodsStart - Do not edit below
    // @GeneratedPrimitiveMethodsEnd
}

Затем я могу написать простой процессор менее чем за 30 строк следующим образом:

public class PrimitiveMethodProcessor {
    private static final String PRIMITIVE_METHODS_START = "@PrimitiveMethodsStart";
    private static final String PRIMITIVE_METHODS_END = "@PrimitiveMethodsEnd";
    private static final String GENERATED_PRIMITIVE_METHODS_START = "@GeneratedPrimitiveMethodsStart";
    private static final String GENERATED_PRIMITIVE_METHODS_END = "@GeneratedPrimitiveMethodsEnd";

    public static void main(String[] args) throws Exception {
        String fileName = args[0];
        BufferedReader inputStream = new BufferedReader(new FileReader(fileName));
        PrintWriter outputStream = null;
        StringBuilder outputContents = new StringBuilder();
        StringBuilder methodsToCopy = new StringBuilder();
        boolean inPrimitiveMethodsSection = false; 
        boolean inGeneratedPrimitiveMethodsSection = false; 
        try {
            for (String line;(line = inputStream.readLine()) != null;) {
                if(line.contains(PRIMITIVE_METHODS_END)) inPrimitiveMethodsSection = false;
                if(inPrimitiveMethodsSection)methodsToCopy.append(line).append('\n');
                if(line.contains(PRIMITIVE_METHODS_START)) inPrimitiveMethodsSection = true;
                if(line.contains(GENERATED_PRIMITIVE_METHODS_END)) inGeneratedPrimitiveMethodsSection = false;
                if(!inGeneratedPrimitiveMethodsSection)outputContents.append(line).append('\n');
                if(line.contains(GENERATED_PRIMITIVE_METHODS_START)) {
                    inGeneratedPrimitiveMethodsSection = true;
                    String methods = methodsToCopy.toString();
                    for (String primative : new String[]{"long", "float", "double"}) {
                        outputContents.append(methods.replaceAll("int\\s", primative + " ")).append('\n');
                    }
                }
            }
            outputStream = new PrintWriter(new FileWriter(fileName));
            outputStream.print(outputContents.toString());
        } finally {
            inputStream.close();
            if(outputStream!= null) outputStream.close();
        }
    }
}

Это заполнит раздел @GeneratedPrimitiveMethods длинными, плавающими и двойными версиями методов в разделе @PrimitiveMethods.

    // @GeneratedPrimitiveMethodsStart - Do not edit below
    /** Find maximum of long inputs */
    public static long max(long a, long b) {
        return (a > b) ? a : b;
    }
    ...

Это преднамеренно простой пример, и я уверен, что он не охватывает все случаи, но вы понимаете суть и можете увидеть, как он может быть расширен, например. для поиска нескольких файлов или использования обычных аннотаций и обнаружения методов.

Кроме того, хотя вы можете установить это как шаг в своей системе сборки, я установил его для запуска как строитель перед Java-конструктором в моем проекте eclipse. Теперь, когда я редактирую файл и нажимаю save; он обновляется автоматически, на месте, менее чем за четверть секунды. Таким образом, это становится скорее инструментом редактирования, чем шагом в системе сборки.

Просто мысль...