Привет! Мы тут собрали тусовку одних из самых крутых русскоязычных Java-практиков и попросили их дать по задаче, чтобы вы сломали зубы, мозг и бились об стену, пытаясь понять, как это работает. Собственно, мы поспорили на бутылку Хеннеси, что за 12 часов после публикации никто не пришлёт все правильные ответы. Я уверен, что кто-то сможет. Поэтому если вы это сделаете первым – с меня бутылка.

Первая задача простая, она от телезрителя Николая Гарбузова, специалиста по скалкам, любящего рекурсию, паттерн-матчинг и магию компиляции:
Скомпилируется ли следующий аспект AJC компилятором?
Если да — то что он выведет на консоль при компиляции?

public aspect QuizAspect {
    public static int count(int i) {
        return i++;
    }

    before (int n) : execution(public int QuizAspect.count(int)) 
            && args(n) && if(QuizAspect.count(1)>1) {
        System.out.println("QuizAspect " + n);
    }
}


Пока просто, правда?

Вторая задача от Владимира Ситникова (NetCracker), грязного извращенца во всём, что касается регулярных выражений. Этот нехороший человек даже анонс своего доклада на JPoint написал с их помощью.

В чём подвох удалять Java-комментарии таким выражением? Укажите 3 причины, почему так делать нельзя. (считаем, что исходник написан нормальными символами) —
Pattern.compile("/\\*(?:[^*]|\\*[^/])*\\*/")


И сразу вторая задача — можно ли написать «hello world» на java без единого пробела?


Третью задачу прислал телезритель Николай Алименков из клуба анонимных разработчиков. 10 лет он пилит свои масштабируемые системы, а к нам пришёл отдохнуть, поэтому задача одна из самых простых:

Есть 2 Spring контекста:

1. a.xml с бином

<util:list id="myList">        
    <value>3</value>        
    <value>4</value>    
</util:list>


2. b.xml с бином

<util:list id="myList">
    <value>6</value>
</util:list>


Что напечатает такой фрагмент кода:

System.out.println(new ClassPathXmlApplicationContext("a.xml", "b.xml").getBean("myList"));


И как можно заставить его бросить ошибку, не изменяя логику работы кода?


Четвёртая задача предоставлена Никитой Сальников-Тарновским, конкретно упоровшимся по хардкору и написавшим инструмент для поиска утечек памяти – Plumbr. Писал не один, конечно, но прочитать полученный код может только он. Долбанный оптимизатор.

Ниже приведены 2 программы. Каждая из них пытается аллоцировать суммарно памяти больше размера хипа. Но одна из них выкидывает java.lang.OutOfMemoryError, а вторая нет. Почему?

public class OOM1 {
    private static final int SIZE = (int) (Runtime.getRuntime().maxMemory() * 0.55);

    public static void main(String[] args) {
        {
            byte[] bytes = new byte[SIZE];
            System.out.println(bytes.length);
        }

        byte[] bytes1 = new byte[SIZE];
        System.out.println(bytes1.length);

        System.out.println("I allocated memory successfully");
    }
}

public class OOM2 {
    private static final int SIZE = (int) (Runtime.getRuntime().maxMemory() * 0.35);

    public static void main(String[] args) {
        {
            byte[] bytes = new byte[SIZE];
            System.out.println(bytes.length);
        }

        byte[] bytes1 = new byte[SIZE];
        System.out.println(bytes1.length);

        byte[] bytes2 = new byte[SIZE];
        System.out.println(bytes2.length);

    System.out.println("I allocated memory successfully");
    }
}



Предпоследняя задача от Баруха Садогурского из JFrog и тусовки Bintray/Artifactory

Что выведет этот код?

def back = 'back'
def quotes = ["I'll be $back",
"I'll be ${-> back}",
"I'll be ${back}",
"I'll be "+back]
println quotes
back = 'bach'
println quotes


Последняя – от Евгения Борисова, тренера офицеров израильской армии по Java. Spring:

Есть два бина:
@Component
public class Няня {
    public void closeAll() {
        while (ребёнокГрязный()) {
            купайРебёнка();
        }
    }
}

@Component
public class Уборщица {
    public void closeAll() {
        while (посудаГрязная()) {
            мойПосуду();
        }
    }
}

Как сделать, чтобы при закрытия контекста, оба метода closeAll работали параллельно (и какие есть варианты)?


Ачивки и решения


  • Небольшой сувенир первому, кто запостит три правильных решения под спойлер в комментарии.
  • Бутылка Хеннеси – тому, кто сможет за 12 часов правильно ответить на все вопросы за один раз. Если таких будет несколько – первому, приславшему правильные ответы.
  • Бесплатные билеты для вас и друга на конференцию JPoint в понедельник тому, кто сможет поправить или существенно доуточнить ответы тех, кто задавал задачи.


Ответы, пожалуйста, в комментарии под спойлер. Если вы не можете комментировать, то делайте #jpoint в Facebook или ВКонтакте.

Вот ответы на первые четыре вопроса, а после JPoint мы опубликуем и ответы на вопросы jbaruch и EvgenyBorisov.

P.S.: подсказки можно поискать в анонсах соответствующих докладов на сайте JPoint.

Комментарии (57)


  1. jbaruch
    16.04.2015 10:10

    Правильные ответы 23derevo выложит не все.
    Поскольку и задачка по Груви от меня и задачка по Спрингу от Борисова являются неотъемлемой частью наших докладов, за правильными ответами на них придется посетить наши доклады.


    1. tolkkv
      16.04.2015 10:23
      +2

      Т.е на них ответы сюда не писать лучше? А то некрасиво получится)


      1. 23derevo Автор
        16.04.2015 10:30

        Пишите все, но под спойлер!


        1. tolkkv
          16.04.2015 10:43
          +2

          Барух что то слишком простую задачку приложил) jbaruch, самые крутые на JPoint оставил?


          1. jbaruch
            16.04.2015 11:07
            +2

            Ты тут не умничай!


          1. gurinderu
            16.04.2015 18:02
            +1

            Простую и не на Java. Несносный groovyист.


            1. jbaruch
              16.04.2015 18:14

              И не говори. Что за люди?!


      1. jbaruch
        16.04.2015 11:06
        +1

        Пиши под спойлер, и мы никому не скажем, что ты прав :)


        1. tolkkv
          16.04.2015 11:21

          Ну а чего писать ) И так же ясно что

          Заголовок спойлера
          только кложур заэвалуэйтится повторно :)


          1. jbaruch
            16.04.2015 11:29
            +1

            Возникает закономерный вопрос
            А сколько там кложуров?


            1. SerCe
              16.04.2015 12:25

              some groovy
              Кложур там только в списке только в 1 месте, AST proof

              А почему так, вкратце описано на относительно новом няшном груви-сайте

              Ну в а сорцах, в groovy.lang.GString#writeTo можно посмотреть что будет, если там будет кложур, принимащий одну переменую


              1. jbaruch
                16.04.2015 13:25
                +2

                Прям ваще.


            1. tolkkv
              16.04.2015 12:53

              Да сразу понятно же чем троллить будешь! В этом и беда)


              1. jbaruch
                16.04.2015 13:26

                Ну, это тебе понятно. Ты не забывай, это идет на jpoint, не на какой нибудь g8conf.


    1. 23derevo Автор
      16.04.2015 10:29

      Все, но ваши — после конфы :)


  1. AlexanderYastrebov
    16.04.2015 10:59
    +4

    Без единого разрыва
    package\u0020ru.nullpointer.jugtest;
    public
    class
    Hello{
    public
    static
    void
    main(String
    args[]){
    System.out.println("Hello\u0020world");
    }
    }
    


  1. Encircled
    16.04.2015 11:06
    +1

    Вторая задача от Владимира Ситникова (NetCracker)
    Регулярка не учитывает /* */ в стрингах

    Написать Hello world без пробелов можно с помощью
    System.out.println("Hello"+(char)32+"World");


    1. lany
      16.04.2015 15:10

      Ваш Hello world не компилируется.


      1. Encircled
        16.04.2015 15:27

        Естественно имелось в виду это
        package
        cz;
        public
        class
        NoSpace
        {
        public
        static
        void
        main(String[]
        args)
        {
        System.out.println("Hello"+(char)32+"World");
        }
        }
        


  1. AlexanderYastrebov
    16.04.2015 11:13
    +2

    Регулярки - зло
    package ru.nullpointer.jugtest;
    
    import java.util.regex.Pattern;
    
    public class CommentCleaner {
    
        public static void main(String args[]) {
            String source = "/***/";
            Pattern p = Pattern.compile("/\\*(?:[^*]|\\*[^/])*\\*/");
    
            String result = p.matcher(source).replaceAll("");
            if (!result.isEmpty()) {
                throw new OHSHIException();
            }
        }
    
        static class OHSHIException extends RuntimeException {
    
            OHSHIException() {
                super("OH SHI--");
            }
        };
    }
    
    


  1. cheremin
    16.04.2015 11:20
    +7

    Не могу удержаться -- задача №4
    Очевидно, что java переиспользует слоты переменных на стеке (в регистрах), когда можно понять, что переменная дальше по коду функции не используется. В обоих случаях аллоцированные массивы перестают быть нужными сразу после вычисления их длины для передачи в println() — поэтому, в идеале, в обоих случаях максимальное необходимое количество памяти не превышает размера одного массива. Почему в одном случае все-таки вылетает исключение?
    Я могу предположить, что есть некоторое количество слотов на стеке, ниже которого нет смысла оптимизировать (тем более, что main вызывается лишь однажды, поэтому о реальной оптимизации C1/C2 речь, скорее всего, не идет — почти наверняка тут мы работаем в интерпретируемом режиме). Методом научного тыка можно обнаружить, что вот такой код
    public static void main( final String[] args ) {
    		{
    			final byte[] bytes = new byte[SIZE];
    			System.out.println( bytes.length );
    		}
    
    		final byte[] a = {}; // <=== !!!
    
    		final byte[] bytes = new byte[SIZE];
    		System.out.println( bytes.length );
    
    		System.out.println( "I allocated memory successfully" );
    	}
    

    выполняется без ошибок. В то время, как вот такой
    public static void main( final String[] args ) {
    		final byte[] a = {};
    		{
    			final byte[] bytes = new byte[SIZE];
    			System.out.println( bytes.length );
    		}
    
    
    
    		final byte[] bytes = new byte[SIZE];
    		System.out.println( bytes.length );
    
    		System.out.println( "I allocated memory successfully" );
    	}
    

    Падает с OOM.

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

    P.S. Любопытно, что вот такой код
    public static void main( final String[] args ) {
    		{
    			final byte[] bytes = new byte[SIZE];
    			System.out.println( bytes.length );
    		}                       
    		int a = 0;   // <== constant
    
    		final byte[] bytes = new byte[SIZE];
    		System.out.println( bytes.length );
    
    		System.out.println( "I allocated memory successfully" );
    	}
    

    Падает с OOM, но если заменить a= 0; на a=args.length — то выполняется успешно. Опять же, можно предположить, что константная переменная просто заменяется на свое значение, без реального размещения ее на стеке, а переменная с хоть сколь либо динамическим значением все-таки размещается — и вытесняет со стека размещенный там byte[]


    1. 23derevo Автор
      16.04.2015 11:24

      грязный, грязный извращенец!


      1. cheremin
        16.04.2015 11:28
        +1

        Ну интересно же было разобраться! А потом уж что, сидеть на этом знании как Кащей над злате? ;)


        1. gvsmirnov
          16.04.2015 12:08
          +1

          К слову, если добавить достаточно циклов, чтобы ворвался компилятор, тоже много чего интересного начинается ;)


          1. gvsmirnov
            16.04.2015 12:11

            1. 23derevo Автор
              16.04.2015 12:23

              а мы с Лёшей сейчас это обсудим!


              1. lany
                17.04.2015 07:41

                Конечно, реклама для лохов, но Лёшу зачем с «будущим Java» вместе поставили? Придётся, видимо, Лёшей пожертвовать в угоду будущему.


                1. 23derevo Автор
                  17.04.2015 07:51

                  когда программа хорошая (а она хорошая!), все время кто-то интересный тебе идет параллельно с кем-то другим, тоже интересным.

                  Скажи лучше, есть ли слоты, в которые тебе не на что пойти?


                  1. lany
                    17.04.2015 08:02

                    На 13:50-14:40 я бы всем пожертвовал. Можно про профилирование послушать, но опционально. Скорее всего пойду в экспертную зону №1, может, там полезнее будет.

                    На 12:50-13:40 в принципе можно бы было всё пропустить, если б было что-то из вечерних докладов в параллели (Куксенко или Чуйко, например: оба интересны и тоже одновременно идут). Катехизис прекрасен, я не спорю, но я смотрел его на ютюбе и Алексей пишет, что будет почти то же самое. А «Сжимай меня полностью», как я понимаю, новый доклад (поправьте, если ошибаюсь), его жалко. Возможно, Фолькера послушаю. Ну или уж Алексея уважу, если места хватит :-)

                    И да, на 10:30-11:20 я бы тоже пропустил, если б в параллель шёл почти любой из остальных докладов :-)


                    1. 23derevo Автор
                      17.04.2015 08:06

                      ты как-то очень сложно все расписал: "… я бы пропустил, если б… ". Я в итоге ничего не понял :)


                    1. jbaruch
                      17.04.2015 15:26

                      легче написать на что ты хочешь пойти. Потому что по комментарию похоже, что ты готов не пойти никуда.


                      1. lany
                        17.04.2015 16:49
                        +3

                        Ага, а деньги я печатаю, и мне ничего не стоит прилететь из Новосиба в Москву и оплатить участие ради того, чтобы пойти никуда =)

                        Если интересно, то вот куда я хочу в порядке убывания приоритета:

                        • Круглый стол. Будущее Java-платформы
                        • Сжимай меня полностью
                        • CompletableFuture уже здесь
                        • Тайны — в наших головах, а не в JVM
                        • Где моя память, чувак?!
                        • Круглый стол. HighLoad
                        • Железные счётчики на страже производительности

                        Если бы эти семь не пересекались, я был бы счастлив. Дальше так:

                        • Лучший отладчик — сделанный своими руками
                        • Катехизис java.lang.String
                        • Packed Objects, Object Layout & Value Types — a Survey
                        • Непрерывное профилирование Java-приложений в ходе эксплуатации
                        • Выражаемся регулярно
                        • Круглый стол. Рефакторинги и технический долг
                        • Круглый стол. Рабочие инструменты Java-разработчика

                        Твои доклады в топ-14 не попали, извиняй =)


                        1. jbaruch
                          17.04.2015 17:29
                          +2

                          всё ок, мне достаточно того, что они у меня получаются лучшими докладами всех jpoint и joker конференций. Но не для всех, да :-)


                          1. jbaruch
                            17.04.2015 17:31
                            +2

                            вон, 23derevo их тоже не любит :-)


                            1. 23derevo Автор
                              17.04.2015 17:36
                              +2

                              я их обожаю!


            1. cheremin
              16.04.2015 12:25
              +1

              Ну да, я сначала тоже с циклами начал экспериментировать. Но там слишком много вариантов получается — и чтобы их грокнуть, чтобы построить какую-то вменяемую модель, нужно было лезть в дизасм или исходники JVM. А мне лень — я хотел простое объяснение для конкретного примера. Потому что общая идея что ссылка живет не до завершения метода, а насколько JVM мозгов хватит — она давно известна, как раз из дискуссий по ранней финализации, на которую Алексей ссылается. И вопрос был лишь в том, чем два отличаются от трех. И — неожиданно — оказалось, что два отличаются от трех ровно на один.


        1. gvsmirnov
          16.04.2015 12:09
          +1

          Ты, кстати, осчастливишь нас своим присутствием, наконец?


          1. 23derevo Автор
            16.04.2015 12:24

            присоединяюсь к вопросу


            1. cheremin
              16.04.2015 12:28
              +1

              Увы, но нет. Зато придут люди из моей команды


              1. jbaruch
                16.04.2015 13:24
                +2

                Пичалька.


              1. gvsmirnov
                16.04.2015 13:40
                +1

                Грустняшка.


                1. 23derevo Автор
                  16.04.2015 13:59

                  пиняшка!


              1. vladimir_dolzhenko
                16.04.2015 15:28

                Кто именно и с каким докладом?


                1. cheremin
                  16.04.2015 15:56

                  Послушать, Володя, просто послушать )


  1. cheremin
    16.04.2015 11:26

    Задача №2
    Регулярка не учитывает контекст — например, применив ее сюда
    String stringLiteral = " /* this is not a comment but a string literal */ ";
    

    будем немного расстроены результатом.

    P.S. Вместо пробела можно использовать unicode escape типа \uXXXX, например.


  1. SerCe
    16.04.2015 11:45
    +1

    ASPECTJ адок
    Скомпилируется то нормально, только при вызове count очевидно получим StackOverflow, дергаем же метод снова прямо из аспекта.


  1. astudent
    16.04.2015 12:12
    +10

    У Вас по Java только 1 задача, остальные имеют к языку лишь опосредованное отношение.


    1. 23derevo Автор
      16.04.2015 12:20
      +2

      К языку да, к платформе — нет.

      Но вообще сейчас разбавим, ага!


  1. red1ynx
    16.04.2015 12:19
    +6

    Предпоследняя задача
    В java не скомпилится.
    image


    1. jbaruch
      16.04.2015 13:23

      данунеможетбыть!


      1. tolkkv
        16.04.2015 14:39
        +3

        мояджавасъелаейнорм


  1. SerCe
    16.04.2015 13:06

    Третья о спринге
    Если не хочется проставлять
    context.setAllowBeanDefinitionOverriding(false);
    

    Всегда можно написать свой BeanPostProcessor, который будет разруливать такие ситуации


  1. Ikryanov
    16.04.2015 17:40
    +1

    Задача №4
    Обе программы выдадут и выдают OOM в JDK 1.8.0.25 32-bit. Получается условие задачи заведомо неверное, так как автор считает, что одна из программ не выбросит OOM, хотя на практике обе бросают.


    1. 23derevo Автор
      17.04.2015 23:58

      окей, тогда почему обе бросают?


    1. lany
      18.04.2015 06:25
      +1

      Давайте считать, что для 32-битной JVM предполагается запускать её с опцией -server.

      >"C:\Program Files (x86)\Java\jdk1.8.0_31\bin\java.exe" -server OOM1
      358862028
      Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
              at OOM1.main(OOM1.java:9)
      
      >"C:\Program Files (x86)\Java\jdk1.8.0_31\bin\java.exe" -server OOM2
      228366745
      228366745
      228366745
      I allocated memory successfully


  1. elDraco
    16.04.2015 22:00
    +2

    Последняя – от Евгения Борисова
    Первый вариант — заимплементить SmartLifecycle.stop(Runnable) и назначить обоим одинаковые фазы.
    Второй — использовать ApplicationEventMulticaster (например, SimpleApplicationEventMulticaster) с соответствующим taskExecutor (например, ThreadPoolExecutor) и повесить обоих на ContextCloseEvent.
    В теории оба варианта должны сработать)


    1. jbaruch
      17.04.2015 23:35

      Ты крут!