Модернизация пустых методов, чтобы вернуть аргумент, чтобы облегчить беглость: ломать изменения? - PullRequest
14 голосов
/ 28 августа 2010

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

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

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

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

Примеры: String implements CharSequence, Vector implements List и т. Д.

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

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

Этот вопрос о другом виде модернизации:

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

Мои начальные догадкида, потому что:

  • Тип возвращаемого значения не влияет на то, какой метод выбран во время компиляции
  • Даже когдавы используете отражение, такие вещи, как Class.getMethod не различаются по типу возвращаемого значения

Однако я хотел бы услышать более тщательный анализ других, более опытных в Java /Дизайн API.


Приложение: Мотивация

Как указано в заголовке, одной из причин является облегчение плавного интерфейса в стиле программирования.

Рассмотрим этот простой фрагмент, который печатает перемешанный список имен:

    List<String> names = Arrays.asList("Eenie", "Meenie", "Miny", "Moe");
    Collections.shuffle(names);
    System.out.println(names);
    // prints e.g. [Miny, Moe, Meenie, Eenie]

Если бы Collections.shuffle(List) было объявлено для возврата списка ввода, мы могли бы написать:

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

В Collections есть другие методы, которые было бы гораздо приятнее использовать, если бы они возвращали список ввода вместо void, например, reverse(List), sort(List) и т. Д. Фактически, наличие Collections.sort и Arrays.sort return 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 также могли бы быть написаны так, чтобы они возвращали this (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.

Похожие темы

Ответы [ 3 ]

14 голосов
/ 28 августа 2010

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

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

13.2 Что такое двоичная совместимость, а какая нет

Двоичная совместимость не совпадает с исходной совместимостью.


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

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

13.4.15 Метод Результат Тип

Изменение типа результата метода, замена результататип с void или замена 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

Смежные вопросы


О непрерывной модернизации

Давайте теперь рассмотрим, почему необходимо модифицировать новую interface или vararg, как объяснено в Effective Java 2ndРедакция , не нарушает бинарную совместимость.

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

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

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

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

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

Смежные вопросы


Нет абсолютно никакого способа сделать это?

На самом деле, даЕсть способ модифицировать возвращаемое значение в ранее void методах.У нас не может быть двух методов с одинаковыми точными сигнатурами на уровне исходного кода Java, но у нас CAN есть такой на уровне JVM, при условии, что они имеют разные дескрипторы (из-за разных типов возврата).

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

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

1 голос
/ 28 августа 2010

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

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

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

1 голос
/ 28 августа 2010

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

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

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

...