Java Generics и числа - PullRequest
       18

Java Generics и числа

18 голосов
/ 18 мая 2009

Пытаясь понять, смогу ли я очистить часть своего математического кода, в основном матричного кода, я пытаюсь использовать некоторые Java Generics. У меня есть следующий метод:

private <T> T[][] zeroMatrix(int row, int col) {
    T[][] retVal = (T[][])new Object[row][col];
    for(int i = row; i < row; i++) {
        for(int j = col; j < col; j++) {
            retVal[i][j] = 0;
        }
    }
    return retVal;
}

Строка retVal [i] [j] = 0 - та, которая вызывает у меня головную боль. Цель этой строки - инициализировать массив с представлением T, равным 0. Я попытался сделать с ним все что угодно: (T определяется в классе как T расширяет Number)

retVal[i][j] = (T)0;
retVal[i][j] = new T(0);

Единственное, что работает, это

retVal[i][j] = (T)new Object(0);

Это не то, что я хочу.

Возможно ли это? Есть ли более простой способ представления матрицы NxM любого типа числа (включая потенциально BigDecimal), или я застрял?

Ответы [ 11 ]

12 голосов
/ 18 мая 2009
<T extends Number> T[][] zeroMatrix(Class<? extends Number> of, int row, int col) {
    T[][] matrix = (T[][]) java.lang.reflect.Array.newInstance(of, row, col);
    T zero = (T) of.getConstructor(String.class).newInstance("0");
    // not handling exception      

    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; 
            matrix[i][j] = zero;
        }
    }

    return matrix;
}

использование:

    BigInteger[][] bigIntegerMatrix = zeroMatrix(BigInteger.class, 3, 3);
    Integer[][] integerMatrix = zeroMatrix(Integer.class, 3, 3);
    Float[][] floatMatrix = zeroMatrix(Float.class, 3, 3);
    String[][] error = zeroMatrix(String.class, 3, 3); // <--- compile time error
    System.out.println(Arrays.deepToString(bigIntegerMatrix));
    System.out.println(Arrays.deepToString(integerMatrix));
    System.out.println(Arrays.deepToString(floatMatrix));

EDIT

общая матрица:

public static <T> T[][] fillMatrix(Object fill, int row, int col) {
    T[][] matrix = (T[][]) Array.newInstance(fill.getClass(), row, col);

    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; j++) {
            matrix[i][j] = (T) fill;
        }
    }

    return matrix;
}    

Integer[][] zeroMatrix = fillMatrix(0, 3, 3); // a zero-filled 3x3 matrix
String[][] stringMatrix = fillMatrix("B", 2, 2); // a B-filled 2x2 matrix
8 голосов
/ 18 мая 2009

Массивы и Обобщения не хорошо играют вместе:

"Массивы являются ковариантными, что означает, что массив ссылок на супертипы является супертипом массива ссылок на подтипы. То есть Object[] является супертипом String[], а к строковому массиву можно обращаться через переменную ссылки типа Object[]. "

см. Часто задаваемые вопросы по Java Generics :

3 голосов
/ 18 мая 2009

должно быть нулевым, а не нулевым.

Если вы действительно хотите поместить туда эквивалентный 0 для объекта T, вам нужно предоставить фабрику T. Примерно так:

interface Factory<T> {
   T getZero();     
}

и вы должны сделать метод следующим образом:

private <T> T[][] zeroMatrix(int row, int col, Factory<T> factory) {
    T[][] retVal = (T[][])new Object[row][col];
    for(int i = row; i < row; i++) {
        for(int j = col; j < col; j++) {
            retVal[i][j] = factory.getZero();
        }
    }

    return retVal;
}

У вас также должны быть правильные реализации для фабрики:

 class IntegerFactory implements Factory<Integer> {
    Integer getZero() {
       return new Integer(0);
    }
}

Обычно вы бы поместили getMatrix(int row, int column) в фабричную реализацию, чтобы на самом деле вернуть правильный типизированный массив.

2 голосов
/ 18 мая 2009

В Java тип стирается во время выполнения, поэтому вам нужно передать другой аргумент, чтобы получить тип во время выполнения.

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

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

Затем вы можете использовать java.util.Arrays.fill для заполнения массива:

private static HashMap<Class<?>, Object> ZEROS = new HashMap<Class<?>,Object>();

static {
    ZEROS.put( Integer.class, Integer.valueOf(0) );
    ...
}

private static <T extends Number> T[][] zeroMatrix ( Class<T> type, int rows, int cols ) {
    @SuppressWarnings("unchecked")
    T[][]   matrix  = (T[][]) java.lang.reflect.Array.newInstance(type, rows, cols);
    Object  zero    = ZEROS.get(type);

    for ( T[] row : matrix ) 
        java.util.Arrays.fill(row,zero);

    return matrix;
}

Integer[][] matrix = zeroMatrix (Integer.class, 10, 10);

Однако, если производительность является проблемой удаленно, вы не хотите использовать коробочные значения для числового кода.

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

1 голос
/ 11 сентября 2009

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

1 голос
/ 18 мая 2009

Если вы действительно хотите использовать дженерики, вы можете сделать что-то вроде этого

private <T extends Number> T[][] zeroMatrix(int row, int col, Class<T> clazz) throws InstantiationException, IllegalAccessException,
        IllegalArgumentException, InvocationTargetException
{
    T[][] retVal = (T[][]) Array.newInstance(clazz, new int[] { row, col });
    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < col; j++)
        {
            Constructor<T> c = clazz.getDeclaredConstructors()[0];
            retVal[i][j] = c.newInstance("0");
        }
    }

    return retVal;
}

Пример:

zeroMatrix(12, 12, Integer.class);
1 голос
/ 18 мая 2009

Вы должны учитывать, что дженерики используются только во время компиляции для проверки безопасности типов. Эта информация теряется во время выполнения, поэтому вы не можете использовать автобокс для retVal [i] [j] = 0; так как Java не может автоматически устанавливать число или объект.

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

private <T> T[][] fillMatrix(int row, int col, T value) {
    T[][] retVal = (T[][])new Object[row][col];
    for(int i = 0; i < row; i++) {
       for(int j = 0; j < col; j++) {
          retVal[i][j] = value;
       }
    }
    return retVal;
}

Кстати, for (int i = row; i

редактирование: Вы не сможете привести результат к чему-то другому, кроме Object [] [], потому что это фактический тип массива.

1 голос
/ 18 мая 2009

Я думаю, вы сражаетесь в проигрышной битве Даже если вы решите это, как вы планируете решать сложение, вычитание и т.д.? Числовой класс не очень полезный суперкласс, и единственным полезным методом является doubleValue ().

Ноль может быть определен как единица при сложении или ноль при умножении, но без общего определения сложения или умножения общее определение нуля маловероятно.

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

Другой очевидный вариант - оставить массив с нулевой инициализацией, а затем изменить другой код, чтобы обработать ноль как ноль.

1 голос
/ 18 мая 2009

Обобщения и массивы не очень хорошо совпадают. Создание универсального массива не допускается, так как он не будет безопасным для типов. Это связано с тем, что если Sub является подтипом Super, то Sub [] является подтипом Super [], что не относится к универсальным типам; для любых двух различных типов Type1 и Type2 List не является ни подтипом, ни надтипом List. (Эффективная Java рассматривает это в главе 5, пункт 25).

1 голос
/ 18 мая 2009

Старые (эталонные) массивы плохо работают с генериками. В этом случае массивы также могут быть неэффективными. Вы создаете массив массивов, поэтому нет необходимости проверять косвенность и границы. Лучше сделать класс Matrix<T>. Вы также можете добавить к Matrix ссылку на экземпляр T, представляющий ноль.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...