Показаны сообщения с ярлыком puzzle. Показать все сообщения
Показаны сообщения с ярлыком puzzle. Показать все сообщения

четверг, 2 июня 2022 г.

И каков результат?

Прикольную задачку обнаружил на просторах Интернет - даже меня заставила задуматься:
int i = (byte) + (char) - (int) + (long) - 1;
System.out.println(i)

среда, 7 декабря 2011 г.

System.exit and locks

Обнаружил тут головоломку: что напечатает следующая программа?
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
System.out.println("Locking");
synchronized (Object.class) {
System.out.println("Locked");
}
}
});
synchronized (Object.class) {
System.exit(0);
}
}

На самом деле, эта головоломка позволяет понять, почему зависает следующая программа:
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
System.exit(0);
}
});
System.exit(0);
}

Вот тут человек пытается объяснить проблему разрывом парности monitorenter/monitorexit, но мне кажется, что ответ на поверхности: срабатывает обычная блокировка, так как shutdown hook выполняется на отдельном потоке. Это даже из API видно, что это не просто Runnable, а целый Thread. А второй пример говорит нам о том, что внутри System.exit есть своя блокировка, которая и подвешивает JVM.

воскресенье, 12 октября 2008 г.

Joshua Bloch уехал

Вчера прошла встреча JUG с Джошуа Блохом, на которую я зарегистрировался, но придти так и не смог, так как Юля сейчас по субботам учится. Стал искать, кому бы Мишку часика на 3 оставить, но так и не нашёл. Таня уехала в деревню к маме, Че просто не смог, Ру в больнице сейчас, а у Серёги ребёнок болеет да и проблемы в семье...
Надо будет послушать его выступление в записи:
- More Effective Java (слайды и запись)
- How to Design a Good API and Why it Matters (слайды и запись)

пятница, 19 сентября 2008 г.

Joshua Bloch в Питере

Яков Сироткин пишет, что планируется встреча Java User Group с автором книг "Эффективное программирование на Java", которую должны знать все Java-программисты, и "Java Puzzlers", про которую я писал. Я попытался зарегистрироваться, но пока "ни ответа, ни привета".

вторник, 3 июня 2008 г.

Java Puzzle 95: Just Desserts

В этой головоломке показаны задачи, которые часто приходится решать нам, разработчикам языка Java. Что напечатают приведённые ниже программы?

public class ApplePie {
public static void main(String[] args) {
int count = 0;
for (int i = 0; i < 100; i++); {
count++;
}
System.out.println(count);
}
}

Эта головоломка по мотивам бага 4157460 напечатает "1".

import java.util.*;
public class BananaBread {
public static void main(String[] args) {
Integer[] array = {3, 1, 4, 1, 5, 9};
Arrays.sort(array, new Comparator() {
public int compare(Integer i1, Integer i2) {
return i1 < i2 ? -1 : (i2 > i1 ? 1 : 0);
}
});
System.out.println(Arrays.toString(array));
}
}

Эта головоломка по мотивам бага 4763901 напечатает "[3, 1, 4, 1, 5, 9]". Хотя, технически, результат неопределён, так как зависит от реализации в конкретной JVM.

public class ChocolateCake {
public static void main(String[] args) {
System.out.println(true?false:true == true?false:true);
}
}

Эта головоломка напечатает "false". При синтаксическом разборе кода компилятор не учитывает пробелы - они никак не влияют на последовательность операторов. Скобки спасут отца русской демократии ;)

Урок этой головоломки, да и всей книги в целом: не кодируй как мой младший брат (Don't code like my brother).

четверг, 29 мая 2008 г.

Java Puzzle 94: Lost in the Shuffle

Рассмотрим метод перемешивания содержимого массива. Хорош ли он? Если нет, то как его исправить?
import java.util.Random;
public class Shuffle {
private static Random rnd = new Random();
public static void shuffle(Object[] a) {
for (int i = 0; i < a.length; i++) {
swap(a, i, rnd.nextInt(a.length));
}
}
private static void swap(Object[] a, int i, int j) {
Object tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}

Aвторы доказывают, почему он не достаточно хорош, и рекомендуют использовать библиотечный класс java.utul.Collections, у которого есть метод shuffle. В принципе, достаточно изменить метод так:
  public static void shuffle(Object[] a) {
for (int i = a.length; i > 1; i--) {
swap(a, i - 1, rnd.nextInt(i));
}
}
}

Говорят, что надо читать Кнута. А я у него только второй том осилил ;)

Java Puzzle 93: Class Warfare

Это головоломка тестирует знания совместимости классов. Авторы приводят два класса. В одном определены статические поля, а второй эти поля использует. После компиляции программы авторы предлагают переопределить эти поля в первом классе и перекомпилить его, не компилируя при этом второй. Естественно, результат работы второго класса не изменится.
Это одна из особенностей языка, которые я не люблю: ссылки на константы резолвятся во время компиляции (JLS 13.1). Из-за этого большие проекты приходится перекомпилировать целиком, чтобы не наступить на грабли.

Однако в этой головоломке скрыта другая. Одна из констант в первом классе - null, а согласно JLS 15.28: null не является константой во время компиляции и ссылка на неё резолвится во время исполнения.
Обратите внимание, что enum-константы, введённые в Java 5, не являются константами времени компиляции, т.е. не надо перекомпилировать классы, использующие эти enum-константы, если они поменяются.
Для всех бы констант так!

пятница, 23 мая 2008 г.

Java Puzzle 92: Twisted Pair

Следующая программа выполняет неестественное действие с анонимным классом. Что она напечатает?
public class Twisted {
private final String name;
public Twisted(String name) {
this.name = name;
}
private String getName() {
return this.name;
}
private void reproduce() {
new Twisted("reproduce") {
private void print() {
System.out.println(getName());
}
}.print();
}
public static void main(String[] args) {
new Twisted("main").reproduce();
}
}

Сначала я подумал, что она совсем не скомпилируется. Потом подумал, что по логике должно напечатать "reproduce". Но и тут чувствуется подвох...
Так было бы, если бы getter не был бы private. Согласно спецификации (JLS 8.2) приватные члены класса никогда не наследуются. Следовательно, из метода print будет вызван метод внешнего (outer) класса, т.е. того класса, экземпляр которого мы создали с именем "main". Соответственно, будет напечатано "main".
Авторы напоминают совет из головоломки 6: если вы не можете с первого взгляда сказать, что делает некая программа, то возможно она делает совсем не то, что вы от нее хотите.

Java Puzzle 91: Serial Killer

А вот объясните-ка, почему сериализация не работает в следующей программе?
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
public class SerialKiller {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Sub sub = new Sub(666);
sub.check();

ByteArrayOutputStream bos = new ByteArrayOutputStream();
new ObjectOutputStream(bos).writeObject(sub);

ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
Sub copy = (Sub) new ObjectInputStream(bis).readObject();
copy.check();
}
}
class Super implements Serializable {
final Set set = new HashSet();
}
class Sub extends Super {
private int id;
Sub(int id) {
this.id = id;
set.add(this);
}
void check() {
if (!set.contains(this))
throw new Error("invariant");
}
public int hashCode() {
return id;
}
public boolean equals(Object object) {
if (object instanceof Sub) {
Sub that = (Sub) object;
return that.id == this.id;
}
return false;
}
}

Вкратце: если необходимо сериализовать HashSet, HashMap или Hashtable, то надо убедиться в том, что их содержимое (элементы, ключи или значения) не содержат прямых или косвенных ссылок на эти коллекции.
И ещё: если вы разрабатываете сериализуемые классы, то в методах readObject или readResolve избегайте прямого или косвенного вызова методов объектов, которые десериализованы.

среда, 14 мая 2008 г.

Java Puzzle 90: It's Absurd, It's Pain, It's Superclass

Следующая программа очень простая и ничего не делает. Но к ней есть два вопроса: почему она не компилируется и как это исправить?
public class Outer {
class Inner1 extends Outer {}
class Inner2 extends Inner1 {}
}

Проблема в том, что при создании класса вызывается "конструктор по-умолчанию", который создаётся автоматически и содержит вызов конструктора суперкласса, а этот вызов выглядит так: this.super(). Теперь становится понятно, почему компилятор ругается "cannot reference this before supertype constructor has been called". Это запрещено согласно спецификации (JLS 8.8.7.1). А исправить можно добавив конструктор без параметров в класс Inner2:
public class Outer {
class Inner1 extends Outer {}
class Inner2 extends Inner1 {
Inner2() {
Outer.this.super();
}
}
}

Такая конструкция уже компилируется, хотя и выглядит очень странно. Кстати, парсер IntelliJ IDEA продолжает показывать ошибку в этом месте.
Авторы предлагают решение лучше и советуют: Когда вы пишете вложенный класс спросите себя, действительно ли нужен доступ к членам класса, в который вы его вкладываете? Если нет, то делайте вложенный класс статическим. Я вообще не люблю вложенные классы. Они усложняют работу с наследованием, а так же с generics и reflection.

Java Puzzle 89: Generic Drugs

В предыдущей задаче мы поняли, что не надо использовать raw type. Написали программу, расставив везде нужные декларации. Что напечатает следующая программа?
public class LinkedList {
private Node head;
private class Node {
E value;
Node next;
Node(E value) {
this.value = value;
this.next = head;
head = this;
}
}
public void add(E e) {
new Node(e);
}
public void dump() {
for (Node n = head; n != null; n = n.next)
System.out.print(n.value + " ");
}
public static void main(String[] args) {
LinkedList list = new LinkedList();
list.add("world");
list.add("Hello");
list.dump();
}
}

А она не хочет компилиться! Дело в том, что типы E разные для класса LinkedList и класса Node. Избегайте затенения параметра типа. Авторы говорили об опасности затенения в головоломках 71, 73 и 79. После этого сразу видно, что надо добавить второй параметр конструктору класса Node и слегка изменить метод add:
public class LinkedList {
private Node head;
private class Node {
F value;
Node next;
Node(F value, Node next) {
this.value = value;
this.next = next;
}
}
public void add(E e) {
this.head = new Node(e, this.head);
}
public void dump() {
for (Node n = head; n != null; n = n.next)
System.out.print(n.value + " ");
}
public static void main(String[] args) {
LinkedList list = new LinkedList();
list.add("world");
list.add("Hello");
list.dump();
}
}

Блин, с этими generics, как по минному полю ходишь. Хотя с template классами в C++ тоже не так всё просто...

понедельник, 5 мая 2008 г.

Java Puzzle 88: Raw Deal

Что напечатает следующая программа?
import java.util.Arrays;
import java.util.List;
public class Pair<T> {
private final T first;
private final T second;
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public List<String> toArray() {
return Arrays.asList(
String.valueOf(this.first),
String.valueOf(this.second));
}
public static void main(String[] args) {
Pair pair = new Pair<Object>(23, "skidoo");
for (String str : pair.toArray()) {
System.out.println(str);
}
}
}

Удивлены? Я тоже. Она просто не скомпилируется. Несовместимость типов в строке:
    for (String str : pair.toArray()) {

Что происходит? Ведь метод toArray всегда возвращает список строк! Проблема в том, что локальная переменная pair использует raw type. А это означает, что все поля и методы тоже будут использовать raw types. Т.е. метод toArray в таком случае возвращает просто список List. Бороться с этим просто. Измените декларацию переменно таким образом:
    Pair<Object> pair = new Pair<Object>(23, "skidoo");

или ещё более просто:
    Pair<?> pair = new Pair<Object>(23, "skidoo");

И запомните, что тип List (raw type) отличается от List<Object> (parametrized type).

А для меня, как разработчика пакета JavaBeans, который в полный рост использует reflection, это знание ОЧЕНЬ важно. Я вышел на новый уровень понимания generics!

Java Puzzle 87: Strained Relations

Тут задачка на равенства. Отношение равенства должно быть рефлексивным (reflexive), транзитивным (transitive) и симметричным (symmetric). Это в теориии...
А в Java определяет ли оператор сравнения == отношение равенства для значений примитивных типов? Если нет, то какое из трёх свойств оно нарушает?

В головоломке 29 мы узнали, что оператор сравнения не рефлексивный, так как Double.NaN == Double.NaN возравщает false.

Головоломка 35 поможет понять, что оператор сравнения не транзитивный. При сравнении двух числовых значений сначала осуществляется приведение типов. Но аккуратнее, конвертация int или long в double может привести к потере точности. А это чревато!
public class Transitive {
public static void main(String[] args) {
long x = Long.MAX_VALUE;
double y = (double)Long.MAX_VALUE;
long z = Long.MAX_VALUE - 1;
System.out.println(x == y);
System.out.println(y == z); // surprise!
System.out.println(x == z);
}
}

пятница, 25 апреля 2008 г.

Java Puzzle 86: Poison-Paren Litter

Можете ли вы придумать корректное выражение, которое превращается в некорректное, если входящее в него выражение взять в скобки?

Обычно добавление пары скобок используется для улучшения читаемости кода. Однако, существует два случая, когда это не так (вспомните задачки 33 и 64).

int i = -2147483648; - корректно
int i = -(2147483648); - некорректно

long l = -9223372036854775808L; - корректно
long l = -(9223372036854775808L); - некорректно

Java Puzzle 85: Lazy Initialization

Следующему классу лень инициализировать себя обычным путём, поэтому он делает это на фоновом потоке. Что программа напечатает? Гарантируется одинаковый результат для каждого запуска?
public class Lazy {
private static boolean initialized = false;
static {
Thread thread = new Thread(
new Runnable {
public void run() {
initialized = true;
}
}
);
thread.start();
try {
thread.join();
} catch (InterruptedException ie) {
throw new AssertionError(ie);
}
}
public static void main(String[] args) {
System.out.println(initialized);
}
}

Кажется, что программа должна каждый раз печатать true, но нет! Она просто виснет.
Симпатичный deadlock. Попробую объяснить.
Понятно, что при обращении к методу main JVM начинает инициализировать класс. И только когда инициализация закончится будет вызван метод main. Во время инициализации запускается метод run. Однако, этот метод перед установкой initialized в true делает тоже проверку на то, что класс уже проинициализирован. Таким образом получается, что основной поток ждёт завершения фонового потока для завершения инициализации, а фоновый поток ждёт завершения инициализации. Вуаля!

вторник, 22 апреля 2008 г.

Java Puzzle 84: Rudely Interrupted

В этой задаче мы пытаемся прервать исполнение текущего потока, а затем проверяем удалось это или нет. Что напечатает следующая программа:
public class SelfInterruption {
public static void main(String[] args) {
Thread.currentThread().interrupt();
if (Thread.interrupted()) {
System.out.println("Interrupted: "
+ Thread.interrupted());
} else {
System.out.println("Not interrupted: "
+ Thread.interrupted());
}
}
}

Вы удивитесь, но она напечатает "Interrupted: false"
Но почему? Дело в том, что вызов метода Thread.interrupted() всегда очищает состояние флага interrupted текущего потока. Ну мы и получили, что первый вызов вернул true, а второй - false. Для решения проблемы нужно вызывать метод Thread.currentThread().isInterrupted(), который не изменяет состояние флага interrupted.

Java Puzzle 83: Dyslexic Monotheism

Однажды один мужик режил что он единственный такой исключительный пёс. И тогда он написал класс, думая что написал singleton:
public class Dog extends Exception {
public static final Dog INSTANCE = new Dog();
private Dog() {
}
public String toString() {
return "Woof";
}
}

Но он ошибался. Во всём. Сможете ли вы создать второй экземпляр класса Dog не используя reflection?

Ну тут надо сообразить, что класс Exception является Serializable. А дальше всё просто. Эта проблема и её решение были описаны в книге Effective Java: singleton, который имплементирует интерфейс Serializable должен определять readResolve метод, который возвращает единственный экземпляр:
public class Dog extends Exception {
public static final Dog INSTANCE = new Dog();
private Dog() {
}
private Object readResolve() {
return INSTANCE;
}
public String toString() {
return "Woof";
}
}

А иначе при десериализации вы получите другой экземпляр.

четверг, 10 апреля 2008 г.

Java Puzzle 82: Beer Blast

Переходим от нитей (Thread) к процессам (Process). Что напечатает следующая программа? А что она напечатает, если при запуске указать параметр командной строки slave?
public class BeerBlast {
private static final String COMMAND = "java BeerBlast slave";
public static void main(String[] args) throws Exception {
if ((args.length == 1) && (args[0].equals("slave"))) {
for (int i = 99; i > 0; i--) {
System.out.println(i + " bottles of beer on the wall");
System.out.println(i + " bottles of beer");
System.out.println("You take one down, pass it around,");
System.out.println((i - 1) + " bottles of beer on the wall");
System.out.println();
}
}
else {
Process process = Runtime.getRuntime().exec(COMMAND);
int value = process.waitFor();
System.out.println("exit value = " + value);
}
}
}

Авторы говорят, что в первом случае программа зависает. Однако у меня она выводит: exit value = 1, хотя по идее должно быть 0. Видимо, компиляторщики что-то пофиксили. Но проблема всё равно остаётся. Чтобы добиться значения 0, надо очистить поток вывода процесса, который можно получить с помощью метода process.getInputStream(). В JDK 5 для этого добавили класс ProcessBuilder. Рекомендуется использовать его, вместо показанного в программе подхода.
Авторы говорят, что класс Process - это пример плохого API. С хорошим API должно быть простым делать всё правильно и должно быть сложным или невозможным делать неправильно.

вторник, 8 апреля 2008 г.

Java Puzzle 81: Charred Beyond Recognition

Следующая программа делает обычные вещи необычным способом. А вы догадаетесь, что она напечатает?
public class Greeter {
public static void main(String[] args) {
String greeting = "Hello world";
for (int i = 0; i < greeting.length(); i++) {
System.out.write(greeting.charAt(i));
}
}
}

А вот ничего и не напечатает! Всё дело в том, что System.out - это буферизованный поток вывода. Многие думают, что этот поток автоматически очищает буфер и выводит данные на консоль. Несмотря на комментарии в классе PrintStream об автоматическом очищении буфера, на практике можно обнаружить, что в этом классе есть единственный метод, который не очищает буфер. Это метод write(int).
Авторы рекомендуют всегда использовать привычные шаблоны где только возможно.

пятница, 28 марта 2008 г.

Java Puzzle 80: Further Reflection

Что напечатает следующая программа, которая создаёт новый экземпляр объекта используя reflection?
public class Outer {
public static void main(String[] args) throws Exception {
new Outer().greetWorld();
}
private void greetWorld() throws Exception {
System.out.println(Inner.class.newInstance());
}
public class Inner {
public String toString() {
return "Hello world";
}
}
}

Программа напечатает Exception stack trace. А всё потому, что для вложенных (не статических!) классов не действует правило создания конструктора без параметров. В этом случае автоматически добавляется параметр, имеющий тип внешнего класса. Перепишем метод правильным образом:
  private void greetWorld() throws Exception {
Constructor c = Inner.class.getConstructor(Outer.class);
System.out.println(c.newInstance(this));
}

Обратите внимание, что в этом случае можно создать вложенный класс, для которого не установлен экземпляр внешнего класса. Надо только передать null вместо this. Автор рекомендует вообще не использовать reflection для создания экземпляров вложенных классов. Я бы сказал по другому: не используйте то, в чём не разбираетесь.
Тут автор ссылается на раздел 18 своей предыдущей книги Эффективное программирование: старайтесь использовать статические члены класса, когда это возможно.