Почему излишне приводить оба аргумента вызова функции в Java, если параметры функции одного типа? - PullRequest
0 голосов
/ 19 июня 2020

Мне любопытно узнать, как работает приведение типов при вызове функций. У меня есть две перегрузки специальной функции c в моем классе с именем execute. Первая перегрузка является базовой реализацией и принимает параметры типа double. Вторая перегрузка принимает параметры типа int. Вторая перегрузка также вызывает базовую перегрузку и делает это путем преобразования параметров в double.

Базовая реализация выглядит так:

public double execute(double leftVal, double rightVal) throws IOException {
    ...
    return solution;
}

, а перегруженная версия выглядит так:

public int execute(int leftVal, int rightVal) throws IOException {
    return (int) execute((double) leftVal, (double) rightVal);
}

Почему приведенная выше часть, в частности, (double) leftVal, (double) rightVal, является избыточностью и почему она работает с удаленным одним из приведений? Вызов первой перегрузки работает независимо от того, в каком порядке происходит приведение типов. И execute(leftVal, (double)rightVal), и execute((double) leftVal, rightVal) выполняются нормально, без ошибок. Я всегда думал, что Java очень строго относится к явному определению типов. Я бы ожидал какого-то предупреждения или ошибки о том, что не существует функции, принимающей вызов типа execute(double, int). Однако у меня есть идея, что первое приведение помогает компилятору определить, какую перегрузку выбрать, поэтому второе приведение будет неявным. Может быть, потому, что типы примитивны и легко преобразовываются? Также мини-вопрос, называются ли обе функции перегрузками или каждое определение после первого является перегрузкой оригинала?

Ответы [ 3 ]

6 голосов
/ 19 июня 2020

Удаление любого из приведений по-прежнему будет работать, потому что в контексте вызова разрешено расширяющее примитивное преобразование. Так что нет, Java не так строг в отношении типов, как вы думали :)

Согласно Java Спецификации языка §5.3 Контексты вызова

Вызов контексты позволяют назначать значение аргумента в вызове метода или конструктора (§8.8.7.1, §15.9, §15.12) соответствующему формальному параметру.

Строгие контексты вызова допускают использование одного из следующих:

  • преобразование идентичности (§5.1.1)

  • расширяющееся примитивное преобразование (§5.1.2)

  • расширяющееся преобразование ссылки (§5.1.5)

«Расширяющееся примитивное преобразование» - это ... ( §5.1.2 )

19 специфических c преобразований для примитивных типов называются расширяющими примитивными преобразованиями:

  • byte в short, int , long, float или double

  • short до int, long, float или double

  • char до int, long, float , или double

  • int до long, float или double

  • long до float или double

  • float до double

int до double является одним из вышеуказанные преобразования и поэтому разрешены при вызове метода. С другой стороны, double в int - это сужающее примитивное преобразование, которое недопустимо в контексте вызова. Это объясняет, почему метод (double, double) вызывается однозначно .

1 голос
/ 19 июня 2020

Почему приведенное выше, в частности (double) leftVal, (double) rightVal часть, является избыточностью?

приведенное выше приведение необходимо, чтобы вы явно указали, что вы хотите вызвать эту перегруженную функцию базового уровня. В противном случае код будет просто предполагать, что вы хотите выполнить рекурсию в том же методе. Итак, да, вы правы, говоря, что «первое приведение помогает компилятору определить, какую перегрузку выбрать»

Таким образом, второе приведение является неявным. Может быть, из-за того, что типы примитивны и легко преобразовываются?

Итак, если вы переходите от примитивного типа, требующего меньшего количества бит (int - 32 бита), к примитивному типу, большее количество бит (double - 64 бита), Java знает, что делать это во время выполнения. Однако, если вы go из double (64 бит) в int (32 бит), вам, как разработчику, необходимо явно выполнить приведение, так как вы можете столкнуться с потерей информации здесь . Вот почему при возврате необходимо приведение типа (int); вы переходите от double к int, который может быть с потерями.

Пример кода:

public class Test {

    public int intMethod(int intArg) {
        return intArg;
    }

    public long longMethod(long longArg) {
        // will NOT compile, since we didn't explicitly cast to narrower int type (potentially lossy)
        return intMethod(longArg);
    }
}
public class Test {

    public int intMethod(int intArg) {
        return intArg;
    }

    public long longMethod(long longArg) {
        // WILL compile, since we explicitly cast to narrower int type. Also note we do not need to cast result, since result is going from int --> long, which is not lossy
        return intMethod((int) longArg); 
    }
}
public class Test {

    public int intMethod(int intArg) {
        // WILL compile. don't need to specify arg since its not lossy, but we do specify return type since its lossy
        return (int) longMethod(intArg);
    }

    public long longMethod(long longArg) {
        return 3L;
    }
}

Я всегда думал, что Java очень строго относится к явному определению типов

Примитивы - это гораздо более тонкая функция языка Java. Я думаю, что по типам вы неявно ссылаетесь на Object в Java. Вы правы, что Java намного строже в отношении приведения объектов. Если мы возьмем приведенный выше пример и будем использовать объекты Long и Integer, они не будут компилироваться и помечают предупреждение Cannot case java.lang.Long to java.lang.Integer;

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

public class Test {

    public Integer intMethod(Integer intArg) {
        return intArg;
    }

    public Long longMethod(Long longArg) {
        return intMethod((Integer) longArg);
    }
}
public class Test {

    public Integer intMethod(Integer intArg) {
        return longMethod((Long) intArg);
    }

    public Long longMethod(Long longArg) {
        return 3L;
    }
}

ps: стандартно ссылаться на все функции с тем же именем метода как перегруженные; не принято называть один метод "базовым" и говорить, что остальные перегружают этот метод

0 голосов
/ 19 июня 2020

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

...