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

Как избежать повторения при работе с примитивными типами?

Мне нужно выполнять алгоритмы на разных примитивных типах; алгоритм по существу тот же, за исключением того, какой тип переменных является. Так, например,

/**
* Determine if <code>value</code> is the bitwise OR of elements of <code>validValues</code> array. 
* For instance, our valid choices are 0001, 0010, and 1000.
* We are given a value of 1001.  This is valid because it can be made from
* ORing together 0001 and 1000.
* On the other hand, if we are given a value of 1111, this is invalid because
* you cannot turn on the second bit from left by ORing together those 3
* valid values.
*/
public static boolean isValid(long value, long[] validValues) {
    for (long validOption : validValues) {
        value &= ~validOption;
    }
    return value == 0;
}

public static boolean isValid(int value, int[] validValues) {
    for (int validOption : validValues) {
        value &= ~validOption;
    }
    return value == 0;
}

Как я могу избежать этого повторения? Я знаю, что нет возможности генерировать примитивные массивы, поэтому мои руки кажутся связанными. У меня есть экземпляры примитивных массивов, а не массивы в штучной упаковке, называемые объектами Number, поэтому я тоже не хочу идти по этому маршруту.

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

Я полагаю, я мог бы сделать что-то вроде:

public static<E extends Number> boolean isValid(E value, List<E> numbers) {
    long theValue = value.longValue();
    for (Number validOption : numbers) {
        theValue &= ~validOption.longValue();
    }
    return theValue == 0;
}

а затем

public static boolean isValid(long value, long[] validValues) {
    return isValid(value, Arrays.asList(ArrayUtils.toObject(validValues)));
}

public static boolean isValid(int value, int[] validValues) {
    return isValid(value, Arrays.asList(ArrayUtils.toObject(validValues)));
}

Это действительно намного лучше? Таким образом, будет создано намного больше объектов, чем исходная реализация, хотя она СУЩЕСТВУЕТ исходный код. Любые мысли в этом вопросе будут оценены.

4b9b3361

Ответ 1

Я задал аналогичный вопрос раньше (Управление сильно повторяющимся кодом и документацией в Java) и отметил, что исходный код для java.util.Arrays очень повторяется в своих алгоритмах для работы с примитивными типами массивов.

Фактически, исходный код содержит этот комментарий:

Код для каждого из семи примитивных типов в значительной степени идентичен. C'est la vie.

Ответ, который я принял, предполагает использование генератора кода, который позволяет вам работать с шаблонами кода. Также есть комментарий, что Sun/Oracle использует внутреннюю систему шаблонов.

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

import java.lang.reflect.Array;

static long maskFor(Class<?> c) {
    return (
        c.equals(int.class) ? 1L << Integer.SIZE :
        c.equals(short.class) ? 1L << Short.SIZE :
        c.equals(byte.class) ? 1L << Byte.SIZE :
        0
    ) - 1;
}   
public static void reflectPrimitiveNumericArray(Object arr) throws Exception {
    int length = Array.getLength(arr);
    Class<?> componentType = arr.getClass().getComponentType();
    long mask = maskFor(componentType);
    System.out.format("%s[%d] = { ", componentType, length);
    for (int i = 0; i < length; i++) {
        long el = Array.getLong(arr, i) & mask;
        System.out.print(Long.toBinaryString(el) + " ");
    }
    System.out.println("}");
}

Вы можете передать int[] для arr, а также другие примитивные типы массивов. Все вставляется в long, с битовой маскировкой для расширения знака адреса.

reflectPrimitiveNumericArray(new byte[] { (byte) 0xF0 });
// byte[1] = { 11110000 }
reflectPrimitiveNumericArray(new int[] { 0xF0F0F0F0 });
// int[1] = { 11110000111100001111000011110000 }
reflectPrimitiveNumericArray(new long[] { 0xF0F0F0F0F0F0F0F0L });
// long[1] = { 1111000011110000111100001111000011110000111100001111000011110000 }

Ответ 2

Если вы посмотрите в java.util.Arrays, вы увидите, что даже им пришлось специализироваться на всех своих алгоритмах (binarySearch, equals и т.д.) для каждого примитивного типа.

Я бы не рекомендовал полагаться на autoboxing, если производительность является проблемой, но если нет (после того, как вы профилировали), это будет допустимый вариант.

Ответ 3

Я думаю, тебе не повезло, я боюсь - не отрываясь от примитивных типов.

Ответ 4

В предыдущей жизни у нас были некоторые примитивные типизированные коллекции, которые были оптимизированы для финансовых данных (миллионы заказов в памяти, используя массивы каналов и т.д.). Наше решение было похоже на Trove, с некоторыми файлами-заглушками. Исходный исходный файл будет, например,... HashSet_key. С некоторыми классами-заглушками для ключа и значения мы следовали модели Trove с использованием задач ant для генерации HashSetInt, HashSetLong и т.д.

Я всегда думал, что это был "болтливый" метод, однако он сработал. Мне любопытно, кто-нибудь когда-либо пробовал Velocity, или, возможно, FMPP, и имел немного более чистые результаты? Основной проблемой, с которой я столкнулся с решением ant, является то, что весь код работает довольно близко друг к другу, тогда как при генерации исходного кода вы можете лучше отделить файлы шаблонов.