Konstruktor dalam suatu Antarmuka?

148

Saya tahu itu tidak mungkin untuk mendefinisikan konstruktor dalam sebuah antarmuka. Tapi saya bertanya-tanya mengapa, karena saya pikir itu bisa sangat berguna.

Jadi Anda bisa yakin bahwa beberapa bidang dalam kelas didefinisikan untuk setiap implementasi antarmuka ini.

Sebagai contoh, pertimbangkan kelas pesan berikut:

public class MyMessage {

   public MyMessage(String receiver) {
      this.receiver = receiver;
   }

   private String receiver;

   public void send() {
      //some implementation for sending the mssage to the receiver
   }
}

Jika sebuah mendefinisikan antarmuka untuk kelas ini sehingga saya dapat memiliki lebih banyak kelas yang mengimplementasikan antarmuka pesan, saya hanya dapat mendefinisikan metode kirim dan bukan konstruktor. Jadi bagaimana saya bisa memastikan bahwa setiap implementasi kelas ini benar-benar memiliki set penerima? Jika saya menggunakan metode seperti setReceiver(String receiver)saya tidak dapat memastikan bahwa metode ini benar-benar dipanggil. Di konstruktor saya bisa memastikannya.

daniel kullmann
sumber
2
Anda mengatakan "Dalam konstruktor saya dapat memastikan [setiap implementasi kelas ini benar-benar memiliki set penerima]." Tetapi tidak, Anda tidak mungkin melakukan itu. Asalkan mungkin untuk mendefinisikan konstruktor seperti itu, parameter hanya akan menjadi petunjuk yang kuat untuk implementor Anda - tetapi mereka dapat memilih untuk mengabaikannya jika mereka mau.
Julien Silland
3
@ mattb Umm, itu bahasa yang berbeda.
yesennes

Jawaban:

129

Mengambil beberapa hal yang telah Anda uraikan:

"Jadi, Anda bisa yakin bahwa beberapa bidang dalam kelas didefinisikan untuk setiap implementasi antarmuka ini."

"Jika sebuah mendefinisikan Antarmuka untuk kelas ini sehingga saya dapat memiliki lebih banyak kelas yang mengimplementasikan antarmuka pesan, saya hanya dapat mendefinisikan metode pengiriman dan bukan konstruktor"

... persyaratan ini persis untuk apa kelas abstrak .

matt b
sumber
tetapi perhatikan bahwa use case @Sebi menjelaskan (memanggil metode overload dari induk konstruktor) adalah ide yang buruk seperti yang dijelaskan dalam jawaban saya.
rsp
44
Matt, itu jelas benar, tetapi kelas abstrak menderita dari keterbatasan warisan tunggal, yang mengarahkan orang untuk melihat cara lain dalam menentukan hierarki.
CPerkins
6
Ini benar dan dapat memecahkan masalah langsung Sebi. Tetapi satu alasan untuk menggunakan antarmuka di Java adalah karena Anda tidak dapat memiliki banyak pewarisan. Dalam kasus di mana saya tidak bisa membuat "benda" saya menjadi kelas abstrak karena saya perlu mewarisi dari sesuatu yang lain, masalahnya tetap ada. Bukannya saya mengaku punya solusi.
Jay
7
@CPerkins sementara ini benar, saya tidak menyarankan bahwa hanya menggunakan kelas abstrak akan menyelesaikan kasus penggunaan Sebi. Jika ada, yang terbaik adalah mendeklarasikan Messageantarmuka yang mendefinisikan send()metode, dan jika Sebi ingin memberikan kelas "basis" untuk implementasi Messageantarmuka, maka sediakan AbstractMessagejuga. Kelas abstrak tidak boleh menggantikan antarmuka, tidak pernah berusaha menyarankannya.
matt b
2
Dipahami, Mat. Saya tidak berdebat dengan Anda, lebih menunjukkan bahwa itu bukan pengganti lengkap untuk apa yang diinginkan op.
CPerkins
76

Masalah yang Anda dapatkan saat Anda mengizinkan konstruktor di antarmuka berasal dari kemungkinan untuk mengimplementasikan beberapa antarmuka secara bersamaan. Ketika sebuah kelas mengimplementasikan beberapa antarmuka yang mendefinisikan konstruktor yang berbeda, kelas harus mengimplementasikan beberapa konstruktor, masing-masing hanya memuaskan satu antarmuka, tetapi tidak yang lain. Tidak mungkin untuk membangun objek yang memanggil masing-masing konstruktor ini.

Atau dalam kode:

interface Named { Named(String name); }
interface HasList { HasList(List list); }

class A implements Named, HasList {

  /** implements Named constructor.
   * This constructor should not be used from outside, 
   * because List parameter is missing
   */
  public A(String name)  { 
    ...
  }

  /** implements HasList constructor.
   * This constructor should not be used from outside, 
   * because String parameter is missing
   */
  public A(List list) {
    ...
  }

  /** This is the constructor that we would actually 
   * need to satisfy both interfaces at the same time
   */ 
  public A(String name, List list) {
    this(name);
    // the next line is illegal; you can only call one other super constructor
    this(list); 
  }
}
daniel kullmann
sumber
1
Tidak bisakah bahasa tersebut melakukannya dengan mengizinkan hal-hal seperticlass A implements Named, HashList { A(){HashList(new list()); Named("name");} }
mako
1
Arti yang paling berguna untuk "constructor in an interface", jika diizinkan, adalah jika new Set<Fnord>()dapat diartikan sebagai "Beri saya sesuatu yang dapat saya gunakan sebagai Set<Fnord>"; jika penulis Set<T>dimaksudkan HashSet<T>untuk menjadi pelaksana implementasi untuk hal-hal yang tidak memiliki kebutuhan khusus untuk hal lain, antarmuka kemudian new Set<Fnord>()dapat didefinisikan dapat dianggap identik dengan new HashSet<Fnord>(). Untuk kelas untuk mengimplementasikan beberapa antarmuka tidak akan menimbulkan masalah, karena new InterfaceName()hanya akan membangun kelas yang ditunjuk oleh antarmuka .
supercat
Kontra-argumen: A(String,List)konstruktor Anda bisa menjadi konstruktor yang ditunjuk, dan A(String)dan A(List)bisa juga yang sekunder yang menyebutnya. Kode Anda bukan contoh balasan, hanya kode yang buruk.
Ben Leggiero
Mengapa Anda memanggil semua konstruktor dalam implementasi ?! Ya, jika mengimplementasikan lebih banyak Antarmuka dengan ctor, satu dengan String dan satu dengan int, itu membutuhkan dua ctor - tetapi bisa atau bisa digunakan. Jika itu tidak berlaku, kelas tidak mengimplementasikan kedua Interface. Terus!? (ada alasan lain untuk tidak memiliki ctor di Interfaces).
kai
@ Kai Tidak, perlu memanggil kedua konstruktor antarmuka saat membangun sebuah instance. Dengan kata lain: Dalam contoh saya, instance memiliki nama dan daftar, sehingga setiap instance harus membuat instance nama dan daftar.
daniel kullmann
13

Antarmuka mendefinisikan kontrak untuk API, yaitu seperangkat metode yang disetujui oleh pelaksana dan pengguna API. Antarmuka tidak memiliki implementasi instances, karenanya tidak ada konstruktor.

Kasus penggunaan yang Anda gambarkan mirip dengan kelas abstrak di mana konstruktor memanggil metode metode abstrak yang diimplementasikan dalam kelas anak.

Masalah yang melekat di sini adalah bahwa sementara konstruktor dasar sedang dieksekusi, objek anak belum dibangun, dan karenanya dalam keadaan yang tidak dapat diprediksi.

Untuk meringkas: apakah itu meminta masalah ketika Anda memanggil metode kelebihan beban dari konstruktor induk, untuk mengutip mindprod :

Secara umum Anda harus menghindari memanggil metode non-final dalam sebuah konstruktor. Masalahnya adalah bahwa inisialisasi instance / variabel inisialisasi dalam kelas turunan dilakukan setelah konstruktor dari kelas dasar.

rsp
sumber
6

Pekerjaan di sekitar yang dapat Anda coba adalah mendefinisikan a getInstance() metode di antarmuka Anda sehingga pelaksana menyadari parameter apa yang perlu ditangani. Ini tidak sekokoh kelas abstrak, tetapi memungkinkan lebih banyak fleksibilitas sebagai antarmuka.

Namun solusi ini memang mengharuskan Anda untuk menggunakan getInstance()instantiate semua objek dari antarmuka ini.

Misalnya

public interface Module {
    Module getInstance(Receiver receiver);
}
Lappro
sumber
5

Hanya ada bidang statis dalam antarmuka yang tidak perlu diinisialisasi selama pembuatan objek di subkelas dan metode antarmuka harus menyediakan implementasi aktual dalam subkelas. Jadi tidak perlu konstruktor dalam antarmuka.

Alasan kedua - selama pembuatan objek subclass, induk konstruktor dipanggil. Tetapi jika akan ada lebih dari satu antarmuka diimplementasikan maka konflik akan terjadi selama panggilan konstruktor antarmuka untuk mana konstruktor antarmuka akan memanggil pertama

Satyajit Gami
sumber
3

Jika Anda ingin memastikan bahwa setiap implementasi antarmuka berisi bidang tertentu, Anda hanya perlu menambahkan antarmuka pengambil untuk bidang itu :

interface IMyMessage(){
    @NonNull String getReceiver();
}
  • itu tidak akan merusak enkapsulasi
  • itu akan memberi tahu semua orang yang menggunakan antarmuka Anda bahwa Receiverobjek tersebut harus diteruskan ke kelas dalam beberapa cara (baik dengan konstruktor atau oleh setter)
Denys Vasylenko
sumber
2

Ketergantungan yang tidak dirujuk dalam metode antarmuka harus dianggap sebagai detail implementasi, bukan sesuatu yang ditegakkan antarmuka. Tentu saja ada pengecualian, tetapi sebagai aturan, Anda harus mendefinisikan antarmuka Anda seperti apa perilaku yang diharapkan. Keadaan internal dari implementasi yang diberikan tidak boleh menjadi masalah desain antarmuka.

Yishai
sumber
1

Lihat pertanyaan ini untuk alasannya (diambil dari komentar).

Jika Anda benar-benar perlu melakukan sesuatu seperti ini, Anda mungkin menginginkan kelas dasar abstrak daripada antarmuka.

JSB ձոգչ
sumber
1

Ini karena antarmuka tidak memungkinkan untuk mendefinisikan tubuh metode di dalamnya. Tetapi kita harus mendefinisikan konstruktor di kelas yang sama dengan antarmuka memiliki pengubah abstrak default untuk semua metode untuk mendefinisikan. Itu sebabnya kami tidak dapat mendefinisikan konstruktor di antarmuka.

Aasif Ali
sumber
0

Inilah contoh menggunakan Teknik ini. Dalam contoh spesifik ini, kode membuat panggilan ke Firebase menggunakan tiruan MyCompletionListeneryang merupakan antarmuka yang ditutupi sebagai kelas abstrak, antarmuka dengan konstruktor

private interface Listener {
    void onComplete(databaseError, databaseReference);
}

public abstract class MyCompletionListener implements Listener{
    String id;
    String name;
    public MyCompletionListener(String id, String name) {
        this.id = id;
        this.name = name;
    }
}

private void removeUserPresenceOnCurrentItem() {
    mFirebase.removeValue(child("some_key"), new MyCompletionListener(UUID.randomUUID().toString(), "removeUserPresenceOnCurrentItem") {
        @Override
        public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {

        }
    });
    }
}

@Override
public void removeValue(DatabaseReference ref, final MyCompletionListener var1) {
    CompletionListener cListener = new CompletionListener() {
                @Override
                public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
                    if (var1 != null){
                        System.out.println("Im back and my id is: " var1.is + " and my name is: " var1.name);
                        var1.onComplete(databaseError, databaseReference);
                    }
                }
            };
    ref.removeValue(cListener);
}
ExocetKid
sumber
Bagaimana Anda dapat menggunakan privatepengubah akses interface?
Rudra
0

Umumnya konstruktor adalah untuk menginisialisasi anggota non-statis dari kelas tertentu sehubungan dengan objek.

Tidak ada pembuatan objek untuk antarmuka karena hanya ada metode yang dideklarasikan tetapi tidak didefinisikan metode. Mengapa kita tidak dapat membuat objek ke metode yang dideklarasikan adalah pembuatan objek tidak lain adalah mengalokasikan sebagian memori (dalam memori tumpukan) untuk anggota non-statis.

JVM akan membuat memori untuk anggota yang sepenuhnya dikembangkan dan siap untuk digunakan. Berdasarkan anggota tersebut, JVM menghitung berapa banyak memori yang diperlukan untuk mereka dan menciptakan memori.

Jika metode yang dideklarasikan, JVM tidak dapat menghitung berapa banyak memori yang diperlukan untuk metode yang dinyatakan ini karena implementasinya di masa depan yang tidak dilakukan saat ini. jadi pembuatan objek tidak dimungkinkan untuk antarmuka.

kesimpulan:

tanpa penciptaan objek, tidak ada kesempatan untuk menginisialisasi anggota non-statis melalui konstruktor. Itulah sebabnya konstruktor tidak diperbolehkan di dalam antarmuka. (karena tidak ada penggunaan konstruktor di dalam antarmuka)

Sai Kumar
sumber