В некотором царстве, в некотором государстве жил был царь. Как-то раз объявил царь всему народу - "Кто решит три моих задачки, тот сам сможет царём стать". И даже контракт метода опубликовал, всё честь по чести.
public interface ЦарёвУказ1844 {
interface РешилВсеЗадачи {}
void setЦарь(Человек<? extends РешилВсеЗадачи> новыйЦарь);
Человек<?> getЦарь();
}
Пришёл к царю кузнец, в разных делах дока, и говорит: "Давай свои задачки"
Царь руки потирает и говорит: "Ну что ж, изволь. Вот моя первая задачка"
Задача о боровиках
"Хочу в указах устанавливать разрешённый сезон сбора боровиков. Скажем, весной и осенью. Могу написать так:
enum ВремяГода { ВЕСНА, ОСЕНЬ, ЗИМА, ЛЕТО }
void идтиСобиратьБоровики(ВремяГода времяГода) {
if (!(времяГода == ОСЕНЬ || времяГода == ВЕСНА))
throw new RuntimeException("Запрещено!");
}
Но тогда ошибка появляется только в рантайме. А я хочу, чтобы ещё на этапе компиляции нельзя было пойти собирать боровики зимой, чтоб прямо в указе было ясно - когда можно, когда нельзя"
"Ну, – говорит кузнец, – сделай разные классы и два перегруженных метода, вот так как-то"
sealed interface ВремяГода {}
final class Зима implements ВремяГода {}
final class Весна implements ВремяГода {}
final class Лето implements ВремяГода {}
final class Осень implements ВремяГода {}
void идтиСобиратьБоровики(Весна весна) {}
void идтиСобиратьБоровики(Осень осень) {}
"Теперь если зимой попытаться пойти – не скомпилируется"
"А если сезоны менять – это мне придётся новые методы дописывать или эти удалять? Не хочу. Хочу один метод!"
"Ежели один метод, тогда можно маркерный интерфейс завести и временам года назначать"
interface МожноСобиратьБоровики {}
final class Зима implements ВремяГода {}
final class Весна implements ВремяГода, МожноСобиратьБоровики {}
final class Лето implements ВремяГода {}
final class Осень implements ВремяГода, МожноСобиратьБоровики {}
<ВГ extends ВремяГода & МожноСобиратьБоровики>
void идтиСобиратьБоровики(ВГ времяГода) {}
"Уже лучше, – говорит царь. – Только неудобно, что надо навешивать интерфейсы сезонам. Можно как-то так сделать, чтобы в одном месте перечень был? Редактировать проще"
"Изволь, – говорит кузнец, – можно и перечень, но тогда чутка посложнее выйдет"
static class ВремяГода<X> {}
interface Зима {}
interface Весна {}
interface Лето {}
interface Осень {}
interface МожноСобиратьБоровики extends Зима, Весна {}
static void идтиСобиратьБоровики(ВремяГода<? super МожноСобиратьБоровики> времяГода) {}
"Эх, теперь времена года не sealed, – вздохнул царь"
"Ну ты уж определись, надёжа государь, тебе боровики или высокий штиль кода"
"Ладно, ладно, с этой задачей справился"
Задача о Кощее
"Вторая задачка такая. От царства кощеева поступил заказ на большое количество иголок, яиц, уток, зайцев и сундуков. Надо выдавать всё это пачками и в правильном порядке - сначала сундук, потом заяц, и в конце – игла. Но если я сделаю как-то так."
enum КощеевоБарахло { Игла, Яйцо, Утка, Заяц, Сундук }
List<КощеевоБарахло> выдатьЗаказ()
"То никто не помешает нерадивым работникам нарушить порядок и положить зайца в яйцо"
return Arrays.asList(Сундук, Яйцо, Заяц);
"А то и вообще – два сундука подсунуть. А хочется такой список возвращать, чтобы порядок на этапе компиляции гарантировался. А, ну и пропуски возможны. Если зайцы вдруг закончатся, можно уток сразу в сундуки."
Задумался кузнец.
"Так, говорит, начнём потихоньку ковать. Чтобы на этапе компиляции сравнивать размеры, нам нужны для них отдельные типы, связанные в иерархию. Вот они"
interface Размер1 { }
interface Размер2 extends Размер1 { }
interface Размер3 extends Размер2 { }
interface Размер4 extends Размер3 { }
interface Размер5 extends Размер4 { }
"Теперь заведём вспомогательные интерфейсы"
interface Размер<Р> { }
interface МеньшеЧем<Р> { }
"Смысл простой – ими будем предметы помечать. И тогда, например, тип Размер<? super Размер3>
предмет опишет любой предмет с размером от Размер1
до Размер3
. Теперь кощеево барахло пометим"
sealed interface КощеевоБарахло { }
final class Игла implements КощеевоБарахло, Размер<Размер1>, МеньшеЧем<Размер2> { }
final class Яйцо implements КощеевоБарахло, Размер<Размер2>, МеньшеЧем<Размер3> { }
final class Утка implements КощеевоБарахло, Размер<Размер3>, МеньшеЧем<Размер4> { }
final class Заяц implements КощеевоБарахло, Размер<Размер4>, МеньшеЧем<Размер5> { }
final class Сундук implements КощеевоБарахло, Размер<Размер5> { }
"Предметы удобнее парами сравнивать, да и твоим работникам сподручнее один предмет в другой положить, а не всеми сразу жонглировать. Поэтому сделаем такой метод, который принимает два предмета, и только таких, где второй – меньше первого."
public <РазмерПредмета,
Предмет extends КощеевоБарахло & Размер<РазмерПредмета>,
ПредметМеньше extends КощеевоБарахло & МеньшеЧем<? super РазмерПредмета>
> void больше(Предмет предмет, ПредметМеньше поменьше) {}
public static void main(String[] args) {
// больше(new Утка(), new Заяц()); // ошибка компиляции
// больше(new Заяц(), new Заяц()); // ошибка компиляции
больше(new Заяц(), new Утка()); // ок
}
"А теперь делаем связный список, но такой, чтобы верхний элемент мог быть только больше следующего элемента."
class SortedList<BaseType, HeadType extends BaseType> {
private HeadType head;
private SortedList<BaseType, ?> tail;
public final int size;
private SortedList(HeadType head, SortedList<BaseType, ?> tail) {
this.head = head;
this.tail = tail;
this.size = 1 + (tail == null ? 0 : tail.size);
}
public BaseType get(int idx) {
assert idx >= 0 && idx < size;
return getUnchecked(idx);
}
private BaseType getUnchecked(int idx) {
return idx == 0 ? head : tail.getUnchecked(idx - 1);
}
}
"А чего это ты на англицкий переключился?"
"Ваше величество, не говори под руку, а то эксепшном зашибёт. Дальше смотри что будет"
public <РазмерПредмета,
Предмет extends КощеевоБарахло & Размер<РазмерПредмета>
>
SortedList<КощеевоБарахло, Предмет> положить(Предмет предмет) {
return new SortedList<>(предмет, null);
}
public <РазмерПредмета,
Предмет extends КощеевоБарахло & Размер<РазмерПредмета>,
ПредметМеньше extends КощеевоБарахло & МеньшеЧем<? super РазмерПредмета>
>
SortedList<КощеевоБарахло, Предмет> внутрь(
Предмет предмет,
SortedList<КощеевоБарахло, ПредметМеньше> предметПоменьше
) {
return new SortedList<>(предмет, предметПоменьше);
}
SortedList<КощеевоБарахло, ?> выдатьЗаказ() {
var игла = new Игла();
var заяц = new Заяц();
var утка = new Утка();
var сундук = new Сундук();
var яйцо = new Яйцо();
return внутрь(сундук, внутрь(заяц, внутрь(утка, внутрь(яйцо, положить(игла)))));
}
"Вот и всё. Теперь только в таком порядке и можно отдавать заказ. А если чего не хватает – можно пропустить"
"Ладно, кузнец, тут ты тоже справился. Вот тебе последняя задача."
Задача о перстне
"Кто-то стащил у меня перстень. И никак не могу понять – кто. Что нам известно:
-
Перстень тяжёлый, трансурановый
-
Вор был один
-
Лекарь и Писарь знают, где перстень лежать должен
-
Ключ от этих палат есть у Писаря и у Попа
-
Лекарь и Писарь щуплые, слабые
-
Конюх должен Лекарю, Поп должен Князю, а Князь должен Писарю
-
Лекарь дружит с Князем, Конюх дружит с Попом
Хочу знать, кто подлец, и чтобы ещё на этапе компиляции – так точно не отвертятся."
"Государь-батюшка, а не проще ли Prolog для этой цели взять?"
"Ты делай давай. А не можешь – так пошёл вон с глаз моих."
Усмехнулся кузнец, и давай размышлять.
"Так, сначала признаки раскидаем. Кто, кому, куда и как."
interface МожетПоднятьТяжесть { }
interface ИмеетКлюч { }
interface Друг<X> { }
interface ЗнаетГдеПерстень { }
interface ДалВзаймы<X> { }
interface Должен<X> { }
class Конюх implements Должен<Лекарь>, МожетПоднятьТяжесть, Друг<Поп> { }
class Лекарь implements ЗнаетГдеПерстень, Друг<Князь>, ДалВзаймы<Конюх> { }
class Поп implements Друг<Конюх>, Должен<Князь>, ИмеетКлюч, МожетПоднятьТяжесть { }
class Писарь implements ИмеетКлюч, ЗнаетГдеПерстень, ДалВзаймы<Князь> { }
class Князь implements Друг<Лекарь>, МожетПоднятьТяжесть, Должен<Писарь>, ДалВзаймы<Поп> { }
"Теперь будем составлять метод, который примет только одного из них – преступника. Но вариантов несколько, а объединений типов в Java пока не завезли, поэтому хочешь не хочешь, а придётся несколько методов делать. Понял?"
"Понял, понял. А компилятор не запутается, ежели generic тип будет? Наругает ещё, что из-за erasure типы одинаковые."
"Наругает, коли не схитрим. А мы схитрим. Начнём с очевидной версии. Тот преступник, кто и сильный, и знает где перстень, и ключ имеет."
public <I extends МожетПоднятьТяжесть & ИмеетКлюч & ЗнаетГдеПерстень>
void преступник(I вор, Double x) {}
"А последний аргумент зачем?"
"А вот потом увидишь. Забиваем всех пятерых, пробуем скомпилировать"
преступник(князь, null);
преступник(поп, null);
преступник(писарь, null);
преступник(лекарь, null);
преступник(конюх, null);
"Все строчки с ошибками, а значит версия наша провалилась. Но мы её пока оставим, чтобы честно было. Проверим другую версию: что если вынес кто-то сильный, а про перстень у друга узнал и ключ у него же взял?"
public static <
H extends ИмеетКлюч & ЗнаетГдеПерстень,
I extends МожетПоднятьТяжесть & Друг<H>
> void преступник(I вор, Short x) {}
"Ах вот зачем тебе второй аргумент, это чтобы компилятор видел у них разные сигнатуры и не донимал."
"А ты шаришь. Но по прежнему все строчки у нас не компилируются.
Тогда другое предположение. Преступник сам сильный, друг ему про перстень рассказал, а ключ он у должника выклянчил"
public static <
I extends МожетПоднятьТяжесть
& ДалВзаймы<? extends ИмеетКлюч>
& Друг<? extends ЗнаетГдеПерстень>
> void преступник(I вор, Integer i) {}
"Ага, а вот и строчка компилирующаяся - преступник(князь, null)
. Значит перстень князь стащил. От лекаря про перстень узнал, а у попа ключ взял."
"Вот я так и знал, что он, подлец, за всем стоит. То-то смотрю – ходит довольный, аж светится!"
Награда
"Ну что ж, кузнец, три задачи ты честно решил. Интерфейс я тебе присваиваю с честью"
public class Кузнец implements Человек<ЦарёвУказ1844.РешилВсеЗадачи> {}
"Интерфейс это хорошо, а теперь давай место на троне уступай"
"Пожалуйста, пожалуйста, вот вам метод, вызывайте"
ЦарёвУказ1844 указ1844 = new ЦарёвУказ1844Impl();
Кузнец кузнец = new Кузнец();
указ1844.setЦарь(кузнец);
Exception in thread "main" java.lang.RuntimeException: Не по сеньке шапка
at ЦарёвУказ1844Impl.setЦарь(ЦарёвУказ1844Impl.java:15)
at Награда.main(Награда.java:12)
"Это ещё что, – нахмурился кузнец"
"А это детали реализации, родной. Я ж не совсем с ума сошёл, чтобы кому попало царство своё отдавать. Ну и подстраховался немножко, предусловия разумные поставил"
@Override
public void setЦарь(Человек<? extends РешилВсеЗадачи> новыйЦарь) {
if (!((Человек<?>)новыйЦарь instanceof Царь)) {
throw new RuntimeException("Не по сеньке шапка");
}
царь = новыйЦарь;
}
"Что-то я не понял – а как ты вообще этот метод с собой вызовешь? Ты же задачи не решил, у тебя интерфейс нереализован!"
указ1844.setЦарь(царь);
java: incompatible types:
Царь cannot be converted to Человек<? extends ЦарёвУказ1844.РешилВсеЗадачи>
"Вот ты смешной кузнец, – хохочет царь, – а reflection на что?"
ЦарёвУказ1844.class
.getDeclaredMethod("setЦарь", Человек.class)
.invoke(указ1844, царь);
Кузнец кое-как глаза обратно впучил,
"Нехорошо это, царь-батюшка, контракт не соблюдать."
"Ты поучи меня тут ещё, – рассердился царь, – вали, пока цел"
Кузнец плечами пожал да и пошёл куда глаза глядят. Говорят, на Кипре его видели, в стартапе куёт криптоподковы, не тужит. А царь сам запутался в своих контрактах да и наступил на exception. Ходит теперь с фингалом, смурной.
Вот и сказочке конец, а кто слушал - молодец.
Автор: Алексей Гришин