Java의 상속, Part 1 : extends 키워드

Java는 상속 및 구성을 통해 클래스 재사용을 지원합니다. 두 부분으로 구성된이 튜토리얼은 Java 프로그램에서 상속을 사용하는 방법을 알려줍니다. 1 부에서는 extends키워드를 사용하여 부모 클래스에서 자식 클래스를 파생시키고 부모 클래스 생성자와 메서드를 호출하고 메서드를 재정의 하는 방법을 배웁니다 . Part 2에서는 java.lang.Object다른 모든 클래스가 상속하는 Java의 수퍼 클래스 인 을 살펴볼 것 입니다.

상속에 대한 학습을 ​​완료하려면 컴포지션과 상속을 사용할시기를 설명하는 Java 팁을 확인하십시오. 컴포지션이 상속에 대한 중요한 보완 요소 인 이유와이를 사용하여 Java 프로그램의 캡슐화 문제를 방지하는 방법을 배우게됩니다.

다운로드 코드 받기이 자습서에서 예제 응용 프로그램의 소스 코드를 다운로드합니다. JavaWorld를 위해 Jeff Friesen이 만들었습니다.

자바 상속 : 두 가지 예

상속 은 소프트웨어 개발자 가 범주 간의 is-a 관계 를 설정하는 데 사용하는 프로그래밍 구조입니다 . 상속을 통해보다 일반적인 범주에서보다 구체적인 범주를 파생 할 수 있습니다. 보다 구체적인 범주 좀 더 일반적인 범주 일종입니다. 예를 들어, 당좌 예금 계좌는 입금 및 인출이 가능한 일종의 계좌입니다. 마찬가지로 트럭은 큰 물건을 운반하는 데 사용되는 일종의 차량입니다.

상속은 여러 수준을 통해 내려갈 수 있으므로 더욱 구체적인 범주로 이어질 수 있습니다. 예를 들어, 그림 1은 차량에서 상속 된 자동차와 트럭을 보여줍니다. 차에서 물려받은 스테이션 왜건; 그리고 트럭에서 물려받은 쓰레기 트럭. 화살표는보다 구체적인 "하위"범주 (아래쪽 아래)에서 덜 구체적인 "상위"범주 (높은 위쪽)까지 가리 킵니다.

Jeff Friesen

이 예 는 하위 범주가 하나의 직계 상위 범주에서 상태 및 동작을 상속하는 단일 상속 을 보여줍니다 . 반대로 다중 상속을 사용하면 하위 범주가 두 개 이상의 직계 상위 범주에서 상태 및 동작을 상속 할 수 있습니다. 그림 2의 계층은 다중 상속을 보여줍니다.

Jeff Friesen

카테고리는 클래스별로 설명됩니다. Java는 클래스 확장을 통해 단일 상속을 지원합니다 . 여기서 한 클래스는 해당 클래스를 확장하여 다른 클래스에서 액세스 가능한 필드와 메서드를 직접 상속합니다. 그러나 Java는 클래스 확장을 통한 다중 상속을 지원하지 않습니다.

상속 계층 구조를 볼 때 다이아몬드 패턴이 있으면 여러 상속을 쉽게 감지 할 수 있습니다. 그림 2는 차량, 육상 차량, 수상 차량 및 호버크라프트의 맥락에서이 패턴을 보여줍니다.

extends 키워드

Java는 extends키워드 를 통해 클래스 확장을 지원 합니다. 존재하는 경우 extends두 클래스 간의 상위-하위 관계를 지정합니다. 내가 사용하는 아래의 extends클래스 사이의 관계 설정 VehicleCar다음 사이 AccountSavingsAccount:

목록 1. extends키워드는 부모-자식 관계를 지정합니다.

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 }

extends키워드는 클래스 이름 뒤에 다른 클래스 이름 앞에 지정됩니다. 앞의 클래스 이름 extends은 자식을 extends식별하고 뒤의 클래스 이름 은 부모를 식별합니다. extendsJava는 클래스 기반 다중 상속을 지원하지 않기 때문에 여러 클래스 이름을 지정할 수 없습니다.

: 이러한 예는 성문화 관계-A이고 CarA는 전문을 Vehicle하고 SavingsAccountA는 전문 Account. Vehicle그리고 Account로 알려져 있습니다 기본 클래스 , 부모 클래스 또는 수퍼 클래스 . Car그리고 SavingsAccount로 알려진 파생 클래스 , 자식 클래스 또는 서브 클래스 .

최종 수업

확장해서는 안되는 클래스를 선언 할 수 있습니다. 예를 들어 보안상의 이유로. Java에서는 final일부 클래스가 확장되는 것을 방지하기 위해 키워드를 사용합니다 . final에서 와 같이 클래스 헤더 앞에 final class Password. 이 선언이 주어지면 컴파일러는 누군가를 확장하려고하면 오류를보고합니다 Password.

자식 클래스는 부모 클래스와 다른 조상으로부터 액세스 가능한 필드와 메서드를 상속합니다. 그러나 그들은 생성자를 상속하지 않습니다. 대신 자식 클래스는 자체 생성자를 선언합니다. 또한 부모와 구별하기 위해 자신의 필드와 방법을 선언 할 수 있습니다. 목록 2를 고려하십시오.

Listing 2. Account부모 클래스

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; } }

목록 2는 이름과 초기 금액이 모두 생성자에 설정된 일반 은행 계좌 클래스를 설명합니다. 또한 사용자가 입금 할 수 있습니다. (마이너스 금액을 입금하여 인출 할 수 있지만이 가능성은 무시합니다.) 계정을 만들 때 계정 이름을 설정해야합니다.

통화 가치 표현

동전의 수. a double또는 a 를 사용하여 float화폐 가치를 저장 하는 것을 선호 할 수 있지만 그렇게하면 부정확해질 수 있습니다. 더 나은 솔루션을 위해 BigDecimalJava 표준 클래스 라이브러리의 일부인을 고려하십시오 .

Listing 3은 SavingsAccount부모 클래스를 확장 하는 자식 클래스를 보여준다 Account.

Listing 3. 부모 클래스를 SavingsAccount확장 하는 자식 Account클래스

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

SavingsAccount이 추가 필드 나 메소드를 선언 할 필요가 없기 때문에 클래스는 간단하다. 그러나 Account수퍼 클래스 의 필드를 초기화하는 생성자를 선언합니다 . 초기화 Account는의 생성자가 Java의 super키워드 를 통해 호출 되고 괄호로 묶인 인수 목록이 뒤따 를 때 발생 합니다.

super () 호출시기 및 위치

것처럼 this()같은 클래스의 다른 생성자를 호출하는 생성자의 첫 번째 요소 여야, super()슈퍼 클래스의 생성자를 호출하는 생성자의 첫 번째 요소해야합니다. 이 규칙을 어길 경우 컴파일러는 오류를보고합니다. 컴파일러는 또한 super()메서드에서 호출을 감지하면 오류를보고합니다 . super()생성자 에서만 호출 합니다.

목록 4 AccountCheckingAccount클래스를 추가로 확장 합니다 .

Listing 4. 부모 클래스를 CheckingAccount확장 하는 자식 Account클래스

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.

하위 클래스의 메서드 헤더에 @Override주석 을 접두사로 추가하여 컴파일 타임에 메서드를 재정의하는 대신 오버로드하려는 시도를 감지 할 수 있습니다 .

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

지정 @Override하면 주어진 메서드가 다른 메서드를 재정의 함을 컴파일러에 알립니다. 누군가가 대신 메서드를 오버로드하려고하면 컴파일러에서 오류를보고합니다. 이 주석이 없으면 메서드 오버로딩이 합법적이기 때문에 컴파일러는 오류를보고하지 않습니다.

@Override를 사용하는 경우

메서드를 @Override. 이 습관은 과부하 실수를 훨씬 빨리 감지하는 데 도움이됩니다.