A. 추상 클래스와 추상 메서드
추상 클래스
- abstract 수식어가 사용된 클래스
- 추상 메서드의 유무와 상관없이 abstract 키워드 사용 가능
- 대부분이 경우, 추상클래스는 1개 이상의 추상 메서드를 갖고 있는 경우가 많음. (반드시 그런 것은 아님.)
- 클래스에 추상메서드가 있는 경우에는 반드시 추상 클래스로 선언해야 한다.
- 추상 클래스는 객체를 생성할 수 없음. 생성자를 호출할 수 없음.
추상 메서드
- 메서드의 signature (수식어, 리턴 타입 , 메서드 이름, 파라미터 선언)만 선언어 있고 바디가 정의되어 있지 않은 메서드를 의미한다.
- 즉, 기능이 정의되어 있지 않은 메서드
- 추상 메서드는 반드시 abstract 키워드로 수식해야 함
- 추상 메서드 선언은 반드시 세미콜론(;)으로 끝나야 함.
- 추상 메서드를 정의할 때, 클래스에도 abstract 키워드가 꼭 필요함.
- 추상 메서드는 추상 클래스에서만 작성할 수 있음
목적
추상 클래스와 추상 메서드는 상속받는 하위 클래스에서의 구현을 목적으로 한다.
하위 클래스에서의 구현을 강제
- 동물이라는 클래스가 있다고 하자.
- 동물은 다양한 구체적 동물(개, 새) 등등이 있고, 각자 다 다른 움직임을 갖는다.
<aside> 💡 추상 클래스 Animal 타입으로는 객체 생성이 되지 않음!
abstract class Animal {
abstract public void move();
}
public class InheritanceMain07 {
public static void main(String[] args) {
Animal animal = new Animal();
}
}
- 하이라이트 부분에서 문제 발생
- CANNOT INSTANTIATE THE TYPE ANIMAL </aside>
추상 클래스를 상속하는 클래스를 만들어보자
class Dog extends ~~Animal~~{}
상속한 추상 메서드를 보충해야 한다/.
[예제] 추상 클래스
package com.itwill.inheritance08;
public abstract class Shape {
// field
protected String type; // (1)
// 생성자
protected Shape(String type) {
this.type = type;
}
// 추상 메서드
public abstract double area();
// 도형의 넓이를 리턴하는 메서드
public abstract double perimeter();
// 도형의 둘레 길이를 리턴하는 메서드
// (2)
public final void draw() {
String info = String.format("%s[넓이=%2f, 둘레=%f]", type, area(), perimeter());
System.out.println(info);
} // (3)
}
- 직사각형, 원, 사각형
- 일반) final method - 하위 클래스에서 재정의 할 수 없는 메서드
- 그래픽 함수 등에서 요런 패턴을
package com.itwill.inheritance08;
public class Rectangle extends Shape {
private double width; // 직사각형 가로 길이
private double height; // 직사각형 세로 길이
protected Rectangle(double width, double height) {
super("RECTANGLE");
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
@Override
public double perimeter() {
return 2 * (width + height);
}
}
package com.itwill.inheritance08;
public class InheritanceMain08 {
public static void main(String[] args) {
// Rectangle 타입 객체 생성
Rectangle rect1 = new Rectangle(4, 3);
rect1.draw();
}
}
- 구글이나 카카오 등에서 제공하는 라이브러리 들을 사용할 때도 마찬가지로, 제공하는 추상 클래스를 상속받아 사용할 수 있는 것들이 있다.
- 추상 메서드들만 특정 클래스들에 맞춰 기능을 만들면, 상위 클래스가 가지고 있는 공통 메서드들을 이용할 수 있다.
B. 인터페이스
사용 목적
- 팀/회사 간의 분업이나 협업을 하기 위해서 메서드들의 signature를 약속하기 위한 규약
- 인터페이스가 가질 수 있는 멤버들:
- public static final 필드 : 즉, 상수만 가질 수 있다.
- 필드의 수식어는 항상 위와 동일하며, public static final 수식어는 보통 생략된다
- public abstract 메서드
- public abstract 수식어는 보통 생략
- public static final 필드 : 즉, 상수만 가질 수 있다.
- 인터페이스를 작성할 때, interface 키워드로 선언.
- 인터페이스는 객체를 생성할 수 없고, 인터페이스를 구현하는 클래스를 작성해서 객체를 생성
- class 클래스_이름 implements 인터페이스 { 구현부 작성 … }
- 클래스는 단일 상속만 가능하나, 인터페이스 구현은 개수 제한이 없음.
- 인터페이스는 상위 인터페이스를 상속(확장)할 수 있다.
- interface 하위 인터페이스 extends 상위인터페이스 {… …}
인터페이스의 다형성
package com.itwill.interface01;
import com.itwill.database.DatabaseModule;
import com.itwill.database.OracleDatabase;
public class InterfaceMain01 {
public static void main(String[] args) {
DatabaseModule db = new OracleDatabase();
// SuperType var = new SubType(); 다형성
db.select();
db.insert(null);
}
}
- DatabaseModule은 인터페이스, OracleDatabase는 구현 클래스
- Super 타입(interface)으로 타입 지정하고, 생성자는 하위타입으로 하면, 실제 구현된 메서드를 사용하면서 만약 기능 변경이 생겨 클래스를 대체하려 하더라도, 생성자의 명만 변경하면 된다.
- 이전에는 인터페이스가 완전 껍데기만 있는 약속의 도구 였는데 지금은 추상 클래스와의 경계가 모호해진 것이 맞음. (선생님도 가장 큰 단점이 된 것 같다고 말씀하심!)
- 그렇지만 그럼에도 자바에서 불가능한 다중 상속을 할 수 있는 것이 가장 큰 차이점
- 또한 목적의 주제가 다르다고 이해하면 될 것 같다.
- ⇒ 인터페이스 : 분업을 위한
- ⇒ 추상 클래스 :
import static 문장
- 인터페이스나 클래스에서 static으로 선언된 필드 또는 메서드 이름을 임포트하는 문장
- 선언 방법
package com.itwill.interface01;
import com.itwill.database.DatabaseModule;
import com.itwill.database.OracleDatabase;
import static com.itwill.database.DatabaseModule.DB_VERSION;
public class InterfaceMain01 {
public static void main(String[] args) {
... ...
System.out.println("버전 = " + DB_VERSION);
// System.out.println("버전 = " + DatabaseModule.DB_VERSION); 와 동일
}
- 자바에서는 임포트 문장들의 순서가 그렇게 중요하지는 않지만, 위와 같은 방식으로
- 클래스 임포트 문장들
- ⇒ 스태틱 임포트 문장들
- 에자일…ㅋㅎㅋ
인터페이스가 가질 수 있는 멤버들
자바 8 버전 이상의 변경 사항
인터페이스의 가시성 수식어는 별다른 명시가 없다면 public이다.
- [public static final] 필드 ⇒ 필드는 유일해!
- [public abstract] 메서드 - body가 없는 추상 메서드
- ⇒ 구현 클래스에서 반드시 구현해야 함
- [public] static 메서드 - Java 8 부터 가능한 멤버
- =>객체 생성 없이, 인터페이스 이름을 접두사로 써서 호출할 수 있는 메서드
- [public] default 메서드 - Java 8부터 가능한 멤버 => 인터페이스 안에 있는 바디가 구현된 메서드. (인스턴스 메서드)
- 바디가 있으면 default 수식어를 꼭 붙여주어야 함.
- 인터페이스를 구현한 하위타입의 객체를 생성한 후 호출할 수 있는 메서드
- private static 메서드 - Java 9 부터
- 바디가 구현된 메서드. static나 default 메서드에서
- static (1) 또는 default(2) 메서드에서만 호출할 목적으로 만드는 메서드
- private 메서드 ⇒ Java 9부터
- body가 구현된 메서드
- default 메서드에서만 호출할 목적으로 만든 메서드
package com.itwill.interface02;
/*
* 인터페이스가 가질 수 있는 멤버들 - 자바 8 버전 이상의 변경 사항
* 1. [public static final] 필드
* 2. [public abstract] 메서드 - body가 없는 추상 메서드 => 구현 클래스에서 반드시 구현해야 함
* 3. [public] static 메서드 - Java 8 부터 가능한 멤버 =>객체 생성 없이, 인터페이스 이름을 접두사로 써서 호출할 수 있는 메서드
* 4. [public] default 메서드 - Java 8부터 가능한 멤버 => 인터페이스 안에 있는 바디가 구현된 메서드. (인스턴스 메서드)
* 바디가 있으면 default 수식어를 꼭 붙여주어야 함.
* 5.
*
*/
interface Test {
// 1. [public static final] 필드
int VERSION = 1;
// 2. [public abstract] 메서드 - body가 없는 추상 메서드
void test();
// 3. [public] static 메서드 - Java 8 부터 가능한 멤버
static void staticMethod() {
System.out.println("공개 static Method");
}
// 4. [public] default 메서드 - Java 8 부터 가능한 멤버
default void defaultMethod() {
System.out.println("공개 기본 메서드");
}
}
class TestImpl implements Test {
public void test() {
System.out.println("테스트 코드");
}
}
public class InterfaceMain02 {
public static void main(String[] args) {
Test.staticMethod(); // 인터페이스의 public static 메서드 호출
Test test = new TestImpl();
test.defaultMethod();
}
}
C. ENUM(열거형 타입)
Enum Weekday
package com.itwill.switch03;
public enum Weekday {
SUN, MON, TUE, WED, THU, FRI, SAT;
}
SwitchMain03 Weekday
package com.itwill.switch03;
public class SwitchMain03 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Weekday weekday = Weekday.MON;
switch (weekday) {
case MON:
System.out.println("회사 가기 싫어...");
break;
case TUE:
case WED:
break;
case THU:
case FRI:
break;
case SAT:
case SUN:
break;
}
}
}
- 상수들만 정의한 특별한 클래스
- 자바 ver.5부터 나왔음… 이전에는 인터페이스나 클래스로 상수를 정의했음
- ENUM이 나오기 이전에 사용되었던 상수 클래스/인터페이스 (1번 단계)
- public static final 이 기본적으로 필드의 속성이 되고 객체 생성되 안되는 interface
- 정수 타입 switch 케이스로 사용
- 코드의 가독성을 높이기 위한 방법
package com.itwill.enum01;
public class Meal {
// 상수들을 정의 :
public static final int BREAKFAST = 0;
public static final int LUNCH = 1;
public static final int DINNER = 2;
private Meal() {}
}
package com.itwill.enum01;
public interface Meal02 {
int BREAKFAST = 0;
int LUNCH = 1;
int DINNER = 2;
}
CLASS, INTERFACE로 만들면 불편한 점…
- 매번 상수들을 정의해야 함
- 각각 다른 값으로 할당을 해줘야함..
- 다음과 같은 상황에서 매우 위험
package com.itwill.enum01; public class EnumTest { public static void main(String[] args) { int meal = Meal.BREAKFAST; switch (meal) { case Season.SPRING: break; case Season.SUMMER: break; case Season.FALL: break; case Season.WINTER: break; } } }
- 위 코드에서 식사를 기준으로 스위치 케이스를 써야 하는데, int 타입의 값만 비교하기 때문에, 다른 ENUM을 사용하더라도 문법상의 오류가 없다.
- package com.itwill.enum01; public interface Season { // public static final 상수들 int SPRING = 0; int SUMMER = 1; int FALL = 2; int WINTER = 3; }
- 객체의 주소값을 이용하여 분기 구분 (2번 단계)
- 객체의 주소값은 겹칠 일이 없기 때문에 위와 같이 각 Enum 역할을 하는 클래스들을 구분할 수 있다.
- 객체의 주소값은 switch 케이스 사용이 불가능하기 때문에 if ~ else 구문을 사용해야 함.
package com.itwill.enum01; public class Meal2 { private Meal2() { } public static final Meal2 BREAKFAST = new Meal2(); public static final Meal2 LUNCH = new Meal2(); public static final Meal2 DINNER = new Meal2(); } class Season2 { private Season2() { } public static final Season2 SPRING = new Season2(); public static final Season2 SUMMER = new Season2(); public static final Season2 FALL = new Season2(); public static final Season2 WINTER = new Season2(); }
- 위 코드를 그냥 특별한! 타입인 enum 타입을 만들어서 사용한 것!
- 즉, enum은 각 상수 별로 객체의 주소값을 저장하는 것.
- enum 안에서도 public 메서드 만들 수 있어여
- 상수 선언이 끝나버리고 난 후, public 메서드 만들 수 있음 ㄷ
- static도 되누…
public enum Meal3 { BREAKFAST, LUNCH, DINNER; }
- Enum도 생성자를 만들 수 있다.
- 하지만 private로만 만들 수 있다.
- 필드를 선언해서 SPRING("봄") 와 같이 상수를 선언할 때 생성자를 호출하여 아규먼트를 넘겨줌
package com.itwill.enum01; public enum Season3 { SPRING("봄"), SUMMER("여름"); private String seasonName; private Season3(String season) { this.seasonName = season; } public String getSeasonName() { return this.seasonName; } } class Season4 { private String name; private Season4(String name) { this.name = name; } public String getName() { return name; } public static final Season4 SPRING = new Season3("봄"); public static final Season4 SUMMER = new Season3("여름"); }
- 위 Season3과 Season4는 같은 역할을 한다.
- Enum인 Season3도 필드를 가질 수 있다… 오호라…
'programming > Java [notion 정리본 업로드]' 카테고리의 다른 글
Collection (0) | 2024.04.13 |
---|---|
예외처리 (Exception) (0) | 2024.04.13 |
상속 (0) | 2024.04.13 |
Class (0) | 2024.04.13 |
연산자 (0) | 2024.04.13 |