В некотором царстве, в некотором государстве жил был царь. Как-то раз объявил царь всему народу - "Кто решит три моих задачки, тот сам сможет царём стать". И даже контракт метода опубликовал, всё честь по чести.
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. Ходит теперь с фингалом, смурной.
Вот и сказочке конец, а кто слушал - молодец.
