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

Это в основном продолжение этого вопрос из вчерашнего дня. Я попытался реализовать «самый общий способ» (который я мог придумать) для создания «действительно» неизменяемых коллекций. Код ниже работает нормально; но это требует подавления предупреждений («сырые типы» и «непроверенные»).

Я пробовал все, что угодно, чтобы избежать этих предупреждений; но альтернативы просто не мог найти. Итак, мой вопрос: есть ли чистый способ избежать предупреждений, которые я подавляю?

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

private interface Generator<T> {
    T generateNewInstance();
}

@SuppressWarnings("rawtypes")
private final static Generator ListGenerator = new Generator<List>() {
    @Override
    public List generateNewInstance() {
        return new ArrayList();
    }
};

public static <T> List<T> unmodifiableListBasedOnCloneOf(List<T> elements) {
    @SuppressWarnings("unchecked")
    List<T> newInstance = makeNewInstanceOf(elements.getClass(), ListGenerator);
    newInstance.addAll(elements);
    return Collections.unmodifiableList(newInstance);
}

private static <T> T makeNewInstanceOf(Class<T> incomingClass, Generator<T> fallbackGenerator) {
    try {
        return (T) incomingClass.newInstance();
    } catch (IllegalAccessException | InstantiationException e) {
        return fallbackGenerator.generateNewInstance();
    }
}

person GhostCat    schedule 21.04.2016    source источник
comment
Я действительно не понимаю, что вы здесь пытаетесь сделать: если список заключен в unmodifiableList, почему имеет значение, какой тип базового списка? Почему недостаточно Collections.unmodifiableList(new ArrayList<>(elements))?   -  person Andy Turner    schedule 21.04.2016
comment
Прочтите мой другой вопрос. Дело в том, что это не так. Этот метод Collections только добавляет декоратор к существующему списку. Если вы обращаетесь к этому первоначальному списку через неукрашенный интерфейс, вы можете изменить этот список; а затем ваш якобы неизменяемый список меняется под обложкой.   -  person GhostCat    schedule 21.04.2016
comment
Collections.unmodifiableCollection(new ArrayList<>(elements)) не является представлением elements: он копирует elements в новый список (точно так же, как вы это делаете здесь), и ссылка на этот новый список не доступна ни для кого, кроме unmodifiableCollection.   -  person Andy Turner    schedule 21.04.2016
comment
Я настоятельно рекомендую вам использовать существующее решение. Например, в Guava есть несколько отличных неизменяемых коллекций, которые не только более производительны, чем эти методы на основе декораторов, но также позволяют избежать ненужного копирования. Главное предостережение неизменяемых коллекций Guava заключается в том, что они не поддерживают нулевые значения.   -  person Andy Turner    schedule 22.04.2016
comment
Как я уже писал в другом вопросе, который я упоминаю в этом вопросе: гуава не вариант.   -  person GhostCat    schedule 22.04.2016
comment
Например. Я не говорил, что это должна быть Гуава.   -  person Andy Turner    schedule 22.04.2016
comment
Ну, я попросил другие варианты в том другом вопросе; и никто не указал на другую реализацию. И, к сожалению, мы в значительной степени ограничены в использовании внешних сторонних библиотек ;-( ... но извините: спасибо за ваши попытки помочь. Это было тяжелое утро для меня; и я не должен защищаться, когда люди пытаются помощь.   -  person GhostCat    schedule 22.04.2016


Ответы (2)


Если вы сделаете копию List<T> в новый ArrayList<T> и передадите эту ссылку встроенной в Collections.unmodifiableList, ничто, кроме неизменяемого декоратора, не получит доступа к копии для ее изменения:

    List<String> original = new ArrayList<>(Arrays.asList("Hello", "World"));

    // This simply decorates original, so updates to original are
    // reflected in this list.
    List<String> unmodifiableDecorator = Collections.unmodifiableList(original);

    // The reference to the new ArrayList is scoped to the call to
    // Collections.unmodifiableList, so nothing else can have a
    // reference to it.
    List<String> unmodifiableCopy =
        Collections.unmodifiableList(new ArrayList<>(original));

    System.out.println("Before clear:");
    System.out.println(original);
    System.out.println(unmodifiableDecorator);
    System.out.println(unmodifiableCopy);

    original.clear();

    System.out.println("After clear:");
    System.out.println(original);
    System.out.println(unmodifiableDecorator);
    System.out.println(unmodifiableCopy);

Выход:

Before clear:
[Hello, World]
[Hello, World]
[Hello, World]
After clear:
[]
[]
[Hello, World]

Таким образом, unmodifiableCopy не изменяется и не может быть изменен, если вы каким-то образом не убедите unmodifiableList предоставить вам ссылку на делегата.

Поскольку вы не можете его изменить, вам не нужно делать копию того же конкретного типа списка, что и входной список. Просто сделайте его ArrayList, так как он обеспечивает лучшую производительность чтения, которую можно достичь с помощью реализации List.

Итак, ваш метод без предупреждений или дополнительных классов может быть:

public static <T> List<T> unmodifiableListBasedOnCloneOf(List<T> elements) {
  return Collections.unmodifiableList(new ArrayList<>(elements));
}
person Andy Turner    schedule 21.04.2016
comment
Вся суть моей реализации заключалась в следующем: что, если этот список поддержки не является ArrayList? Но, может быть, LinkedList? Смысл моей реализации состоит в том, чтобы использовать ArrayList только в качестве запасного варианта, когда код не может создать список того же характера, что и тот, который входит ... - person GhostCat; 21.04.2016
comment
В чем преимущество использования связанного списка? В неизменяемом списке могут использоваться только методы, определенные в List; Единственные методы, для которых LinkedList имеет преимущество, - это вставка или удаление в середине списка, которые не используются. Все методы только для чтения работают так же или хуже по сравнению с ArrayList. - person Andy Turner; 21.04.2016
comment
Ну, пример касается списка; но я хочу предоставить по-настоящему неизменяемые оболочки для всех 5 методов из Коллекций. И там то же самое. И чтобы прояснить это: я не думаю, что ответ, содержащий в основном тот же код, что и вопрос, на самом деле не является ответом. Я знал, что могу сделать копию с помощью ArrayList; но я не об этом просил. - person GhostCat; 22.04.2016
comment
Ответ один и тот же во всех (6) случаях: используйте реализацию для каждого метода, которая имеет лучшую производительность чтения и сохраняет порядок вставки. - person Andy Turner; 22.04.2016

Не всегда удается избежать аннотации @SuppressWarnings. Я пытаюсь ограничить его область действия локальной переменной.

Я придумал это:

private interface Generator<T> {

    T generateNewInstance();
}

private static class ListGenerator<T> implements Generator<List<T>> {
    @Override
    public List<T> generateNewInstance() {
        return new ArrayList<>();
    }
};

public static <T> List<T> unmodifiableListBasedOnCloneOf(List<T> elements) {
    @SuppressWarnings("unchecked")
    Class<List<T>> clazz = (Class<List<T>>)elements.getClass();
    List<T> newInstance = makeNewInstanceOf(clazz , new ListGenerator<T>());
    newInstance.addAll(elements);
    return Collections.unmodifiableList(newInstance);
}

private static <T> T makeNewInstanceOf(Class<T> incomingClass, Generator<T> fallbackGenerator) {
    try {
        return (T) incomingClass.newInstance();
    } catch (IllegalAccessException | InstantiationException e) {
        return fallbackGenerator.generateNewInstance();
    }
}
person Maurice Perry    schedule 21.04.2016
comment
Понятно. Это избавляет от одного из предупреждений; ценой одного нового оператора (вероятно, это была преждевременная оптимизация; но я намеренно предложил решение, которое пытается минимизировать действия, которые имеют место в хорошем случае, когда не генерируется исключение). - person GhostCat; 21.04.2016
comment
incomingClass.newInstance() может обходить поддержку компилятора для проверенных исключений. Если вы вызовете incomingClass.getConstructor().newInstance(), он заставит вас поймать InvocationTargetException, который будет переносить исключения, созданные в конструкторе, позволяя вам обрабатывать их или использовать fallbackGenerator по своему усмотрению. - person Hank D; 21.04.2016