Java8: почему запрещается определять метод по умолчанию для метода из java.lang.Object

Способы по умолчанию - отличный новый инструмент в нашем инструменте Java. Однако я попытался написать интерфейс, который определяет версию default метода toString. Java говорит мне, что это запрещено, поскольку методы, объявленные в java.lang.Object, могут не быть default ed. Почему это так?

Я знаю, что правило "базовый класс всегда выигрывает", поэтому по умолчанию (кадр;) любая реализация default метода Object в любом случае будет перезаписана методом от Object. Однако я не вижу причин, по которым не должно быть исключения для методов из Object в спецификации. Особенно для toString может быть очень полезно иметь реализацию по умолчанию.

Итак, в чем причина, почему разработчики Java решили не разрешать методы default переопределять методы из Object?

Ответ 1

Это еще одна из тех проблем, связанных с языковым дизайном, которая кажется "явно хорошей идеей", пока вы не начнете копать, и вы понимаете, что на самом деле это плохая идея.

В этой почте есть много вопросов (и по другим темам тоже.) Было несколько проектных сил, которые сходились, чтобы привести нас к текущий дизайн:

  • Желание сохранить модель наследования простой,
  • Тот факт, что как только вы просматриваете очевидные примеры (например, превращая AbstractList в интерфейс), вы понимаете, что наследование equals/hashCode/toString сильно привязано к единому наследованию и состоянию, а интерфейсы многократно наследуются и не имеют;
  • Что это потенциально открыло дверь для некоторых удивительных поведений.

Вы уже коснулись цели "сохранить это просто"; правила наследования и разрешения конфликтов разработаны очень просто (классы перехватывают интерфейсы, производные интерфейсы выигрывают над суперинтерфейсами, а любые другие конфликты разрешаются классом реализации.) Конечно, эти правила могут быть изменены, чтобы сделать исключение, но Я думаю, вы обнаружите, что когда начнете тянуть эту строку, инкрементная сложность не так мала, как вы думаете.

Конечно, есть определенная степень пользы, которая оправдывала бы большую сложность, но в этом случае она не существует. Методы, о которых мы говорим, это equals, hashCode и toString. Эти методы суть внутреннее состояние объекта, и именно классу принадлежит государство, а не интерфейс, который находится в лучшем положении для определения того, что означает равенство для этого класса (особенно, поскольку контракт на равенство довольно силен, см. Эффективный Java для некоторых неожиданных последствий); интерфейсные писатели слишком далеки.

Легко вытащить пример AbstractList; было бы прекрасно, если бы мы могли избавиться от AbstractList и поместить поведение в интерфейс List. Но как только вы выйдете за пределы этого очевидного примера, не так много других хороших примеров. В корневом каталоге AbstractList предназначен для одиночного наследования. Но интерфейсы должны быть разработаны для множественного наследования.

Далее, представьте, что вы пишете этот класс:

class Foo implements com.libraryA.Bar, com.libraryB.Moo { 
    // Implementation of Foo, that does NOT override equals
}

Писатель Foo смотрит на супертипы, не видит реализации равных и делает вывод, что для получения ссылочного равенства все, что ему нужно, это наследовать равно от Object. Затем, на следующей неделе, хранитель библиотеки для бара "полезно" добавляет реализацию по умолчанию equals. По электронной почте Ой! Теперь семантика Foo была нарушена интерфейсом в другом домене обслуживания "с пользой", добавив значение по умолчанию для общего метода.

Предполагаются значения по умолчанию. Добавление значения по умолчанию к интерфейсу, где его не было (нигде в иерархии), не должно влиять на семантику конкретных классов реализации. Но если параметры по умолчанию могут "переопределять" методы объекта, это не так.

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

Ответ 2

Запрещается определять методы по умолчанию в интерфейсах для методов в java.lang.Object, поскольку методы по умолчанию никогда не будут "достижимыми".

Методы интерфейса по умолчанию могут быть перезаписаны в классах, реализующих интерфейс, и реализация класса метода имеет более высокий приоритет, чем реализация интерфейса, даже если этот метод реализован в суперклассе. Поскольку все классы наследуются от java.lang.Object, методы в java.lang.Object имеют приоритет над методом по умолчанию в интерфейсе и будут вызываться вместо этого.

Брайан Гетц из Oracle предоставляет несколько подробностей о дизайнерском решении в этом почтовом отправлении.

Ответ 3

Я не вижу в голове авторов языка Java, поэтому мы можем только догадываться. Но я вижу много причин и согласен с ними абсолютно в этом вопросе.

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

Ни одно из них не относится к toString и другим методам Object. Проще говоря, методы по умолчанию были разработаны для обеспечения поведения по умолчанию, где нет другого определения. Не предоставлять реализаций, которые будут "конкурировать" с другими существующими реализациями.

В стандарте "базовый класс всегда выигрывает" есть и веские причины. Предполагается, что классы определяют реальные реализации, а интерфейсы определяют реализации по умолчанию, которые несколько слабее.

Кроме того, введение любых исключений из общих правил вызывает ненужную сложность и вызывает другие вопросы. Объект является (более или менее) классом как любой другой, поэтому почему он должен иметь другое поведение?

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

Ответ 4

Обоснование очень просто, потому что Object является базовым классом для всех классов Java. Поэтому, даже если у нас есть метод Object, определенный как метод по умолчанию в каком-либо интерфейсе, он будет бесполезен, поскольку метод Object всегда будет использоваться. Поэтому, чтобы избежать путаницы, мы не можем использовать методы по умолчанию, которые переопределяют методы класса Object.

Ответ 5

Чтобы дать очень педантичный ответ, запрещается определять метод default для общедоступного метода из java.lang.Object. Существует 11 методов, которые можно классифицировать тремя способами, чтобы ответить на этот вопрос.

  • Шесть из методов Object не могут иметь методы default, потому что они final и не могут быть переопределены вообще: getClass(), notify(), notifyAll(), wait(), wait(long) и wait(long, int).
  • Три метода Object не могут иметь методы default по причинам, указанным выше Брайаном Гетцем: equals(Object), hashCode() и toString().
  • Два из методов Object могут иметь методы default, хотя значение таких значений по умолчанию в лучшем случае сомнительно: clone() и finalize().

    public class Main {
        public static void main(String... args) {
            new FOO().clone();
            new FOO().finalize();
        }
    
        interface ClonerFinalizer {
            default Object clone() {System.out.println("default clone"); return this;}
            default void finalize() {System.out.println("default finalize");}
        }
    
        static class FOO implements ClonerFinalizer {
            @Override
            public Object clone() {
                return ClonerFinalizer.super.clone();
            }
            @Override
            public void finalize() {
                ClonerFinalizer.super.finalize();
            }
        }
    }