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

StackOverflow https://stackoverflow.com//questions/24016962

Вопрос

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

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

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

Это было полезно?

Решение

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

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

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

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

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

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

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

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

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

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

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

Другие советы

It is forbidden to define default methods in interfaces for methods in java.lang.Object, since the default methods would never be "reachable".

Default interface methods can be overwritten in classes implementing the interface and the class implementation of the method has a higher precedence than the interface implementation, even if the method is implemented in a superclass. Since all classes inherit from java.lang.Object, the methods in java.lang.Object would have precedence over the default method in the interface and be invoked instead.

Brian Goetz from Oracle provides a few more details on the design decision in this mailing list post.

I do not see into the head of Java language authors, so we may only guess. But I see many reasons and agree with them absolutely in this issue.

The main reason for introducing default methods is to be able to add new methods to interfaces without breaking the backward compatibility of older implementations. The default methods may also be used to provide "convenience" methods without the necessity to define them in each of the implementing classes.

None of these applies to toString and other methods of Object. Simply put, default methods were designed to provide the default behavior where there is no other definition. Not to provide implementations that will "compete" with other existing implementations.

The "base class always wins" rule has its solid reasons, too. It is supposed that classes define real implementations, while interfaces define default implementations, which are somewhat weaker.

Also, introducing ANY exceptions to general rules cause unnecessary complexity and raise other questions. Object is (more or less) a class as any other, so why should it have different behaviour?

All and all, the solution you propose would probably bring more cons than pros.

The reasoning is very simple, it is because Object is the base class for all the Java classes. So even if we have Object's method defined as default method in some interface, it will be useless because Object's method will always be used. That is why to avoid confusion, we cannot have default methods that are overriding Object class methods.

To give a very pedantic answer, it is only forbidden to define a default method for a public method from java.lang.Object. There are 11 methods to consider, which can be categorized in three ways to answer this question.

  1. Six of the Object methods cannot have default methods because they are final and cannot be overridden at all: getClass(), notify(), notifyAll(), wait(), wait(long), and wait(long, int).
  2. Three of the Object methods cannot have default methods for the reasons given above by Brian Goetz: equals(Object), hashCode(), and toString().
  3. Two of the Object methods can have default methods, though the value of such defaults is questionable at best: clone() and 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();
            }
        }
    }
    
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top