[이것이 자바다] 7장 정리
자바에서의 상속에 대한 기본 개념을 정리한 포스트입니다. 클래스 상속, 부모 생성자 호출, 메소드 재정의, final 클래스, final 메소드, protected 접근 제한다, 타입 변환, 다형성, 객체 타입 확인, 추상 클래스, 봉인된 클래스를 다룹니다.
Jan 11, 2024
Contents
Phone.javaSmartPhone.javaSmartPhoneExam.java핵심 키워드Calculator.javaComputer.javaCalculatorExam.java핵심 키워드Airplane.javaSupersonicAirplane.javaAirplaneExam.javaFlyMode.java핵심 키워드A.javaB.javaC.java핵심 키워드PromotionExam.java핵심 키워드Parent.javaChild.javaChildExam.java핵심 키워드Tire.javaHankookTire.javaKumhoTire.javaTireExam.java핵심 키워드Vehicle.javaBus.javaTaxi.javaDriver.javaDriverExample.java핵심 키워드Person.javaStudent.javaStudentExam.java핵심 키워드Animal.javaCat.javaDog.javaAnimalExam.java핵심 키워드People.javaEmployee.javaManager.javaDirector.javaSealedClassExam.java핵심 키워드결론Phone.java
package ch07; public class Phone { public String model; public String color; Phone(){ System.out.println("phone 기본생성자"); } public Phone(String model, String color) { this.model = model; this.color = color; System.out.println("phone(String, String) 생성자"); } public void bell() { System.out.println("벨이 울립니다."); } public void sendVoice(String message) { System.out.println("나: "+message); } public void receiveVoice(String message) { System.out.println("상대: "+message); } public void hangUp() { System.out.println("전화를 끊습니다."); } }
SmartPhone.java
package ch07; public class SmartPhone extends Phone{ public boolean wifi; public SmartPhone() { super(); // 부모의 기본생성자, super는 부모 객체를 가리킨다. System.out.println("SmartPhone의 기본생성자"); // 생성자의 호출 순서는 부모생성자, 자식생성자 순으로 이뤄진다. } public SmartPhone(String model, String color) { super(model, color); System.out.println("SmartPhone(String, String)의 생성자"); } public SmartPhone(String model, String color, boolean wifi) { super(model, color); this.wifi = wifi; // 부모 생성자에게는 wifi가 없으므로 super 사용 불가. System.out.println("SmartPhone(String, String, boolean)의 생성자"); } public void setWifi(boolean wifi) { this.wifi = wifi; System.out.println("와이파이 상태를 변경합니다."); } public void internet() { System.out.println("인터넷에 연결합니다."); } }
SmartPhoneExam.java
package ch07; public class SmartPhoneExam { public static void main(String[] args) { // 상속 시 생성자 생성 순서 SmartPhone myPhone = new SmartPhone("아이폰", "검정", false); myPhone.model = "아이폰"; myPhone.color = "검정"; myPhone.wifi = false; System.out.println("모델: " + myPhone.model); System.out.println("색상: " + myPhone.color); System.out.println("와이파이: " + myPhone.wifi); } }
핵심 키워드
- 클래스 상속 시 생성자의 호출 순서는 부모생성자, 자식생성자 순으로 이뤄진다.
- super는 부모 객체를 가리키는 레퍼런스 변수다.
Calculator.java
package ch07; public class Calculator { public double areaCircle(double r) { System.out.println("Calculator.areaCircle() 입니다."); return 3.14 * r * r; } }
Computer.java
package ch07; public class Computer extends Calculator{ @Override public double areaCircle(double r) { super.areaCircle(r); // 부모 클래스의 메서드를 호출하고 싶다면 사용. System.out.println("Computer.areaCircle() 입니다."); return r * r * Math.PI; } }
CalculatorExam.java
package ch07; public class CalculatorExam { public static void main(String[] args) { // 메서드를 재정의하는 것을 오버라이딩이라고 한다. int r = 10; Calculator cal = new Calculaator(); System.out.println(cal.areaCircle(r)); Computer com = new Computer(); System.out.println(com.areaCircle(r)); } }
핵심 키워드
- 상속받은 메소드를 자식 클래스에서 재정의 하는 것을 오버라이딩 이라고 한다.
- 메소드가 오버라이딩 되었다면 해당 부모 메소드는 숨겨지고, 자식 메소드가 우선적으로 사용된다.
- @Override를 붙여주면 컴파일 단계에서 정확히 오버라이딩이 되었는지 체크한다.
Airplane.java
package ch07; public class Airplane { public void land() { System.out.println("착륙합니다."); } public void fly() { System.out.println("일반 비행합니다."); } public void takeOff() { System.out.println("이륙합니다."); } }
SupersonicAirplane.java
package ch07; public class SupersonicAirplane extends Airplane{ public FlyMode flymode = FlyMode.NORMAL; @Override public void fly() { if(this.flymode == FlyMode.SUPERSONIC) { System.out.println("초음속 비행합니다."); }else { super.fly(); } } }
AirplaneExam.java
package ch07; public class AirplaneExam { public static void main(String[] args) { SupersonicAirplane sa = new SupersonicAirplane(); sa.takeOff(); sa.fly(); sa.flymode = FlyMode.SUPERSONIC; sa.fly(); sa.flymode = FlyMode.NORMAL; sa.fly(); sa.land(); } }
FlyMode.java
package ch07; public enum FlyMode { // 조잡하게 1,2 와 같은 상수를 설정해두는 게 아니라 명확한 상태를 enum을 통해 설정할 수 있다. NORMAL, SUPERSONIC }
핵심 키워드
- 자식 클래스에서 부모 클래스의 메소드를 오버라이딩 할 경우 부모 메소드는 super. 의 형태로 사용할 수 있으며 원하는 위치에 위치시킬 수 있다.
A.java
package ch07; public class A { protected String field; protected A() { } protected void method() { } }
B.java
package ch07; public class B { public void method() { A a = new A(); a.field = "value"; a.method(); } }
C.java
package ch02; import ch07.A; public class C extends A{ public void method() { // protected는 다른 패키지에 있으면 해당 클래스의 필드, 생성자, 메소드에 접근이 불가능하다. A a = new A(); a.field = "value"; a.method(); super.method(); // 하지만 super로 접근은 가능하다. } }
핵심 키워드
- protected로 접근 제한자를 설정하면 다른 패키지에 있는 클래스는 접근할 수 없다.
- 다른 패키지에 있더라도 상속받은 경우라면 super() 로만 생성자를 호출할 수 있다.
PromotionExam.java
package ch07; class A {} class B extends A{} class C extends A{} class D extends B{} class E extends C{} public class PromotionExam { public static void main(String[] args) { B b = new B(); C c = new C(); D d = new D(); E e = new E(); A ab = b; A ac = c; A ad = d; A ae = e; B bd = d; C ce = e; B be = e; // 상속 관계에 있지 않으므로 컴파일 에러 발생 } }
핵심 키워드
- 상속 계층에서 상위 타입이라면 상위 타입 변수로 자동 타입 변환이 일어날 수 있다.
- 하지만 상속 관계에 있지 않다면 컴파일 에러가 발생한다.
Parent.java
package ch07; public class Parent { public void method1() { System.out.println("parent의 method1 입니다."); } public void method2() { System.out.println("parent의 method2 입니다."); } }
Child.java
package ch07; public class Child extends Parent{ @Override public void method2() { System.out.println("child의 method2 입니다."); } public void method3() { System.out.println("child의 method3 입니다."); } }
ChildExam.java
package ch07; public class ChildExam { public static void main(String[] args) { // Child child = new Child(); // Parent parent = child; // 자동 타입 변환 - 참조 타입만 parent로 바꿨다. // // parent.method1(); // parent.method2(); // child에서 오버라이딩한 메소드가 실행된다. // parent.method3(); // non-visible 해진다. parent로 강제로 형변환 했기 때문 // // Child child2 = (Child)parent; // child2.method3(); // 참조 타입을 다시 돌리면 visible해진다. Child child = new Child(); Parent parent = new Parent(); method(child); // child도 parent이기 때문에 parent를 타입으로 받는 메서드도 실행 가능. method(parent); } static void method(Parent p) { p.method2(); } }
핵심 키워드
- 부모 타입은 자식 타입으로 자동 변환되지 않지만, 캐스팅 연산자를 통해 강제 타입 변환을 할 수 있다.
- 자식 객체가 부모 타입으로 자동 변환되면 부모 타입에 선언된 필드와 메소드만 사용이 가능하다.
- 만약 자식 타입에 선언된 필드와 메소드를 사용해야 한다면, 강제 타입 변환을 통해 다시 자식 타입으로 변환해야 한다.
Tire.java
package ch07; public class Tire { public void roll() { System.out.println("회전합니다."); } }
HankookTire.java
package ch07; public class HankookTire extends Tire{ @Override public void roll() { System.out.println("한국타이어가 회전합니다."); } }
KumhoTire.java
package ch07; public class KumhoTire extends Tire{ @Override public void roll() { System.out.println("금호타이어가 회전합니다."); } }
TireExam.java
package ch07; public class TireExam { public static void main(String[] args) { Car myCar = new Car(); myCar.tire = new Tire(); myCar.run(); myCar.tire = new HankookTire(); myCar.run(); myCar.tire = new KumhoTire(); myCar.run(); } }
핵심 키워드
- 같은 부모를 상속하고 있는 자식 클래스들은 부모의 메소드를 동일하게 가지고 있다.
- 다형성이란 자식 클래스들이 부모 클래스의 메소드를 오버라이딩하고 있다면, 각각의 자식 클래스에서 오버라이딩한 메소드는 다른 실행 결과를 가지는 것이다.
Vehicle.java
package ch07; public class Vehicle { public void run(String name) { System.out.println(name+"이 운전하는 "); // System.out.println("차량이 달립니다."); } }
Bus.java
package ch07; public class Bus extends Vehicle{ // is a 관계 @Override public void run(String name) { super.run(name); System.out.println("버스가 달립니다."); } }
Taxi.java
package ch07; public class Taxi extends Vehicle{ // is a 관계 @Override public void run(String name) { super.run(name); System.out.println("택시가 달립니다."); } }
Driver.java
package ch07; public class Driver { String name; Vehicle vehicle; public void drive(Vehicle vehicle) { // has a 관계 vehicle.run(name); } Driver(String name){ this.name = name; } }
DriverExample.java
package ch07; public class DriverExample { public static void main(String[] args) { Driver driver = new Driver("김김김"); Bus bus = new Bus(); driver.vehicle = bus; driver.drive(bus); Taxi taxi = new Taxi(); driver.vehicle = taxi; driver.drive(taxi); } }
핵심 키워드
- 메소드가 클래스 타입의 매개변수를 가질 경우, 호출할 때 동일한 객체를 제공하는 것이 아니라 자식 객체를 제공할 수도 있다.
- 따라서 어떤 자식 객체가 제공되느냐에 따라 실행 결과가 달라진다.
Person.java
package ch07; public class Person { public String name; public Person(String name) { this.name = name; } public void walk() { System.out.println("걷습니다."); } }
Student.java
package ch07; public class Student extends Person{ public int studentNo; public Student(String name, int studentNo) { super(name); this.studentNo = studentNo; } public void study() { System.out.println("공부를 합니다."); } }
StudentExam.java
package ch07; public class StudentExam { public static void personInfo(Person person) { // person 인스턴스일 경우 System.out.println("name: " + person.name); person.walk(); // student 인스턴스일 경우 if (person instanceof Student student) { System.out.println("studentNo: " + student.studentNo); student.study(); } } public static void main(String[] args) { Person p = new Person("홍길동"); personInfo(p); System.out.println(); Person s = new Student("김길동", 10); personInfo(s); } }
핵심 키워드
- 파라미터가 아니더라도 변수가 참조하는 객체의 타입을 확인하고 싶을 때, instanceof 연산자를 사용한다.
- 이를 이용해 메소드의 매개값으로 특정 인스턴스일 경우만 동작하게 코드를 작성할 수 있다.
Animal.java
package ch07; public abstract class Animal { public void breathe() { System.out.println("숨을 쉰다."); } public abstract void sound(); }
Cat.java
package ch07; public class Cat extends Animal{ @Override public void sound() { System.out.println("야옹"); } }
Dog.java
package ch07; public class Dog extends Animal{ @Override public void sound() { System.out.println("멍멍"); } }
AnimalExam.java
package ch07; public class AnimalExam { public static void main(String[] args) { // Animal animal = new Animal(); // 추상 클래스는 구현 불가. Cat cat = new Cat(); animalSound(cat); Dog dog = new Dog(); animalSound(dog); } public static void animalSound(Animal animal) { // Animal을 상속받을 때 모든 자식이 sound 메소드를 가지기 때문에 구현 가능. animal.sound(); } }
핵심 키워드
- abstract 키워드를 통해 추상 클래스와 추상 메소드를 선언할 수 있다.
- 추상 클래스는 구현이 불가능하다.
- 추상 메소드는 자식 클래스들이 공통적으로 가져야만 하는 것을 정의한 것이다. 하지만 실행 내용은 가지지 않는다.
- 따라서 자식 클래스들은 추상 메소드를 반드시 재정의해야 한다.
People.java
package ch07; public sealed class People permits Employee, Manager{ public String name; public void work() { System.out.println("하는 일이 결정되지 않았습니다."); } }
Employee.java
package ch07; public final class Employee extends People{ @Override public void work() { System.out.println("제품을 생성합니다."); } }
Manager.java
package ch07; public non-sealed class Manager extends People{ @Override public void work() { System.out.println("생산 관리를 합니다."); } }
Director.java
package ch07; public class Director extends Manager{ @Override public void work() { System.out.println("제품을 기획합니다."); } }
SealedClassExam.java
package ch07; public class SealedClassExam { public static void main(String[] args) { People p = new People(); Employee e = new Employee(); Manager m = new Manager(); Director d = new Director(); p.work(); e.work(); m.work(); d.work(); } }
핵심 키워드
- sealed 키워드를 통해 지정한 클래스들만 제외하고 자식 클래스 생성을 제한할 수 있다.
- 봉인된 클래스를 상속한 자식 클래스들은 final 또는 non-sealed 키워드를 반드시 지정해야 한다.
- 이를통해 상속을 제한하거나, 자식 클래스의 제한을 풀 수 있다.
결론
해당 문제를 풀면서 자바에서의 상속에 대한 기본 개념을 익힐 수 있었다.
Share article