개발자입니다
[비트캠프] 52일차(11주차2일) - Java: myapp-14~15, UML, 다형성, 다형적 변수, 메서드 오버라이딩, 메서드 오버로딩 본문
[비트캠프] 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"
예제 소스
다형성 - 다형적 변수(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("-----------------------");
}
}
메서드 오버로딩(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 언어의 기본 철학은 프로그래머를 신뢰하는 것이다. 잘못된 문법을 검사하지 않는 경우가 있다.
과제
/
'네이버클라우드 AIaaS 개발자 양성과정 1기 > Java' 카테고리의 다른 글
[Java] 예제 소스 정리 - Collection(ArrayList, 제네릭, Iterator, LinkedList) (0) | 2023.01.18 |
---|---|
[비트캠프] 53일차(11주차3일) - Java(오버라이딩, final, Object 클래스, String 클래스) (0) | 2023.01.18 |
[비트캠프] 51일차(11주차1일) - 해커톤: 자유 주제 서비스 CRUD 만들기 (0) | 2023.01.16 |
[비트캠프] 50일차(10주차5일) - Java(상속, specialization, generalization, 추상 클래스, 추상 메서드) (0) | 2023.01.13 |
[Java] 예제 소스 정리 - 상속, 추상 클래스 (0) | 2023.01.12 |