미니 프로젝트 동향 살피기
객체지향의 가장 중요한 점 => 클래스 사이의 관계 파악하기
App (조건문 블록으로 분류)
-> App (멤버 추가, 조회), App1(프로젝트 추가, 조회), App2(작업 추가, 조회) 각 클래스마다 main() 1개씩
-> App (의 main()안으로 합침) 메서드가 너무 길어져서 관리하기 힘들어짐
-> App 를 main(), addMember(), listMember(), addProject(), listProejct(), addTask(), listTask(), prompt() 메서드로 분류
-> App 안에 내장 클래스(nested class) 추가 Member, Project, Task -> 데이터 타입 새로 정의
-> App, MemberHandler(Member 중첩), ProjectHandler(Project 중첩), TaskHandler(Task 중첩), Prompt
App 클래스의 메인 메서드가 나머지 클래스의 메서드를 사용하는 구조
-> com.eomcs.pms : App
com.eomcs.pms.handler : MemberHandler, ProjectHandler, TaskHandler
com.eomcs.util : Prompt
관리시스템 - CRUD(등록, 수정, 조회, 삭제)가 웹 개발의 기본
회원관리 / 로그인 / 로그아웃까지는 구현할 수 있어야한다.
상속(inheritance)
eomcs-java-basic/ src/ main/ java com.eomcs.oop.ex05.a~d
상속이 필요한 이유
여러 고객사에게 Calculator 클래스(plus(), minus() 메서드 내장)로 서비스를 제공한다고 가정하자.
한 고객사에게만 multiple() 추가 서비스를 제공해야한다면?
1. 직접 multiple() 을 Calculator 클래스 안에 추가한다.
Calculator에 직접 추가하면 multiple() 이 필요 없는 고객사까지 영향을 받으며 다른 코드들이 영향을 받아 오류가 생길 위험이 높아진다.
eomcs-java-basic/ src/ main/ java com.eomcs.oop.ex05.b
썬루프 장착 여부와 자동 변속 여부를 추가하고 싶다면 직접 클래스에 생성자를 추가해서 그 안에 변수를 추가하는 방법.
기존의 소스 코드를 손댔다가 심각한 오류가 발생할 수 있다. 또한 계속 코드를 계속 덧붙이다보면 누더기 코드가 될 수 있다.
2. 그 고객사만을 위한 Calculator2 클래스를 별도로 만들어 원본을 복사하고 거기에 multiple()을 추가한다.
기존의 코드가 영향을 받지 않아 좋은 방법이지만, 각각 다른 것들을 원하는 고객사가 매우 많아진다면 그만큼 너무 많은 클래스들이 복사되어 관리하기 힘들어진다.
eomcs-java-basic/ src/ main/ java com.eomcs.oop.ex05.c
기존 코드를 손대지 않고 Car을 복사하여 Car2 클래스를 만들고 썬루프 장착 여부와 자동 변속 여부 기능을 추가하는 방법
그러나 이런식으로 클래스를 무한으로 복사하다보면 너무 많은 클래스와 코드가 생기고, 이 클래스 중에서 하나라도 오류가 발생하면 복사된 모든 클래스에서 버그를 잡아야하는 상황이 발생할 수 있다.
3. 원본 클래스를 기능을 그대로 쓰는 다른 클래스(상속)를 선언한 후 추가할 것만 안에 만들어준다.
상속 받은 클래스에 원본 클래스가 갖고 있는 메서드를 직접 쓰지 않아도 메서드를 사용할 수 있다. 물론 원본 클래스를 변경하면 이를 상속 받은 클래스에서도 자동적으로 변경된 코드를 사용한다. 메서드가 있는데 이름이 같은 또다른 메서드를 선언하여 또다른 선택권을 주는 것을 추가적재(오버로드)라고 한다.
eomcs-java-basic/ src/ main/ java com.eomcs.oop.ex05.d
Car 클래스를 상속하여 Sedan 클래스를 만들자. 이렇게 만든 클래스의 인스턴스를 생성하면 중복된 코드를 작성하지 않아도 되고 변경할 사항이 있으면 Car 클래스만 바꿔줘도 다른 클래스에서 동일하게 적용된다.
상속에 관한 문법
eomcs-java-basic/ src/ main/ java com.eomcs.oop.ex05.e~f
오해하지 말 것!
기존 클래스의 코드를 상속받은 클래스 안으로 가져온다고 착각하면 안된다.
자동 복사하는 개념이 맞다면 Sedan 클래스를 실행할 때 Car 클래스가 필요 없을 것이다.
그러나 Sedan 클래스가 실행될 때 무조건 Car 클래스도 함께 실행된다.
상속은 원본 클래스의 코드를 그대로 사용하는 것이다. Sedan은 Car의 링크 정보를 가질 뿐이다.
* 클래스 계층도
UML표기법에 따라서 상속 관계를 그릴 수 있다. extends의 목적어, 즉 super 클래스 쪽으로 화살표를 향하게 한다.
- 상속 받는 클래스 : sub 클래스(= child 클래스)
- 상속을 해주는 클래스 : super 클래스(= parent 클래스)
* UML - Unified Modeling Language 객체지향 분석 설계에서 유명한 대가들의 객체지향적인 모델링 언어를 통합하여 국제 표준으로 만들었다. IBM안에서 rational 부서가 안에서 만들어졌다. IBM은 운영 체제도 제공하지만 소프트웨어 개발에 관련된 통합된 솔루션 전체를 고객에게 제공하고자 헀다. 고객 뿐만 아니라 소프트웨어를 개발하는 회사들에게까지를 대상으로. 고객의 요구사항을 수집하고 정리하여 분석, 설계하여 구현, 그다음에 테스트까지 마치면 납품까지 하는 일련의 처리 과정을 프로세스라고 부른다. 개발 절차(developement process) 라고 부르는 rational 회사가 갖고 있는 도구들을 합병을 통해 IBM이 가져간 것이다.
클래스 로딩 순서 / 인스턴스 생성 순서
상속된 클래스를 로딩하면 sub클래스보다도 먼저 super 클래스를 로딩한다.
인스턴스를 생성할 때도 sub클래스의 인스턴스 변수보다 먼저 super 클래스의 인스턴스 변수를 heap에 먼저 생성한다.
package com.eomcs.oop.ex05.e;
public class A {
int v1;
static {
System.out.println("A클래스의 static{} 실행!");
}
}
package com.eomcs.oop.ex05.e;
public class B extends A {
int v2;
static {
System.out.println("B클래스의 static{} 실행!");
}
}
public class Exam01 {
public static void main(String[] args) {
B obj = new B();
obj.v2 = 200;
obj.v1 = 100;
System.out.printf("v2=%d, v1=%d\n", obj.v2, obj.v1);
B obj2 = new B();
obj2.v2 = 2000;
obj2.v1 = 1000;
System.out.printf("v2=%d, v1=%d\n", obj2.v2, obj2.v1);
// 결과 ! A클래스의 static{} 실행!
// B클래스의 static{} 실행!
// v2 = 100, v1 = 200
// v2 = 1000, v1 = 2000
1) B 클래스가 사용한다고 선언한 클래스를 먼저 메모리에 로딩한다.
즉 A 클래스를 메모리에 로딩한다.
2) B 클래스를 메모리에 로딩한다.
3) A 클래스에 선언된 대로 인스턴스 변수를 Heap에 만든다.
4) B 클래스에 선언된 대로 인스턴스 변수를 Heap에 만든다.
## 클래스 로딩 과정
$ java com.eomcs.oop.ex03.Exam0130
1) 클래스 파일 'Exam0130.class'을 찾는다.
- JDK가 설치된 폴더의 하위 폴더인 /lib에서 찾는다.
- OS의 CLASSPATH 환경 변수에 설정된 디렉토리를 탐색하여 찾는다.
- JVM을 실행할 때 -classpath 또는 -cp 옵션으로 설정된 디렉토리를 탐색하여 찾는다.
- JVM을 실행하는 현재 폴더에서 찾는다.
- 그래도 없으면 오류를 띄운다.
2) 바이트코드 검증(Verify)
- 클래스의 바이트코드 유효성을 검사한다.
3) Exam0130.class를 "Method Area 영역"에 로딩한다.
- 즉 클래스를 외부 저장소(HDD)에서 내부 저장소(RAM)로 로딩한다.
- bytecode를 분석하여 코드(생성자, 메서드)와 상수를 따로 보관한다.
4) 스태틱 필드 및 메서드 테이블 준비(Prepare)
- Method Area 에 스태틱 필드 생성한다.
- 클래스 내부에서 사용하는 이름(변수명, 메서드명, 클래스명 등) 목록을 준비한다.
5) 참조하는 외부 클래스나 인터페이스 검사(Resolve)
- 다른 클래스나 인터페이스를 참조하는 것이 유효한지 검사한다.
6) 클래스 초기화시키기
- 스태틱 변수 초기화 문장(variable initializers), 스태틱 블록(static initializers)을 실행한다.
7) main() 메서드를 호출한다.
- 클래스를 실행하는 것이라면 main() 메서드를 찾아 실행한다.
생성자
sub 클래스의 생성자에는 첫째줄에 super();가 숨겨져있다.
super()는 슈퍼 클래스의 기본 생성자를 호출하는 것이다.
오해하지 말 것!
super클래스의 생성자 먼저 호출하는 것이 아니다. 그냥 sub클래스의 생성 과정에 super()가 있어서 일 뿐
package com.eomcs.oop.ex05.f;
public class A /*extends Object*/ {
int v1;
A() {
System.out.println("A() 생성자!");
}
}
package com.eomcs.oop.ex05.f;
public class B extends A {
int v2;
B() {
System.out.println("B() 생성자!");
}
}
package com.eomcs.oop.ex05.f;
public class C extends /*A,*/ B {
int v3;
C() {
System.out.println("C() 생성자!");
}
}
// 상속 - 생성자 호출 순서
package com.eomcs.oop.ex05.f;
public class Exam01 {
public static void main(String[] args) {
C obj = new C();
obj.v1 = 100;
obj.v2 = 200;
obj.v3 = 300;
System.out.printf("v1 = %d, v2 = %d, v3 = %d\n", obj.v1, obj.v2, obj.v3);
}
}
// 결과 ! A() 생성자!
// B() 생성자!
// C() 생성자!
// v1 = 100, v2 = 200, v3 = 300
생성자 호출 순서
1) C 클래스의 생성자를 호출하면,
그 생성자의 첫 번째 문장이 수퍼 클래스의 생성자를 호출하는 명령이다.
그래서 수퍼 클래스인 B 클래스의 생성자를 호출한다.
2) B 클래스의 생성자를 호출하면,
그 생성자의 첫 번째 문장이 수퍼 클래스의 생성자를 호출하는 명령이다.
그래서 수퍼 클래스 A의 생성자를 호출한다.
3) A 클래스의 생성자를 호출하면,
그 생성자의 첫 번째 문장이 수퍼 클래스의 생성자를 호출하는 명령이다.
그래서 수퍼 클래스 Object의 생성자를 호출한다.
4) Object 클래스의 생성자를 호출하면,
더이상 수퍼 클래스가 없기 때문에 Object() 생성자를 실행한다.
그리고 이 생성자를 호출한 A 클래스의 생성자로 리턴한다.
5) A 클래스의 생성자를 실행한 후
이 생성자를 호출한 B 클래스의 생성자로 리턴한다.
6) B 클래스의 생성자를 실행한 후
이 생성자를 호출한 C 클래스의 생성자로 리턴한다.
7) C 클래스의 생성자를 실행한다.
오브젝트 클래스
누구도 상속받겠다고 붙이지 않는다면 자동적으로 클래서 선언 뒤에 extends Object가 붙는다.
따라서 오브젝트 클래스는 모든 클래스의 슈퍼 클래스다.
오브젝트에는 이러한 메서드가 있다.
- toString()
- equals()
- hashCode()
- getClass()
- clone() : 인스턴스 복제
- finalize() : 가비지 컬렉터가 인스턴스를 제거하기 직전에 호출하는 메서드
따라서 자바에서 생성된 모든 클래스는 이 메서드를 가지고 있다.
😒 이클립스 단축키
command+ shift + o -> 임포트 명령이 현재 코드에 적절하게 나타난다.
* super 클래스의 기본 생성자가 아니라 파라미터를 받는 생성자를 만들었다면
sub 클래스의 super() 생성자에도 이를 명시해야한다.
class A {
A(int value) {
System.out.println("A(int) 생성자 호출!");
}
}
class B extends A {
B() {
}
}
// => 컴파일 오류!
*자동적으로 생기는 super(); 생성자는 기본생성자이므로 기본 생성자가 class A에 없어 컴파일 오류가 뜬다!
다중 상속
c나 c++에서 다중상속이 가능하다. 그러나 이 다중 상속의 단점은 다음과 같다.
1) 같은 이름의 메서드가 두 개 이상의 슈퍼 클래스에 있을 때 sub 클래스에서 이걸 호출하면 어떤 방법으로도 어떤 클래스의 메서드인 지 구별을 할 수 없다.
2) 생성자의 경우에는 파리미터를 받는 생성자만을 갖는 슈퍼 클래스와 기본 생성자만을 갖는 슈퍼 클래스를 갖는 섭 클래스가 파라미터를 갖는 생성자를 호출한다면 둘 중에 어떤 클래스의 생성자를 호출해야하는지 알 수 없다는 문제가 있다.
따라서 자바는 다중 상속을 문법적으로 금지한다.
객체지향과 추상화
우리는 업무에서 사람과 사물, 개념을 다루는 일을 한다. 이들을 컴퓨터안에서 다루려면 1) 이들의 특징을 분석해서 데이터화 시켜야 한다. 업무의 종류에 따라 그것들의 특징은 달라질 수 있다. 서점이면 책의 내용, 책의 저자, 가격, 가로세로 높이너비, 몇 장 등등. 업무에서 다뤄야할 특징을 분석해서 이를 데이터로 정의해야한다. 이 데이터 정의가 클래스를 사용하는 목적 중 하나이다. 특징 뿐만 아니라 이것들을 갖고 하게 되는 2) 행위(컴퓨터를 이용하여 처리할 업무)도 분석한다. 책을 배송한다하면 배송업체에 배달 정보를 등록해야한다. 이것들이 method로 구현되고 여러개의 클래스로 분류가 가능하다.
이런 식으로 물리적인 것들을 클래스의 형태로 변환하는 것을 추상화(Abstraction)라고 일컫는다.
추상화 과정에서 코드를 쉽게 재사용하기 위해 상속(inheritance)을 사용하고, 코드 작성을 쉽게 해주는 그런 문법으로서 다형성을 이용하며 코드 사용 범위를 제어할 목적으로 캡슐화(Ebcapsulation)를 이용한다.
다형성(polymorphism) 1. 다형적 변수 (polymorphic variable)
2. 오버로딩 (overloading)
3. 오버라이딩 (overriding)
다형적 변수
상속이란 개념이 존재하게 되면서 클래스들을 상위 개념으로 퉁쳐서 가리킬 수 있게 했다. 이것을 다형적 변수라고 부른다.
상위 클래스의 레퍼런스는 하위 객체를 가리킬 수 있다. 사람을 가리켜 동물이라고 할 수 있는 것과 같다.
product 클래스를 book과 appliance 가 상속받았다면 이 두 개념을 product로 묶어 부를 수 있다.
book과 Appliance 종류와 상관없이 배열을 만들고 싶다면 product 배열로 만들 수 있다.
상위 클래스의 레퍼런스가 하위 인스턴스를 가리킬 수 있는 이유는 상위 클래스가 갖는 인스턴스 변수들을 하위 인스턴스가 갖고 있기 때문이다. 마치 우리가 유인원의 특징을 갖고 있기 때문에 유인원으로 분류될 수 있는 것처럼.
그렇다고 유인원은 반대로 사람이라고 할 수는 없는 것이다. 왜냐하면 사람의 모든 특징을 유인원이 갖고 있지 않기 때문이다.
따라서 상위클래스는 자신의 변수를 항상 갖고 있는 하위 클래스를 자신으로 가리킬 수 있는 것이다.
반대로 하위 클래스가 상위 클래스를 가리킬수는 없다.
Car c = new Sedan();
c.sunroof = true; // 컴파일 오류!
c.auto = true; // 컴파일 오류!
하위 클래스의 객체를 상위 클래스의 레퍼런스 변수에 넣으면, 상위 클래스에는 없는 인스턴스 변수는 사용할 수 없다.
자바 컴파일러는 비록 v1 변수에 Sedan 객체의 주소가 들어 있다 할지라도, 실제 들어 있는 객체의 주소로 판단하지 않고 레퍼런스가 어떤 클래스냐에 따라 판단하기 때문이다. 따라서 레퍼런스의 클래스 타입을 바꿔주면 충분히 가능하다.
((Sedan)c).sunroof = true;
Sedan s = (Sedan) c;
s.sunroof = true;
이렇게 하여 s 레퍼런스 변수를 사용하는 것도 가능하다
그러나 실제로 상위 클래스의 인스턴스의 주소를 가진 레퍼런스를 형변환한다면?
컴파일은 된다. 컴파일러는 레퍼런스의 클래스 타입만 확인하기 때문이다.
Vehicle v = new Vehicle();
Bike b2 = new Bike(); // Bike 클래스는 Vehicle 클래스의 하위 클래스이다.
b2 = (Bike) v;
그렇지만 실행 중에 오류(ClassCastException)가 발생한다. 따라서 이렇게 하위 개념으로 상위 개념을 가리켜서는 안된다.
파라미터와 아규먼트 관계
public static void printSedan(Sedan sedan) {
System.out.printf("모델명: %s\n", sedan.model);
System.out.printf("cc: %d\n", sedan.cc);
System.out.println("-------------------------"); }
마찬가지로 해당 메서드의 파라미터로 들어가야하는 것은 Sedan 이나 Sedan의 하위 클래스의 주소이다.
Sedan 뿐만 아니라 Truck 까지 받는 메서드를 만들고 싶다면 Sedan 과 Truck 의 상위 클래스인 Car 클래스를 파라미터의 타입으로 지정한다.
public static void printCar(Car car) {
System.out.printf("모델명: %s\n", car.model);
System.out.printf("cc: %d\n", car.cc);
System.out.println("-------------------------");
}
이렇게 선언하면 Car는 물론 Car의 sub클래스까지 받을 수 있다.
instanceof 연산자
=> 레퍼런스에 들어있는 주소가 특정 클래스의 인스턴스인지 검사한다.
=> 또는 그 하위 클래스의 인스턴스인지 검사한다.
Vehicle v = new Sedan();
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);
getClass() 메서드
java.lang.Object.getClass() 메서드 : 해당 레퍼런스 변수의 데이터 타입이 되는 클래스 정보를 리턴한다.
* 모든 클래스에는 class 라는 스태틱 변수가 내장되어있다.
=> == 연산자를 사용하여 특정 클래스의 인스턴스인지 좁혀서 검사할 수 있다.
Vehicle v = new Sedan();
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);
* 다형적 변수에 관한 것들은 static 변수와는 상관없다. 레퍼런스 주소에 인스턴스를 넣을 수 있느냐 마느냐에 관한 문제이기 때문이다.
'국비 교육' 카테고리의 다른 글
2020.8.14일자 수업 : String, Wrapper, ArrayList 구현 실습 (0) | 2020.08.15 |
---|---|
2020.8.12일자 수업 : 오버로딩, 오버라이딩, 캡슐화 (0) | 2020.08.12 |
2020.8.10일자 수업 : 클래스와 메서드 활용 (0) | 2020.08.11 |