예외 1 - 문법
오리엔테이션
예외 처리란 간단하게 말하자면 프로그램 개발 과정에서 발생하는 오류를 제어, 처리하는 것이다.
지금까지 배운 것이 성공하기 위한 지식이였다면
예외 처리는 실패하지 않기 위한 지식이라고 볼 수 있다.
예외란 무엇인가
오류라는 것은 예외의 일종이다.
예외란 개발자가 계획한 것 외에 일반적이지 않은 상황이 발생했을 경우를 뜻한다.
예외 상황을 처리, 관리하는 것 또한 예외 혹은 예외 처리라고 부른다.
ex) 계산기
class Calculator{
int left, right;
public void setOprands(int left, int right){
this.left = left;
this.right = right;
}
public void divide(){
System.out.print("계산결과는 ");
System.out.print(this.left/this.right);
System.out.print(" 입니다.");
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 0);
c1.divide();
}
}
자바는 0으로 숫자를 나눌 경우 에러를 발생시키기 때문에 다음과 같은 에러메시지가 나온다.
계산결과는 Exception in thread "main" java.lang.ArithmeticException: / by zero
at org.opentutorials.javatutorials.exception.Calculator.divide(CalculatorDemo.java:10)
at org.opentutorials.javatutorials.exception.CalculatorDemo.main(CalculatorDemo.java:18)
- main에서 java.lang.ArithmeticException: / by zero 문제가 발생했다.
- 이 문제가 발생한 이유는 10번 라인에 divide 메소드 때문이다.
- 이 메소드는 18번 라인인 main에서 사용하고 있다.
예외 처리를 적용시키면 다음과 같이 변경할 수 있다.
class Calculator{
int left, right;
public void setOprands(int left, int right){
this.left = left;
this.right = right;
}
public void divide(){
try {
System.out.print("계산결과는 ");
System.out.print(this.left/this.right);
System.out.print(" 입니다.");
} catch(Exception e){
System.out.println("오류가 발생했습니다 : "+e.getMessage());
}
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 0);
c1.divide();
Calculator c2 = new Calculator();
c2.setOprands(10, 5);
c2.divide();
}
}
계산결과는 오류가 발생했습니다 : / by zero
계산결과는 2 입니다.
나누기를 할 경우 분모에 0이 올 경우 에러가 발생할 것을 예측할 수 있기 때문에
해당 부분을 try로 묶어준다.
try에 해당하는 코드를 실행시켜주다가 에러가 발생할 경우 catch 안에 코드를 실행시킨다.
뒷수습
예외가 발생했을 때 뒷수습 역할을 하는 것은 catch이다.
계산기 코드를 조금 수정하여 catch로 뒷수습하는 방법에 대해 알아보자.
class Calculator{
int left, right;
public void setOprands(int left, int right){
this.left = left;
this.right = right;
}
public void divide(){
try {
System.out.print("계산결과는 ");
System.out.print(this.left/this.right);
System.out.print(" 입니다.");
} catch(Exception e){
System.out.println("\n\ne.getMessage()\n"+e.getMessage());
System.out.println("\n\ne.toString()\n"+e.toString());
System.out.println("\n\ne.printStackTrace()");
e.printStackTrace();
}
System.out.println("Divide End");
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 0);
c1.divide();
}
}
실행 결과
계산결과는
e.getMessage()
/ by zero
e.toString()
java.lang.ArithmeticException: / by zero
e.printStackTrace()
java.lang.ArithmeticException: / by zero
at org.opentutorials.javatutorials.exception.Calculator.divide(CalculatorDemo.java:11)
at org.opentutorials.javatutorials.exception.CalculatorDemo.main(CalculatorDemo.java:25)
Divide End
앞서 계산기 예제와 마찬가지로 0으로 나누었을 때 예외가 발생하여 catch 안으로 들어가게 된다.
사용된 catch 관련 메소드
e.getMessage() | 오류에 대한 기본적인 내용을 출력한다. |
e.toString() | e.getMessage() 보다 상세히 예외 정보를 제공한다. |
e.printStackTrace() | 리턴값이 없다는 것이 특징이다. 내부적으로 예외 결과를 화면에 출력한다. 가장 자세한 예외 정보를 제공한다. |
catch문 안에 코드를 실행 완료한 후 프로그램이 종료되는 것이 아니라
그 다음에 있는 코드를 이어서 실행하게 된다.
다양한 예외들과 다중캐치
class A{
private int[] arr = new int[3];
A(){
arr[0]=0;
arr[1]=10;
arr[2]=20;
}
public void z(int first, int second){
System.out.println(arr[first] / arr[second]);
}
}
public class ExceptionDemo1 {
public static void main(String[] args) {
A a = new A();
a.z(10, 1);
}
}
위 예제는 2가지 예외 상황을 예상할 수 있다.
- 배열의 크기를 벗어나는 곳에 접근하려 할 경우
- 0으로 숫자를 나누려고 할 경우
예외 상황이 여러 개 발생할 경우 예외마다 다르게 대응하고 싶을 때 다중캐치를 사용한다.
다중 캐치 코드
class A{
private int[] arr = new int[3];
A(){
arr[0]=0;
arr[1]=10;
arr[2]=20;
}
public void z(int first, int second){
try {
System.out.println(arr[first] / arr[second]);
} catch(ArrayIndexOutOfBoundsException e){ // 배열 크기 이상에 접근
System.out.println("ArrayIndexOutOfBoundsException");
} catch(ArithmeticException e){ // 0으로 나눌 때
System.out.println("ArithmeticException");
} catch(Exception e){ // 그 외 예외
System.out.println("Exception");
}
}
}
public class ExceptionDemo1 {
public static void main(String[] args) {
A a = new A();
a.z(10, 0);
a.z(1, 0);
a.z(2, 1);
}
}
- Exception은 모든 예외를 다 포함하는 것이기 때문에 다중 캐치로 작성할 경우 제일 마지막 catch문에 작성해야 한다.
finally
finally는 예외 발생 여부와 상관없이 언제나 실행되는 로직이다.
예외 여부와 상관없이 반드시 끝내줘야 하는 작업이 있을 때 사용한다.
ex) 데이터베이스 서버 종료
예제 코드
class A{
private int[] arr = new int[3];
A(){
arr[0]=0;
arr[1]=10;
arr[2]=20;
}
public void z(int first, int second){
try {
System.out.println(arr[first] / arr[second]);
} catch(ArrayIndexOutOfBoundsException e){
System.out.println("ArrayIndexOutOfBoundsException");
} catch(ArithmeticException e){
System.out.println("ArithmeticException");
} catch(Exception e){
System.out.println("Exception");
} finally {
System.out.println("finally");
}
}
}
public class ExceptionDemo1 {
public static void main(String[] args) {
A a = new A();
a.z(10, 0); // ArrayIndexOutOfBoundsException 발생
a.z(1, 0); // ArithmeticException 발생
a.z(2, 1); // 정상 실행
}
}
실행 결과
ArrayIndexOutOfBoundsException
finally
ArithmeticException
finally
2
finally
예외 2 - 예외 던지기
예외의 강제
API를 사용할 때 반드시 예외 처리를 해야 하는 경우도 있다.
파일을 읽어오는 코드
import java.io.*;
public class CheckedExceptionDemo {
public static void main(String[] args) {
BufferedReader bReader = new BufferedReader(new FileReader("out.txt"));
String input = bReader.readLine();
System.out.println(input);
}
}
예외에 대한 내용이기에 코드 설명은 생략한다.
실행 결과
Exception in thread "main" java.lang.Error: Unresolved compilation problems:
Unhandled exception type FileNotFoundException
Unhandled exception type IOException
at org.opentutorials.javatutorials.exception.CheckedExceptionDemo.main(CheckedExceptionDemo.java:5)
- Unhandled exception type FileNotFoundException는 예외처리가 필요하다는 것을 뜻한다.
FileReader를 공식 문서에서 찾아보면 FileReader 생성자에 Throws를 확인할 수 있다.filename에 해당하는 파일이 없거나 파일이 아니거나 하는 이유로 파일을 사용할 수 없다면 FileNotFoundException을 발생하기 때문에 이에 대한 예외 처리를 사용자에게 throws(던진다)고 해석할 수 있다.그렇기에 FileReader를 사용할 경우 반드시 예외 처리를 해주어야 한다.동일한 이유로 readLine 또한 예외처리를 해주면 다음과 같이 코드를 수정할 수 있다.
수정 코드
import java.io.*;
public class CheckedExceptionDemo {
public static void main(String[] args) {
BufferedReader bReader = null;
String input = null;
try {
bReader = new BufferedReader(new FileReader("out.txt"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try{
input = bReader.readLine();
} catch (IOException e){
e.printStackTrace();
}
System.out.println(input);
}
}
주의할 점은 유효 범위이다.
첫 번째 try 안에 BufferedReader 생성자를 넣을 경우 두 번째 trye 안에서는 해당 생성자를 사용할 수 없기에
전역으로 생성해주어야 한다.
throw와 throws
API 뿐만 아니라 사용자도 예외 처리에 대한 책임을 던질 수 있다.
class B{
void run(){
}
}
class C{
void run(){
B b = new B();
b.run();
}
}
public class ThrowExceptionDemo {
public static void main(String[] args) {
C c = new C();
c.run();
}
}
만약 B 클래스 내에서 예외가 발생한다면 B가 예외 처리를 해도 되지만
실질적으로 B를 사용하는 C 클래스에 책임을 전가할 수 있으며,
C 클래스가 예외 처리를 해도 되지만
실질적으로 C 클래스를 사용하는 사용자(메인)에게 책임을 전가할 수 있다.
책임의 전가 throws
B 클래스에서 예외 처리하는 코드
import java.io.*;
class B{
void run(){
BufferedReader bReader = null;
String input = null;
try {
bReader = new BufferedReader(new FileReader("out.txt"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try{
input = bReader.readLine();
} catch (IOException e){
e.printStackTrace();
}
System.out.println(input);
}
}
class C{
void run(){
B b = new B();
b.run();
}
}
public class ThrowExceptionDemo {
public static void main(String[] args) {
C c = new C();
c.run();
}
}
C클래스에서 예외 처리 하는 코드
B 클래스 메소드에 throws Exception을 작성해주면 된다.
- IOException 안에 FileNotFoundException이 있기 때문에 IOException만 예외 처리해주어도 된다.
import java.io.*;
class B{
void run() throws IOException, FileNotFoundException{
BufferedReader bReader = null;
String input = null;
bReader = new BufferedReader(new FileReader("out.txt"));
input = bReader.readLine();
System.out.println(input);
}
}
class C{
void run(){
B b = new B();
try {
b.run();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class ThrowExceptionDemo {
public static void main(String[] args) {
C c = new C();
c.run();
}
}
main에서 예외 처리 하는 코드
import java.io.*;
class B{
void run() throws IOException, FileNotFoundException{
BufferedReader bReader = null;
String input = null;
bReader = new BufferedReader(new FileReader("out.txt"));
input = bReader.readLine();
System.out.println(input);
}
}
class C{
void run() throws IOException, FileNotFoundException{
B b = new B();
b.run();
}
}
public class ThrowExceptionDemo {
public static void main(String[] args) {
C c = new C();
try {
c.run();
} catch (FileNotFoundException e) {
System.out.println("out.txt 파일은 설정 파일 입니다. 이 파일이 프로잭트 루트 디렉토리에 존재해야 합니다.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
예외 3 - 만들기
예외 만들기
class Calculator{
int left, right;
public void setOprands(int left, int right){
//if(right == 0){
// throw new IllegalArgumentException("두번째 인자의 값은 0이 될 수 없습니다.");
//}
this.left = left;
this.right = right;
}
public void divide(){
if(this.right == 0){
throw new ArithmeticException("0으로 나누는 것은 허용되지 않습니다.");
}
try {
System.out.print("계산결과는 ");
System.out.print(this.left/this.right);
System.out.print(" 입니다.");
} catch(Exception e){
System.out.println("\n\ne.getMessage()\n"+e.getMessage());
System.out.println("\n\ne.toString()\n"+e.toString());
System.out.println("\n\ne.printStackTrace()");
e.printStackTrace();
}
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 0);
try{
c1.divide();
}catch(ArithmeticException e){
System.out.println(e.getMessage());
}
}
}
위 코드는 이전에 보았던 코드로 0으로 나눌 경우에 대해 예외 처리가 되어있는 코드이다.
처음 보이는 주석문처럼 예외를 발생시킬 경우 right에 0이 아예 들어가지 못하게 되며
divide 안 if문처럼 예외를 발생시킬 경우 divide 메소드를 사용할 경우에만 right에 0이 들어가지 못한다.
위 코드를 실행하면
main 안 try 구문에서 divide 메소드를 실행시키는데 right가 0이기 때문에 throw를 ArithmeticException 객체와 함께 main에 던지고 main에 catch 구문이 실행되게 된다. catch 인자에는 throw 때 받은 객체가 들어가게 되어 아래와 같은 실행 결과가 나온다.
실행 결과
0으로 나누는 것은 허용되지 않습니다.
기억할만한 Exception들
IllegalArgumentException | 매개변수가 의도하지 않은 상황을 유발시킬 때 |
IllegalStateException | 메소드를 호출하기 위한 상태가 아닐 때 |
NullPointerException | 매개 변수 값이 null 일 때 |
IndexOutOfBoundsException | 인덱스 매개 변수 값이 범위를 벗어날 때 |
ArithmeticException | 산술적인 연산에 오류가 있을 때 |
예외의 여러 상황들
import java.io.IOException;
class E{
void throwArithmeticException(){
throw new ArithmeticException();
}
// 에러 발생
//void throwIOException(){
// throw new IOException();
//}
void throwIOException1(){
try {
throw new IOException();
} catch (IOException e) {
e.printStackTrace();
}
}
void throwIOException2() throws IOException{
throw new IOException();
}
}
throwArithmeticException과는 다르게 throwIOException은 예외 처리를 해주어야만 한다.
예외 처리를 try~catch 구문을 사용하거나 throw 해주면 된다.
예외의 선조 - Throwable
다음은 ArithmeticException의 API 공식문서를 통한 예외들의 가계도이다.
이것을 통해 ArithmeticException의 부모 클래스 중 java.lang.Exception이 있다는 것을 알 수 있다.
Throwable 클래스 안에는 getMessage(), printStackTrace(), toString() 등이 있다.
위와 같은 예외 클래스들의 상속 관계를 대략적으로 정리하면 아래와 같다.
- Error는 하드웨어적인 문제가 생겼을 때 발생하는 것으로 개발자가 해결할 수 있는 것은 없다. 메모리 사용을 최대한 줄이거나 메모리를 변경하는 것이 방법이다.
- 여기서 주의할 점은 IOExcetion에 부모에는 RuntimeException이 없고, ArithmeticException에게는 있다는 점이다. RuntimeException을 부모로 가지고 있는 클래스는 unchekced 예외, 가지고 있지 않는 것을 checked 예외라고 부른다.
checked 예외는 반드시 예외처리를 해야하는 것이다. 그렇기 때문에 "예외의 여러 상황들" 속 코드에서 IOExcetion만 예외 처리를 했던 것이다.
사용자 정의 예외
대체로 자바에서 제공하는 표준 예외 클래스를 사용하는 것이 좋다.
- 의사소통이 수월하다
- 유지보수와 자원면에서 뛰어나다
checked 예외는 사용자에게 문제 해결할 기회를 줄 때 사용하며 반대로 unchecked 예외는 프로그램을 종료하는 것이 더 좋을 경우에 사용한다. 이를 고려해서 예외를 만들면 된다.
unchecked 예외 클래스 생성 코드
class DivideException extends RuntimeException {
DivideException(){
super();
}
DivideException(String message){
super(message);
}
}
class Calculator{
int left, right;
public void setOprands(int left, int right){
this.left = left;
this.right = right;
}
public void divide(){
if(this.right == 0){
throw new DivideException("0으로 나누는 것은 허용되지 않습니다.");
}
System.out.print(this.left/this.right);
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 0);
c1.divide();
}
}
checked 예외 클래스 생성 코드
class DivideException extends Exception {
DivideException(){
super();
}
DivideException(String message){
super(message);
}
}
class Calculator{
int left, right;
public void setOprands(int left, int right){
this.left = left;
this.right = right;
}
public void divide(){
if(this.right == 0){
try {
throw new DivideException("0으로 나누는 것은 허용되지 않습니다.");
} catch (DivideException e) {
e.printStackTrace();
}
}
System.out.print(this.left/this.right);
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 0);
c1.divide();
}
}
checked 예외는 예외 처리를 해주어야 하기 때문에 위 코드처럼 try~catch 구문을 사용해도 좋고
throws를 사용해도 된다.
Obejct 클래스
상속
class O {}
class O extends Object {}
위 두 코드는 같은 코드이다.
자바에서 모든 클래스는 암시적으로 Object를 상속받고 있는 것이다.
Object는 모든 클래스가 공통으로 포함하고 있어야 하는 기능을 제공하기 위한 모든 클래스의 조상이다.
Object가 가지고 있는 여러 메소드 중 잘 사용하는 메소드들을 몇가지 살펴보자
toString
객체를 문자로 표현하는 메소드
class Calculator{
int left, right;
public void setOprands(int left, int right){
this.left = left;
this.right = right;
}
public void sum(){
System.out.println(this.left+this.right);
}
public void avg(){
System.out.println((this.left+this.right)/2);
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 20);
System.out.println(c1);
// System.out.println(c1.toString());
}
}
c1과 c1.toString()은 동일한 값을 가진다.
실행 결과는 아래와 같다.
org.opentutorials.javatutorials.progenitor.Calculator@11be650f
toString 메소드를 정의한 적이 없는데 사용이 가능한 것은 Calculator가 Object 클래스를 상속받고 있기 때문이다.
그렇다는 것은 toString 메소드를 Overriding 할 수 있다는 것이기에 아래와 같이 코드를 변경할 수 있다.
class Calculator{
int left, right;
public void setOprands(int left, int right){
this.left = left;
this.right = right;
}
public void sum(){
System.out.println(this.left+this.right);
}
public void avg(){
System.out.println((this.left+this.right)/2);
}
public String toString(){
return "left : " + this.left + ", right : "+ this.right;
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 20);
System.out.println(c1);
System.out.println(c1.toString());
}
}
실행 결과
left : 10, right : 20
left : 10, right : 20
toString은 직접 재정의하여 사용하는 것이 유용하기에 재정의를 권장한다.
equals
객체와 객체가 같은 것인지를 비교하는 메소드
class Student{
String name;
Student(String name){
this.name = name;
}
public boolean equals(Object obj) {
Student _obj = (Student)obj;
return name == _obj.name;
}
}
class ObjectDemo {
public static void main(String[] args) {
Student s1 = new Student("egoing");
Student s2 = new Student("egoing");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
}
equals는 인자로 Object를 받는 메소드이기에 오버라이딩할 때도 Object를 인자로 갖고 있어야 한다.
하지만 Object에는 name이 없기 때문에 Student로 형변환 해주어야 한다.
자식 클래스를 부모 클래스로 형변환할 때는 묵시적 형변환이 가능하지만
부모 클래스를 자식 클래스로 형변환할 때는 명시적 형변환을 해주어야 하기에 (Student)를 적어준다.
실행 결과
false
true
s1과 s2는 다른 객체이기 때문에 s1==s2의 결과값은 false이다.
하지만 각각의 name은 같기 때문에 오버라이딩한 equals의 결과값으론 true가 나온다.
equals를 직접 구현할 때에는 hashcode도 함께 구현해야 한다.
IDE에서 이 둘을 자동으로 생성해주는 기능도 있다
equals는 객체를 비교할 때 사용되고 그 외 원시 데이터형(byte, short, int, long, float, double, boolean, char)을 비교할 때는 ==를 사용한다.
finalize
객체가 소멸될 때 호출되기로 약속된 메소드이다.
많은 자바의 전문가들은 이 메소드의 사용을 만류하고 있기에 이정도만 알아도 된다.
그보다 가비지 컬렉션에 대해 알아보자.
인스턴스를 만드는 것은 내부적으로 컴퓨터의 메모리(RAM)을 사용하는 것이다.
RAM은 가격이 비싸고 용량이 적기 때문에 최대한 적게 사용하는 것이 좋은 프로그램이다.
많은 프로그래밍 언어들은 램을 효율적으로 사용하기 위한 방법들을 제공하는데
자바의 경우는 가비지 컬렉션이 사용하지 않는 변수와 인스턴스는 삭제해준다.
clone
어떤 객체가 있을 때 그 객체와 똑같은 객체를 복제해주는 기능
class Student implements Cloneable{
String name;
Student(String name){
this.name = name;
}
public Object clone() throws CloneNotSupportedException{
return super.clone();
}
}
class ObjectDemo {
public static void main(String[] args) {
Student s1 = new Student("egoing");
try {
Student s2 = (Student)s1.clone();
System.out.println(s1.name);
System.out.println(s2.name);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
clone을 하려면 복사하려는 대상이 clone이 가능하다는 것을 cloneable로 명시해주어야 한다.
clone 메소드의 접근 제한자는 protected라서 서로 다른 패키지에서는 접근할 수 없지만 상속은 가능하다.
또한 clone 메소드는 예외 처리를 필수적으로 해주어야 하는 checked 메소드이다.
그렇기에 public으로 clone 메소드를 오버라이딩해주되 예외 처리를 throws를 통해 해주었다.
main에서 사용할 때에 clone 메소드의 리턴값은 Object이기에 Student로 명시적 형변환을 해주었다.
실행 결과
egoing
egoing
상수와 enum
상수
상수는 변하지 않는 값이다.
public class ConstantDemo {
public static void main(String[] args) {
/*
* 1. 사과
* 2. 복숭아
* 3. 바나나
*/
int type = 1;
switch(type){
case 1:
System.out.println(57);
break;
case 2:
System.out.println(34);
break;
case 3:
System.out.println(93);
break;
}
}
}
위 코드는 주석이 없으면 어떤 기능을 하는 코드인지 알아볼 수가 없다.
리팩토링하면 아래와 같이 바꿀 수 있다.
public class ConstantDemo {
private final static int APPLE = 1;
private final static int PEACH = 2;
private final static int BANANA = 3;
public static void main(String[] args) {
int type = APPLE;
switch(type){
case APPLE:
System.out.println(57+" kcal");
break;
case PEACH:
System.out.println(34+" kcal");
break;
case BANANA:
System.out.println(93+" kcal");
break;
}
}
}
과일명으로 switch를 돌며 과일에 해당하는 숫자를 상수로 지정했다.
좋은 코드는 주석이 잘 작성된 코드며 더 좋은 코드는 주석이 필요없는 코드이다.
위 코드는 주석이 필요없는 코드이다.
enum이 도입된 배경
위 코드에서 과일뿐만 아니라 기업을 추가한다고 하면 아래와 같이 코드를 짤 수 있다.
public class ConstantDemo {
// fruit
private final static int APPLE = 1;
private final static int PEACH = 2;
private final static int BANANA = 3;
// company
private final static int GOOGLE = 1;
//private final static int APPLE = 2;
private final static int ORACLE = 3;
public static void main(String[] args) {
int type = APPLE;
switch(type){
case APPLE:
System.out.println(57+" kcal");
break;
case PEACH:
System.out.println(34+" kcal");
break;
case BANANA:
System.out.println(93+" kcal");
break;
}
}
}
위 코드에서 문제점은 상수인데 APPLE이 2개라는 것이다.
해결하기 위해서는 FRUIT_APPLE과 같이 이름을 바꾸는 방법이 있다. 이를 네임스페이스라고 한다.
하지만 상수 이름이 지저분해지기 때문에 인터페이스를 사용할 수 있다.
interface FRUIT{
int APPLE=1, PEACH=2, BANANA=3;
}
interface COMPANY{
int APPLE=1, GOOGLE=2, ORACLE=3;
}
상수로 정의한 코드를 인터페이스로 바꾼 것이다.
과일에 APPLE와 회사에 APPLE는 분명히 다른 것이지만 비교할 경우 같다고 나온다.
동일하게 1이기 때문이다.
데이터 타입이 다르면 비교 자체를 못하게 되기에 아래와 같이 코드를 변경할 수 있다.
class Fruit{
public static final Fruit APPLE = new Fruit();
public static final Fruit PEACH = new Fruit();
public static final Fruit BANANA = new Fruit();
}
class Company{
public static final Company GOOGLE = new Company();
public static final Company APPLE = new Company();
public static final Company ORACLE = new COMPANY(Company);
}
클래스를 만들어 각각 객체를 생성해주면 비교 자체를 할 수 없다.
하지만 이 코드 또한 다음의 문제가 있다.
- switch문에는 byte, short, char, int, enum, String, Character, Byte, Short, Integer만 사용 가능하다.
- 선언이 복잡하다.
enum의 문법
enum은 열거형이라고도 부르며 서로 연관된 상수들의 집합이다.
위 코드를 enum을 사용하여 수정하면 아래와 같다.
enum Fruit{
APPLE, PEACH, BANANA;
}
enum Company{
GOOGLE, APPLE, ORACLE;
}
public class ConstantDemo {
public static void main(String[] args) {
Fruit type = Fruit.APPLE;
switch(type){
case APPLE:
System.out.println(57+" kcal");
break;
case PEACH:
System.out.println(34+" kcal");
break;
case BANANA:
System.out.println(93+" kcal");
break;
}
}
}
enum을 사용하는 이유
- 코드가 단순해진다
- 클래스와 달리 인스턴스 생성과 상속이 불가능하여 제작자의 의도대로 개발하기 용이하다
- 키워드 enum을 사용하기 때문에 구현의 의도가 열거임을 분명하게 나타낼 수 있다
enum의 활용
enum은 클래스이기 때문에 생성자, 필드, 메소드를 가질 수 있다.
enum Fruit{
// 생성자를 통해 열거형 상수 생성
APPLE("red"), PEACH("pink"), BANANA("yellow");
private String color;
// 색상을 받는 생성자. 접근제어자 private
Fruit(String color){
System.out.println("Call Constructor "+this);
this.color = color;
}
// getter 메소드
String getColor(){
return this.color;
}
}
enum Company{
GOOGLE, APPLE, ORACLE;
}
public class ConstantDemo {
public static void main(String[] args) {
for(Fruit f : Fruit.values()){
System.out.println(f+", "+f.getColor());
}
}
}
실행 결과
APPLE
PEACH
BANANA
'JAVA > [생활코딩] 자바' 카테고리의 다른 글
[생활코딩] 참조, 제네릭, collections framework (0) | 2023.05.07 |
---|---|
[생활코딩] 접근 제어자, abstract, final, 인터페이스, 다형성 (1) | 2023.04.17 |
[생활코딩] overriding, overloading, 클래스 패스, 패키지, API와 API 문서 보는 법 (0) | 2023.04.09 |
[생활코딩] 자바 유효범위, 초기화와 생성자, 상속, 상속과 생성자 (0) | 2023.03.31 |
[생활코딩] 자바 객체지향프로그래밍, 클래스와 인스턴스 그리고 객체, 클래스 멤버와 인스턴스 멤버 (0) | 2023.03.31 |