В начале января я написал пост с интересными тестовыми задачками по Java. Он вызвал достаточно большой интерес, интересные задачки еще остались, поэтому продолжим.
Сразу отвечу на некоторые вопросы, ответы на которые могли затеряться в комментах.
Во-первых, спрашивали, что почитать по теме. Очень рекомендую эту книжку. На русском не встречал, но читается она почему-то гораздо легче большинства книг по программированию, так что для большинства это не должно стать проблемой. Во-вторых, спрашивали где взять таких задачек. Тут что-то конкретное не посоветую, задачи из разных источников, в том числе некоторых нефришных наборов тестов, поэтому как вариант можно обратить внимание на источники, ссылки на которые есть в комментах к первой части статьи.
Так получилось, что в данную часть попали более легкие задачи, так что результаты должны быть лучше. Итак, очередной тест под хабракатом (Осторожно, во второй половине ответы и пояснения).
Правильный ответ только один, если не указано обратное.
Имеется следующий код:
Что будет выведено на консоль?
Что мы получим в результате выполнения:
Варианты:
В результате будет выведено:
Имеется следующий код:
Что будет выведено?
Имеется следующий код:
В результате его выполнения мы получим:
Что можно сказать о данном коде?
Варианты ответа (ВОЗМОЖНО НЕСКОЛЬКО ПРАВИЛЬНЫХ ОТВЕТОВ):
Имеется следующий код:
Выбросит ли данный код NullPointerException?
Итак, правильные ответы такие:
Итак,
Данный код выведет Tort Tort. Вроде как должен работать полиморфизм, и, как известно, выбор осуществляется по типу объекта, на который ссылается ссылка, а не по типу самой ссылки. Но в джаве есть переопределенные методы. И всё. Переопределенных полей либо конструкторов просто не существует. То есть реализация метода всегда выбирается в зависимости от объекта, на который мы ссылаемся. Поля класса, наоборот, выбираются исходя из типа ссылки, и переопределение тут не работает. Поэтому мы и получаем такой результат.
Тут мы получим ошибку компиляции. Всё просто, вопрос только в приоритете операторов. != имеет более высокий приоритет, соответственно мы получим false = !b2, то есть попытку присвоить значение константе.
Аргументы методов вычисляются слева направо и то, что во втором аргументе мы присваиваем s новое значение уже ничего не меняет, так как первый аргумент уже вычислен. Если вычисление одного из аргументов заканчивается неудачно, все остальные аргументы расчитываться не будут вообще.
Метод print() переопределен в классе B. Соответственно, выбор метода осуществляется по типу актуального объекта. Когда создается объект класса B, сначала вызывается конструктор A, который вызывает метод print(). Поскольку мы создаем объект класса B, вызывается версия print() из класса B. В это время переменной i еще не присвоено значение, поэтому выводится значение по умолчанию (0). Ну и в последствии выводится проинициализированное значение i. В результате получаем ответ 0 4.
Известно, что при создании объектов класса Thread в конструктор можно передавать Runnable объект, метод run() которого будет выполняться в отдельном потоке. Поэтому ожидаемый ответ — вывод «In Threader». Причина нестандартного поведения — это метод run() в классе Pooler. Переопределив метод run(), мы тем самым потеряли дефолтное поведение класса Thread.
Данный код абсолютно работоспособен. То, что в обоих интерфейсах присутствует одинаковый метод m() не создает проблем, так как реализация в любом случае будет одна. Сложнее с полем VALUE. Мы не можем напрямую обращаться к нему, так как возникнет неоднозначность. Но это можно сделать, например, так:
( (I1) habr).VALUE;
После приведения объекта к одному из интерфейсов, мы получаем доступ к полям, которые ранее не могли использовать.
Имеется следующий код:
NullPointerException в данном случае не возникнет, так как любое действие с массивом, в том числе проверка существования объекта происходит только после того, как будет полностью рассчитан индекс элемента.
Ситуация становится интереснее, если мы присваиваем что-либо элементу массива. Например дан код:
Как правило, множественные операции присваивания рассчитываются справа налево. Но индексы — исключение. В данном случае сначала будет вычислен индекс (arr[0]), и только потом переменной i присвоится новое значение.
Спасибо всем за внимание. Вероятно, в данном формате писать больше не буду, планирую переходить к рассмотрению отдельных малоосвещенных в книгах тем.
Сразу отвечу на некоторые вопросы, ответы на которые могли затеряться в комментах.
Во-первых, спрашивали, что почитать по теме. Очень рекомендую эту книжку. На русском не встречал, но читается она почему-то гораздо легче большинства книг по программированию, так что для большинства это не должно стать проблемой. Во-вторых, спрашивали где взять таких задачек. Тут что-то конкретное не посоветую, задачи из разных источников, в том числе некоторых нефришных наборов тестов, поэтому как вариант можно обратить внимание на источники, ссылки на которые есть в комментах к первой части статьи.
Так получилось, что в данную часть попали более легкие задачи, так что результаты должны быть лучше. Итак, очередной тест под хабракатом (Осторожно, во второй половине ответы и пояснения).
Тест
Правильный ответ только один, если не указано обратное.
Вопрос 1
Имеется следующий код:
class Super {
public String name = "Tort";
public String getName() {
return name;
}
}
class Sub extends Super {
public String name = "Habr";
public static void main(String[] args) {
Super s = new Sub();
System.out.println(s.name + " " + s.getName());
}
}
Что будет выведено на консоль?
- Habr Tort
- Habr Habr
- Tort Habr
- Tort Tort
- ошибка компиляции
Вопрос 2
Что мы получим в результате выполнения:
boolean b1 = false;
boolean b2 = false;
if (b2 != b1 = !b2) {
System.out.println("YES");
}
else {
System.out.println("NO");
}
Варианты:
- ошибку компиляции
- ошибку времени выполнения
- будет выведено YES
- будет выведено NO
- ничего не будет выведено
Вопрос 3
class Test{
public static void main(String[] args) {
String s = "old";
print(s, s = "new");
}
static void print(String s1, String s2) {
System.out.println(s1 +" "+ s2);
}
}
В результате будет выведено:
- new old
- old new
- new new
- old old
- код не скомпилируется
Вопрос 4
Имеется следующий код:
class A {
A() { print(); }
void print() { System.out.println("A"); }
}
class B extends A {
int i = Math.round(3.5f);
public static void main(String[] args) {
A a = new B();
a.print();
}
void print() { System.out.println(i); }
}
Что будет выведено?
- А 4
- A A
- 0 4
- 4 4
- другой ответ
Вопрос 5
Имеется следующий код:
class Threader extends Thread {
public void run() {
System.out.println("In Threader");
}
}
class Pooler extends Thread {
public Pooler(){ }
public Pooler(Runnable r) {
super( r );
}
public void run() {
System.out.println("In Pooler");
}
}
public class TestClass {
public static void main(String[] args) {
Threader t = new Threader();
Thread h = new Pooler(t);
h.start();
}
}
В результате его выполнения мы получим:
- In Pooler
- In Threader
- код не скомпилируется
- ошибку времени выполнения
Вопрос 6
Что можно сказать о данном коде?
class Habr implements I1, I2{
public void m() { System.out.println("habr"); }
public static void main(String[] args) {
Habr habr = new Habr();
habr.m();
}
}
interface I1{
int VALUE = 1;
void m();
}
interface I2{
int VALUE = 2;
void m();
}
Варианты ответа (ВОЗМОЖНО НЕСКОЛЬКО ПРАВИЛЬНЫХ ОТВЕТОВ):
- будет выведено «habr»
- Из класса Habr нельзя получить доступ к значению VALUE
- Код будет работоспособным, только если удалить VALUE в одном из интерфейсов
- код не скомпилируется
Вопрос 7
Имеется следующий код:
class Test{
public static void main(String[] args) throws Exception {
int[] a = null;
int i = a [ m() ];
}
public static int m() throws Exception {
throw new Exception("Another Exception");
}
}
Выбросит ли данный код NullPointerException?
- да
- нет
Внимание — Ответы
Итак, правильные ответы такие:
- 4
- 1
- 2
- 3
- 1
- 1
- 2
Пояснения
Вопрос 1
Итак,
class Super {
public String name = "Tort";
public String getName() {
return name;
}
}
class Sub extends Super {
public String name = "Habr";
public static void main(String[] args) {
Super s = new Sub();
System.out.println(s.name + " " + s.getName());
}
}
Данный код выведет Tort Tort. Вроде как должен работать полиморфизм, и, как известно, выбор осуществляется по типу объекта, на который ссылается ссылка, а не по типу самой ссылки. Но в джаве есть переопределенные методы. И всё. Переопределенных полей либо конструкторов просто не существует. То есть реализация метода всегда выбирается в зависимости от объекта, на который мы ссылаемся. Поля класса, наоборот, выбираются исходя из типа ссылки, и переопределение тут не работает. Поэтому мы и получаем такой результат.
Вопрос 2
boolean b1 = false;
boolean b2 = false;
if (b2 != b1 = !b2) {
System.out.println("YES");
}
else {
System.out.println("NO");
}
Тут мы получим ошибку компиляции. Всё просто, вопрос только в приоритете операторов. != имеет более высокий приоритет, соответственно мы получим false = !b2, то есть попытку присвоить значение константе.
Вопрос 3
class Test{
public static void main(String[] args) {
String s = "old";
print(s, s = "new");
}
static void print(String s1, String s2) {
System.out.println(s1 +" "+ s2);
}
}
Аргументы методов вычисляются слева направо и то, что во втором аргументе мы присваиваем s новое значение уже ничего не меняет, так как первый аргумент уже вычислен. Если вычисление одного из аргументов заканчивается неудачно, все остальные аргументы расчитываться не будут вообще.
Вопрос 4
class A {
A() { print(); }
void print() { System.out.println("A"); }
}
class B extends A {
int i = Math.round(3.5f);
public static void main(String[] args) {
A a = new B();
a.print();
}
void print() { System.out.println(i); }
}
Метод print() переопределен в классе B. Соответственно, выбор метода осуществляется по типу актуального объекта. Когда создается объект класса B, сначала вызывается конструктор A, который вызывает метод print(). Поскольку мы создаем объект класса B, вызывается версия print() из класса B. В это время переменной i еще не присвоено значение, поэтому выводится значение по умолчанию (0). Ну и в последствии выводится проинициализированное значение i. В результате получаем ответ 0 4.
Вопрос 5
class Threader extends Thread {
public void run() {
System.out.println("In Threader");
}
}
class Pooler extends Thread {
public Pooler(){ }
public Pooler(Runnable r) {
super( r );
}
public void run() {
System.out.println("In Pooler");
}
}
public class TestClass {
public static void main(String[] args) {
Threader t = new Threader();
Thread h = new Pooler(t);
h.start();
}
}
Известно, что при создании объектов класса Thread в конструктор можно передавать Runnable объект, метод run() которого будет выполняться в отдельном потоке. Поэтому ожидаемый ответ — вывод «In Threader». Причина нестандартного поведения — это метод run() в классе Pooler. Переопределив метод run(), мы тем самым потеряли дефолтное поведение класса Thread.
Вопрос 6
class Habr implements I1, I2{
public void m() { System.out.println("habr"); }
public static void main(String[] args) {
Habr habr = new Habr();
habr.m();
}
}
interface I1{
int VALUE = 1;
void m();
}
interface I2{
int VALUE = 2;
void m();
}
Данный код абсолютно работоспособен. То, что в обоих интерфейсах присутствует одинаковый метод m() не создает проблем, так как реализация в любом случае будет одна. Сложнее с полем VALUE. Мы не можем напрямую обращаться к нему, так как возникнет неоднозначность. Но это можно сделать, например, так:
( (I1) habr).VALUE;
После приведения объекта к одному из интерфейсов, мы получаем доступ к полям, которые ранее не могли использовать.
Вопрос 7
Имеется следующий код:
class Test{
public static void main(String[] args) throws Exception {
int[] a = null;
int i = a [ m() ];
}
public static int m() throws Exception {
throw new Exception("Another Exception");
}
}
NullPointerException в данном случае не возникнет, так как любое действие с массивом, в том числе проверка существования объекта происходит только после того, как будет полностью рассчитан индекс элемента.
Ситуация становится интереснее, если мы присваиваем что-либо элементу массива. Например дан код:
int i = 0 ;
int[] arr = {1, 2} ;
arr[i] = i = 6 ;
Как правило, множественные операции присваивания рассчитываются справа налево. Но индексы — исключение. В данном случае сначала будет вычислен индекс (arr[0]), и только потом переменной i присвоится новое значение.
Спасибо всем за внимание. Вероятно, в данном формате писать больше не буду, планирую переходить к рассмотрению отдельных малоосвещенных в книгах тем.