본 게시글은 대학 전공수업을 들으며 노션에 정리한 내용을 블로그로 옮긴 것으로, 노션 웹을 통해 최적화된 형태로 읽으시길 권장드립니다.(➡️ 노션 링크)
5강 - 추상 클래스, 인터페이스, 다형성, enum, 익명클래스, 중첩클래스
//추상클래스
abstract class Shape {
//어떤 도형인지 모르므로, 추상메소드로 선언
abstract public double getArea();
}
추상메소드
- 메소드 정의에 abstract 키워드를 사용
- 몸체 구현 없이 형식만 존재
- ⇒ 상속 될 때 반드시 구현이 필요
✅ 완전히 못박은 final과는 상반된 의미 → 함께 사용 x
✅ 추상메소드는 추상클래스 또는 인터페이스와 함께 사용되어야 한다.
추상 클래스
정의
- 클래스 정의에 abstract 키워드를 사용
- 추상 메소드를 포함
- 당연히, 데이터 필드와 일반 메소드 포함 가능.
✅ 객체 생성x : 구체적이지 못한, 불완전한 클래스.
사용
- 의미적으로 유사한 자식클래스를 묶고자 할 때
- 기능적으로 구현하기 어려운 메소드가 있을 때 ⇒ 불완전한 클래스를 만듬
- 자식클래스로 상속될 때 사용됨
- 자식클래스에서 추상 메소드를 구현해야 하고, 구현해 사용한다. ⇒ 구현 후 객체 생성 가능
✅ 자식클래스가 추상 메소드를 구현하지 않으면, 자식클래스도 추상클래스여야 함.
class Circle extends Shape {
private double radius;
@Override // 구현
public double getArea() {
return Math.PI * radius * radius;
}
}
인터페이스
코드
package lecture05; // Comparable 인터페이스 선언: 제네릭 타입 T를 사용하여 다른 객체와 크기를 비교하는 메서드를 정의 interface Comparable<T> { // 다른 객체와 크기를 비교하는 메서드: 구현 클래스에서 정의해야 함 boolean isLargerThan(T o); } // Box 클래스 선언: Comparable 인터페이스를 구현 class Box implements Comparable<Box> { // 인스턴스 변수: 길이, 너비, 높이 private int length, width, height; // 생성자: Box 객체를 초기화 public Box(int length, int width, int height) { this.length = length; this.width = width; this.height = height; } // Comparable 인터페이스의 메서드 구현: Box 객체의 부피를 비교 @Override public boolean isLargerThan(Box otherBox) { // 현재 Box 객체의 부피 계산 int thisVolume = this.length * this.width * this.height; // 비교 대상 Box 객체의 부피 계산 int otherVolume = otherBox.length * otherBox.width * otherBox.height; // 현재 Box 객체의 부피가 더 큰지 여부 반환 return thisVolume > otherVolume; } } public class InterfaceEx { public static void main(String[] args) { Box box1 = new Box(3, 3, 3); Box box2 = new Box(4, 4, 4); // Box 객체의 크기를 비교하고 결과 출력 System.out.println("Box1 is larger than Box2: " + box1.isLargerThan(box2)); System.out.println("Box2 is larger than Box1: " + box2.isLargerThan(box1)); } }
정의
- 100% 추상클래스로, 모든 메소드가 추상메소드.
- 구현되어야 할 메소드의 형식만 정의해둠.
- 단, default 인스턴스 메소드와 static 메소드는 몸체를 구현함.
- 참조형으로, 직접 객체 생성 불가.
- 이름은 보통 형용사 :
Runnable
,Serializable
,Comparable
사용방법
클래스 정의와 유사, class 대신 interface (abstract 생략)
✅ 접근제어자는 생략해도 public abstract.
✅ 몸체는 없음, 반환형, 이름, 매개변수 목록만 표시
✅ 모든 메소드의 기본 접근제어자는 public.
✅ 데이터 필드는 클래스 상수만 가능(public static final)
언제 사용하는가?
- 자식클래스에서 상속되어 사용됨.
- 인터페이스를 상속받아 자식 인터페이스를 정의할 수 있음
interface SubInterface extends SuperInterface {
...
}
위의 경우 SubInterface를 상속받는 클래스는 Sub와 Super 인터페이스 메소드를 모두 구현해야함.
다중 상속이 가능하다.
코드
interface Movable { void add(double dx, double dy); void sub(double dx, double dy); } interface Scalable { void mul(double s); void div(double s); } public class MultipleInherit implements Movable, Scalable { @Override public void add(double dx, double dy) { ... } @Override public void sub(double dx, double dy) { ... } @Override public void mul(double s) { ... } @Override public void div(double s) { ... } }
디폴트 메소드
- 인터페이스에서 선언하는 메소드에, 기본 구현(=몸체)를 넣을 수 있음.
- 자식클래스에서 상속 받을 때 그대로 사용 가능.
- default를 사용해 몸체 구현
왜 사용하는가?
인터페이스에 새 메소드를 추가할 때, 기존 인터페이스를 구현한 클래스들이 오류없이 작동하기 위함.
따라서 그러한 오류해결을 위해 별도로 수정하지 않아도 됨!
아니면 모든 클래스에 추가/수정하던가..예시 코드
package lecture05; // 인터페이스 선언 interface Vehicle { void start(); // 디폴트 메소드: 기본 구현을 제공 default void stop() { System.out.println("Vehicle is stopping"); } } // 클래스가 인터페이스를 구현 class Car implements Vehicle { @Override public void start() { System.out.println("Car is starting"); } // stop() 메소드를 재정의하지 않으면 디폴트 메소드가 사용됨 } // 또 다른 클래스가 인터페이스를 구현 class Bike implements Vehicle { @Override public void start() { System.out.println("Bike is starting"); } // 디폴트 메소드를 재정의하여 다른 동작을 구현 @Override public void stop() { System.out.println("Bike is stopping in its own way"); } } public class DefaultMethod { public static void main(String[] args) { Vehicle car = new Car(); car.start(); car.stop(); // Car 클래스는 stop() 메소드를 재정의하지 않았으므로 인터페이스의 디폴트 메소드가 호출됨 Vehicle bike = new Bike(); bike.start(); bike.stop(); // Bike 클래스는 stop() 메소드를 재정의했으므로 재정의된 메소드가 호출됨 } }
형 변환
인터페이스와 클래스는 모두 사용자 정의형.
extends와 implements에 따라 상위/하위 자료형 관계가 설정
상위 유형의 변수는 하위 객체의 참조값을 가질 수 있음.
동적 바인딩
: 상위 유형의 변수가 가리키는 객체의 실제 유형에 따라 메소드가 결정됨코드
// 인터페이스 선언 interface Animal { void sound(); } // 클래스 정의 class Dog implements Animal { @Override public void sound() { System.out.println("멍멍!"); } public void wagTail() { System.out.println("꼬리 흔들!"); } } public class Main { public static void main(String[] args) { // Dog 객체 생성 Dog dog = new Dog(); // 상위 유형의 변수에 하위 객체의 참조값 할당 Animal animal = dog; // 인터페이스 메소드 호출 animal.sound(); // Dog의 sound() 메소드가 호출됨(동적 바인딩) // 하위 유형의 메소드는 호출 불가능 (Animal 인터페이스에는 없음) // animal.wagTail(); // 컴파일 에러 발생 } }
다형성(polymorphism)
- 유사하지만 다양한 형상 / 기능을 가진다는 뜻.
- 한 부모에서 나온 두 자식의 객체는 비슷하지만 다르다.
종류
- 메소드 오버로딩 - 유사하지만 다르다
- 추상메소드 - 자식 클래스에서 정의된 메소드는 부모의 것과 유사하지만 다르다.
- 클래스의 다형성(오버라이딩)
- 부모로부터 상속받은 메소드를 자식에서 오버라이딩 가능 : 같은 이름의 메소드가 다른 기능 수행
- 인터페이스의 다형성
- 여러 자식클래스들에서 상위 인터페이스의 메소드를 다르게 구현
형변환
- 전혀 다른 클래스간에는 형 변환이 되지 않는다. but! 상속관계의 클래스끼리는 가능하다.
- 하위클래스(dog) → 상위(Animal)클래스로 변환은 문제 없다. (UpCasting : 자동 형변환 가능)
- 코드의 Animal animal = (Animal) new Dog; 참고 !
뭐가 좋은가?
코드의 유연성과 재사용성
동적 바인딩
- 각 객체의 실제 타입에 따라 동적으로 결정
- 따라서 실제 유형을 명시적으로 다룰 필요가 없음.
좋은 이유 예시
public static void main(String[] args) { // 동물 배열 생성 Animal[] animals = new Animal[2]; animals[0] = new Dog(); animals[1] = new Cat(); // 동물들이 소리를 내도록 호출 for (Animal animal : animals) { animal.sound(); // 각 동물의 실제 타입에 따라 makeSound() 메소드가 호출됨 } }
열거형(enum)
- 미리 정의된 상수값의 집합을 만들기 위한 자료형
- 미리 지정된 값만 대입 가능
enum Day {
SUNDAY , MONDAY , TUESDAY , WEDNESDAY , THURSDAY , FRIDAY , SATURDAY
}
- 열거형 변수에는 미리 지정된 값만 대입 가능.
- 상수값을 배열로 리턴하는 values() 제공
public class EnumEx {
public static void main(String args[]) {
Day day = Day.MONDAY;
for (Day d : Day.values()) {
System.out.println(d);
}
}
}
- 필드와 메소드 포함 가능하나 상수가 먼저 정의되어야 함. (’;’로 끝나야 함)
- 상수값은 하나의 객체와 같다. ex) day.Monday
- 생성자는 열거형과 같은 이름을 가지며, 접근제어자는 생략 or private이여야 함.
- 열거형 생성자는 모든 객체에 대한 초기화 수행. (상수가 사용될 때 한꺼번에 자동 호출)
코드
// 열거형 Day 정의 enum Day { // 상수 정의 (하나의 객체와 같음) Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday; // 필드와 메소드 정의 private String description; // 열거형 생성자 (생성자 이름은 열거형과 같아야 함) Day() { this.description = "A day of the week"; } // 열거형 생성자를 통한 초기화 Day(String description) { this.description = description; } // Getter public String getDescription() { return description; } } public class Main { public static void main(String[] args) { // 열거형 상수 사용 Day today = Day.Monday; System.out.println("Today is " + today); // 열거형 상수의 초기화 : 모든 객체에 대한 초기화 System.out.println("Description of Monday: " + Day.MONDAY.getDescription()); } }
익명 클래스
일회성으로 1개의 객체를 생성하기 위한 클래스
- 따라서, 정의와 동시에 객체를 생성함.
부모클래스를 상속받거나 / 인터페이스 또는 추상클래스 메소드를 구현하도록 익명클래스를 정의함.
- new 슈퍼클래스() {…}
- new 인터페이스() {…}
- 이 때, {…}가 익명클래스의 몸체임. new Greeting() {…}
코드
interface Greeting { void greet(); } public class AnonymousClass { public static void main(String[] args) { // 익명 클래스를 이용한 Greeting 인터페이스의 구현 Greeting greeting = new Greeting() { @Override public void greet() { System.out.println("Hello!"); } }; // 익명 클래스의 메소드 호출 greeting.greet(); } }
중첩 클래스(내부클래스, inner class) - 어렵다.
- 외부 클래스 정의 내부에 정의된 또 다른 클래스
- 외부 클래스의 멤버가 되며, 외부 클래스의 모든 멤버에 접근 가능.
- 내부 클래스는 보통의 클래스와 다르게 private, protected클래스가 될 수 있음.
- 대개 내부클래스는 외부클래스의 필드와 관련된 작업을 처리
non-static 중첩 클래스
- 외부 클래스 객체가 생성된 후 사용 가능
- 외부 클래스의 모든 멤버에 접근할 수 있음.
- 객체 생성 방법 🔽
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
//외부클래스객체.new 내부클래스();를 통해 객체 생성
코드
class Outer { class Inner { void greet() { System.out.println("Hello from inner class!"); } } { public class Main { psvm { Outer outer = new Outer(); Outer.Inner inner = outer.new Inner(); inner.greet(); } }
static 중첩 클래스
외부클래스 객체 생성과 무관하게 사용 가능
외부클래스 정적 멤버에 접근 가능.
코드
class Outer { static class StaticNested { void greet() { System.out.println("Hello from static nested class!"); } } } public class Main { public static void main(String[] args) { // 외부 클래스의 객체 생성과 무관하게 정적 중첩 클래스의 객체 생성 가능 //StaticNested 클래스는 Outer의 정적멤버 => Outer 클래스 생성 없이 직접 접근 Outer.StaticNested staticNested = new Outer.StaticNested(); staticNested.greet(); } }
왜 사용?
논리적 그룹화 + 캡슐화
중요하진 않지만 어려운 부분임 → 필요할 때 한번 더 보도록
'Back-End > Java' 카테고리의 다른 글
(전공 정리) 8강 - java.lang 패키지: Object, String, StringBuffer (0) | 2024.06.22 |
---|---|
(전공 정리) 7강 - 패키지와 예외처리 (0) | 2024.06.22 |
(전공 정리) 6강 - 제네릭, 람다식 (0) | 2024.06.22 |
(전공 정리)4강 - static, final, 오버로딩/오버라이딩, 상속, this, super, 메소드체이닝 (0) | 2024.06.22 |
(전공 정리) 3강 - 배열, String, Scanner, 클래스, 접근제어자, 생성자 (0) | 2024.06.22 |
(전공 정리) 2강 - 변수, 자료형, 연산자, 반복문, 제어문 (0) | 2024.06.22 |