Модифицировать void методы, чтобы вернуть аргумент, облегчающий беглость: ломать изменения?

«Дизайн API похож на секс: сделай одну ошибку и поддержи ее на всю оставшуюся жизнь» ( Джош Блох в твиттере )

В библиотеке Java много ошибок проектирования. Стек расширяет Vector (обсуждение ), и мы не можем это исправить, не вызвав поломку. Мы можем попытаться осудить Integer.getInteger (обсуждение ), но это, вероятно, останется навсегда.

Тем не менее, некоторые виды дооснащения могут быть выполнены без поломки.

Effective Java 2nd Edition, Item 18: Предпочитают интерфейсы абстрактным классам : Существующие классы могут быть легко модифицированы для реализации нового интерфейса ".

Примеры: String реализует CharSequence , Vector реализует List и т. Д.

Effective Java 2nd Edition, Item 42: Используйте varargs разумно : Вы можете модифицировать существующий метод, который принимает массив в качестве окончательного параметра, чтобы принимать varags вместо этого, не влияя на существующих клиентов.

Известным примером (in) является Arrays.asList , который вызвал путаницу ( обсуждение ),

    System.out.println(
        Collections.shuffle(Arrays.asList("Eenie", "Meenie", "Miny", "Moe"))
    );

В коллекциях есть и другие методы, которые было бы гораздо приятнее использовать, если бы они возвращали список ввода вместо void , например reverse (List ) , sort (List) и т. Д. Фактически, имея Collections.sort и Arrays.sort , возвращайте void особенно неудачно, потому что это лишает нас возможности писать выразительный код, такой как этот:

// DOES NOT COMPILE!!!
//     unless Arrays.sort is retrofitted to return the input array

static boolean isAnagram(String s1, String s2) {
    return Arrays.equals(
        Arrays.sort(s1.toCharArray()),
        Arrays.sort(s2.toCharArray())
    );
}

Этот тип возврата void , предотвращающий беглость, конечно, не ограничивается только этими служебными методами. Методы java.util.BitSet также могут быть написаны для возврата этого (ala StringBuffer и StringBuilder ) для облегчения беглости.

// we can write this:
    StringBuilder sb = new StringBuilder();
    sb.append("this");
    sb.append("that");
    sb.insert(4, " & ");
    System.out.println(sb); // this & that

// but we also have the option to write this:
    System.out.println(
        new StringBuilder()
            .append("this")
            .append("that")
            .insert(4, " & ")
    ); // this & that

// we can write this:
    BitSet bs1 = new BitSet();
    bs1.set(1);
    bs1.set(3);
    BitSet bs2 = new BitSet();
    bs2.flip(5, 8);
    bs1.or(bs2);
    System.out.println(bs1); // {1, 3, 5, 6, 7}

// but we can't write like this!
//  System.out.println(
//      new BitSet().set(1).set(3).or(
//          new BitSet().flip(5, 8)
//      )
//  );

К сожалению, в отличие от StringBuilder / StringBuffer , ALL из BitSet возвращают void

Похожие темы

14
задан Community 23 May 2017 в 12:34
поделиться

3 ответа

Если вы не можете модифицировать класс, вы все равно можете превратить свой класс в новый, который использует те же методы, но с правильными результатами (MyClassFluent). Или вы можете добавить новые методы, но с другими именами, вместо Arrays.sort() у нас может быть Arrays.getSorted().

Я думаю, что решение не в том, чтобы форсировать события, а в том, чтобы справляться с ними.

РЕДАКТИРОВАТЬ: я знаю, что не ответил на вопрос о «модернизации пустых методов», но ваш ответ уже очень ясен.

1
ответ дан 1 December 2019 в 13:58
поделиться

К сожалению, да, изменение метода void для возврата чего-либо является критическим изменением. Это изменение не повлияет на совместимость исходного кода (т. е. тот же исходный код Java будет по-прежнему компилироваться так же, как и раньше, без абсолютно никакого заметного эффекта), но нарушит двоичную совместимость (т. е. байт-коды, которые ранее были скомпилированы для старого API, больше не будут выполняться). ).

Вот соответствующие выдержки из Спецификации языка Java, 3-е издание:

13.2 Что такое бинарная совместимость и чем она не является

Двоичная совместимость — это не то же самое, что совместимость исходного кода.


13.4 Эволюция классов

В этом разделе описывается влияние изменений в объявлении класса, его членов и конструкторов на ранее существовавшие двоичные файлы.

13.4.15 Тип результата метода

Изменение типа результата метода, замена типа результата на void или замена void типом результата имеет комбинированный эффект. из:

  • удаление старого метода и
  • добавление нового метода с новым типом результата или новым недействительным результатом.

13.4.12 Объявлений методов и конструкторов

Удаление метода или конструктора из класса может нарушить совместимость с любым ранее существовавшим двоичным файлом, который ссылался на этот метод или конструктор; NoSuchMethodError может быть сгенерировано, когда такая ссылка из уже существующего двоичного файла связана. Такая ошибка возникает только в том случае, если в суперклассе не объявлен ни один метод с совпадающей сигнатурой и возвращаемым типом.

То есть, хотя возвращаемый тип метода игнорируется компилятором Java во время компиляции во время процесса разрешения метода, эта информация важна во время выполнения на уровне байт-кода JVM.


В дескрипторах методов байт-кода

Сигнатура метода не включает тип возвращаемого значения, но включает его дескриптор байт-кода.

8.4.2 Сигнатура метода

Два метода имеют одинаковую сигнатуру, если они имеют одинаковые имена и типы аргументов.


15.12 Выражения вызова метода

15.12.2 Шаг 2 во время компиляции: определение сигнатуры метода

Дескриптор (сигнатура плюс тип возвращаемого значения) наиболее конкретного метода используется во время выполнения для выполнения отправка метода.

15.12.2.12 Пример: разрешение во время компиляции

Наиболее подходящий метод выбирается во время компиляции; его дескриптор определяет, какой метод фактически выполняется во время выполнения.

Если к классу добавляется новый метод, то исходный код, скомпилированный со старым определением класса, может не использовать новый метод, даже если перекомпиляция приведет к выбору этого метода.

В идеале исходный код следует перекомпилировать при каждом изменении кода, от которого он зависит.Однако в среде, где разные классы поддерживаются разными организациями, это не всегда возможно.

Небольшой осмотр байт-кода поможет прояснить это. Когда javap -c запускается для фрагмента перетасовки имен, мы видим такие инструкции:

invokestatic java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
             \______________/ \____/ \___________________/\______________/
                type name     method      parameters        return type

invokestatic java/util/Collections.shuffle:(Ljava/util/List;)V
             \___________________/ \_____/ \________________/|
                   type name        method     parameters    return type

Похожие вопросы


О ненарушающей модернизации

Давайте теперь рассмотрим, почему модернизация нового интерфейса или vararg, как описано в Effective Java 2nd Edition, не нарушает двоичную совместимость .

13.4.4 Суперклассы и суперинтерфейсы

Изменение прямого суперкласса или набора прямых суперинтерфейсов типа класса не нарушит совместимость с ранее существовавшими двоичными файлами при условии, что общий набор суперклассов или суперинтерфейсов, соответственно, тип класса не теряет членов.

Модернизация нового интерфейса не приводит к потере какого-либо члена типа, следовательно, это не нарушает двоичную совместимость. Точно так же из-за того, что переменные varargs реализованы с использованием массивов, такая модификация также не нарушает двоичную совместимость.

8.4.1 Формальные параметры

Если последний формальный параметр является параметром переменной арности типа T, считается, что он определяет формальный параметр типа T[]. .

Вопросы по теме


Абсолютно никак нельзя это сделать?

На самом деле, да, есть способ изменить возвращаемое значение для ранее void методов. У нас не может быть двух методов с одинаковыми точными сигнатурами на уровне исходного кода Java, но мы МОЖЕМ иметь это на уровне JVM,при условии, что у них разные дескрипторы (из-за разных типов возврата).

Таким образом, мы можем предоставить двоичный файл, например. java.util.BitSet, который имеет методы с возвращаемыми типами void и non-void одновременно. Нам нужно только опубликовать неvoid версию в качестве нового API. На самом деле, это единственное, что мы можем опубликовать в API, поскольку наличие двух методов с одинаковой сигнатурой в Java является незаконным.

Это решение представляет собой ужасный взлом, требующий специальной (и противоречащей спецификации) обработки для компиляции BitSet.java в BitSet.class, поэтому оно может не стоить затраченных усилий. сделать это.

14
ответ дан 1 December 2019 в 13:58
поделиться

Если вам нужно иметь дело только с совместимостью исходного кода, то просто продолжайте. Изменение с void на возвращаемый тип не сломается.

Но если говорить о том, что вы на самом деле хотите сделать: Я считаю, что проблема с плавными интерфейсами заключается в том, что строки становятся довольно длинными и — после форматирования — несколько нечитаемыми. Для строителей это работает нормально, но я, вероятно, не буду использовать его ни для чего другого.

Это для того, чтобы поиграть с ним, или потому что вы обнаружили, что это действительно здорово?

1
ответ дан 1 December 2019 в 13:58
поделиться
Другие вопросы по тегам:

Похожие вопросы: