Нетривиальные возможности Java

JAVA*
Java — язык простой. И после года активного использования для Вас не остаётся секретов. Совершенно случайно я обнаружил, что на stackoverfow люди решили поделиться скрытыми возможностями (Hidden Features of Java). Вышло очень занимательно, получился своеобразный рейтинг нетривиальных возможностей, который я далее запротоколирую в вольном переводе на русский.

double brace


С большим отрывом лидирует «double brace», уже обсуждавшийся ранее в статье Эффект «double brace» by zeroed. Подробное описание метода — 
http://www.c2.com/cgi/wiki?DoubleBraceInitialization
несомненно это самое забавное и неочевидное из списка. Однако как уже отмечалось, метод имеет свои минусы в виде анонимного класса на каждое использование этого метода. А также невозможности использования метода equals () для подобных объектов.



ThreadLocal


ThreadLocal — http://java.sun.com/javase/6/docs/api/java/lang/ThreadLocal.html
Незаслуженно забытый класс, существующий ещё с версии 1.2. И ставший ещё более привлекательным, подружившись с generics. Класс позволяет имея одну переменную, иметь различное значение для каждого из потоков.

Instance Initializers


Если эта возможность ушла от вашего взгляда — тогда пример ниже отлично и без лишних слов продемострирует её. Ссылка на JLS

public class Foo {
  public Foo() {
    System.out.println("constructor called");
  }

  static {
    System.out.println("static initializer called");
  }

  {
    System.out.println("instance initializer called");
  }
}


* This source code was highlighted with Source Code Highlighter.


Выполним:
new Foo();
new Foo();

На выходе получим:
static initializer called
instance initializer called
constructor called
instance initializer called
constructor called

Пересечение множеств классов, как generic-тип


public class Baz<T extends Foo & Bar> {} — ссылка на JLS

Именованные блоки и метки


Нечто малоиспользуемое, однако всё также компилируемо:
twoCycle:
    {
      while (true) {
        while (true) {
          break twoCycle;
        }
      }

    }


* This source code was highlighted with Source Code Highlighter.


Кстати, вспоминается забавный факт о зарезервированных словах const и goto. Которые являются ключевыми, однако использовать их нельзя. Ещё метки дают забавный эффект. Код ниже отлично компилируется (отлично громко сказанно, компилятор такое без ворчания не пропустит)
class Example {
  public static void main(String[] args) {
    System.out.println("Hello World!");
    http://Phi.Lho.free.fr

    System.exit(0);
  }
}


* This source code was highlighted with Source Code Highlighter.


Enum — это класс


И в нем можно определить конструктор, статические и не очень методы. Поподробнее в JLS. А так же у любого enum есть методы, которые возвращают все значения, а так же по строковому имени элемента возвращают объект :

public static E[] values();
public static E valueOf(String name);

finally и return


finally может “съесть” любой эксепшн — jamesjava.blogspot.com/2006/03/dont-return-in-finally-clause.html
  public static int f() {
    try {
      throw new RuntimeException();
    } finally {
      return 0;
    }
  }


* This source code was highlighted with Source Code Highlighter.


Победить finally может только System.exit(..)

Коллекции



Метод asList в java.util.Arrays значительно преобразился начиная с версии 1.5. Выражение ниже не было бы возможным без списка аргументов переменной длины, autoboxing'а и generics.
List<Integer> ints = Arrays.asList(1,2,3);</p>

Типы для параметризованных методов могут быть указаны следующим способом:
Collections.<String,Integer>emptyMap()

static import'ы при всей неоднозначности могут помочь создать эффект потрясающей локаничности.
List<String> ls = List("a", "b", "c");
List<Map<String, String>> data = List(Map( o("name", "michael"), o("sex", "male")));


Подробнее gleichmann.wordpress.com/2008/01/13/building-your-own-literals-in-java-lists-and-arrays/ и code.google.com/p/google-collections/
Для фанатов jquery – использование знака $: garbagecollected.org/2008/04/06/dollarmaps/

И на последок ещё один подпунктик — List.subList(int fromIndex, int toIndex) возвращает view оригинального объекта.
Документированная, однако мало распространённая возможность. Позволяет работать с подсписком, при том изменения sub-листа будут отражены и в родительском обекте.

Класс URL


Значение выражения
new URL("http://www.yahoo.com").equals(new URL("http://209.191.93.52"))
— true

Инициализация final переменной


Инициализация final переменной может быть отложено
public Object getElementAt(int index) {
  final Object element;
  if (index == 0) {
     element = "Result 1";
  } else if (index == 1) {
     element = "Result 2";
  } else {
     element = "Result 3";
  }
  return element;
}


* This source code was highlighted with Source Code Highlighter.


Thread dump


dump всех потоков в stdout:
windows: CTRL-Break в консоле, где запущено приложение
unix: kill -3 PID
+34
19 августа 2009, 22:13
63
sedovmik 64,3

комментарии (50)

+4
sse #
>> new URL(«www.yahoo.com»).equals(new URL(«209.191.93.52»))
А это куда он обращается behind-the-scene? В DNS? Неприятный сайд-эффект ИМХО
+1
demitsuri #
Это дает весьма интересный эффект. Например, следующий код

    URL sse = new URL("sse.habrahabr.ru");
    URL demitsuri = new URL("demitsuri.habrahabr.ru");
    URL habr = new URL("62.213.122.2");
    URL google = new URL("google.com");

    if(sse.equals(habr))
      System.out.println("sse == habr");
    else
      System.out.println("sse != habr");

    if(demitsuri.equals(habr))
      System.out.println("demitsuri == habr");
    else
      System.out.println("demitsuri != habr");

    if(demitsuri.equals(sse))
      System.out.println("demitsuri == sse");
    else
      System.out.println("demitsuri != sse");

    if(habr.equals(google))
      System.out.println("habr == google");
    else
      System.out.println("habr != google");


* This source code was highlighted with Source Code Highlighter.


выведет:

sse == habr
demitsuri == habr
demitsuri == sse
habr != google

URL в первых трех случаях совпадают, а ресурсы (в контексте расшифровки аббревиатуры URL) — разные.
+1
sedovmik #
а если отключиться от интернета?
+4
demitsuri #
Только что попробовал. Получилось в духе высказывания «Я не я, и лошадь не моя»

sse != habr
demitsuri != habr
demitsuri != sse
habr != google
0
tenshi #
тупняк какой-то… не проще ли было ввести функцию getIP?
+2
Zert #
It is Java-way…
+5
maxcom #
это не Java-way, а ошибка дизайна которую тянут еще с Java 1.0
–3
Zert #
Ошибка в дизайне чего? Джавы. От неё избавились? Нет.
Следовательно, it is Java-way
+1
sdmitry #
Да, таки неприятный сайд-эффект, о которм предупреждает FindBugs: invokes java.net.URL.equals(Object), which blocks to do domain name resolution. Полезная тулзовина.
+5
TheShade #
Это как раз тот случай, когда нарушен контракт на equals. По контракту, кроме всего прочего, equals не должен зависеть от внешних данных, а только от внутренного состояния сравниваемых объектов. Про этот недостаток URL.equals(...) красочно пишет Josh Bloch в «Effective Java».
0
ice9 #
Вообще-то это не сайд-эффект, а специфицированное поведение. Другой вопрос: насколько оно вам нравится или подходит.
–1
ice9 #
Хабродебилы не перестают удивлять. Проще нажать минус, чем открыть джавадок?
+1
demitsuri #
Основная проблема заключается в том, что URL — локатор ресурса. Т.е. один из его «позывных», образно говоря. И когда на одинаковые (в контексте использования метода equals) «позывные» откликаются разные ресурсы, это уже беда.
0
ice9 #
Речь о том, что это описанное поводение, оно не должно быть неожиданным. Как и то, что приведенные выше примеры с URL не содержат протокол доступа, и вы получите исключение до первого if.
А по сути проблемы: что ж поделать, придется с этим мириться, использовать альтернативные решения. Хотя бы из-за совместимости, изменять такое поведение не станут.
+1
sse #
Благодаря этому описанному поведению следующий код
bool fun(Object a, Object b) {
  return a.equals(b);
}
имеет все шансы выполняться по-разному в зависимости от того, задела уборщица на пару секунд шваброй сетевой кабель, или нет.

Это описанное (специфицированное) поведение, но от этого оно не менее неинтуитивно и нелогично.

Если метод bool isValid(SomeClass x) в качестве побочного действия переводит объект в бинарную форму и отсылает через сокет на Марс, то это — неинтуитивно, даже если описано.
+1
ice9 #
Если из моего ответа это неясно, я поясню: я не спорю с тем, что это нелогичное поведение метода equals. Кроме того, такая реализация еще и создаст неслабый оверхед, если кому-то вздумается сравнить коллекции URL'ов.
0
sse #
Благодаря этому описанному поведению следующий код

bool fun(Object a, Object b) {
return a.equals(b);
}

имеет все шансы выполняться по-разному в зависимости от того, задела уборщица на пару секунд шваброй сетевой кабель, или нет.

Это описанное (специфицированное) поведение, но от этого оно не менее неинтуитивно и нелогично.

Если метод bool isValid(SomeClass x) в качестве побочного действия переводит объект в бинарную форму и отсылает через сокет на Марс, то это — неинтуитивно, даже если описано.
0
demitsuri #
На самом деле, в примерах выше протокол присутствует, только его съел хаброраспознаватель ссылок.
0
ice9 #
Ок, про протокол беру слова обратно.
0
zeroed #
Я как-то писал про double brace:

habrahabr.ru/blogs/java/63192/
+1
sedovmik #
да-да, я в курсе. Если приглядеться — то можно увидеть, что там закралась ссылка. Пожалуй сделаю её более заметной
0
zeroed #
блин, извините :)
+5
zeroed #
Вообще я бы не стал говорить о том, что ява простой язык и что за год его можно изучить и после этого писать топик, где больше половины должен знать каждый программист =)
+3
znvPredatoR #
Аналогичное ощущение возникло после прочтения статьи — больше половины описанного я активно использую.
+3
sedovmik #
может я не очень крут в русском, и я уже далеко не год программирую на джава. И из всего списка для меня чем-то действительно новым было пересечение в generics типизации. Сама исходная статья даёт отличный рейтинг упущенных вещей при изучении языка. Так например в 10ку попал assert, который я даже не стал упоминать.

Изначально мой как бы «перевод» должен был содержать около 50 пунктов, однако я их перегруппировывал, отсеивал и ленился переводить дальше, в итоге вышло сколько вышло. Хотя материала хватит ещё на несколько частей. Может продолжить?
+1
u_story #
да, будет интересно!
+2
murr #
Спасибо за статью, около половины возможностей знал. Во многом узнал благодаря тестам на прохождение сертификации, в реальных проектах стараюсь поменьше использовать такую экзотику, разве что инициализаторы и дженерики, как вполне известные и с трудом заменимые.
+1
TheShade #
Про ThreadLocal: его применение оправдано в редких случаях, когда известно, что класс будет использоваться несколькими потоками и нужно организовать разделение данных на контексты каждого потока. При этом класс «не очень в курсе», что он исполняется на разных потоках. В общем случае такая практика не оправдана по простой причине. Данные, используемые несколькими потоками одновременно, являются одним из источников сложных ошибок, поэтому обычно стараются как можно больше данных держать сразу в контексте потока [в обычных переменных].

В этом смысле применение ThreadLocal'а как «затычки» против описанного анти-паттерна сходит на нет. Самое частое применение, которое видел я, это thread-local caches, когда известно, что расходы на синхронизацию могут многократно перекрыть выигрыш от кеширования, если класс будут «неправильно» использовать, и поэтому насильно организуют внутриклассовые «контексты» для каждого потока.
0
unchqua #
Например я рассматриваю ThreadLocal как singleton per thread, если можно так выразиться, и это удобно. Не фонтан конечно, но свою маленькую задачу класс выполняет.
0
sedovmik #
мне ThreadLocal очень помог в следующем случае:

я реализовывал Listener класс, у которого было несколько методов onBlaBlaAction(), методы вызываются в строгом порядке. Однако сама библиотека не гарантировала, что все вызовы будут из одного потока. И однажды пришлось добавить поле «состояние» в класс listener. А чтобы потоки его нагло не перетирали — спас ThreadLocal. Синхронизация в этом случае была невозможной.
НЛО прилетело и опубликовало эту надпись здесь
0
sedovmik #
Есть популярный метод сравнения объектов — делать проверку классов сравниваемых объектов через равенство классов. По ссылке есть есть пример: DoubleBraceInitialization

Если в классе equals уже определён таким образом, тогда от Вас мало что зависит
0
ice9 #
И всё-таки не стоит писать столь категорично. Я, например, использую double brace для коллекций в тестах, по вашей же ссылке в посте прямо говориться о том, что это нормально работает, что, впрочем, неудивительно :)
НЛО прилетело и опубликовало эту надпись здесь
0
sedovmik #
если бы мне на ревью прислали код с double brace и переопределённым методом equals в этих же двойных скобках — я бы попросил удалить и не позориться
НЛО прилетело и опубликовало эту надпись здесь
0
sedovmik #
ничего плохого в equals для коллекций не вижу. А по поводу equals, переопределяемый в double brace классе — стоит ли овчина выделки? если лаконичность так важна — посмотрите лучше ссылки из статьи:

own-literals-in-java-lists-and-arrays
dollarmaps
google-collections
0
Rai220 #
Спасибо! Много нового узнал! По поводу thread dump'а… вот если бы вы сказали способ, как программно сохранить его в файл, было бы совсем круто :)
0
sedovmik #
В век eclips'ов и других продвинутых ide — это легко можно сделать во время дебага. в Idea — нажать на pause, затем — Export Threads. А метод описанный в статье больше подойдёт для отлавливания dead-lock'ов на рабочей системе — все равно приложению уже не жить.
0
Rai220 #
Ну да, в IDE — понятно :) Но самое сложное, это объяснить конечному клиенту, как ему скопировать вывалившийся стек ниток из «черного окошка». Вот где настоящая проблема…
0
sedovmik #
В моём случае самым сложным было объяснить клиенту, что Java 1.3 больше не в моде :)
+2
sedovmik #
и ещё один не программный метод — jstack из JDK.

Программно — получить дамп одного треда — Thread.dumpStack(), который вызывает new Exception(«Stack trace»).printStackTrace();
Остаётся получить список всех тредов. Думаю стоит посмотреть на ThreadMXBean из пакета java.lang.management.
+1
sedovmik #
блин, все же на поверхности: Thread.getAllStackTraces(), видимо ещё сплю
+1
Rai220 #
Да, так и сделал в итоге — через ThreadMXBean, только там инфы сильно меньше, чем в оригинальном стэктрейсе. По крайней мере в 1.5. Про переход на 1.6 — то же, что и у вас :) А вот за jstack спасибо большое! Сейчас покопаю, как его можно применить :)
0
skorn #
С великой силой, приходит великая ответственность (с) из спайдермена

Если язык позволяет и вы пишите

} finally {
return 0;
}
работает принцип «сам дурак». Ворнинг от компилятора вы ведь получили =)
0
Vanger13 #
Почти все что здесь написано(кроме double brace и break label) есть в книгах Core Java vol. 1, 2 Хорстманна и Корнелла (у нас они называются вроде Java 2 том 1, 2).
0
sedovmik #
Все уже где-то упоминалось, даже на хабре. А по описанию нетривиальных возможностей лидирует The Java Language Specification. Я и сам не люблю когда меня отсылают к официальной документации, однако здесь не тот случай. Это действительно полезный, упорядоченный источник с хорошими примерами.
0
Vanger13 #
Как то пытался читать, но забросил.
Вообще самых полезных чтива два:
уже названная вами статья Language Specification
и не менее интригующая спецификация JVM
0
Vanger13 #
Ссылка не добавилась:
java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html
0
Red_Dragon_DVL #
Прикольно, сам хотел в ближайшее время написать похожую статью.
В довесок к написанному — заметил, что многие не знают:

1. Нормально скомпилируется:
public interface I1 { void m3(); }
public interface I2 { void m3(); }
public class A {public void m3() { } }
public class B extends A implements I1, I2 { }

2. Узнать соответствующий экземпляр внешнего класса из внутреннего можно выражением ИмяВнешнегоКласса.this.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.