От примера ниже взрывается ^_^ голова. Объясните, пожалуйста, хоть на пальцах, почему в строчке //(?!)
не выбрасывается исключение ClassCastExeption??? Там стоит чёртово (T)
, где T=String, а значит там (String) new Integer(42)
, что должно давать исключение... JVM должна привести тип, но почему-то она этого не делает. Дальше -- интереснее. Мы смотрим тип t
и видим Integer
, что действительно является реальным типом t
. Но факт того, что мы вывели тип, показывает: исключение не создалось. Потом программа выбрасывает исключение там, где была вызвана функция A.<String>f()
, что ну просто до возгорания меня доводит... Объясните, что творится в этом чёртовом куске кода?! Я думаю вам будут благодарны миллионы юзеров, кто потом увидит ответ на этот вопрос!
Кстати! Помнится мне, Bruce Eckel учит тому, что существует некоторый декомпилятор javap, он сам пишет код программы то ли из байт-кода, то ли из .class... Не изучал. Если кто сведущ в этом, попробуйте заставить декомпилятор построить этот чёртов проклятый кусок кода, может он покажет, что там происходит на самом деле, и почему?
public class A {
public static <T> T f() {
T t = (T) new Integer(42); // (?!) ЧТО ЧЁРТ ВОЗЬМИ ЗДЕСЬ ВООБЩЕ ПРОИСХОДИТ?!
System.out.println(t.getClass());
return t;
}
public static void main(String[] args) {
System.out.println(A.<String>f()); // как здесь возможно исключение? ахаха, вы серьёзно?
}
}
Новое:
Итак, друзья. Самым частым ответом стало: метод f()
-- это обычный метод, который компильнулся как обычный, но с учётом того, что результаты его вызовов приводятся к типу, передаваемому как <NameOfTheClass>
. Вроде бы уже всё хорошо, и можно принять такой ответ, но если бы оно правда так работало в Java... Дело в том, что можно в main(String[])
попытаться вызвать кое что ещё более взрывное:
System.out.println(A.<Double>f());
Удивительно, но в консоли выйдет:
//output:
class java.lang.Integer
42
Так вот если у нас невидимо это выглядит так:
System.out.println((Double)A.<Double>f());
То попробуйте это компильнуть -- выйдет ошибка ахахахха, от куда вывод, что всё таки работает оно не так...
Мои догадки на этот счёт таковы, что метод println()
перегружен, и в случае A.<String>f()
он выбирал версию println(String arg)
, а в случае A.<Double>f()
он понимал, что Double
версии нет и ставил println(Object arg)
из-за чего, для нашего (Object)Integer(42)
вызывалось .toString()
и выводилось 42.
Для меня здесь странно то, что почему-то в первом случае, когда мы вызывали A.<String>f()
он, как многие здесь считают вызывал это так (String)A.<String>f()
, от куда ставил версию println(String arg)
, а во второй раз, при вызове A.<Double>f()
он не попытался сделать так (Double)A.<Double>f()
, он сразу выбрал println(Object arg)
...
Хотите сказать, что вся технология такая умная, что если возврат параметризованной/обобщённой функции проверяется на существование как аргумент в перегруженном методе println()
с успехом, то приведение не вызывается [например String
версия println()
существует, ага, приведём выходной Object
от A.f()
к указанному String
], а если перегруженной версии с указанным в типе-параметре для A.f()
классом версии println()
нет, то он (компилятор) оставляет версию println(Object)
и решает не пытаться даже сделать (Object)(Double)A.<Double>f()
, где сам результат f()
есть Object
???
В общем друзья, помогите разобраться с этим вопросом, я вас умоляю! Голова кругом, вы все в сотни раз поумнее меня будете :_(
Итого: Я хочу объяснения принципа работы параметризованных методов (классов), такое объяснение, чтобы был точно понятен алгоритм действий компилятора для создаваемого кода. Если какие-то мои догадки на счёт действий компилятора верны, то я хочу их (моих догадок) подтверждения и очень желательно (!) с ссылкой на какие-нибудь документационные файлы!
Это байткод main
при вызове A.<Double>f()
public static void main(java.lang.String[]);
Code:
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: invokestatic #7 // Method f:()Ljava/lang/Object;
6: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
9: return
А это байткод main
при вызове A<String>.f()
public static void main(java.lang.String[]);
Code:
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: invokestatic #7 // Method f:()Ljava/lang/Object;
6: checkcast #8 // class java/lang/String
9: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: return
Обратите внимание на строку invokevirtual
, в первом случае вызывается метод с сигнатурой println(Object o)
, во втором - println(String s)
В случае с Double
после стирания типов метод f()
возвращает Object
, метода println
для Double
нету (да, можно анбоксить Double
и вызвать println
для примитива double
, но приоритет отдаётся выбору подходящего метода), поэтому выбирается метод для Object
, внутри спокойно вызывается String.valueOf
и всё хорошо.
В случае со String
есть метод println
для String
и компилятор решает, что нужно вызывать именно его, поэтому там есть каст.
Я обернул System.out.println
в свой println
для 3 типов:
static void println(Double d){
System.out.println(String.valueOf(d));
}
static void println(String s){
System.out.println(String.valueOf(s));
}
static void println(Object o){
System.out.println(String.valueOf(o));
}
И теперь вы уже догадались что будет при вызове?
public static void main(String[] args) {
println(Main.<Double>f());
}
Компилятор увидит, что есть перегруженный println
для Double
и поставит каст (и тут как раз ошибка будет):
public static void main(java.lang.String[]);
Code:
0: invokestatic #7 // Method f:()Ljava/lang/Object;
3: checkcast #8 // class java/lang/Double
6: invokestatic #9 // Method println:(Ljava/lang/Double;)V
9: return
Типов у генериков не существует в рантайме. Это просто проверки при компиляции.
public static <T> T f() {
public static Object T f() {
T t = (T) new Integer(42); // (?!) ЧТО ЧЁРТ ВОЗЬМИ ЗДЕСЬ ВООБЩЕ ПРОИСХОДИТ?!
Object t = (Object) new Integer(42); // всё хорошо
return t;
По-прежнему всё хорошо. Object.
System.out.println(A.<String>f()); // как здесь возможно исключение? ахаха, вы серьёзно?
System.out.println((String)f());
Упс.. Там не String. Вот и исключение.
Получается такая штука:
(String) (Object) new Integer(42);
^^^^^^^^------------------ Integer - наследник Object
^^^^^^^^--------------------------- String - наследник Object
Ошибки при компиляции нет, а при выполнении первое преобразование оказывается в f
и оно валидно, а вот второе - уже в main
, где оно и валится.
Если что то непонятно, всегда можно посмотреть bytecode
:
public class ru.izebit.A {
public ru.izebit.A();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static <T> T f();
Code:
0: new #2 // class java/lang/Integer
3: dup
4: bipush 42
6: invokespecial #3 // Method java/lang/Integer."<init>":(I)V
9: astore_0
10: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
13: aload_0
14: invokevirtual #5 // Method java/lang/Object.getClass:()Ljava/lang/Class;
17: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
20: aload_0
21: areturn
public static void main(java.lang.String[]);
Code:
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: invokestatic #7 // Method f:()Ljava/lang/Object;
6: checkcast #8 // class java/lang/String
9: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: return
}
Из кода видно, что в методе f()
не происходит кастования к String
(судя по всему тип ссылки к которой присваивается новый объект будет Object). Но в момент возвращения этого объекта из параметризированного метода и происходит тот самый каст из за чего и возникает исключение.
Дженериком может быть только не примитивный тип, т.е. супер тип самого простого дженерика - Object
. Таким образом
//позволяет любые непримитивы
public class MyClass<T> {
T t;
public MyClass(T tArg) {
t = tArg;
}
}
вышеприведённый класс будет допускать внутри себя только вызывать у переменной t
только методы класса Object
. Т.е. переменная t
для всего кода в классе MyClass
будет иметь тип Object
. Это и объясняет отсутствие ClassCastException
при приведении числа к объекту.
А вот если более точно указать тип дженерика, то ошибку компиляции мы получим (и не сможет дойти до ошибки времени исполнения):
//можно использовать только String или его предков
public class MyClass<? super String> {
T t;
public MyClass(T tArg) {
t = new Integer(42); //ОШИБКА компиляции - переменной t можно присваивать String или Object
}
}
или так, что, правда, бессмысленно, т.к. String
- финальный класс (т.е. "стерильный" - расширять его нельзя и потомков у него быть не может)
//можно использовать только String и его потомков
public class MyClass<T extends String> {
T t;
public MyClass(T tArg) {
t = new Integer(42); //ОШИБКА компиляции - переменной t можно присваивать String или его наследников
}
}
Правильная работа вывода типа объясняется иначе:
public static <T> T f() {
T t = (T) new Integer(42); //Всё ОК, т.к. внутри метода T - всегда Object
System.out.println(t.getClass()); //выведет конкретный тип (String, Integer etc)
}
методы в Java
виртуальны. Т.е. будет вызван не метод класса T
(т.е. Object
в рамках метода), а метод конкретной реализации класса Object
которая находится в памяти. А это уже String
, Integer
etc.
Итоговая ошибка времени исполнения, видимо, аналогична примеру из доки и код можно аналогично переписать так:
System.out.println((String) A.<String>f());
Т.е. вы, указав что метод должен вернуть строку вызываете метод System.out.println(String arg)
, передавая в него ссылку типа Object
, коя на самом деле хранит объект типа Integer
. И вот в момент приведения конкретного числа, хранящегося в памяти к строке и возникает исключение
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Как ввести целые числа как аргументы командной строки, подсчитать их сум- мы (произведения) и вывести результат на консоль ? если можно напишите...
кто что использует для отображения кнопок на странице (без тэга form)