추상클래스
구체적이지 않은 클래스를 추상클래스(abstract class)
구현부가 없는 메소드를 추상메소드(abstract method)
추상 클래스, 추상 메소드는 abstract예약어를 사용
abstract int add(int x, int y);
public abstract class 클래스명{
public abstract void 메소드명();
}
추상 메소드가 속한 클래스를 추상 클래스로 선언하지 않으면 오류가 발생한다.
추상 클래스 구현
public abstract class Computer {
public abstract void display();
public abstract void typing();
public void turnOn() {
System.out.println("전원 켜기");
}
public void turnOff() {
System.out.println("전원 끄기");
}
}
추상 클래스를 상속받은 클래스는 추상클래스가 가진 메소드를 상속받는다. 따라서 상속받는 클래스는 추상메소드를 포함한다. 그렇기 때문에 추상메소드를 모두 구현하거나 추상클래스로 만들던지 해야한다.
추상 클래스 상속 받기
public class Desktop extends Computer{
@Override
public void display() {
System.out.println("Desktop display()");
}
@Override
public void typing() {
System.out.println("Desktop typing()");
}
}
즉, 추상 클래스를 상속받은 하위클래스는 구현되지 않은 추상메소드를 모두 구현해야 구체적인 클래스가 된다.
하위 클래스 추상 클래스 구현
public abstract class Notebook extends Computer{
@Override
public void display() {
System.out.println("Notebook display()");
}
}
추상 클래스 Notebook의 상속을 받은 MyNotebook
public class MyNotebook extends Notebook{
@Override
public void typing() {
System.out.println("MyNotebook typing()");
}
}
모든 메소드를 구현했어도 abstract 예약어를 사용하면 추상 클래스이다.
public abstract class TV {
public void turnOn() {
System.out.println("전원 켜기");
}
public void turnOff() {
System.out.println("전원 끄기");
}
}
위의 클래스는 모든 추상 메소드를 구현한 클래스이다. 하지만 이것으로 완벽한 TV 기능이 구현된 것이 아니고 TV의 공통 기능만을 구현해 놓은 것이다. 이 클래스는 생성해서 사용할 목적이 아닌 상속만을 위해 만든 추상 클래스이다. 이 경우 new 예약어로 인스턴스를 생성할 수 없다.
추상 클래스를 만드는 이유
public class ComputerTest {
public static void main(String[] args) {
// Computer c1 = new Computer();
Computer c2 = new Desktop();
// Computer c3 = new Notebook();
Computer c4 = new MyNotebook();
}
}
Computer 클래스형으로 인스턴스 4개를 생성했다. Computer와 Notebook에서 오류가 난다.
즉, 추상 클래스는 인스턴스로 생성할 수 없다. 하지만 추상 클래스에서도 형 변환을 사용할 수는 있다.
생성할 수 없는 추상 클래스는 상속을 하기 위해 만든 클래스이다.
추상 클래스에서 구현하는 메소드는 하위 클래스에서도 사용할, 하위 클래스에서도 구현 내용을 공유할 메소드를 구현한다. 실제 하위 클래스에서 내용을 각각 다르게 구현해야 한다면, 구현 내용을 추상 메소드로 남겨 두고 하위 클래스에 구현을 위임하는 것이다.
추상메소드와 구현된 메소드
추상 메소드는 하위 클래스가 어떤 클래스냐에 따라 구현 코드가 달라진다.
구현된 메소드는 하위 클래스에서 공통으로 사용할 구현 코드이며 하위 클래스에서 재정의 할 수 있다.
템플릿 메소드
템플릿(template) 틀이나 견본을 뜻함
템플릿 메소드는 추상클래스를 사용하여 구현할 수 있다.
예시
public abstract class Car {
public abstract void drive();
public abstract void stop();
public void startCar() {
System.out.println("시동을 켠다");
}
public void stopCar() {
System.out.println("시동은 끈다");
}
//템플릿 메소드
final public void run() {
startCar();
drive();
stopCar();
stop();
}
}
public class AutoCar extends Car{
@Override
public void drive() {
System.out.println("자율 주행");
System.out.println("자동차가 알아서 방향 전환을 한다.");
}
@Override
public void stop() {
System.out.println("스스로 멈춘다.");
}
}
public class ManualCar extends Car{
@Override
public void drive() {
System.out.println("사람이 운전한다.");
System.out.println("사람이 핸들을 조작한다.");
}
@Override
public void stop() {
System.out.println("브레이크로 정지한다.");
}
}
public class CarTest {
public static void main(String[] args) {
Car ac = new AutoCar();
ac.run();
System.out.println("=======================");
Car mc = new ManualCar();
mc.run();
}
}
시동을 켠다
자율 주행
자동차가 알아서 방향 전환을 한다.
시동은 끈다
스스로 멈춘다.
=======================
시동을 켠다
사람이 운전한다.
사람이 핸들을 조작한다.
시동은 끈다
브레이크로 정지한다.
Car 클래스를 상속받는 두 클래스는 AutoCar와 ManualCar이다. 이 클래스들은 추상 클래스 Car를 상속받았기 때문에 구현되지 않은 추상 메소드를 구현해야한다.
AutoCar는 자율 주행이 가능하고, 사람이 시동을 켠 후 자동차가 알아서 주행한다.
반면 ManualCar는 사람이 시동을 켜고 핸들을 조작한다.
따라서 자동차 종류에 따라서 구현 내용이 달라지는 부분은 추상 메소드로 만들고, 공통으로 사용하는 메소드는 추상 클래스에 구현하여 상속받아 사용한다.
※추상 메소드 중 하나라도 구현하지 않는다면, 추상 메소드를 포함하고 있기 때문에 추상 클래스가 된다.
템플릿 메소드의 역할은 메소드 실행 순서와 시나리오를 정의하는 것이다.
템플릿 메소드에서 호출하는 메소드가 추상 메소드라면 차종에 따라 구현 내용(AutoCar와 ManualCar 작동 방식의 일부가 다른 것처럼)이 바뀔 수 있다.
하지만 시나리오는 바뀌지 않는다. 템플릿 메소드는 실행 순서, 즉 시나리오를 정의한 메소드이므로 바뀔 수 없다.
상위 클래스를 상속받은 하위 클래스에서 템플릿 메소드를 재정의하면 안된다는 것이다.
그렇기 때문에 템플릿 메소드는 final 예약어를 사용해 선언한다.
메소드 앞에 final을 사용하면 상속받은 하위 클래스가 메소드를 재정의할 수 없다.
추상 클래스는 하위 클래스에서도 사용할 수 있는 코드를 구현한다. 일반 메소드는 하위 클래스에서 재정의할 수 있다.
하지만 템플릿 메소드는 로직 흐름을 정의하는 역할이다. 이 흐름은 하위 클래스가 공통으로 사용하고 코드를 변경하면 안되기 때문에 final로 선언한다.
public abstract class Player {
public abstract void run();
public abstract void jump();
public abstract void turn();
public abstract void lv();
final public void go() {
lv();
run();
jump();
turn();
}
}
public class Beginner extends Player{
@Override
public void run() {
System.out.println("천천히 달릴 수 있다.");
}
@Override
public void jump() {
// TODO Auto-generated method stub
}
@Override
public void turn() {
// TODO Auto-generated method stub
}
@Override
public void lv() {
System.out.println("초보자 레벨");
}
}
public class Advanced extends Player{
@Override
public void run() {
System.out.println("천천히 달릴 수 있다.");
}
@Override
public void jump() {
System.out.println("빠르게 달리고 점프할 수 있다.");
}
@Override
public void turn() {
// TODO Auto-generated method stub
}
@Override
public void lv() {
System.out.println("중급자 레벨");
}
}
public class Super extends Player {
@Override
public void run() {
System.out.println("천천히 달릴 수 있다.");
}
@Override
public void jump() {
System.out.println("빠르게 달리고 점프할 수 있다.");
}
@Override
public void turn() {
System.out.println("엄청 빠르게 달리고 점프하고 턴할 수 있다.");
}
@Override
public void lv() {
System.out.println("고급자 레벨");
}
}
public class PlayerTest {
public static void main(String[] args) {
Player p1 = new Beginner();
p1.go();
System.out.println("============");
Player p2 = new Advanced();
p2.go();
System.out.println("============");
Player p3 = new Super();
p3.go();
}
}
상위 클래스인 추상 클래스는 하위에 구현된 여러 클래스를 하나의 자료형(상위 클래스 자료형)으로 선언하거나 대입할 수 있다. 추상 클래스에 선언된 메소드를 호출하면 가상 메소드에 의해 각 클래스에 구현된 기능이 호출된다.
즉, 하나의 코드가 다양한 자료형을 대상으로 동작하는 다형성을 활용할 수 있는 것이다.
final 예약어
final 예약어는 수정할 수 없다는 뜻이다.
변수, 메소드, 클래스에 사용할 수 있다.
변수 앞에 final을 사용하면 변수는 상수를 의미하게 된다.
final int NUM = 10;
메소드 앞에 final을 사용하면 하위 클래스에서 오버라이딩(재정의)할 수 없다.
클래스 앞에 final을 사용하면 클래스를 상속할 수 없다.
※상수를 선언할 때는 일반 변수와 구별하기 위해 대문자로 쓰는 경우가 많다.
하나의 자바 파일에서만 사용하는 상수 값은 해당 파일 안에서 정의해서 사용할 수 있다.
프로젝트를 하다보면 여러 파일에서 똑같이 공유해야하는 상수 값도 있다.
이런 값을 파일마다 선언한다면 코드가 중복될 뿐만 아니라 값이 변하거나 추가될 때 그 값을 사용하는 파일을 모두 수정해야하는 문제가 생긴다. 따라서 여러 파일에서 공유해야하는 상수 값은 한 파일에 모아 public static final로 선언하여 사용하면 좋다.
public class Define {
public static final int MIN = 1;
public static final int MAX = 99999;
public static final double PI = 3.14;
public static final String HELLO_WORLD = "Hello World";
}
상수 모두 public 예약어로 선언했으므로 외부에서도 사용할 수 있고, static으로 선언했기 때문에 인스턴스를 생성하는 것과 관계없이 클래스 이름으로 참조할 수 있다.
public class DefineTest {
public static void main(String[] args) {
System.out.println(Define.MIN);
System.out.println(Define.MAX);
System.out.println(Define.PI);
System.out.println(Define.Hello_World);
}
}
1
99999
3.14
Hello World
상속을 하면 변수나 메소드를 재정의할 수 있는데, 그렇게 되면 클래스가 가지고 있는 기능에 오류가 생길 수도 있다.
따라서 클래스를 final로 선언하면 상속할 수 없다.
주로 보안과 관련되어 있거나 기반 클래스가 변하면 안되는 경우에 클래스를 final로 선언한다.
'KDT > Java' 카테고리의 다른 글
240108 Java (0) | 2024.01.08 |
---|---|
240104 Java (0) | 2024.01.04 |
231228 Java (0) | 2023.12.28 |
231227 Java (0) | 2023.12.27 |
231221 Java (0) | 2023.12.21 |