[TIL] 240425 <자바> 상속,인터페이스
- -
[8. 상속]
public class 자식클래스 extends 부모클래스 {
}
- 부모 클래스에 새로운 필드와 메서드가 추가되면, 자식 클래스는 이를 상속받아 사용 가능.
- 자식 클래스에 새로운 필드와 메서드가 추가되어도, 부모 클래스에 영향X!
- 따라서 자식 클래스의 멤버 개수는 부모 클래스보다 항상 같거나 많음.
자바는 다중 상속 허용하지 않음!!! (하나의 자식에 대한 부모가 여러개 X!!!)
: 다중 상속을 허용하면 클래스 간의 관계가 복잡해짐
- 만약 자식 클래스에서 상속받는 서로 다른 부모 클래스들이 같은 이름의 멤버를 가지고 있다면?
→ 자식 클래스에서는 이 멤버를 구별할 수 있는 방법이 없다!
final 클래스와 메소드는 상속 불가!
- 클래스에 final 키워드를 지정하여 선언하면 최종적인 클래스가 되므로 더 이상 상속불가!!!
- 메서드에 final 키워드를 지정하여 선언하면 최종적인 메서드가 됨으로 더 이상 오버라이딩불가!!!
Object 클래스
- Java 내 모든 클래스들의 최상위 부모 클래스
- 따라서, 모든 클래스는 Object의 메서드를 사용 가능
- 부모 클래스가 없는 자식 클래스는 컴파일러에 의해 자동으로 Object 클래스를 상속받음.
- Object clone() : 해당 객체의 복제본을 생성하여 반환
- boolean equals(Object object) : 해당 객체와 전달받은 객체가 같은지 여부를 반환
- Class getClass() : 해당 객체의 클래스 타입을 반환
- int hashCode() : 자바에서 객체를 식별하는 정수값인 해시 코드를 반환
- String toString() : 해당 객체의 정보를 문자열로 반환. & Object 클래스에서는 클래스이름 @해쉬코드값 반환.
상속관계 vs 포함관계
- 상속관계 : is - a (”~은 ~(이)다”)
- 포함관계 : has - a (”~은 ~을(를) 가지고 있다”)
<포함관계> : Car 클래스는 Tire, Door, Handle 클래스를 포함하고 있음!
public class Car {
static final String company = "GENESIS"; // 자동차 회사
String model; // 자동차 모델
String color; // 자동차 색상
double price; // 자동차 가격
double speed; // 자동차 속도 , km/h
char gear = 'P'; // 기어의 상태, P,R,N,D
boolean lights; // 자동차 조명의 상태
/* Car 클래스는 Tire,Door,Handle클래스를 포함하고 있음!! */
Tire[] tire; //배열
Door[] door; //배열
Handle handle;
public Car(String model, String color, double price) {
this.model = model;
this.color = color;
this.price = price;
}
public void setTire(Tire ... tire) { //가변 매개변수
this.tire = tire;
}
public void setDoor(Door ... door) { //가변 매개변수
this.door = door;
}
public void setHandle(Handle handle) {
this.handle = handle;
}
. . .
}
public class Main {
public static void main(String[] args) {
// 자동차 객체 생성
Car car = new Car("GV80", "Black", 50000000);
// 자동차 부품 : 타이어, 차문, 핸들 선언
Tire[] tires = new Tire[]{
new Tire("KIA", 150000), new Tire("금호", 150000),
new Tire("Samsung", 150000), new Tire("LG", 150000)
};
Door[] doors = new Door[]{
new Door("LG", "FL"), new Door("KIA", "FR"),
new Door("Samsung", "BL"), new Door("LG", "BR")
};
Handle handle = new Handle("Samsung", "S");
// 자동차 객체에 부품 등록
car.setTire(tires);
car.setDoor(doors);
car.setHandle(handle);
// 등록된 부품 확인하기
for (Tire tire : car.tire) {
System.out.println("tire.company = " + tire.company);
}
System.out.println();
for (Door door : car.door) {
System.out.println("door.company = " + door.company);
System.out.println("door.location = " + door.location);
System.out.println();
}
System.out.println();
// 자동차 핸들 인스턴스 참조형 변수에 저장
Handle carHandle = car.handle;
System.out.println("carHandle.company = " + carHandle.company);
System.out.println("carHandle.type = " + carHandle.type + "\n");
// 자동차 핸들 조작해보기
carHandle.turnHandle("Right");
carHandle.turnHandle("Left");
}
}
오버라이딩
부모 클래스로부터 상속받은 메서드의 내용을 재정의 하는 것
public class SportsCar extends Car{
String engine;
public void booster() {
System.out.println("엔진 " + engine + " 부앙~\n");
}
public SportsCar(String engine) {
this.engine = engine;
}
@Override //어노테이션
public double brakePedal() {
speed = 100;
System.out.println("스포츠카에 브레이크란 없다");
return speed;
}
@Override //어노테이션
public void horn() {
booster();
}
}
super : 부모 클래스의 멤버 참조가능
super(...) : 부모 클래스의 생성자 호출
// 자식 클래스 SportsCar 생성자
public SportsCar(String model, String color, double price, String engine) {
// this.engine = engine; // 오류 발생
super(model, color, price); //부모클래스 생성자 호출
this.engine = engine;
}
<참조변수의 타입변환>
1. 자동 타입 변환 : 부모 타입 변수 = 자식 타입 객체;
public class Main {
public static void main(String[] args) {
// 고래는 포유류이기 때문에 포유류 타입으로 변환될 수 있습니다.
Mammal mammal = new Whale(); //부모타입변수 = 자식타입객체
// 하지만 포유류 전부가 바다에 살고 수영을 할 수 있는 것은 아니기 때문에
// 수영 하다 메서드는 실행 불가
// 즉, 부모 클래스에 swimming이 선언되어있지 않아서 사용 불가능합니다.
// mammalia.swimming(); // 오류 발생
// 반대로 모든 포유류가 전부 고래 처럼 수영이 가능한 것이 아니기 때문에 타입변환이 불가능합니다.
// 즉, 부모타입의 객체는 자식타입의 변수로 변환될 수 없습니다.
// Whale whale = new Mammal(); // 오류 발생
mammal.feeding();
}
}
2. 강제 타입 변환 : 자식 타입 변수 = (자식 타입) 부모 타입 객체;
- 자식 타입 객체가 부모 타입으로 자동 타입 변환된 후, 다시 자식 타입으로 변환될 때만 강제 타입 변환이 가능!
- 부모 타입 변수로는 자식 타입 객체의 고유한 멤버를 사용할 수 없기 때문에 사용이 필요한 경우가 생겼을 때 강제 타입 변환을 사용함.
// 자식타입객체가 자동 타입변환된 부모타입의 변수
Mammal mammal = new Whale();
mammal.feeding();
// 자식객체 고래의 수영 기능을 사용하고 싶다면
// 다시 자식타입으로 강제 타입변환을 하면된다.
Whale whale = (Whale) mammal;
whale.swimming();
다형성
여러 가지 형태를 가질 수 있는 능력
public class Car {
//Tire : 부모 클래스
Tire tire;
public Car(Tire tire) {
this.tire = tire;
}
//HankookTire : 자식클래스
Tire getHankookTire() {
return new HankookTire("HANKOOK");
}
//KiaTire : 자식클래스
Tire getKiaTire() {
return new KiaTire("KIA");
}
}
public class Main {
public static void main(String[] args) {
// '매개변수' 다형성 확인!
//Car생성자에서 매개변수 타입이 부모 타이어이기 때문에
//자식 타이어 객체들을 매개값으로 전달가능
Car car1 = new Car(new KiaTire("KIA"));
Car car2 = new Car(new HankookTire("HANKOOK"));
// '반환타입' 다형성 확인!
//반환타입이 부모 타이어이기 때문에 자식 타이어 객체들을 반환값으로 지정가능
Tire hankookTire = car1.getHankookTire();
//자동타입변환이 된 반환값인 자식 타이어객체를 강제타입변환가능
KiaTire kiaTire = (KiaTire) car2.getKiaTire();
// 오버라이딩된 메서드 호출
car1.tire.rideComfort(); // KIA 타이어 승차감은 60
car2.tire.rideComfort(); // HANKOOK 타이어 승차감은 100
}
}
< instanceof > : 다형성 기능으로 인해 해당 클래스 객체의 원래 클래스명을 체크하는 것이 필요
- {대상 객체} instance of {클래스 이름} : 응답값은 boolean
// 다형성
class Parent { }
class Child extends Parent { }
class Brother extends Parent { }
public class Main {
public static void main(String[] args) {
Parent p = new Parent();
System.out.println(p instanceof Object); // true 출력
System.out.println(p instanceof Parent); // true 출력
System.out.println(p instanceof Child); // false 출력
Parent c = new Child();
System.out.println(c instanceof Object); // true 출력
System.out.println(c instanceof Parent); // true 출력
System.out.println(c instanceof Child); // true 출력
}
}
추상 클래스
public abstract class 추상클래스명 {
abstract 리턴타입 메서드이름(매개변수, ...);
}
- 추상 메서드를 포함가능 (없어도됨)
- 추상 메서드는 일반적인 메서드와는 다르게 블록{ }이 없음!!!! (내용없음!) - 추상 클래스는 자식 클래스에 상속되어 자식 클래스에 의해서만 완성가능
- 여러 개의 자식 클래스들에서 공통적인 필드나 메서드를 추출해서 생성가능
<추상 클래스를 상속 시>
상속받은 클래스에서 추상 클래스의 추상 메서드는 반드시 오버라이딩 되어야함!
public class 클래스명 extends 추상클래스명 {
@Override
public 리턴타입 메서드이름(매개변수, ...) {
// 실행문
}
}
[9. 인터페이스]
상속 관계가 없는 다른 클래스들이 서로 동일한 행위 즉, 메서드를 구현해야 할 때
인터페이스는 구현 클래스들의 동일한 사용 방법과 행위를 보장가능
- 인터페이스는 스펙이 정의된 메서드들의 집합
- 인터페이스의 구현 클래스들은 반드시 정의된 메서드들을 구현해야함.
- 이러한 특징은 인터페이스에 다형성을 적용할 수 있게해줌
- 인터페이스는 클래스와 마찬가지로 public, default 접근 제어자를 지정가능
public interface 인터페이스명 {
public static final char A = 'A';
static char B = 'B';
final char C = 'C';
char D = 'D';
void turnOn(); // public abstract void turnOn();
}
- 모든 멤버 변수는 public static final이어야 함 (생략가능)
- 모든 메서드는 public abstract이어야 함 (생략가능 but static 메서드와 default 메서드 예외)
- 생략되는 제어자는 컴파일러가 자동으로 추가해줍니다.
<인터페이스 구현>
추상 클래스와 마찬가지로 직접 인스턴스를 생성할 수 없기 때문에 클래스에 구현되어 생성됨
public class 클래스명 implements 인터페이스명 {
// 추상 메서드 오버라이딩
@Override
public 리턴타입 메서드이름(매개변수, ...) {
// 실행문
}
}
- 인터페이스의 추상 메서드는 구현될 때 반드시 오버라이딩 되어야 함!!!
- 만약 인터페이스의 추상 메서드를 일부만 구현해야 한다면, 해당 클래스를 추상 클래스로 변경해주면됨
<인터페이스 간 상속>
- 인터페이스 간의 상속은 implements가 아니라 extends 키워드 사용
- 인터페이스는 클래스와는 다르게 다중 상속 가능
public class Main extends D implements C { //D클래스 상속 & C 인터페이스
@Override //인터페이스의 추상 메서드는 구현될 때 반드시 오버라이딩되어야함!!!
public void a() {
System.out.println("A");
}
@Override //인터페이스의 추상 메서드는 구현될 때 반드시 오버라이딩되어야함!!!
public void b() {
System.out.println("B");
}
@Override //오버라이딩
void d() {
super.d();
}
public static void main(String[] args) {
Main main = new Main();
main.a();
main.b();
main.d();
}
}
//인터페이스
interface A {
void a(); //인터페이스의 모든 메서드는 public abstract
}
interface B {
void b(); //인터페이스의 모든 메서드는 public abstract
}
interface C extends A, B { //인터페이스A,B 다중상속받음
}
class D {
void d() {
System.out.println("D");
}
}
default 메소드
추상 메서드의 기본적인 구현을 제공
- 블럭{ }이 존재해야함
- 접근 제어자가 public이며 생략이 가능
- 추상 메서드가 아니기 때문에 인터페이스의 구현체들에서 필수로 재정의 할 필요 없음 (오버라이드할 필요X!)
static 메소드
인터페이스에서 static 메서드 선언 가능
- static의 특성 그대로. 인터페이스의 static 메서드 또한 객체 없이 호출 가능
- 접근 제어자를 생략하면 컴파일러가 public을 추가해줌
public class Main implements A {
@Override //추상메서드 오버라이드 필수
public void a() {
System.out.println("A");
}
public static void main(String[] args) {
Main main = new Main();
main.a();
// default 메서드의 재정의 없이 바로 사용가능
main.aa();
System.out.println();
// static 메서드 aaa() 호출
//객체 없이 호출 가능!!!!!!
A.aaa();
}
}
interface A {
void a(); //인터페이스의 모든 메서드는 추상메서드
default void aa() { //default 메서드
System.out.println("AA");
}
static void aaa() { //static 메서드
System.out.println("static method");
}
}
<인터페이스의 타입변환>
1. 자동 타입 변환 : 인터페이스 변수 = 구현객체;
public class Main {
public static void main(String[] args) {
// A 인터페이스에 구현체 B 대입
A a1 = new B();
// A 인터페이스에 구편체 B를 상속받은 C 대입
A a2 = new C();
}
}
interface A { }
class B implements A {}
class C extends B {}
2. 강제 타입 변환 : 구현 객체 타입 변수 = (구현 객체 타입) 인터페이스 변수;
public class Main {
public static void main(String[] args) {
// A 인터페이스에 구현체 B 대입
A a1 = new B();
a1.a();
//a1은 인터페이스 A타입이기 때문에(자동 형변환) a() 메서드만 가지고 있음
// a1.b(); // 불가능 : B가 A로 형변환될 때 B에만 있는 b()메서드는 없음
System.out.println("\nB 강제 타입변환");
B b = (B) a1;
b.a();
b.b(); // 강제 타입변환으로 b()메서드 사용 가능하게됨
System.out.println();
// A 인터페이스에 구편체 B를 상속받은 C 대입
A a2 = new C();
a2.a();
//a2.b(); // 불가능
//a2.c(); // 불가능
System.out.println("\nC 강제 타입변환");
C c = (C) a2;
c.a();
c.b(); // 강제 타입변환으로 사용 가능
c.c(); // 강제 타입변환으로 사용 가능
}
}
interface A {
void a();
}
class B implements A {
@Override
public void a() {
System.out.println("B.a()");
}
public void b() {
System.out.println("B.b()");
}
}
class C extends B {
public void c() {
System.out.println("C.c()");
}
}
<인터페이스의 다형성>
(부모 추상클래스) Tv
public abstract class Tv { //부모 추상 클래스
private String company; // 티비 회사
private int channel = 1; // 현재 채널 상태
private int volume = 0; // 현재 볼륨 상태
private boolean power = false; // 현재 전원 상태
public Tv(String company) { //생성자
this.company = company;
}
public void displayPower(String company, boolean power) {
if(power) {
System.out.println(company + " Tv 전원이 켜졌습니다.");
} else {
System.out.println(company + " Tv 전원이 종료되었습니다.");
}
}
public void displayChannel(int channel) {
System.out.println("현재 채널은 " + channel);
}
public void displayVolume(int volume) {
System.out.println("현재 볼륨은 " + volume);
}
public String getCompany() {
return company;
}
public int getChannel() {
return channel;
}
public int getVolume() {
return volume;
}
public boolean isPower() {
return power;
}
public void setChannel(int channel) {
this.channel = Math.max(channel, 0);
}
public void setVolume(int volume) {
this.volume = Math.max(volume, 0);
}
public void setPower(boolean power) {
this.power = power;
}
}
(자식클래스1) SamsungTv
public class SamsungTv extends Tv implements MultiRemoteController{
public SamsungTv(String company) {
super(company);
}
//인터페이스의 추상클래스들 오버라이드
@Override
public void turnOnOff() {
setPower(!isPower());
displayPower(getCompany(), isPower());
}
@Override
public void channelUp() {
setChannel(getChannel() + 1);
displayChannel(getChannel());
}
@Override
public void channelDown() {
setChannel(getChannel() - 1);
displayChannel(getChannel());
}
@Override
public void volumeUp() {
setVolume(getVolume() + 1);
displayVolume(getVolume());
}
@Override
public void volumeDown() {
setVolume(getVolume() - 1);
displayVolume(getVolume());
}
}
(자식클래스2) LgTv
public class LgTv extends Tv implements MultiRemoteController {
public LgTv(String company) {
super(company);
}
//인터페이스의 추상클래스들 오버라이드
@Override
public void turnOnOff() {
setPower(!isPower());
displayPower(getCompany(), isPower());
}
@Override
public void channelUp() {
setChannel(getChannel() + 1);
displayChannel(getChannel());
}
@Override
public void channelDown() {
setChannel(getChannel() - 1);
displayChannel(getChannel());
}
@Override
public void volumeUp() {
setVolume(getVolume() + 1);
displayVolume(getVolume());
}
@Override
public void volumeDown() {
setVolume(getVolume() - 1);
displayVolume(getVolume());
}
}
(인터페이스) MultiRemoteController
public interface MultiRemoteController {
void turnOnOff();
void channelUp();
void channelDown();
void volumeUp();
void volumeDown();
// 매개변수와 반환타입 다형성 확인 메서드
default MultiRemoteController getTV(Tv tv) {
if(tv instanceof SamsungTv) {
return (SamsungTv) tv;
} else if(tv instanceof LgTv){
return (LgTv) tv;
} else {
throw new NullPointerException("일치하는 Tv 없음");
}
}
}
Main 클래스
public class Main {
public static void main(String[] args) {
// LG TV 구현체를 조작
//자동 형변환
MultiRemoteController mrc = new LgTv("LG");
mrc.turnOnOff();
mrc.volumeUp();
mrc.channelDown();
mrc.channelUp();
mrc.turnOnOff();
// 조작 대상을 Samsung TV로 교체
//TV 구현 객체를 교체해도 멀티 리모컨 인터페이스 변수는 전혀 수정 작업 없이
//그대로 기능 호출가능
System.out.println("\n<Samsung TV로 교체>");
mrc = new SamsungTv("Samsung");
mrc.turnOnOff();
mrc.channelUp();
mrc.volumeDown();
mrc.volumeUp();
mrc.turnOnOff();
//멀티 리모컨으로 티비를 사용하는 방법은 동일하지만
//어떤 TV 구현 객체가 대입되었느냐에 따라 실행 결과가 다르게 나옴을 통해
//다형성이 적용되었음을 확인가능
// 매개변수, 반환타입 다형성 적용가능
System.out.println("\n<매개변수, 반환타입 다형성 체크>");
//1. 인터페이스의 default 메서드 getTV의 매개변수로 Tv가 들어와야하는데
// 여기서 SamsungTv인스턴스생성하므로
// SamsungTv가 Tv로 자동 형변환되어 getTV의 매개변수로 들어가게됨
//2. getTV의 리턴값은 SamsungTv인데 인터페이스로 자동 형변환된 samsung
MultiRemoteController samsung = mrc.getTV(new SamsungTv("Samsung"));
samsung.turnOnOff();
//인터페이스 samsung을 다시 강제 형변환하여 SamsungTv가 됨.
SamsungTv samsungTv = (SamsungTv) samsung;
samsungTv.turnOnOff();
System.out.println();
MultiRemoteController lg = mrc.getTV(new LgTv("LG"));
lg.turnOnOff();
LgTv lgTv = (LgTv) lg;
lgTv.turnOnOff();
}
}
- 멀티 리모컨 인터페이스 변수 = TV 구현 객체;
- TV 구현 객체를 교체해도 멀티 리모컨 인터페이스 변수는 전혀 수정 작업 없이 그대로 기능을 호출가능
- 멀티 리모컨으로 티비를 사용하는 방법은 동일하지만, 어떤 TV 구현 객체가 대입되었느냐에 따라
실행 결과가 다르게 나옴을 통해 다형성이 적용되었음을 확인
- 인터페이스도 마찬가지로 매개변수와 반환 타입에서 다형성 적용가능
- 위 예제는 반환 타입에는 인터페이스, 매개변수에는 추상 클래스로 다형성이 적용되어있습니다.- 인터페이스의 default 메서드
'TIL' 카테고리의 다른 글
[TIL] 240428 <자바> 예외처리 (0) | 2024.04.29 |
---|---|
[TIL] 240426 <자바> 계산기 만들기 (0) | 2024.04.26 |
[TIL] 240424 <자바> 클래스 (0) | 2024.04.24 |
[TIL] 240423 <자바> 연산자,조건문,반복문,배열,컬렉션 (0) | 2024.04.23 |
[TIL] 240422 <자바> JVM,변수 (0) | 2024.04.22 |
소중한 공감 감사합니다