Подтвердить что ты не робот

Когда общее возвращаемое значение функции, отлитой после стирания стирания?

Этот вопрос был введен qaru.site/info/845811/.... Отвечая на вопрос, с которым я столкнулся, я не мог объяснить, основываясь исключительно на спецификации

Я нашел следующее выражение в Учебниках Java на Документация Oracle:

Не объясняется, что "если необходимо" означает точно, и Я нашел никаких упоминаний об этих приведениях в Java Language Спецификация, поэтому я начал экспериментировать.

Посмотрим на следующий фрагмент кода:

// Java source
public static <T> T identity(T x) {
    return x;
}
public static void main(String args[]) {
    String a = identity("foo");
    System.out.println(a.getClass().getName());
    // Prints 'java.lang.String'

    Object b = identity("foo");
    System.out.println(b.getClass().getName());
    // Prints 'java.lang.String'
}

Скомпилирован с javac и декомпилирован с Java Decompiler:

// Decompiled code
public static void main(String[] paramArrayOfString)
{
    // The compiler inserted a cast to String to ensure type safety
    String str = (String)identity("foo");
    System.out.println(str.getClass().getName());

    // The compiler omitted the cast, as it is not needed
    // in terms of runtime type safety, but it actually could
    // do an additional check. Is it some kind of optimization
    // to decrease overhead? Where is this behaviour specified?
    Object localObject1 = identity("foo");
    System.out.println(localObject1.getClass().getName());
}

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

public class Erasure {
    public static <T> T unsafeIdentity(Object x) {
        return (T) x;
    }

    public static void main(String args[]) {
        // I would expect c to be either an Integer after this
        // call, or a ClassCastException to be thrown when the
        // return value is not Integer
        Object c = Erasure.<Integer>unsafeIdentity("foo");
        System.out.println(c.getClass().getName());
        // but Prints 'java.lang.String'
    }
}

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

// The type of the return value of unsafeIdentity is not checked,
// just as in the second example.
Object localObject2 = unsafeIdentity("foo");
System.out.println(localObject2.getClass().getName());

Это означает, что если общая функция должна возвращать объект заданного тип, он не гарантирует, что он вернет этот тип в конечном счете. приложение, использующее вышеуказанный код, потерпит неудачу в первой точке, где он пытается чтобы вернуть возвращаемое значение в Integer, если это вообще произойдет, поэтому я чувствую, что он нарушает принцип отказоустойчивости.

Каковы точные правила компилятора, вставляющие этот листинг во время компиляция, которая обеспечивает безопасность типов и где указаны эти правила?

EDIT:

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

// It could compile to this, throwing ClassCastException:
Object localObject2 = (Integer)unsafeIdentity("foo");
4b9b3361

Ответ 1

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

В этом случае код стираемого компилятора выглядит следующим образом:

public static Object identity(Object x) {
    return x;
}
public static void main(String args[]) {
    String a = (String)identity("foo");
    System.out.println(a.getClass().getName());

    Object b = identity("foo");
    System.out.println(b.getClass().getName());
}

В первом случае приведение требуется в стираемом коде, потому что, если вы его удалили, стертый код не будет компилироваться. Это связано с тем, что Java гарантирует, что то, что удерживается во время выполнения в ссылочной переменной возвращаемого типа, должно быть instanceOf, которое может быть повторно идентифицируемым, поэтому здесь требуется проверка времени выполнения.

Во втором случае стираемый код компилируется без трансляции. Да, он также будет скомпилирован, если вы добавите бросок. Поэтому компилятор может решить в любом случае. В этом случае компилятор решил не вставлять бросок. Это совершенно правильный выбор. Вы не должны полагаться на компилятор для решения в любом случае.

Ответ 2

Версия 1 предпочтительнее, поскольку она терпит неудачу в compiletime.

Тип безопасного кода версии 1:

class Erasure {
public static <T> T unsafeIdentity(T x) {
    //no cast necessary, type checked in the parameters at compile time
    return x;
}

public static void main(String args[]) {
    // This will fail at compile time and you should use Integer c = ... in real code
    Object c = Erasure.<Integer>unsafeIdentity("foo");
    System.out.println(c.getClass().getName());
   }
}

Устаревший код типа safeafe версии 2 (Ошибка типа времени выполнения [...] В автоматически созданный бросок, введенный для обеспечения действительности операции на невосстанавливаемый тип и ссылка для ссылочного типа):

class Erasure {
public static <T> T unsafeIdentity(Object x) {
    return (T) x;
    //Compiled version: return (Object) x; 
    //optimised version: return x;
}

public static void main(String args[]) {
    // This will fail on return, as the returned Object is type Object and Subtype Integer is expected, this results in an automatic cast and a ClassCastException:
    Integer c = Erasure.<Integer>unsafeIdentity("foo");
    //Compiled version: Integer c = (Integer)Erasure.unsafeIdentity("foo");
    System.out.println(c.getClass().getName());
   }
}

Устаревший код TypeSafe версии 3, методы, в которых вы каждый раз знаете супертип (JLS Стирание переменной типа (§4.4) - это стирание ее левой части.):

class Erasure {
public static <T extends Integer> T unsafeIdentity(Object x) {
    // This will fail due to Type erasure and incompatible types:
    return (T) x;
    // Compiled version: return (Integer) x;
}

public static void main(String args[]) {
    //You should use Integer c = ...
    Object c = Erasure.<Integer>unsafeIdentity("foo");
    System.out.println(c.getClass().getName());
   }
}

Объект использовался только для иллюстрации того, что Object является допустимым целевым назначением в версиях 1 и 3, но при необходимости вы должны использовать реальный тип или общий тип.

Если вы используете другую версию java, вы должны посмотреть на конкретные страницы спецификации, я не ожидаю никаких изменений.

Ответ 3

Я не могу объяснить это очень хорошо, но комментарий не может добавить код так же, как я хочу, поэтому я добавляю этот ответ. Просто надеюсь, что этот ответ может помочь вашему пониманию. Комментарий не может добавить код, а также я хочу.

В вашем коде:

public class Erasure {
    public static <T> T unsafeIdentity(Object x) {
        return (T) x;
    }

    public static void main(String args[]) {
        // I would expect it to fail:
        Object c = Erasure.<Integer>unsafeIdentity("foo");
        System.out.println(c.getClass().getName());
        // but Prints 'java.lang.String'
    }
}

Он уничтожит Generics после компиляции. Во время компиляции у Erasure.unsafeIdentity нет ошибок. Дженерики jvm erasure зависят от параметров Generics, которые вы даете (Integer). После этого функция выглядит так:

public static Integer unsafeIdentity(Object x) {
    return x;
}

Фактически, ковариантные возвращения добавят Bridge Methods:

public static Object unsafeIdentity(Object x) {
    return x;
}

Если функция похожа на последнюю, вы считаете, что код в вашем основном методе будет скомпилирован? У него нет ошибок. Генераторы Erasure не добавят приведение в эту функцию, а возвращаемые параметры не являются отступом java-функции.

Мои объяснения немного надуманны, но надежда может помочь вам понять.

Edit:

После google об этой теме, я думаю, ваши проблемы - это ковариантные типы возврата с использованием мостовых методов. BridgeMethods