Аргумент общего типа, проверенный параметром класса, можно взломать, есть ли лучшие способы?

Рассмотрим класс:

class OnlyIntegerTypeAllowed<T> {
    OnlyIntegerTypeAllowed(Class<T> clazz) {
        System.out.println(clazz);
        if (clazz != Integer.class)
            throw new RuntimeException();
    }
}

Он предназначен для приема только аргументов типа Integer. Мы добавили проверку if-throw в его конструктор. Это очень распространенный способ проверки аргументов типа.

Однако эту проверку можно обойти (взломать, обмануть) с помощью:

OnlyIntegerTypeAllowed<Integer> normalWay =
        new OnlyIntegerTypeAllowed<Integer>(Integer.class);
OnlyIntegerTypeAllowed<String> hacking =
        new OnlyIntegerTypeAllowed<String>((Class<String>) Class.forName(Integer.class.getName()));

В обеих приведенных выше строках нет ошибок компиляции и никаких исключений!

МОЙ БОГ. - есть ли лучший способ применить аргумент типа?


person midnite    schedule 03.09.2013    source источник
comment
Если вы решите игнорировать или подавлять предупреждения компилятора, то да, вы можете нанести всевозможный ущерб. Если вы обратите внимание на предупреждения компилятора, описанная вами ситуация никогда не может произойти (кроме как через отражение).   -  person VGR    schedule 03.09.2013
comment
@VGR да, есть предупреждение unchecked cast. Но я имею в виду, что кто-то может намеренно сломать (взломать) это правоприменение.   -  person midnite    schedule 03.09.2013


Ответы (1)


Добро пожаловать в стирание типов. Во время выполнения Class<T> был стерт до Class - для JVM, то, что было в исходном коде Class<Integer>, Class<String> и т. д., все выглядит одинаково. В этом смысл предупреждения непроверенное приведение — разработчик делает это на свой страх и риск, потому что оно не быстро выйдет из строя с ClassCastException, если оно неправильный. Вместо этого некоторые более поздние ClassCastException могут возникать вместо приведения, которое было вставлено компилятором во время процесса стирания типа. Это состояние, в котором ссылки общего типа указывают на объекты, к которым им не должно быть разрешено, известно как загрязнение кучи.

МОЙ БОГ. - есть ли лучший способ применить аргумент типа?

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

В качестве примечания, непроверенные приведения иногда допустимы, например, при реализации пункта 27 Джошуа Блоха Effective Java, "отдавать предпочтение универсальным методам":

private static final Comparator<Object> HASH_CODE_COMPARATOR =
        new Comparator<Object>() {
            @Override
            public int compare(final Object o1, final Object o2) {
                return Integer.compare(o1.hashCode(), o2.hashCode());
            }
        };

public static <T> Comparator<T> hashCodeComparator() {
    @SuppressWarnings("unchecked") // this is safe for any T
    final Comparator<T> withNarrowedType =
            (Comparator<T>)(Comparator<?>)HASH_CODE_COMPARATOR;
    return withNarrowedType;
}

Здесь непроверенное приведение безопасно, потому что HASH_CODE_COMPARATOR ведет себя контравариантно. Он не имеет состояния и работает для любого Object, поэтому мы можем позволить вызывающей стороне определить его универсальный тип:

Comparator<String> c = hashCodeComparator();

В этом случае мы можем использовать @SuppressWarnings("unchecked"), чтобы удалить непроверенное предупреждение, по сути сообщая компилятору, что нам доверяют. Также рекомендуется добавить пояснительный комментарий.

person Paul Bellora    schedule 03.09.2013
comment
Спасибо за хороший ответ. Будет ли лучше, если мы на самом деле примем T typeObject вместо Class<T>? OnlyIntegerTypeAllowed<Number> valid = new OnlyIntegerTypeAllowed<Number>((Number) new Integer(1)); Таким образом, мы уверены, что определенный параметр типа является суперклассом правильного типа, потому что только приведение к суперклассу не вызывает ошибок при компиляции. Однако это приводит к ограничению, заключающемуся в том, что некоторые типы не могут создавать экземпляры (например, рекурсивное определение) и не могут использовать эту структуру. - person midnite; 03.09.2013
comment
@midnite Я склонен сказать нет, что от вызывающей стороны следует ожидать правильного использования дженериков в рамках контракта. Но на самом деле это зависит от того, что вы пытаетесь сделать. Ясно, что тип с именем OnlyIntegerTypeAllowed вообще не будет общим и будет иметь дело только с Integer напрямую, но я понимаю, что это надуманный пример. - person Paul Bellora; 03.09.2013
comment
@midnite Глядя на ваш исходный вопрос, кажется, что вы пытаетесь использовать тип self, но на самом деле это не поддерживается язык (см. мой ответ здесь). Я рекомендую обновить / уточнить ваш исходный вопрос. - person Paul Bellora; 03.09.2013