Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

개발자입니다

[비트캠프] 52일차(11주차2일) - Java: myapp-14~15, UML, 다형성, 다형적 변수, 메서드 오버라이딩, 메서드 오버로딩 본문

네이버클라우드 AIaaS 개발자 양성과정 1기/Java

[비트캠프] 52일차(11주차2일) - Java: myapp-14~15, UML, 다형성, 다형적 변수, 메서드 오버라이딩, 메서드 오버로딩

끈기JK 2023. 1. 17. 11:25

 

myapp

 

14. 공통 코드(필드,메서드)를 공유하는 방법 : 상속  

 

### 14. 공통 코드(필드,메서드)를 공유하는 방법 : 상속  
- 서로 관련된 클래스에 공통으로 나타나는 코드가 있다면 상속을 이용하여 공유한다. 
- 일반화(generalization)   
  - 클래스들의 공통 코드(필드, 메서드)를 추출하여 별도의 클래스로 정의하는 것.   
  - 이렇게 정의한 클래스를 공유하는 것. 
- 전문화(specialization)   
  - 기존 클래스를 연결하고 필드나 변수를 추가하여 역할을 특화시키는 것.

 

package bitcamp.myapp.vo;

// 회원 데이터를 담을 메모리를 설계한다.
public class Student extends Member {
  private String postNo;
  private String basicAddress;
  private String detailAddress;
  private boolean working;
  private  char gender;
  private byte level;

  public String getPostNo() {
    return postNo;
  }
  public void setPostNo(String postNo) {
    this.postNo = postNo;
  }
  public String getBasicAddress() {
    return basicAddress;
  }
  public void setBasicAddress(String basicAddress) {
    this.basicAddress = basicAddress;
  }
  public String getDetailAddress() {
    return detailAddress;
  }
  public void setDetailAddress(String detailAddress) {
    this.detailAddress = detailAddress;
  }
  public boolean isWorking() {
    return working;
  }
  public void setWorking(boolean working) {
    this.working = working;
  }
  public char getGender() {
    return gender;
  }
  public void setGender(char gender) {
    this.gender = gender;
  }
  public byte getLevel() {
    return level;
  }
  public void setLevel(byte level) {
    this.level = level;
  }
}

 

package bitcamp.myapp.vo;

public class Teacher extends Member {
  private String email;
  private int degree;
  private String school;
  private String major;
  private int wage;

  public String getEmail() {
    return email;
  }
  public void setEmail(String email) {
    this.email = email;
  }
  public int getDegree() {
    return degree;
  }
  public void setDegree(int degree) {
    this.degree = degree;
  }
  public String getSchool() {
    return school;
  }
  public void setSchool(String school) {
    this.school = school;
  }
  public String getMajor() {
    return major;
  }
  public void setMajor(String major) {
    this.major = major;
  }
  public int getWage() {
    return wage;
  }
  public void setWage(int wage) {
    this.wage = wage;
  }
}

 

 

 

여기서 잠깐! - 클래스 간의 관계와 UML

 

5개만 암기하면 관계 파악이 편하다.

① 상속 (inheritance)

Truck은 Car를 상속한다.

 

② 연관 (association)

MemberController가 MemberDao를 (지속적인)사용한다.

사람이 휴대폰을 사용한다.

 

③ 집합 (aggregation) 약 결합

Computer는 Keyboard를 포함하지만 떼놓고 생각할 수는 있다.

container lifecycle ≠ containee lifecycle

 

④ 합성 (composition) 강결합

Computer는 Mainboard를 포함한다. 떼놓고 생각할 수 없다.

container lifecycle = containee lifecycle

 

⑤ 의존 (dependency) : 특정 작업에서 일시적으로 사용

Car가 GasStation을 (일시적으로)사용한다.

 

②~④ 는 서로 비슷하여 구분하지 않고 사용하기도 한다.

 

 

 

 

다형성 (polymorphism)

- polymorphic variables (다형적 변수)

- overloading (오버로딩)

- overriding (오버라이딩; 메서드 재정의)

 

 

 

polymorphic variables

 

Vehicle v; 선언 한다.

v = new Vehicle(); 처럼 v에 sub class의 인스턴스 주소를 저장할 수 있다.

 

 

 

다형적 변수와 형변환

 

Vehicle obj;

obj = new Car();

Car c = (Car) obj; 는 가능하다. Vehicle 가 Car 를 포함하므로 obj에 Car 클래스를 담을 수 있다. 그리고 원래 자료형인 (Car) 로 변환하여 Car c 에 담을 수 있다.

 

obj = new Vehicle();

Car c ≠ (Car) obj; 는 불가하다. Vehicle 형인 obj를 (Car) 로 변환할 수 없다.

컴파일러는 승인한다. 그러나 JVM에서 실행할 때 예외 발생

 

 

Vehicle obj;

obj = new Bike(); 로 Bike 주소를 넣을 수 있다. 이때 Vehicle의 필드 model, capacity 가 생성되고 Bike의 필드 engine이 생성된다. obj.model, obj.capacity로 값에 접근할 수 있다.

 

 

Vehicle obj = new Vehicle(); 시 Vehicle의 인스턴스 필드 생성되고 주소 200이 obj에 저장된다.

Bike b ≠ obj;  ← 상위 클래스의 인스턴스 주소를 담을 수 없다!

만약 담을수 있다 친다면 b.model, b.capacity 는 접근 가능하지만 b.engine 은 접근 불가능하다.

 

 

Car c = new Sedan(); 으로 Vehicle의 필드 model, capacity, Car의 필드 cc, valve, Sedan의 필드 sunroof, auto가 만들어진다.

c.model, c.capacity, c.cc, c.valve 에 접근 가능하지만 c.sunroof, c.auto 에 접근 불가하다. c가 가리키는 인스턴스에는 분명히 Sedan 필드가 있지만, 문법적으로 c는 Car 타입이기 때문에 Car 범위 내에서만 멤버를 사용할 수 있다.

 

 

 

myapp

 

15. 다형적 변수의 활용 (+ 상속 + 오버라이딩)

 

StudentDao, TeacherDao, BoardDao 의 공통점을 모아 ObjectDao 를 만든다.

Object obj; 에는 Object, Member, Student, Teacher, Board 형 모두 담을 수 있다.

 

### 15. 다형성을 이용하여 코드를 재사용하기 
- 다형성(polymorphism): 상황에 맞춰 여러 용도로 사용되는 것   
- 다형적 변수(polymorphic variables):      
- 상위 타입의 변수는 하위 타입의 개체를 가리킬 수 있다.    
- 오버라이딩(Overriding)     
- 상속 받은 수퍼 클래스의 메서드를 서브 클래스의 역할에 맞춰 재정의 하는 것.   
- 오버로딩(Overloading)     
- 파라미터의 타입이나 개수, 순서가 다르더라도 같은 기능을 수행하는 메서드에 대해 같은 이름을 부여함으로써 프로그래밍의 일관성을 제공하는 문법

 

package bitcamp.myapp.dao;

import java.util.Arrays;

public abstract class ObjectDao {
  private static final int SIZE = 100;

  private int count;
  protected Object[] objects = new Object[SIZE];

  public void insert(Object object) {
    this.objects[this.count++] = object;
  }

  public Object[] findAll() {
    return Arrays.copyOf(objects, count);
  }

  public void update(Object object) {
    this.objects[this.indexOf(object)] = object;
  }

  public void delete(Object object) {
    for (int i = this.indexOf(object) + 1; i < this.count; i++) {
      this.objects[i - 1] = this.objects[i];
    }
    this.objects[--this.count] = null; // 레퍼런스 카운트를 줄인다.
  }

  // 객체의 위치를 찾는 것은
  // 객체의 타입에 따라 다를 수 있기 때문에
  // 이 클래스에서 정의하지 말고,
  // 서브 클래스에서 정의할 수 있도록
  // 그 구현의 책임을 위임해야 한다.
  protected abstract int indexOf(Object b);

  public int size() {
    return this.count;
  }

  // 개발하는 중에 서브 클래스들이 공통으로 필요로 하는 기능을 발견하게 된다.
  // 그런 상황이면 이렇게 수퍼 클래스에 해당 메서드를 정의하면 된다.
  public Object get(int i) {
    if (i < 0 || i >= this.count) {
      return null;
    }
    return objects[i];
  }
}

 

package bitcamp.myapp.dao;

import java.sql.Date;
import bitcamp.myapp.vo.Board;

public class BoardDao extends ObjectDao {

  // 가장 최근 게시글의 글 번호를 저장하는 필드
  // 가장 최근 게시글이 삭제되더라도 그 값은 그대로 유지할 것이다.
  int lastNo;

  // Board 객체를 게시글 번호를 찾는 메서드
  public Board findByNo(int no) {
    Board b = new Board();
    b.setNo(no);

    //    int index = this.indexOf(b);
    //
    //    if (index < 0) {
    //      return null;
    //    } else {
    //      return (Board) this.get(index);
    //    }

    return (Board) this.get(this.indexOf(b));
  }

  @Override // 컴파일러에게 오버라이딩을 제대로 했는지 검사해 달라고 표시함
  protected int indexOf(Object obj) {
    for (int i = 0; i < this.size(); i++) {
      if (((Board)this.objects[i]).getNo() == ((Board)obj).getNo()) {
        return i;
      }
    }
    return -1;
  }

  // 수퍼 클래스의 insert()는 객체를 등록할 때 번호를 자동증가시키는 기능이 없다.
  // 그러나 BoardDao는 그런 기능이 필요하다.
  // => 수퍼 클래스의 메서드를 서브 클래스의 역할이나 목적에 맞게 재정의 한다.
  // => 이것을 '오버라이딩(overriding)"이라 부른다.
  @Override
  public void insert(Object object) {
    // 객체를 배열에 담기 전에 그 객체의 번호를 설정한다.
    ((Board) object).setNo(++lastNo);

    // 인스턴스를 생성할 때의 날짜와 시각을 설정한다.
    ((Board) object).setCreatedDate(new Date(System.currentTimeMillis()).toString());

    // 그런 후에 수퍼 클래스에서 상속 받은 insert()를 사용하여 객체를 배열에 보관한다.
    super.insert(object);

    // super.insert() ?
    // => 현재 클래스에서 insert()를 찾지 말고, 수퍼 클래스에서 찾아 올라 가라!
  }
}

 

package bitcamp.myapp.dao;

import java.sql.Date;
import bitcamp.myapp.vo.Student;

public class StudentDao extends ObjectDao {

  int lastNo;

  public Student findByNo(int no) {
    Student s = new Student();
    s.setNo(no);
    return (Student) this.get(this.indexOf(s));
  }

  @Override
  protected int indexOf(Object obj) {
    for (int i = 0; i < this.size(); i++) {
      if (((Student) this.objects[i]).getNo() == ((Student) obj).getNo()) {
        return i;
      }
    }
    return -1;
  }

  @Override
  public void insert(Object object) {
    Student s = (Student) object;
    s.setNo(++lastNo);
    s.setCreatedDate(new Date(System.currentTimeMillis()).toString());

    super.insert(object);
  }
}

 

package bitcamp.myapp.dao;

import java.sql.Date;
import bitcamp.myapp.vo.Teacher;

public class TeacherDao extends ObjectDao {

  int lastNo;

  public Teacher findByNo(int no) {
    Teacher t = new Teacher();
    t.setNo(no);
    return (Teacher) this.get(this.indexOf(t));
  }

  @Override
  protected int indexOf(Object obj) {
    for (int i = 0; i < this.size(); i++) {
      if (((Teacher) this.objects[i]).getNo() == ((Teacher) obj).getNo()) {
        return i;
      }
    }
    return -1;
  }

  @Override
  public void insert(Object object) {
    Teacher t = (Teacher) object;
    t.setNo(++lastNo);
    t.setCreatedDate(new Date(System.currentTimeMillis()).toString());

    super.insert(object);
  }
}

 

 

 

DAO를 generalization → 다형적 변수 + overriding, 추상메서드 + 추상 클래스

 

《abstract》 ObjectDao의 필드는 SIZE, count, Object[ ] 가 있다.

                                        메서드는 insert() ~ indexOf() 가 있다.

get() ← 서브 클래스들에서 공통으로 필요한 기능을 추가

findByNo() ← 번호를 식별자로 사용하는 경우에만 유효하다. 범용으로 사용할 수 있는 메서드가 아니다. 서브 클래스의 역할에 따라 필요할 수는 있겠다.

《abstract》 indexOf() ← sub 클래스마다 구현이 다를 수 있다. 따라서 수퍼 클래스에서 정의하는 것은 소용없다. 서브 클래스가 구현하도록 강제시키는 게 낫다. "추상 메서드"

 

BoardDao의 메서드 indexOf() {-} 는 수퍼 클래스에 선언된(정의된) 메서드를 서브 클래스의 역할에 맞춰서 재정의 → "Overriding(오버라이딩)"

findByNo() {-} ← 메서드 추가

 

 

컴파일러는 실행 전에 문법을 검사한다. 지금 순간에 문법이 옳은지 옳지 않은지 검사한다. 옳으면 기계어로 바꾸고 옳지 않으면 바꾸지 않는다.

 

 

[L 은 Array 를 의미한다.

 

 

 

오버라이딩 (overriding)

 

ObjectDao의 insert() ← 범용성을 고려해 만들었기 대문에 '번호 자동 증가' 와 같은 기능은 수행하지 않는다.

수퍼 클래스의 insert() 메서드를 서브 클래스의 역할(목적)에 맞게 변경한다 = "overriding"

 

 

 

예제 소스

 

 

com.eomcs.oop.ex06

 

 

다형성 - 다형적 변수(polymorphic variables)
package com.eomcs.oop.ex06.a;

public class Exam0110 {

  public static void main(String[] args) {
    Vehicle vehicle = new Vehicle();
    Bike bike = new Bike();
    Car car = new Car();
    Sedan sedan = new Sedan();
    Truck truck = new Truck();

    // 레퍼런스는 같은 타입의 객체를 가리킬 수 있을 뿐만아니라
    // 그 클래스의 서브클래스 객체까지 가리킬 수 있다.
    // 왜? 
    // - 서브 클래스는 항상 상위 클래스의 모든 것을 사용할 수 있기 때문이다.
    // - 서브 클래스의 인스턴스를 만들 때 상위 클래스의 인스턴스 변수도 만든다.
    // - 따라서 상위 클래스의 레퍼런스를 사용하여 그 클래스의 인스턴스 변수를 100% 사용할 수 있다.
    // 
    // 이런 규칙에 따라, 다음 vehicle2 변수는 Vehicle 객체 뿐만아니라
    // Bike, Car, Sedan, Truck 객체까지 다양한 서브클래스의 객체를 가리킬 수 있다.
    // 그래서 vehicle2를 "다형적 변수"의 기능을 갖고 있다 말한다.
    Vehicle vehicle2 = null;
    vehicle2 = bike; // OK
    vehicle2 = car; // OK
    vehicle2 = sedan; // OK
    vehicle2 = truck; // OK

    Car car2 = null;
    car2 = sedan; // OK
    car2 = truck; // OK
    //    car2 = bike; // Error!

    Bike bike2 = null;
    //    bike2 = car; // Error!
    //    bike2 = sedan; // Error!
    //    bike2 = truck; // Error!
  }

}

 

 

package com.eomcs.oop.ex06.a;

public class Exam0111 {

  public static void main(String[] args) {

    Bike bike = new Bike();

    Vehicle vehicle = bike; // OK

    // 이렇게 상위 클래스의 레퍼런스로 하위 클래스의 인스턴스를 가리킬 수 있는 이유?
    // => 하위 클래스의 인스턴스는 상위 클래스의 인스턴스 멤버를 갖고 있기 때문이다.
    // => 그래서 상위 클래스의 레퍼런스로
    //    상위 클래스의 인스턴스 멤버 사용을 보장한다.
    //

    // vehicle을 통해 Vehicle의 인스턴스 변수에 접근할 수 있다.
    vehicle.model = "티코";
    vehicle.capacity = 5;

    // 왜?
    // => Bike 인스턴스에는 수퍼 클래스인 Vehicle의 인스턴스 변수가 있기 때문.
  }

}

 

 

package com.eomcs.oop.ex06.a;

public class Exam0112 {

  public static void main(String[] args) {

    Vehicle v = new Vehicle();
    Bike b = new Bike();
    Car c = new Car();
    Sedan s = new Sedan();
    Truck t = new Truck();

    Bike b2 = null;

    // 하위 클래스의 레퍼런스로 상위 클래스의 인스턴스를 가리킬 수 없다.
    // => 상위 클래스의 인스턴스에는 하위 클래스의 멤버가 없을 수 있기 때문이다.
    //    b2 = v; // 컴파일 오류!

    // 만약 위의 코드가 가능하다면,
    // 다음과 같이 Bike 레퍼런스로 Bike 인스턴스 변수를 사용하려 할 것이다.
    b2.engine = true;

    // 그러나, b2가 실제 가리키는 것은 Bike의 인스턴스가 아니기 때문에
    // Bike의 engine 변수를 사용할 수 없다.
    //
    // 이렇게 개발자가 레퍼런스를 통해 존재하지 않는 인스턴스 멤버를 사용할까봐,
    // 이것을 미리 방지하려고 컴파일 단계에서 이런 사용을 막는 것이다.

  }

}

 

 

package com.eomcs.oop.ex06.a;

public class Exam0113 {

  public static void main(String[] args) {

    Vehicle v = new Vehicle();
    Bike b = new Bike();
    Car c = new Car();
    Sedan s = new Sedan();
    Truck t = new Truck();

    Bike b2 = null;

    // 설사 같은 부모의 자식 클래스라도
    // 다른 클래스의 인스턴스는 가리킬 수 없다.
    // 왜?
    // => Bike 클래스의 멤버(예: 인스턴스 변수)를 갖고 있지 않기 때문이다.
    //
    //    b2 = c; // 컴파일 오류!

    // => 당연히 다른 클래스의 자식 인스턴스도 가리킬 수 없다.
    //    b2 = s; // 컴파일 오류!
    //    b2 = t; // 컴파일 오류!
  }

}

 

 

다형성 - 다형적 변수와 형변환(type casting)
package com.eomcs.oop.ex06.a;

public class Exam0210 {

  public static void main(String[] args) {

    // 상위 클래스의 레퍼런스로 하위 클래스의 인스턴스를 가리킬 때
    Car c = new Sedan();

    c.model = "티코"; // Vehicle의 인스턴스 변수
    c.capacity = 5;  // Vehicle의 인스턴스 변수
    c.cc = 890;      // Car의 인스턴스 변수
    c.valve = 16;    // Car의 인스턴스 변수

    // 레퍼런스가 실제 하위 인스턴스를 가리키고 있다 하더라도,
    // 레퍼런스 타입의 범위를 벗어나서 사용할 수 없다.
    //    c.sunroof = true; // 컴파일 오류!
    //    c.auto = true;    // 컴파일 오류!

    // 왜?
    // => 자바 컴파일러는 레퍼런스가 실제 어떤 인스턴스를 가리키는지 따지지 않고
    //    레퍼런스의 타입에 한정하여 인스턴스나 클래스의 멤버 사용을 허락한다.

    // 해결책?
    // => 레퍼런스 변수가 실제 가리키는 것이 무엇인지 알려줘야 한다.
    // => ((실제 레퍼런스가 가리키는 인스턴스의 타입) 레퍼런스).멤버
    ((Sedan)c).sunroof = true; // OK!
    ((Sedan)c).auto = true;    // OK!

    // => 또는 인스턴스의 원래 클래스 레퍼런스에 저장한 다음에 사용.
    Sedan s = (Sedan)c;
    s.sunroof = true;
    s.auto = true;

    System.out.println("종료!");
  }

}

 

 

package com.eomcs.oop.ex06.a;

public class Exam0211 {

  public static void main(String[] args) {
    Vehicle v1 = new Sedan();

    // model과 capacity 변수는 원래 Vehicle 설계도에 있는 변수이기 때문에
    // 당연히 레퍼런스를 통해 사용할 수 있다.
    v1.model = "티코";
    v1.capacity = 5;

    // 자바 컴파일러는 레퍼런스의 클래스를 보고 사용할 수 있는 변수/메서드 인지 아닌지
    // 판단한다. 비록 v1 변수에 Sedan 객체의 주소가 들어 있다 할지라도,
    // 실제 들어 있는 객체의 주소로 판단하지 않고 레퍼런스가 어떤 클래스냐에 따라 판단한다.
    //
    //    v1.cc = 1980; // 컴파일 오류!
    //    v1.valve = 16; // 컴파일 오류!
    //    v1.sunroof = true; // 컴파일 오류!
    //    v1.auto = true; // 컴파일 오류!

    // 그럼에도 불구하고 레퍼런스가 실제 가리키는 객체의 모든 메모리에 접근하고 싶은가?
    // => 형변환 하라!
    ((Sedan)v1).cc = 1980;
    ((Sedan)v1).valve = 16;
    ((Sedan)v1).sunroof = true;
    ((Sedan)v1).auto = true;

    System.out.printf("%s, %d, %d, %d, %b, %b\n",
        v1.model, v1.capacity,
        ((Sedan)v1).cc, ((Sedan)v1).valve,
        ((Sedan)v1).sunroof, ((Sedan)v1).auto);

    // 각각의 변수에 대해 일일이 형변환해서 사용하기가 불편한가?
    // => 그냥 레퍼런스를 형변환 해서 사용하라!
    Sedan s = (Sedan)v1;
    s.cc = 1980;
    s.valve = 16;
    s.sunroof = true;
    s.auto = true;

    System.out.printf("%s, %d, %d, %d, %b, %b\n",
        s.model, s.capacity,
        s.cc, s.valve,
        s.sunroof, s.auto);


  }

}

 

 

다형성 - 다형적 변수와 형변환(type casting) II
package com.eomcs.oop.ex06.a;

public class Exam0310 {

  public static void main(String[] args) {

    Car c = new Car();

    c.model = "티코"; // Vehicle의 인스턴스 변수
    c.capacity = 5;  // Vehicle의 인스턴스 변수
    c.cc = 890;      // Car의 인스턴스 변수
    c.valve = 16;    // Car의 인스턴스 변수

    // 잘못된 형변환을 할 경우, 
    // => 형변환(type casting)으로 컴파일러를 속일 수는 있지만,
    //    실행할 때 오류가 발생할 것이다.
    // => 속이지 말라!
    Sedan s = (Sedan) c; // 실행할 때 오류 발생! (runtime exception)
    s.sunroof = true;
    s.auto = true;

  }

}

 

 

package com.eomcs.oop.ex06.a;

public class Exam0311 {

  public static void main(String[] args) {
    Vehicle v1 = new Vehicle();

    v1.model = "티코";
    v1.capacity = 5;

    // v1 변수에는 Vehicle 객체가 들어 있다.
    // 그런데 그 주소가 Sedan 객체의 주소라고 속이고 컴파일을 시도하면,
    // 컴파일러는 그러거니 하고 그냥 통과시켜준다.
    // 문제는 실행할 때 들통난다!
    Sedan s = (Sedan)v1; // JVM이 형변환을 처리할 때 진짜 Sedan 객체가 맞는지
    // 검사한다. 따라서 실행 오류 발생!
    s.cc = 1980;
    s.valve = 16;
    s.sunroof = true;
    s.auto = true;

    System.out.printf("%s, %d, %d, %d, %b, %b\n",
        s.model, s.capacity,
        s.cc, s.valve,
        s.sunroof, s.auto);


  }

}

 

 

다형성 - 다형적 변수의 활용
package com.eomcs.oop.ex06.a;

public class Exam0410 {

  // Sedan과 Truck의 모델명과 cc를 출력하라!

  public static void printSedan(Sedan sedan) {
    System.out.printf("모델명: %s\n", sedan.model); // Vehicle의 설계도로 만든 인스턴스 변수
    System.out.printf("cc: %d\n", sedan.cc); // Car 설계도로 만든 인스턴스 변수
    System.out.println("-------------------------");
  }

  public static void main(String[] args) throws Exception {

    Sedan sedan = new Sedan();
    sedan.model = "티코";
    sedan.cc = 800;

    Truck truck = new Truck();
    truck.model = "타이탄II";
    truck.cc = 10000;

    printSedan(sedan);

    // printSedan()의 파라미터는 Sedan 객체의 주소만 받을 수 있다.
    // 그래서 Truck 객체를 전달할 수 없다.
    //    printSedan(truck); // 컴파일 오류!

    // Truck 인스턴스에서 model과 cc 값을 꺼내서 출력할 메서드를
    // 따로 만들어야 한다.
  }

}

 

 

package com.eomcs.oop.ex06.a;

public class Exam0411 {

  // Sedan과 Truck의 모델명과 cc를 출력하라!

  public static void printSedan(Sedan sedan) {
    System.out.printf("모델명: %s\n", sedan.model);
    System.out.printf("cc: %d\n", sedan.cc);
    System.out.println("-------------------------");
  }

  public static void printTruck(Truck truck) {
    System.out.printf("모델명: %s\n", truck.model);
    System.out.printf("cc: %d\n", truck.cc);
    System.out.println("-------------------------");
  }

  public static void main(String[] args) {
    Sedan sedan = new Sedan();
    sedan.model = "티코";
    sedan.cc = 800;

    Truck truck = new Truck();
    truck.model = "타이탄II";
    truck.cc = 10000;

    // Sedan 객체에서 model 과 cc를 뽑아 출력할 때는 해당 메서드를 호출하고,
    printSedan(sedan);

    // Truck은 다음과 같이 그에 대한 메서드를 호출한다.
    printTruck(truck);

    // Sedan의 모델명과 cc를 출력하는 메서드와
    // Truck의 모델명과 cc를 출력하는 메서드를
    // 모두 만들어야 하는 번거로움이 있다.
    // 해결책?
    // => 두 개의 클래스가 같은 조상을 가질 때는 다형적 변수를 활용하라!
  }

}

 

 

package com.eomcs.oop.ex06.a;

public class Exam0412 {

  // Sedan과 Truck의 모델명과 cc를 출력하라!

  // 다형적 변수를 사용하게 되면 동일한 코드를 갖고 있는 메서드를
  // 한 개의 메서드로 통합할 수 있다.
  // => 즉 Sedan 객체와 Truck 객체를 모두 가리킬 수 있는
  //    상위 클래스의 레퍼런스를 선언하면 된다.
  //
  public static void printCar(Car car) {
    System.out.printf("모델명: %s\n", car.model);
    System.out.printf("cc: %d\n", car.cc);
    System.out.println("-------------------------");
  }

  public static void main(String[] args) {
    Sedan sedan = new Sedan();
    sedan.model = "티코";
    sedan.cc = 800;

    Truck truck = new Truck();
    truck.model = "타이탄II";
    truck.cc = 10000;

    // 또다른 해결책?
    // Sedan과 Truck을 모두 처리하는 메서드를 만들어 사용하라!
    printCar(sedan); // OK! 왜? Sedan은 Car의 일종이다.
    printCar(truck); // OK! 왜? Truck도 Car의 서브클래스이다.

  }

}

 

 

다형성 - 다형적 변수와 instanceof 연산자
package com.eomcs.oop.ex06.a;

public class Exam0510 {

  public static void main(String[] args) {
    Vehicle v = new Sedan();

    // instanceof 연산자?
    // => 레퍼런스에 들어있는 주소가 특정 클래스의 인스턴스인지 검사한다.
    // => 또는 그 상위/하위 클래스의 인스턴스인지 검사한다.
    //
    System.out.println(v instanceof Sedan);
    System.out.println(v instanceof Car);
    System.out.println(v instanceof Vehicle);
    System.out.println(v instanceof Object);

    System.out.println(v instanceof Truck);
    System.out.println(v instanceof Bike);
  }

}

 

 

package com.eomcs.oop.ex06.a;

public class Exam0511 {

  public static void main(String[] args) {
    Vehicle v = new Sedan();

    // getClass() ?
    // => 레퍼런스가 가리키는 인스턴스의 실제 클래스 정보를 리턴한다.
    // => == 연산자를 사용하여 특정 클래스의 인스턴스인지 좁혀서 검사할 수 있다.
    //
    // 클래스명.class
    // => 클래스 정보를 갖고 있는 스태틱 변수이다.
    //
    System.out.println(v.getClass() == Sedan.class);
    System.out.println(v.getClass() == Car.class);
    System.out.println(v.getClass() == Vehicle.class);
    System.out.println(v.getClass() == Truck.class);
    System.out.println(v.getClass() == Bike.class);
  }

}

 

 

다형성 - 다형적 변수의 형변환 응용 - instanceof 연산자
package com.eomcs.oop.ex06.a;

public class Exam0520 {

  // Sedan과 Truck, Bike의 모든 정보를 자세히 출력하라!
  // 단, 한 개의 메서드로 처리하라!

  public static void print(Vehicle v) {
    
    System.out.println("[기본정보]");
    System.out.printf("모델명: %s\n", v.model);
    System.out.printf("수용인원: %d\n", v.capacity);

    // 파라미터 v에 들어있는 주소가 Bike인지 Sedan인지 Truck인지를 구분해서 처리해야 한다.
    // 자바는 이런 경우를 대비해 인스턴스의 주소가 어떤 클래스의 주소인지 
    // 판단할 수 있는 연산자를 제공한다. 
    // instanceof 연산자!

    if (v instanceof Bike) {
      Bike bike = (Bike) v;
      System.out.println("[바이크 정보]");
      System.out.printf("엔진의 존재: %b\n", bike.engine);
      
    } else if (v instanceof Car) {
      Car car = (Car) v;
      System.out.println("[자동차 기본정보]");
      System.out.printf("cc: %d\n", car.cc);
      System.out.printf("밸브: %d\n", car.valve);

      if (v instanceof Sedan) {
        Sedan sedan = (Sedan) v;
        System.out.println("[승용차 기본정보]");
        System.out.printf("썬루프: %b\n", sedan.sunroof);
        System.out.printf("자동변속: %b\n", sedan.auto);
        
      } else if (v instanceof Truck) {
        Truck truck = (Truck) v;
        System.out.println("[트럭 정보]");
        System.out.printf("덤프가능: %b\n", truck.dump);
        System.out.printf("중량: %.1f\n", truck.ton);
      }
    }
    System.out.println("-------------------------");
  }

  public static void main(String[] args) {
    Bike bike = new Bike();
    bike.model = "비트오토바이2018";
    bike.capacity = 2;
    bike.engine = true;

    Sedan sedan = new Sedan();
    sedan.model = "티코";
    sedan.capacity = 5;
    sedan.cc = 800;
    sedan.valve = 16;
    sedan.auto = true;
    sedan.sunroof = true;

    Truck truck = new Truck();
    truck.model = "타이탄II";
    truck.capacity = 3;
    truck.cc = 10000;
    truck.valve = 32;
    truck.dump = true;
    truck.ton = 15;

    print(bike);
    
    print(sedan);
    
    print(truck);


  }

}

 

 

다형성 - 다형적 변수의 활용
package com.eomcs.oop.ex06.a;

public class Exam0521 {

  public static void main(String[] args) {
    // 수퍼 클래스의 레퍼런스는 하위 클래스의 인스턴스를 담을 수 있다.
    Vehicle[] arr = new Vehicle[] {
        new Car("비트자동차", 5, 1980, 16), 
        new Bike("캠프모터", 5, true), 
        new Sedan("현대자동차", 5, 1980, 16, true, true), 
        new Truck("기아자동차", 5, 10000, 32, 15f, true)};

    for (int i = 0; i < arr.length; i++) {
      printCar(arr[i]);
    }
  }

  static void printCar(Vehicle v) {
    System.out.printf("모델: %s\n", v.model);
    System.out.printf("수용인원: %s\n", v.capacity);

    if (v instanceof Car) {
      Car c = (Car) v;
      System.out.printf("cc: %s\n", c.cc);
      System.out.printf("밸브: %s\n", c.valve);

      if (v instanceof Sedan) {
        Sedan s = (Sedan) v;
        System.out.printf("썬루프: %b\n", s.sunroof);
        System.out.printf("오토: %b\n", s.auto);
      } else if (v instanceof Truck) {
        Truck t = (Truck) v;
        System.out.printf("톤: %f\n", t.ton);
        System.out.printf("덤프여부: %b\n", t.dump);
      }
    } else if (v instanceof Bike) {
      Bike b = (Bike) v;
      System.out.printf("engine: %b\n", b.engine);
    }
    System.out.println("-----------------------");
  }

}

 

 

 

com.eomcs.oop.ex06.b

 

 

메서드 오버로딩(overloading) - 문법 사용 전
public class Exam0110 {

  static class Calculator {

    // 만약 같은 이름의 메서드를 여러 개 만들 수 없다면,
    // 다음과 같이 같은 일을 수행(두 수를 더하는 일)하는 메서드라도 
    // 이름을 다르게 해야 한다.
    static int plusi(int a, int b) {
      return a + b;
    }

    static int plusi2(int a) {
      return a + a;
    }

    static float plusf(float a, float b) {
      return a + b;
    }


  }

  public static void main(String[] args) {

    // 두 개의 정수를 더할 때는 plusi()를 호출해야 한다.
    int r1 = Calculator.plusi(100, 200);

    // 한 개의 정수를 두 번 더할 때는 plusi2()를 호출해야 한다.
    int r2 = Calculator.plusi2(100);

    // 두 개의 부동소수점을 더할 때는 plusf()를 호출해야 한다.
    float r3 = Calculator.plusf(35.7f, 22.2f);

    System.out.printf("%d, %d, %.1f\n", r1, r2, r3);

    // 같은 더하기 일을 하더라도 더하는 값의 타입이나 개수에 따라
    // 메서드 이름이 다르다면, 메서드를 사용하기가 번거로울 것이다.
    // => 이것이 오버로딩 문법이 등장한 이유이다!

  }
}

 

 

메서드 오버로딩(overloading) - 문법 사용 후
package com.eomcs.oop.ex06.b;

public class Exam0120 {

  static class Calculator {

    // 파라미터의 타입이나 개수가 다르더라도
    // 같은 일을 하는 메서드에 대해서는 같은 이름을 갖게 할 수 있다.
    //
    static int plus(int a, int b) {
      return a + b;
    }

    static int plus(int a) {
      return a + a;
    }

    static float plus(float a, float b) {
      return a + b;
    }
  }

  public static void main(String[] args) {

    // 호출하는 메서드 이름은 같지만,
    // 아규먼트의 타입이나 개수에 따라 호출되는 메서드가 결정된다.
    //
    int r1 = Calculator.plus(100, 200);
    int r2 = Calculator.plus(100);
    float r3 = Calculator.plus(35.7f, 22.2f);
    //    double f4 = Calculator.plus(35.7, 22.2); // 파라미터 타입이나 개수가 일치하는 메서드가 없다.

    System.out.printf("%d, %d, %.1f\n", r1, r2, r3);

    // 오버로딩(overloading)?
    // => 파라미터의 형식(타입과 개수)은 다르지만 
    //    같은 기능을 수행하는 메서드에 대해 같은 이름을 부여함으로써 
    //    프로그래밍의 일관성을 제공하기 위한 문법이다.


  }
}

 

 

메서드 오버로딩(overloading) - 정의하는 규칙과 사용 규칙
package com.eomcs.oop.ex06.b;

public class Exam0210 {
  public static void main(String[] args) {
    // 메서드 사용하기
    // => 같은 이름의 메서드가 여러 개 있을 경우, 
    //    메서드를 호출할 때 넘겨주는 값(아규먼트)의 타입과 개수로
    //    호출할 메서드를 결정한다.

    // => 다음은 m() 메서드 중에서 아규먼트를 받지 않는 메서드를 호출한다.
    A.m();

    // => 다음은 int 값을 받는 m() 을 호출한다.
    A.m(100);

    // => 다음은 String 값을 받는 m()을 호출한다.
    A.m("Hello");

    // => 다음은 int와 String 값을 순서대로 받는 m()을 호출한다.
    A.m(100, "Hello");

    // => 다음은 String과 int 값을 순서대로 받는 m()을 호출한다.
    A.m("Hello", 100);

    // => 아규먼트 타입의 값을 받는 m()이 없기 때문에 컴파일 오류!
    //    A.m(3.14f); // 컴파일 오류!
    //    A.m(100L);  // 컴파일 오류!

    // 메서드를 찾을 때 아규먼트의 타입과 일치하는 메서드를 찾기 때문에
    // 메서드의 파라미터 이름은 아무 상관이 없다.
  }
}

 오버로딩(overloading)
 => 파라미터 타입이나 개수, 순서는 다르지만 
    이름이 같은 메서드를 여러 개 만들 수 있는 문법

 왜 이런 문법이 등장했는가?
 => 파라미터의 형식은 다르지만 같은 기능을 수행하는 메서드에 대해 
    같은 이름을 부여함으로써 프로그래밍의 일관성을 제공하기 위함.  

 

 

메서드 오버로딩(overloading) - 서브 클래스에서 오버로딩하기
package com.eomcs.oop.ex06.b;

public class Exam0220 {
  public static void main(String[] args) {
    // 자식 클래스에서 부모 클래스에 있는 메서드와 
    // 같은 이름의 메서드를 만들어도 오버로딩이다.
    // 물론 파라미터 형식은 달라야 한다. 
    // 
    B.m(); // 부모 클래스인 A의 m()을 호출
    B.m(100, 200, 300); // 자식 클래스인 B의 m(int, int, int)을 호출
  }

}

 

package com.eomcs.oop.ex06.b;

public class A {
  static public void m() {
    System.out.println("m()");
  }

  // 파라미터의 타입이나 개수가 다르지만 이름이 같은 메서드를 여러 개 만들 수 있다.
  static public void m(int a) {
    System.out.println("m(int)");
  }

  static public void m(String a) {
    System.out.println("m(String)");
  }

  static public void m(String a, int b) {
    System.out.println("m(String,int)");
  }

  static public void m(int a, String b) {
    System.out.println("m(int,String)");
  }

  // 주의!
  // => 변수의 이름만 다른 메서드를 중복해서 만들 수 없다.
  // => 이유?
  //    메서드를 찾을 때 파라미터 값의 타입으로 찾기 때문이다.
  //    따라서 다음 메서드는 위의 메서드와 같은 메서드이기 때문에 컴파일 오류이다!
  //
  //  static public void m(int x, String y) {
  //    System.out.println("m(int,String)");
  //  }

  // => 리턴 타입만 다른 메서드를 중복해서 만들 수 없다.
  // => 이유?
  //    메서드를 찾을 때 파라미터 값의 타입으로 찾기 때문이다.
  //    따라서 다음 메서드는 컴파일 오류이다.
  //
  //  static public int m(int a, String b) {
  //    return 0;
  //  }
}

 

package com.eomcs.oop.ex06.b;

public class B extends A {
  
  // 부모 클래스인 A에 이미 m() 이라는 이름을 가진 메서드가 여러 개 있다.
  // 그럼에도 불구하고 파라미터 형식이 다른 메서드를 추가한다면 
  // 이것도 마찬가지로 "오버로딩"이다.
  // 즉 한 클래스 안에서 같은 이름의 메서드를 여러 개 만드는 것만 오버로딩이 아니다.
  // 수퍼 클래스에 있는 메서드와 같은 이름을 가진 메서드를 추가해도 오버로딩이다.
  //
  static void m(int a, int b, int c) {
    System.out.println("m(int,int,int)");
  }
}

 

package com.eomcs.oop.ex06.b;

// 오버로딩 규칙 정리!
//
public class C {

  public void m1() {}

  // 메서드를 찾을 때 이름과 파라미터 타입, 개수로 구분하기 때문에
  // 리턴 타입이 다른 것은 구분할 수 없다.
  //  public int m1() {return 0;} // 컴파일 오류!


  // => 파라미터 타입이 달라야 한다.
  public void m1(float a) {} // OK
  public void m1(byte a) {} // OK
  public int m1(short a) {return 0;} // OK
  public String m1(long a) {return null;} // OK

  // => 파라미터 개수가 달라야 한다.
  public float m1(float a, float b) {return 0f;} // OK
  public void m1(short a, String b) {} // OK

  // => 파라미터 이름이 다른 것으로는 메서드를 구분할 수 없다.
  //  public void m1(float f) {} // 컴파일 오류!

  // => 접근 범위는 상관없다.
  void m1(float a, int b) {} // OK
  private void m1(float a, int b, int c) {} // OK
  protected void m1(float a, int b, int c, String d) {} // OK
}

 

 

 


 

 

조언

 

*5년차 이전까지는 SI와 카카오가 기대하는 바가 비슷하지만 그 이후는 기대하는 바의 차이가 크기 때문에 옮길 수 있을 때 옮겨야 한다.

*나는 왜 의지가 약할까? 라고 생각하지 말라. 우리는 모두 보통사람이다. 보통 사람은 원래 의지가 약하다. 그냥 책상에 앉아있어라. 졸리면 카페인 들이부어라.

*C 언어의 기본 철학은 프로그래머를 신뢰하는 것이다. 잘못된 문법을 검사하지 않는 경우가 있다.

 

 

 


 

과제

 

/