Warisan di Jawa, Bahagian 1: Kata kunci yang diperluas

Java menyokong penggunaan semula kelas melalui pewarisan dan komposisi. Tutorial dua bahagian ini mengajar anda bagaimana menggunakan warisan dalam program Java anda. Pada Bahagian 1 anda akan belajar bagaimana menggunakan extendskata kunci untuk memperoleh kelas anak dari kelas induk, memanggil pembina dan kaedah kelas induk, dan kaedah ganti. Di Bahagian 2 anda akan melancong java.lang.Object, yang merupakan superclass Java dari mana setiap kelas lain mewarisi.

Untuk menyelesaikan pembelajaran anda mengenai warisan, pastikan untuk melihat tip Java saya yang menjelaskan kapan menggunakan komposisi vs pewarisan. Anda akan mengetahui mengapa komposisi adalah pelengkap penting untuk pewarisan, dan bagaimana menggunakannya untuk melindungi daripada masalah dengan enkapsulasi dalam program Java anda.

muat turun Dapatkan kod Muat turun kod sumber misalnya aplikasi dalam tutorial ini. Dicipta oleh Jeff Friesen untuk JavaWorld.

Pewarisan Jawa: Dua contoh

Inheritance adalah konstruk pengaturcaraan yang digunakan pembangun perisian untuk menjalin hubungan antara kategori. Warisan membolehkan kita memperoleh kategori yang lebih spesifik daripada yang lebih generik. Kategori yang lebih spesifik adalah jenis kategori yang lebih umum. Contohnya, akaun cek adalah sejenis akaun di mana anda boleh membuat deposit dan pengeluaran. Begitu juga, trak adalah sejenis kenderaan yang digunakan untuk mengangkut barang-barang besar.

Warisan boleh turun melalui pelbagai peringkat, membawa kepada kategori yang lebih spesifik. Sebagai contoh, Rajah 1 menunjukkan kereta dan trak yang berasal dari kenderaan; gerabak stesen mewarisi dari kereta; dan trak sampah yang diwarisi dari trak. Anak panah menunjukkan dari kategori "anak" yang lebih spesifik (bawah ke bawah) ke kategori "ibu bapa" yang kurang spesifik (lebih tinggi ke atas).

Jeff Friesen

Contoh ini menggambarkan warisan tunggal di mana kategori anak mewarisi keadaan dan tingkah laku dari satu kategori ibu bapa terdekat. Sebaliknya, pelbagai warisan membolehkan kategori anak mewarisi keadaan dan tingkah laku dari dua atau lebih kategori ibu bapa terdekat. Hirarki dalam Rajah 2 menggambarkan pelbagai warisan.

Jeff Friesen

Kategori dijelaskan mengikut kelas. Java menyokong pewarisan tunggal melalui peluasan kelas , di mana satu kelas secara langsung mewarisi bidang dan kaedah yang dapat diakses dari kelas lain dengan memperluas kelas itu. Java tidak menyokong pelbagai warisan melalui peluasan kelas.

Semasa melihat hierarki warisan, anda dapat dengan mudah mengesan banyak warisan dengan adanya corak berlian. Gambar 2 menunjukkan corak ini dalam konteks kenderaan, kenderaan darat, kenderaan air, dan kapal terbang.

Kata kunci meluas

Java menyokong peluasan kelas melalui extendskata kunci. Semasa hadir, extendsmenentukan hubungan ibu bapa-anak antara dua kelas. Di bawah ini saya gunakan extendsuntuk menjalin hubungan antara kelas Vehicledan Car, dan kemudian antara Accountdan SavingsAccount:

Penyenaraian 1. Kata extendskunci menentukan hubungan ibu bapa-anak

class Vehicle { // member declarations } class Car extends Vehicle { // inherit accessible members from Vehicle // provide own member declarations } class Account { // member declarations } class SavingsAccount extends Account { // inherit accessible members from Account // provide own member declarations }

Kata extendskunci ditentukan selepas nama kelas dan sebelum nama kelas yang lain. Nama kelas sebelum extendsmengenal pasti anak dan nama kelas setelah extendsmengenal pasti ibu bapa. Tidak mustahil untuk menentukan beberapa nama kelas extendskerana Java tidak menyokong pelbagai warisan berdasarkan kelas.

Contoh-contoh ini mengekodkan adalah-a hubungan: Carialah khusus Vehicledan SavingsAccountadalah khusus Account. Vehicledan Accountdikenali sebagai kelas asas , kelas induk , atau cermin mata hitam . Cardan SavingsAccountdikenali sebagai kelas turunan , kelas anak , atau subkelas .

Kelas akhir

Anda mungkin mengisytiharkan kelas yang tidak boleh dilanjutkan; contohnya atas sebab keselamatan. Di Jawa, kami menggunakan finalkata kunci untuk mengelakkan beberapa kelas diperpanjang. Cukup awalan tajuk kelas dengan final, seperti dalam final class Password. Dengan adanya pernyataan ini, penyusun akan melaporkan kesalahan sekiranya seseorang berusaha untuk memanjangkan Password.

Kelas kanak-kanak mewarisi bidang dan kaedah yang boleh diakses dari kelas induk dan nenek moyang mereka yang lain. Namun, mereka tidak pernah mewarisi pembina. Sebaliknya, kelas kanak-kanak menyatakan pembina mereka sendiri. Selanjutnya, mereka dapat menyatakan bidang dan kaedah mereka sendiri untuk membezakannya dengan ibu bapa mereka. Pertimbangkan Penyenaraian 2.

Penyenaraian 2. AccountKelas induk

class Account { private String name; private long amount; Account(String name, long amount) { this.name = name; setAmount(amount); } void deposit(long amount) { this.amount += amount; } String getName() { return name; } long getAmount() { return amount; } void setAmount(long amount) { this.amount = amount; } }

Penyenaraian 2 menerangkan kelas akaun bank generik yang mempunyai nama dan jumlah awal, yang kedua-duanya ditetapkan dalam konstruktor. Ia juga membolehkan pengguna membuat deposit. (Anda boleh membuat pengeluaran dengan memasukkan sejumlah wang yang negatif tetapi kami akan mengabaikan kemungkinan ini.) Perhatikan bahawa nama akaun mesti ditetapkan semasa akaun dibuat.

Mewakili nilai mata wang

kiraan wang. Anda mungkin lebih suka menggunakan a doubleatau a floatuntuk menyimpan nilai wang, tetapi melakukannya boleh menyebabkan ketidaktepatan. Untuk penyelesaian yang lebih baik, pertimbangkan BigDecimal, yang merupakan sebahagian dari perpustakaan kelas standard Java.

Penyenaraian 3 menunjukkan SavingsAccountkelas anak yang memperluas Accountkelas induknya.

Penyenaraian 3. SavingsAccountKelas kanak-kanak melanjutkan Accountkelas induknya

class SavingsAccount extends Account { SavingsAccount(long amount) { super("savings", amount); } }

The SavingsAccountkelas adalah remeh kerana ia tidak perlu mengisytiharkan bidang atau kaedah tambahan. Namun, ia menyatakan pembina yang menginisialisasi ladang di Accountkaca supernya. Inisialisasi berlaku apabila Accountkonstruktor dipanggil melalui superkata kunci Java , diikuti oleh senarai argumen yang dibentuk tanda kurung.

Kapan dan di mana hendak memanggil super ()

Sama seperti this()elemen pertama dalam konstruktor yang memanggil konstruktor lain dalam kelas yang sama, super()begitu juga elemen pertama dalam konstruktor yang memanggil konstruktor dalam superclassnya. Sekiranya anda melanggar peraturan ini, penyusun akan melaporkan ralat. Penyusun juga akan melaporkan ralat jika mengesan super()panggilan dalam kaedah; hanya memanggil super()dalam konstruktor.

Penyenaraian 4 semakin bertambah Accountdengan CheckingAccountkelas.

Penyenaraian 4. CheckingAccountKelas kanak-kanak melanjutkan Accountkelas induknya

class CheckingAccount extends Account { CheckingAccount(long amount) { super("checking", amount); } void withdraw(long amount) { setAmount(getAmount() - amount); } }

CheckingAccount is a little more substantial than SavingsAccount because it declares a withdraw() method. Notice this method's calls to setAmount() and getAmount(), which CheckingAccount inherits from Account. You cannot directly access the amount field in Account because this field is declared private (see Listing 2).

super() and the no-argument constructor

If super() is not specified in a subclass constructor, and if the superclass doesn't declare a no-argument constructor, then the compiler will report an error. This is because the subclass constructor must call a no-argument superclass constructor when super() isn't present.

Class hierarchy example

I've created an AccountDemo application class that lets you try out the Account class hierarchy. First take a look at AccountDemo's source code.

Listing 5. AccountDemo demonstrates the account class hierarchy

class AccountDemo { public static void main(String[] args) { SavingsAccount sa = new SavingsAccount(10000); System.out.println("account name: " + sa.getName()); System.out.println("initial amount: " + sa.getAmount()); sa.deposit(5000); System.out.println("new amount after deposit: " + sa.getAmount()); CheckingAccount ca = new CheckingAccount(20000); System.out.println("account name: " + ca.getName()); System.out.println("initial amount: " + ca.getAmount()); ca.deposit(6000); System.out.println("new amount after deposit: " + ca.getAmount()); ca.withdraw(3000); System.out.println("new amount after withdrawal: " + ca.getAmount()); } }

The main() method in Listing 5 first demonstrates SavingsAccount, then CheckingAccount. Assuming Account.java, SavingsAccount.java, CheckingAccount.java, and AccountDemo.java source files are in the same directory, execute either of the following commands to compile all of these source files:

javac AccountDemo.java javac *.java

Execute the following command to run the application:

java AccountDemo

You should observe the following output:

account name: savings initial amount: 10000 new amount after deposit: 15000 account name: checking initial amount: 20000 new amount after deposit: 26000 new amount after withdrawal: 23000

Method overriding (and method overloading)

A subclass can override (replace) an inherited method so that the subclass's version of the method is called instead. An overriding method must specify the same name, parameter list, and return type as the method being overridden. To demonstrate, I've declared a print() method in the Vehicle class below.

Listing 6. Declaring a print() method to be overridden

class Vehicle { private String make; private String model; private int year; Vehicle(String make, String model, int year) { this.make = make; this.model = model; this.year = year; } String getMake() { return make; } String getModel() { return model; } int getYear() { return year; } void print() { System.out.println("Make: " + make + ", Model: " + model + ", Year: " + year); } }

Next, I override print() in the Truck class.

Listing 7. Overriding print() in a Truck subclass

class Truck extends Vehicle { private double tonnage; Truck(String make, String model, int year, double tonnage) { super(make, model, year); this.tonnage = tonnage; } double getTonnage() { return tonnage; } void print() { super.print(); System.out.println("Tonnage: " + tonnage); } }

Truck's print() method has the same name, return type, and parameter list as Vehicle's print() method. Note, too, that Truck's print() method first calls Vehicle's print() method by prefixing super. to the method name. It's often a good idea to execute the superclass logic first and then execute the subclass logic.

Calling superclass methods from subclass methods

In order to call a superclass method from the overriding subclass method, prefix the method's name with the reserved word super and the member access operator. Otherwise you will end up recursively calling the subclass's overriding method. In some cases a subclass will mask non-private superclass fields by declaring same-named fields. You can use super and the member access operator to access the non-private superclass fields.

To complete this example, I've excerpted a VehicleDemo class's main() method:

Truck truck = new Truck("Ford", "F150", 2008, 0.5); System.out.println("Make = " + truck.getMake()); System.out.println("Model = " + truck.getModel()); System.out.println("Year = " + truck.getYear()); System.out.println("Tonnage = " + truck.getTonnage()); truck.print();

The final line, truck.print();, calls truck's print() method. This method first calls Vehicle's print() to output the truck's make, model, and year; then it outputs the truck's tonnage. This portion of the output is shown below:

Make: Ford, Model: F150, Year: 2008 Tonnage: 0.5

Use final to block method overriding

Occasionally you might need to declare a method that should not be overridden, for security or another reason. You can use the final keyword for this purpose. To prevent overriding, simply prefix a method header with final, as in final String getMake(). The compiler will then report an error if anyone attempts to override this method in a subclass.

Method overloading vs overriding

Suppose you replaced the print() method in Listing 7 with the one below:

void print(String owner) { System.out.print("Owner: " + owner); super.print(); }

The modified Truck class now has two print() methods: the preceding explicitly-declared method and the method inherited from Vehicle. The void print(String owner) method doesn't override Vehicle's print() method. Instead, it overloads it.

Anda dapat mengesan percubaan untuk membebankan dan bukannya menimpa kaedah pada waktu kompilasi dengan awalan tajuk kaedah subkelas dengan @Overrideanotasi:

@Override void print(String owner) { System.out.print("Owner: " + owner); super.print(); }

Menentukan @Overridememberitahu penyusun bahawa kaedah yang diberikan mengatasi kaedah lain. Sekiranya seseorang cuba membebani kaedah tersebut, penyusun akan melaporkan kesalahan. Tanpa penjelasan ini, penyusun tidak akan melaporkan ralat kerana kaedah overloading adalah sah.

Bila hendak menggunakan @Override

Kembangkan kebiasaan kaedah awalan awalan dengan @Override. Kebiasaan ini akan membantu anda mengesan kesalahan memuatkan lebih cepat.