-
🌑 상속 (Inheritance)
-
✔️ 상속
-
✔️ 포함관계 ≠ 상속관계
-
✔️ 단일 상속 (Single Inheritance)
-
🌒 오버라이딩 (Overriding)
-
✔️ 상속받은 메서드를 Sub class에 맞게 변경하는 것
-
✔️ super - 참조 변수 ~ 상속받은 멤버를 참조
-
✔️ super() - 생성자
-
🌓 Package · Import
-
✔️ 패키지 (package) ~ class · Interface 의 묶음
-
✔️ import 문
-
🌔 제어자 (Modifier)
-
✔️ 제어자 (Modifier)
-
✔️ 접근 제어자 (Access Modifier)
-
✔️ 싱글톤 (Singleton)
-
✔️ 제어자 (Modifier) 의 조합
-
🌕 다형성 (Polymorphism)
-
✔️ 다형성 ?
-
✔️ 참조변수가 사용할 수 있는 멤버의 개수
-
✔️ 참조변수가 참조하는 인스턴스의 타입
-
✔️ 참조변수와 instance의 연결
-
✔️ 다형성 ( Polymorphism ) 의 장점
-
🌖 추상클래스 (abstract class)
-
✔️ abstact ?
-
🌗 인터페이스 (interface)
-
✔️ interface ?
-
✔️ interface 다형성
-
✔️ iterface의 이해
-
✔️ default 메서드 · static 메서드
-
🌘 내부 클래스 (inner class)
-
✔️ 내부 클래스 ~ class 내에 선언된 class
-
▼ Study📋
🌑 상속 (Inheritance)
✔️ 상속
🔹 코드의 재사용성 · 프로그램의 생산성 · 유지보수성 ⇑
class SubClass_Name extends SuperClass_Name
🔻 Sub class의 instance를 생성하면 Super class의 instance를 생성하지 않아도
Super class의 멤버들을 사용 가능
➟ Sub class들의 공통적인 부분을 Super class에서 관리 ➠ 코드의 중복성 X · 유지보수성 ⇑
class superClass {
int pmv1;
char pmv2;
boolean pmv3;
// 초기화 블럭
{ ... }
superClass() { ... }
superClass( ... ) { ... }
void pmf( ... ) { ... }
}
class subClass1 extends superClass {
int cmv1;
void cmf( ... ) { ... }
}
class InheritanceExample {
public static void main(String[] args) {
subClass1 childObj = new subClass1(); // Instantiate
childObj.pmv1 = 10;
childObj.pmv2 = 'a';
childObj.pmf( ... );
}
}
🔸 생성자와 초기화 블럭은 상속 불가능 !
( SubClass_Name obj = new SuperClass_Name(); // Not Allowed )
▪️ 생성자가 상속 불가능한 이유
- 생성자는 해당 class의 이름과 동일한 이름을 가져야 한다는 제약 조건에 어긋난다.
- Super class의 생성자를 사용하여 Super class의 private 멤버에 접근 가능하게 되기 때문이다.
▪️ 생성자는 상속되지 않으며, 생성자는 클래스의 객체가 생성될 때 호출
➟ super()가 Sub class에 명시되어 있지 않은 경우 자동으로 추가
➟ Super class 생성자 먼저 호출
➟ 자식 class 생성자 호출
✔️ 포함관계 ≠ 상속관계
🔹 상속관계 ~ IS-A : ' ~은 ~ 이다. '상속관계 - IS-A : ' ~은 ~이다. '
🔹 포함관계 ~ HAS-A : ' ~은 ~을 가지고 있다. '
🔻 class 'Triangle' · 'Circle' ⬌ class 'Point' : 포함관계
class DrawShape {
public static void main(String[] args {
Point[] p = { new Point(100, 100),
new Point(140, 50),
new Point(200, 100)
// 객체를 생성해서 객체 배열의 각 요소에 저장
};
Triangle t = new Triangle(p);
// Point p = new Point(150, 150);
// Circle c = new Circle(p, 50);
Circle c = new Circle(new Point(150, 150), 50);
t.draw();
c.draw();
}
}
class Shape {
String color = "black"
void draw() {
System.out.printf("[color=%s]%n", color);
}
}
class Point {
int x;
int y;
Point() {
this.x = x;
this. y = y;
}
Point() {
this(0, 0);
}
String getXY() {
return "("+x+", "+y+")"; // x와 y이 값을 문자열을 반환
}
}
class Circle extends Shape {
Point center;
Point r;
Circle() {
this(new Point(0, 0), 100); // Circle(Point center, int r)를 호출
}
Circle(Point center, int r) {
this.center = center;
this.r = r;
}
void draw() {
System.out.printf("[center=%d, %d), r=%d, color%s]%n"
center.x, center.y, color);
}
}
class Triangle extends Shape { // IS-A ~ ' 삼각형은 도형이다. '
Point[] p = new Point[3]; // HAS-A ~ ' 삼각형은 점을 가지고 있다. '
Triangle(Point[] p) {
this.p = p;
}
@Overriding
void draw() {
System.out.printf("[p=%s, p2=%s, p3=%s, color=%s]%n",
p[0].getXY(), p[1].getXY(), p[2].getXY(), color);
}
}
🔹 toString()
- 모든 class의 Super class인 Object class에 정의된 메서드
- 어떤 종류의 객체라도 toString()을 호출하는 것이 가능 !
🔻 class Card 의 instance의 주소를 저장하고 있는 c, 즉 참조 변수 c를 출력하면
참조변수가 c가 가리키고 있는 instance가 class Card 에서 오버라이딩된 toString()을 호출
class DeckTest {
public static void main(String[] args) {
Deck d = new Deck();
Card c = d.pick(0);
System.out.println(c); // System.out.println(c.toString());
...
}
}
class Deck {
Card carArr[] = new Card[52];
...
Card pick(int index) {
return cardArr[index];
}
...
}
class Card {
...
public String toString() { // Object Class의 메서드인 toString()을 오버라이딩한 것
...
}
}
🔹 Object Class : 모든 클래스의 상속계층도의 최상위에 있는 Super class
class A {
...
}
⬇ ⬇
class A extends Object { // 자동적으로 상속
...
}
✔️ 단일 상속 (Single Inheritance)
🔹 Java에서 다중상속을 허용하지 않는 이유 ➟ class 간의 관계의 명확성 · 코드의 신뢰성 상승
- 다중상속을 할 경우에 상속받는 class들 안에 선언부(이름, 매개변수)가 같은 메서드가 존재한다면,
static 메서드는 메서드 이름 앞에 class의 이름을 붙여서 구별할 수 있다지만,
인스턴스 메서드는 구별이 불가능하여 호출하는 과정에서 문제가 발생 ! - 이 문제를 해결하려면 Super class의 메서드의 이름이나 매개변수를 변경해야 하는데
그렇게 하면 해당 Super class의 해당 메서드를 사용하는 모든 class들을 변경해야 한다...
🌒 오버라이딩 (Overriding)
✔️ 상속받은 메서드를 Sub class에 맞게 변경하는 것
🔷 오버라이딩의 조건
🔻 동일한 이름 · 매개변수 · 반환타입
🔻 접근 제어자는 Super class의 메서드보다 좁은 범위로 변경 불가능 !
🔻 Super class의 메서드보다 많은 수의 예외 (ex. " throw IOException ") 선언 X
🔷 오버라이딩 (change, modify) ≠ 오버로딩 : 기존에 없는 새로운 메서드 정의 (new)
✔️ super - 참조 변수 ~ 상속받은 멤버를 참조
🔹 Super class의 멤버와 Sub class의 멤버가 중복 정의되어 서로 구별해야 하는 경우 사용
➞ 멤버변수와 지역이름이 같을 때 this를 붙여서 구별하는 것과 유사
class superClass {
int v = 20;
}
class subClass extends superClass {
int v = 10;
void method() {
System.out.println(v); // 10
System.out.println(this.v); // 10
System.out.println(super.v); // 20
}
}
🔻 Super class에 선언된 멤버변수와 같은 이름의 멤버변수를 자손 클래스에서 중복 정의 가능 !
➞ 참조변수 super를 이용해서 서로 구별 가능
🔹 this와 마찬가지로 instance와 관련이 없어, static 메서드에선 사용 불가능
🔹 Super class의 메서드도 super로 호출 가능
➞ Super class의 메서드의 내용에 추가 작업을 덧붙이기 위해 오버라이딩한 경우에 자주 사용 !
✔️ super() - 생성자
🔹 Object class를 제외한 모든 class의 생성자 첫 줄에 생성자, this() 또는 super()를 호출 !
➞ 없을 경우 컴파일러가 자동적으로 'super();'를 생성자의 첫 줄에 삽입
class superClass {
int x, y;
superClass(int x, int y) {
this.x = x;
this.y = y;
}
}
class subClass extends superClass {
int z;
subClass(int x, int y, int z) {
super(x, y);
this.z = z;
}
}
🔻 Super class의 멤버변수는 Super class의 생성자로 초기화하도록 해야 한다 !
🌓 Package · Import
✔️ 패키지 (package) ~ class · Interface 의 묶음
🔹 하나의 소스파일(.java)에는 첫 번째 문장으로 단 한 번의 package 선언만 허용
🔹 모든 class는 반드시 하나의 package에 속해야 한다.
🔹 package는 점(.)을 구분자로 하여 다른 package를 포함 가능
🔹 패키지는 물리적으로 클래스 파일(.class)를 포함하는 하나의 디렉토리이다.
🔸 패키지를 선언하지 않고도 문제가 발생하지 않는 이유
➞ 소스파일(.java)에 자신이 속할 package를 지정하지 않은 class는
자동적으로 'unnamed package'에 속하게 된다.
✔️ import 문
🔹 소스파일(*.java)의 구성
- package 문
- import 문
- class 선언
// import java.util.Calender;
// import java.util.Date;
// import java.util.ArrayList;
import java.util.*;
🔻 import 문을 여러번 사용하는 대신 위와 같이 한 문장으로 처리 가능
➞ import 하는 package의 수가 많을 때는
어느 class가 어느 package에 속하는지 구별하기 어려울 수 있다 !
import java.util.*;
import java.text.*;
≠
import java.*;
🔻 import 문에서 class의 이름 대신 ' * ' 을 사용하는 것이
하위 package의 class까지 포함하는 것이 아니다 !
🔺 System과 String과 같은 java.lang package의 class들을 package명 없이 사용 가능했던 이유
( ex ) java.lang.System.*, java.lang.String variable ...
import java.lang.*;
🔻 위와 같은 import 문이 모든 소스파일(.java)에 묵시적으로 선언되어 있기 때문이다 !
🔹static import 문 ~ static 멤버를 호출할 때 클래스 이름 생략 가능
import static java.lang.Integer.*;
// Integer의 모든 static 메서드를 호출할 때 "Integer" 생략 가능
import static java.lang.Math.random;
import static java.lang.System.out;
// System.out.println(Math.random());
out.println(random());
🔻 특정 class의 static 멤버를 자주 사용할 때 편리 !
🔸 클래스의 package를 import 할 때 클래스의 이름을 일일이 지정해주는 것과
' * ' 를 사용하는 것이 성능상의 차이가 없는 이유
- Compiler가 코드를 Compile 할 때 ' import ' 문이 실제로 포함된 class를 로드하는 것이 아니라,
Compiler는 해당 class의 package 경로를 알려주는 역할 ! - Compiler는 필요한 class를 찾아서 Compile 하는 동안 해당 class를 직접 로드한다.
🌔 제어자 (Modifier)
✔️ 제어자 (Modifier)
🔹 static
- static 멤버변수
- 모든 instance가 공유하는 class 변수 ➟ instance 생성 없이 사용 가능
- class가 메모리에 로드될 때 생성
- static 메서드
- instance 생성 없이 호출 가능한 class 메서드 ➟ instance 멤버 사용 불가능
➟ instance 멤버를 사용하지 않는다면, static으로 선언해주는 것이 편리하며 속도가 빠르다 !
- instance 생성 없이 호출 가능한 class 메서드 ➟ instance 멤버 사용 불가능
🔹 final
- final Class
- 변경 · 확장이 불가능한 class ➟ Super class가 될 수 없다 !
- 변경 · 확장이 불가능한 class ➟ Super class가 될 수 없다 !
- final 메서드
- 변경 · Overriding 불가능 !
- 변경 · Overriding 불가능 !
- final 멤버변수 · 지역변수
- 값을 변경할 수 없는 상수 ➟ 일반적으로 선언 · 초기화를 동시에 한다.
➟ 인스턴스 변수의 경우 생성과 동시에 하지 않아도 생성자에서 단 한 번 초기화 가능 !
- 값을 변경할 수 없는 상수 ➟ 일반적으로 선언 · 초기화를 동시에 한다.
class Example {
final int NUMBER = 5;
final String STR;
final int NUM;
Example(String str, int num) {
STR = str;
NUM = num;
}
}
Class Example Test {
public static void main(String[] args) {
Example obj = new Example("string", 1);
obj.NUM = 2; // Compile Error
}
}
🔹 abstract (추상)
- abstract Class
- Class 내에 abstarct 메서드가 선언되어 있음을 의미 !
- abstract 메서드 ~ 선언부만 작성하고 구현부는 작성하지 않은 메서드
- abstract 메서드가 없는 abstract Class도 존재
✔️ 접근 제어자 (Access Modifier)
🔹 접근 제어자
- private
- 같은 Class 내에서만 접근 가능
- 같은 Class 내에서만 접근 가능
- default
- 같은 package 내에서만 접근 가능
- 같은 package 내에서만 접근 가능
- protected
- 같은 package 내 + 다른 package의 Sub class에서 접근 가능
- 같은 package 내 + 다른 package의 Sub class에서 접근 가능
- public
- 접근 제한 X
🔹 접근제어자를 이용한 Encapsulation (캡슐화)
- 데이터 감추기 (Data Hiding) ➟ 객체지향 개념의 캡슐화 (Encapsulation)
- class나 멤버, 주로 멤버에 접근 제어자를 사용하는 이유
➟ class의 내부에 선언된 데이터 보호 ! - 외부에는 불필요한, 내부적으로만 사용되는 부분을 감추기 위해 !
- 메서드 하나를 변경하고 오류가 없는지 테스트해야 하는 경우, 이 메서드의 접근 제어자가
⇨ public ➟ 테스트해야 하는 범위가 매우 넓다.
⇨ default ➟ 해당 package만 살펴보면 된다 !
⇨ private ➟ 해당 class만 살펴보면 된다 !
- class나 멤버, 주로 멤버에 접근 제어자를 사용하는 이유
🔻 외부에서 잘못된 값을 지정하는 것을 막기 위해 멤버변수를 private이나 protected로 제한하고,
해당 멤버변수의 값을 읽고 변경할 수 있는 public 메서드를 제공하여 간접적으로 멤버변수의 값을 다룬다!
- getter ~ 멤버변수의 값을 읽는 메서드 : 'get멤버변수이름'
- setter ~ 멤버변수의 값을 변경하는 메서드 : 'set멤버변수이름'
public class Time {
private int hour;
private int minute;
private int second;
public int getHour() { return hour; }
pbulic void setHour(int hour) {
if(hour < 0 || hour > 23) return;
this.hour = hour;
}
public int getMinute() { return minute; }
public void setMinute(int minute) {
if(minute < 0 || minute > 59) return;
this.minute = minute;
}
public int getSecond() { return second; }
public void setSecond(int second) {
if(second < 0 | second > 59) return;
this.second = second;
}
}
🔹 생성자의 접근 제어자
- 일반적으로, 생성자의 접근 제어자 = class의 접근 제어자
final class Singleton { // Singleton Pattern
...
private static Singleton instance = new Singleton();
// getInstance()에서 사용할 수 있도록 인스턴스가 미리 생성되어 있어야 하므로 static이어야 한다.
private Singleton() {}
pubilc static Singleton getInstance() {
if(instance == null) instance = new Singleton();
return instance;
}
...
}
- 생성자를 통해 직접 인스턴스를 생성하지 못하게 하는 방법 ➟ 생성자를 private으로 선언
- public 메서드를 통해 instance에 접근하게 함으로써 instance의 개수를 제한 가능 !
- Sub class의 instance를 생성할 때 Super class의 생성자를 먼저 호출
➟ 생성자가 private으로 선언된 class는 Super class가 될 수 없다 !
➟ 따라서, class 앞에 final을 추가적으로 선언하여 상속할 수 없는 class라는 것을 알리는 것이 좋다.
- public 메서드를 통해 instance에 접근하게 함으로써 instance의 개수를 제한 가능 !
✔️ 싱글톤 (Singleton)
🔸Singleton Pattern ( 싱글톤 패턴 ) ~ Anti Pattern
class SingletonTest {
public static void main(String[] args) {
Singleton s = new Singleton(); // Compile Error!
Singleton s = Singleton.getInstance(); // 객체 최초 생성
}
}
- 생성자가 호출되더라도, 실제로 생성되는 객체는 하나이며
최초로 생성된 이후에 호출된 생성자는 이미 생성한 개체를 반환하도록 만드는 것 !
(+) S원래라면 객체를 생성할 때마다 메모리 영역을 할당받아야 하지만,
한번의 new를 통해 객체를 생성하여 메모리 낭비 방지
(+) Singleton으로 구현한 instance는 '전역'이므로, 다른 class의 instance들과의 데이터 공유 가능
(-) Client가 interface가 아닌 구체 class에 의존하게 하여 DIP (의존관계 역전 법칙) 위반
(-) Singleton으로 구현한 instance가 혼자 너무 많은 일을 하거나, 많은 데이터를 공유시키면
다른 class들 간의 결합도 상승
➟ OCP (개방-폐쇄 원칙) 위반, 유지보수가 힘들고 테스트도 원할하게 진행 X
(-) 멀티 스레드 환경에서 동기화 처리를 하지 않았을 때, instance가 2개가 생성될 수 있다.
➟ 해결방안 - Lazy Initialization
private static Singleton s = new Singleton();
➟ Lazy Initialization 는 프로그램 구조가 복잡해지고 비용 문제가 발생할 가능성 ⇈
- Initialization on demand holder idiom (Holder에 의한 초기화)
public class Singleton {
private Singleton() {
}
private static class Holder {
public static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
🔻 Singleton의 초기화 문제에 대한 책임을 JVM에게 떠넘기는 것
- class Holder 에서 선언된 instance는 static이기 때문에, class 로딩 시점에서 한 번만 호출 !
또한, final을 사용해서 다시 값이 할당되지 않도록 만드는 방식
✔️ 제어자 (Modifier) 의 조합
🔹 사용가능한 제어자
- class ➟ public, (default), final, abstract
- 메서드 ➟ 모든 접근 제어자, final, abstract, static
- 멤버변수 ➟ 모든 접근 제어자, final, static
- 지역변수 ➟ final
🔹 불가능한 제어자 조합
- 메서드 ⇏ static + abstract
- 몸통이 있는 메서드만 static으로 선언 가능
- 몸통이 있는 메서드만 static으로 선언 가능
- 메서드 ➟ private와 final (~ 변경 불가능한 메서드) 중 하나만 !
- private으로 선언한 메서드는 어차피 Overriding이 불가능
- private으로 선언한 메서드는 어차피 Overriding이 불가능
- abstarct 메서드 ⇏ private
- abstract로 선언된 메서드는 Sub class에서 구현해주어야 하기 때문이다.
- abstract로 선언된 메서드는 Sub class에서 구현해주어야 하기 때문이다.
- class ⇏ abstact + final
- 선언하려는 의도가 서로 모순
🌕 다형성 (Polymorphism)
class Animal {
String name;
String food;
void move() { ... }
}
class Dog extends Animal { ... }
✔️ 다형성 ?
🔹 Super class 타입의 참조변수로 Sub class의 인스턴스를 참조할 수 있는 성질
🔹 참조변수의 타입에 따라 달라지는 "사용할 수 있는 멤버의 개수" & "참조하는 인스턴스의 타입"
✔️ 참조변수가 사용할 수 있는 멤버의 개수
Animal animal = new Dog(); // OK
Dog dog = new Animal(); // Compile Error!
🔹 ( 참조변수가 사용할 수 있는 멤버의 개수 ) ≤ ( instance 멤버의 개수 ) ➟ 그 이유는?
- Sub class ( Dog ) 타입의 참조변수 ' dog '는 Sub class ( Dog )의 멤버를 사용하려 할 수 있다.
➟ 하지만 ' dog '가 참조하고 있는 instance는 Super class ( Animal ) 타입이기 때문에,
Super class ( Animal ) 타입의 instance에는 해당 멤버가 존재하지 않아 사용 불가능 !
( Super class의 instance의 멤버 개수 ≤ Sub class의 인스턴스의 멤버 개수 ) - Sub class 타입의 참조변수 ➟ Super class 타입의 instance 참조 X
/ Super class 타입의 참조변수 ➟ Sub class 타입의 참조변수 O
✔️ 참조변수가 참조하는 인스턴스의 타입
🔹 참조변수의 형변환 ( 상속관계인 클래스들 사이에만 )
Dog dog = new Dog();
Animal animal = dog; // Up-casting
Dog dog = (Dog) animal; // Down-casting
- Sub Class 타입 ➟ Super Class 타입 ( Up-casting ) : 형변환 생략 O
- Super Class 타입 ➟ Sub Class 타입 ( Down-casting ) : 형변환 생략 X
🔹 Down-casting은 형변환 생략이 불가능한 이유
🔺Up-casting
Animal anmial = (Animal)new Dog();
Animal anmial = new Dog(); ➟ 생략된 형태
// 아래 두 줄을 간략히 한 형태
Dog dog = new Dog();
Animal animal = (Animal) dog;
- Super class ( Animal ) 타입의 참조변수가 사용할 수 있는 멤버의 개수가
실제 instance가 갖고 있는 멤버의 개수보다 적을 것이 분명하므로 형변환을 생략해도 문제 X
🔺Down-casting
Dog wildDog = (Dog)new Animal();
Dog wildDog = new Animal(); ➟ ??
// 아래 두 줄을 간략히 한 형태
Animal animal = new Animal();
Dog wildDog = (Dog)animal; // Runtime Error!
- Down-casting 은 참조변수가 다룰 수 있는 멤버의 개수를 늘리는 것 !
Sub class ( Dog ) 타입의 참조변수가 사용할 수 있는 멤버의 개수가
실제 instance가 갖고 있는 멤버의 개수보다 많아질 수 있으므로 문제가 발생할 수 있다
➠ 따라서, Sub class 타입으로의 형변환은 생략( Compile Error )할 수 없는 동시에,
Sub class 타입으로의 형변환 ( Runtime Error )자체가 불가능 !
🔻이러한 실수를 피하기 위한 방법
➠ 형변환을 수행하기 전에 instancOf 연산자를 사용하여 형변환이 가능한지 체크 !
🔸 참조변수의 형변환은 참조변수의 타입을 변환하는 것
➠ instance를 변환하는 것이 아니기 때문에, instance에 아무런 영향을 미치지 않는다.
➠ 참조변수의 형변환을 통해, 참조하고 있는 instance에서 사용할 수 있는 멤버의 범위(개수)를 조절 !
🔹 instanceOf 연산자 ( left : 참조변수 / right : 타입 (class명) )
Animal animal = new Animal();
Dog dog = new Dog();
if(animal instanceOf Dog) {
System.out.println("animal is a instance of Dog.");
}
if(dog instanceOf Animal) {
System.out.println("dog is a instance of Animal.");
}
dog is a instance of Animal
🔻 instanceOf를 이용한 연산결과 ➟ true (참조변수에 null 값이 이 저장된 경우엔 false)
➜ 검사한 타입으로 형변환이 가능하다는 것 !
✔️ 참조변수와 instance의 연결
🔹 Sub class에서 Super class의 멤버를 중복으로 정의하지 않은 경우
➜ 참조변수의 타입에 상관없이 Super class의 멤버 사용
class RvTest {
public static void main(String[] args) {
...
}
}
class Animal {
static int num = 10;
String name = "animal_name";
String food = "food";
void move() {
System.out.println("Animal is moving");
}
static void eat() {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
static int num = 5;
String name = "dog_name";
String food = "meat";
@Overide
void move() {
System.out.println("Dog is moving");
}
@Overide
static void eat() {
System.out.println("Dog is eating");
}
}
🔸 Sub class에서 Super class의 멤버를 중복으로 정의한 경우
Animal animal = new Dog();
Dog dog = new Dog();
// 참조변수 'animal', 'dog' 모두 class 'Dog'의 instance를 참조
- 중복된 멤버변수
➢ 참조변수 타입과 같은 클래스의 멤버변수 반환
System.out.println("animal name : " + animal.name);
System.out.println("dog name : " + dog.name);
animal name : animal_name
dog name : dog_name
- Overriding한 메서드
➢ 실제 instance의 타입인 class에 정의된 메서드 호출
animal.move();
dog.move();
dog is moving
dog is moving
- static 메서드 ( ~ static 변수처럼 )
➢ 참조변수 타입에 영향을 받아, 꼭 'className.메서드()'로 호출
animal.eat();
dog.eat();
System.out.println();
Dog.eat();
animal is eating
dog is eating
dog is eating
🔹 참조변수 super · this 로 구분
- Sub class ( Dog ) 에 선언된 instance 변수 ' num '
- Super class ( Animal ) 로부터 상속받은 instance 변수 ' num '
🔸 Encapsulation ( 캡슐화 ) 의 필요성
- 멤버변수들은 주로 private으로 접근 제한하여, 외부에서는 메서드를 통해서만 멤버변수에 접근하도록 한다 !
- 보통은 위의 예제들처럼 참조변수를 통해 직접적으로 instance변수에 접근하지 않는다 !
✔️ 다형성 ( Polymorphism ) 의 장점
🔸 다양한 타입의 객체들을 동일한 타입의 참조변수로 참조할 수 있다 !
- 유연성 ⇈
➜ 객체 배열에 여러 타입의 객체들을 저장하고 일관된 방식으로 다룬다. - 가독성 · 유지보수성 ⇈
➜ 동일한 타입의 참조변수로 다양한 객체들을 다루기 때문에, 코드가 더 명확하고 간결해진다. - 확장성 ⇈
➜ 기존 class를 변경하지 않고도 새로운 class를 추가하거나 새로운 객체들을 추가할 수 있다.
class Product {
int price;
...
}
class Tv extends Product { ... }
class Computer extens Product { ... }
class Buyer {
int money = 1000;
int bonusPoint = 0;
}
🔸 매개변수의 다형성
void buy(Product p) {
money -= p.price;
bonusPoint += p.bonusPoint;
}
/*
void buy(Tv t) {
money -= t.price;
bonusPoint += t.bonusPoint;
}
void buy(Computer c) {
money -= c.price;
bonusPoint += c.bonusPoint;
}
*/
➠ 매개변수 ( p ) 가 Super class ( Product ) 타입의 참조변수라는 것
➨ Super class의 Sub class 타입의 참조변수면 어느 것이나 매개변수로 받을 수 있다는 것
⇨ 즉, 새로운 class를 추가하더라도 Super class를 상속받기만 하면
Sub class의 instance를 매개변수로 제공 가능 !
🔸 매개변수의 다형성의 또 다른 예
// 실제 코드
public void print(Object obj) {
write(String.valueOf(obj));
}
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
- print(Object obj)
- Object는 최상위 Super class이므로 이 메서드의 매개변수로 어떤 타입의 intance도 가능
➠ 이 print(Object obj) 메서드로 모든 타입의 instance 처리 !
- Object는 최상위 Super class이므로 이 메서드의 매개변수로 어떤 타입의 intance도 가능
🔸 객체 배열 ( item )을 이용해 여러 종류의 객체를 다룬다
- 객체 배열의 크기를 동적으로 선언하는 방법
➟ ' Vector item = new Vector(); ' , ' ArrayList<item> items = new ArrayList<>(); ' , etc.
class Product {
int price;
int bonusPoint;
Product(int price) {
this.price = price;
bonusPoint = (int)(price/10.0);
}
}
class Tv extends Product {
Tv() {
super(100);
}
@Override
public String toString() { return "Tv"; }
}
class Computer extends Product {
Computer() {
super(200);
}
@Override
public String toString() { return "Computer"; }
}
class Buyer {
int money = 1000;
int bonusPoint = 0;
// Product p1 = new Tv();
// Product p2 = new Computer();
Product[] item = new Product[10];
int i = 0;
void buy(Product p) {
if(money < p.price) { // 구매 불가
return;
}
// 제품 구입
money -= p.price;
bonusPoint += p.bonusPoint;
item[i++] = p;
}
void printHistory() {
int sumPrice = 0;
String itemList = "";
StringBuilder sb = new StringBuilder();
for(int i = 0; i < item.length; i++) {
if(item[i] == null) break;
sum += item[i].price;
sb.append(item[i]);
}
itemList = sb.toString();
System.out.println(sum);
System.out.println(itemList);
}
}
Buyer b = new Buyer();
// Tv t = new Tv();
// b.buy(t);
b.buy(new Tv());
b.buy(new Computer());
b.printHistory();
700
Tv Computer
➢ Sub class의 instance를 매개변수에 제공
🌖 추상클래스 (abstract class)
✔️ abstact ?
🔹 abstract class
abstract class ClassName {
...
abstract void abMtd();
...
}
- abstract 메서드를 포함하고 있다는 점을 제외하고는 일반 class와 차이 X
- 생성자, 멤버변수, 그리고 메서드도 가질 수 있다 !
- abstract class는 instantiate 불가
🔹 abstract 메서드
abstract returnType methodName();
- 선언부만 존재 +
구현부➟ 미완성 메서드
🔸 abstract 메서드 구현 (강제) ⟺ Super class 메서드 Overriding (선택)
🔹 추상화 (abstract)
➟ class 간의 공통점을 찾아내서 공통의 Super class를 만드는 작업
🔹 구체화
➟ 상속을 통해 class를 구현, 확장하는 작업
🔹 abstract class를 사용하는 이유
public abstract class Animal {
...
public abstract void bark();
// 동물들이 짖는다는 공통점을 찾아 추상화, 짖는 소리는 달라 abstract 메서드로 생성
...
}
public class Dog extends Animal {
...
@Override
public void bark() {
System.out.println("Bark");
};
}
public class Wolf extends Animal {
...
@Override
public void bark() {
System.out.println("Howl");
};
}
🔻 구현의 강제성을 통한 기능 보장
- 일반 class로 상속 관계를 맺는 상황에서 Overriding을 깜빡한다면, 디버깅으로 찾기 까다롭다
🔻 객체들의 멤버변수와 메서드의 이름을 통일하여 코드의 가독성 ⇈
➟ 개발자 멤버변수와 메서드 추측 가능 ( " Dog에 bark가 있으니까 Wolf에도 bark가 있을 것 ! " )
🔻 상속과 다형성의 장점을 누릴 수 있다
- abstract class를 상속한 Sub class를 abstract class로 다룰 수도 있기 때문이다 !
🔻 초기 설계 시간 절약 · 구현부 작성 용이
- abstract class를 상속받아서 미리 선언된 abstract 메서드를 구현하고,
실체 class에서 필요한 기능을 class 별로 확장시킴으로써 코드의 유지보수성 ⇈ - 설계자가 규격에 맞게 소스를 구현해놓기 때문에, 코더가 해당 규격에 맞게 실체 class를 작성하면 된다.
🌗 인터페이스 (interface)
class Unit {
int currentHP;
int x;
int y;
}
✔️ interface ?
🔹 일종의 abstract class ~ 기본 설계도 ( ⟺ 미완성 설계도)
- abstract class보다 추상화 정도가 높다
➠ 몸통을 갖춘 일반 메서드 또는 멤버 변수를 구성원으로 X
➠ 오직 추상 메서드 ( public abstract ) 와 상수 ( public static final ) 만을 멤버로 가질 수 있다 !
⤷ 생략 가능 ⤷ 생략 가능 ( Compiler가 자동적으로 추가 ) - interface는 간단히 말해서 어떠한 기능 또는 행위를 하기 위한 메서드를 제공하는 용도로 쓰인다.
( interface명을 주로 ' ~able '로 작성하는 이유 ) - interface로부터만 상속(extends)받을 수 있으며, 다중 상속이 가능 !
➠ 상속받은 interface에 정의된 멤버를 모두 상속받는다.
interface Movable { void move(int x, int y); }
interface Attackable { void attack(Unit u); }
interface Fightable extends Movable, Attackable { }
- Sub class에 상속(extends)과 구현(implements)은 동시에 가능
class Fighter extends Unit implements Fightable {
public void move(int x, int y) { ... }
public void attack(Unit u) { ... }
}
🔻 interface ' Movable '에 정의되어 있는 ' move '는 ' public abstract '가 생략된 것 !
따라서, Overriding 할 때에는 조상의 메서드보다 넓은 범위의 접근제어자를 지정해야 한다는 점 때문에,
상속받은 interface의 메서드를 구현할 때의 선언부는 public으로 지정해줘야 한다 !
- 상속받는 class에서 구현한다는 의미로 ' implements '로 interface를 상속
- interface는 class와 달리 Object class와 같은 최상위 Super interface가 없다.
➠ 따라서, class를 상속하는 행위 불가능 - 구현해야 하는 메서드들 중 일부만 구현한다면, abstract class로 선언해줘야 한다.
abstract class Fighter implements Fightable {
public void move(int x, int y) { ... }
}
✔️ interface 다형성
🔹 interface를 이용한 다형성
- interface 타입의 참조변수로 이를 구현한 (Sub) class의 instance를 참조 가능하며,
interface 타입으로의 형변환도 가능
Fightable f = (Fightable)new Fighter(); // Up-casting
// Fightable f = new Fighter();
🔻 Fightable 타입의 참조변수는 interface Fightable에 정의된 멤버들만 호출 가능
void attack(Fightable f) { ... }
// void attack(new Fighter()) { ... }
🔻 매개변수가 interface 타입 ➠ 해당 interface를 구현한 (Sub) class의 instance를 매개변수로 제공
Fightable method() {
...
// Fighter f = new Fighter();
// return f;
return new Fighter();
}
🔻 return 타입이 interface ➠ 메서드가 해당 interface를 구현한 (Sub) class의 instance를 반환 !
- interface를 자료형으로 쓰는 습관을 들이자 !
- 객체는 class가 아닌 interface로 참조 !
- 적당한 interface가 있다면 매개변수뿐만 아니라 return값, 변수, 필드를 전부 interface 타입으로 선언
- 객체의 실제 class를 사용할 상황은 '오직' 생성자로 생성할 때 뿐이다.
- 매개변수 타입으로는 class 보다는 interface를 활용하자 !
// LinkedHashSet<Object> s = new LinkedHashSet<>(); // Bad case
Set<Object> s = new LinkedHashSet<>(); // Good case
// LinkedHashSet으로 구현했다가 TreeSet class로 변경해야 한다면,
// 그냥 interface 타입의 변수에 재할당만 해주면 된다.
s = new TreeSet<>();
🔻 위처럼 변수에 담긴 구현 class를 다른 Set 자료형 class ( TreeSet ) 로 교체하고자 할 때,
그냥 새 class의 생성자를 다시 호출해주기만 하면 되어 간편하다 !
🔻 하지만 LinkedHashSet 과 달리 반복자의 순회 순서를 보장하지 않는 HashSet으로 변환하고자 하는 경우엔
로직상 문제가 생길 수 있기 때문에, interface 타입으로 선언하는 습관이 항상 좋은 것만은 아니다.
✔️ iterface의 이해
🔹 interface의 장점
- 서로 관계없는 class들에게 관계를 맺어줄 수 있다 !
- Java의 class 상속 구조는 Super - Sub 관계로만 가능하며 단일 상속의 원칙을 따르기 때문에,
관계를 맺어주고자 하는 class들이 각기 다른 Super class를 상속하고 있다면 관계를 맺어줄 수 없다. - interface를 이용하면 아무 관계도 없는 class들에게 하나의 interface를
구현(상속)하게 함으로써 관계를 맺어줄 수 있다.
- Java의 class 상속 구조는 Super - Sub 관계로만 가능하며 단일 상속의 원칙을 따르기 때문에,
- 표준화 가능
- 프로젝트에 사용되는 기본 틀을 interface로 제공하고, 개발자들이 interface를
구현하여 프로그램을 작성함으로써 보다 일관되고 정형화된 프로그램 개발 가능
➠ Java의 Data Base interface인 JDBC가 대표적이다.
➠ 추상화된 interface 규칙에 따라 다양한 종류의 DB를 사용하더라도
동일하게 접속할 수 있는 것은 JDBC라는 interface로 표준화되어 있기 때문이다 !
- 프로젝트에 사용되는 기본 틀을 interface로 제공하고, 개발자들이 interface를
- 개발시간 단축
- interface를 사용하여 메서드를 호출하는 쪽에선 interface가 구현되지 않았더라도
메서드의 선언부만 보고 프로그램 작성 가능 - interface를 사용하는 쪽과 구현하는 쪽이 동시에 개발 가능하다 !
- interface를 사용하여 메서드를 호출하는 쪽에선 interface가 구현되지 않았더라도
- 독립적인 프로그래밍이 가능
- interface를 이용하면 class의 선언과 구현을 분리,
즉 class 간의 직접적인 관계를 간접적인 관계로 변경하면, 어떤 한 class의 변경이
그 class와 관계있는 다른 class에 영향을 미치지 않는 독립적인 프로그래밍이 가능한 것 !
- interface를 이용하면 class의 선언과 구현을 분리,
🔹 interface의 본질적인 측면
🔺 class를 사용하는 User와 class를 제공하는 Provider가 있다.
🔺 메서드를 사용(호출)하는 쪽 (User)에서는 사용하려는 메서드 (Provider)의 선언부만 알면 된다.
( 구현부는 몰라도 된다 )
class User {
private MySql db = new MySql();
public void connectDb() {
db.connectDb();
}
}
🔻 class ' User ' - class ' MySql ' (Provider)
➠ 직접적인 관계 (' MySql '이 변경되면 ' User '도 영향을 받는다)
interface Database {
public abstract void connectDb();
}
⇨ interface를 이용해 메서드 ' connecDb'의 선언부와 구현부를 분리
class MySql implements Database {
public void connectDb() { ... }
}
⇨ class ' MySql'이 interface ' Database '의 메서드 'connectDb '를 구현
class User {
Database db = new MySql();
public void loadDb() {
db.connectDb();
}
}
⇨ class ' User '가 interface ' Database '에 의존하도록 변경
➠ 'User - Database - MySql'의 간접적인 관계
🔻 하지만, class ' User ' class ' MySql '에 대한 의존성이 완전히 제거된 것은 아니다 !
➠ 제 3의 class를 통해 class ' Database '를 구현한
class ' MySql '의 instance를 동적으로 제공받는다면 해결 가능 !
class User {
public void loadDb() {
Database db = DbManager.getDb(); // static으로 선언되어 instance 생성없이 호출 가능
db.connect();
}
}
// 제 3의 class
class DbManager {
public static Database getDb() {
return new MySql();
}
}
✔️ default 메서드 · static 메서드
🔸 default 메서드와 static 메서드를 통해 abstract class처럼 구현 메서드를 정의 가능 !
⭢ 기존 interface에 abstract 메서드를 추가하면,
해당 interface를 구현하고 있는 모든 class가 변경이 필요해지는 문제 발생
⭢ interface에 default 메서드를 새로이 추가하여 해결
⭢ static 메서드도 instance와 관계없는 독립적인 메서드이기 때문에 interface에 추가
해도 상관없지만, interface의 규칙을 단순히 하기 위해서 interface ' Collections '와
관련된 static 메서드들이 별도의 class ' Collenctions '에 들어가게 되었다.
⭢ 하지만 JDK1.8부터는 static 메서드도 interface에 추가 가능하게 된 것 !
➠ 이제는 interface와 abstract class와의 차이점이 거의 사라졌다고 말하기도 한다.
🔹 default 메서드
interface I {
void method();
default void newMethod();
}
- interface가 변경되야만 하는 경우에,
default 메서드를 새로 추가하더라도 해당 interface를 구현한 class를 변경할 필요 X
➠ 만약 기존의 메서드와 이름이 중복되어 충돌한 경우 !
- 새롭게 추가한 메서드와 같은 내용으로 Overriding 해주기만 하면 된다.
- 접근 제어자는 public 이며, 생략 가능
🔹 static 메서드
- 접근 제어자는 public 이며, 생략 가능
🌘 내부 클래스 (inner class)
✔️ 내부 클래스 ~ class 내에 선언된 class
🔹 inner class 를 쓰는 이유
- outer class와 긴밀한 관계에 있지만, 해당 inner class가 그 외의 class에선 잘 사용되지 않는 경우
➠ inner class에서 outer class의 멤버들에 쉽게 접근 가능 !
➠ 코드의 복잡성을 줄일 수 있다 ! ( Encapsulation )
🔹 inner class 의 종류
- 변수처럼 inner class의 선언 위치에 따라 유효범위와 접근성이 달라진다 !
➠ instance class, static class, local class, anonymous class
class Outer {
private int iv = 0;
protected static int cv = 0;
void myMethod() {
int iv = 0;
}
}
class OuterClass {
private class InstanceInner {}
protected static class StaticInner {}
void myMethod() {
class LocalInner {}
}
}
🔻 instance class (' InstanceInner ')와 static class (' StaticInner ')는 outer class (' OuterClass ')의
멤버변수(instance 변수, class 변수)와 같은 위치에 선언되며, 멤버변수와 같은 성질을 갖는다.
🔻 inner class도 class이기 때문에 abstract, final과 같은 제어자도 사용 가능하며,
멤버변수처럼 private, protected과 같은 접근 제어자도 사용 가능 !
- instance class
- outer class의 instance 멤버를 객체생성 없이 바로 사용 가능 ( ~ instance 멤버 )
- outer class의 static 멤버도 접근 가능하며, private로 선된 멤버도 접근 가능
- static class
- static class만 static 멤버를 가질 수 있다.
하지만, final static으로 정된 변수는 상수(constant)이므로, 모든 inner class에서 정의 가능 - outer class의 instance 멤버를 객체생성 없이 사용 불가능 ( ~ class (static) 멤버 )
- static class만 static 멤버를 가질 수 있다.
- local class
- local class가 정의된 메서드에서 final로 정의된 local 변수에만 접근 가능 !
- JDK1.8부터는 local class에서 접근하는 local 변수 앞에 final을 생략하더라도
Compiler에서 자동적으로 추가
➠ 단, local class에서 접근한 local 변수의 값이 바뀌는 문장이 있다면 Compile Error ! - local 변수처럼 local inner class도 다른 메서드에 같은 이름의 inner class가 존재
- Anonymous class (익명 클래스)
- 다른 inner class와 달리 이름이 없고, 이는 곧 나중에 다시 불러질 이유가 없다는 의미.
➠ 즉, 프로그램에서 일시적으로 한 번만 사용되고 버려지는 객체 ! - class 정의와 동시에 객체( = 익명 객체 )를 생성한다.
➠ 단 하나의 class를 상속받거나 단 하나의 interface만을 구현 가능 ! - anonymous 객체는 단 하나의 interface만 구현하여 생성된다는 한계점을 지니고,
이는 다중 상속(구현)이 가능하다는 interface의 가장 큰 본질에 적합 X
➠ 어쩔 수 없이 일회용 용도일지라도 아래의 코드처럼,
다중 상속(구현)한 class를 따로 정의하여 사용해야 한다.
- 다른 inner class와 달리 이름이 없고, 이는 곧 나중에 다시 불러질 이유가 없다는 의미.
interface IAnimal { ... }
interface ICreature { ... }
abstract class myClass { ... }
public class Main {
public static void main(String[] args) {
// class와 interface를 상속, 구현한 일회용 class
class useClass1 implements IAnimal, ICreature { ... }
// class와 interface를 상속, 구현한 일회용 class
class useClass2 extends myClass implements IAnimal { ... }
useClass1 u1 = new useClass1() { ... };
useClass2 u2 = new useClass2() { ... };
}
}
class Animal {
public String bark() {
return "동물이 웁니다";
}
}
class Dog extends Animal {
@Override
public String bark() {
return "개가 짖습니다";
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog();
a.bark();
}
}
🔻 위처럼 Super class로부터 상속받은 메서드를 Overriding 하여
사용할 Sub class가 한 번만 사용되고 버려질 자료형인 경우
➠ 해당 Sub class를 local 변수처럼 anonymous class로 정의하고 stack이 끝나면 삭제되도록
하는 것이 유지보수성을 향상시키고 프로그램 메모리적인 측면에서도 이점을 얻을 수 있다 !
class Animal {
public String bark() {
return "동물이 웁니다";
}
}
public class Main {
public static void main(String[] args {
// anonymous class
Animal dog = new Animal() { // dog는 anonymous class의 객체
@Override
public String bark() {
return "개가 짖습니다";
}
// 새로 정의한 메서드
public String run() {
return "달리기";
}
};
dog.bark();
dog.run(); // Compile Error! - 외부에서 호출 불가능
}
}
🔻 재사용할 필요가 없는 일회성 class를 굳이 class로 정의하여 생성하지 않고,
anonymous class를 통해 코드를 줄이는 일종의 기법이라 할 수 있다.
➠ 즉, anonymous class는 Super class의 메서드를 일회성으로 Overriding 하여 사용하기 위한 용도이다 !
🔻 다만 anonymous class 방식으로 선언한다면 Overriding 한 메서드 사용만 가능하고,
새로 정의한 메서드는 Super class 자체에는 선언되어 있지 않기에 외부에서 사용 불가능 !
(다형성의 법칙)
🔺 anonymous class는 아래 코드처럼 람다 표현식과 잘 어울리며 자주 쓰인다 !
Operate operate = new Operate() {
public int operate(int a, int b) {
return a + b;
}
};
// 람다식
Operate operate = (a, b) -> {
return a + b;
};
🔺 아래 코드는 Android app을 개발할 때 anonymous class가 사용된 코드의 일부이다.
FloatingActionButton fab = rootView.findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TreeSet<Integer> set = new TreeSet<>();
while(set.size() < 6) {
int random = new Random().nextInt(45);
set.add(random);
}
ArrayList Aset = new ArrayList(set);
...
▼ Study📋
☑️ 생성자가 상속 불가능한 이유
☑️ 다중상속을 허용했을 때의 문제점
☑️ 클래스의 package를 import 할 때 클래스의 이름을 일일이 지정해주는 것과
' * ' 를 사용하는 것이 성능상의 차이가 없는 이유
☑️ Singleton pattern (싱글톤 패턴 ~ Anti Pattern) 을 사용하는 이유
☑️ static 메서드는 반드시 ' className . 메서드 '로 호출
☑️ interface와 JDBC의 관계
☑️ Android 개발 중 anonymous class (inner class)가 사용되었던 코드
☑️ anonymous class는 람다표현식과 자주 사용
🌑 상속 (Inheritance)
✔️ 상속
🔹 코드의 재사용성 · 프로그램의 생산성 · 유지보수성 ⇑
class SubClass_Name extends SuperClass_Name
🔻 Sub class의 instance를 생성하면 Super class의 instance를 생성하지 않아도
Super class의 멤버들을 사용 가능
➟ Sub class들의 공통적인 부분을 Super class에서 관리 ➠ 코드의 중복성 X · 유지보수성 ⇑
class superClass {
int pmv1;
char pmv2;
boolean pmv3;
// 초기화 블럭
{ ... }
superClass() { ... }
superClass( ... ) { ... }
void pmf( ... ) { ... }
}
class subClass1 extends superClass {
int cmv1;
void cmf( ... ) { ... }
}
class InheritanceExample {
public static void main(String[] args) {
subClass1 childObj = new subClass1(); // Instantiate
childObj.pmv1 = 10;
childObj.pmv2 = 'a';
childObj.pmf( ... );
}
}
🔸 생성자와 초기화 블럭은 상속 불가능 !
( SubClass_Name obj = new SuperClass_Name(); // Not Allowed )
▪️ 생성자가 상속 불가능한 이유
- 생성자는 해당 class의 이름과 동일한 이름을 가져야 한다는 제약 조건에 어긋난다.
- Super class의 생성자를 사용하여 Super class의 private 멤버에 접근 가능하게 되기 때문이다.
▪️ 생성자는 상속되지 않으며, 생성자는 클래스의 객체가 생성될 때 호출
➟ super()가 Sub class에 명시되어 있지 않은 경우 자동으로 추가
➟ Super class 생성자 먼저 호출
➟ 자식 class 생성자 호출
✔️ 포함관계 ≠ 상속관계
🔹 상속관계 ~ IS-A : ' ~은 ~ 이다. '상속관계 - IS-A : ' ~은 ~이다. '
🔹 포함관계 ~ HAS-A : ' ~은 ~을 가지고 있다. '
🔻 class 'Triangle' · 'Circle' ⬌ class 'Point' : 포함관계
class DrawShape {
public static void main(String[] args {
Point[] p = { new Point(100, 100),
new Point(140, 50),
new Point(200, 100)
// 객체를 생성해서 객체 배열의 각 요소에 저장
};
Triangle t = new Triangle(p);
// Point p = new Point(150, 150);
// Circle c = new Circle(p, 50);
Circle c = new Circle(new Point(150, 150), 50);
t.draw();
c.draw();
}
}
class Shape {
String color = "black"
void draw() {
System.out.printf("[color=%s]%n", color);
}
}
class Point {
int x;
int y;
Point() {
this.x = x;
this. y = y;
}
Point() {
this(0, 0);
}
String getXY() {
return "("+x+", "+y+")"; // x와 y이 값을 문자열을 반환
}
}
class Circle extends Shape {
Point center;
Point r;
Circle() {
this(new Point(0, 0), 100); // Circle(Point center, int r)를 호출
}
Circle(Point center, int r) {
this.center = center;
this.r = r;
}
void draw() {
System.out.printf("[center=%d, %d), r=%d, color%s]%n"
center.x, center.y, color);
}
}
class Triangle extends Shape { // IS-A ~ ' 삼각형은 도형이다. '
Point[] p = new Point[3]; // HAS-A ~ ' 삼각형은 점을 가지고 있다. '
Triangle(Point[] p) {
this.p = p;
}
@Overriding
void draw() {
System.out.printf("[p=%s, p2=%s, p3=%s, color=%s]%n",
p[0].getXY(), p[1].getXY(), p[2].getXY(), color);
}
}
🔹 toString()
- 모든 class의 Super class인 Object class에 정의된 메서드
- 어떤 종류의 객체라도 toString()을 호출하는 것이 가능 !
🔻 class Card 의 instance의 주소를 저장하고 있는 c, 즉 참조 변수 c를 출력하면
참조변수가 c가 가리키고 있는 instance가 class Card 에서 오버라이딩된 toString()을 호출
class DeckTest {
public static void main(String[] args) {
Deck d = new Deck();
Card c = d.pick(0);
System.out.println(c); // System.out.println(c.toString());
...
}
}
class Deck {
Card carArr[] = new Card[52];
...
Card pick(int index) {
return cardArr[index];
}
...
}
class Card {
...
public String toString() { // Object Class의 메서드인 toString()을 오버라이딩한 것
...
}
}
🔹 Object Class : 모든 클래스의 상속계층도의 최상위에 있는 Super class
class A {
...
}
⬇ ⬇
class A extends Object { // 자동적으로 상속
...
}
✔️ 단일 상속 (Single Inheritance)
🔹 Java에서 다중상속을 허용하지 않는 이유 ➟ class 간의 관계의 명확성 · 코드의 신뢰성 상승
- 다중상속을 할 경우에 상속받는 class들 안에 선언부(이름, 매개변수)가 같은 메서드가 존재한다면,
static 메서드는 메서드 이름 앞에 class의 이름을 붙여서 구별할 수 있다지만,
인스턴스 메서드는 구별이 불가능하여 호출하는 과정에서 문제가 발생 ! - 이 문제를 해결하려면 Super class의 메서드의 이름이나 매개변수를 변경해야 하는데
그렇게 하면 해당 Super class의 해당 메서드를 사용하는 모든 class들을 변경해야 한다...
🌒 오버라이딩 (Overriding)
✔️ 상속받은 메서드를 Sub class에 맞게 변경하는 것
🔷 오버라이딩의 조건
🔻 동일한 이름 · 매개변수 · 반환타입
🔻 접근 제어자는 Super class의 메서드보다 좁은 범위로 변경 불가능 !
🔻 Super class의 메서드보다 많은 수의 예외 (ex. " throw IOException ") 선언 X
🔷 오버라이딩 (change, modify) ≠ 오버로딩 : 기존에 없는 새로운 메서드 정의 (new)
✔️ super - 참조 변수 ~ 상속받은 멤버를 참조
🔹 Super class의 멤버와 Sub class의 멤버가 중복 정의되어 서로 구별해야 하는 경우 사용
➞ 멤버변수와 지역이름이 같을 때 this를 붙여서 구별하는 것과 유사
class superClass {
int v = 20;
}
class subClass extends superClass {
int v = 10;
void method() {
System.out.println(v); // 10
System.out.println(this.v); // 10
System.out.println(super.v); // 20
}
}
🔻 Super class에 선언된 멤버변수와 같은 이름의 멤버변수를 자손 클래스에서 중복 정의 가능 !
➞ 참조변수 super를 이용해서 서로 구별 가능
🔹 this와 마찬가지로 instance와 관련이 없어, static 메서드에선 사용 불가능
🔹 Super class의 메서드도 super로 호출 가능
➞ Super class의 메서드의 내용에 추가 작업을 덧붙이기 위해 오버라이딩한 경우에 자주 사용 !
✔️ super() - 생성자
🔹 Object class를 제외한 모든 class의 생성자 첫 줄에 생성자, this() 또는 super()를 호출 !
➞ 없을 경우 컴파일러가 자동적으로 'super();'를 생성자의 첫 줄에 삽입
class superClass {
int x, y;
superClass(int x, int y) {
this.x = x;
this.y = y;
}
}
class subClass extends superClass {
int z;
subClass(int x, int y, int z) {
super(x, y);
this.z = z;
}
}
🔻 Super class의 멤버변수는 Super class의 생성자로 초기화하도록 해야 한다 !
🌓 Package · Import
✔️ 패키지 (package) ~ class · Interface 의 묶음
🔹 하나의 소스파일(.java)에는 첫 번째 문장으로 단 한 번의 package 선언만 허용
🔹 모든 class는 반드시 하나의 package에 속해야 한다.
🔹 package는 점(.)을 구분자로 하여 다른 package를 포함 가능
🔹 패키지는 물리적으로 클래스 파일(.class)를 포함하는 하나의 디렉토리이다.
🔸 패키지를 선언하지 않고도 문제가 발생하지 않는 이유
➞ 소스파일(.java)에 자신이 속할 package를 지정하지 않은 class는
자동적으로 'unnamed package'에 속하게 된다.
✔️ import 문
🔹 소스파일(*.java)의 구성
- package 문
- import 문
- class 선언
// import java.util.Calender;
// import java.util.Date;
// import java.util.ArrayList;
import java.util.*;
🔻 import 문을 여러번 사용하는 대신 위와 같이 한 문장으로 처리 가능
➞ import 하는 package의 수가 많을 때는
어느 class가 어느 package에 속하는지 구별하기 어려울 수 있다 !
import java.util.*;
import java.text.*;
≠
import java.*;
🔻 import 문에서 class의 이름 대신 ' * ' 을 사용하는 것이
하위 package의 class까지 포함하는 것이 아니다 !
🔺 System과 String과 같은 java.lang package의 class들을 package명 없이 사용 가능했던 이유
( ex ) java.lang.System.*, java.lang.String variable ...
import java.lang.*;
🔻 위와 같은 import 문이 모든 소스파일(.java)에 묵시적으로 선언되어 있기 때문이다 !
🔹static import 문 ~ static 멤버를 호출할 때 클래스 이름 생략 가능
import static java.lang.Integer.*;
// Integer의 모든 static 메서드를 호출할 때 "Integer" 생략 가능
import static java.lang.Math.random;
import static java.lang.System.out;
// System.out.println(Math.random());
out.println(random());
🔻 특정 class의 static 멤버를 자주 사용할 때 편리 !
🔸 클래스의 package를 import 할 때 클래스의 이름을 일일이 지정해주는 것과
' * ' 를 사용하는 것이 성능상의 차이가 없는 이유
- Compiler가 코드를 Compile 할 때 ' import ' 문이 실제로 포함된 class를 로드하는 것이 아니라,
Compiler는 해당 class의 package 경로를 알려주는 역할 ! - Compiler는 필요한 class를 찾아서 Compile 하는 동안 해당 class를 직접 로드한다.
🌔 제어자 (Modifier)
✔️ 제어자 (Modifier)
🔹 static
- static 멤버변수
- 모든 instance가 공유하는 class 변수 ➟ instance 생성 없이 사용 가능
- class가 메모리에 로드될 때 생성
- static 메서드
- instance 생성 없이 호출 가능한 class 메서드 ➟ instance 멤버 사용 불가능
➟ instance 멤버를 사용하지 않는다면, static으로 선언해주는 것이 편리하며 속도가 빠르다 !
- instance 생성 없이 호출 가능한 class 메서드 ➟ instance 멤버 사용 불가능
🔹 final
- final Class
- 변경 · 확장이 불가능한 class ➟ Super class가 될 수 없다 !
- 변경 · 확장이 불가능한 class ➟ Super class가 될 수 없다 !
- final 메서드
- 변경 · Overriding 불가능 !
- 변경 · Overriding 불가능 !
- final 멤버변수 · 지역변수
- 값을 변경할 수 없는 상수 ➟ 일반적으로 선언 · 초기화를 동시에 한다.
➟ 인스턴스 변수의 경우 생성과 동시에 하지 않아도 생성자에서 단 한 번 초기화 가능 !
- 값을 변경할 수 없는 상수 ➟ 일반적으로 선언 · 초기화를 동시에 한다.
class Example {
final int NUMBER = 5;
final String STR;
final int NUM;
Example(String str, int num) {
STR = str;
NUM = num;
}
}
Class Example Test {
public static void main(String[] args) {
Example obj = new Example("string", 1);
obj.NUM = 2; // Compile Error
}
}
🔹 abstract (추상)
- abstract Class
- Class 내에 abstarct 메서드가 선언되어 있음을 의미 !
- abstract 메서드 ~ 선언부만 작성하고 구현부는 작성하지 않은 메서드
- abstract 메서드가 없는 abstract Class도 존재
✔️ 접근 제어자 (Access Modifier)
🔹 접근 제어자
- private
- 같은 Class 내에서만 접근 가능
- 같은 Class 내에서만 접근 가능
- default
- 같은 package 내에서만 접근 가능
- 같은 package 내에서만 접근 가능
- protected
- 같은 package 내 + 다른 package의 Sub class에서 접근 가능
- 같은 package 내 + 다른 package의 Sub class에서 접근 가능
- public
- 접근 제한 X
🔹 접근제어자를 이용한 Encapsulation (캡슐화)
- 데이터 감추기 (Data Hiding) ➟ 객체지향 개념의 캡슐화 (Encapsulation)
- class나 멤버, 주로 멤버에 접근 제어자를 사용하는 이유
➟ class의 내부에 선언된 데이터 보호 ! - 외부에는 불필요한, 내부적으로만 사용되는 부분을 감추기 위해 !
- 메서드 하나를 변경하고 오류가 없는지 테스트해야 하는 경우, 이 메서드의 접근 제어자가
⇨ public ➟ 테스트해야 하는 범위가 매우 넓다.
⇨ default ➟ 해당 package만 살펴보면 된다 !
⇨ private ➟ 해당 class만 살펴보면 된다 !
- class나 멤버, 주로 멤버에 접근 제어자를 사용하는 이유
🔻 외부에서 잘못된 값을 지정하는 것을 막기 위해 멤버변수를 private이나 protected로 제한하고,
해당 멤버변수의 값을 읽고 변경할 수 있는 public 메서드를 제공하여 간접적으로 멤버변수의 값을 다룬다!
- getter ~ 멤버변수의 값을 읽는 메서드 : 'get멤버변수이름'
- setter ~ 멤버변수의 값을 변경하는 메서드 : 'set멤버변수이름'
public class Time {
private int hour;
private int minute;
private int second;
public int getHour() { return hour; }
pbulic void setHour(int hour) {
if(hour < 0 || hour > 23) return;
this.hour = hour;
}
public int getMinute() { return minute; }
public void setMinute(int minute) {
if(minute < 0 || minute > 59) return;
this.minute = minute;
}
public int getSecond() { return second; }
public void setSecond(int second) {
if(second < 0 | second > 59) return;
this.second = second;
}
}
🔹 생성자의 접근 제어자
- 일반적으로, 생성자의 접근 제어자 = class의 접근 제어자
final class Singleton { // Singleton Pattern
...
private static Singleton instance = new Singleton();
// getInstance()에서 사용할 수 있도록 인스턴스가 미리 생성되어 있어야 하므로 static이어야 한다.
private Singleton() {}
pubilc static Singleton getInstance() {
if(instance == null) instance = new Singleton();
return instance;
}
...
}
- 생성자를 통해 직접 인스턴스를 생성하지 못하게 하는 방법 ➟ 생성자를 private으로 선언
- public 메서드를 통해 instance에 접근하게 함으로써 instance의 개수를 제한 가능 !
- Sub class의 instance를 생성할 때 Super class의 생성자를 먼저 호출
➟ 생성자가 private으로 선언된 class는 Super class가 될 수 없다 !
➟ 따라서, class 앞에 final을 추가적으로 선언하여 상속할 수 없는 class라는 것을 알리는 것이 좋다.
- public 메서드를 통해 instance에 접근하게 함으로써 instance의 개수를 제한 가능 !
✔️ 싱글톤 (Singleton)
🔸Singleton Pattern ( 싱글톤 패턴 ) ~ Anti Pattern
class SingletonTest {
public static void main(String[] args) {
Singleton s = new Singleton(); // Compile Error!
Singleton s = Singleton.getInstance(); // 객체 최초 생성
}
}
- 생성자가 호출되더라도, 실제로 생성되는 객체는 하나이며
최초로 생성된 이후에 호출된 생성자는 이미 생성한 개체를 반환하도록 만드는 것 !
(+) S원래라면 객체를 생성할 때마다 메모리 영역을 할당받아야 하지만,
한번의 new를 통해 객체를 생성하여 메모리 낭비 방지
(+) Singleton으로 구현한 instance는 '전역'이므로, 다른 class의 instance들과의 데이터 공유 가능
(-) Client가 interface가 아닌 구체 class에 의존하게 하여 DIP (의존관계 역전 법칙) 위반
(-) Singleton으로 구현한 instance가 혼자 너무 많은 일을 하거나, 많은 데이터를 공유시키면
다른 class들 간의 결합도 상승
➟ OCP (개방-폐쇄 원칙) 위반, 유지보수가 힘들고 테스트도 원할하게 진행 X
(-) 멀티 스레드 환경에서 동기화 처리를 하지 않았을 때, instance가 2개가 생성될 수 있다.
➟ 해결방안 - Lazy Initialization
private static Singleton s = new Singleton();
➟ Lazy Initialization 는 프로그램 구조가 복잡해지고 비용 문제가 발생할 가능성 ⇈
- Initialization on demand holder idiom (Holder에 의한 초기화)
public class Singleton {
private Singleton() {
}
private static class Holder {
public static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
🔻 Singleton의 초기화 문제에 대한 책임을 JVM에게 떠넘기는 것
- class Holder 에서 선언된 instance는 static이기 때문에, class 로딩 시점에서 한 번만 호출 !
또한, final을 사용해서 다시 값이 할당되지 않도록 만드는 방식
✔️ 제어자 (Modifier) 의 조합
🔹 사용가능한 제어자
- class ➟ public, (default), final, abstract
- 메서드 ➟ 모든 접근 제어자, final, abstract, static
- 멤버변수 ➟ 모든 접근 제어자, final, static
- 지역변수 ➟ final
🔹 불가능한 제어자 조합
- 메서드 ⇏ static + abstract
- 몸통이 있는 메서드만 static으로 선언 가능
- 몸통이 있는 메서드만 static으로 선언 가능
- 메서드 ➟ private와 final (~ 변경 불가능한 메서드) 중 하나만 !
- private으로 선언한 메서드는 어차피 Overriding이 불가능
- private으로 선언한 메서드는 어차피 Overriding이 불가능
- abstarct 메서드 ⇏ private
- abstract로 선언된 메서드는 Sub class에서 구현해주어야 하기 때문이다.
- abstract로 선언된 메서드는 Sub class에서 구현해주어야 하기 때문이다.
- class ⇏ abstact + final
- 선언하려는 의도가 서로 모순
🌕 다형성 (Polymorphism)
class Animal {
String name;
String food;
void move() { ... }
}
class Dog extends Animal { ... }
✔️ 다형성 ?
🔹 Super class 타입의 참조변수로 Sub class의 인스턴스를 참조할 수 있는 성질
🔹 참조변수의 타입에 따라 달라지는 "사용할 수 있는 멤버의 개수" & "참조하는 인스턴스의 타입"
✔️ 참조변수가 사용할 수 있는 멤버의 개수
Animal animal = new Dog(); // OK
Dog dog = new Animal(); // Compile Error!
🔹 ( 참조변수가 사용할 수 있는 멤버의 개수 ) ≤ ( instance 멤버의 개수 ) ➟ 그 이유는?
- Sub class ( Dog ) 타입의 참조변수 ' dog '는 Sub class ( Dog )의 멤버를 사용하려 할 수 있다.
➟ 하지만 ' dog '가 참조하고 있는 instance는 Super class ( Animal ) 타입이기 때문에,
Super class ( Animal ) 타입의 instance에는 해당 멤버가 존재하지 않아 사용 불가능 !
( Super class의 instance의 멤버 개수 ≤ Sub class의 인스턴스의 멤버 개수 ) - Sub class 타입의 참조변수 ➟ Super class 타입의 instance 참조 X
/ Super class 타입의 참조변수 ➟ Sub class 타입의 참조변수 O
✔️ 참조변수가 참조하는 인스턴스의 타입
🔹 참조변수의 형변환 ( 상속관계인 클래스들 사이에만 )
Dog dog = new Dog();
Animal animal = dog; // Up-casting
Dog dog = (Dog) animal; // Down-casting
- Sub Class 타입 ➟ Super Class 타입 ( Up-casting ) : 형변환 생략 O
- Super Class 타입 ➟ Sub Class 타입 ( Down-casting ) : 형변환 생략 X
🔹 Down-casting은 형변환 생략이 불가능한 이유
🔺Up-casting
Animal anmial = (Animal)new Dog();
Animal anmial = new Dog(); ➟ 생략된 형태
// 아래 두 줄을 간략히 한 형태
Dog dog = new Dog();
Animal animal = (Animal) dog;
- Super class ( Animal ) 타입의 참조변수가 사용할 수 있는 멤버의 개수가
실제 instance가 갖고 있는 멤버의 개수보다 적을 것이 분명하므로 형변환을 생략해도 문제 X
🔺Down-casting
Dog wildDog = (Dog)new Animal();
Dog wildDog = new Animal(); ➟ ??
// 아래 두 줄을 간략히 한 형태
Animal animal = new Animal();
Dog wildDog = (Dog)animal; // Runtime Error!
- Down-casting 은 참조변수가 다룰 수 있는 멤버의 개수를 늘리는 것 !
Sub class ( Dog ) 타입의 참조변수가 사용할 수 있는 멤버의 개수가
실제 instance가 갖고 있는 멤버의 개수보다 많아질 수 있으므로 문제가 발생할 수 있다
➠ 따라서, Sub class 타입으로의 형변환은 생략( Compile Error )할 수 없는 동시에,
Sub class 타입으로의 형변환 ( Runtime Error )자체가 불가능 !
🔻이러한 실수를 피하기 위한 방법
➠ 형변환을 수행하기 전에 instancOf 연산자를 사용하여 형변환이 가능한지 체크 !
🔸 참조변수의 형변환은 참조변수의 타입을 변환하는 것
➠ instance를 변환하는 것이 아니기 때문에, instance에 아무런 영향을 미치지 않는다.
➠ 참조변수의 형변환을 통해, 참조하고 있는 instance에서 사용할 수 있는 멤버의 범위(개수)를 조절 !
🔹 instanceOf 연산자 ( left : 참조변수 / right : 타입 (class명) )
Animal animal = new Animal();
Dog dog = new Dog();
if(animal instanceOf Dog) {
System.out.println("animal is a instance of Dog.");
}
if(dog instanceOf Animal) {
System.out.println("dog is a instance of Animal.");
}
dog is a instance of Animal
🔻 instanceOf를 이용한 연산결과 ➟ true (참조변수에 null 값이 이 저장된 경우엔 false)
➜ 검사한 타입으로 형변환이 가능하다는 것 !
✔️ 참조변수와 instance의 연결
🔹 Sub class에서 Super class의 멤버를 중복으로 정의하지 않은 경우
➜ 참조변수의 타입에 상관없이 Super class의 멤버 사용
class RvTest {
public static void main(String[] args) {
...
}
}
class Animal {
static int num = 10;
String name = "animal_name";
String food = "food";
void move() {
System.out.println("Animal is moving");
}
static void eat() {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
static int num = 5;
String name = "dog_name";
String food = "meat";
@Overide
void move() {
System.out.println("Dog is moving");
}
@Overide
static void eat() {
System.out.println("Dog is eating");
}
}
🔸 Sub class에서 Super class의 멤버를 중복으로 정의한 경우
Animal animal = new Dog();
Dog dog = new Dog();
// 참조변수 'animal', 'dog' 모두 class 'Dog'의 instance를 참조
- 중복된 멤버변수
➢ 참조변수 타입과 같은 클래스의 멤버변수 반환
System.out.println("animal name : " + animal.name);
System.out.println("dog name : " + dog.name);
animal name : animal_name
dog name : dog_name
- Overriding한 메서드
➢ 실제 instance의 타입인 class에 정의된 메서드 호출
animal.move();
dog.move();
dog is moving
dog is moving
- static 메서드 ( ~ static 변수처럼 )
➢ 참조변수 타입에 영향을 받아, 꼭 'className.메서드()'로 호출
animal.eat();
dog.eat();
System.out.println();
Dog.eat();
animal is eating
dog is eating
dog is eating
🔹 참조변수 super · this 로 구분
- Sub class ( Dog ) 에 선언된 instance 변수 ' num '
- Super class ( Animal ) 로부터 상속받은 instance 변수 ' num '
🔸 Encapsulation ( 캡슐화 ) 의 필요성
- 멤버변수들은 주로 private으로 접근 제한하여, 외부에서는 메서드를 통해서만 멤버변수에 접근하도록 한다 !
- 보통은 위의 예제들처럼 참조변수를 통해 직접적으로 instance변수에 접근하지 않는다 !
✔️ 다형성 ( Polymorphism ) 의 장점
🔸 다양한 타입의 객체들을 동일한 타입의 참조변수로 참조할 수 있다 !
- 유연성 ⇈
➜ 객체 배열에 여러 타입의 객체들을 저장하고 일관된 방식으로 다룬다. - 가독성 · 유지보수성 ⇈
➜ 동일한 타입의 참조변수로 다양한 객체들을 다루기 때문에, 코드가 더 명확하고 간결해진다. - 확장성 ⇈
➜ 기존 class를 변경하지 않고도 새로운 class를 추가하거나 새로운 객체들을 추가할 수 있다.
class Product {
int price;
...
}
class Tv extends Product { ... }
class Computer extens Product { ... }
class Buyer {
int money = 1000;
int bonusPoint = 0;
}
🔸 매개변수의 다형성
void buy(Product p) {
money -= p.price;
bonusPoint += p.bonusPoint;
}
/*
void buy(Tv t) {
money -= t.price;
bonusPoint += t.bonusPoint;
}
void buy(Computer c) {
money -= c.price;
bonusPoint += c.bonusPoint;
}
*/
➠ 매개변수 ( p ) 가 Super class ( Product ) 타입의 참조변수라는 것
➨ Super class의 Sub class 타입의 참조변수면 어느 것이나 매개변수로 받을 수 있다는 것
⇨ 즉, 새로운 class를 추가하더라도 Super class를 상속받기만 하면
Sub class의 instance를 매개변수로 제공 가능 !
🔸 매개변수의 다형성의 또 다른 예
// 실제 코드
public void print(Object obj) {
write(String.valueOf(obj));
}
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
- print(Object obj)
- Object는 최상위 Super class이므로 이 메서드의 매개변수로 어떤 타입의 intance도 가능
➠ 이 print(Object obj) 메서드로 모든 타입의 instance 처리 !
- Object는 최상위 Super class이므로 이 메서드의 매개변수로 어떤 타입의 intance도 가능
🔸 객체 배열 ( item )을 이용해 여러 종류의 객체를 다룬다
- 객체 배열의 크기를 동적으로 선언하는 방법
➟ ' Vector item = new Vector(); ' , ' ArrayList<item> items = new ArrayList<>(); ' , etc.
class Product {
int price;
int bonusPoint;
Product(int price) {
this.price = price;
bonusPoint = (int)(price/10.0);
}
}
class Tv extends Product {
Tv() {
super(100);
}
@Override
public String toString() { return "Tv"; }
}
class Computer extends Product {
Computer() {
super(200);
}
@Override
public String toString() { return "Computer"; }
}
class Buyer {
int money = 1000;
int bonusPoint = 0;
// Product p1 = new Tv();
// Product p2 = new Computer();
Product[] item = new Product[10];
int i = 0;
void buy(Product p) {
if(money < p.price) { // 구매 불가
return;
}
// 제품 구입
money -= p.price;
bonusPoint += p.bonusPoint;
item[i++] = p;
}
void printHistory() {
int sumPrice = 0;
String itemList = "";
StringBuilder sb = new StringBuilder();
for(int i = 0; i < item.length; i++) {
if(item[i] == null) break;
sum += item[i].price;
sb.append(item[i]);
}
itemList = sb.toString();
System.out.println(sum);
System.out.println(itemList);
}
}
Buyer b = new Buyer();
// Tv t = new Tv();
// b.buy(t);
b.buy(new Tv());
b.buy(new Computer());
b.printHistory();
700
Tv Computer
➢ Sub class의 instance를 매개변수에 제공
🌖 추상클래스 (abstract class)
✔️ abstact ?
🔹 abstract class
abstract class ClassName {
...
abstract void abMtd();
...
}
- abstract 메서드를 포함하고 있다는 점을 제외하고는 일반 class와 차이 X
- 생성자, 멤버변수, 그리고 메서드도 가질 수 있다 !
- abstract class는 instantiate 불가
🔹 abstract 메서드
abstract returnType methodName();
- 선언부만 존재 +
구현부➟ 미완성 메서드
🔸 abstract 메서드 구현 (강제) ⟺ Super class 메서드 Overriding (선택)
🔹 추상화 (abstract)
➟ class 간의 공통점을 찾아내서 공통의 Super class를 만드는 작업
🔹 구체화
➟ 상속을 통해 class를 구현, 확장하는 작업
🔹 abstract class를 사용하는 이유
public abstract class Animal {
...
public abstract void bark();
// 동물들이 짖는다는 공통점을 찾아 추상화, 짖는 소리는 달라 abstract 메서드로 생성
...
}
public class Dog extends Animal {
...
@Override
public void bark() {
System.out.println("Bark");
};
}
public class Wolf extends Animal {
...
@Override
public void bark() {
System.out.println("Howl");
};
}
🔻 구현의 강제성을 통한 기능 보장
- 일반 class로 상속 관계를 맺는 상황에서 Overriding을 깜빡한다면, 디버깅으로 찾기 까다롭다
🔻 객체들의 멤버변수와 메서드의 이름을 통일하여 코드의 가독성 ⇈
➟ 개발자 멤버변수와 메서드 추측 가능 ( " Dog에 bark가 있으니까 Wolf에도 bark가 있을 것 ! " )
🔻 상속과 다형성의 장점을 누릴 수 있다
- abstract class를 상속한 Sub class를 abstract class로 다룰 수도 있기 때문이다 !
🔻 초기 설계 시간 절약 · 구현부 작성 용이
- abstract class를 상속받아서 미리 선언된 abstract 메서드를 구현하고,
실체 class에서 필요한 기능을 class 별로 확장시킴으로써 코드의 유지보수성 ⇈ - 설계자가 규격에 맞게 소스를 구현해놓기 때문에, 코더가 해당 규격에 맞게 실체 class를 작성하면 된다.
🌗 인터페이스 (interface)
class Unit {
int currentHP;
int x;
int y;
}
✔️ interface ?
🔹 일종의 abstract class ~ 기본 설계도 ( ⟺ 미완성 설계도)
- abstract class보다 추상화 정도가 높다
➠ 몸통을 갖춘 일반 메서드 또는 멤버 변수를 구성원으로 X
➠ 오직 추상 메서드 ( public abstract ) 와 상수 ( public static final ) 만을 멤버로 가질 수 있다 !
⤷ 생략 가능 ⤷ 생략 가능 ( Compiler가 자동적으로 추가 ) - interface는 간단히 말해서 어떠한 기능 또는 행위를 하기 위한 메서드를 제공하는 용도로 쓰인다.
( interface명을 주로 ' ~able '로 작성하는 이유 ) - interface로부터만 상속(extends)받을 수 있으며, 다중 상속이 가능 !
➠ 상속받은 interface에 정의된 멤버를 모두 상속받는다.
interface Movable { void move(int x, int y); }
interface Attackable { void attack(Unit u); }
interface Fightable extends Movable, Attackable { }
- Sub class에 상속(extends)과 구현(implements)은 동시에 가능
class Fighter extends Unit implements Fightable {
public void move(int x, int y) { ... }
public void attack(Unit u) { ... }
}
🔻 interface ' Movable '에 정의되어 있는 ' move '는 ' public abstract '가 생략된 것 !
따라서, Overriding 할 때에는 조상의 메서드보다 넓은 범위의 접근제어자를 지정해야 한다는 점 때문에,
상속받은 interface의 메서드를 구현할 때의 선언부는 public으로 지정해줘야 한다 !
- 상속받는 class에서 구현한다는 의미로 ' implements '로 interface를 상속
- interface는 class와 달리 Object class와 같은 최상위 Super interface가 없다.
➠ 따라서, class를 상속하는 행위 불가능 - 구현해야 하는 메서드들 중 일부만 구현한다면, abstract class로 선언해줘야 한다.
abstract class Fighter implements Fightable {
public void move(int x, int y) { ... }
}
✔️ interface 다형성
🔹 interface를 이용한 다형성
- interface 타입의 참조변수로 이를 구현한 (Sub) class의 instance를 참조 가능하며,
interface 타입으로의 형변환도 가능
Fightable f = (Fightable)new Fighter(); // Up-casting
// Fightable f = new Fighter();
🔻 Fightable 타입의 참조변수는 interface Fightable에 정의된 멤버들만 호출 가능
void attack(Fightable f) { ... }
// void attack(new Fighter()) { ... }
🔻 매개변수가 interface 타입 ➠ 해당 interface를 구현한 (Sub) class의 instance를 매개변수로 제공
Fightable method() {
...
// Fighter f = new Fighter();
// return f;
return new Fighter();
}
🔻 return 타입이 interface ➠ 메서드가 해당 interface를 구현한 (Sub) class의 instance를 반환 !
- interface를 자료형으로 쓰는 습관을 들이자 !
- 객체는 class가 아닌 interface로 참조 !
- 적당한 interface가 있다면 매개변수뿐만 아니라 return값, 변수, 필드를 전부 interface 타입으로 선언
- 객체의 실제 class를 사용할 상황은 '오직' 생성자로 생성할 때 뿐이다.
- 매개변수 타입으로는 class 보다는 interface를 활용하자 !
// LinkedHashSet<Object> s = new LinkedHashSet<>(); // Bad case
Set<Object> s = new LinkedHashSet<>(); // Good case
// LinkedHashSet으로 구현했다가 TreeSet class로 변경해야 한다면,
// 그냥 interface 타입의 변수에 재할당만 해주면 된다.
s = new TreeSet<>();
🔻 위처럼 변수에 담긴 구현 class를 다른 Set 자료형 class ( TreeSet ) 로 교체하고자 할 때,
그냥 새 class의 생성자를 다시 호출해주기만 하면 되어 간편하다 !
🔻 하지만 LinkedHashSet 과 달리 반복자의 순회 순서를 보장하지 않는 HashSet으로 변환하고자 하는 경우엔
로직상 문제가 생길 수 있기 때문에, interface 타입으로 선언하는 습관이 항상 좋은 것만은 아니다.
✔️ iterface의 이해
🔹 interface의 장점
- 서로 관계없는 class들에게 관계를 맺어줄 수 있다 !
- Java의 class 상속 구조는 Super - Sub 관계로만 가능하며 단일 상속의 원칙을 따르기 때문에,
관계를 맺어주고자 하는 class들이 각기 다른 Super class를 상속하고 있다면 관계를 맺어줄 수 없다. - interface를 이용하면 아무 관계도 없는 class들에게 하나의 interface를
구현(상속)하게 함으로써 관계를 맺어줄 수 있다.
- Java의 class 상속 구조는 Super - Sub 관계로만 가능하며 단일 상속의 원칙을 따르기 때문에,
- 표준화 가능
- 프로젝트에 사용되는 기본 틀을 interface로 제공하고, 개발자들이 interface를
구현하여 프로그램을 작성함으로써 보다 일관되고 정형화된 프로그램 개발 가능
➠ Java의 Data Base interface인 JDBC가 대표적이다.
➠ 추상화된 interface 규칙에 따라 다양한 종류의 DB를 사용하더라도
동일하게 접속할 수 있는 것은 JDBC라는 interface로 표준화되어 있기 때문이다 !
- 프로젝트에 사용되는 기본 틀을 interface로 제공하고, 개발자들이 interface를
- 개발시간 단축
- interface를 사용하여 메서드를 호출하는 쪽에선 interface가 구현되지 않았더라도
메서드의 선언부만 보고 프로그램 작성 가능 - interface를 사용하는 쪽과 구현하는 쪽이 동시에 개발 가능하다 !
- interface를 사용하여 메서드를 호출하는 쪽에선 interface가 구현되지 않았더라도
- 독립적인 프로그래밍이 가능
- interface를 이용하면 class의 선언과 구현을 분리,
즉 class 간의 직접적인 관계를 간접적인 관계로 변경하면, 어떤 한 class의 변경이
그 class와 관계있는 다른 class에 영향을 미치지 않는 독립적인 프로그래밍이 가능한 것 !
- interface를 이용하면 class의 선언과 구현을 분리,
🔹 interface의 본질적인 측면
🔺 class를 사용하는 User와 class를 제공하는 Provider가 있다.
🔺 메서드를 사용(호출)하는 쪽 (User)에서는 사용하려는 메서드 (Provider)의 선언부만 알면 된다.
( 구현부는 몰라도 된다 )
class User {
private MySql db = new MySql();
public void connectDb() {
db.connectDb();
}
}
🔻 class ' User ' - class ' MySql ' (Provider)
➠ 직접적인 관계 (' MySql '이 변경되면 ' User '도 영향을 받는다)
interface Database {
public abstract void connectDb();
}
⇨ interface를 이용해 메서드 ' connecDb'의 선언부와 구현부를 분리
class MySql implements Database {
public void connectDb() { ... }
}
⇨ class ' MySql'이 interface ' Database '의 메서드 'connectDb '를 구현
class User {
Database db = new MySql();
public void loadDb() {
db.connectDb();
}
}
⇨ class ' User '가 interface ' Database '에 의존하도록 변경
➠ 'User - Database - MySql'의 간접적인 관계
🔻 하지만, class ' User ' class ' MySql '에 대한 의존성이 완전히 제거된 것은 아니다 !
➠ 제 3의 class를 통해 class ' Database '를 구현한
class ' MySql '의 instance를 동적으로 제공받는다면 해결 가능 !
class User {
public void loadDb() {
Database db = DbManager.getDb(); // static으로 선언되어 instance 생성없이 호출 가능
db.connect();
}
}
// 제 3의 class
class DbManager {
public static Database getDb() {
return new MySql();
}
}
✔️ default 메서드 · static 메서드
🔸 default 메서드와 static 메서드를 통해 abstract class처럼 구현 메서드를 정의 가능 !
⭢ 기존 interface에 abstract 메서드를 추가하면,
해당 interface를 구현하고 있는 모든 class가 변경이 필요해지는 문제 발생
⭢ interface에 default 메서드를 새로이 추가하여 해결
⭢ static 메서드도 instance와 관계없는 독립적인 메서드이기 때문에 interface에 추가
해도 상관없지만, interface의 규칙을 단순히 하기 위해서 interface ' Collections '와
관련된 static 메서드들이 별도의 class ' Collenctions '에 들어가게 되었다.
⭢ 하지만 JDK1.8부터는 static 메서드도 interface에 추가 가능하게 된 것 !
➠ 이제는 interface와 abstract class와의 차이점이 거의 사라졌다고 말하기도 한다.
🔹 default 메서드
interface I {
void method();
default void newMethod();
}
- interface가 변경되야만 하는 경우에,
default 메서드를 새로 추가하더라도 해당 interface를 구현한 class를 변경할 필요 X
➠ 만약 기존의 메서드와 이름이 중복되어 충돌한 경우 !
- 새롭게 추가한 메서드와 같은 내용으로 Overriding 해주기만 하면 된다.
- 접근 제어자는 public 이며, 생략 가능
🔹 static 메서드
- 접근 제어자는 public 이며, 생략 가능
🌘 내부 클래스 (inner class)
✔️ 내부 클래스 ~ class 내에 선언된 class
🔹 inner class 를 쓰는 이유
- outer class와 긴밀한 관계에 있지만, 해당 inner class가 그 외의 class에선 잘 사용되지 않는 경우
➠ inner class에서 outer class의 멤버들에 쉽게 접근 가능 !
➠ 코드의 복잡성을 줄일 수 있다 ! ( Encapsulation )
🔹 inner class 의 종류
- 변수처럼 inner class의 선언 위치에 따라 유효범위와 접근성이 달라진다 !
➠ instance class, static class, local class, anonymous class
class Outer {
private int iv = 0;
protected static int cv = 0;
void myMethod() {
int iv = 0;
}
}
class OuterClass {
private class InstanceInner {}
protected static class StaticInner {}
void myMethod() {
class LocalInner {}
}
}
🔻 instance class (' InstanceInner ')와 static class (' StaticInner ')는 outer class (' OuterClass ')의
멤버변수(instance 변수, class 변수)와 같은 위치에 선언되며, 멤버변수와 같은 성질을 갖는다.
🔻 inner class도 class이기 때문에 abstract, final과 같은 제어자도 사용 가능하며,
멤버변수처럼 private, protected과 같은 접근 제어자도 사용 가능 !
- instance class
- outer class의 instance 멤버를 객체생성 없이 바로 사용 가능 ( ~ instance 멤버 )
- outer class의 static 멤버도 접근 가능하며, private로 선된 멤버도 접근 가능
- static class
- static class만 static 멤버를 가질 수 있다.
하지만, final static으로 정된 변수는 상수(constant)이므로, 모든 inner class에서 정의 가능 - outer class의 instance 멤버를 객체생성 없이 사용 불가능 ( ~ class (static) 멤버 )
- static class만 static 멤버를 가질 수 있다.
- local class
- local class가 정의된 메서드에서 final로 정의된 local 변수에만 접근 가능 !
- JDK1.8부터는 local class에서 접근하는 local 변수 앞에 final을 생략하더라도
Compiler에서 자동적으로 추가
➠ 단, local class에서 접근한 local 변수의 값이 바뀌는 문장이 있다면 Compile Error ! - local 변수처럼 local inner class도 다른 메서드에 같은 이름의 inner class가 존재
- Anonymous class (익명 클래스)
- 다른 inner class와 달리 이름이 없고, 이는 곧 나중에 다시 불러질 이유가 없다는 의미.
➠ 즉, 프로그램에서 일시적으로 한 번만 사용되고 버려지는 객체 ! - class 정의와 동시에 객체( = 익명 객체 )를 생성한다.
➠ 단 하나의 class를 상속받거나 단 하나의 interface만을 구현 가능 ! - anonymous 객체는 단 하나의 interface만 구현하여 생성된다는 한계점을 지니고,
이는 다중 상속(구현)이 가능하다는 interface의 가장 큰 본질에 적합 X
➠ 어쩔 수 없이 일회용 용도일지라도 아래의 코드처럼,
다중 상속(구현)한 class를 따로 정의하여 사용해야 한다.
- 다른 inner class와 달리 이름이 없고, 이는 곧 나중에 다시 불러질 이유가 없다는 의미.
interface IAnimal { ... }
interface ICreature { ... }
abstract class myClass { ... }
public class Main {
public static void main(String[] args) {
// class와 interface를 상속, 구현한 일회용 class
class useClass1 implements IAnimal, ICreature { ... }
// class와 interface를 상속, 구현한 일회용 class
class useClass2 extends myClass implements IAnimal { ... }
useClass1 u1 = new useClass1() { ... };
useClass2 u2 = new useClass2() { ... };
}
}
class Animal {
public String bark() {
return "동물이 웁니다";
}
}
class Dog extends Animal {
@Override
public String bark() {
return "개가 짖습니다";
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog();
a.bark();
}
}
🔻 위처럼 Super class로부터 상속받은 메서드를 Overriding 하여
사용할 Sub class가 한 번만 사용되고 버려질 자료형인 경우
➠ 해당 Sub class를 local 변수처럼 anonymous class로 정의하고 stack이 끝나면 삭제되도록
하는 것이 유지보수성을 향상시키고 프로그램 메모리적인 측면에서도 이점을 얻을 수 있다 !
class Animal {
public String bark() {
return "동물이 웁니다";
}
}
public class Main {
public static void main(String[] args {
// anonymous class
Animal dog = new Animal() { // dog는 anonymous class의 객체
@Override
public String bark() {
return "개가 짖습니다";
}
// 새로 정의한 메서드
public String run() {
return "달리기";
}
};
dog.bark();
dog.run(); // Compile Error! - 외부에서 호출 불가능
}
}
🔻 재사용할 필요가 없는 일회성 class를 굳이 class로 정의하여 생성하지 않고,
anonymous class를 통해 코드를 줄이는 일종의 기법이라 할 수 있다.
➠ 즉, anonymous class는 Super class의 메서드를 일회성으로 Overriding 하여 사용하기 위한 용도이다 !
🔻 다만 anonymous class 방식으로 선언한다면 Overriding 한 메서드 사용만 가능하고,
새로 정의한 메서드는 Super class 자체에는 선언되어 있지 않기에 외부에서 사용 불가능 !
(다형성의 법칙)
🔺 anonymous class는 아래 코드처럼 람다 표현식과 잘 어울리며 자주 쓰인다 !
Operate operate = new Operate() {
public int operate(int a, int b) {
return a + b;
}
};
// 람다식
Operate operate = (a, b) -> {
return a + b;
};
🔺 아래 코드는 Android app을 개발할 때 anonymous class가 사용된 코드의 일부이다.
FloatingActionButton fab = rootView.findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TreeSet<Integer> set = new TreeSet<>();
while(set.size() < 6) {
int random = new Random().nextInt(45);
set.add(random);
}
ArrayList Aset = new ArrayList(set);
...