В некотором царстве, в некотором государстве жил был царь. Как-то раз объявил царь всему народу - "Кто решит три моих задачки, тот сам сможет царём стать". И даже контракт метода опубликовал, всё честь по чести.

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. Ходит теперь с фингалом, смурной.

Вот и сказочке конец, а кто слушал - молодец.