본문 바로가기
Language & Framework & GIT/JAVA

[언어공부] 코테를 위한 JAVA 뽀개기 #3

by veganwithbacon 2022. 10. 28.
반응형

3개로 나누기로 했으나, 관련성을 고려해 4분할로 하기로 했다.

 

 

객체지향언어

5️⃣추상클래스, 인터페이스

✔ 추상클래스란?

: 추상메소드를 선언할 수 없는 클래스

  추상클래스는 일반 클래스와 다르게 상속받는 클래스 없이 그 자체로 인스턴스를 생성할 수 없다

 

일단 추상메소드를 먼저 알아보자.

 

✔ 추상메소드란?

: 추상 메소드는 설계만 되어있으며 수행되는 코드에 대해서는 작성이 안된 메소드

 

의문이 든다. 왜 수행되는 코드에는 작성을 안했을까?

미완성으로 남겨두는 이유는 추상 메소드가 포함된 클래스를 상속받는 자식 클래스가 반드시 추상 메소드를 구현하도록 하기 위함이다.

 

예로 모듈같이 중복되는 부분이나 공통되는 부분은 다 만들어진 것을 사용하고, 받아 사용하는 쪽은 자신에게 필요한 부분만을 재정의해서 사용함으로써 생산성이 향상되고 배포가 쉬워지기 때문이다.

 

추상 메소드는 선언부만이 존재하며, 구현부는 작성되어 있지 않다

작성되어 있지 않은 구현부를 자식 클래스에서 오버라이딩하여 사용하는 것이다.

 

추상메소드의 문법은 다음과 같다.

아래와 같이 선언부만 있고 구현부가 없다는 의미로 선언부 끝에 바로 세미콜론(;)을 추가한다

abstract 반환타입 메소드이름();

뒤에서 한 번 더 언급하겠지만, 추상 메소드를 가지고 있으면 반드시 추상 클래스로 선언되어야 한다.

 

>추상클래스 예제 

더보기
abstract class Bird {
    private int x, y, z;

    void fly(int x, int y, int z) {
        printLocation();
        System.out.println("이동합니다.");
        this.x = x;
        this.y = y;
        if (flyable(z)) {
            this.z = z;
        } else {
            System.out.println("그 높이로는 날 수 없습니다");
        }
        printLocation();
    }

    abstract boolean flyable(int z);

    public void printLocation() {
        System.out.println("현재 위치 (" + x + ", " + y + ", " + z + ")");
    }
}

class Pigeon extends Bird {
    @Override
    boolean flyable(int z) {
        return z < 10000;
    }
}

class Peacock extends Bird {
    @Override
    boolean flyable(int z) {
        return false;
    }
}

public class Main {
    public static void main(String[] args) {
        Bird pigeon = new Pigeon();
        Bird peacock = new Peacock();
        System.out.println("-- 비둘기 --");
        pigeon.fly(1, 1, 3);
        System.out.println("-- 공작새 --");
        peacock.fly(1, 1, 3);
        System.out.println("-- 비둘기 --");
        pigeon.fly(3, 3, 30000);
    }
}

 

 

  • fly(x, y, z) 함수는 Bird 를 상속받는 모든 클래스에서 동일한 동작을 합니다. 다만, 그 안에서 호출된 flyable(z) 의 동작만 그것을 구현하는 자식 클래스에서 구현한대로 동작하는 것입니다.
  • 공작새(peacok)는 새이지만 전혀 날 수가 없죠? 그래서 공작새의 flyable() 은 항상 false 를 리턴해서 언제나 x,y 좌표로만 움직입니다. 반면에, 비둘기(pigeon)는 일정 높이까지는 날아갈 수 있기 때문에 그 기준(여기서는 10000)이 되기 전까지는 z좌표로도 움직일 수 있습니다. 이것을 새의 종류마다 중복코드 없이 구현하려면 추상클래스와 추상메소드를 이용해서 이렇게 구현할 수 있습니다. 이렇게 코드를 짜면, 중복코드가 없으면서도 새의 종류마다 주어진 위치까지 날 수 있는지를 판단할 수 있는 유연성을 허용하며 구현할 수 있습니다.

(해당 코드 및 설명은 스파르타코딩클럽을 퍼옴)

 

 인터페이스란?

: 객체의 특정 행동의 특징을 정의하는 간단한 문법

  함수의 특징(method signature)인 접근제어자, 리턴타입, 메소드 이름만을 정의한다. 함수의 내용은 없다

  인터페이스를 구현하는 클래스는 인터페이스에 존재하는 함수의 내용( {} 중괄호 안의 내용)을 반드시 구현해야한다.

 

❕인터페이스 형식

 interface 인터페이스명{
    public abstract void 추상메소드명();
}

-> 인터페이스의 메소드는 추상메소드,static 메소드, default 메소드 모두 허용된다.(JDK 1.8부터)

 

인터페이스 예제 - Main 함수

더보기
interface Bird {
    void fly(int x, int y, int z);
}

class Pigeon implements Bird{
    private int x,y,z;

    @Override
    public void fly(int x, int y, int z) {
        printLocation();
        System.out.println("날아");
        this.x = x;
        this.y = y;
        this.z = z;
        printLocation();
    }
    public void printLocation() {
        System.out.println("현재 위치 (" + x + ", " + y + ", " + z + ")");
    }
}

public class Main {

    public static void main(String[] args) {
        Bird bird = new Pigeon();
        bird.fly(1, 2, 3);
//        bird.printLocation(); // compile error
    }
}

3)인터페이스 vs 추상클래스

- 인터페이스

1. 구현하려는 객체의 동작의 명세

2. 다중 상속 가능

3. implements를 이용한 구현

4. 메소드 시그니처(이름, 파라미터, 리턴 타입)에 대한 선언만 가능

 

- 추상클래스

1. 클래스를 상속받아 이용 및 확장을 위함

2. 다중 상속 불가능, 단일 상속

3. extends를 이용한 구현

4. 추상메소드에 대한 구현 가능

 

 

예외, 에러 처리

 

   예외처리(Exception, Error Handling)

언제나 최악을 대비해야 하듯이, 코드를 완벽하게 짰다해도 프로그램이 성공적으로 돌지는 않는다.

혹시 모를 다양한 상황에 대응하기 위해서 예외 처리 코드가 필요하다.

 

- 예외처리 그래서 왜 쓰는데?

   1. 예외의 발생으로 인한 실행 중인 프로그램의 비정상 종료를 막기 위함

   2. 개발자에게 에러를 알려 코드를 보완할 수 있도록 하기 위함

 

자바에서는 상속을 이용해 모든 예외를 표현한다.

모든 예외 클래스는 Throwable의 자손클래스이다.

 

✔ Throwable에는 크게 두 종류의 자식 클래스가 있다

   - Error는 프로그램이 종료되는 심각한 문제이며, 대부분 컴퓨터나 JVM이 시스템적으로 동작할 수 없는 경우이다.

더보기
JAVA는 JVM 내의 Heap 메모리 공간을 운영체제에서 할당 받아 사용한다.
할당 가능한 최대 메모리 이상을 사용하면 JVM이 다운되고, OutofMemoryError가 나며 프로그램이 종료된다
자바의 대표적인 에러 상황으로 줄여 OOM이라고 한다

  - Exception은 프로그램이 종료되지는 않지만 예외나 문제상황을 표현하기 위해 사용한다

 

✔ 자바에 미리 정의 되어있는 예외 클래스 들이 있다. 기본적으로 있는 것을 사용하되, 기본적인 것들로 표현이 불가하거나 구체적인 목적으로 예외를 정의하고 싶으면, Throwable 또는 하위에 있는 예외 클래스를 상속받아 자신만의 예외 클래스를 정의할 수 있다

자바의 기본 예외 클래스 중 대표적인 것들의 상속 관계도이다.

 

기본적으로 쓰는 대부분의 예외 상황은 Exception이다.

실행 도중 발생하는 예외는 RuntimeException을 상속받아 쓰고

파일을 읽고 쓰거나,원격 저장소의 데이터를 읽고 쓸 때 나는 에러를 표현하려면 IOException을 상속받아 쓰자

 

   try-catch(-finally)형식

try {
    // 예외가 발생할 가능성이 있는 코드를 구현
} catch (FileNotFoundException e) {
    // FileNotFoundException이 발생했을 경우,이를 처리하기 위한 코드를 구현
} catch (IOException e) {
    // FileNotFoundException이 아닌 IOException이 발생했을 경우,이를 처리하기 위한 코드를 구현
} finally {
    // 예외의 발생여부에 관계없이 항상 수행되어야하는 코드를 구현
}

finally 구문은 필수는 아니다

만약, 예외가 발생하지 않는다면 try -> finally 순으로 실행된다

또한, 예외는 중복 catch 블럭을 사용하여 다양한 예외처리를 수행할 수 있다.

중복 catch블럭을 사용할 때는 먼저 선언된 catch블럭부터 확인한다.

앞의 catch블럭에서 잡혔다면, 뒤의 catch블록으로는 전파되지 않는다. 좁은 범위의 예외부터 앞에 선언하는 것이 좋다

좁은 범위란 상속관계에서 자식 클래스에 위치 할수록 좁은 범위이다.

예를 들어 IOException 이 발생할 것 같아 예외처리를 하고, 그 외의 예외도 예외처리를 하고 싶다면 IOException을 catch하는 구문을 먼저, Exception을 catch하는 구문을 그 뒤에 작성한다.

 

 

   try-with-resource 형식

  • try-catch문 이외에 try-with-resource문도 존재한다
  • 입출력과 함께 자주 쓰이는 구문으로 일반적으로 사용된 자원을 끝낸 후에 닫아줘야 하는 것들이 존재하는데 위에 언급된 try-catch-finally구문보다 편리한 것이 try-with-resource이다.
  • try-catch(-finally)문은 자원을 닫을 때 close()를 사용해줘야하나,try-with-resource문은 try문을 벗어나는 순간 자동적으로 close가 호출

어떻게 사용해야할까?

->try()안의 입출력 스트림을 생성하는 로직을 작성할 때 해당 객체가 AutoClosable 인터페이스를 구현한 객체여야한다

import java.io.FileOutputStream;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {

        try (FileOutputStream out = new FileOutputStream("test.txt")) {
            // test.txt file 에 Hello Sparta 를 출력
            out.write("Hello Sparta".getBytes());
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

->형식은 try-catch문과 비슷하지만, try()안에 AutoClosable 인터페이스를 구현한 객체를 선언하면 사용

   간단하나 매우 예외형식

 

 

👌AutoClosable 인터페이스 

그럼 왜 AutoClosable 인터페이스를 사용해야할까?

바로 AutoClosable 인터페이스는 예외가 발생할 경우 close()메소드를 호출하게 정의되어있기 때문이다

 

 

  • 만약에 try-with-resource가 아니라 일반 try-catch문을 사용하면 아래 코드가 된다.코드가 길어질 뿐 아니라 FileOutputStream을 열고 닫을때 생기는 Exception까지 위에서 catch를 하거나 throws로 감싸줘야한다.
import java.io.FileOutputStream;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        FileOutputStream out = new FileOutputStream("test.txt");
        try {
            // test.txt file 에 Hello Sparta 를 출력
            out.write("Hello Sparta".getBytes());
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        out.close();
    }
}

 

 

   메소드에서의 예외 선언

catch문을 이용해 예외처리를 하지 않은 경우, 메소드에 throws로 예외가 발생할 수 있다는 것을 알려줘야한다. throws가 키워드가 있는 함수를 호출한다면, caller쪽에서 catch와 관련된 코드를 작성

void method() throws IndexOutOfBoundsException, IllegalArgumentException {
    //메소드의 내용
}

키워드 throws를 사용해 메소드 내에 발생가능한 예외를 적어주면 된다

 

 

 

반응형

댓글