Java: Memanggil metode super yang memanggil metode yang diganti

97
public class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        this.method2();
    }

    public void method2()
    {
        System.out.println("superclass method2");
    }

}

public class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}



public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1();
    }
}

keluaran yang saya harapkan:

subclass method1
superclass method1
superclass method2

keluaran sebenarnya:

subclass method1
superclass method1
subclass method2

Saya tahu secara teknis saya telah mengganti metode publik, tetapi saya pikir karena saya memanggil super, panggilan apa pun di dalam super akan tetap di super, ini tidak terjadi. Ada ide bagaimana saya bisa mewujudkannya?

jsonfry.dll
sumber
2
Saya curiga Anda mungkin ingin "lebih memilih komposisi daripada warisan".
Tom Hawtin - tackline

Jawaban:

80

Kata kunci supertidak "melekat". Setiap pemanggilan metode ditangani secara individual, jadi meskipun Anda harus melakukannya SuperClass.method1()dengan memanggil super, itu tidak memengaruhi pemanggilan metode lain yang mungkin Anda lakukan di masa mendatang.

Itu berarti tidak ada cara langsung untuk memanggil SuperClass.method2()dari SuperClass.method1()tanpa meskipun SubClass.method2()kecuali Anda bekerja dengan sebuah contoh aktual SuperClass.

Anda bahkan tidak dapat mencapai efek yang diinginkan dengan menggunakan Refleksi (lihat dokumentasijava.lang.reflect.Method.invoke(Object, Object...) ).

[EDIT] Sepertinya masih ada kebingungan. Izinkan saya mencoba penjelasan yang berbeda.

Saat Anda memohon foo(), Anda benar-benar memohon this.foo(). Java hanya memungkinkan Anda menghilangkan this. Dalam contoh di pertanyaan, tipe dari thisadalah SubClass.

Jadi, ketika Java mengeksekusi kode tersebut SuperClass.method1(), kode itu akhirnya tiba dithis.method2();

Menggunakan supertidak mengubah contoh yang ditunjukkan oleh this. Jadi panggilan masuk ke SubClass.method2()karena thisadalah tipe SubClass.

Mungkin lebih mudah untuk dipahami ketika Anda membayangkan bahwa Java lolos thissebagai parameter pertama yang tersembunyi:

public class SuperClass
{
    public void method1(SuperClass this)
    {
        System.out.println("superclass method1");
        this.method2(this); // <--- this == mSubClass
    }

    public void method2(SuperClass this)
    {
        System.out.println("superclass method2");
    }

}

public class SubClass extends SuperClass
{
    @Override
    public void method1(SubClass this)
    {
        System.out.println("subclass method1");
        super.method1(this);
    }

    @Override
    public void method2(SubClass this)
    {
        System.out.println("subclass method2");
    }
}



public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1(mSubClass);
    }
}

Jika Anda mengikuti stack panggilan, Anda dapat melihat bahwa thistidak pernah berubah, itu selalu instance yang dibuat main().

Aaron Digulla
sumber
bisakah seseorang mengunggah diagram ini (permainan kata-kata) melalui tumpukan? Terima kasih sebelumnya!
laycat
2
@laycat: Tidak perlu diagram. Ingatlah bahwa Java tidak memiliki "memori" untuk super. Setiap kali memanggil metode, ia akan melihat jenis instans, dan mulai mencari metode dengan jenis ini, tidak peduli seberapa sering Anda memanggil super. Jadi, saat Anda memanggil method2sebuah instance SubClass, itu akan selalu melihat yang SubClasspertama.
Aaron Digulla
@AaronDigulla, Bisakah Anda menjelaskan lebih lanjut tentang "Java tidak memiliki memori untuk super"?
MengT
@ Truman'sworld: seperti yang saya katakan dalam jawaban saya: menggunakan supertidak mengubah contoh. Itu tidak menyetel beberapa bidang tersembunyi "mulai sekarang, semua panggilan metode harus mulai menggunakan SuperClass". Atau dengan kata lain: Nilai thistidak berubah.
Aaron Digulla
@ AaronDigulla, jadi apakah itu berarti kata kunci super sebenarnya memanggil metode yang diwariskan di subkelas alih-alih pergi ke kelas super?
MengT
15

Anda hanya dapat mengakses metode yang diganti dalam metode penggantian (atau dalam metode lain dari kelas penggantian).

Jadi: jangan timpa method2()atau panggil super.method2()di dalam versi yang diganti.

Sean Patrick Floyd
sumber
8

Anda menggunakan thiskata kunci yang sebenarnya merujuk pada "instance yang sedang berjalan dari objek yang Anda gunakan", yaitu, Anda memanggil this.method2();superclass Anda, yang akan memanggil method2 () pada objek yang Anda ' menggunakan kembali, yang merupakan SubClass.

Jose Diaz
sumber
8
benar, dan tidak menggunakan thisjuga tidak akan membantu. Doa yang tidak memenuhi syarat secara implisit menggunakanthis
Sean Patrick Floyd
3
Mengapa ini mendapat suara positif? Ini bukanlah jawaban untuk pertanyaan ini. Ketika Anda menulis method2()kompilator akan melihat this.method2(). Jadi meskipun Anda menghapusnya thistetap tidak akan berfungsi. Apa yang dikatakan @Sean Patrick Floyd benar
Shervin Asgari
4
@ Shervin dia tidak mengatakan sesuatu yang salah, dia hanya tidak menjelaskan apa yang terjadi jika Anda pergithis
Sean Patrick Floyd
4
Jawabannya benar dalam menunjukkan bahwa thismengacu pada "kelas contoh berjalan beton" (dikenal dalam runtime) dan bukan (seperti yang tampaknya diyakini poster) ke "kelas unit kompilasi saat ini" (di mana kata kunci digunakan, dikenal di waktu kompilasi). Tapi itu juga bisa menyesatkan (seperti yang dikatakan Shervin): thisjuga direferensikan secara implisit dengan pemanggilan metode biasa; method2();adalah sama denganthis.method2();
leonbloy
7

Saya memikirkannya seperti ini

+----------------+
|     super      |
+----------------+ <-----------------+
| +------------+ |                   |
| |    this    | | <-+               |
| +------------+ |   |               |
| | @method1() | |   |               |
| | @method2() | |   |               |
| +------------+ |   |               |
|    method4()   |   |               |
|    method5()   |   |               |
+----------------+   |               |
    We instantiate that class, not that one!

Biarkan saya memindahkan subclass itu sedikit ke kiri untuk mengungkapkan apa yang ada di bawah ... (Sobat, saya suka grafik ASCII)

We are here
        |
       /  +----------------+
      |   |     super      |
      v   +----------------+
+------------+             |
|    this    |             |
+------------+             |
| @method1() | method1()   |
| @method2() | method2()   |
+------------+ method3()   |
          |    method4()   |
          |    method5()   |
          +----------------+

Then we call the method
over here...
      |               +----------------+
 _____/               |     super      |
/                     +----------------+
|   +------------+    |    bar()       |
|   |    this    |    |    foo()       |
|   +------------+    |    method0()   |
+-> | @method1() |--->|    method1()   | <------------------------------+
    | @method2() | ^  |    method2()   |                                |
    +------------+ |  |    method3()   |                                |
                   |  |    method4()   |                                |
                   |  |    method5()   |                                |
                   |  +----------------+                                |
                   \______________________________________              |
                                                          \             |
                                                          |             |
...which calls super, thus calling the super's method1() here, so that that
method (the overidden one) is executed instead[of the overriding one].

Keep in mind that, in the inheritance hierarchy, since the instantiated
class is the sub one, for methods called via super.something() everything
is the same except for one thing (two, actually): "this" means "the only
this we have" (a pointer to the class we have instantiated, the
subclass), even when java syntax allows us to omit "this" (most of the
time); "super", though, is polymorphism-aware and always refers to the
superclass of the class (instantiated or not) that we're actually
executing code from ("this" is about objects [and can't be used in a
static context], super is about classes).

Dengan kata lain, mengutip dari Spesifikasi Bahasa Jawa :

Formulir super.Identifiermengacu pada bidang bernama Identifierobjek saat ini, tetapi dengan objek saat ini dipandang sebagai turunan dari kelas super kelas saat ini.

Formulir T.super.Identifiermerujuk ke bidang bernama Identifierdari instance yang melingkupi secara leksikal sesuai dengan T, tetapi dengan instance itu dilihat sebagai instance dari superclass T.

Dalam istilah awam, thispada dasarnya adalah sebuah objek (* objek **; objek yang sama yang dapat Anda pindahkan dalam variabel), instance dari kelas yang dipakai, variabel biasa dalam domain data; superseperti penunjuk ke blok kode pinjaman yang ingin Anda jalankan, lebih seperti pemanggilan fungsi belaka, dan ini relatif terhadap kelas tempat pemanggilannya.

Oleh karena itu jika Anda menggunakan superdari superclass Anda mendapatkan kode dari kelas superduper [the grandparent] dieksekusi), sedangkan jika Anda menggunakan this(atau jika digunakan secara implisit) dari superclass itu terus menunjuk ke subclass (karena tidak ada yang mengubahnya - dan tidak ada bisa).

Unai Vivi
sumber
2

Jika Anda tidak ingin superClass.method1 memanggil subClass.method2, jadikan metode2 bersifat pribadi sehingga tidak dapat diganti.

Inilah sarannya:

public class SuperClass {

  public void method1() {
    System.out.println("superclass method1");
    this.internalMethod2();
  }

  public void method2()  {
    // this method can be overridden.  
    // It can still be invoked by a childclass using super
    internalMethod2();
  }

  private void internalMethod2()  {
    // this one cannot.  Call this one if you want to be sure to use
    // this implementation.
    System.out.println("superclass method2");
  }

}

public class SubClass extends SuperClass {

  @Override
  public void method1() {
    System.out.println("subclass method1");
    super.method1();
  }

  @Override
  public void method2() {
    System.out.println("subclass method2");
  }
}

Jika tidak berhasil seperti ini, polimorfisme tidak mungkin (atau setidaknya tidak setengah berguna).

Joeri Hendrickx
sumber
2

Karena satu-satunya cara untuk menghindari metode untuk diganti adalah dengan menggunakan kata kunci super , saya telah berpikir untuk memindahkan method2 () dari SuperClass ke kelas Base baru lainnya dan kemudian memanggilnya dari SuperClass :

class Base 
{
    public void method2()
    {
        System.out.println("superclass method2");
    }
}

class SuperClass extends Base
{
    public void method1()
    {
        System.out.println("superclass method1");
        super.method2();
    }
}

class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}

public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1();
    }
}

Keluaran:

subclass method1
superclass method1
superclass method2
carlos_lm
sumber
2

this selalu mengacu pada objek yang sedang dieksekusi.

Untuk lebih mengilustrasikan poin di sini adalah sketsa sederhana:

+----------------+
|  Subclass      |
|----------------|
|  @method1()    |
|  @method2()    |
|                |
| +------------+ |
| | Superclass | |
| |------------| |
| | method1()  | |
| | method2()  | |
| +------------+ |
+----------------+

Jika Anda memiliki contoh kotak luar, suatu Subclassobjek, di mana pun Anda kebetulan menjelajah di dalam kotak, bahkan ke dalam Superclass'area', itu tetaplah contoh kotak luar.

Terlebih lagi, dalam program ini hanya ada satu objek yang dibuat dari tiga kelas, jadi thishanya bisa merujuk ke satu hal dan itu adalah:

masukkan deskripsi gambar di sini

seperti yang ditunjukkan di Netbeans 'Heap Walker'.

Johnny Baloney
sumber
2
class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        SuperClass se=new SuperClass();
        se.method2();
    }

    public void method2()
    {
        System.out.println("superclass method2");
    }
}


class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}

panggilan

SubClass mSubClass = new SubClass();
mSubClass.method1();

keluaran

subclass method1
superclass method1
superclass method2

Vijay
sumber
1

Saya tidak percaya Anda bisa melakukannya secara langsung. Salah satu solusinya adalah memiliki implementasi internal pribadi dari metode2 di superclass, dan memanggilnya. Sebagai contoh:

public class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        this.internalMethod2();
    }

    public void method2()
    {
        this.internalMethod2(); 
    }
    private void internalMethod2()
    {
        System.out.println("superclass method2");
    }

}
David Gelhar
sumber
1

Kata kunci "ini" mengacu pada referensi kelas saat ini. Itu berarti, ketika digunakan di dalam metode, kelas 'saat ini' masih SubKelas dan jawabannya dijelaskan.

Ozil
sumber
1

Untuk meringkas, ini menunjuk ke objek saat ini dan pemanggilan metode di java bersifat polimorfik. Jadi, pemilihan metode untuk eksekusi, sangat bergantung pada objek yang ditunjuk oleh ini. Oleh karena itu, memanggil metode method2 () dari kelas induk memanggil method2 () dari kelas anak, karena ini menunjuk ke objek kelas anak. Definisi ini tidak berubah, terlepas dari kelas mana pun itu digunakan.

PS. tidak seperti metode, variabel anggota kelas tidak polimorfik.

Mangu Singh Rajpurohit
sumber
0

Selama penelitian saya untuk kasus serupa, saya telah mengakhiri dengan memeriksa pelacakan tumpukan dalam metode subclass untuk mencari tahu dari mana panggilan itu berasal. Mungkin ada cara yang lebih cerdas untuk melakukannya, tetapi itu berhasil bagi saya dan ini adalah pendekatan yang dinamis.

public void method2(){
        Exception ex=new Exception();
        StackTraceElement[] ste=ex.getStackTrace();
        if(ste[1].getClassName().equals(this.getClass().getSuperclass().getName())){
            super.method2();
        }
        else{
            //subclass method2 code
        }
}

Saya pikir pertanyaan untuk mendapatkan solusi untuk kasus ini masuk akal. Tentu saja ada cara untuk menyelesaikan masalah dengan nama metode yang berbeda atau bahkan jenis parameter yang berbeda, seperti yang telah disebutkan di utas, tetapi dalam kasus saya, saya tidak suka bingung dengan nama metode yang berbeda.

Kalahkan Siegrist
sumber
Kode ini berbahaya dan berisiko serta mahal. Membuat pengecualian memerlukan VM untuk membuat pelacakan tumpukan penuh, membandingkan hanya nama dan bukan tanda tangan lengkap rentan kesalahan. Juga, itu menunjukkan cacat desain yang besar.
M. le Rutte
Dari sudut pandang kinerja, kode saya tampaknya tidak menghasilkan lebih banyak dampak daripada 'baru HashMap (). Size ()'. Namun, saya mungkin telah mengabaikan kekhawatiran yang selama ini Anda pikirkan dan saya bukanlah pakar VM. Saya melihat keraguan Anda dengan membandingkan nama kelas, tetapi itu termasuk paket yang membuat saya cukup yakin, ini adalah kelas orang tua saya. Bagaimanapun, saya suka Ide untuk membandingkan tanda tangan, bagaimana Anda akan melakukannya? Secara umum, jika Anda memiliki cara yang lebih lancar untuk menentukan apakah peneleponnya adalah superclass atau siapa pun yang akan saya hargai di sini.
Kalahkan Siegrist
Jika Anda perlu menentukan apakah penelepon itu kelas super, saya akan berpikir lebih lama jika ada desain ulang. Ini adalah anti pola.
M. le Rutte
Saya mengerti intinya tetapi permintaan umum utas itu masuk akal. Dalam beberapa situasi, masuk akal jika panggilan metode superclass tetap berada dalam konteks superclass dengan panggilan metode bertingkat apa pun. Namun, tampaknya tidak ada cara untuk mengarahkan pemanggilan metode yang sesuai di Superclass.
Kalahkan Siegrist
0

Lebih jauh lagi, hasil yang lebih luas dari pertanyaan yang diajukan, ini akan memberikan lebih banyak wawasan tentang penentu akses dan perilaku penggantian.

            package overridefunction;
            public class SuperClass 
                {
                public void method1()
                {
                    System.out.println("superclass method1");
                    this.method2();
                    this.method3();
                    this.method4();
                    this.method5();
                }
                public void method2()
                {
                    System.out.println("superclass method2");
                }
                private void method3()
                {
                    System.out.println("superclass method3");
                }
                protected void method4()
                {
                    System.out.println("superclass method4");
                }
                void method5()
                {
                    System.out.println("superclass method5");
                }
            }

            package overridefunction;
            public class SubClass extends SuperClass
            {
                @Override
                public void method1()
                {
                    System.out.println("subclass method1");
                    super.method1();
                }
                @Override
                public void method2()
                {
                    System.out.println("subclass method2");
                }
                // @Override
                private void method3()
                {
                    System.out.println("subclass method3");
                }
                @Override
                protected void method4()
                {
                    System.out.println("subclass method4");
                }
                @Override
                void method5()
                {
                    System.out.println("subclass method5");
                }
            }

            package overridefunction;
            public class Demo 
            {
                public static void main(String[] args) 
                {
                    SubClass mSubClass = new SubClass();
                    mSubClass.method1();
                }
            }

            subclass method1
            superclass method1
            subclass method2
            superclass method3
            subclass method4
            subclass method5
sudhirkondle
sumber