Mengapa tidak bidang abstrak?

102

Mengapa kelas Java tidak dapat memiliki bidang abstrak seperti mereka dapat memiliki metode abstrak?

Sebagai contoh: Saya memiliki dua kelas yang memperluas kelas dasar abstrak yang sama. Kedua kelas ini masing-masing memiliki metode yang identik kecuali untuk konstanta String, yang kebetulan merupakan pesan kesalahan, di dalamnya. Jika bidang bisa abstrak, saya bisa membuat abstrak konstan ini dan menarik metode ke kelas dasar. Sebaliknya, saya harus membuat metode abstrak, yang disebut getErrMsg()dalam kasus ini, yang mengembalikan String, menimpa metode ini dalam dua kelas turunan, dan kemudian saya dapat menarik metode (yang sekarang disebut metode abstrak).

Mengapa saya tidak bisa membuat bidang abstrak untuk memulai? Mungkinkah Java dirancang untuk memungkinkan ini?

Paul Reiners
sumber
3
Sepertinya Anda dapat menghindari seluruh masalah ini dengan membuat bidang menjadi tidak konstan dan hanya memberikan nilai melalui konstruktor, berakhir dengan 2 instance dari satu kelas, bukan 2 kelas.
Nate
5
Dengan membuat sebuah field abstrak dalam sebuah super class, Anda harus memastikan bahwa setiap sub class harus memiliki field ini, jadi ini tidak berbeda dengan field non-abstrak.
Peter Lawrey
@ Peter, saya tidak yakin saya mengikuti maksud Anda. jika konstanta non-abstrak ditentukan di kelas abstrak, maka nilainya juga konstan melalui semua subkelas. jika abstrak, maka nilainya harus diimplementasikan / disediakan oleh setiap subclass. jadi, itu tidak akan sama sama sekali.
liltitus 27
1
@ liltitus27 Saya pikir poin saya 3,5 tahun yang lalu adalah bahwa memiliki bidang abstrak tidak akan banyak berubah kecuali memutuskan seluruh gagasan untuk memisahkan pengguna antarmuka dari penerapan.
Peter Lawrey
Ini akan sangat membantu karena dapat memungkinkan anotasi bidang khusus di kelas anak
geg

Jawaban:

104

Anda dapat melakukan apa yang Anda gambarkan dengan memiliki bidang terakhir di kelas abstrak Anda yang diinisialisasi dalam konstruktornya (kode yang belum diuji):

abstract class Base {

    final String errMsg;

    Base(String msg) {
        errMsg = msg;
    }

    abstract String doSomething();
}

class Sub extends Base {

    Sub() {
        super("Sub message");
    }

    String doSomething() {

        return errMsg + " from something";
    }
}

Jika kelas anak Anda "lupa" untuk menginisialisasi final melalui konstruktor super, kompilator akan memberikan peringatan kesalahan, seperti ketika metode abstrak tidak diimplementasikan.

rsp
sumber
Belum punya editor untuk mencobanya di sini, tapi inilah yang akan saya posting juga ..
Tim
6
"kompiler akan memberikan peringatan". Sebenarnya, konstruktor Anak akan mencoba menggunakan konstruktor noargs yang tidak ada dan itu adalah kesalahan kompilasi (bukan peringatan).
Stephen C
Dan menambahkan komentar @Stephen C, "seperti saat metode abstrak tidak diimplementasikan" juga merupakan kesalahan, bukan peringatan.
Laurence Gonsalves
4
Jawaban yang bagus - ini berhasil untuk saya. Saya tahu sudah lama sejak diposting tetapi saya pikir Baseperlu diumumkan abstract.
Steve Chambers
2
Saya masih tidak menyukai pendekatan ini (meskipun itu satu-satunya cara untuk pergi) karena menambahkan argumen tambahan ke konstruktor. Dalam pemrograman jaringan, saya suka membuat jenis pesan, di mana setiap pesan baru mengimplementasikan jenis abstrak tetapi harus memiliki karakter khusus yang ditunjuk seperti 'A' untuk AcceptMessage dan 'L' untuk LoginMessage. Ini memastikan bahwa protokol pesan khusus akan dapat menempatkan karakter pembeda ini di kabel. Ini implisit ke kelas, jadi mereka akan lebih baik disajikan sebagai bidang, bukan sesuatu yang diteruskan ke konstruktor.
Adam Hughes
7

Saya melihat tidak ada gunanya itu. Anda dapat memindahkan fungsi ke kelas abstrak dan mengganti beberapa bidang yang dilindungi. Saya tidak tahu apakah ini berfungsi dengan konstanta tetapi efeknya sama:

public abstract class Abstract {
    protected String errorMsg = "";

    public String getErrMsg() {
        return this.errorMsg;
    }
}

public class Foo extends Abstract {
    public Foo() {
       this.errorMsg = "Foo";
    }

}

public class Bar extends Abstract {
    public Bar() {
       this.errorMsg = "Bar";
    }
}

Jadi maksud Anda adalah bahwa Anda ingin menerapkan implementasi / overriding / apa pun dari errorMsgsubclass? Saya pikir Anda hanya ingin memiliki metode di kelas dasar dan tidak tahu bagaimana menangani bidang itu.

Felix Kling
sumber
4
Yah, tentu saja saya bisa melakukannya. Dan saya telah memikirkannya. Tetapi mengapa saya harus menetapkan nilai di kelas dasar ketika saya tahu pasti bahwa saya akan mengganti nilai itu di setiap kelas turunan? Argumen Anda juga dapat digunakan untuk menyatakan bahwa tidak ada nilai dalam mengizinkan metode abstrak.
Paul Reiners
1
Nah dengan cara ini tidak setiap subclass harus mengganti nilainya. Saya juga bisa mendefinisikan protected String errorMsg;yang memberlakukan entah bagaimana saya menetapkan nilai di subclass. Ini mirip dengan kelas abstrak yang benar-benar mengimplementasikan semua metode sebagai placeholder sehingga pengembang tidak harus mengimplementasikan setiap metode meskipun mereka tidak membutuhkannya.
Felix Kling
7
Mengatakan protected String errorMsg;tidak tidak menegakkan bahwa Anda mengatur nilai di subclass.
Laurence Gonsalves
1
Jika nilai menjadi null jika Anda tidak menyetelnya dihitung sebagai "penegakan", lalu apa yang akan Anda sebut sebagai non-penegakan?
Laurence Gonsalves
9
@felix, ada perbedaan antara penegakan dan kontrak . seluruh posting ini berpusat di sekitar kelas abstrak, yang pada gilirannya mewakili kontrak yang harus dipenuhi oleh setiap subkelas. Jadi, ketika kita berbicara tentang memiliki bidang abstrak, kita mengatakan bahwa setiap subkelas harus mengimplementasikan nilai bidang itu, per kontrak. pendekatan Anda tidak menjamin nilai apa pun, dan tidak menjelaskan apa yang diharapkan secara eksplisit, seperti kontrak. sangat, sangat berbeda.
liltitus 27
3

Jelas itu bisa dirancang untuk memungkinkan ini, tetapi di bawah selimut itu masih harus melakukan pengiriman dinamis, dan karenanya panggilan metode. Desain Jawa (setidaknya di masa-masa awal), sampai batas tertentu, merupakan upaya untuk menjadi minimalis. Artinya, para desainer mencoba menghindari penambahan fitur baru jika mereka dapat dengan mudah disimulasikan oleh fitur lain yang sudah ada dalam bahasa tersebut.

Laurence Gonsalves
sumber
@ Laurence - tidak jelas bagi saya bahwa bidang abstrak dapat dimasukkan. Sesuatu seperti itu kemungkinan memiliki dampak yang dalam dan mendasar pada sistem tipe, dan pada kegunaan bahasa. (Dengan cara yang sama bahwa beberapa warisan membawa masalah yang dalam ... yang dapat menggigit programmer C ++ yang tidak waspada.)
Stephen C
0

Membaca judul Anda, saya pikir Anda mengacu pada anggota contoh abstrak; dan saya tidak melihat banyak gunanya bagi mereka. Tetapi anggota statis abstrak adalah masalah lain sama sekali.

Saya sering berharap dapat mendeklarasikan metode seperti berikut ini di Java:

public abstract class MyClass {

    public static abstract MyClass createInstance();

    // more stuff...

}

Pada dasarnya, saya ingin menegaskan bahwa implementasi konkret dari kelas induk saya menyediakan metode pabrik statis dengan tanda tangan tertentu. Ini akan memungkinkan saya untuk mendapatkan referensi ke kelas beton dengan Class.forName()dan memastikan bahwa saya dapat membangunnya dalam konvensi yang saya pilih.

Drew Wills
sumber
1
Ya baiklah ... ini mungkin berarti bahwa Anda menggunakan refleksi terlalu jauh !!
Stephen C
Kedengarannya seperti kebiasaan pemrograman yang aneh bagi saya. Class.forName (..) berbau seperti cacat desain.
whiskysierra
Hari-hari ini pada dasarnya kami selalu menggunakan Spring DI untuk mencapai hal semacam ini; tapi sebelum kerangka itu dikenal & populer, kita akan menetapkan kelas implementasi dalam properti atau file XML, lalu menggunakan Class.forName () untuk memuatnya.
Drew Wills
Saya dapat memberi Anda kasus penggunaan untuk mereka. Tidak adanya "bidang abstrak" hampir mustahil untuk menyatakan bidang jenis generik di kelas generik abstrak: @autowired T fieldName. Jika Tdidefinisikan sebagai sesuatu seperti T extends AbstractController, penghapusan jenis menyebabkan bidang dihasilkan sebagai jenis AbstractControllerdan yang tidak dapat dipasang secara otomatis karena tidak konkret dan tidak unik. Saya umumnya setuju bahwa tidak banyak gunanya bagi mereka, dan karenanya mereka tidak termasuk dalam bahasa tersebut. Apa yang saya tidak setuju adalah bahwa TIDAK ADA gunanya bagi mereka.
DaBlick
0

Pilihan lain adalah untuk mendefinisikan bidang sebagai publik (final, jika Anda suka) di kelas dasar, dan kemudian menginisialisasi bidang itu di konstruktor kelas dasar, bergantung pada subkelas mana yang saat ini digunakan. Ini agak teduh, karena memperkenalkan ketergantungan melingkar. Tapi, setidaknya itu bukan ketergantungan yang bisa berubah - yaitu, subkelas akan ada atau tidak ada, tetapi metode atau bidang subkelas tidak dapat memengaruhi nilai field.

public abstract class Base {
  public final int field;
  public Base() {
    if (this instanceof SubClassOne) {
      field = 1;
    } else if (this instanceof SubClassTwo) {
      field = 2;
    } else {
      // assertion, thrown exception, set to -1, whatever you want to do 
      // to trigger an error
      field = -1;
    }
  }
}
ragerdl.dll
sumber