on
자바 기초 03 - 클래스/상속/오버로딩
자바 기초 03 - 클래스/상속/오버로딩
유투버 '데어프로그래밍'님 강의 참조
01 클래스
클래스는 상태(필드)와 행위(메소드)의 개념으로 정의 된다.
자동차 클래스(필드) 상태: Color = 파란색 Name = 소나타 Brand = 현대 Power = 2000 Speed = 0
클래스의 상태는 변할 수 있는게 있고 (Speed) 변하지 못하는것이 있다 (이름/브랜드 등)
바뀔 수 있는 상태의 변수들은 어떠한 행위가 이루어지면 바뀔 수 있다 (예: 메소드를 통한 행위). 즉 여기에서 자바에서 가장 중요한 '객체지향(OOP)'개념이 나온다.
첫번째 객체지향 중요 개념 - 상태는 스스로 변하는게 아니라 행위에 의해서 변한다 다시말해 클래스의 필드는
메서드에 의해서 변한다
첫번째 객체지향 중요 개념 - 상태는 스스로 변하는게 아니라 행위에 의해서 변한다 다시말해 클래스의 필드는 메서드에 의해서 변한다 또한 원인없이 상태가 변하면 잘못된 프로그래밍이다
행위(메서드) 엑셀() { speed +=; } 브레이크() { speed -=; }
예를 통해서 보자
class Player{ String name; private int thirsty; //(0~100) 3번 시나리오를 위해 'private' 추가 public Player(String name, int thirsty) { this.name = name; this.thirsty = thirsty; } //원인을 위한 메소드 void Drink() { System.out.println("Drank water"); this.thirsty = this.thirsty - 50; } int thirstyStatus() { return this.thirsty; } } public class OOPEx01 { public static void main(String[] args) { Player p1 = new Player("Kim", 100); System.out.println("Name: "+p1.name); System.out.println("thirst : "+p1.thirstyStatus()); //1. 첫번째 시나리오 = 원인없이 상태 변경 (x) //p1.thirsty = 50; //2. 원인을 통해 상태가 행위를 변경함, 하지만 실수 가능성이 있는 코드 //p1.Drink(); //System.out.println("thirst : "+p1.thirsty); //3.private을 걸어 변수 접근 차단 p1.Drink(); System.out.println("thirstyStatus: "+p1.thirstyStatus()); } } Name: Kim thirst : 100 Drank water thirstyStatus: 50
객체지향 프로그래밍에서는 '상태는 행위를 통해 변한다' 누군가는 상태를 직접 변경 할 수 있기 때문에 직접 접근을 하지 못하게 'private'을 쓰자 접근 제어자 + 적절한 메소드를 수반하여 원인을 통해 행위가 이루어지면서 상태가 변해야 한다
02 상속
'extends'로 쓰이고 상속(확장)한다는 개념으로 보면 된다.
상속은 '추상화' 가능, '상태/행위'를 가져와서 쓸 수 있으므로 엄청나게 편리하다 (상속시에는 타입이 일치해야 한다. 쉽게 말해 자동차는 엔진을 상속할 수 없지만 치즈햄버거는 햄버거를 상속 할 수 있는 개념)
꼭 굳이 상속을 안해도 된다. 아래 두코드를 비교해보면 컴포지션으로도 가능하고 상속해서도 가능하다.(편한걸 쓰자)
타입이 다른경우 상속을 하지 못하지만 불러와서 쓸 수는 있다. (콤포지션이라고 부름)
class Engine { int power = 2000; } class Car { Engine e; public Car(Engine e) { this.e = e; } } public class OOPEx02 { public static void main(String[] args) { Engine e1 = new Engine(); Car c1 = new Car(e1); System.out.println("Power: " + c1.e.power); } } Power: 2000
상속시에는 상속하는 클래스의 모든 변수 및 기능을 가져와 쓸 수 있고, 자기 클래스에 선언된것을 먼저 적용하게 된다. 또한 변수 및 메소드는 따로 정의 안해도 쓸 수 있다. (나중에 Overriding을 배우면 가져오는 메소드를 변형 해서 쓸 수 있다)
class Hamburger { String name = "Hamburger"; String topping = "cavage"; String topping2 = "patty"; } //상속시에 상태/행위를 물려받게되고 타입이 일치 해야 한다 class CheeseBurger extends Hamburger { String name = "Cheese"; } public class OOPEx02 { public static void main(String[] args) { CheeseBurger ch1 = new CheeseBurger(); System.out.println("Name: " + ch1.name); System.out.println("Topping: " + ch1.topping); System.out.println("Topping: " + ch1.topping2); } } Name: Cheese Topping: cavage Topping: patty
03 오버로딩
가장 기본적인 오버로딩 예를 보자
class 전사 { String name = "전사"; void 기본공격(궁사 e1) { System.out.println("검으로 " +e1.name+" 공격"); } } class 궁사 { String name = "궁수"; void 기본공격(광전사 e1) { System.out.println("활로 "+e1.name+" 공격"); } } class 광전사 { String name = "광전사"; void 기본공격(전사 e1) { System.out.println("도끼로 "+e1.name+" 공격"); } } public class OOPEx03 { public static void main(String[] args) { 전사 u1 = new 전사(); 궁사 u2 = new 궁사(); 광전사 u3 = new 광전사(); u1.기본공격(u2); u2.기본공격(u3); u3.기본공격(u1); } }
→ 이렇게 되면 각각의 클래스가 가진 기본공격에는 지정되어있는 다른클래스밖에 넣지 못한다. 해결은 가능하다 아래처럼
class 전사 { String name = "전사"; void 기본공격(궁사 e1) { System.out.println("검으로 " +e1.name+" 공격"); } void 기본공격2(광전사 e1) { System.out.println("검으로 " +e1.name+" 공격"); } }
→ 가장 치명적인 단점 및 해서는 안될 코드이다. 공격 대상이 늘어나면 기본공격 메소드도 계속해서 늘어나야 한다. 이때 쓰는게 오버로딩이다.
class 전사 { String name = "전사"; void 기본공격(궁사 e1) { System.out.println("검으로 " +e1.name+" 공격"); } void 기본공격(광전사 e1) { System.out.println("검으로 " +e1.name+" 공격"); } } public class OOPEx03 { public static void main(String[] args) { 전사 u1 = new 전사(); u1.기본공격(u3);
→ 이렇게 편하게 하나의 메소드 이름으로 통일하여 오버로딩을 할 수 있다. 하지만 여기에도 치명적인 단점이 있다.
더보기 class 전사 {
String name = "전사";
void 기본공격(궁사 e1) {
System.out.println("검으로 " +e1.name+" 공격");
}
void 기본공격(광전사 e1) {
System.out.println("검으로 " +e1.name+" 공격");
}
void 기본공격(엘프 e1) {
System.out.println("검으로 " +e1.name+" 공격");
}
void 기본공격(흑마법사 e1) {
System.out.println("검으로 " +e1.name+" 공격");
}
}
class 궁사 {
String name = "궁수";
void 기본공격(광전사 e1) {
System.out.println("활로 "+e1.name+" 공격");
}
}
class 광전사 {
String name = "광전사";
void 기본공격(전사 e1) {
System.out.println("도끼로 "+e1.name+" 공격");
}
}
class 마법사 {
String name = "마법사";
void 기본공격(전사 e1) {
System.out.println("마법으로 "+e1.name+" 공격");
}
}
class 엘프 {
String name = "엘프";
void 기본공격(전사 e1) {
System.out.println("표창으로 "+e1.name+" 공격");
}
}
class 흑마법사 {
String name = "흑마법사";
void 기본공격(전사 e1) {
System.out.println("마법검으로 "+e1.name+" 공격");
}
}
public class OOPEx03 {
public static void main(String[] args) {
전사 u1 = new 전사();
궁사 u2 = new 궁사();
광전사 u3 = new 광전사();
엘프 u4 = new 엘프();
흑마법사 u5 = new 흑마법사();
u1.기본공격(u2);
u2.기본공격(u3);
u3.기본공격(u1);
u1.기본공격(u3);
u1.기본공격(u4);
u1.기본공격(u5);
}
}
→ 오버로딩은 메소드를 통일해서 편하게 가져다 쓸 수 있지만 이렇게 객체가 가히 급수적으로 늘어나면 한도 끝도 없이 계속해서 만들어 줘야 한다 (유닛 100개 = 기본공격 100개)
→ 오버로딩은 어느정도 경우의 수에 제한이 있으면 편하다. 경우의 수가 많다면 오버로딩의 한계는 분명하다. 가장 간단하게 해결하는 방법은 상속을 받아서 오버라이딩(재정의)해서 쓰는게 가장 베스트이다. (따로 선언도 안해도되고 필요기능만 수정해서 쓰면 된다.)
from http://treasure0326.tistory.com/121 by ccl(A) rewrite - 2021-11-01 12:01:14