Pull to refresh

Comments 56

UFO just landed and posted this here
Столько слов, а суть одна: reference type != object type :)
На самом деле, изначально я хотел написать только о сигнатуре в Object.java, про невозможность описать требуемое синтаксисом джавы, но все остальное — потребовалось для полноты картины.
Есть предположения — почему это не компилируется?

public class HelloWorld{
public static void main(String []args){
Class<HelloWorld> aClass = ((HelloWorld)null).class;
System.out.println(aClass);
}
}

(попробовал здесь www.compileonline.com/compile_java_online.php)

p.s. прощу прощения, но не тег source не помогает
Вы совершенно правы, я облажался. Такое не скомпилируется. Суть в том, что class — это совсем не статическое поле и даже не пытается им казаться. вызывать class у объекта нельзя. Поправил в посте, премного благодарен.
Недавно столкнулся с такой штукой, раньше не знал.
Объявляем
A obj = new A() { @Override public void someOverridenMethod() { ... } };
И получаем, что obj.getClass() нам вообще класс замыкания вернёт. И тогда надо дёргать getEnclosingClass()
Пару лет назад очень увлекались в проекте анонимными классами. А потом огребли=)
Интересно. Подробности расскажете?

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

Замыкание (closure) и вложение (enclosure) — разные же понятия, не?
С enum еще интересней:
enum E {
    first,
    second {
        public String toString() {
            return "not first";
        }
    }
}

E.first.getClass().isEnum() вернет true;
E.second.getClass().isEnum() вернет false!
Чуть выше уже говорили про getEnclosingClass() :)
И чего? :) Как нам это поможет отличить объект вложенного класса от enum константы?

enum E {
    first,
    second {
        public String toString() {
            return "not first";
        }
    };

    static class Helper { }
    
    public static final Helper helper = new Helper();
}


E.second.getClass().getEnclosingClass().isEnum() вернет true;
E.helper.getClass().getEnclosingClass().isEnum() вернет true.

Я хотел сказать, что проверять, является ли объект enum константой, через obj.getClass().isEnum() — неправильно.
А правильно будет obj instanceof Enum.
Красота, не знаю, куда впихнуть в пост, но примерчик возьму на заметку, спасибо;)
Автор написал уже что ошибся, a.class больше нету

UPD: Снято. Ответили выше, что ответили выше.
Автор, после достаточно интересного предыдущего поста пост про .getClass() — это даже не шаг назад, а какой-то прыжок со скалы вниз.
ИМХО, такое максимум тянет на мини-шпаргалку для осваивающих Java.
Вообще, про getClass я узнал ровно два года назад, т.ч. возможно это действительно прыжок со скалы;) Тем не менее многие мои знакомые удивлялись, что сигнатура в Object.java != реально проверяемая компилятором сигнатура. Возможно, привычка стараться все объяснять максимально подробно и угробила «короткий забавный факт», превратив его в пост-мини-шпаргалку-для-новичков. Буду учиться на ошибках, спасибо.
Никакого знака неравенства между сигнатурами там нет. Object — это корневой элемент в любой классовой иерархии, поэтому extends Object в сигнатуре можно не писать. Вы же не пишете в каждом вашем классе, что он extend Object, верно?
Как то, что любой класс по дефолту наследуется от Object связано с тем, что в этом самом Object написано
Class<?> getClass()
, а на самом деле там не совсем такой дженерик?
pastebin.com/nB8THEMy
Это равнозначные записи. После компиляции это будет одно и то-же. Первый и второй вариант — сокращенные формы записи третьего варианта, синтаксический сахар, как и то, что «любой класс по дефолту наследуется от Object» (вас не заставляют каждый раз явно прописывать extend Object). Не понимаю вашего недоумения.
Ещё раз могу повторить. Об «extends Object» речь не идёт вообще, а идёт о том, что если бы компилятор проверял сигнатуру, указанную в Object, то нельзя было бы написать Class<String> cls = String.class.
Как уже сказали, String.class это не поле. Оно возвращает ровно то что должно — дженерик с конкретным типом. Причем тут вообще сигнатура Object#getClass().
Да, извиняюсь. Нельзя было бы написать Class<? extends String> cls = "".getClass(); (а на самом деле можно).
Почему нельзя? Все снова сводится к моему комментарию, на который Вы ответили #6859374. Там именно такая сигнатура, которая должна быть, иначе бы #getClass() нельзя было заоверрайдить при наследовании. И компилятор проверяет именно такую сигнатуру, потому что String к Object апкастится отлично. Знака неравенства между сигнатурами нет.
Я все еще не понимаю, что здесь неочевидного.
иначе бы #getClass() нельзя было заоверрайдить при наследовании

Так и нельзя, он final.
И компилятор проверяет именно такую сигнатуру, потому что String к Object апкастится отлично.

Ну вот попробуйте определить свой метод, который возвращает Class<?>, например

public class Foo { public Class<?> getClass1() { return getClass(); } }

Тогда

Foo foo = new Foo(); Class<? extends Foo> cls1 = foo.getClass(); Class<? extends Foo> cls2 = foo.getClass1();

cls1 компилируется, а cls2 нет. Притом, что сигнатура getClass() (в Object.java) и getClass1() выглядит одинаково.
> Так и нельзя, он final.

А еще native, и особой магией все таки оверрайдится, что бы возвращать то, что возвращает. В вашем варианте сигнатуры это было бы невозможно.

> cls1 компилируется, а cls2 нет. Притом, что сигнатура getClass() (в Object.java) и getClass1() выглядит одинаково.

Если добавить final, разве не скомпилируется? Тут надо сесть и более детально разобраться, сдается мне сигнатура cls2 только на первый взгляд выглядит одинаково.
> Class result1 = a.getClass(); // Compilation error!

Ну так разумеется же

Class
Не понял вопрос, если это вопрос=)
Нет, это не вопрос. Я пытаюсь понять, в чем ваше недоумение :) Как по мне, все логично. См. мой коммент в ветке выше.
> Так что в Object.java написана одна сигнатура, а компилятор подставляет другую.
Чего это вдруг?
Сигнатура та же. Просто в джавадоке более детально указано что может быть возвращено, чем в дженерике в сигнатуре.

Так может каждый

 /**
 * return The actual result type is String.
 */
 public static <?> getSomething() {
     return "Hello World";
 }

P.S. А, понял о чем речь.
> на вопрос, почему не компилировался пример выше
Вопрос не в том был почему не компилируется Class<A> result1 = a.getClass(); , а в том почему компилируется Class<? extends A> result = a.getClass();
Здесь да, должен был быть cast, но он не нужен. Действительно, есть какая-то хитрость.
Что значит «не нужен». Обычный неявный каст, как здесь:

class Object1 { }
class Object2 extends Object1 { }

Object2 obj2 = new Object2();
Object1 obj1 = obj2;
Ну это просто upcast, приведение к классу-предку. Это совсем не то, и дженериков нет — что какбы намекает.
Так я и я про upcast — кем бы этот «а» не был в конкретной реализации, он отлично апкастится к A. А в жденерике у нас сказано, что подходит все А и его наследники. Почему же оно не должно компилится?
На этот вопрос как мне кажется Эккель отвечает в «Thinking in Java 4 edition» (к великому сожалению читал в переводе).
1. В главе про RTTI говорится что:
Если обычная ссылка на класс может быть связана с любым объектом Class, параметризованная ссылка может связываться только с объектами типа, указанного при объявлении.

Значит код:
Class<A> result1 = a.getClass(); //error

содержит ошибку — объектная ссылка a может ссылаться (пардон за каламбур) на объект класса-потомка A, что недопустимо. Соответственно, расширение параметра типа проблему решает:
Class<? extends A> result = a.getClass();//ok

2. А в главе про параметризацию дается объяснение (как мне кажется) всего этого мракобесия:
Параметризация в Java реализуется с применением стирания (erasure).

А это значит что код:
class A{}
class B{}
List<A> aList = new ArrayList<A>();
List<B> bList = new ArrayList<B>();
System.out.println(aList.getClass().equals(bList.getClass() )  );

выведет:
true
что говорит о том, что при использовании параметризации вся конкретная информация о типе утрачивается :( и по словам Эккеля это есть проблема Java. Автор указал на это:
The actual result type is Class<? extends |X|> where |X| is the erasure of the static type of the expression on which getClass is called.

но сидящие здесь гуру (никакого сарказма!), намекают на это настолько неявно и как само собой разумеющееся, что я занервничал и решил-таки побыть К.О. и попозориться немного
> при использовании параметризации вся конкретная информация о типе утрачивается
… в рантайме.
Но речь шла о compilation error.
Подтвержаю насчет runtime. Далее надеюсь не спороть чепухи. Насколько я понял в Java параметризация реализуется с применением стирания и это учитывается/влияет на компилятор. А вот результат стирания можно увидеть в runtime. И да, мой код совсем не к месту. К месту другой:
class SomeObject{
	public void someMeth(){
		System.out.println("Hello");
	}
}

class Controller<T>{
	private T mObj = null;
	public Controller(T obj){
		mObj = obj;
	}
	public void control(){
		mObj.someMeth();// <---- compilation error!
	} 
}

public class Parameterising {
	public static void main(String [] args){
		SomeObject someObj = new SomeObject();
		Controller<SomeObject> controller = new Controller<SomeObject>(someObj);
		controller.control();
	}
}

Из-за стирания параметра типа код внутри дженерика не знает о наличии метода someFn(). Могу предположить что подобная мантра:
Информация о параметрах типов недоступна внутри параметризованного кода
отрабатывает и в методе Object.getClass(). И только ограничения параметра типа
class Controller<T extends SomeObject>{...}
могут спасти ситуацию
Почему это надо спасать ситуацию. Я не представляю, как можно знать о типе , не указав от чего он наследуется. Получается какая-то динамика, сложно представить что должна проделать IDE, чтобы работало автодополнение для метода someMeth.
Все-таки это не имеет отношения к стиранию типа (хоть я и солидарен с Эккелем=))

Пример не скомпилируется не только в джаве, но и в любом другом языке. Дженерик — это обобщение (см. словарь) — это по определению «нечто, работающее с 'произвольным' типом». И крутизна как раз в том, что произвольный тип — это не обязательно Object.
Грубо говоря, вместо дженериков можно работать с Object'ами (в рантайме так и есть), но тогда компилятор и не будет проводить дополнительных проверок.

В вашем примере someObj — с точки зрения Controller \<T\> — объект некоего, заранее неизвестного, класса T. Причем Т — произвольно, никаких ограничений.

То, что Вы неявно просите — это чтобы каждое использование дженерика — в данном случае Controller\<SomeObject\> — накладывало ограничения на дженерик.

В частности, кто-то другой может написать
Controller<Object> controller = new Controller<Object>(new Object());


Вот откапитанил, так откапитанил;)
подобный код (с неизвестным типом) легко скомпилируется в с++, например. просто потому что реальный код класса появится только во время явного указания типа (то есть специализации), а пока класса нет, дженерик — просто кусок исходника.

в яве, из-за того что нет технической возможности иметь отдельно объявление и реализацию — получились вот такие, какие есть, дженерики
В C# реализованы Дженерики как надо, там такое будет работать (вроде нет)? В С++ шаблоны, которые довольно сложны в анализе для компиляторов. Ведь это нарушение правила — интерфейс должен быть известен в момент написания кода, в данном случае интерфейс становится известен только когда мы объявим объект. Выглядит это как костыль.
Не как костыль, а как template. Что собственно и было сделано. Дженерик — это просто удобное следствие.

В с# есть слово where, которое и помогает ему узнать какие методы есть у типа-параметра. Так что как в с++ — c# тоже не умеет
Вот этот самый where и есть тот самый интерфейс. В Java можно создать интерфейс и поместить в extends. Интерфейс это лишь описание набора методов без реализации.

Не нужно брать С++ за эталон, это не простой язык.
java и с# — дженерики. хотя принципиальные отличия у них есть
c++ — тру-темплейты. собственно поэтому я и решил прокомментировать «В C# реализованы Дженерики как надо, там такое будет работать»
UFO just landed and posted this here
В который раз поражаюсь, как много можно придумать вопросов для собеседования, которые абсолютно никак не показывают навык программиста.
А много вы знаете вопросов, которые показывают навык программиста?

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

И вот этот конкретный случай лично я считаю прекрасным поводом для разговора на джуниора-мидла
Ex.:
1) поговорили про дженерики и коллекции
2) А какие вы знаете еще примеры дженериков? Если отвечает — прекрасно
3) если задумался — подкидываем удочку: а как насчет Class? Если уверенно отвечает, то фиг с ним, не судьба
4) Если удивился — стимулируем подумать: «А как Вы думаете, зачем?» Намекаем на возвращаемый аргумент метода, просим написать метод, возвращающий произвольный тип
5) Ну и наконец — а что по-вашему должен возвращать getClass? А как это написать? Ну и говорим, что это особенный метод

Я вообще считаю, что добиться на собеседовании, чтобы кандидат рассуждал на тему того, чего не знает — это как раз и есть один из способов оценить навык программиста и опыт. Конкретные вопросы — это скучно. Они быстро устраревают и отлично гуглятся (хотя я бы не сказал, что умение подготовиться — это плохо;), но это другая история)
Если спрашивать лично мое мнение, то я считаю, что человек от вопросов, с которыми не сталкивался (а редко кто сталкивается с getClass() в работе, а не а разработке собственных ORM, хаках и пр.), может впасть в панику и в худшем случае наговорить глупости, а в лучшем просто почувствовать себя дерьмом и бестолочью (это, к примеру, после дюжины лет опыта и десятка завершенных больших проектов). Конечно, без каверзных вопросов сложно понять уровень и адекватность, но ответы на них могут и быть просто заучены. Думаю, хоть сколько-либо достоверно уровень показать может лишь тестовое задание, или даже испытательный срок.
Но я еще раз повторюсь — это только мое мнение, причем, я очень наивен в этом вопросе, а когда пару раз нанимал программистов, в основном изучал их предыдущие проекты и просил рассказать, какую они роль там выполняли, с какими проблемами сталкивались. «Пытки» же оставлял другим собеседователям :)
Возращаемый тип не входит в сигнатуру метода, там только название метода и передаваемые параметры.
Да, сам влетал в это. Прямое следствие негибких явовских генериков. Вот и пришлось такой костыль прикручивать. Таже абсолютно тема, что и якобы нативные методы в JDK, которые просто на уровне JVM подменяются на платформозависимые реализации, а никакого JNI там нет.
Sign up to leave a comment.

Articles