Знаешь ли ты JAVA, %username%? Часть вторая

    JAVA Evil EditionВ начале января я написал пост с интересными тестовыми задачками по Java. Он вызвал достаточно большой интерес, интересные задачки еще остались, поэтому продолжим.

    Сразу отвечу на некоторые вопросы, ответы на которые могли затеряться в комментах.
    Во-первых, спрашивали, что почитать по теме. Очень рекомендую эту книжку. На русском не встречал, но читается она почему-то гораздо легче большинства книг по программированию, так что для большинства это не должно стать проблемой. Во-вторых, спрашивали где взять таких задачек. Тут что-то конкретное не посоветую, задачи из разных источников, в том числе некоторых нефришных наборов тестов, поэтому как вариант можно обратить внимание на источники, ссылки на которые есть в комментах к первой части статьи.

    Так получилось, что в данную часть попали более легкие задачи, так что результаты должны быть лучше. Итак, очередной тест под хабракатом (Осторожно, во второй половине ответы и пояснения).

    Тест


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

    Вопрос 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());
       }
    }
    

    Что будет выведено на консоль?
    1. Habr Tort
    2. Habr Habr
    3. Tort Habr
    4. Tort Tort
    5. ошибка компиляции

    Вопрос 2

    Что мы получим в результате выполнения:
    boolean b1 = false;
    boolean b2 = false;
    if (b2 != b1 = !b2) {
        System.out.println("YES");
    }
    else {
        System.out.println("NO");
    }
    

    Варианты:
    1. ошибку компиляции
    2. ошибку времени выполнения
    3. будет выведено YES
    4. будет выведено NO
    5. ничего не будет выведено

    Вопрос 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);
       }
    }

    В результате будет выведено:
    1. new old
    2. old new
    3. new new
    4. old old
    5. код не скомпилируется

    Вопрос 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); }
    }

    Что будет выведено?
    1. А 4
    2. A A
    3. 0 4
    4. 4 4
    5. другой ответ

    Вопрос 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();
        }
    }

    В результате его выполнения мы получим:
    1. In Pooler
    2. In Threader
    3. код не скомпилируется
    4. ошибку времени выполнения

    Вопрос 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();
    }

    Варианты ответа (ВОЗМОЖНО НЕСКОЛЬКО ПРАВИЛЬНЫХ ОТВЕТОВ):
    1. будет выведено «habr»
    2. Из класса Habr нельзя получить доступ к значению VALUE
    3. Код будет работоспособным, только если удалить VALUE в одном из интерфейсов
    4. код не скомпилируется

    Вопрос 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?
    1. да
    2. нет


    Внимание — Ответы



    Итак, правильные ответы такие:
    1. 4
    2. 1
    3. 2
    4. 3
    5. 1
    6. 1
    7. 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 присвоится новое значение.

    Спасибо всем за внимание. Вероятно, в данном формате писать больше не буду, планирую переходить к рассмотрению отдельных малоосвещенных в книгах тем.
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 28

      +3
      Ответил только на второй. Нет, Java я не знаю.
        +5
        К большому счастью для окружающих я не босс. Иначе бы с радостью уволил за применение кода из этих примеров.

        С одной стороны — хорошо наизусть помнить все подобные трюки, с другой стороны есть компилятор, IDE и тесты.
          +8
          А кто предлагает ТАК писать? Я бы и сам уволил за такое ))

          Данный топик скорее just for fun чем для использования в реальной работе. Хотя, как я писал в первой статье, практически весь экзамен Oracle certified professional java programmer состоит из таких заданий, так что иногда такие знания полезны.
            +1
            Да, готовились — знаем. Просто нужен дисклэймер, а то мало ли кто чего подумает :)

            Есть еще «Java Puzzlers» — такого же рода книжка, только еще более забавная.
              0
              Там вообще пипец Т_т «cafebabe» =)
              +4
              Я вот уже на большую часть не могу правильно ответить, с одной стороны из-за невнимательности — лень вчитываться долго, а с другой стороны — всё позабывал, хотя сдавал на SCJP всего полгода назад. Ненужные знания вообще быстро забываются, а это по большей части не особо нужные знания, хотя ими можно классно повыпендриваться :)
            +2
            Если кого-то интересуют такие вот штуки, советую почитать «Java Puzzlers».
            www.javapuzzlers.com/
            Впрочем, уж насколько я люблю всякие необычные штуки, но все эти знания уж слишком непрактичны.
              0
              а не было бы интересно в топике только вопросы оставить, а мы бы тут в комментах отвечали, не? а то никакой интриги
                0
                Учиться, учиться и еще раз учиться… Правда, в этот раз получше получилось.
                  0
                  Я знаю Java плохонько, да и три года на ней ничего не писал. Да и в документацию лезть было облом. Так что — 3/7.
                    +2
                    Хм, на мой вкус прошлый раз было намного сложнее — этот тест 6/7.
                    Тока с (b2 != b1 = !b2) не угадал с приоритетом.
                      +1
                      Мнемоника для запоминания приоритета: while ((line = in.readLine()) != null)
                        +1
                        Тут просто ) Достаточно включить логику.
                        Сам посуди: когда ты пишешь код a =…, ожидается, что все, что написано справа, присвоится переменной a. Следовательно, присваивание всегда идет последним. А, стало быть, присвоить что-либо выражению нельзя, т.к. это не L-value.
                          0
                          Я ожидал какого-то подвоха.
                            +1
                            В Java этого, к счастью, меньше, чем, скажем, в Си. Все-таки спецификация языка очень строгая.
                      • UFO just landed and posted this here
                          +1
                          Почитайте Джоэля Спольски «О программировании». Вот там действительно о программировании с юмором.
                          +3
                          Проверка того на сколько хорошо компилятор Java скопировался в твой мозг :)
                            0
                            До теста (даже после первого топика) я был уверен, что хоть чуть-чуть знаю Java.
                            Сейчас ответил только на 2 вопроса и понял, что Java не знаю.
                              +1
                              Правильно ответил на 2 вопроса. Еще раз убедился, что нужно писать код максимально просто и понятно, а за подобные извращения жестко бить по рукам. :-)
                                +1
                                Тест явно проще предыдущего. 5/7. На знал тонкостей Thread'ов и не ожидал такого ответа в вопросе про интерфейсы. Как показало последующее расследование — в интерфейсах все поля являются финальными; это меня и запутало :-)
                                  0
                                  Такие статьи лишь подтверждают слабую интуитивность кода на Java.
                                    0
                                    интуиция — одна из тех способностей, которая подвержена развитию
                                  0
                                  6 из 7 — на 5-й неверный ответ дал )
                                  Надо еще раз исходники основный классов Java полистать )
                                    0
                                    3/7
                                      0
                                      > Как правило, множественные операции присваивания рассчитываются справа налево. Но индексы — исключение. В данном случае сначала будет вычислен индекс (arr[0]), и только потом переменной i присвоится новое значение.

                                      Действительно ли тут «исключение»? Может, просто у операции взятия индекса приоритет выше, чем у присваивания, поэтому она выполняется первой?
                                        0
                                        Бинго — 7 из 7 правильно :-)

                                        Only users with full accounts can post comments. Log in, please.