[Study #17] 예외와 내부 클래스

     

    01. C언어

     

    [ 기본 형식 ]

    #include <stdio.h>
    
    int main()
    {
        printf("Hello World");
    
        return 0;
    }
    • #include는 java에서 import와 똑같은 역할
    • st는 standard라는 의미
    • java와 for문, if문 사용하는 방식은 동일

    [ 활용 ]

    int num = 0;
    printf("%d", sizeof(num));
    //sizeof는 저장 공간의 크기를 출력
    //4byte라서 값이 4가 나옴
    
    double num1 = 0;
    printf("%d", sizeof(num));
    //8byte라서 값이 8이 나옴
    
    int number = 10;
    printf("Hello World %d", number);
      
    int a[] = {0, 2, 4, 8};//배열 선언 방법 1
    int b[3] = {};//배열 선언 방법 2

     

    02. 예외란 

     

    • 예외는 사용자의 잘못된 조작 또는 개발자의 잘못된 코딩으로 인해 발생하는 프로그램 오류로, 실행 시에 발생되는 모든 에러 상황을 통칭
    • 자바에서는 예외라는 것을 두고 목적에 따라 핸들링(처리)하도록 되어있음
    • 예외는 예외 처리를 통해 프로그램을 종류하지 않고 정상 실행 상태가 유지되도록 할 수 있음
    • 특정 API에서는 컴파일 시에 예외를 처리하지 않으면 컴파일 되지 않는 예외도 있음
    • 예외가 발생하는 순서 : 
      컴파일 → 실행 → 실행 중 예외 발생 → VM이 발생된 예외의 종류 및 내용 파악 → 예외 객체 생성 → 발생된 코드 밖으로 예외 객체를 던짐(throw) → 예외에 콜 스택에 전이 → main메서드 밖까지 던지게 되면 프로그램 강제 종료
    • 위처럼 예외는 '던지다'라고 표현

     

    03. 예외 클래스

    • 예외는 객체이기 때문에 클래스로 정의되어 있음
    • 모든 예외 클래스는 java.lang.Exception 클래스를 상속받음
    • Exception 클래스의 하위 타입이 바로 프로그래머가 처리해야 할 예외 타입들임
       : 실행 예외 클래스
    • Error의 하위 타입은 일반적으로 자바 실행기 즉, VM*에 관련된 에러 상황들을 정의한 클래스
    • VM이나 JRE 전체의 Error 사항은 프로그래머가 처리할 수 없음
    • 따라서 프로그래머는 Error하위 타입의 에러들은 처리하지 않고 무시(처리할 수 없음)
    • Throwable 클래스는 자식으로 Error를 가지고 있기 때문에 예외의 최고 클래스라고 표현하지 않음

    * VM (Virtual Machine) : 바이트코드를 실행시키기 위한 가상머신

     

    04. 예외의 종류

     

    1) ERROR (예외에 포함 안됨)

    • 문법 상의 오류가 있는 경우
    • try/catch로 해결할 수 없는 경우
    • 프로그래머가 처리할 수 없는 경우
    public class Exception01 {
    	public static void main(String[] args) {
    		print();//문법 상의 오류
    	}
    }

     

    2) 일반 예외 (Exception) = 알려진 예외

    • 컴파일러 체크 예외로, 컴파일러가 관여하는 예외 (ex. 밑줄 가는 경우)
    • 자바 소스를 컴파일하는 과정에서 해당 예외 처리 코드가 있는지 검사
    • 예외 처리 코드가 없다면 컴파일 오류 발생
    • RuntimeException의 하위클래스가 아닐 경우 일반 예외

    [ 일반 예외의 예시 ]

    package exception;
    
    public class Exception {
    	public static void main(String[] args) {
    		
    		Class clazz = Class.forName("java.lang.String2");
    		//컴파일 오류 : 'java.lang.String2'가 존재하지 않음
    	}
    }

    [ 활용 ]

    try {
        Class clazz = Class.forName("java.lang.String2");
        // "java.lang.String2"는 없음
        System.out.println("윗 문장이 예외가 발생하지 않으면 실행");
        // 예외가 있다면 위 문장은 절대 출력되지 않음
    } catch (ClassNotFoundException e) {
        System.out.println("클래스가 존재하지 않습니다.");
        //e.printStackTrace();
    }
    System.out.println("try~catch블럭 끝. 정상 종료");
    // 클래스가 존재하지 않습니다.
    // try~catch블럭 끝. 정상 종료

     

    3) 실행 예외 (Runtime Exception) = 알려지지 않은 예외 (UnChecked Exception)

    • 컴파일러 넌 체크 예외라고도 하는데, 컴파일러가 관여하지 않음 
    • 실행 시 예외가 발생할 수도 있고 안할 수도 있어서 예측할 수 없음
    • 컴파일하는 과정에서 예외 처리 코드 있는지 검사하지 않음
    • 자바 컴파일러가 체크하지 않기 때문에 개발자의 경험에 의해서 예외 처리 코드 작성
    • 해당 예외가 발생하면 프로그램 종료
    • RuntimeException의 하위클래스일 경우 실행 예외

    [ 실행 예외의 예시 ]

    //문법 상에서는 오류가 나지 않음
    //컴파일 하는 경우에 오류가 나옴
    public class Exception01 {
    	public static void main(String[] args) {
    
    		int[] arr = {1, 2, 3, 4, 5 };
    		
    		 for (int i = 1; i < arr.length; i++) {
    			 System.out.printf("%d", arr[i + 1]);//arr[i + 1]이 존재하지 않음
    		}
    	}
    }

    [ 활용 ]

    public class Exception01 {
    	public static void main(String[] args) {
    
    		System.out.print("숫자 하나 입력하세요. : ");
    		try {// 예외가 발생할 것 같은 문장
    			int num = System.in.read() - 48;// 한글자씩 입력할 때 사용
    			System.out.println("입력에 문제가 없으면 여기가 실행됩니다.");
    			System.out.println(num);
    
    		} catch (IOException e) {// 예외가 발생할 경우 실행 (예외가 없다면 실행되지 않음)
    			System.out.println("입출력에 문제가 발생했습니다.");
    			e.printStackTrace();
    		}
    
    	}
    }

     

    4) 자주 발생되는 실행 예외

    항목 예외 상황
    NullPointerException 객체 참조가 없는 상태인 null값을 갖는 참조 변수를 객체로 사용할 경우
    ArrayIndexOutBoundsException 배열에서 인덱스 범위를 초과할 경우
    NumberFormatException 문자열로 되어 있는 데이터를 숫자로 잘못 변경하는 경
    ClassCastException 형 변환 작업에서 실제 객체의 유형이 변환하려는 타입과 호환되지 않을 경우

     

    05. 예외 처리 방법

     

    01) 직접 처리

    • try / catch / finally 이용
    • finally 예외 발생 유무와 상관없이 반드시 실행할 코드로 주로 닫기 명령문을 작성
    • 닫기 명령어를 쓸 때에는 나중에 오픈한 것부터 차례로 닫아줘야 함
    • 닫기 명령어를 쓰지 않아도 메인 메소드 블록이 끝나면 자동으로 닫히지만 오류가 있을 경우, 다음에 실행했을 때 파일이 열리지 않음 (안 써도 되지만 쓰는 걸 권장)
    • finally를 쓰는 목적은 프로그램을 정상 종료하기 위해서임
    • catch ( ) 괄호 안에서 오류를 제거 하는 방법은 다양함
      (ex. FileNotFoundException : 파일이 없을 때, IOException : 파일을 읽지 못할 때, Exception : 모든 오류 처리)

     

    [ 기본 형식 ]

    try {
    	// Exception이 발생할 코드
    } catch(Exception e) {
    	// 예외가 발생하면 실행할 코드
    } finally {
    	// 예외 발생여부와 상관없이 공통적으로 실행할 코드
    }
    
    //다중 try~catch 가능
    try {
    	try {
        } catch(Exception e) {
        }
    } catch(Exception e) {
    
    } finally {
    
    }

     

     

    [ 활용 1 ]

    package exception;
    
    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.io.IOException;
    
    public class Exception {
    	public static void main(String[] args) {
    		
    		// FileReader는 텍스트 파일의 문자 데이터를 읽기 위해 사용
    		FileReader fr = null;
    		try {
    			fr = new FileReader("c:\\temp\\temp.txt");
    			//fr = new FileReader("c:/temp/temp.txt");위랑 같은 의미
    			System.out.println("파일이 있습니다.");
    			
    			int data;
    			
    			while (true) {
    				data = fr.read();//글자 하나를 읽어오고 글자가 없다면 -1
    				if (data == -1) {//읽어올 데이터가 없다면
    					break;//종료
    				}
    				System.out.print((char) data);//int타입을 char로 변환해서 출력
    			}
    			
    		} catch (FileNotFoundException e) {
    			//e.printStackTrace();
    			//파일이 없을 때 실행할 catch
    			System.out.println("파일이 없습니다. 확인해주세요.");
    		} catch (IOException e) {
    			//e.printStackTrace();
    			//파일을 읽을 때 문제 발생했을 경우 실행할 catch
    			System.out.println("파일을 읽을 수 없습니다.");
    		} finally {//예외 발생 유무와 상관없이 반드시 실행할 문장
    			//닫기 명령문 (파일 열었다면 거꾸로 닫아주기)
    			try {
    				fr.close();
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }

     

    [ 활용 2-1 ] 위 코드에서 fr.close(); 제거하는 방법(1) : Add catch clause to surrounding try

    try {
        FileReader fr = new FileReader(file);
        System.out.println("파일 있음.");
        fr.close();
        
    } catch (FileNotFoundException e) {
        System.out.println("파일 없음.");
        
    } catch (IOException e) {
        e.printStackTrace();
        
    } finally {
        sc.close();
    }

     

    [ 활용 2-2 ] 위 코드에서 fr.close(); 제거하는 방법(2) : Add exception to existing catch clause

     

    try {
        FileReader fr = new FileReader(file);
        System.out.println("파일 있음.");
        fr.close();
    
    } catch (IOException e) {
        System.out.println("파일 없음.");
    
    } finally {
        sc.close();
    }

     

    [ 활용 2-3 ] 위 코드에서 fr.close(); 제거하는 방법(3) : Surround with try/catch

    try {
        FileReader fr = new FileReader(file);
        System.out.println("파일 있음.");
        try {
            fr.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    
    } catch (FileNotFoundException e) {
        System.out.println("파일 없음.");
    } finally {
        sc.close();
    }

     

    [ 활용 3 ] 배열 길이가 넘어갔을 경우

    int[] arr01 = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    		
    try {
        System.out.println(arr01[arr01.length]);
        System.out.println("이 출력문은 볼 수 없습니다.");
        System.out.println(arr01[0] / 0);
    
    } catch (ArrayIndexOutOfBoundsException e) {
        System.out.println("배열 길이 밖으로 나갔습니다.");
    } catch (ArithmeticException e) {
        System.out.println("0으로 나눌 수 없습니다.");
    } finally {
        System.out.println("예외 발생 유무와 상관없이 실행합니다.");
    }
    
    // 배열 길이 밖으로 나갔습니다.
    // 예외 발생 유무와 상관없이 실행합니다.

     

    [ 활용 4 ] 0으로 나누었을 경우

    int[] arr01 = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    		
    try {
        System.out.println(arr01[arr01.length - 1]);
        System.out.println("이 출력문은 볼 수 없습니다.");
        System.out.println(arr01[0] / 0);
    
    } catch (ArrayIndexOutOfBoundsException e) {
        System.out.println("배열 길이 밖으로 나갔습니다.");
    } catch (ArithmeticException e) {
        System.out.println("0으로 나눌 수 없습니다.");
    } finally {
        System.out.println("예외 발생 유무와 상관없이 실행합니다.");
    }
    
    // 1
    // 이 출력문은 볼 수 없습니다.
    // 0으로 나눌 수 없습니다.
    // 예외 발생 유무와 상관없이 실행합니다.
     

    0으로 나눌 수 없는 이유

     

    0을 다른 숫자로 나누는 것은 수학적으로 정의되지 않은 연산입니다.

    수학적으로, 어떤 수를 0으로 나누는 것은 무한대(infinity)나 음의 무한대(negative infinity)로 수렴하는 경향을 가집니다. 이러한 결과는 정확한 숫자로 표현할 수 없기 때문에 수학적으로는 정의되지 않은 것으로 간주됩니다.

    자바에서는 이러한 연산을 수행하려고 할 때 "ArithmeticException"이라는 예외를 발생시킵니다. 이 예외는 프로그램의 실행을 중단시킵니다.

    따라서 0으로 나누는 것은 불가능하며, 이를 시도하면 예외가 발생하여 프로그램이 종료됩니다. 프로그램에서 0으로 나누어야 하는 경우, 이에 대한 예외 처리를 수행하거나 나누기 연산을 수행하기 전에 0으로 나눌 수 있는지 여부를 확인해야 합니다.

     

     

     

    02) 던지기(Throw)

    • 메서드 뒤 부분에 throw 처리할 타입을 적어줌
    • 메소드가 실행되다가 예외를 만나면 메소드를 호출한 쪽으로 예외를 던짐
    • 던지기가 main메서드까지 온다면 vm으로 던지고 강제 종료됨

    [ 예시 ]

    package exception;
    
    import java.io.IOException;
    
    public class Exception {
    	public static void main(String[] args) throws IOException {
    		
    		int num = System.in.read();
    		
    	}
    }

     

     

    03) 직접 Exception 객체 만들어서 처리하기

    • 거의 쓰는 경우가 없음
    throw 객체명;
    
        @override
        public void printStrackTrace(){
            System.out.print("예외가 발생했습니다.");
            super.printStrackTrace();
        }

     

    06. 내부 클래스 (중첩 클래스)

     

    • 클래스 속에 클래스가 있는 형태 or 메서드 속에 클래스가 있는 형태
    • 보통의 클래스 :  class A{}    class B{}
    • 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있음
    • 캡슐화, 코드의 복잡성을 줄여줌
    • 보통 내부 클래스가 외부 클래스의 부품을 활용할 때, 내부 클래스는 잘 활용되지 않을 때 사용

    [ 내부 클래스 기본 형식 ]

    public class A {
    	class B {
        }
    }

     

    07. 내부 클래스의 종류

     

    01) 정적 멤버 클래스 = 스타틱 클래스

    • 외부 클래스의 멤버 변수 위치에 선언
    • Static 멤버처럼 다뤄짐
    • 주로 외부 클래스의 스타틱 멤버, 특히 스타틱 메드에 사용될 목적으로 선언
    • Class 앞에 Static이 붙어서 객체 생성 없이 사용
    • 클래스의 Static변수처럼 사용
    • 외부 클래스와 내부 클래스는 다르게 동작
    • 외부 클래스와 내부 클래스의 멤버가 private이더라도 상호 모드 접근 가능
    • 경로만 지정되면 단독으로 직접 사용할 수 있음

    [ 기본 형식 ]

    package inner;
    
    class A{
    	static class C{//static 클래스로 선언
    		public C() {}//생성자
    		int field;//인스턴스 변수
    		static int field2;//정적 변수
    		void mathod1() {}//인스턴스 메소드
    		static void method2() {}//static 메서드
    	}
    }
    
    public class StaticClass {
    	public static void main(String[] args) {
    		A.C c = new A.C();//C만 뽑아서 c로 만든것임
    		c.field = 100;
    		c.mathod1();
    		
    		A.C.field2 = 10;
    		A.C.method2();
    	}
    }

     

    [ 활용 ]

    public class StaticClass01 {
    	
    	public static class Inner{
    		private int innerMember = 20;//객체 생성하고 써야 함
    		private static int outDF = 22;
    		final int LV = 120;
    		
    		//static은 non-static을 부를 수 없다
    		//static 붙은 애들은 static만 부를 수 있음
    		public static void nestedMethod() {
    			//System.out.println(innerM);
    			System.out.println(outDF);
    			//System.out.println(LV);
    		}
    		public void innerMathod() {
    			System.out.println(innerMember);
    			System.out.println(outDF);
    			System.out.println(LV);
    		}
    		
    	}//Inner Class End
    	
    	public static void main(String[] args) {
    		StaticClass01.Inner in = new Inner();
    		//StaticClass01.Inner in = new StaticClass01.Inner(); 이렇게도 가능
    		in.innerMathod();
    		StaticClass01.Inner.nestedMethod();
    		//static 붙어도 딱 한 개의 객체 만들 수 있음
    		StaticClass01.Inner.outDF = 100;
    		
    	}
    }

     

    02) 멤버 클래스

    • static 키워드 없이 중첩 선언된 클래스
    • 내부에 있는 클래스 앞에 static 붙으면 정적 클래스,
      내부에 있는 클래스 앞에 아무것도 없으면 멤버 클래스
    • 내부 클래스는 클래스 내에 선언되었으므로 인스턴스 속성처럼 사용 (메서드 {} 안에서 유효)
    • 인스턴스 필드와 메서드만 선언 가능 (정적 필드와 정적 메서드는 선언 불가)
    • 인스턴스 변수는 클래스 내에서 선언되지만 메서드 밖에서, 생성자 밖에서, 다른 블록 밖에서 선언되는데 이 경우 반드시 초기화 필요
    • 내부 클래스는 외부 클래스의 멤버를 사용할 수 있지만, 외부 클래스는 내부 클래스의 멤버 변수를 사용할 수 없음
    • Static 붙은 메서드 내에서는 내부 클래스의 객체 선언은 할 수 없
    • 외부 클래스의 멤버 변수 위치에 선언
    • 인스턴스 멤버처럼 사용됨
    • 주로 외부 클래스의 인스턴스 멤버들과 관련된 작업에 사용

    [ 예시 ]

    package inner;
    
    public class MemberClass {
    	private int outerDF = 500;
    	private static int SI = 100;
    	int c = 777;
    
    	void outerMethod() {
    		System.out.println(c);
    		System.out.println(outerDF);
    		System.out.println(MemberClass01.SI);
    		//내부 클래스 객체 생성
    		//내부에 있는거 가져다가 객체 생성
    		InnerMember im = new InnerMember();
    		im.method();//im 안에 있는 method 불러오기
    		System.out.println(im.iX);
    		// 다른 클래스에 있는 private을 내꺼처럼 호출하기
    		// private 걸어도 내부에서는 내꺼처럼 볼 수 있음
    		//System.out.println(InnerMember.B3);
    	}
    	public void outterM() {//반환타입 없음
    		InnerMember im2 = new InnerMember();//생성가능
    		//void 앞에 static이 붙으면 생성 불가능
    		//static이 붙으면 객체 생성 후 사용해야 함
    	}
    	
    	public class InnerMember{
    		private int iX = 1;
    		int outDF = 10;
    		static final int intB2 = 222;
    		//static은 final 붙은것만 처리 가능
    		static int B3 = 100;
    		
    		public void method() {
    			int methodInt = 130;
    		}
    	}//class end
    	
    	public static void main(String[] args) {
    		MemberClass01 inst = new MemberClass01();
    		MemberClass01.InnerMember in = inst.new InnerMember();
    		in.method();//이렇게 호출 할 수 있습니다.
    	}
    }

     

    03) 지역 클래스

    • 외부 클래스의 메소드나 초기화 블럭 안에서 선언
    • 선언된 영역 내부에서만 사용 (지역 바디가 닫히면 그 클래스도 소멸됨)
    • 메서드 내에서도 선언할 수 있음
    • 접근제어자 및 static을 붙일 수 없음
    • 메서드 내부에서만 사용되므로 접근제어가 필요 없음
    • 로컬 클래스 내부에는 인스턴스 필드와 메서드만 선언 가능 (정적 필드와 메서드는 선언 불가)

    [ 기본 ]

    class LA {//외부 클래스
    	void method() {//메서드 속
    		class LB {//로컬 클래스(메서드가 닫히면 못 씀)
    			public LB() {
    				System.out.println("LB가 만들어졌습니다.");
    			}//생성자 선언
    			int num;//필드
    			void method() {}
    			
    		}//LB class end (로컬 클래스)
    		LB lb = new LB();
    		lb.num = 100;
    		lb.method();
    	}
    }
    
    public class LocalClass01 {
    	public static void main(String[] args) {
    		//지역 클래스는 바로 호출 불가능
    		LA la = new LA();
    		la.method();
    	}
    }

     

    04) 익명 클래스

    • 클래스 선언과 객체의 생성을 동시에 하는 이름없는 클래스
    • 클래스를 인수의 값으로 사용하는 클래스
    • 객체를 한번만 사용할 수 있고 다른 곳에서 사용 불가
    • 클래스의 선언부가 없기 때문에 생성자가 없음
    • 슈퍼 클래스의 이름이나 구현할 인터페이스의 이름을 사용해서 정의
    • 오직 하나의 클래스를 상속받거나 하나의 인터페이스만 구현 가능
    • 코드블럭을 클래스 선언에 하는 점만 제외하고는 생성자 호출과 동일
    • 객체를 구성하는 new문장 뒤에 클래스의 {}블록을 첨부하여 몸통을 닫는 형식으로 구성
    • new 슈퍼클래스 또는 인터페이스명(){};
    • 객체를 생성한 후에 {}; 즉, 메서드를 구현한 블록이 있고 블록 끝에는 세미콜론을 붙임
    • new 뒤에 오는 생성자명이 기존 클래스명이며 익명 클래스가 자동으로 호출한 클래스의 하위 클래스가 됨
    • 인터페이스인 경우에는 인터페이스를 상속받는 부모 클래스가 object가 됨
    • 중첩 클래스 중 가장 많이 씀

    [ 기본 ]

    package inner;
    
    class  AA {
    	public void method() {
    		System.out.println("AA's method()");
    	}
    }
    
    public class AnonymousClass {
    	public static void main(String[] args) {
    		
    		AA aa = new AA() {
    			@Override //여기에서만 쓸 용도로 오버라이드 해서 만들어줌
    			public void method() {
    				System.out.println("익명 객체입니다.");
    			}
    			//public void addMet() {//못씀
    				//System.out.println("메서드 추가");
    			//}
    		};//생성자 끝
    		
    		aa.method();
    		//aa.addMEt();//못씀
    
    	}
    }

     

    [ 활용 1 ] 상속과 익명 클래스 비교

    package inner;
    
    class  AAA {
    	public void method() {
    		System.out.println("AA's method()");
    	}
    }
    
    //상속
    class AB extends AAA {
    	@Override
    	public void method() {
    		System.out.println("자식에서 재정의");
    	}
    }
    
    public class AnonymousClass01 {
    	public static void main(String[] args) {
    		AAA aa = new AB();
    		aa.method();//자식에서 재정의
    		
    		//익명 클래스
    		AAA a2 = new AAA() {
    			@Override
    			public void method() {
    				System.out.println("익명형태로 제작되었습니다.");
    			}
    		};
    		a2.method();//한번만 만들고 다시는 안씀
    	}
    }

     

    [ 활용 2 ] 중첩 인터페이스

    package inner;
    
    interface AC {
    	public void print();
    	public void method2();
    }
    
    public class AnonymouseClass02 {
    	public static void main(String[] args) {
    		AC ac = new AC() {//인터페이스는 생성자가 없음
    
    			@Override
    			public void print() {
    			}
    
    			@Override
    			public void method2() {
    			}
    			
    		};
    		ac.method2();
    		ac.print();
    	}
    }

     

     

    참고자료
    신용권, '혼자 공부하는 자바'

     

     

    댓글