Про compound assignment операторы в Java

Я же теперь снова Java программист. И вот, не прошло и пары месяцев в этой роли, как я узнал новую мелочь. Оказывается, a = a && x и a &= x это не совсем одно и то же.

&= называется Compound Bitwise & assignment operator, и, как и другие бинарные операторы, он не «ленив», т. е. правая часть всегда вычисляется, вне зависимости от значения левой. Это отличает его от логического &&, который проигнорирует правую часть, если левая false.

Поэтому, если я напишу так

boolean a = false;
a = a && compute();

то compute() не будет вызвана. А если так

boolean a = false;
a &= compute();

то будет.

Visualizing Concurrency Patterns in Go

Там вначале всё понятно и легко, а потом появляется Concurrent Prime Sieve (Многопоточное решето Эратосфена), которое слегка взрывает мозг поначалу, а потом оказывается очень элегантным.

Вообще, Go заставляет по-новому думать о многопоточности, и это делает его интересным.

Diablo

В этом году игре Diablo исполнится 20 лет. Её главный разработчик и автор идеи David Brevik выступил с докладом про создание игры. Очень интересно, посмотрел с удовольствием. Вопросы-ответы в конце не менее занятные, чем сам доклад. Понравился ответ на вопрос, как они сделали battle.net таким стабильным в 1997-то году, но не буду портить вам удовольствие.

На английском.

Памятка: перегрузка и переопределение

Overriding — переопределение — когда подкласс подсовывает свою реализацию метода вместо реализации в суперклассе. При вызове метода выбирается наиболее «специфическая» реализация, то есть реализация в подклассе, если она есть, приоритетнее реализации в суперклассе. В Java выбор выполняемого метода при переопределении происходит динамически (в рантайме) и не зависит от того, каким типом объявлена ссылка на объект:

public class TestOverriding {
    
    private static class Parent {
        String getName() { return "Parent"; }
    }

    private static class Child extends Parent {
        @Override
        String getName() { return "Child"; }
    }

    public static void main(String[] args) {
        Parent child = new Child();
        // Prints "Child"
        System.out.println(child.getName()); 
    }
}

Overloading — перегрузка — объявление методов (или конструкторов) с одинаковыми именами, но разными сигнатурами. Пример — все конструкторы любого класса, если их больше одного. В Java выбор имплементации при перегрузке происходит статически (во время компиляции) и не зависит от типа объекта в рантайме:

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

public class TestOverloading {

    static void classifyCollection(Collection<?> c) {
        System.out.println("Collection");
    }

    static void classifyCollection(Set<?> s) {
        System.out.println("Set");
    }

    public static void main(String[] args) {
        Collection<?> c = new HashSet<Object>();
        // prints "Collection"
        classifyCollection(c);
    }
}

Среды разработки

Работая в предыдущей команде (GCM), я писал на Java. Я пользовался Intellij IDEA (не люблю Eclipse) с плагинами для Protobuf и чего-то ещё. Всё было хорошо, и я был доволен.

В новой команде (Spanner/Test Infrastructure) я пишу на C++, Python, Go, Java — у нас тут несколько компонентов, написанных на разных языках и обильно склеенных Protobuf-ом. Я начал с Vim, мне не понравилось, попробовал нашу собственную IDE, тоже не особо. Потом подумал, что если мне нравилась Intellij, то может понравиться и CLion, среда разработки на C++ от тех же Jetbrains. Так оно и получилось, в общем. Конечно, с CLion у меня всё не идеально, и я продолжаю разбираться и подстраивать его под себя, но ничего удобнее мне пока не известно.

Как и Intellij, CLion имеет плагины для Python, Go и Protobuf. Однако у CLion нет плагина для Java, ровно как и у Intellij нет плагина для C/C++. Поэтому я, к большому своему сожалению, не могу вести всю свою разработку ни в одном из них. И я не могу понять, почему нельзя было сделать общую IDE платформу с плагинами под разные языки (чтобы некоторые плагины, например C++ и Java, были платными). Меня лично это бы очень устроило. Кажется, что сделать такую среду было бы вполне возможно, и она даже могла быть удобной и приятной в использовании (как умеют сделать в Intellij). Я не проверял, но есть подозрение, что Eclipse как раз таким комбайном и является, что, впрочем, не делает его более привлекательным для меня.

Ключ гаечный

Хвалюсь. Три недели назад я покинул Google Cloud Messaging и пошёл во внутреннюю инфраструктуру. Присоединился к команде Spanner (разрабатывают собственную очень продвинутую базу данных, активно используемую внутри компании).

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

Почему не написал сразу? На всякий случай, вдруг сразу бы выгнали :) , ведь полезного опыта для этой работы у меня мало. Впрочем, никакой критичной работы мне пока не дают, поэтому и сломать ничего толком не получится: я работаю над тестовой инфраструктурой и познаю устройство Spanner.

Наблюдение дня: каких только ошибок не увидишь, когда на диске кончилось место.

Исключения без стек трейса в Java

Бывает, видишь в логе исключение, а стек трейса у него нет (он пустой):

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException

Одна строка и всё.

Это происходит, когда программа кидает очень много таких исключений. Сначала они генерируются как надо, с заполненным стек трейсом, и полностью попадают в лог. Потом виртуальная машина решает, что раз такие ошибки происходят очень часто, имеет смысл оптимизировать генерацию этих исключений (см. JIT). Тогда-то у них и пропадает стек трейс.

Поэтому когда вы у себя такое видите и не можете понять, откуда эти исключения вылетают, имеет смысл промотать лог назад (очень далеко назад, как правило) и поискать в том месте, где они впервые появились. И/или можно выключить эту оптимизацию с помощью следующего аргумента JVM

-XX:-OmitStackTraceInFastThrow

Если интересно, можно сымитировать подобную ситуацию простой программой:

public class Main2 {
 public static void main(String... args) {
  for (int i = 0; i < 1000000; i++) {
   try {
    int x = 0 / 0;
   } catch (Exception e) {
    if (e.getStackTrace().length == 0) {
     System.out.println("i=" + i);
     throw e;
    }
   }
  }
 }
}

У меня, например, цикл заканчивается на 12288-й итерации.

Мне не совсем понятна философия этой оптимизации. Да, исключения действительно “тяжёлые”. Но на то они и исключения, что должны происходить редко. И если в вашей программе они выкидываются очень часто, вы явно что-то делаете не так, и плохая производительность — это не главная ваша проблема. Ваша проблема в том, что вы не читали Item 57: Use exceptions only for exceptional conditions из Effective Java Блоха.

Может быть, кто-нибудь из читателей может объяснить?