Интересное поведение возвращает значения побитовых операторов и сдвиг бит в Java

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

public class Weirdness {

    private final static int constant = 3;

    private static int notConstant = 3;

    public void stuff() {
        byte a = 0b1 << 3;
        byte b = 0b1 << (int) 3;
        byte c = 0b1 << constant;
        byte d = 0b1 << notConstant; //error
        byte e = 0b1 << getAnInt(); //error
        byte f = 0b1 << getAFinalInt(); //error
        int i = 3;
        byte g = 0b1 << i; //error
        final int j = 3;
        byte h = 0b1 << j;
    }

    public static int getAnInt() {
        return 3;
    }

    public static final int getAFinalInt() {
        return 3;
    }

}

a, b, c и h не дают ошибок компиляции; Но d, e, f и g do. Компилятор просит явно указать byte или объявить последние переменные как int. Я заметил аналогичное поведение с bitwize & и | тоже.

Может кто-нибудь объяснить, что здесь происходит? Какая магия для компилятора работает для a, b, c и h для работы?

РЕДАКТИРОВАТЬ: Или как это не совсем дубликат

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

И так как я ищу теоретический ответ (потому что я уже понимаю, что могу сделать свой код скомпилированным путем кастинга), как сдвиг и другие операции битвизации определяют их возвращаемое значение, я считаю, что этот вопрос может дополнять смещение Java-битов с целыми числами и байтами и добавление более интересной информации в StackOverflow.

Ответ 1

Краткий ответ:

Компилятор знает, что значение final или literal не может измениться и может безопасно неявно cast constant и 3 до byte с заданными значениями.

Не конечные значения не могут быть аргументированы таким же образом.

Явный лучше, чем неявный.

Это один из примеров того, почему я ненавижу implicit все, что связано с написанием программ.

Упражнение:

измените constant или литерал 3 на то, что не поместится в byte, и посмотрите, как он жалуется

Ответ 2

Из JLS: Тип выражения shift - это продвинутый тип левого операнда.

Продвинутый тип byte равен int - и что причина, по которой в большинстве случаев приходится приводить результат следующим образом:

byte e = (byte) (0b1 << getAnInt()); 

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

private final static int constant = 3;

в

private final static int constant = 1000;

вы получите ошибку компиляции:

byte c = 0b1 << constant;

.

Операция shift может создать целое число, которое имеет большее значение, чем байт в левой части задания, который может удерживать - который запускает ошибку времени компиляции и заставляет нас передавать байт, чтобы захватить только наименее значимые 8 бит.

Итак, почему в первых 3 строках нам не нужно приводить байт?
Компилятор признал, что мы используем константу (или окончательную) и, таким образом, "знаем", что это значение не может быть изменено позже, поэтому оно позволяет сужение примитивного преобразования для присвоения байту - с левой стороны:

byte c = 0b1 << 3;