Сказка о типизации

—

от автора

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

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

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


ссылка на оригинал статьи https://habr.com/ru/articles/724700/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *