Почему бы не создать объект [] и применить к родовому типу? Какое решение?

Некоторые разработчики создают массивы родового типа, создавая Objec[] и отбрасывая общий тип, как в этом примере кода:

public class ArrTest<E> {

  public void test(E a){
    E[] b = (E[])new Object[1];
    b[0] = a;
    System.out.println(b[0]);
  }

  public static void main(String[] args){
    ArrTest<String> t = new ArrTest<String>();
    t.test("Hello World");
  }
}

Этот пример будет работать и имеет только предупреждение: Type safety: Unchecked cast from Object[] to E[].

Неужели это обескураживает? Это лучший способ создать массив родового типа? Может ли это вызвать неожиданные результаты или исключения, если я широко использую этот объект в своем программном обеспечении?

Ответ 1

В примере вопроса переменная b не является String[], хотя мы набросили ее на E[] и определили, что E есть String при построении экземпляра. Это a Object[]. Это происходит потому, что Java не знает, какой тип E находится во время выполнения, потому что в этом примере мы не определяли родительский класс для E. Таким образом, он будет автоматически иметь Object в качестве своего родителя.

В других терминах public class ArrTest<E> идентичен public class ArrTest<E extends Object>.

Java не знает, что E находится во время выполнения, потому что он unchecked. unchecked означает, что Java не будет проверять, является ли тип E расширением или реализацией определенного родительского класса. Итак, единственное, что Java знает о E во время выполнения, это <E extends Object>.

Поэтому

E[] b = (E[]) new Object[1];

будет выполняться как

Object[] b = (Object[]) new Object[1];

Вот почему пример не будет бросать ClassCastException и путает разработчика.

Если мы попытаемся использовать b как реальный String[], тогда Java будет бросать ClassCastException, поскольку Java видит его как Object[]. Например, если мы изменим метод на:

public E[] test(E a){
  E[] b = (E[])new Object[1];
  b[0] = a;
  System.out.println(b[0]);
  return b;
}

public static void main(String[] args){
    ArrTest<String> t = new ArrTest<String>();
    String[] result = t.test("Hello World");
}

Теперь мы получим ClassCastException в String[] result, потому что возвращаемый тип будет Object[], и мы пытаемся сохранить его в переменной String[]. Java увидит разницу типов и выдаст исключение.

То, что литье Object[] в общий массив не рекомендуется, это только приводит к путанице.

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

public class ExampleType<A extends Number>{
    public <T extends A> T[] bestMethod(T[] array)
    {
        if(array.length < testSize)
            array = (T[]) Array.newInstance(array.getClass().getComponentType(), testSize); //Type safety: Unchecked cast from Object to T[]
        System.out.println("in this case: "+array.getClass().getComponentType().getSimpleName());
        return array;
    }
}

Гарантируется возврат массива того же типа, что и массив, переданный как аргумент, и он должен быть экземпляром A, определенным в ExampleType<A extends Number>. Если вы создаете ExampleType of Integer, вам нужно использовать Integer[] в качестве аргумента. Если вам не нужен массив Integer, но вы хотите сохранить любой тип номера, вы можете использовать Number[] в качестве аргумента.

Если вам не нужны общие типы в классе, вы можете упростить его:

public <T> T[] bestMethod(T[] array)

Если вы хотите, чтобы он возвращал только подклассы Number:

public <T extends Number> T[] bestMethod(T[] array)

Вот мой тестовый пример, если вы хотите проверить его самостоятельно:

public class Test {
    public static class ArrTest<E>
    {
        public void test(E a){
            E[] b = (E[])new Object[1];
            b[0] = a;
            System.out.println(b[0]);
        }
        public E[] test2(E a){
            E[] b = (E[])new Object[1];
            b[0] = a;
            System.out.println(b[0]+" "+b.getClass().getComponentType());
            return b;
        }
        public static void main(String[] args){
            ArrTest<String> t = new ArrTest<String>();
            t.test("Hello World");
            try{String[] result = t.test2("Hello World");}catch(Exception e){System.out.println(e);}
        }
    }

    public static void main(String[] args) {
        ArrTest.main(args);

        System.out.println("#############\nWe want an array that stores only integers, sampledata: 1, samplearray: Integer");
        test(new ExampleType<Integer>(Integer.class), 1, new Integer[0], new Integer[10]);

        System.out.println("#############\nWe want an array that stores any type of Number, sampledata: 2L, samplearray: Number");
        test(new ExampleType<Number>(Number.class), 2L, new Number[0], new Number[10]);

        System.out.println("#############\nWe want an array that stores any type of CustomNumberA, sampledata: CustomB(3L), samplearray: CustomNumberA");
        test(new ExampleType<CustomNumberA>(CustomNumberA.class), new CustomNumberB(3L), new CustomNumberA[0], new CustomNumberA[10]);

        System.out.println("#############\nWe want A to be any type of number but we want to create an array of CustomNumberA, sampledata: CustomB(3L), samplearray: CustomNumberA");
        test(new ExampleType<Number>(Number.class), new CustomNumberB(3L), new CustomNumberA[0], new CustomNumberA[10]);
    }

    public static <A extends Number> void test(ExampleType<A> testType, A sampleData, A[] smallSampleArray, A[] bigSampleArray)
    {
        Class<A> clazz = testType.clazz;
        System.out.println("#############\nStarting tests with ExampleType<"+clazz.getSimpleName()+">");
        System.out.println("============\nCreating with badMethod()...");
        A[] array;
        try
        {
            array = testType.badMethod();
            testType.executeTests(array);
        }
        catch(Exception e){ System.out.println(">> ERR: "+e); }
        System.out.println("============\nCreating with alsoBadMethod("+sampleData+" ["+sampleData.getClass().getSimpleName()+"])...");
        try
        {
            array = testType.alsoBadMethod(sampleData);
            testType.executeTests(array);
        }
        catch(Exception e){ System.out.println(">> ERR: "+e); }
        System.out.println("============\nCreating with nearlyGoodMethod("+smallSampleArray.getClass().getSimpleName()+" len: "+smallSampleArray.length+")...");
        try
        {
            array = testType.nearlyGoodMethod(smallSampleArray);
            testType.executeTests(array);
        }
        catch(Exception e){ System.out.println(">> ERR: "+e); }
        System.out.println("============\nCreating with nearlyGoodMethod("+bigSampleArray.getClass().getSimpleName()+" len: "+bigSampleArray.length+")...");
        try
        {
            array = testType.nearlyGoodMethod(bigSampleArray);
            testType.executeTests(array);
        }
        catch(Exception e){ System.out.println(">> ERR: "+e); }
        System.out.println("============\nCreating with bestMethod("+smallSampleArray.getClass().getSimpleName()+" len: "+smallSampleArray.length+")...");
        try
        {
            array = testType.bestMethod(smallSampleArray);
            testType.executeTests(array);
        }
        catch(Exception e){ System.out.println(">> ERR: "+e); }
        System.out.println("============\nCreating with bestMethod("+bigSampleArray.getClass().getSimpleName()+" len: "+bigSampleArray.length+")...");
        try
        {
            array = testType.bestMethod(bigSampleArray);
            testType.executeTests(array);
        }
        catch(Exception e){ System.out.println(">> ERR: "+e); }
    }

    @RequiredArgsConstructor @ToString()
    public static class CustomNumberA extends Number{
        @Delegate final Long n;
    }

    public static class CustomNumberB extends CustomNumberA{
        public CustomNumberB(Long n) { super(n); }
    }

    @RequiredArgsConstructor
    public static class ExampleType<A>{
        private int testSize = 7;
        final Class<A> clazz;

        public A[] badMethod()
        {
            System.out.println("This will throw a ClassCastException when trying to return the array because Object is not a type of "+clazz.getSimpleName());
            A[] array = (A[]) new Object[testSize]; //Warning: Type safety: Unchecked cast from Object[] to A[]
            System.out.println("Array of "+array.getClass().getComponentType()+" created");
            return array;
        }

        public A[] alsoBadMethod(A sampleType)
        {
            System.out.println("Will not respect A type ("+clazz.getSimpleName()+"), will always use the highest type in sampleType and tell that it A[] but it not, in this case will return "+sampleType.getClass().getSimpleName()+"[] and said it was "+clazz.getSimpleName()+"[] while developing");
            A[] array = (A[]) Array.newInstance(sampleType.getClass(), testSize); //Type safety: Unchecked cast from Object to A[]
            return array;
        }

        public A[] nearlyGoodMethod(A[] array)
        {
            System.out.println("The only guarantee is that the returned array will be of something that extends A ("+clazz.getSimpleName()+") so the returned type is not clear, may be of A or of the type passed in the argument but will tell it A[] but may not be");
            if(array.length < testSize)
                array = (A[]) Array.newInstance(array.getClass().getComponentType(), testSize); //Type safety: Unchecked cast from Object to A[]
            System.out.println("in this case: "+array.getClass().getComponentType().getSimpleName()+"[], expecting: "+clazz.getSimpleName()+"[]");
            return array;
        }

        public <T extends A> T[] bestMethod(T[] array)
        {
            System.out.println("It guaranteed to return on array of the same type as the sample array and it must be an instance of A, so, this is the best method");
            if(array.length < testSize)
                array = (T[]) Array.newInstance(array.getClass().getComponentType(), testSize); //Type safety: Unchecked cast from Object to T[]
            System.out.println("in this case: "+array.getClass().getComponentType().getSimpleName()+"[], expecting: "+array.getClass().getComponentType().getSimpleName()+"[]");
            return array;
        }

        public void executeTests(A[] array)
        {
            tryToSet(array, 0, 1);
            tryToSet(array, 1, 2L);
            tryToSet(array, 2, 3.1);
            tryToSet(array, 3, 4F);
            tryToSet(array, 4, (byte)0x5);
            tryToSet(array, 5, new CustomNumberA(6L));
            tryToSet(array, 6, new CustomNumberB(7L));
        }

        public void tryToSet(A[] array, int index, Object value)
        {
            System.out.println("Trying to set "+value+" ("+value.getClass().getSimpleName()+") at "+index+" in a array of "+array.getClass().getComponentType().getSimpleName());
            try
            {
                if(array instanceof Object[]) ((Object[]) array)[index] = value;
                else array[index] = (A) value; //Type safety: Unchecked cast from Object to A
                System.out.println("## OK: Success: "+array.getClass().getComponentType().getSimpleName()+"["+index+"] = "+array[index]);
            }
            catch(Exception e){ System.out.println(">> ERR: "+e); }
        }
    }
}

И вот результаты теста... Вы можете видеть, что bestMethod всегда возвращает ожидаемый результат.

http://pastebin.com/CxBSHaYm

Ответ 2

Предупреждение, которое вы получили, только отмечает, что компилятор не может обеспечить безопасность статического типа в соответствии с правилами, указанными в Спецификации языка Java. Другими словами, он отмечает, что безопасность статического типа была нарушена.

Это не делает эту идиому недвусмысленно обескураженной. Вот полностью законный случай из самого JDK (в формате Grepcode):

 323 @SuppressWarnings ( "unchecked" ) 
324 public < T> T [] toArray (T [] a) {
325 if (a. length < size)
326//Создайте новый массив типа времени выполнения, но мое содержимое:
327 return (T []) Массивы. copyOf (elementData, size, a. getClass());
328 Система. arraycopy (elementData, 0, a, 0, size);
329 , если (размер a.length>)
330 a [size] = null;
331 return a;
332}

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