Статическое и динамическое связывание Java, upcast, перегрузка, смешанная вместе

Допустим, у нас есть следующий код

class TestEqual{
    public boolean equals(TestEqual other ) {
    System.out.println( "In equals from TestEqual" ); return false;
}

    public static void main( String [] args ) {
        Object t1 = new TestEqual(), t2 = new TestEqual();
        TestEqual t3 = new TestEqual();
        Object o1 = new Object();

        int count = 0;
        System.out.println( count++ );// shows 0
        t1.equals( t2 ) ;
        System.out.println( count++ );// shows 1
        t1.equals( t3 );
        System.out.println( count++ );// shows 2
        t3.equals( o1 );
        System.out.println( count++ );// shows 3
        t3.equals(t3);
        System.out.println( count++ );// shows 4
        t3.equals(t2);
    }
}

По сути, в классе TestEqual (который, конечно, расширяет Object) у нас есть метод, который перегружает метод equals из Object.

Кроме того, у нас есть некоторые переменные: Object t1, t2 экземпляры как TestEqual, TestEqual t3 экземпляры как TestEqual и Object o1 экземпляры как Объект.

Если мы запустим программу, это будет вывод.

0
1
2
3
In equals from TestEqual
4

Этот пример выглядит немного сложнее, чем обычный Car c = new Vehicle(); c.drive(); потому что экземпляр объекта, из которого мы вызываем метод, отличается от его типа, а также параметр метода отличается от его типа.

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

 show 0
 t1.equals(t2)
 show 1

t1 считается объектом TestEqual. Метод equals перегружен, поэтому привязка является статической, это означает, что мы передадим t2 как Object, поэтому он вызовет метод equals, унаследованный от суперкласса Object, поэтому он не будет показывать никакого текста.

 show 1
 t1.equals(t3)
 show 2

Это кажется немного странным. Я ожидал бы показать "In equals from TestEqual", потому что t3 является объектом TestEqual, поэтому должны вызываться equals from t1. Мое объяснение здесь состоит в том, что t1 является статической границей и рассматривается как объект, поэтому вызывается метод, унаследованный от класса Object, при этом параметр TestEqual t3 передается в объект. Но не значит ли это, что предыдущее объяснение из t1.equals(t2) неверно?

show 2
t3.equals(o1);
show 3

t3 - это объект TestEqual, параметр o1 - это объект, поэтому вызывается метод equals, унаследованный от Object, поэтому ничего не печатается.

show 3
t3.equals(t3)
show 4

t3 - объект TestEqual, параметр - объект TestEqual, поэтому будет вызван перегруженный метод из TestEqual, который выдает "In equals from TestEqual".

show 4
t3.equals(t2)

t3 - это объект TestEqual, параметр - это объект из-за статической привязки (перегруженный метод), поэтому вызывается равный метод, унаследованный от объекта, ничего не печатая.

2 ответа

Решение

Это кажется немного странным. Я ожидал бы показать "In equals from TestEqual", потому что t3 является объектом TestEqual, поэтому должны вызываться equals from t1. Мое объяснение здесь состоит в том, что t1 является статической границей и рассматривается как объект, поэтому вызывается метод, унаследованный от класса Object, при этом параметр TestEqual t3 передается в объект. Но не значит ли это, что предыдущее объяснение из t1.equals(t2) неверно?

Для вызова метода в контексте перегрузки происходит наиболее специфичный вызов метода, который определяется во время компиляции. Правило выбора наиболее конкретного метода определено в спецификации языка Java 15.12.2.5. Выбор наиболее специфического метода: при другом обсуждении упомянутое утверждение:

Метод m1 строго более специфичен, чем другой метод m2, если и только если m1 более специфичен, чем m2, а m2 не более специфичен, чем m1.

Для объяснения вашего контекста, однако, давайте объявим два простых суперкласса и класса sup:

class SuperA
{
    public void test(SuperA a)
    {
        System.out.println("super class's test() is called");
    }
}

class SubB extends SuperA
{

    public void test(SubB subB)
    {
        System.out.println("subclass's test() is called");


    }    
}

Теперь, если мы создадим два экземпляра таким образом:

SuperA obj = new SubB();
SubB obj2 = new SubB();
obj.test(obj2);

Вы увидите, что суперкласс test() называется, потому что он определяется как более конкретный во время компиляции, и компилятор видит, что obj это экземпляр типа SuperA, Теперь брось obj в SuubB и вызвать test(obj2):

((SubB)obj).test(obj2); // cast to SubB

И это печать: "subclass's test() is called" подразумевая, что это вызывает test(obj) метод SubB потому что на этот раз компилятор знает, что obj имеет тип SubB и наиболее конкретное разрешение для вызова test,

Однако теперь давайте объявим два экземпляра следующим образом:

   SuperA obj = new SubB();
   SuperA obj2 = new SubB();
   obj.test(obj2); // invokes super class's test method
   ((SubB)obj).test(obj2);// invokes super class's test method
   ((SubB)obj).test((SubB)obj2); // invoke sub class's test method

В этой серии вызовов первые два оператора будут вызывать суперкласс SuperA Это тестовый метод, потому что эти вызовы более специфичны. Чтобы объяснить второй случай, однако:

((SubB)obj).test(obj2);// invokes super class's test method

На этот раз компилятор знает, что obj имеет тип SubB но он все еще видит, что obj2 имеет тип SperA что более конкретно для test вызов метода. Отсюда для второго примера, в котором оба obj а также obj2 был объявлен с типом SuperA: нам нужно, чтобы они оба SubB вызывать SubB "S test метод.

Как Object это супер класс всего класса в Java, вы должны понимать, однако equal метод вызова системы вашего контекста. Чтобы избежать ошибок такого рода, вы увидите, что все реализованные equal Метод в классах Java на самом деле переопределить equal метод Object класс и использовал instanceof проверка. Например, равный метод реализации Integer учебный класс:

public boolean equals(Object obj) {
        if (obj instanceof Integer) { //<<<---- instance of checking
            return value == ((Integer)obj).intValue();
        }
        return false;
    } 

Метод Object.equals(Object obj) принимает другой Object Экземпляр в качестве параметра. Если вы определите свой TestEqual как:

class TestEqual{
    @override
    public boolean equals(Object other ) {
        System.out.println( "In equals from TestEqual" ); return false;
    }
}

Это будет работать, как вы ожидаете.

Другие вопросы по тегам