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

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

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.

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

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

0
1
2
3
In equals from TestEqual
4

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

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

 show 0
 t1.equals(t2)
 show 1

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

 show 1
 t1.equals(t3)
 show 2

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

show 2
t3.equals(o1);
show 3

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

show 3
t3.equals(t3)
show 4

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

show 4
t3.equals(t2)

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


person Silent Control    schedule 01.12.2013    source источник


Ответы (2)


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

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

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

Однако, чтобы объяснить ваш контекст, давайте объявим два простых класса Super и sup class:

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 проверку. Например, реализация метода equal класса Integer:

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

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

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

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

person Johnbot    schedule 01.12.2013
comment
Да, это будет работать, как и ожидалось, потому что мы переопределяем метод из Object. Но я хотел услышать, правильно ли я понял, что происходит, когда мы вызываем каждый метод, шаг за шагом относительно связывания. Другими словами, верны ли мои объяснения для каждого звонка? - person Silent Control; 01.12.2013
comment
Я не думаю, что этот вообще ответил на вопрос Роберта. Мы знаем правильный путь, но он ожидал объяснения. - person Sage; 01.12.2013