반응형

Object 클래스는 모든 클래스가 직간접적으로 상속을 하는 클래스이므로 모든 클래스들의 조상이라고 할 수 있습니다.

Object 클래스 내부의 메소드 중에서도 아래의 메소드들을 순서대로 정리해보겠습니다.

1. finalize

2. equals

3. clone


1. finalize 메소드

Object 클래스에는 finalize 메소드가 다음과 같이 정의되어 있습니다.

이 메소드는 아무도 참조하지 않는 인스턴스가 가비지 컬렉션에 의해 소멸되기 전에 자동으로 호출되는 메소드입니다.

    protected void finalize() throws Throwable { }

 

따라서 인스턴스 소멸 시 반드시 실행해야 할 코드가 있다면 이 메소드의 오버라이딩을 고려해볼 수 있습니다.

아래는 간단하게 작성한 예제입니다.

public class Person {
    String name;

    public Person(String name) {
        this.name = name;
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("Instance Destroy : " + name);
    }
}

public class FinalizeMethod {
    public static void main(String[] args) {
        Person person1 = new Person("Park");
        Person person2 = new Person("Han");

        person1 = null;
        person2 = null;

        System.gc();
        System.runFinalization();
        
        System.out.println("Main Method End");
    }
}​

 

위 예제 코드에서는 인스턴스의 소멸을 강제하기 위해 GC를 강제로 진행했지만,

평소 JVM은 매우 합리적인 방법으로 GC를 수행하기 때문에 특별한 상횡이 아니라면 강제하지 않는 것이 좋습니다.

결과 출력값은 다음과 같습니다.

Instance Destroy : Park
Instance Destroy : Han
Main Method End

2. equals 메소드

equals 메소드는 간단하게  두 개의 값을 비교할 때 사용합니다.

따라서 비슷한 역할을 가진 == 연산자와 많은 비교를 하곤 합니다.

 

이 둘은 양 쪽의 값을 비교한 후 그 결과를 boolean type 으로 반환한다는 공통점을 가집니다.

하지만 가장 눈에 띄는 차이점으로는 equals() 는 메소드이고, ==은 연산자라는 점이 있습니다.

우선 자세한 내용은 아래의 코드들을 통해 알아보도록 합시다.

 

Object 클래스에는 equals 메소드가 다음과 같이 정의되어 있습니다.

    public boolean equals(Object obj) {
        return (this == obj);
    }

 

equals 메소드는 양 쪽의 내용을 비교해야하는 상황일 때 오버라이딩을 고려해볼 수 있습니다.

아래는 간단하게 작성한 예제입니다.

public class INum {
    private int num;

    public INum(int num) {
        this.num = num;
    }

    @Override
    public boolean equals(Object obj) {
        return this.num == ((INum) (obj)).num;
    }
}

public class ObjectEquality {
    public static void main(String[] args) {
        INum num1 = new INum(10);
        INum num2 = new INum(20);
        INum num3 = new INum(10);

        if (num1.equals(num2)) {
            System.out.println("num1 = num2");
        } else {
            System.out.println("num1 != num2");
        }

        if (num1.equals(num3)) {
            System.out.println("num1 = num3");
        } else {
            System.out.println("num1 != num3");
        }
    }
}

 

결과 출력값은 다음과 같습니다.

num1 != num2
num1 = num3

 

위 코드의 INum 클래스는 Object 클래스의 equals 메소드를 다음과 같이 클래스 내에 선언된 인스턴스 변수의 내용을 비교하여,

그 결과에 따라 true 또는 false의 boolean 값을 반환하도록 오버라이딩 했습니다.

@Override
    public boolean equals(Object obj) {
        return this.num == ((INum) (obj)).num;
    }

 

이렇듯 두 인스턴스의 비교 조건은 해당 클래스를 정의하는 개발자가 결정해야 합니다.

그리고 그 결정 사항을 equals 메소드를 equals 메소드의 오버라이딩을 통해 반영해야 합니다.

Object 메소드는 참조변수의 참조값을 비교하도록 정의되어 있습니다.

그런데 == 연산을 통해서도 참조값은 비교가 가능합니다.

따라서 equals 메소드의 호출을 통해 참조값을 비교할 필요는 없습니다.

즉 equals 메소드는 내용 비교가 이뤄지도록 오버라이딩 하라고 존재하는 메소드입니다.

자바에서 제공하는 표준 클래스의 경우 equals 메소드가 이미 내용 비교를 하도록 오버라이딩 된 경우가 많습니다.

그 대표적인 예가 String 클래스인데 이와 관련하여 다음 코드를 보겠습니다.

public class StringEquality {
    public static void main(String[] args) {
        String str1 = new String("Park");
        String str2 = new String("Park");

        if (str1 == str2) {
            System.out.println("str1 = str2");
        } else {
            System.out.println("str1 != str2");
        }

        if (str1.equals(str2)) {
            System.out.println("str1 = str2");
        } else {
            System.out.println("str1 != str2");
        }
    }
}

 

결과 출력은 다음과 같습니다.

str1 != str2
str1 = str2

 

분명 일반적인 equals 메소드일 경우는 str1 과 str2는 동일하지 않다고 출력할 것입니다.

하지만 결과는 그렇지 않습니다.

그 이유는 String 내부에 오버라이딩 된 equals 메소드는 내용을 비교하도록 정의되어있기 때문입니다.

 public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

 

결론입니다.

두 인스턴스의 내용 비교를 원한다면 Object 클래스의 equals 메소드를 오버라이딩하여 사용합니다.

자바 개발자들이 원한 설계가 바로 그것입니다.

만약 단순한 참조변수의 참조값을 비교하려면 == 연산을 사용합니다.


3. clone 메소드

Object 클래스에는 인스턴스의 복사를 위한 다음 메소드가 정의되어 있습니다.

protected native Object clone() throws CloneNotSupportedException;

 

이 메소드가 호출되면, 호출된 메소드가 속한 인스턴스의 복사본이 생성되고,

이렇게 만들어진 복사본의 참조값이 반환됩니다.

단, 다음 인터페이스를 구현한 인스턴스를 구현한 인스턴스를 대상으로만 clone 메소드를 호출할 수 있습니다.

 

만약 Cloneable 인터페이스를 구현하지 않은 클래스의 인스턴스를 대상으로 clone 메소드를 호출하면,

CloneNotSupportedException 예외가 발생합니다.

Cloneable 인터페이스의 구현은 "이 클래스의 인스턴스는 복사해도 됩니다" 와 같은 마커 인터페이스 역할을 합니다.

즉, 정의해야 할 메소드가 존재하지 않는, 복사를 해도 된다는 표식의 인터페이스입니다.

 

인스턴스의 복사는 클래스에 따라 허용해서는 안되는 작업이 될 수 있습니다.

따라서 인스턴스 복사의 허용 여부를 클래스를 정의하는 과정에서 고민하고 결정한 후 Cloneable 인터페이스를 구현해서 clone 메소드의 호출이 가능하도록 하면 됩니다.

아래의 코드로 확인해보겠습니다.

public class Point implements Cloneable{
    private int xPos;
    private int yPos;

    public Point(int xPos, int yPos) {
        this.xPos = xPos;
        this.yPos = yPos;
    }

    public void showPosition() {
        System.out.printf("[%d, %d]", xPos, yPos);
        System.out.println();
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class InstanceCloning {
    public static void main(String[] args) throws CloneNotSupportedException {
        Point original = new Point(3, 5);
        Point copy;

        copy = (Point) original.clone();
        original.showPosition();
        copy.showPosition();
    }
}

 

결과 출력은 다음과 같습니다.

[3, 5]
[3, 5]

 

실행 결과는 인스턴스의 복사가 정상적으로 이뤄졌음을 보여줍니다.

이건 관련성이 없는 문제이지만, 위 코드에서는 clone 메소드를 다음과 같이 오버라이딩 했습니다.

잘보면 그저 상위 클래스의 clone 메소드를 호출한 것이 전부이기 때문에, 오버라이딩이 무의미해 보입니다.

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

그러나 중요한 차이가 있습니다.

앞서 Object 에 정의된 clone 메소드는 접근제어지시자가 protected 로 되어있었습니다.

즉 오버라이딩의 목적은 protected -> public 으로 접근 범위를 넓힌 것입니다.

하지만 반대의 경우인 public -> protected 로 접근 범위를 제한하는 형태의 오버라이딩은 불가능합니다.

 

마지막으로 얕은 복사와 깊은 복사라는 개념을 아래의 코드로 확인해보겠습니다.

public class Point implements Cloneable{
    private int xPos;
    private int yPos;

    public Point(int xPos, int yPos) {
        this.xPos = xPos;
        this.yPos = yPos;
    }

    public void showPosition() {
        System.out.printf("[%d, %d]", xPos, yPos);
        System.out.println();
    }

    public void changePos(int x, int y) {
        xPos = x;
        yPos = y;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Rectangle implements Cloneable{
    private Point upperLeft; //좌측 상단 좌표
    private Point lowerRight; //우측 상단 좌표

    public Rectangle(int x1, int y1, int x2, int y2) {
        upperLeft = new Point(x1, y1);
        lowerRight = new Point(x2, y2);
    }

    public void changePos(int x1, int y1, int x2, int y2) {
        upperLeft.changePos(x1, y1);
        lowerRight.changePos(x2, y2);
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public void showPosition() {
        System.out.print("좌측 상단 좌표 : ");
        upperLeft.showPosition();

        System.out.print("우측 하단 좌표 : ");
        lowerRight.showPosition();
        System.out.println();
    }
}

public class ShallowCopy {
    public static void main(String[] args) throws CloneNotSupportedException {
        Rectangle original = new Rectangle(1, 1, 9, 9);
        Rectangle copy;

        copy = (Rectangle) original.clone();
        original.changePos(2, 2, 7, 7);
        original.showPosition();
        copy.showPosition();
        
        System.out.println(original);
        System.out.println(copy);
        System.out.println(original.getUpperLeft());
        System.out.println(original.getLowerRight());
        System.out.println(copy.getUpperLeft());
        System.out.println(copy.getLowerRight());
    }
}

 

결과 기대값은 아래와 같습니다.

좌측 상단 좌표 : [2, 2]
우측 하단 좌표 : [7, 7]

좌측 상단 좌표 : [1, 1]
우측 하단 좌표 : [9, 9]

 

실제 결과 출력값은 아래와 같습니다.

좌측 상단 좌표 : [2, 2]
우측 하단 좌표 : [7, 7]

좌측 상단 좌표 : [2, 2]
우측 하단 좌표 : [7, 7]

 

예상했던 결과와 상이합니다.

인스턴스가 정상적으로 복사된 것은 맞지만,

Rectangle 클래스 내부의 Point 형 참조변수인 upperLeft 와 lowerRight 의 참조값이 그대로 새 인스턴스에 복사가 된 것입니다.

	System.out.println(original);
        System.out.println(copy);
        System.out.println(original.getUpperLeft());
        System.out.println(original.getLowerRight());
        System.out.println(copy.getUpperLeft());
        System.out.println(copy.getLowerRight());

출력 결과값은 다음과 같습니다.

복사본의 내부 참조변수의 참조값이 원본의 참조변수의 참조값과 동일하단 것을 알 수 있습니다.

그리고 이런 현상을 '얕은 복사'라고 합니다.

또한 원래 기대하던 완전한 복사를 '깊은 복사'라고 생각하면 됩니다.

objectClass.Rectangle@60e53b93
objectClass.Rectangle@5e2de80c
objectClass.Point@1d44bcfa
objectClass.Point@266474c2
objectClass.Point@1d44bcfa
objectClass.Point@266474c2

 

깊은 복사를 하기 위해 Rectangle 에 오버라이딩 된 clone 메소드를 아래의 코드와 같이 수정합니다.

    @Override
    public Object clone() throws CloneNotSupportedException {
        Rectangle copy = (Rectangle) super.clone();

        copy.upperLeft = (Point) upperLeft.clone();
        copy.lowerRight = (Point) lowerRight.clone();

        return copy;
    }

 

출력 결과값입니다.

처음 기대한 값 그대로 결과값으로 확인할 수 있습니다.

그렇다면 String 도 참조형인데 방금과 같은 깊은 복사를 적용해야 하나? 라는 의문점이 생길 수도 있습니다.

다행히도 String 인스턴스의 내용을 이루는 문자열은 인스턴스 생성 시 결정이 되고,

변경이 불가능하기 때문에 얕은 복사를 해도 문제 없습니다.

좌측 상단 좌표 : [2, 2]
우측 하단 좌표 : [7, 7]

좌측 상단 좌표 : [1, 1]
우측 하단 좌표 : [9, 9]

objectClass.Rectangle@60e53b93
objectClass.Rectangle@5e2de80c
objectClass.Point@1d44bcfa
objectClass.Point@266474c2
objectClass.Point@6f94fa3e
objectClass.Point@5e481248

 

이상입니다.

오늘도 좋은 하루 보내세요.

감사합니다.

반응형

'JAVA > 기본 개념' 카테고리의 다른 글

#10 열거형(Enum)  (0) 2022.08.31
#5 추상 클래스 (Abstract Class)  (0) 2022.08.24
#4 예외처리(Exception Handling)  (0) 2022.08.22
#3 인터페이스  (0) 2022.08.22
#1 자바 개발 핵심 원칙  (0) 2022.07.11
반응형

우선 결론부터 말씀 드리겠습니다.

@Override 어노테이션은 쓰지 않아도 프로그램 동작에는 문제가 없지만,

안정적인 프로그램을 구현하기 위해서는 작성하는 것이 정석입니다.

 


백문이 불여일견,

한 번 코드로 확인해보겠습니다.

이 코드는 정상적으로 컴파일도 되고 실행도 잘 됩니다.

아래의 코드들의 동작 결과를 예상해보시고, 개발자의 실수를 찾아보세요.

public class ParentAdder {
    public int add(int a, int b) {
        return a + b;
    }
}

public class ChildAdder extends ParentAdder {
    public double add(double a, double b) {
        System.out.println("ChildAdder.add");
        return a + b;
    }
}

public class OverrideMistake {
    public static void main(String[] args) {
        ParentAdder parentAdder = new ChildAdder();
        System.out.println(parentAdder.add(3, 4));
    }
}

이 코드 작성자의 기대값은 이렇습니다.

ChildAdder.add
7

 

하지만 실제 결과값은 다음과 같습니다.

7

 

실수를 찾으셨나요?

개발자의 실수는 아래의 코드입니다.

public double add(double a, double b) {}

 

개발자의 의도는 상위 클래스의 add 메소드를 Override 하는 것이었습니다.

하지만 매개변수형과 반환형이 달랐기 때문에 Override 되지 않았고, 그 결과값이 달라지게 된 것입니다.

이런 유형의 실수는 매우 흔합니다.

그럼에도 불구하고 발견이 쉽지 않기 때문에 치명적인 실수가 될 수 있습니다.

개발자의 실수들은 컴파일 과정에서 확인되는 것이 가장 좋습니다.

그러나 위의 경우에는 정상적으로 컴파일도 되고 실행도 됩니다.

 

이러한 상황을 방지하기 위해서 @Override 어노테이션을 사용하는 것입니다.

쉽게 말해 자바 컴파일러에게 '나는 상위 클래스의 메소드를 오버라이딩(Overriding) 할거야!'

라는 메모를 남기는 것이라고 생각하면 됩니다.

 

 

이제 개발자의 실수가 들어간 코드에 @Override 어노테이션을 적용해보겠습니다.

public class ChildAdder extends ParentAdder {

    @Override
    public double add(double a, double b) {
        System.out.println("ChildAdder.add");
        return a + b;
    }
}

 

결과는 아래와 같습니다.

컴파일 과정에서 오류가 발생하여 프로그램이 실행되지 않고, 컴파일러가 친절하게 오류 원인을 제공합니다.

 

컴파일러 덕분에 실수를 인지할 수 있었고, 코드를 다음과 같이 수정할 수 있었습니다.

public class ChildAdder extends ParentAdder {
    public int add(int a, int b) {
        System.out.println("ChildAdder.add");
        return a + b;
    }
}

 

코드의 결과값은 다음과 같습니다.

드디어 개발자가 원하는 결과를 제공할 수 있게 되었습니다.

ChildAdder.add
7

 

코드를 잘 보시면 @Override 어노테이션이 없다는 것을 알 수 있습니다.

맞습니다. 해당 어노테이션이 존재하지 않아도 정상적으로 Override 를 할 수 있습니다.

하지만, 우리는 조금 더 안정적인 코드 작성 습관을 들여야할 필요가 있습니다.

따라서 다음과 같이 메소드를 수정합니다.

package inheritance;

public class ChildAdder extends ParentAdder {
    @Override
    public int add(int a, int b) {
        System.out.println("ChildAdder.add");
        return a + b;
    }
}

 

 

이상입니다.

오늘도 좋은 하루 보내세요.

감사합니다.

반응형

'JAVA > 호기심천국' 카테고리의 다른 글

#2 추상클래스 vs 인터페이스  (0) 2022.08.25
반응형

자바의 예외처리 관련 내용이 의외로 적지 않습니다.

따라서 예외처리를 하지 않는 상황에서는 불필요한 내용이라 생각하고 넘어가고 싶을 수도 있지만,

자바에서는 예외처리가 차지하는 부분이 크기 때문에 마지막까지 이해하고 넘어가야 합니다.

 

예외 상황을 알리기 위해 정의된 클래스의 종류

package ExceptionHandling;

public class ArrayIndexOutOfBounds {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        for (int i = 0; i < 4; i++) {
            System.out.println(arr[i]);
        }
    }
}

package ExceptionHandling;

class Board {
}

class PBoard extends Board {
}

public class ClassCast {
    public static void main(String[] args) {
        Board pboard1 = new PBoard();
        PBoard pBoard2 = (PBoard) pboard1;

        System.out.println("-----");

        Board board1 = new Board();
        PBoard board2 = (PBoard) board1;
    }
}

package ExceptionHandling;

public class NullPointer {
    public static void main(String[] args) {
        String str = null;
        System.out.println(str);
        int length = str.length();
    }
}


예외 클래스의 구분

예외 클래스의 최상위 클래스는 Throwable 입니다.

그런데 이를 상속하는 예외 클래스는 다음과 같이 세 부류로 나뉩니다.

  1. Error 클래스를 상속하는 예외 클래스
  2. RuntimeException 클래스를 상속하는 예외 클래스 -> RuntimeException 클래스는 Exception 클래스를 상속합니다.
  3. Exception 클래스를 상속하는 예외 클래스

1.  Error 클래스를 상속하는 예외 클래스

예외 클래스의 예와 그 발생 상황을 정리하면 다음과 같습니다.(객체명에 Error가 포함됩니다.)

  • VirtualMachineError -> 가상머신에 심각한 오류 발생
  • IOError -> 입출력 관련해서 코드 수준 복구가 불가능한 오류 발생

VirtualMachineError의 구체적인 예

VirtualMachineError 클래스를 상속하는 예외 클래스로 OutOfMemoryError가 있습니다.

이는 프로그램의 실행에 필요한 메모리 공간이 부족한 상황에서 발생하는 예외입니다.

따라서 이 예외가 발생하면 메모리를 비효율적으로 또는 부적절하게 사용하는 부분의 코드를 수정해야 합니다.

 

IOError의 구체적인 예

자바 프로그램이 임의의 파일에 저장된 데이터를 읽는 중에 갑자기 하드디스크에 물리적 오류가 발생하여 더 이상 파일에 저장된 데이터를 읽을 수 없는 상황이 생길 수 있습니다.

그리고 이런 상항에서 발생하는 것이 IOError Exception 입니다.

즉 Error 클래스를 상속하는 예외는 처리의 대상이 아닙니다.

바꿔 말하면 처리할 수 있는 예외가 아닙니다. 따라서 이런 유형의 예외가 발생하면 그냥 프로그램이 종료되도록 놔두고 이후에 원인을 파악하는 과정이 필요합니다.

 

2. RuntimeException 클래스를 상속하는 예외 클래스

앞서 보였던 예외 클래스가 이에 해당합니다.

  • ArithmeticException
  • ClassCastException
  • IndexOutOfBoundsException
  • NegativeArraySizeException -> 배열 생성시 길이를 음수로 지정하는 예외 발생
  • NullPointerException
  • ArrayStoreException -> 배열에 적절치 않는 인스턴스를 저장하는 예외 발생

3. Exception 클래스를 상속하는 예외 클래스

마지막으로 Exception 클래스를 상속하는 예외 클래스가 있습니다.

RuntimeException 클래스를 직접 혹은 간접적으로 상속하지 않고 Exception 클래스만을 상속하는 예외클래스가 해당됩니다.

그 수로만 따지면 나머지 둘에 비해 가장 많습니다.

그리고 이들은 특정 클래스 또는 메소드에 연관되어 있어서 나열하여 설명하는 것은 의미가 없습니다.

앞으로 공부하면서 이 부류에 속한 예외 클래스를 하나씩 접하게 됩니다.

Exception을 상속하는 예외 클래스 중에서 비교적 빨리 접하게 될 클래스는 다음과 같습니다.

java.io.IOException


프로그래머가 정의하는 예외

지금까지 소개한 예외 클래스는 모두 자바에서 정의한 클래스였습니다.

그러나 프로그래머가 직접 예외 클래스를 정의하고 이를 기반으로 특정 상황에서 예외가 발생하도록 할 수 있습니다.

프로그래머가 정의하는 예외 클래스의 예의 핵심은 Exception 을 상속하는데 있습니다.

Exception 클래스를 아래의 코드로 구현해보겠습니다.

package ExceptionHandling;

public class ReadAgeException extends Exception {
    public ReadAgeException() {
        super("유효하지 않은 나이가 입력되었습니다.");
    }
}

위의 클래스는 Exception 을 상속하는 점을 제외하면 일반 클래스와 차이가 없습니다.

생성자에서는 상위 클래스의 생성자를 호출하면서 예외 상황에 대한 설명을 담고 있는 문자열을 전달하는데,

이 문자열은 앞서 보였던 Throwable 클래스에 정의된 public String getMessage() 메소드 호출 시 반환 됩니다.

 

그럼 우리가 정의한 ReadAgeException 예외 클래스를 대상으로 예외를 발생시키고 이를 처리해보겠습니다.

package ExceptionHandling;

import java.util.Scanner;

public class MyExceptionClass {
    public static void main(String[] args) {
        System.out.println("나이 입력 : ");

        try {
            int age = readAge();
            System.out.printf("입력된 나이 : %d", age);
        } catch (ReadAgeException e) {
            System.out.println(e.getMessage());
        }
    }

    public static int readAge() throws ReadAgeException {
        Scanner scanner = new Scanner(System.in);
        int age = scanner.nextInt();
        if (age < 0) {
            throw (new ReadAgeException());
        }
        return age;
    }
}

정수를 입력받았는데 그 수가 음수인 것은 문법적으로는 오류가 아닙니다.

하지만 프로그램 특성상 사람의 나이는 음수가 될 수 없으므로 오류가 맞습니다.

이러한 상황을 예외로 처리하기 위해서 예외 클래스를 직접 정의하였습니다.

이렇듯 예외 클래스의 인스턴스를 생성하고, 이를 대상으로 throw 선언을 하면 예외가 발생합니다.

물론 이렇게 발생한 예외도 Exception 을 상속하는 예외이므로 try ~ catch 문으로 처리를 하거나 throws 선언을 통해 넘겨야 합니다.


잘못된 catch 구문의 구성

다음과 같이 세 개의 예외 클래스가 정의되었다고 가정합니다.

class FirstException extends Exception {
}

class SecondException extends FirstException {
}

class ThirdException extends SecondException {
}

그리고 위의 세 종류 예외가 모두 발생 가능한 영역에 다음과 같이 try ~ catch 문을 구성하였다고 가정합니다.

 try {
            ...
        } catch (FirstException e) {

        } catch (SecondException e) {

        } catch (ThirdException e) {
            
        }

예외처리의 내용만 놓고 보면 문제가 없어보이지만 컴파일 오류가 발생합니다.

그리고 그 오류의 내용은 다음과 같습니다.

"두 번째, 세 번째 catch 구문은 실행될 일이 절대 없습니다."

그 이유는 예외 클래스 Second, Third 가 직접 혹은 간접적으로 FirstException 을 상속하고 있기 때문입니다.

그래서 catch (FirstException e) {} 구문에서 모든 예외 인스턴스를 처리할 수 있기 때문에 컴파일 오류가 발생합니다.

따라서 위와 같이 catch 문을 구성하고자 한다면 다음과 같이 순서를 변경해야 합니다.

try {
            ...
        } catch (ThirdException e) {

        } catch (SecondException e) {

        } catch (FirstException e) {
            
        }

finally 구문

try ~ catch 문은 하나의 문장이므로 try 구문 홀로 존재할 수 없습니다.

반드시 catch 구문이 하나 이상 등장해야 합니다.

그런데 try 에 이어서 다음과 같이 finally 구문을 둘 수도 있습니다.

반응형

'JAVA > 기본 개념' 카테고리의 다른 글

#5 추상 클래스 (Abstract Class)  (0) 2022.08.24
#8 Object 클래스  (0) 2022.08.23
#3 인터페이스  (0) 2022.08.22
#1 자바 개발 핵심 원칙  (0) 2022.07.11
[Java] 컴파일(Compile)의 이해  (0) 2020.11.04
반응형

인터페이스의 특징

1. class 대신 interface 선언이 붙어있고, 메소드는 구현없이 선언부만 존재합니다.

인터페이스는 다음과 같이 생겼습니다.

기본 골격은 클래스와 동일합니다.

이렇게 메소드의 구현부 없이 선언부만 존재하는 메소드를 가리켜 '추상 메소드(Abstract Methods)' 라 합니다.

interface Printable {
    public abstract void print(String doc); //추상 메소드
}

 

2. 인터페이스를 대상으로는 인스턴스 생성이 불가능합니다.

따라서 아래의 코드처럼 다른 클래스에 의해 다음과 같이 상속이 될 뿐입니다.

이 때 키워드 implements 가 사용됩니다.

package interfaceAndAbstractClass;

interface Printable {
    public abstract void print(String doc); //추상 메소드
}

class Printer implements Printable {

    @Override
    public void print(String doc) {
        System.out.println(doc);
    }
}

public class PrintableInterface {
    public static void main(String[] args) {
        Printable printable = new Printer();
        printable.print("abc");
    }
}

다만 아래의 코드와 같이 익명클래스를 구현할 수 있습니다.

익명 클래스의 형태로 정의하면 아래의 코드처럼 클래스의 정의와 인스턴스의 생성을 하나로 묶을 수 있습니다.

interface Printable {
    public abstract void print(String doc); //추상 메소드
}

public class PrintableInterface {
    public static void main(String[] args) {
        Printable printable = new Printable() {
            @Override
            public void print(String doc) {
                System.out.println(doc);
            }
        };
        printable.print("abc");
    }
}

그리고 이 익명클래스는 람다표현식을 이용해 아래와 같이 간소화 될 수 있습니다.

이 때 람다표현식과 익명 클래스는 분명히 다릅니다. (내부적인 동작 원리가 다릅니다.)

interface Printable {
    public abstract void print(String doc); //추상 메소드
}

public class PrintableInterface {
    public static void main(String[] args) {
        Printable printable = System.out::println;
        printable.print("abc");
    }
}

 

3. 한 클래스는 둘 이상의 인터페이스를 동시에 구현할 수 있습니다.

4. 상속과 구현은 동시에 가능합니다.

따라서 Robot이라는 클래스가 Machine 클래스를 상속하면서

인터페이스 Movable, Runnable 을 구현한다면 다음과 같이 정의할 수 있습니다.

class Robot extends Machine implements Movable, Runnable {}

 

5. 인터페이스의 형을 대상으로 참조변수의 선언이 가능합니다.

6. 인터페이스의 추상 메소드와 이를 구현하는 메소드 사이에 오버라이딩 관계가 성립합니다.

따라서 어노테이션 @Override 선언이 가능합니다.


 

인터페이스의 본질적 의미

인터페이스의 사전적 의미는 '연결점' 또는 '접점' 으로 둘 사이를 연결하는 매개체를 의미합니다.

이와 관련하여 간단한 예를 들면

 


인터페이스 내의 메소드와 변수의 특징

  1. 인터페이스의 모든 메소드는 public이 선언된 것으로 간주합니다.
  2. 인터페이스의 추상 메소드는 abstract 예약어를 생략해도 됩니다. (추상 클래스는 추상 메소드 선언 시 abstract 예약어 생략 불가능)
  3. 인터페이스 내에 선언되는 변수는 선언과 동시에 초기화 해야 합니다.
  4. 인터페이스 내에 선언되는 변수는 모두 public, static, final 이 선언된 것으로 간주합니다.결론적으로 인터페이스 내의 변수는 상수입니다.따라서 상수는 대문자로 이름을 짓는 관례를 따라 인터페이스 내에 변수의 이름은 대문자로 작성합니다.

인터페이스의 문법 구성과 추상 클래스

인터페이스에는 다음 세 가지 종류의 메소드만 존재할 수 있습니다.

1. 추상 메소드

2. 디폴트 메소드

3. static 메소드

 

그리고 인터페이스 간 상속도 가능하며 인터페이스의 형(Type) 이름을 대상으로 instanceof 연산을 할 수도 있습니다.

즉 많은 특성이 클래스와 유사합니다.

 

1. 추상 메소드

2. 디폴트 메소드

3. static 메소드

반응형

'JAVA > 기본 개념' 카테고리의 다른 글

#8 Object 클래스  (0) 2022.08.23
#4 예외처리(Exception Handling)  (0) 2022.08.22
#1 자바 개발 핵심 원칙  (0) 2022.07.11
[Java] 컴파일(Compile)의 이해  (0) 2020.11.04
[JAVA] 구조 개념 및 명명 규칙  (0) 2020.11.03
반응형

이펙티브 자바 3판의 기술 용어는 대부분 JAVA 1.8 의 명세를 따르며, 주요 사항은 다음과 같습니다.

 

자바가 지원하는 자료형(Type)

1. 인터페이스(Interface)

2. 클래스(Class)

3. 배열(Array)

4. 기본 타입(Primitive)

 

애노테이션(Annotation) 은 인터페이스의 일종이며,

열거 타입(Enum) 은 클래스의 일종입니다.

네 가지 타입 중 처음 세 가지는 참조 타입(Reference Type) 이라고 합니다.

즉, 클래스의 인스턴스와 배열은 객체(Object) 인 반면, 기본 타입 값은 그렇지 않습니다.

 

클래스의 멤버로는 필드(Field), 메서드(Method), 멤버 클래스, 멤버 인터페이스가 있습니다.

메서드 시그니처는 메서드 이름과 입력 매개변수(Parameter)의 타입들로 이뤄집니다. (반환값의 타입은 시그니처에 포함되지 않습니다.)

 

반응형
반응형

자바 개발의 핵심적인 기본 원칙은 바로 명료성과 단순성이다.

이 두 가지는 무엇보다 중요하다.

컴포넌트는 정해진 동작이나 예측할 수 있는 동작만 수행해야 한다.

(컴포넌트란 개별 메서드부터 여러 패키지로 이뤄진 복잡한 프레임워크까지 재사용 가능한 모든 소프트웨어 요소를 뜻한다.)

컴포넌트는 가능한 작되, 그렇다고 너무 작아서는 안된다.

코드는 복사되는 게 아니라 재사용되어야 한다.

컴포넌트 사이의 의존성은 최소로 유지해야 한다.

오류는 만들어지자마자 가능한 한 빨리 잡아야 한다. (되도록 컴파일타임에 해결되어야 한다.)

 

이 규칙들이 100% 옳을 수는 없겠지만, 거의 모든 경우에 적용되는 최고의 모범 사례일 것이다.

이 규칙들을 생각 없이 맹종하진 말아야 하나, 어겨야 할 때는 합당한 이유가 있어야 한다.

-이펙티브 자바-

반응형

'JAVA > 기본 개념' 카테고리의 다른 글

#8 Object 클래스  (0) 2022.08.23
#4 예외처리(Exception Handling)  (0) 2022.08.22
#3 인터페이스  (0) 2022.08.22
[Java] 컴파일(Compile)의 이해  (0) 2020.11.04
[JAVA] 구조 개념 및 명명 규칙  (0) 2020.11.03
반응형

Nuxt3 프로젝트 개발 및 운영 모드 포트번호 설정 방법입니다.

 

 

1. 개발 모드

/package.json 파일 설정 (default port 는 3000)

 

{
  "private": true,
  "scripts": {
    "dev": "nuxi dev --port 3001",
    "build": "nuxi build",
    "start": "node .output/server/index.mjs"
  },
  "devDependencies": {
    "nuxt3": "latest"
  }
}

 

2. 운영 모드

/package.json 파일 설정 (default port 는 3000)

 

{
  "private": true,
  "scripts": {
    "dev": "nuxi dev",
    "build": "nuxi build",
    "start": "PORT=3333 node .output/server/index.mjs"
  },
  "devDependencies": {
    "nuxt3": "latest"
  }
}

 

직접 수정 방식 (권장하지 않음)

/.output/server/chunks/nitro/node-server.mjs(361행)

/.output 디렉토리는 Nuxt 애플리케이션을 운영모드로 빌드할 때 생성됩니다.

 

const port = destr(process.env.NITRO_PORT || process.env.PORT) || 3e3;

=>

const port = destr(process.env.NITRO_PORT || process.env.PORT) || 3333;

 

 

 

참고 문서:

https://v3.nuxtjs.org/api/commands/dev/

 

nuxi dev

- Website: https://v3.nuxtjs.org/ - Setup and Contribution Guide: https://v3.nuxtjs.org/community/contribution#documentation-guide

v3.nuxtjs.org

https://v3.nuxtjs.org/guide/deploy/node-server/#configuring-defaults-at-runtime

 

Node.js Server

Discover the Node.js server preset with Nitro to deploy on any Node hosting.

v3.nuxtjs.org

 

 

오늘도 좋은 하루 보내세요.

감사합니다.

반응형

'Nuxt3 > 정리' 카테고리의 다른 글

#1 Head Management  (0) 2022.04.28
반응형

Nuxt3은 기본적으로 charset 및 viewport 등 다양한 메타 태그를 지원하고 있습니다.

필요할 경우 커스터마이징 할 수 있을 뿐만 아니라 여러 가지 방법으로 다른 메타 태그들을 지정할 수 있습니다.

메타 태그들은 다음 세 가지 방식으로 설정할 수 있습니다.

 

 

  1. useHead Composable을 사용하는 방식
  2. Meta 컴포넌트를 사용하는 방식
  3. nuxt.config.ts 설정 방식

 

 

1. useHead Composable을 사용하는 방식

setup 함수 안에 useHead Composable을 사용하여 다양한 메타 태그를 호출할 수 있습니다.

또한 reactive한 메타데이터에 대한 개체를 반환하는 함수를 전달할 수도 있습니다.

 

다음은 동적으로 Meta 데이터를 설정하는 방법입니다.

<script setup> 에서 Route를 이용하여 매개변수를 받고

viewProduct 함수를 호출합니다. (하단에 다른 파일에 모듈화된 viewProduct 함수가 있습니다.)

함수를 통해 받아온 data 를 useHead Composable을 통해 동적으로 적용합니다.

 

<script setup>
import {useHead, useRoute} from "nuxt3/app";
import {viewProduct} from "../../useFetch";

const route = useRoute()

const { data } = await viewProduct(route.params.productId)

useHead({
  meta: [
    { name: '상품이름', content: JSON.stringify(data.value.name) },
    { name: '상품내용', content: JSON.stringify(data.value) },
    { name: '상품등록일', content: JSON.stringify(data.value.registerDate) }
  ]
})

</script>

 

viewProduct 함수

function viewProduct(data) {
    const url = baseURL+'product/'+data
    return useAsyncData('product', () => $fetch(url))
}
 

페이지 호출 시 다음과 같은 메타 데이터를 확인할 수 있습니다.

<head >
  <meta name="상품이름" content="&quot;과자&quot;">
  <meta name="상품내용" content="{&quot;productId&quot;:5,&quot;name&quot;:&quot;과자&quot;,&quot;price&quot;:1000,&quot;content&quot;:&quot;달달한 과자&quot;,&quot;stock&quot;:100,&quot;categoryId&quot;:1000,&quot;registerDate&quot;:&quot;2022-04-21T09:36:50.572317&quot;}">
  <meta name="상품등록일" content="&quot;2022-04-21T09:36:50.572317&quot;">
</head>

 

 

2. Meta 컴포넌트를 사용하는 방식

Nuxt는 메타데이터를 조작할 수 있게 다음 컴포넌트를 제공합니다.

이 컴포넌트 이름은 HTML의 기본적인 element와 일치하므로 템플릿에서 대문자로 시작해서 차이를 주는게 중요합니다.

<Title> <Base> <Script> <Style> <Meta> <Link> <Body> <Html> <Head>

 

다음은 동적으로 Meta 데이터를 설정하는 방법입니다.

<script setup> 에서 Route를 이용하여 매개변수를 받고

viewProduct 함수를 호출합니다. (하단에 다른 파일에 모듈화된 viewProduct 함수가 있습니다.)

 

<script setup>
import {useRoute} from "nuxt3/app";
import {viewProduct} from "../../useFetch";

const route = useRoute()

const { data } = await viewProduct(route.params.productId)

</script>

 

viewProduct 함수

function viewProduct(data) {
    const url = baseURL+'product/'+data
    return useFetch(url);
}

 

받아온 데이터를 Meta 컴포넌트에 동적으로 적용합니다.

    <Head>
      <Title>titleHeadTest</Title>
        <Meta name="productId" :content='JSON.stringify(data.productId)'/>
        <Meta name="productName" :content='data.name'/>
        <Meta name="productPrice" :content='JSON.stringify(data.price)'/>
        <Meta name="productContent" :content='data.content'/>
        <Meta name="productStock" :content='JSON.stringify(data.stock)'/>
        <Meta name="productCategoryId" :content='JSON.stringify(data.categoryId)'/>
        <Meta name="productRegisterDate" :content='data.registerDate'/>
    </Head>

 

페이지 호출 시 다음과 같은 메타 데이터를 확인할 수 있습니다.

<!DOCTYPE html>
<html  data-head-attrs="">

<head >
  <title>titleHeadTest</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="companyName" content="plateer">
  <meta name="teamName" content="EC1">
  <meta name="projectName" content="nuxt3">
  <meta name="productId" content="5">
  <meta name="productName" content="&quot;과자&quot;">
  <meta name="productPrice" content="1000">
  <meta name="productContent" content="&quot;달달한 과자&quot;">
  <meta name="productStock" content="100">
  <meta name="productCategoryId" content="1000">
  <meta name="productRegisterDate" content="&quot;2022-04-21T09:36:50.572317&quot;">​

 

 

3. nuxt.config.ts 설정 방식

/nuxt.config.ts 파일에 아래와 같이 설정할 수 있습니다.

 
import { defineNuxtConfig } from 'nuxt3'

// https://v3.nuxtjs.org/docs/directory-structure/nuxt.config
export default defineNuxtConfig({
    app: {
        head: {
            meta: [
                { name: 'companyName', content: 'ABCD'},
                { name: 'teamName', content: 'KKK'},
                { name: 'projectName', content: 'Nuxt3'}
            ]
        }
    }
})

 

설정한 메타 데이터를 앱 전역적으로 확인할 수 있습니다.

<head >
 <meta name="companyName" content="ABCD">
 <meta name="teamName" content="KKK">
 <meta name="projectName" content="nuxt3">
</head>

 

참고문서:

https://v3.nuxtjs.org/guide/features/head-management

 

 

이상입니다.

오늘도 좋은 하루 보내세요.

반응형

'Nuxt3 > 정리' 카테고리의 다른 글

#2 포트번호 설정  (0) 2022.04.28

+ Recent posts