2026년 1월 14일 수요일 - 4주차
enum의 주 목적은 도메인(값의 범위)를 제약하는것이며, 가독성 및 타입의 안전성을 확보한다.
강사님께서 상속, 오버라이딩, 다형성은 객체지향 설계의 핵심 요소이며, 이들이 서로 어떻게 연결되는지 하나라도 이해하지 못하면 이 3개의 개념을 명확히 이해하지 못하고 있는것이라고 하셨다. 그리고, 이 3가지 개념이 가장 잘 드러나는 예가 인터페이스(interface)라고 한다.
- 사용 목적: 의존도를 낮추는 코딩을 위함.
- 상속: 공통된 기능을 부모 클래스에 두고 재사용 하는 것. (이미 있는것을 물려받는것)
- 다형성: 부모 타입으로 자식을 다루지만 행동은 자식을 기준으로 실행되는 것 (같은 타입처럼 다루지만 실제 행동은 다르게 하곘다)
- 오버라이딩: 부모의 메서드를 자식에 맞게 다시 정의하는 것. (물려받긴 했지만 행동은 다르게 하겠다)
- extends: 부모로부터 상속을 받아 확장하는 것.
- implement: 추상 클래스 부모로부터 상속을 받아 구현을 하는 것.- 부모는 2개 이상이 될 수 없고 자식은 하나의 부모만을 가져야 하며(다중상속이 불가능), 부모는 자식을 여러개 가질 수 있지만, interface의 경우에는 다중 상속이 가능하다. 하지만 implement만 가능하고, extents는 불가능 하다. (확장은 안되는데 구현은 가능)
[명명법]
- interface: 클래스와 같이 파스칼 케이스(camelCase에서 앞 자리를 대문자로 하는것)를 쓰며, 형용사(명사 뒤에 able이 붙는 식)와 명사를 씀
- 변수: camelCase를 따르며, 명사를 사용한다.
- 클래스: PascalCase를 따르며, 명사를 사용한다
- 메서드: CamelCase를 따르며, 동사를 사용한다
- public static final을 붙이연 더 이상 변경할 수 없는 완전한 상수가 되며, 관례적으로 대문자만으로 이루어지며, 단어가 바뀌면 _를 사용한다. (소문자 사용x)
[p1.App]
package p1;
public class App {
public static void main(String[] args) {
// 상속 문법
AAA aaa = new AAA();
aaa.a1 = 10;
aaa.a2 = 20;
aaa.testA();
BBB bbb = new BBB();
bbb.b1 = 10;
bbb.b2 = 10;
bbb.a1 = 10;
bbb.a2 = 20;
bbb.testA();
bbb.testB();
}
}
class AAA {
public int a1;
public int a2;
public void testA() {
//...
}
}
class BBB extends AAA {
// AAA를 확장해서 BBB라는 클래스를 정의하고 싶다. 즉, 상속받는다는 것.
public int b1;
public int b2;
public void testB() {
// 구현부...
}
}
[p2.App]
package p2;
public class App {
public static void main(String[] args) {
WorldGame worldGame = new WorldGame();
// 이 부분이 코드를 조립시키는 부분. 즉, 다형성이다.
// addPerson 메서드를 호출 할때 아규먼트로 Person 클래스의 메서드 대신
// Person을 상속받고 있는 자식 클래스들의 메서드가 호출되는 것.
worldGame.addPerson(new Student("철수"));
worldGame.addPerson(new Student("민수"));
worldGame.addPerson(new Police("영희"));
worldGame.addPerson(new Doctor("수진"));
worldGame.addPerson(new Chef("아영"));
worldGame.start();
}
}
class WorldGame {
private Person[] personList = new Person[5];
private int count = 0;
// DI
public void addPerson(Person person) {
personList[count++] = person; // 여기에 자식값도 있다
}
public void start() {
System.out.println("아침이 되었습니다.");
// 아침이 되면 모든 사람들은 일을 한다.
for(Person person : personList) {
person.work();
}
System.out.println("점심 시간이 되었습니다.");
// 점심이 되면 모든 사람들은 점심을 먹는다.
for(Person person : personList) {
person.eat();
}
System.out.println("밤 시간이 되었습니다.");
// 밤이 되면 모든 사람들은 잠을 잔다.
for(Person person : personList) {
person.sleep();
}
}
}
// 개념(추상) 클래스 - 상속 전용 클래스
abstract class Person {
protected String name;
public Person(String name) {
this.name = name;
}
// 추상 메서드: 모양만 선언이 되어 있는것을 말함.
// 구현이 불가능하여 무조건 상속을 받는 자식 클래스가 오버라이딩 해야함
public abstract void work();
public void eat() {
System.out.println("[사람] " + name + "은 밥을 먹습니다.");
}
public void sleep() {
System.out.println("[사람] " + name + "은 잠을 잡니다.");
}
}
class Student extends Person { // 자식 클래스
public Student(String name) {
super(name);
}
public void work() { // 이게 메서드를 오버라이딩하는 부분
System.out.println("[학생] " + name + "은 공부를 합니다.");
}
}
class Police extends Person {
public Police(String name) {
super(name);
}
public void work() {
System.out.println("[경찰] " + name + "은 범인을 잡으러 다닙니다.");
}
public void eat() {
System.out.println("[경찰] 밥 먹을 시간이 없습니다. 여전히 범인을 잡습니다.");
}
}
class Doctor extends Person {
public Doctor(String name) {
super(name);
}
public void work() {
System.out.println("[의사] " + name + "는 환자를 돌봅니다.");
}
public void eat() {
System.out.println("[의사] 응급상황을 대비해 긴장하고 급하게 밥을 먹습니다.");
}
}
class Chef extends Person {
public Chef(String name) {
super(name);
}
public void work() {
System.out.println("[셰프] " + name + "은 요리를 합니다.");
}
}
[p3.App]
package p3;
public class App {
public static void main(String[] args) {
// 인터페이스(자바의 꽃): 상속 - 다형성 - 오버라이딩
AAA aaa = new BBB();
// 구현이 불가능해 생성자가 없기 때문에,
// AAA의 인스턴스를 생성할수 없어서 다형성이 강제된다.
aaa.test1();
// abstract이기 때문에 문법적으로 test1이 실행된다.
}
}
interface AAA {
public void test1();
// interface는 구현 불가. abstract가 생략되어 있는것 - 추상화 클래스
// 추상화이기 때문에 알아서 오버라이딩을 써서 구현해라.
/* public abstract void test()1;
void test1(); */ // 이 2개의 경우로도 쓰긴 하지만 위에걸 제일 자주 쓴다.
public void test2();
// 물론 구현이 안되기 때문에 생성자가 없어 인스턴스 생성을 할 수 없다.
}
class BBB implements AAA {
// extends가 아닌 implements를 쓴다. 개념적으론 상속받는것이 맞지만 extends는
// 확장한다는 뜻이 강하기 때문에 implements라는 용어가 새로 나왔다.
// 상속이긴 하지만 부모 클래스를 확장해서 사용한다는 것이 아닌.
// 추상 메서드인 부모 클래스를 구현하는 구현관계에 있다는 뜻이다.
public void test1() {
}
public void test2() {
}
}
[p4.App]
package p4;
public class App {
public static void main(String[] args) {
QWER qwer = new QWER(); // 얘는 그냥 인스턴스 생성
AAA aaa = qwer; // AAA aaa = new QWER(); 랑 같음
BBB bbb = qwer; // 이거랑 위에는 다형성
aaa.test1();
bbb.test2();
// aaa는 test1만 쓸 수 있고, bbb는 test2만 쓸 수 있다.
// aaa에는 interface 클래스 AAA만 있고, bbb에는 interface 클래스
// BBB만 있기 때문에, 당연히 그 안에 있는것만 쓸 수 있음.
}
}
class QWER implements AAA, BBB {
public void test1() { }
public void test2() { }
}
interface AAA {
public void test1();
}
interface BBB {
public void test2();
}
[p5.App]
package p5;
public class App {
public static void main(String[] args) {
}
}
interface AAA {
public static final int NUMBER_TEN = 10; // 완전한 상수 명명법 + 관례
public static void qwer() {
// interface는 인스턴스를 생성할 수 없기 때문에, 인스턴스 생성이 없이,
// interface명으로 바로 호출이 가능한 static 메서드를 사용한다.
System.out.println("asdasd");
}
default public void asdf(){
// 상속 받는게 조금 편해지게 하기 위해서 기본값을 제공해줌.
}
public void test1();
}
// interface끼리는 서로 extends 상속이 된다, 묶어주는 느낌으로.
// 물론 class는 interface를 extends 할수 없고 implements로 구현만 가능하다.
interface BBB extends AAA {
public void test2();
}
class Some implements BBB {
public void test1() {
}
public void test2() {
}
}