1. 예외를 처리하는 방법

※ 최근 자바 개발자들끼리 checked exception을 unchecked exception으로 전환시키는 것이 좋다는 결론을 내림

 

1) 예외 복구

// 일반적으로 예외를 던지는 경우
public void exceptionTest() throws SomeException {

}

// 어느정도 처리하고 예외를 던지는 경우
public void exceptionTest() throws SomeException {
    try {
        ...
    } catch (SomeException e) {
// 예외 처리의 필요성이 있을 때 어느정도 처리하고 던지는 경우
	throw e;
    }
}

- 예외 발생 시 다른 작업 흐름으로 유도 ( try ~ catch ~ finally )

- 예외가 발생해도 어플리케이션은 정상적인 흐름으로 진행됨

- 예외 발생 시 재시도를 통해 정상 흐름으로 진행되게 하거나 미리 예측하여 다른 흐름으로 유도시키도록 구현하면 예외가 발생하더라도 정상적으로 작업 종료 가능

 

2) 예외 처리 회피

public void exceptionTest() {
    try {
        ...
    } catch (SQLException e) {
// 더 명확하게 인지할 수 있도록 다른 예외로 전환해서 던지는 방법
	throw new DuplicationUserIdException(message);
    }
}

- 예외 처리를 직접 담당하지 않고 호출한 쪽으로 던져 회피하는 방법

- 무책임하게 상위 메서드로 throw를 던지는 행위는 상위 메서드의 책임이 증가하기 때문에 적절한 경우에만 사용하는 것이 좋음

 

3) 예외 전환

- 예외를 잡아 다른 적절한 예외로 전환하여 자신을 호출한 메서드로 던져버리는 방법

- 호출한 쪽에서 예외를 받아 처리 시 좀 더 명확하게 인지할 수 있도록 돕기 위한 방법

- 어쩔 수 없이 API에서 가져다 쓰는 기능(Thread.sleep() 등)은 checked exception이 강제되는 경우 어떤 로직을 호출하거나 실행하려 하는지 명시되는 문제가 발생

→ try ~ catch를 쓰되 catch 블럭에서 다른 runtimeException을 throw

→  예외를 유발했던 요소가 아닌 catch 블럭에서 일으킨 예외가 대신 콘솔에 찍힘

→  throw 되는 예외를 바꿔서 어떤 로직이 예외를 유발했는지 추론하기 어렵게 만들어 캡슐화 유지

'네트워크캠퍼스 > JAVA' 카테고리의 다른 글

프로세스와 쓰레드  (0) 2024.01.24
자바 API  (0) 2024.01.18
예외처리  (0) 2024.01.17
인터페이스  (0) 2024.01.16
추상화  (0) 2024.01.15

1. 예외처리(Exception)

→ 오류 발생 가능성이 있는 부분에 대한 처리를 미리 프로그래밍 해주는 것

 

1) 컴파일러  체크 예외 (Checked Exception)

- 자바 소스를 컴파일하는 과정에서 예외 처리 코드를 검사하여 예외 처리 코드가 없다면 컴파일 오류 발생

- 실행하지 않고도 오류가 발생한 것을 알 수 있음

- Checked Exception은 필요하지 않다고 결론이 남

 

2) 실행 예외 (Runtime Exception)

- 컴파일하는 과정에서 예외처리 코드를 검사하지 않는 예외

- 개발자들 사이에서 실행 예외만 신경쓰기로 합의, 오로지 개발자의 경험에 의해 처리

- 실행 예외에 대한 예외처리 코드를 넣지 않았을 때 해당 예외가 발생하면 프로그램 즉시 종료

 

RuntimeException을 상속받는 예외들은 모두 Unchecked Exception이다.

둘 다 확인 시점은 실행 단계이고, rollback 여부는 설정에 따라 달라진다.

 

 

2. 실행 예외 (Unchecked Exception = Runtime Exception)

1) NullPointerException

→ 객체 참조가 없는 상태, null 값을 갖는 참조 변수로 객체 접근 연산자인 "."을 사용했을 때 발생

public class NullPointerEx {
	public static void main(String[] args) {
		String str = null;
		//str = "HELLO";
		
		// .toLowercase()는 모든 문자를 소문자로 만들어줌
		System.out.println(str.toLowerCase());
	}
}

참조형 변수로 선언한 str이 null값이므로 위와 같은 오류가 발생하는 것을 확인할 수 있다.

 

2) ArrayIndexOutOfBoundsException

→ 배열에서 인덱스 범위를 초과하여 사용할 경우 발생

public class ArrayIndexEx {
	public static void main(String[] args) {
		int [] arr = {3, 6, 9};
		
		// 있지도 않은 인덱스 번호 조회하기, 그러나 문법상 오류는 없음
		System.out.println(arr[3]);
	}
}

배열의 길이가 3인데 존재하지 않는 3번 인덱스 값을 조회했으므로 위와 같은 오류가 발생하는 것을 확인할 수 있다. 

 

3) NumberFormatException

→ 문자열로 되어 있는 데이터를 숫자로 변경하는 경우 발생

public class NumberFormatEx {
	public static void main(String[] args) {
		String a = "35";
		String b = "21";
		System.out.println(a + b);
		
		// str -> int 변환
		int i = Integer.parseInt(a); // 문자 35를 숫자 35로 변환
		int j = Integer.parseInt(b); // 문자 21을 숫자 21로 변환 
		System.out.println(i + j);
		
		// parseInt는 문자열 내부에 순수한 정수가 들어있어야 변환을 실행하며 
		// 정수값이 아니라면 NumberFormatException이 발생
		String s = "Hello";
		System.out.println(Integer.parseInt(s));
	}
}

s 변수는 String 타입으로 정수값이 아닌데 parseInt()는 문자열 내부에 순수한 정수가 있어야함 변환을 실행하기 때문에 위와 같은 오류가 발생하는 것을 확인할 수 있다.

 

4) ClassCastException

→ 상속 관계나 인터페이스 관계가 없는 클래스들을 억지로 형 변환할 경우 발생

☆ 형 변환은 부모클래스 -자식클래스, 구현클래스-인터페이스 간에 발생  이 관계가 아니라면 다른 클래스로 타입 변환 불가

//하나의 클래스 파일에 2개 이상의 클래스 선언 가능 (자주 사용하지는 않음)
// 상속관계 : 부모 Animal을 상속한 자식 Dog, Cat
class Animal{}
class Dog extends Animal{}
class Cat extends Animal{}

public class ClassCastEx {
	public static void main(String[] args) {
		Dog d = new Dog();
		Animal da = d;
		d = (Dog)da;
		
		System.out.println("타입 변환 성공 : Animal -> Dog");
		
		Animal c = new Cat();
		Dog d2 = (Dog)c; // Cat은 Dog 타입으로 변환 불가
		
		System.out.println("타입 변환 성공? : Dog -> Cat");
	}
}

 

Cat과 Dog는 상속 관계에 있는 클래스가 아니므로 위와 같은 오류가 발생하는 것을 확인할 수 있다.

 

 

3. 예외처리코드

1) try ~ catch

- 예외 발생 시 갑작스러운 종료를 막고 정상 실행을 유지할 수 있도록 처리하는 코드

- try~catch~finally 블록 : 생성자나 메서드 내부에서 작성되어 컴파일 예외나 실행 예외가 발생할 경우 예외처리

- try 블록 : 예외 발생 가능성이 있는 코드 작성 예외 발생 시 실행을 멈추고 catch 블록으로 이동

- finally 블록 : 예외 발생 여부와 상관없이 항상 실행 → 필수는 X, 로직이 블럭과 관련 있을 시 작성

※ finally 구문이 실행되지 않는 경우

① finally 구문 이전에 System.exit() 구문 호출 시

② 컴퓨터가 꺼져서 시스템이 멈추었을 시

③ finally 블록 내부에서 예외가 발생했을 시

public class TryCatchEx1 {
	public static void main(String[] args) {
		int i = 10;
		int j = 5;
		
		try { // 예외가 발생할 가능성이 있는 코드를 넣는 구역
			System.out.println(i / j); // 예외 발생 가능성이 있음
			System.out.println("예외 발생하지 않을 때만 실행됨");
		} catch(Exception e) { //catch 블럭에는 Exception의 종류를 기입
			System.out.println("0으로 나눠서 catch 블럭으로 넘어왔습니다.");
		} finally { // try, catch 둘 중 어느 블럭이라도 실행되면 마무리 블럭 실행
			System.out.println("어쨌든 잘 마무리 했습니다.");
		}
	}
}

변수 j가 5라면 try 블럭 안의 내용과 finally 블럭 내용이 출력된다.

변수 j가 0이라면 예외가 발생하여 catch 블럭으로 넘어가고 해당 내용과 finally 블럭 내용이 출력된다.

 

2) 다중 catch

- 여러 가지 예외가 발생한다면 다중 catch 블록 작성

- 상위 예외 클래스가 하위 예외 클래스보다 아래쪽에 위치해야 함

- 상위 예외 클래스의 catch 블록이 위에 있다면 하위 예외 클래스의 catch 블록은 실행되지 않음

- 자바 7 버전부터 하나의 catch 블록에서 여러 개의 예외 처리 가능

- catch() 괄호 안에 동일하게 처리하고 싶은 예외를 |로 연결 (※ 두 예외가 상속 관계에 있으면 안됨)

ublic class MultiCatchEx {
	public static void main(String[] args) {
		String data1 = "30";
		String data2 = "11";
		
		try {
			// NumberFormatException 발생 가능 (정수가 아닌 값이 들어갈 경우)
			int i = Integer.parseInt(data1);
			int j = Integer.parseInt(data2);
		
			// ArithmeticException 발생 가능 (j에 0이 들어갈 경우)
			int result = i / j;
			System.out.println("i / j = " + result);
		
			// NullPointerException 발생 가능
			String str = null;
			str.charAt(0); // 0번째 문자 얻기인데 null값인 경우
		} catch(NumberFormatException | NullPointerException e) {
			System.err.println("데이터를 숫자만 넣어주세요.");
			System.err.println("혹은 문자를 제대로 만들어주세요.");
		} catch(ArithmeticException e) {
			System.err.println("0으로 나눌 수 없습니다.");
		} catch(Exception e) { // 범용 에러 처리 (대부분의 에러를 다 커버)
			System.err.println("알 수 없는 에러가 발생했습니다.");
			System.err.println("복구중입니다.");
		}
	}
}

catch 블록은 위에서부터 차례대로 검색되고, 예외 발생 시 해당하는 예외처리를 할 수 있게 된다.

위 예제의 경우 NullPointerException 예외만 발생하므로 첫번째 catch 블록의 내용만 출력하는 것을 확인할 수 있다.

 

3) throws

- 메서드나 생성자를 호출한 곳으로 예외를 던지는 방법 → 예외 처리를 직접 수행하지 않고 메서드 호출자에게 던짐

- try 블록 내부에서 호출되어야 하고, catch 블록에서 떠넘겨 받은 예외 처리를해야 함

- throws가 붙어있는 메서드는 반드시 try 블록 내부에서 호출되어야 하고, catch 블록에서 떠넘겨 받은 예외를 처리해야 함

- main() 메서드에서 throws를 사용하는 것은 예외처리를 JVM에게 넘기겠다는 의미

  ※ JVM은 예외를 직접 처리해주지 않고 예외 메시지만 출력하고 프로그램 종료

public class ThrowsEx {
	public static String[] greetings = {"안녕", "싸왓디", "헬로"};
	
	// 예외의 원인이 메서드 선언부가 아닌 호출부에 있을 경우
	// 메모리 영역이 다르므로 예외처리를 메서드 호출지역으로 떠넘겨줘야 함
	// 이를 throws라고 하고, 메서드 혹은 생성자 호출 시 예외처리를 강요할 때 사용
	public static void greet(int idx) {
		try {
			System.out.println(greetings[idx]);
		} catch(ArrayIndexOutOfBoundsException e){
			// 코드 안적어도됨
		}
		
	}
	
	public static void main(String[] args) {
		// throws가 붙어 있는 메서드나 생성자 호출 시에는
		// 해당 메서드를 try 블록 내부에서 호출해야 예외처리를 진행해줌
		greet(3);
	}
}

위 예제에서 greetings 배열의 크기가 3인데 4번째 인덱스를 조회하고 있으므로 ArrayIndexOutOfBoundsException이 발생하고, 이 경우 위와 같이 greet 함수에서 예외처리를 해줄 수 있다. 하지만, 객체 지향 측면에서 해당 메서드의 책임이 많아지므로 위 코드는 좋은 코드가 아니다. 

public class ThrowsEx {
	public static String[] greetings = {"안녕", "싸왓디", "헬로"};
	
	// 예외의 원인이 메서드 선언부가 아닌 호출부에 있을 경우
	// 메모리 영역이 다르므로 예외처리를 메서드 호출지역으로 떠넘겨줘야 함
	// 이를 throws라고 하고, 메서드 혹은 생성자 호출 시 예외처리를 강요할 때 사용
	
	// 해당 예외가 발생하면 호출부(여기서는 main)에게 처리를 떠넘기는 것				
	public static void greet(int idx) throws Exception {
		System.out.println(greetings[idx]);
	}
	
	public static void main(String[] args) {
		// throws가 붙어 있는 메서드나 생성자 호출 시에는
		// 해당 메서드를 try 블록 내부에서 호출해야 예외처리를 진행해줌
		try {
			greet(3);
		} catch(Exception e) {
			// .printSackTrace()는 예외발생 경로를 추적하는 메세지 출력
			// 주로 개발 과정에서 예외의 원인을 역추적할 때 유용
			e.printStackTrace();
		}
		System.out.println("프로그램 정상 종료!");
	}
}

위와 같이 throws가 붙은 greet() 메서드 호출 시 해당 메서드가 호출되고 있는 메인함수 쪽에서 예외처리를 진행할 수 있도록 하는 것이 바람직하다.

위와 같이 예외처리가 잘 이루어지고, 프로그램이 정상 종료된 것을 확인할 수 있다.

 

public class ThrowsEx2 {
	public void aaa(int n) throws Exception {
		System.out.println("aaa 호출");
		int i = 10 / n;
		System.out.println("계산 결과 : " + i);
		System.out.println("aaa 실행 중");
	}
	
	public void bbb() throws Exception {
		System.out.println("bbb 호출");
		aaa(0);
		System.out.println("bbb 실행 중");
	}
	
	public void ccc() throws Exception {
		System.out.println("ccc 호출");
		bbb();
		System.out.println("ccc 실행 종료");
	}
	
	public ThrowsEx2() throws Exception {
		System.out.println("생성자 호출");
		ccc();
		System.out.println("생성자 실행 종료");
	}
	
	public static void main(String[] args) {
		try {
			ThrowsEx2 te = new ThrowsEx2();
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("진짜 코드 호출 끝!");
	}
}

 

4) throw

- 예외를 강제로 발생시키려면 throw 키워드 이용 → 개발자가 직접 예외 클래스를 정의하여 만들 수 있음

public class ThrowEx {
	public static int calcSum(int n) throws Exception {
		// 프로그램이 throw 구문을 만나는 순간 예외를 즉시 발생시키고,
		// 해당 예외를 처리해줄 catch 블록이 있는지 검색
		if(n <= 0) {
			throw new Exception(); // 예외도 클래스로 정의되기 때문에 인스턴스 생성
		}
		int sum = 0;
		for(int i = 1; i <= n; i++) {
			sum += i;
		}
		return sum;
	}
	
	public static void main(String[] args) {
		try {
			int result1 = calcSum(100);
			System.out.println("1~100까지의 누적합 : " + result1);
			
			int result2 = calcSum(-100);
			System.out.println("1~100까지의 누적합 : " + result2);
		} catch (Exception e) {
			e.printStackTrace();
			System.err.println("매개값을 양수로 전달해주세요.");
		}
	}
}

throw 키워드가 없다면 위 코드에서 논리적으로는 문제가 없기 때문에 정상적으로 작동한다.

하지만, 반복문 사용 시 i 값에 음수가 들어간다면 반복문을 실행하지 않기 때문에 위 예시에서는 throw를 사용하여 강제로 예외처리를 해주는 것이 좋다.

객체 생성 시 throw 키워드를 붙여주면 위와 같이 강제로 설정한 예외처리를 해주는 것을 확인할 수 있다.

 

5) 사용자 정의 예외

- 자바 표준 API에서 제공하는 예외 클래스만으로 다양한 종류의 예외를 표현할 수 없는 문제가 발생

- 일반 예외로 선언할 경우 Exception 클래스 상속, 실행 예외로 선언할 경우 RuntimeException 클래스 상속

- 사용자 정의 예외 클래스의 이름은 Exception으로 끝나는 것이 좋음

// 잔고 불충분 예외
// 사용자 정의 예외 클래스 생성 시 Exception 클래스나 RuntimeException 클래스 상속
public class BalanceInsufficientException extends RuntimeException {
	// 일반적으로 사용자 정의 예외 클래스를 만들 때는 
	// 기본 생성자와 예외원인 메세지를 받는 생성자를 두 개 오버로딩하여 선언만 해줌
	public BalanceInsufficientException() {};
	
	public BalanceInsufficientException(String message) {
		super(message);
	}
}
// 음수값 입금 예외
public class DepositMinusMoneyException extends RuntimeException {
	public DepositMinusMoneyException() {}
	
	public DepositMinusMoneyException(String message) {
		super(message);
	}
}
public class Account {
	private long balance;
	
	public long getBalance() {
		return this.balance;
	}
	
	// 음수로 입금할 경우 강제 예외처리
	public void deposit(int money) throws DepositMinusMoneyException {
		if (money < 0) {
			throw new DepositMinusMoneyException("음수로 입금할 수 없습니다.");
		}
		this.balance += money;
	}
	
	// 인출 시 잔액이 부족한 경우 강제 예외처리
	public void withdraw(int money) throws BalanceInsufficientException {
		if(this.balance < money) {
			throw new BalanceInsufficientException("잔고가 부족합니다.");
		}
		this.balance -= money;
	}
}
public class MainClass {
	public static void main(String[] args) {
		Account acc = new Account();
		
		try {
			acc.deposit(100000);
			System.out.println("입금 후 잔액 : " + acc.getBalance() + "원");
			acc.withdraw(100000);
		} catch(BalanceInsufficientException e) {
			// 예외 클래스가 제공하는 getMessage() 메서드는 예외의 원인 메세지를 String 타입으로 리턴
			// 자바 표준 API에서 제공하는 다양한 예외클래스들은 각각의 예외 원인 메세지가 기본적으로 객체 안에 저장
			e.printStackTrace();
			// 생성자에서 제공해준 메세지 그대로 출력
			System.err.println(e.getMessage());
		} catch(DepositMinusMoneyException e) {
			e.printStackTrace();
			System.err.println(e.getMessage());
		}
		System.out.println("출금 후 잔액 : " + acc.getBalance() +"원");
	}
}

위와 같이 어떤 종류의 예외가 발생했는지 정확히 알 수 있기 때문에 유지보수성을 높일 수 있다.

'네트워크캠퍼스 > JAVA' 카테고리의 다른 글

자바 API  (0) 2024.01.18
자바의 예외처리전략  (0) 2024.01.18
인터페이스  (0) 2024.01.16
추상화  (0) 2024.01.15
final  (0) 2024.01.15

1. Shell Script

→ 실행할 명령어를 미리 파일에 작성해두고 매크로처럼 실행

1) 셔뱅을 이용하는 방법

ex) du -h ~ | tall -n 1 : 홈 디렉토리의 파일 사용량 출력

위와 같은 명령어는 직접 타이핑하기 불편함 쉘 스크립트로 작성해두고 짧게 실행하는 것이 효율적

#!/bin/bash : 셔뱅

#! 뒤에 오는 파일/경로를 실행매개로 간주하고 명령을 내린다는 의미 → 어디에서 해당 파일을 실행하든 /bin/bash를 통해 실행

위와 같이 해당 파일에 실행 권한을 준 후 ./파일명을 통해 실행하면 위와 같이 동일한 결과가 출력되는 것을 확인할 수 있다.

 

2) source 명령어를 이용하는 방법

위와 같이 source 명령어를 이용해서도 쉘 스크립트 실행이 가능하고,

source를 사용하면 셔뱅을 적지 않아도 현재 실행한 쉘을 이용해 스크립트를 실행한다.

위와 같이 "."를 사용하여 source 명령어를 대체할 수 있지만 가독성 문제로 source를 사용하여 실행한다.

 

3) source와 ./ 실행의 차이점

위와 같이 vim 에디터를 이용해 test1.sh 파일을 생성하고, poiuqwer라는 사전에 지정된 적 없는 리눅스 명령어를 실행한다.

alias 명령어로 별명을 등록할 수 있고, 해당 명령을 실행하면 poiuqwer은 ls -alF로 대체된다.

또한, source 명령어로 해당 파일을 실행해보면 ls -alF 명령을 수행한 결과가 잘 출력되는 것을 확인할 수 있다.

하지만, 위와 같이 . 명령어를 사용하여 실행하면 command not found 오류가 발생하는 것을 확인할 수 있다.

. 명령어로 실행 시 새로운 쉘을 하나 더 순간적으로 열게 되는데 이 때 새로운 쉘에는 alias 설정이 되어 있지 않기 때문에 해당 명령을 수행하지 못한다.

 

4) 쉘스크립트의 배치

→ 파일 관리 용이성을 위해 쉘스크립트를 모아두어야 함

먼저, ~/bin 디렉토리를 생성하고 해당 경로로 모든 쉘 스크립트를 옮긴다.

어떤 경로에서든 파일명만 적어도 실행되도록 만드는 것이 효율적이다. → 전역 경로 설정

검색 경로에 ~/bin을 추가하기 위해 ~/.profile 파일에 위와 같은 PATH를 추가해준다.

위 명령어를 실행하면 즉시 적용되고, 해당 작업을 수행한 계정은 어디서든 .sh 파일을 호출할 수 있다.

위와 같이 파일명만 입력해도 쉘스크립트에 작성해뒀던 명령어가 잘 실행되는 것을 확인할 수 있다.

 

5) 전역 경로 비활성화

shopt -u sourcepath 명령어를 사용하면 source 명령어 사용 시 해당 단축경로를 비활성화할 수 있다.

위와 같이 현재 경로에 test.sh 파일이 없기 때문에 source 명령어를 사용하여 실행하면 오류가 발생하는 것을 확인할 수 있다.

 

2. Shell Script 작성하기

1) 개행

- 실제 쉘 스크립트는 여러줄로 작성

- 여러 줄 명령어를 순차적으로 실행시킬 때 기본적으로 엔터키를 눌러 개행시키면 순차적으로 하나씩 실행

위와 같이 엔터로 개행시킨후 실행해보면 쉘스크립트에 작성했던 명령들이 잘 수행되는 것을 확인할 수 있다.

- 엔터키 개행 대신 ;로 대체 가능

위와 같이 ; 로 수정해도 같은 결과로 동작되는 것을 확인할 수 있다.

 

2) 개행 무시

\ (역슬래시)를 사용하여 여러줄을 한 줄에 쓴 것처럼 동작시킬 수있다.

위와 같이 개행이 있지만 echo "root" 명령을 한 결과가 출력되는 것을 확인할 수 있다.

 

3) 주석 : #

 

4) 변수 

변수명=저장할내역 : 변수 저장

$변수명 : 변수값 참조

기본적으로 위와 같이 변수를 저장하고 사용할 수 있다.

\$문자 : 일반문자 그대로 출력

\\$문자 : 백슬래시 문자(변수값) 출력

\\\$문자 : 백슬래시 일반문자 출력

 

5) quote : 따옴표

홑따옴표 : 변수값 포매팅 X

쌍따옴표 : 변수값 포매팅 O

위와 같이 홑따옴표는 일반 문자를 그대로 출력하고, 쌍따옴표는 해당 이름의 변수값이 출력되는 것을 확인할 수 있다.

만약 변수값 대입이 아닌 $name을 출력하고 싶다면 마찬가지로 \ (역슬래쉬)를 붙여주면 된다.

 

6) 명령어 치환

특정 명령어를 수행한 결과를 파일 이름 등으로 사용하고 싶은 경우 $(명령어) 로 이용 가능

위와 같이 $(명령어)를 이용해 변수를 생성하고 파일 생성 명령어 touch를 이용하는 쉘스크립트를 작성했다.

쉘스크립트를 실행해보면 위와 같이 touch $filename 명령이 잘 수행된 것을 확인할 수 있다.

 

7) 파라미터 전달

쉘 스크립트에서는 파라미터의 위치에 따라 $번호에 해당하는 변수에 대입된다. ($0부터 시작)

위와 같은 쉘스크립트를 작성해주고

위 파일을 실행해보면 위와 같이 파라미터 위치에 따라 0부터 순서대로 해당 변수에 대입되는 것을 확인할 수 있다.

위와 같이 전달된 인자의 개수가 몇 개인지는 $#를 사용하여 조회할 수 있다.

 

8) 제어문

① if문

- if [ 명령어 ]; then ~ else ~ fi

위와 같이 첫번째 파라미터 값이 bin이면 OK를, 아니면 NO를 출력하는 조건문을 작성했다.

※ if [ 명령어 ]; 입력 시 대괄호 안에 공백을 주의해야 한다.

※ 세미콜론이 있다면 then과 조건식이 같이 놓일 수 있지만 생략했다면 다음줄로 내려야 한다.

위와 같이 출력 결과가 잘 수행되는 것을 확인할 수 있다.

 

- 명령어의 종료 상태

$?를 사용하여 직전에 실행한 명령어의 상태를 조회할 수 있다.

기본적으로 명령어는 정상 종료 시 0을 가지지만, 에러 발생 시 0이 아닌 다른 번호를 가진다.

 

- if와 종료 상태

조건식을 작성하는 [ ] 역시 명령어이기 때문에 해당 조건식이 참이면 0, 거짓이면 0 이외의 숫자를 반환한다.

조건문 사용 시 반드시 [ ] 를 사용해야 하는 것은 아니고 위와 같이 참과 거짓을 판단할 수 있는 명령어라면 [ ] 없이도 사용이 가능하다.

 

- test 명령어로 [ ] 대신하기

위와 같은 조건문이 있을 때 [ ]를 test 명령어로 대체할 수 있다.

이처럼 [ ]를 사용했을 때와 같은 결과를 출력하는 것을 볼 수 있지만 [ ]가 가독성이 더 좋으므로 test 명령어를 잘 사용하지는 않는다.

 

- 문자열 비교

= 같다
!= 다르다
-n null이 아니다
-z null이다

위 스크립트에서는 첫 파라미터(filename)가 null이라면 default.dat을, 값이 있으면 filename을 출력해준다.

위와 같이 결과가 잘 출력되는 것을 확인할 수 있다.

 

- 정수 비교

-eq 같다
-ne 다르다
-lt 작다
-gt 크다
-le 작거나 같다
-ge 크거나 같다

위와 같이 기본적으로 max 변수에는 첫번째 파라미터 값이 들어 있고, -lt로 두 값을 비교하여 두번째 파라미터 값이 더크다면 max를 두 번째 파라미터로 넣어주는 조건문을 작성했다.

위와 같이 조건에 따라 결과가 잘 수행되는 것을 확인할 수 있다.

 

- 파일 비교

-e 파일 존재
-d 파일 존재하고, 디렉터리임
-h / -L 파일 존재하고, 심볼릭링크임
-f 파일 존재하고, 일반파일임
-r 파일 존재하고, 읽기 권한이 있음
-w 파일 존재하고, 쓰기 권한이 있음
-x 파일 존재하고, 실행 권한이 있음
file1 -nt file2 file1이 file2보다 최근에 변경됨
file1 -ot file2 file2가 file1보다 최근에 변경됨

 

- 연산자 결합

-a and 연산
-o or 연산
! not 연산

연산자 결합 역시 ( )를 이용하여 우선순위를 다르게 지정할 수도 있다.

 

- &&와 ||를 활용한 명령어 순차평가

&& 왼쪽이 참이어야 오른쪽을 실행
|| 왼쪽이 거짓일 때 오른쪽을 실행

위와 같이 && 연산자를 이용하여 iftest.sh 파일이 존재하기 때문에 ifex.sh의 내용을 잘 출력하는 것을 볼 수 있다.

위와 같이 || 연산자를 이용하여 ifor.sh 파일이 존재하지 않기 때문에 ifor.sh 파일을 잘 생성해주는 것을 볼 수 있다.

 

② for문

- for 변수 in 리스트 ~ do ~ done

위와 같이 반복문을 작성할 수 있고 name이라는 변수에 값을 차례로 넣어 출력하는 것을 확인할 수 있다.

head : 맨 위부터, tail : 맨 뒤부터

위와 같이 경로에 따라서는 와일드카드를 사용할 수도 있고, 현재 경로의 모든 .sh 파일의 내용을 세번째 줄까지 출력해주는 것을 확인할 수 있다.

 

- seq 1 5 : 1~5 출력

for을 쓰지 않고 seq을 이용하여 더 간단하게 반복문을 사용할 수 있다.

위와 같이 seq을 이용하여 현재 경로에 5개의 파일을 생성하라는 코드를 작성할 수 있다.

실행 후 확인해보면 위와 같이 5개의 파일이 잘 생성된 것을 확인할 수 있다.

 

- 커맨드라인으로 보낸 인자 일괄처리

"$@"를 in 우변에 사용하면 받은 파라미터를 전부 반복문 타겟으로 잡는다.

위 코드는 paratest.sh를 실행하며 넘긴 모든 인자를 $@에 받아 처리해준다는 의미이다.

이와 같이 파라미터 값들이 다 넘겨지고 출력되는 것을 확인할 수 있다.

 

③ case문 : if문보다 강한 분기처리

case 문자열 in 패턴1) ~ ;; 패턴2) ~ ;; esac

case문을 사용하여 첫번째 파라미터 값이 start나 stop인 경우에만 checked가 출력되도록 하는 코드를 작성했다.

위와 같이 분기문이 잘 처리되어 결과가 출력되는 것을 확인할 수 있다.

 

④ while문 : 조건이 거짓이 될 때까지 반복

i 값이 10보다 작은 경우 계속 반복하는 while문을 작성했다.

실행해보면 위와 같이 반복문이 수행된 결과가 잘 출력되는 것을 확인할 수 있다.

 

⑤ until문 : while문과 반대로 조건이 거짓인 동안만 실행

위와 같이 until은 while과 사용법이 동일하게 수행된다.

 

⑥ C언어식 반복문

위와 같이 C언어식으로 반복문을 작성하는 것도 가능하다.

이 때 괄호는 겹쳐야 하고, 겹친 괄호 내에서는 변수에 $를 붙이지 않는다는 것을 주의해야 한다.

실행해보면 위와 같이 반복문이 잘 수행되는 것을 확인할 수 있다.

 

9) 셀 내부에서 함수 활용하기

function 함수명 (){
	실행문;
}

function 함수명 {
	실행문;
}

함수명(){
	실행문;
}

위의 세 가지 방식으로 쉘 내부에서 함수를 활용할 수 있다. 주로 세번째 방식을 사용한다.

date 결과와 디렉터리의 용량을 보여주는 du 명령어를 수행한 결과를 마지막 1줄만 출력하라는 homesize() 함수를 선언했고, 선언 후 homesize 함수를 호출해준다.

※ 쉘 함수는 반드시 호출구문 위쪽에 위치해야 한다.

위와 같이 수행 결과가 잘 출력되는 것을 확인할 수 있다.

또한, 종료코드는 return 상태코드 형식으로 전달하면 된다.

'네트워크캠퍼스 > LINUX' 카테고리의 다른 글

크론잡과 크론탭  (0) 2024.02.21
Shell Script 실습  (0) 2024.02.20
Devops와 SE를 위한 리눅스 커널 이야기  (0) 2024.01.17
프로세스  (0) 2024.01.16
텍스트에디터  (0) 2024.01.15

1. 시스템 구성정보 확인하기

uname 커널 버전 관련 정보 조회
dmesg 디버깅 메세지 체크
dmidecode bios, system, processor
dmidecode memory
df 디스크 파티션 타입 정보 조회
smartctl 디스크의 물리적 정보 조회
lspci 네트워크 정보 조회
ethtool 네트워크 카드의 세팅 정보 조회

 

 

2. top 명령어를 통해 프로세스 정보 조회하기

1) top 명령어

- top : 시스템 정보 확인 가능

- 구동된 시간, 로그인된 사용자 숫자, 실행 중인 프로세스의 개수, cpu, memory, swap 메모리의 사용량

- 우선순위, 조정 우선순위, 가상배정 메모리, 실제배정 메모리, 공통사용영역 등

 

2) VIRT, RES, SHR

- VIRT : VIRT + RES + SHR를 모두 포함한 영역, 각 Task에 할당된 메모리 전체

- RES : 할당된 메모리 중 실제로 사용되고 있는 물리 메모리의 양

- SHR : 다른 프로세스와 공유 중인 메모리의 양

 

 

3. 가상 메모리 배정과 memory commit

1) memory  commit : 메모리에 쓰기 작업이 들어가야 진짜 할당이 되도록 하는 것

- 메모리는 동적으로 배정

- 처음부터 할당 시 비효율적 → 최대 배분 상한선만 정해놓고 실제 쓰기 작업 시 진짜 할당 진행

 

2) 가상 메모리 배정의 이유

- fork() 프로세스 콜 : 현재 실행 중인 프로세스와 똑같은 프로세스를 하나 더 할당 → 실질적으로 사용하는 영역만 배정

- COW(Copy On Write) : 복사된 메모리 영역에 실제 쓰기 작업이 발생한 후에 실질적인 메모리 할당 시작

 

 

4. 프로세스의 상태 변화

- SHR의 S 항목 : 프로세스 상태 확인 가능

D 디스크 혹은 네트워크 I/O를 위해 대기하는 프로세스
R 실행 중인 프로세스
S 요청한 리소스를 즉시 사용할 수 있는 상태의 프로세스
T 프로세스의 시스템콜을 추적하고 있는 상태
Z 부모 프로세스가 죽으 자식 프로세스
Zombie 프로세스라고 함

 

 

5. 프로세스의 우선순위

→ PR과 NI 항목을 통해 조절

- NI(Nice value) : 우선순위 → 숫자가 낮을수록 높은 값

- Run Queue : 우선순위별로 프로세스 연결

- Run Queue 상태에서 NI를 호출해 PR을 올리면 호출 우선순위가 올라가 실행될 확률이 높아지는 형식

 

 

6. Load Average

1) Load Average

- R/D 상태인 프로세스의 1, 5, 15분마다의 평균 개수

- 얼마나 많은 프로세스가 실행중이거나 대기중인지 나타내는 수치

- 값이 높을수록 실행/대기중인 프로세스가 많음을 의미

- 하나의 코어에 2개의 프로세스 vs 두 개의 코어에 2개의 프로세스

   → load average값은 같으나 가용자원 측면에서는 다르게 인식

위와 같이 uptime 명령어를 이용하여 load average를 계산할 수 있다.

 

2) CPU Bound vs I/O Bound

→ 부하의 종류에 따라 처리하는 방법론이 달라지기 때문에 구분하는 것이 중요

- CPU Bound : CPU의 자원(ex.연산)을 많이 필요로 하는 프로세스

- I/O Bound : 많은 I/O 자원(ex.디스크 쓰기)을 필요로 하는 프로세스

 

3) vmstat : 부하 원인 확인 (r, b 값이 중요)

- r : 현재 실행되기를 기다리거나 실행되고 있는 프로세스의 개수 → r이 높으면 CPU Bound일 확률이 높음

- b : I/O를 위해 대기열에 있는 프로세스의 개수 → b가 높으면 I/O Bound일 확률이 높음

 

4) Load Average가시스템에 끼치는 영향

Load Average 값이 같다고 해도 실제 시스템에 미치는 영향은 부하의 원인에 따라 달라짐

→ CPU나 I/O 자원에 대한 경합 정도에 따라 실제롤 딜레이되는 시간이 다를 수 있음

 

5) OS 버전과 Load Average

커널 차이로 인해 버그건 아니면 실제로 건 차이가 발생할 수도 있음

'네트워크캠퍼스 > LINUX' 카테고리의 다른 글

Shell Script 실습  (0) 2024.02.20
Shell Script  (0) 2024.01.17
프로세스  (0) 2024.01.16
텍스트에디터  (0) 2024.01.15
파일시스템  (0) 2024.01.11

1. 인터페이스(Interface)

- 객체의 사용 방법을 정의한 타입으로 객체의 교환성을 높여줌 → 다형성을 구성하는 매우 중요한 역할

- interface 키워드로 선언, 클래스 이름 뒤에 implements 키워드로 구현

- 상수와 메서드만을 구성멤버로 가짐

- 데이터를 저장할 수 없기 때문에 데이터를 저장할 객체 또는 정적 변수 선언 불가능

- 인터페이스 선언된 변수는 public static final을 생략하더라도 자동으로 붙음

- 인터페이스의 메서드를 추상메서드 형식으로 선언하면 abstract를 생략하더라도 자동으로 붙음

- extends 키워드를 사용하여 인터페이스 간의 상속 구현 가능, 다중 상속 표현 가능

 

public interface RemoteController {
	// 최대 배터리량, 최소 배터리량을 상수로 지정
	int MAX_BATTERY = 100;
	int MIN_BATTERY = 0;
	
	// 리모콘이 가져야 하는 필수 기능에 대해서만 정의
	public void turnOn();
	public void turnOff();
	public void showStatus();
}
public class TVRemoteController implements RemoteController {
	private final int inch;
	private int channel;
	
	public TVRemoteController(int inch) {
		this.inch = inch;
		this.channel = 1;
	}
	
	@Override
	public void turnOn() {
		System.out.println("TV를 켭니다.");
	}

	@Override
	public void turnOff() {
		System.out.println("TV를 끕니다.");
	}

	@Override
	public void showStatus() {
		System.out.println("화면크기 : " + this.inch);
		System.out.println("채널 : " + this.channel);
	}
	
	public void setChannelDown() {
		// 1번까지만 채널이 있음
		if(this.channel - 1 < 1) {
			this.channel = 1;
		} else {
			this.channel--;
		}
	}
	
	public void setChannelUp() {
		this.channel++;
	}
}
public class RobotCleanerRemoteController implements RemoteController{
	public String modelName;
	public String price;
	
	// 로봇청소기 생성자
	public RobotCleanerRemoteController(String modelName, String price) {
		this.modelName = modelName;
		this.price = price;
	}
	
	@Override
	public void turnOn() {
		System.out.println("로봇청소기를 켭니다.");
	}

	@Override
	public void turnOff() {
		System.out.println("로봇청소기를 끕니다.");
	}

	// 로봇청소기에 맞는 정보조회
	@Override
	public void showStatus() {
		System.out.println("모델명 : " + this.modelName);
		System.out.println("가격 : " + this.price);
	}
}
public class MainClass {

	public static void main(String[] args) {
		// 인터페이스 역시 구현체를 다형성 형식으로 받을 수 있음
		RemoteController rc = new TVRemoteController(50);
		//RemoteController rc = new RobotCleanerRemoteController("imou", "280000");
	
		rc.turnOn();
		rc.showStatus();
		rc.turnOff();
	}
}

위와 같이 인터페이스를 이용하여 해당하는 타입의 객체를 생성할 수 있고, 해당 객체에서 구현한 함수에 따라 내용이 다르게 출력, 즉 다형성이 활용되는 것을 확인할 수 있다.

 

 

2. has - a 관계

- 상속의 전제는 is - a 관계

- 현실에서는 is - a 관계가 아니더라도 다른 객체의 기능을 쓸 수 있는 경우가 많음

  → has - a 관계로 설정해 객체 간 사용 구현

※ 현업에서는 상속보다 합성을 많이 이용한다. 

public class Gun {
	private int bullet;
	private String modelName;
	private String gunNumber;
	
	public Gun(String modelName, String gunNumber) {
		this.bullet = 5;
		this.modelName = modelName;
		this.gunNumber = gunNumber;
	}
	
	public void shoot() {
		if(this.bullet > 0) {
			this.bullet--;
			System.out.println("총을 쐈습니다.");
		} else {
			System.out.println("방아쇠를 당겼지만 총알이 없습니다.");
		}
	}
	
	public void reload() {
		this.bullet = 5;
	}
}
public class Police {
	// 상속 없이 Gun 기능을 사용하기 위해 멤버변수도 Gun을 가짐
	private Gun gun;
	private String name;
	private int height;
	
	public Police(Gun gun, String name, int height) {
		this.gun = gun;
		this.name = name;
		this.height = height;
	}
	
	public void shoot() {
		this.gun.shoot();
	}
	
	public void showStatus() {
		System.out.println("소유총기 : " + this.gun);
		System.out.println("총기이름 : " + this.name);
		System.out.println("총기크기 : " + this.height);
	}
}
public class MainClass {
	public static void main(String[] args) {
		// Gun을 new 키워드로 생성해야 Police 생성자에 전달 가능
		Gun gun = new Gun("M-16", "369486");
		
		// Gun을 사전에 생성하지 않으면 넘길 방법이 없음
		Police police = new Police(gun, "나경찰", 180);

		police.shoot();
		police.shoot();
		police.shoot();
		police.shoot();
		police.shoot();
		police.shoot();
	}
}

'네트워크캠퍼스 > JAVA' 카테고리의 다른 글

자바의 예외처리전략  (0) 2024.01.18
예외처리  (0) 2024.01.17
추상화  (0) 2024.01.15
final  (0) 2024.01.15
싱글톤 패턴  (0) 2024.01.15

1. 프로세스

1) 프로그램 실행 시

① 디스크에 저장된 프로그램을 커널이 메모리에 올린다.

② 메모리에 올라간 프로그램을 CPU가 실행한다.

 

2) 프로세스 

→ 메모리 위에 올라가서 실행 중인 프로그램, 각각 pid라는 고유 번호를 할당받아 관리

 

 

2. ps 명령어 

1) $ ps : 프로세스 조회

bash는 실행하자마자 열리고, ps는 ps를 입력하여 추가된 것으로 각각 고유의 PID가 배정되는 것을 확인할 수 있다.

위와 같이 특정 명령이 실행되면 프로세스로 전환되고, 실행이 끝나면 프로세스 목록에서 사라진다.

 

2) $ ps x : 백그라운드에서 돌아가는 프로세스 조회

※ 데몬 프로세스 : 터미널과 무관하게 돌아가는 프로세스 → 항상 켜져 있어야 하는 속성의 프로세스

 

3) $ ps a : 시스템의 모든 프로세스 조회

위와 같이 시스템 내부에서 동작 중인 모든 프로세스를 조회할 수 있고, 조회된 PID를 이용해 여러가지 작업을 할 수 있다.

 

4) ps 옵션 : "-"를 쓰지 않는다.

x 사용자의 프로세스 전체 출력
ux 사용자의 프로세스 상세히 출력
ax  모든 사용자의 프로세스 출력
aux 모든 사용자의 프로세스를 상세히 출력
auxww aux 결과가 화면에 잘리지 않도록 출력

 

5) $ ps ux | less : 모든 프로세스를 페이지별로 출력 (종료:q)

 

6) $ top : 메모리 구조 살펴보기

위와 같이 메모리 사용량 등을 실시간으로 보여준다.

 

※ 참고

- VIRT : 가상 할당량 → RES의 최대치

- RES : 실질 할당량

- SHR : 공유 메모리 영역

 

 

3. 잡

1) 잡 (job)

- 프로세스 : 커널 입장에서 실행

- 잡 : 셸에서 실행하고 있는 단위

위와 같이 man bash를 ctrl+z로 강제종료시켰을 때 job이 하나 생성된 것을 확인할 수 있다.

실행 중인 프로그램이 종료되면 프로세스 목록에서 사라지기 때문에 잡에서는 확인할 수 없다.

 

잡 목록에서 PID를 조회하고자 할 때 -l 옵션을 사용한다.

 

2) $ fg : 잡의 포그라운드 전환

foreground : 사용자의 입력을 받을 수 있는 상태

→ 정지 상태에 있는 bash 메뉴얼을 다시 확인하기 위해서는 포그라운드로 되돌려야 함

위와 같이 fg %번호 명령어를 이용하여 포그라운드로 전환할 수 있고, 잡이 하나인 경우에는 fg만 입력해도 된다.

 

3) $ bg : 잡의 백그라운드 전환

background : 잡을 종료하지 않은 채 셸로 전환

위와 같이 포그라운드 상태에서는 sleep이 끝날 때까지 아무것도 하지 못한다.

백그라운드 상태에서는 sleep이 끝나지 않고도 제어권을 되찾아오고, 잡은 백그라운드에서 60초를 수행할 때까지 돌아가는 병렬적인 작업을 수행하게 된다.

포그라운드와 마찬가지로 bg %번호 명령어를 이용하여 백그라운드로 전환할 수 있고, 잡이 하나인 경우에는 bg만 입력해도 된다.

 

4) 잡과 프로세스의 종료

① 잡의 종료 

- 포그라운드 : ctrl + c

- 백그라운드 : kill %잡번호

위와 같이 kill 명령어를 이용하여 백그라운드에 있는 잡을 종료시킬 수 있다.

 

② 프로세스의 종료 : kill PID번호

위와 같이 kill PID번호 명령어를 입력하여 프로세스를 종료할 수 있다.

프로세스는 현재 로그인중인 유저가 실행한 프로세스만 삭제할 수 있고, 루트 계정만 다른 사용자의 프로세스를 강제로 종료할 수 있다.

 

5) kill : 프로세스에 특정한 신호를 보내는 명령어

SIGHUP, 1 터미널 제어 시 또는 제어 프로세스 종료 시 감지
SIGUNT, 2 키보드에서 보내는 인터럽트 신호
SIGQUIT, 3  키보드에서 보낸 종료 신호
SIGABRT, 6 abort(3)로 받은 신호 무시
SIGKILL, 9 kill 신호
SIGTERM, 15 중단 신호
SIGCONT, 19, 18, 25 중지된 프로세스 재개
SIGSTOP, 17, 19, 23 프로세스 중지

 

ex) PID가 10023인 프로세스를 중지시킬 경우

$ kill 10023

$ kill -9 10023

$ kill -SIGKILL 10023

위와 같이 해당 프로세스가 잘 종료된 것을 확인할 수 있다.

 

6) killall : 프로세스명으로 종료시키는 명령어

$ killall -9 프로세스명

위와 같이 killall 명령어를 이용하면 특정 pid가 아닌 같은 이름을 가진 모든 프로세스를 동시에 종료할 수 있다.

'네트워크캠퍼스 > LINUX' 카테고리의 다른 글

Shell Script  (0) 2024.01.17
Devops와 SE를 위한 리눅스 커널 이야기  (0) 2024.01.17
텍스트에디터  (0) 2024.01.15
파일시스템  (0) 2024.01.11
프롬프트  (0) 2024.01.10

1. abstract 키워드

- 클래스와 메서드에 적용

- 추상 클래스는 실제 클래스들의 멤버변수와 메서드들의 이름을 통일할 목적으로 사용

- 추상 메서드가 있는 클래스는 반드시 추상 클래스여야 함

- 추상클래스에 반드시 추상 메서드만 선언할 필요는 없고 일반 메서드도 선언할 수 있음

 

- abstract 키워드를 사용하지 않았을 때 문제가 되는 예시

public class PopupStore {
	public void orderApple() {
		System.out.println("착즙 사과주스를 내줍니다.");
	}
	
	public void orderOrange() {
		System.out.println("착즙 오렌지주스를 내줍니다.");
	}
	
	public void orderGrape() {
		System.out.println("착즙 포도주스를 내줍니다.");
	}
}
public class Store extends PopupStore{
	
	@Override
	public void orderApple() {
		System.out.println("착즙 사과주스를 팝니다. 가격은 20000원");
	}
	
	@Override
	public void orderOrange() {
		System.out.println("착즙 오렌지주스를 팝니다. 가격은 24000원");
	}

	// 실수로 포도주스 가격 업데이트를 누락
}
public class MainClass {
	public static void main(String[] args) {
		// 2가지 문제점 체크
		// 1. 정식매장이 존재하는데 팝업스토어 생성 가능
		PopupStore ps = new PopupStore();
		
		// 2. 팝업스토어 클래스 내부에서 오버라이딩이 필수인 메서드가 누락될 수도 있음
		PopupStore s = new Store();
		s.orderApple();
		s.orderOrange();
		s.orderGrape();
	}
}

위 예시와 같이 abstract 키워드를 이용하지 않았을 때 위의 2가지 문제점이 발생할 수 있다.

포도주스를 오버라이딩 하지 않으면 경고를 띄우게 해야 하지 않을까? → 추상화 적용

PopupStore(부모타입)으로 Store(자식) 객체 형변환이 이루어짐

부모타입으로 선언한 객체에서도 자식이 오버라이딩했던 함수의 내용이 출력되는 것을 확인할 수 있다. (Promotion-자동형변환)

 

1) abstract class

- new 키워드를 이용하여 객체 생성 불가

- 오직 상속을 통해서 자식클래스로 구체화 시켜야 함

- new를 사용하여 직접 생성자를 호출할 수는 없지만 자식 객체가 생성될 때 super()를 호출하여 추상 클래스 객체를 생성하므로 추상 클래스도 생성자가 반드시 있어야 함

- 추상 메서드를 하나 이상 보유하고 있다면 해당 클래스는 추상 클래스 → 무조건 abstract 키워드를 붙여줘야 함

 

2) abstract method

- 추상 클래스 내에서만 선언 가능

- 추상 메서드는 메서드의 선언부만 있고, 메서드 실행 내용이 들어가는 중괄호 {}가 없는 메서드를 말함

- 추상 클래스 설계 시 자식 클래스가 반드시 실행 내용을 채우도록 강요하고 싶은 메서드가 있을 경우 해당 메서드를 추상 메서드로 선언

- 자식 클래스에서 반드시 부모 추상 클래스의 추상 메서드를 재정의하여 실행 내용을 작성해야 함

 

3) abstract 사용 예제

public abstract class PopupStore {
	// 1. 메서드에 abstract를 붙이면 해당 메서드는 추상메서드가 되고, 이 메서드는 반드시 오버라이딩 해야 함
	// 2. 추상메서드는 상속을 목적으로 선언한 메서드
	//    실행을 목적으로 선언된 메서드가 아니므로 메서드의 몸체({}) 부분이 없고 선언 마무리도 ;로 함
	// 3. 일반 클래스에는 추상 메서드 선언 불가
	//    추상메서드가 하나 이상 존재하면 무조건 추상클래스로 선언
	// 4. 추상클래스 내부에서는 추상메서드가 하나 이상 존재한다면 일반메서드 선언도 여전히 가능
	public abstract void orderApple();
	public abstract void orderOrange();
	public abstract void orderGrape();
	
	public void refund() {
		System.out.println("제품에 문제가 있어서 환불합니다.");
	}
}
public class Store extends PopupStore {
	@Override
	public void orderApple() {
		System.out.println("착즙 사과주스를 20000원에 팝니다.");
	}

	@Override
	public void orderOrange() {
		System.out.println("착즙 오렌지주스 24000원에 팝니다.");
	}

	@Override
	public void orderGrape() {
		System.out.println("가격은 못 정했습니다.");
	}
}
public class ConvenientStore extends PopupStore {
	@Override
	public void orderApple() {
		System.out.println("가당 사과주스 4000에 팝니다.");
	}

	@Override
	public void orderOrange() {
		System.out.println("가당 오렌지주스 5000에 팝니다.");
	}

	@Override
	public void orderGrape() {
		System.out.println("가당 포도주스 3500에 팝니다.");
	}
}
public class MainClass {

	public static void main(String[] args) {
		// PopupStore 클래스는 직접 객체 생성 불가능
		// PopupStore ps = new PopupStore();
		
		// PopupStore s = new Store();
		PopupStore s = new ConvenientStore();
		// 객체 종류에 따라 실행구문이 다르게 정의되었지만 명세는 같은 메서드
		s.orderApple();
		s.orderOrange();
		s.orderGrape();
		
		// 어떤 객체가 와도 공통적으로 실행되는 메서드
		s.refund();
	}
}

 

 

※ 템플릿 메서드 패턴 활용

public abstract class Lottery {
	// 템플릿 메서드 패턴은 큰 틀에서 호출구문은 구현메서드(실행문이 있는 메서드)로 정의해놓고
	// 구현메서드가 호출하는 추상메서드들은 상속 후에 특징을 정하도록 만들어서
	// 호출 순서는 그대로 가져갈 수 있도록 하되, 사용자가 특징만 정의하도록 하는 디자인 패턴
	
	// 구현메서드는 큰 틀은 같지만, 세부사항이 달라질 수 있는 내용을 먼저 작성
	public void lotteryCycle() {
		// 1. 어디서 사는가?
		buyLottery();
		// 2. 당첨 여부 확인
		checkWinLottery();
		// 3. 당첨 시 수령
		getLotteryMoney();
	}
	
	// 세부사항은 상속받은 주체가 무엇인지에 따라 다르게 정의할 수 있도록
	// 추상메서드만 정의해놓고, 추가적인 작업은 하지 않음
	abstract void buyLottery();
	abstract void checkWinLottery();
	abstract void getLotteryMoney();
}
public class KoreanLotto extends Lottery {
	@Override
	public void buyLottery() {
		System.out.println("한 게임에 천원짜리 로또를 삽니다.");
	}

	@Override
	public void checkWinLottery() {
		System.out.println("45C6의 확률을 뚫고 1등에 당첨되었습니다.");
	}

	@Override
	public void getLotteryMoney() {
		System.out.println("1등 상금으로 대략 수십억을 받았습니다.");
	}
}
public class StatesSuperball extends Lottery {
	@Override
	public void buyLottery() {
		System.out.println("미국 가서 슈퍼볼 복권을 삽니다.");
	}

	@Override
	public void checkWinLottery() {
		System.out.println("69C5 * 26C1 분의 1의 확률로 당첨되었습니다.");
	}

	@Override
	public void getLotteryMoney() {
		System.out.println("당첨 금액은 최소 수천억원입니다.");
	}
}
public class MainClass {
	public static void main(String[] args) {
		//Lottery lottery = new KoreanLotto();
		Lottery lottery = new StatesSuperball();
		lottery.lotteryCycle();
	}
}

'네트워크캠퍼스 > JAVA' 카테고리의 다른 글

예외처리  (0) 2024.01.17
인터페이스  (0) 2024.01.16
final  (0) 2024.01.15
싱글톤 패턴  (0) 2024.01.15
사용제한자  (0) 2024.01.12

1. final 키워드

- 클래스, 메서드, 변수에 적용

- abstract와 동시에 사용 불가

- 상속 및 재정의 불가, 즉 서브클래스를 가질 수 없음

- final 변수 값 변경 불가

 

1) final class

- 클래스 선언 시 final을 사용하면 그 클래스는 상속 불가능

- 자식클래스를 가질 수 없고, 오직 외부에서 객체 생성을 통해서만 사용 가능

- final 클래스의 대표적인 예가 String 클래스

  → 사용자가 임의로 String 클래스를 상속받아 메서드를 재정의하는 것을 방지하기 위함

위와 같이 final 키워드를 가진 클래스는 상속이 불가하다는 것을 확인할 수 있다.

 

2) final method

- 메서드 선언 시 final을 사용하면 그 메서드는 상속 시 오버라이딩 불가

- 원본 메서드를 변형할 수 없도록 하기 위함

- 호출은 가능

위와 같이 상속은 가능하지만 final 키워드가 붙은 method2는 자식 측에서 재정의할 수 없는 것을 확인할 수 있다.

 

3) final 변수

- 한 번 값을 할당하면 그 값을 변경할 수 없음

- 선언 시 초기화하는 방법과 생성자를 통하여 초기화하는 방법이 있는데, 초기화하지 않고 남겨두면 컴파일 에러 발생

public class Person {
	// final 변수는 단 한 번 초기화될 수 있고 이후에는 변경이 불가능함
	// 선언 시에 아예 직접 초기화를 해주거나 생성자에서 초기화를 해줘야 함
	public final String nationality = "대한민국"; // 선언부 초기화
	public final String name; // 이렇게 선언부에서 초기화를 안하면 생성자 초기화
	public int age; // final이 안 붙은 멤버변수는 초기화 의무 X
	
	public Person(String name) {
		this.name = name;
	}
}
public class MainClass {

	public static void main(String[] args) {
		Person kim = new Person("김자바");
		//kim.nationality = "일본"; // final 변수 변경 불가
		//kim.name = "채자바";       // final 변수 변경 불가
		kim.age = 25;
		
		System.out.println("국적: " + kim.nationality); // public이므로 호출 가능
		System.out.println("이름: " + kim.name); // public이므로 호출 가능
		System.out.println("나이: " + kim.age); // public이므로 호출 가능
	}
}

위와 같이 final 키워드로 가장 처음 생성했던 값이 출력되는 것을 확인할 수 있다.

즉, final 키워드를 이용하여 변수를 생성하면 변수의 값은 변경할 수 없지만 호출하는 것에는 문제가 없다.

 

- final 배열 예시

public class Collector {
	// 참조형 변수를 가진 경우 변수 자체의 주는 final이지만,
	// 참조형 변수의 내부자료까지 바뀌지 않음을 보장하지 않음
	public final String[] stickers = {"피카츄", "꼬부기", "미뇽"};
}
import java.util.Arrays;

public class MainClass2 {
	public static void main(String[] args) {
		Collector c1 = new Collector();
		System.out.println(Arrays.toString(c1.stickers));
		
		c1.stickers[0] = "파이리";
		System.out.println(Arrays.toString(c1.stickers));
	}
}

final 키워드로 생성한 c1 객체는 해당 배열이 있는 주소를 변경하는 것이 불가능할 뿐이고,

해당 배열이 가리키고 있는 참조형 변수의 내부자료까지는 변경 방지를 보장해주지 않는다.

따라서 위와 같이 배열 내부 요소가 변경된 값이 오류 없이 출력되는 것을 확인할 수 있다.

 

4) 상수(static final)

- 상수 : 불변의 값을 저장하는 필드

- 상수는 객체마다 저장할 필요가 없는 공용성을 가져야 하고,

  여러가지 값으로 초기화될 수 없기 때문에 static과 final 제한자를 동시에 붙여 선언해야 함

- 상수 이름은 모두 대문자로 작성하는 것이 관례, 연결된 단어라면 "_"로 연결

// 상수를 선언할 때는 상수 집합을 만드는 목적으로 아래와 같이 클래스 선언
public class CountrySizes {
	// 상수는 보통 public으로 풀고 사용 (값 변경이 불가능하고, 공용성을 띔)
	public final static int KOREA_SIZE = 100431;
	public final static int STATES_SIZE = 9833519;
	public final static int THAILAND_SIZE = 513120;
}
public class MainClass {
	public static void main(String[] args) {
		// 상수만 모아둔 클래스 특성 상 클래스명이 곧 집합을 대표하는 이름이 됨
		System.out.println(CountrySizes.KOREA_SIZE);
		System.out.println(CountrySizes.STATES_SIZE);
		System.out.println(CountrySizes.THAILAND_SIZE);
	
		// 이를 잘 사용하는 예시는 자바의 Math 클래스가 있음
		System.out.println(Math.PI);
		System.out.println(Math.E);
	}
}

위와 같이 잘 출력되는 것을 확인할 수 있다.

'네트워크캠퍼스 > JAVA' 카테고리의 다른 글

인터페이스  (0) 2024.01.16
추상화  (0) 2024.01.15
싱글톤 패턴  (0) 2024.01.15
사용제한자  (0) 2024.01.12
매개변수의 다형성  (0) 2024.01.11

+ Recent posts