Back-End/백엔드 관련 정리

[백엔드 과정][자바 기초] - 1. 추상클래스와 인터페이스(1)

얄루몬 2022. 8. 29. 21:41


1. 인터페이스와 추상 클래스란?

인터페이스란?

추상 클래스란?

  •  공통점
    • 추상클래스와 인터페이스는 선언부만 있고 구현 내용이 없는 클래스이다.
    • 자기 자신이 직접 객체를 생성할 수 없으며, 자식 클래스가 추상클래스를 상속(extends)받거나, 인터페이스를 구현(implements)하여 객체를 생성할 수 있다.
    • 선언된 타입과 자식 타입이 같아야 한다.
  • 차이점
    • 인터페이스
      • 상속이 아닌 기능 구현에 사용한다.
      • 다중 상속이 가능하다.
      • 아주 간단한 설계도로 생각하면 편하고 이때 메소드 바디를 작성하면 안 된다.(선언만 가능)
      • implements 구현한다.
    • 추상 클래스
      • 다중 상속이 불가하다.
      • 상속 관계가 필요할 때 사용한다.(그러나 요즘 추세는 인터페이스를 사용하는 것이다. 여러 상속 관계가 얽히면 복잡해지기 때문)
      • 인터페이스보다는 더 구체적인 설계도이다.
      • extends 확장한다.
      • 객체 생성이 불가하다.

2. Number class 구현

package number;

public abstract class MyNumber{

    abstract MyNumber add(MyNumber myNumber);
    abstract MyNumber subtract(MyNumber myNumber);
    abstract MyNumber multiply(MyNumber myNumber);
    abstract MyNumber divide(MyNumber myNumber);

    abstract int toInt();
    abstract double toDouble();

}
  • 추상 클래스와 인터페이스는 아주 간단한 역할만 한다.
  • 이 안에 모든 기능을 넣으려고 할 필요는 없다.

3. Rational class 구현

package number;

import org.jetbrains.annotations.NotNull;

public class MyRationalNumber extends MyNumber{

    private int numerator;
    private int denominator;

    /** 생성자
     * 분자(numerator)
     * -----------------
     * 분모 (denominator)
     * **/
    public MyRationalNumber() {
        this.numerator = 0;
        this.denominator = 1;
    }

    public MyRationalNumber(int num) {
        this.numerator = num;
        this.denominator = 1;
    }

    public MyRationalNumber(int num, int deno){
        if (deno == 0) {
            throw new IllegalArgumentException("분모는 0이 될 수 없습니다.");
        } else if (deno < 0) {
            numerator = -num;
            denominator = -deno;
        }

        //최대공약수를 구해 약분해준다.
        int gcd = getGCD(Math.abs(num), Math.abs(deno));
        this.numerator = num/gcd;
        this.denominator = deno/gcd;
    }

    private int getGCD(int num, int deno) {
        if (deno == 0){
            return num;
        }
        return getGCD(deno, num%deno);
    }


    public int getNumerator() {
        return this.numerator;
    }

    public int getDenominator() {
        return this.denominator;
    }

    @Override
    public String toString() {
        return numerator + "/" + denominator;
    }

    /** 사칙연산
     * 더하기 (유리수끼리 전부 가능)
     * 빼기
     * 곱하기
     * 나누기
     * **/
    @Override
    public MyRationalNumber add(MyNumber myNumber) {
        if (!(myNumber instanceof MyRationalNumber)){
            // 해당 객체가 유리수가 아닌 경우라면 예외를 던져 처리
            throw new IllegalArgumentException("Number is not rational number");
        }
        return new MyRationalNumber(this.numerator *((MyRationalNumber)myNumber).getDenominator()
                    +this.denominator*((MyRationalNumber)myNumber).getNumerator(),
                this.denominator*((MyRationalNumber)myNumber).getDenominator()
        );

    }

    @Override
    public MyRationalNumber subtract(MyNumber myNumber) {
        if (!(myNumber instanceof MyRationalNumber)){
            throw new IllegalArgumentException("Number is not rational number");
        }
        return new MyRationalNumber(this.numerator *((MyRationalNumber)myNumber).getDenominator()
                -this.denominator*((MyRationalNumber)myNumber).getNumerator(),
                this.denominator*((MyRationalNumber)myNumber).getDenominator()
        );
    }

    @Override
    public MyRationalNumber multiply(MyNumber myNumber) {
        if (!(myNumber instanceof MyRationalNumber)){
            throw new IllegalArgumentException("Number is not rational number");
        }
        return new MyRationalNumber(this.numerator *((MyRationalNumber)myNumber).getDenominator()
                , this.denominator * ((MyRationalNumber)myNumber).getDenominator()
        );
    }

    @Override
    public MyRationalNumber divide(MyNumber myNumber) {
        if (!(myNumber instanceof MyRationalNumber)){
            throw new IllegalArgumentException("Number is not rational number");
        }
        if (((MyRationalNumber)myNumber).getDenominator() == 0){
            throw new ArithmeticException("0으로는 나눌 수 없습니다.");
        }

        return new MyRationalNumber(
                this.numerator *((MyRationalNumber)myNumber).getDenominator()
                , this.denominator * ((MyRationalNumber)myNumber).getDenominator()
        );
    }

    @Override
    int toInt() {
        return this.numerator/this.denominator;   }

    @Override
    double toDouble() {
        return this.numerator / (double)this.denominator;
    }

    /**
     *
     * 해당 사칙연산은 자식의 메소드에 리턴타입이 부모 메서드와 다르게 구성되어있다.
     * 이런 경우는 java에서만 허용되는 코드이며,
     * 1. 원시타입이 아닐 경우
     * 2. 부모(상위) 클래스의 메서드의 반환형으로 오버라이드된 메서드의 반환형이 자동 형변환 가능한 경우
     * 위의 두가지에 한해서 상위 클래스와 반환 타입이 다를때도 override가 가능하다.
     * 원칙적으로는(오버라이드의 경우) 리턴타입, 메소드 이름, 매개변수가 모두 같아야 한다.
     * 오버로딩의 경우엔 반환타입은 상관 없고 매개변수만 신경쓰면 된다.
     * **/


}

이 상속 클래스 작성에 주의한(할) 점

  • 불필요한 필드 사용 자제 (일회성이 강한 변수의 경우 필드로 만들어 사용하지 말자)
  • 예외 처리를 신경써서 진행한다.
  • 직접 변수의 값을 변경하기보다는 getter을 사용하자

4. NaturalNumber class & IntegerNumber class 구현

package number;

import org.jetbrains.annotations.NotNull;

public class MyIntegerNumber extends MyRationalNumber{
    public MyIntegerNumber() {
        super();
    }
    public MyIntegerNumber(int num) {
        super(num);
    }

    int getValue(){
        return this.getNumerator();
    }


    @Override
    public MyIntegerNumber add(@NotNull MyNumber myNumber) {
        if (!(myNumber instanceof MyIntegerNumber)){
            throw new IllegalArgumentException("정수가 아닙니다.");
        }

        return new MyIntegerNumber(this.getValue() + ((MyIntegerNumber)myNumber).getValue());
    }

    @Override
    public MyIntegerNumber subtract(@NotNull MyNumber myNumber) {
        if (!(myNumber instanceof MyIntegerNumber)){
            throw new IllegalArgumentException("정수가 아닙니다.");
        }

        return new MyIntegerNumber(this.getValue() - ((MyIntegerNumber)myNumber).getValue());
    }

    @Override
    public MyIntegerNumber multiply(@NotNull MyNumber myNumber) {
        if (!(myNumber instanceof MyIntegerNumber)){
            throw new IllegalArgumentException("정수가 아닙니다.");
        }

        return new MyIntegerNumber(this.getValue() * ((MyIntegerNumber)myNumber).getValue());
    }


    @Override
    public MyIntegerNumber divide(@NotNull MyNumber myNumber) {
        if (!(myNumber instanceof MyIntegerNumber)){
        throw new IllegalArgumentException("정수가 아닙니다.");
        }

        if (((MyIntegerNumber)myNumber).getValue() == 0){
            throw new ArithmeticException("0으로는 나누지 못합니다.");
        }
        return new MyIntegerNumber(this.getValue() / ((MyIntegerNumber)myNumber).getValue());
    }
}
package number;

import org.jetbrains.annotations.NotNull;

public class MyNaturalNumber extends MyIntegerNumber{

    public MyNaturalNumber() {
        super();
    }
    public MyNaturalNumber(int num) {
        super(num);
        if (num <= 0) {
            throw new IllegalArgumentException("자연수가 아닙니다.");
        }
    }

    public MyNaturalNumber add(@NotNull MyNaturalNumber myNumber) {
        if (!(myNumber instanceof MyIntegerNumber)){
            throw new IllegalArgumentException("정수가 아닙니다.");
        }

        return new MyNaturalNumber(this.getValue() + myNumber.getValue());
    }

    public MyNaturalNumber subtract(@NotNull MyNaturalNumber myNumber) {
        return new MyNaturalNumber(this.getValue() - myNumber.getValue());
    }


    public MyNaturalNumber multiply(@NotNull MyNaturalNumber myNumber) {
        return new MyNaturalNumber(this.getValue() * myNumber.getValue());
    }


    public MyNaturalNumber divide(@NotNull MyNaturalNumber myNumber) {
        return new MyNaturalNumber(this.getValue() / myNumber.getValue());
    }
}