안녕하세요, 여행벌입니다.

오늘은 자바 최상위 클래스 Object 클래스의 equals 메소드에 대해서 알아보겠습니다.


equals 메소드

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

public boolean equals(Object obj)

 메소드 이름 그대로 같은지 다른지 판단해서 boolean 값을 return 해주는 메소드로, 오버라이딩을 통해 원하는 내용 비교를 할 수 있도록 최상위 클래스인 Object 클래스에 정의되어 있습니다.

 

 우리가 비교를 생각하면 "==" 연산이 가장 먼저 떠오릅니다. 하지만, "==" 연산은 참조대상이 같은지, 다른지 판단해주는 연산으로 우리가 원하는 내용 비교와는 조금 다릅니다.

 같은 문자열을 담고 있는 2개의 String 인스턴스를 생성해 보도록 하겠습니다.

public class test{
	public static void main(String args[]) {
		String name1 = new String("travelbeeee");
		String name2 = new String("travelbeeee");
		if(name1 == name2) System.out.println("둘이 같습니다");
		else System.out.println("둘이 다릅니다");
	}
}

 name1, name2 인스턴스에는 분명히 같은 문자열이 저장되어있지만, 각각 인스턴스를 생성했기 때문에 서로 다른 인스턴스를 참조하고 있습니다. 따라서, "==" 연산의 결과는 둘이 다르다고 판단합니다.

[Output]
둘이 다릅니다

 그럼 내용 비교를 하고 싶으면 어떻게 해야 될까요? 그럴 때 equals 메소드를 오버라이딩해서 사용하면 됩니다. String 클래스에서는 이미 equals 메소드를 오버라이딩해서 지원해주고 있기 때문에, 다음과 같이 사용하면 됩니다.

public class test{
	public static void main(String args[]) {
		String name1 = new String("travelbeeee");
		String name2 = new String("travelbeeee");
		if(name1.equals(name2)) System.out.println("둘이 내용이 같습니다");
		else System.out.println("둘이 내용이 다릅니다");
	}
}
[Output]
둘이 내용이 같습니다

 x좌표와 y좌표를 담고 있는 클래스를 만든 후, equals 메소드를 좌표가 같은지 판단해주도록 오버라이딩 해보겠습니다.

class Point{
	private int xPos;
	private int yPos;
	public Point(int x, int y) {
		xPos = x;
		yPos = y;
	}
	@Override
	public boolean equals(Object obj) {
		if(((Point)obj).xPos == xPos && ((Point)obj).yPos == yPos) return true;
		return false;
	}
}

public class test{
	public static void main(String args[]) {
		Point p1 = new Point(5, 7);
		Point p2 = new Point(5, 7);
		if(p1 == p2)
			System.out.println("참조대상이 같다");
		else
			System.out.println("참조대상이 다르다");
		
		if(p1.equals(p2))
			System.out.println("내용이 같다");
		else
			System.out.println("내용이 다르다");
	}
}
[Output]
참조대상이 다르다
내용이 같다

 "==" 연산 결과는 서로 다른 인스턴스를 참조하고 있다고 알려주고 있지만, equals 메소드를 둘의 좌표를 비교하도록 오버라이딩 했기 때문에 내용이 같다고 비교해주고 있는 것을 볼 수 있습니다.

 

 이렇듯 두 인스턴스의 내용 비교 결과인 true, false 의 반환 조건은 해당 클래스를 정의하는 프로그래머가 결정해야 합니다. 기본적으로 Object 클래스의 equals 메소드는 "==" 연산자와 마찬가지로 참조변수의 참조 값을 비교하도록 정의되어 있습니다. 하지만, "==" 연산이 참조변수의 참조 값을 비교하는 기능을 지원해주므로 String 클래스 등과 같이 자바에서 제공하는 표준 클래스의 경우 equals 메소드가 내용 비교를 하도록 이미 오버라이딩 되어 있는 경우가 많습니다.


 

안녕하세요, 여행벌입니다.

오늘은 자바 가상 머신의 메모리 모델에 대해서 포스팅 해보겠습니다.


자바 메모리 모델

 자바 가상머신은 운영체제 위에서 실행되는 하나의 프로그램이고, 자바 프로그램은 자바 가상머신 위에서 실행되는 프로그램입니다. 따라서, 자바 가상 머신은 운영체제한테 할당 받은 메모리를 사용해 자바 프로그램이 실행합니다.

 자바 가상 머신은 효율적으로 할당받은 메모리를 사용하기 위해 3개의 영역으로 나눠서 관리합니다.

  메소드 영역, 스택영역, 힙 영역으로 나눠서 데이터를 보관합니다.

1) 메소드영역

 메소드 영역은 메소드의 바이트 코드, static 변수가 저장되는 영역입니다.

 소스파일을 컴파일할 때, 자바 가상머신에 의해 실행이 가능한 코드를 가리켜 '바이트 코드'라고 합니다. 쉽게 말하면 메소드 영역은 특정 클래스의 정보가 담겨있는 영역입니다.

 우리가 인스턴스를 생성하려면 먼저 클래스에 대한 정보가 메모리에 로딩되어있어야 합니다. 이를 클래스의 바이트 코드라고 합니다. 

package Hello;

import java.io.IOException;

class memoryEX{
	static int ex1 = 0;
}

public class test{
	public static void main(String args[]) {
		memoryEX.ex1++;
		new memoryEX();
	}
}

 memoryEX의 static 변수를 참조하고, 인스턴스를 생성하는 코드입니다. 메소드 영역에 memoryEX 클래스에 대한 정보가 로딩되어있기 때문에, 우리가 static 변수를 참조하고, 인스턴스를 생성할 수 있는 것 입니다.

2) 스택 영역

 스택 영역은 지역 변수와 매개 변수가 저장되는 공간입니다. 지역 변수와 매개 변수는 모두 "중괄호로 구분되는 지역 내에서만 유효한 변수들" 입니다. 즉, 중괄호 내에 할당된 이후에 해당 중괄호를 벗어나면 소멸되는 특성의 데이터들이 저장되는 공간입니다.

 또, 스택 영역은 말 그대로 자료구조 스택을 이용해 메모리가 관리됩니다.

package Hello;

public class test{
	public static void main(String args[]) {
		int num1 = 1;
		int num2 = 2;
	}
}

 위의 코드가 컴파일되면 먼저 args 변수가 스택 영역에 쌓이고, num1, num2 변수가 차례대로 스택 영역에 쌓이게 됩니다.

3) 힙 영역

 힙 영역은 인스턴스 정보가 저장되는 영역입니다. 인스턴스는 소멸 시점과 소멸 방법이 지역 변수와 다르기 때문에, 스택 영역과 구분해서 힙 영역에 저장이 됩니다.

 지역 변수와 매개 변수는 중괄호를 벗어나면 변수가 소멸된다고 했습니다. 그럼, 인스턴스는 언제 소멸될까요?? 인스턴스의 소멸시기는 가상머신이 결정합니다. 즉, 가상머신이 "이제 이 인스턴스를 소멸시켜야겠다" 라고 판단하면 인스턴스는 자동으로 소멸이 됩니다. 자바가 다른 프로그래밍 언어에 비해 '메모리 관리에 신경을 덜 써도 된다' 라는 평가를 받는 이유입니다.

 자바에서는 "가비지 컬렉션" 이라는 기능의 인스턴스 소멸 방식을 가지고 있는데, 가비지 컬렉션이 빈번하게 발생하면 시스템에 부담되기 때문에, 자바 가상 머신이 적절한 시기에 가비지 컬렉션을 실행합니다.


 

안녕하세요, 여행벌입니다.

저번 포스팅에 이어서 예외 클래스에 대해서 포스팅해보겠습니다.


예외 클래스 ( Throwable 클래스 )

 저번 포스팅에서 예외가 발생하면 해당 예외 클래스의 인스턴스를 생성한다고 했습니다. 그럼 예외 클래스는 어떤 클래스들이 있고, 어떤 메소드가 있고, 어떤 구조로 이루어져 있는지 알아보겠습니다.

 

 자바의 최상위 클래스인 Object 클래스를 제외하고 예외 클래스의 최상위 클래스는 Throwable 클래스 입니다. Throwable 클래스에는 여러 가지 메소드가 정의되어 있는데, 자주 쓰이는 메소드 2가지만 알아보겠습니다.

// 예외의 원인을 담고 있는 문자열을 반환
public String getMessage()

// 예외가 발생한 위치와 호출된 메소드의 정보를 출력
public void printStackTrace()

 위의 2가지 메소드는 예외 상황을 파악할 때 자주 쓰이는 메소드이므로 알아두시면 좋습니다.

예외 클래스 구분

 예외 클래스의 최상위 클래스는 항상 Throwable 클래스 이지만, 세부적으로 들어가 보면 이를 상속하는 3가지 부류로 나뉩니다.

 예외 클래스의 상속 관계는 다음과 같습니다. 우리는 이를 기반으로 크게 3가지 부류로 예외 클래스를 나눌 수 있습니다.

 

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

2) RuntimeException 클래스를 상속하는 예외 클래스( 당연히 Excpetion 클래스도 상속 )

3) RuntimeException 클래스를 상속하지 않고 Excpetion 클래스만 상속하는 예외 클래스

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

 Error 클래스를 상속하는 예외 클래스는 'VirtualMachineError', 'IOError' 와 같이 우리가 처리할 수 없는 예외 상황을 의미하기 때문에 그냥 프로그램이 종료되도록 놔두고 이후에 원인을 파악해야 합니다.

 'VirtualMachineError' 는 자바 가상머신에 심각한 오류가 발생한 것이고, 'IOError' 는 입출력 관련해서 코드 수준 복구가 불가능한 오류가 발생한 상황입니다. 자바 프로그램이 임의의 파일에 데이터를 저장하는 중에 하드디스크에 물리적 오류가 생긴다는 등 우리가 해결할 수 없는 상황들을 뜻합니다.

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

 RuntimeException 클래스를 상속하는 예외 클래스 같은 경우는 우리가 자주 보게 되는 예외 상황들입니다. 대표적인 클래스는 다음과 같습니다.

 

1. java.lang.ArithmeticException // 0으로 나누기 등의 산술 오류

2. java.lang.InputMismatchException // 잘못된 입력을 한 경우

3. java.lang.ArrayIndexOufOfBoundsException // 배열의 인덱스를 잘못 참조한 경우

4. java.lang.NegativeArraySizeException // 배열 생성 시 길이를 음수로 지정하는 경우

5. java.lang.NullPointerException // null 객체를 잘못 참조한 경우

6. java.lang.ArrayStoreException // 배열에 적절치 않은 인스턴스를 저장하는 경우

 

 이 외에도 다양한 예외 클래스들이 있지만, 대체로 프로그래머가 예외 처리를 해야 하는 상황보다는 코드를 수정해야 하는 상황입니다.

3) Exception 클래스만 상속하는 예외 클래스

 3종류 중 Exception 클래스만 상속하는 예외 클래스가 가장 많습니다. 종류가 너무 많기 때문에 예외 상황을 마주칠 때마다 정리해두는 것을 추천합니다. 다른 예외 클래스와의 차이점은 Exception 클래스만을 상속하는 예외 클래스는 try ~ catch 문으로 처리하거나 다른 영역으로 처리를 넘긴다고 반드시 명시해야 한다는 점입니다. 그렇지 않으면 컴파일을 할 수 없습니다.

 

 정리하면, Error를 상속하거나 RuntimeException을 상속하는 예외의 발생은 코드 작성 과정에서 특별히 무언가를 하지 않아도 되지만, Exception 을 상속하는 예외의 발생에 대해서는 try ~ catch 문을 통해서 예외를 처리하거나 예외의 처리를 다른 영역으로 넘겨야 한다.

throws 키워드

 위에서 계속 예외의 처리를 다른 영역으로 넘긴다고 표현하고 있다. throws 키워드를 이용하면 현재 영역에서 발생한 예외를 다른 영역으로 처리해달라고 넘길 수 있다. 먼저, 간단한 코드를 통해서 예외가 발생할 때 호출 흐름이 어떻게 되는지 알아보자.

package Hello;

public class test{
	public static void method1() {
		method2();
	}
	public static void method2() {
		System.out.println(5 / 0); // 예외 발생
	}
	
	public static void main(String args[]) {
		try {
			method1();
		}
		catch(Throwable e) {
			e.printStackTrace();
		}
		System.out.println("프로그램이 정상적으로 종료");
	}
}

 메인 메소드에서 method1 메소드를 호출하고, method1 메소드에서는 method2 메소드를 호출하고, method2 에서는 0으로 나누는 ArithmeticException 이 발생합니다. printStackTrace 메소드를 이용해서 예외가 발생한 위치와 호출된 메소드의 정보를 출력해보았습니다.

[Output]
java.lang.ArithmeticException: / by zero
	at HelloWorld/Hello.test.method2(test.java:8)
	at HelloWorld/Hello.test.method1(test.java:5)
	at HelloWorld/Hello.test.main(test.java:13)
프로그램이 정상적으로 종료

  method2 에서 에러가 발생했지만, method1 을 호출하고, 다시 main을 호출한 것을 확인할 수 있습니다. 즉, 예외는 처리되지 않으면 그 책임이 넘어가고 그 끝은 main입니다. 위의 코드는 main 함수에서 try ~ catch 구문을 이용해 예외를 처리하였으므로, try~catch 문 뒤에 있는 출력이 정상적으로 돌아간 것을 확인할 수 있습니다.

 throws 키워드를 이용하면 특정 예외 상황에 대해서 처리를 넘길 수 있습니다.

	public static void method2() throws ArithmeticException {
		System.out.println(5 / 0); // 예외 발생
	}

 다음과 같이 throws 키워드를 이용하면 ArithmeticException이 발생했을 때, 예외 처리를 메소드를 호출한 영역으로 넘길 수 있습니다. 또, 다음과 같이 여러 가지 예외 상황은 넘긴다고 명시할 수도 있습니다.

	public static void method2() throws ArithmeticException, IOException{
		System.out.println(5 / 0); // 예외 발생
	}

직접 정의하는 예외 클래스

 프로그래머가 직접 예외 클래스를 정의하고 이를 기반으로 특정 상황에서 예외가 발생하도록 할 수도 있습니다. 핵심은 우리가 직접 정의하는 예외 클래스는 모두 Exception 클래스를 상속해야 합니다.

class MyError extends Exception{
	public MyError() {
		super("내가 정의한 에러발생!");
	}
}

 예외 클래스를 정의하는 방법은 간단합니다. 그럼 어떻게 사용하는지 알아보겠습니다.

package Hello;

import java.io.IOException;

class MyError extends Exception{
	public MyError() {
		super("내가 정의한 에러발생! 양수를 입력해주세요!");
	}
}
public class test{
	public static void method1(int n) throws MyError{
		if(n < 0)
			throw new MyError();
	}
	public static void main(String args[]) {
		try {
			method1(-5);
		}
		catch(Throwable e){
			e.printStackTrace();
		}
	}
}

 method1 에서 입력받은 수가 음수라면 throw new MyError() 를 통해 내가 정의한 예외 상황을 발생시킬 수 있습니다.

[Output]
Hello.MyError: 내가 정의한 에러발생! 양수를 입력해주세요!
	at HelloWorld/Hello.test.method1(test.java:13)
	at HelloWorld/Hello.test.main(test.java:17)

이상으로 예외 처리 및 예외 클래스 포스팅을 마무리해보겠습니다.

예외 처리는 항상 필요하지만, 너무 과한 예외 처리는 프로그램의 성능 저하를 초래하므로

조심하셔야 됩니다!

 

안녕하세요, 여행벌입니다.

오늘은 자바에서 예외란 무엇이고, 어떻게 처리하는지에 대해서 포스팅해보겠습니다.


예외

 자바에서 예외란 "예외적인 상황"을 의미합니다. 단순한 문법 오류 뿐만 아니라, 실행 중간에 발생하는 "정상적이지 않은 상황"을 모두 뜻하는 표현입니다. 자바 가상머신은 예외가 발생하면 그 내용을 간단히 출력하고 프로그램을 종료합니다.

예외 처리(try ~ catch)

 자바는 예외 상황 별로 그 상황을 알리기 위한 클래스를 정의하고 있습니다. 이러한 클래스를 가리켜 "에외 클래스" 라고 합니다. 자바에서는 예외가 발생하면 해당 예외 클래스의 인스턴스를 생성합니다. 이때, 이 인스턴스를 프로그래머가 처리해준다면, 예외는 처리된 것으로 간주하여 프로그램을 종료하지 않습니다.

 

 예외가 발생했을 때, 예외를 처리하고 프로그램을 정상적으로 실행하기 위해서는 try ~ catch 구문을 이용해야 합니다. 기본 구조는 다음과 같습니다.

try{
	// 관찰영역
}
catch(Exception name){
	// 예외 처리 영역
}
// try ~ catch 다음 영역

 예외가 발생할 수 있는 코드는 try 로 관찰 영역 안에 포함시키고, try 영역 안에서 발생하는 예외를 catch 영역에서 처리해주면 예외가 발생했을 때, 프로그램을 종료하지 않아도 됩니다.

 쉽게 정리하면 try 영역에서 발생하는 예외를 catch 영역에서 해결한다!! 라고 생각하시면 될 것 같습니다.

 

 구체적으로 예외가 발생하면 어떤 일들이 일어나는지 알아보겠습니다.

먼저, try 영역에서 에러가 발생합니다. 그러면 에러에 해당하는 인스턴스가 생성됩니다. 이 인스턴스를 catch 구문에 전달하게 되고, catch 구문 안에서 예외를 처리한 후, try ~ catch 다음 영역부터 다시 프로그램을 실행합니다. 이때, catch 구문 안에서 예외를 어떻게 처리하는지는 가상머신이 신경쓰지 않습니다.

 

 예시를 통해서 익혀보겠습니다.

package Hello;

public class test{
	public static void main(String args[]) {
		System.out.println("프로그램시작");
		System.out.println(5 / 0); // 예외 발생
		System.out.println("예외발생");
		System.out.println("예외가 발생하지만 시스템은 정상적으로 실행");
	}
}

우리는 0으로 나누면 에러가 발생하는 것을 알고 있습니다.

따라서, 위에서 얘기한 대로 예외가 발생한 지점에서 ArithmeticException 이라는 예외 내용을 간단히 출력하고 종료되는 것을 확인할 수 있습니다. 

[Output]
프로그램시작
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at HelloWorld/Hello.test.main(test.java:6)

 이번에는 try ~ catch 구문을 이용해 예외가 발생하는 영역을 try 영역 안에 넣어보았습니다.

package Hello;

public class test{
	public static void main(String args[]) {
		System.out.println("프로그램시작");
		try {
			System.out.println(5 / 0); // 예외 발생
		}
		catch(Exception e) {
			System.out.println("예외발생");
		}
		System.out.println("예외가 발생하지만 시스템은 정상적으로 실행");
	}
}
[Output]
프로그램시작
예외발생
예외가 발생하지만 시스템은 정상적으로 실행

 프로그램이 시작하고 예외가 발생했지만, 프로그램이 정상적으로 끝까지 실행되는 것을 확인할 수 있습니다. 또, catch 구문에서 예외를 처리하지않고 무슨 일을 하던 가상머신은 신경쓰지 않고 try ~ catch 다음 구문부터 프로그램을 시작하는 것을 확인할 수 있습니다.

다중 예외 처리

 예외가 발생하는 종류가 여러가지면 어떻게 해야할까요?? 

 정수 2개를 입력받고 나누기를 하는 프로그램을 생각해보겠습니다.

package Hello;

import java.util.Scanner;

public class test{
	public static void main(String args[]) {
		Scanner kb = new Scanner(System.in);
		System.out.println("첫 번째 정수를 입력해주세요");
		int n1 = kb.nextInt();
		System.out.println("두 번째 정수를 입력해주세요");
		int n2 = kb.nextInt();
		System.out.println(n1 / n2);
	}
}

 1. 정수가 아닌 다른 문자가 입력으로 들어온다면??

 2. 나누는 수가 0이 들어온다면??

 다음과 같은 2가지 상황에서 에러가 발생할 수 있습니다. 그러면 에러가 발생하는 상황을 강제로 만들어서 어떤 에러가 발생하는지 보겠습니다.

[Output]
첫 번째 정수를 입력해주세요
a
Exception in thread "main" java.util.InputMismatchException
	at java.base/java.util.Scanner.throwFor(Scanner.java:939)
	at java.base/java.util.Scanner.next(Scanner.java:1594)
	at java.base/java.util.Scanner.nextInt(Scanner.java:2258)
	at java.base/java.util.Scanner.nextInt(Scanner.java:2212)
	at HelloWorld/Hello.test.main(test.java:9)

 첫 번째 정수를 'a' 라는 문자를 입력했더니, InputmismatchException이 발생한 것을 확인 할 수 있습니다.

 두 번째 정수에 0을 입력하면, 0으로 나누는 상황이 되므로 위에서 다뤘던 ArithmeticException이 발생하겠죠?? 그럼 각각 다른 에러 상황에 다른 조치를 취하려면 어떻게 해야될까요?

package Hello;

import java.util.InputMismatchException;
import java.util.Scanner;

public class test{
	public static void main(String args[]) {
		try {
			Scanner kb = new Scanner(System.in);
			System.out.println("첫 번째 정수를 입력해주세요");
			int n1 = kb.nextInt();
			System.out.println("두 번째 정수를 입력해주세요");
			int n2 = kb.nextInt();
			System.out.println(n1 / n2);
		}
		catch(InputMismatchException e) {
			System.out.println("잘못된 입력을 했습니다.");
		}
		catch(ArithmeticException e) {
			System.out.println("잘못된 나누기 연산입니다.");
		}
	}
}

 다음과 같이 catch 구문을 2개 만들고, 각각 발생할 수 있는 에러 인스턴스를 넘겨주면 됩니다.

[Output]
첫 번째 정수를 입력해주세요
a
잘못된 입력을 했습니다.
[Output]
첫 번째 정수를 입력해주세요
5
두 번째 정수를 입력해주세요
0
잘못된 나누기 연산입니다.

 문자열을 입력해도 정상적으로 프로그램이 진행되고, 다음과 같이 0으로 나눠도 프로그램이 잘 진행되는 것을 확인할 수 있습니다.

 

 자바7에서부터는 다음과 같이 한 번에 여러가지 예외를 처리할 수 있도록 묶는 것도 가능합니다.

package Hello;

import java.util.InputMismatchException;
import java.util.Scanner;

public class test{
	public static void main(String args[]) {
		try {
			Scanner kb = new Scanner(System.in);
			System.out.println("첫 번째 정수를 입력해주세요");
			int n1 = kb.nextInt();
			System.out.println("두 번째 정수를 입력해주세요");
			int n2 = kb.nextInt();
			System.out.println(n1 / n2);
		}
		catch(InputMismatchException | ArithmeticException e) {
			System.out.println("잘못된 입력 혹은 잘못된 나누기 연산입니다.");
		}
	}
}

 따라서, 상황 별 예외의 처리 방식이 다르지 않은 경우에는 위와 같이 한 번에 catch 구문 안에서 여러 예외가 처리될 수 있또록 묶는 것도 좋은 방법이 될 수 있습니다.

예외 처리(try ~ catch ~ finally)

 try ~ catch 문을 이용해 예외 처리를 진행하면, 예외가 발생한 시점에 catch 문으로 넘어가고, catch 문이 실행되고 나서 try ~ catch 문 다음부터 다시 시작합니다. 그럼, 다음과 같이 try 문에서 예외가 발생한 시점보다 뒤에 꼭 실행되야하는 코드가 있으면 어떻게 해야될까요?

try{
	// 예외발생
	// 꼭 실행해야되는 코드
}
catch(Exception e){

}

 예외가 발생하면 꼭 실행해야되는 코드가 실행되지 않고 try ~ catch 문이 종료될 것입니다.

 그럼 또 문제가 발생하겠죠?? 이를 막기 위해 finally 가 추가되었습니다.

try{
	// 관찰영역
}
catch(Exception e){
	// 에러처리영역
}
finally{
	// 꼭 실행되야하는 영역
}

 finally 영역은 try 영역이 실행된다면 무조건 실행되는 영역입니다. 따라서, try 문 안에서 예외가 발생해 catch 영역으로 넘어가더라도 마지막에는 finally 영역을 실행하게 됩니다.


 

 

+ Recent posts