Можно ли избежать неконтролируемого приведения при вызове `clone ()`? - PullRequest
1 голос
/ 07 ноября 2019

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

Учитывая этот код:

1 public static void main(String[] args) {
2    Map<String,String> map = null;
3
4    HashMap<String,String> hmap;
5
6    if(map instanceof HashMap)
7        hmap = (HashMap<String,String>)map;
8    else
9        hmap = new HashMap<String,String>(map);
10
11   map = (Map<String,String>)hmap.clone();
12
13    Object o = hmap.clone();
14    if(o instanceof Map<?,?>)
15        map = (Map<String,String>)o;
16 }

Код в обеих строках 11 и 15 генерирует компиляторпредупреждение:

Unchecked cast from Object to Map<String,String>

Строка 11 немного понятна: Object.clone() возвращает Object, и проверки перед проверкой не было instanceof. Программист знает , что клоном будет Map<String,String>, но компилятор не может этого доказать.

Строка 15, однако, озадачивает меня. Обычно проверка типа переменной с помощью instanceof и последующее приведение к ней не генерируют такого предупреждения. Действительно, замена кода на непараметрические классы, подобные этому, не вызовет предупреждений ни в одной из следующих строк кода:

static class A {}
static class B extends A implements Cloneable {
    public Object clone() { return null; }
}
public static void main(String[] args) {
    A a = null;

    B b;

    if(a instanceof B)
        b = (B)a;
    else
        b = new B();

    a = (A)b.clone();

    Object o = b.clone();
    if(o instanceof A)
        a = (A)o;
}

Назад к исходному коду (со ссылками Map<String,String>), даже добавляяэта неуклюжая конструкция до конца кода генерирует похожее предупреждение:

map = (Map<String,String>)hmap.getClass().cast(o);

На этот раз предупреждение Unchecked cast from capture#11-of ? extends HashMap to Map<String,String>. Попытка написать:

map = HashMap<String,String>.class.cast(o);

Генерирует компилятор error , потому что он не может понять, что HashMap<String,String>.class является ссылкой на статический класс так же, как, например, HashMap.class, поэтомумы должны использовать ссылку «правильного» типа для вызова Class.cast.

Это то, что Java просто не может сделать?

Ответы [ 2 ]

1 голос
/ 07 ноября 2019

Это то, что Java просто не может сделать?

Да, это так, как задумано.

Посмотрите на java-источники (1.8) HashMapметод клонирования:

@SuppressWarnings("unchecked")
@Override
public Object clone() {
    HashMap<K,V> result;
    try {
        result = (HashMap<K,V>)super.clone();
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
    result.reinitialize();
    result.putMapEntries(this, false);
    return result;
}

Для тех же целей используется @SuppressWarnings("unchecked") для подавления предупреждения на super.clone().

Вы не можете полностью избежать этого, но если в вашем коде много методов clone (), вы можете минимизировать эти предупреждения, извлекая в метод, подобный:

@SuppressWarnings("unchecked")
HashMap<String,String> cloneWithoutWarning(HashMap<String,String> map) { return (HashMap<String,String>) map.clone(); } 
0 голосов
/ 07 ноября 2019

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

Это не так. instanceof s не влияют на предупреждения о сотворении.

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

В данном случае это реальная проблема. HashMap.clone() возвращает Object для обратной совместимости. Невозможно определить, является ли его реализация безопасным типом. Например:

import java.util.*;
class Main {
    public static void main(String[] args) {
        HashMap<String,String> map = new HashMap() {
            @Override
            public Object clone() {
                HashMap o = (HashMap)super.clone();
                o.put("x", new Object());
                return o;
            }
        };
        Map<String,String> copy = (Map<String,String>)map.clone(); // will pass
        System.out.println(copy.get("x")); // no cast but fails with ClassCastException
    }
}

clone() проблематично. Если возможно, просто создайте новый HashMap.

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

Map<?, ?> copy = (Map<?, ?>)map.clone();
System.out.println((String)copy.get("x")); // explicit cast will fail

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

...