※ 객체지향으로 개발해야 하는 이유

- 개발 과정에서 자주 발생하는 문제를 객체지향 프로그래밍이라는 방법론으로 해결하기 위해 사용

- 변수와 함수를 밀접하게 하나의 클래스 소속으로 만들었기 때문에 관리가 쉽고 테스트가 용이함

 

1. 클래스와 상속

1) 상속과 다형성

- 상속은 extends 키워드 사용

- 상속을 전제로 부모타입 변수에는 부모타입이나 자식타입의 변수를 모두 대입 가능

- 부모타입으로는 다형성적으로 대입된 객체가 자식타입이어도 부모측 자원만 호출 가능

public static void main(String[] args) {
    Parent parent = new Parent(); // 부모타입 변수에 부모타입 객체 대입
    Parent parentTypeChild = new Child(); // 부모타입 변수에 자식타입 객체 대입, 다형성
    Child child = new Child();

    parent.process();
    System.out.println("--------------");
    parentTypeChild.process(); 
    //parentTypeChild.childProcess(); // Child 타입객체 내부에 선언된 요소는 Parent로 호출 불가
    System.out.println("--------------");
    child.process();
    child.childProcess();
}

 

 

2) 메서드 오버라이딩 (재정의)

- 부모클래스에서 상속받은 메서드를 자식에서 재정의하는 것

- 메서드의 시그니처(리턴타입, 메서드명, 요구하는 파라미터)는 같아야 함

- 오버라이딩이 정의되었다면 부모 타입으로도 자식 측의 재정의 메서드 호출 가능

public static void main(String[] args) {
    Parent parent = new Parent();
    Parent parentTypeChild = new Child(); // 다형성

    parent.process();
    parentTypeChild.process(); // 오버라이딩이 전제된 메서드는 부모타입으로도 자식측 메서드 자동 호출
}

 

 

3) 메서드 오버로딩 (중복정의)

- 메서드명이 같아도 요구 파라미터의 개수나 타입이 다르면 허용

- 리턴자료만 다른 것은 허용 불가

// 메서드 오버로딩 요건
// 1. 메서드 이름을 중복하여 여러 개 선언
// 2. 단, 선언된 같은 이름의 메서드 간 요구 파라미터의 개수나 타입은 달라야 함
// 3. 리턴타입의 동일여부는 오버로딩에 영향을 주지 않음
public int add(int num1, int num2) {
    return num1 + num2;
}

// return 타입만 다르게 오버로딩 하는 것은 불가능
// public long add(int num1, int num2) {
//   	return num1 + num2;
//}

public double add(double num1, double num2) {
    return num1 + num2;
}

public long add(long num1, long num2) {
    return num1 + num2;
}

 

 

4) 상속 관련 시 주의사항

- 상속 시 메서드와 필드를 모두 재사용할 수는 있지만 기본적으로는 필드 재사용을 전제로 해야 함

- 메서드 재사용을 위해 상속을 쓴다면 전략 패턴 구성 (Composite) 활용

☆ 리스코프 치환 원칙 : 부모타입으로 할 수 있는 일은 자식타입으로도 할 수 있어야 함

 

 

2. 추상 클래스와 인터페이스

1) 추상클래스

- abstract 키워드 활용

- 일반적으로 인스턴스를 생성할 수 없음 (생성자에서 추상메서드에 대한 오버라이딩을 직접 해주면 가능)

- 일반적으로 하나 이상의 추상 메서드 포함, 해당 추상 메서드를 상속하며 오버라이딩해야만 인스턴스 생성

public abstract class AbstractClass {
	public void implementedMethod() {
		System.out.println("AbstractClass 내부에서 직접 구현된 메서드");
		this.abstractMethod(); // 추후 구현될 템플릿 메서드
	}
	
	abstract public void abstractMethod();
};
public class ExtendedClass extends AbstractClass {
	@Override
	public void abstractMethod() {
		System.out.println("ExtendedClass에서 정의된 추상 메서드");
	}
}
public class AbstractClassExMain {
	public static void main(String[] args) {
		// 추상클래스의 인스턴스를 직접 생성해주고 싶다면?
		AbstractClass abstractClass = new AbstractClass() {
			@Override
			public void abstractMethod() {
				// 생성자에서 직접 추상메서드를 구현해주면 상속 없이 생성 가능
				System.out.println("Abstract Class 내부에서 정의한 abstractMethod()");
			}
		};
		
		abstractClass.implementedMethod();
		abstractClass.abstractMethod();
		
		System.out.println("--------------");
		
		AbstractClass extendedClass = new ExtendedClass();
		extendedClass.implementedMethod();
		extendedClass.abstractMethod();
	}
}

 

 

2) 인터페이스

- 다중상속이 가능

- 디폴트 메서드를 이용하여 정의된 메서드와 정의되지 않은 메서드 호출 가능

public interface SomeInterface {
	void someMethod();
	
	default void defaultMethod() {
		// default 키워드를 메서드에 붙이면 인터페이스 내부에서도 구현된 메서드를 가질 수 있음
		this.someMethod();
	}
}
public interface AnotherInterface {
	void anotherMethod();
}
public class ImplementsClass implements SomeInterface, AnotherInterface { // 다중상속 가능
	@Override
	public void anotherMethod() {
		// 리스코프 치환 원칙  : 자식의 실행 커버리지는 부모의 실행 커버리지보다 넓어져서는 안 됨.
		System.out.println("ImplementsClass의 anotherMethod()");	
	}

	@Override
	public void someMethod() {
		System.out.println("ImplementsClass의 someMethod()");	
	}
}
public class InterfaceExMain {
	public static void main(String[] args) {
		SomeInterface someInterface = new ImplementsClass();
		AnotherInterface anotherInterface = new ImplementsClass();
		
		someInterface.someMethod();
		anotherInterface.anotherMethod();
		
		ImplementsClass implementClass = new ImplementsClass();
		
		// SomeInterface, anotherInterface로는 양쪽 모두를 호출할 수 없음
		//someInterface.anotherMethod();
		//anotherInterface.someMethod();
		
		// ImplementsClass 구현체 타입으로는 양쪽 모두 호출 가능
		implementClass.anotherMethod();
		implementClass.someMethod();
	}
}

 

 

※ 일반적인 상황에서는 인터페이스를 쓰면 정답인 경우가 많다.

 

※ 추상클래스를 인터페이스 대신 사용하는 경우

① 인스턴스 변수(필드)를 정의해야 하는 경우 : 인터페이스는 상수만 정의 가능

② 생성자가 필요한 경우 : 인터페이스는 내부에 생성자 정의 불가

③ Object 클래스의 메서드를 오버라이딩 하고 싶은 경우

 

 

3. ENUM(이늄)

열거상수 이늄을 이용하면 강력한 객체지향 코딩 기법을 배울 수 있다.

import java.util.function.BiFunction;

public enum CalculateType {
	// basic과는 다르게 연산 종류와 연산 메커니즘을 함께 정의
	// 관련된 자료들이 잘 모여있음(응집도가 높아졌음)
	ADD((num1, num2) -> num1 + num2),
	MINUS((num1, num2) -> num1 - num2),
	MULTIPLY((num1, num2) -> num1 * num2),
	DIVIDE((num1, num2) -> num1 / num2);

	// 위에 붙인 익명함수들은 Bifunction이라는 타입으로 멤버변수를 정의해야 사용 가능
	// 따라서, 생성자에서 BiFunction을 주입받도록 처리
	CalculateType(BiFunction<Integer, Integer, Integer> expression){
		this.expression = expression;
	}
	
	private BiFunction<Integer, Integer, Integer> expression;
	
	public int calculate(int num1, int num2) {
		return this.expression.apply(num1, num2);
	}
}

이늄 자료를 위와 같이 정의하면 CalculateType은 총 4개의 자료만 가질 수 있다.

이때 타입에 따른 연산까지 함께 이늄에 정의해둘 수 있고, BiFunction은 입력자료형과 리턴자료형을 정의할 수 있도록 해준다.

함수를 마치 하나의 객체처럼 다룰 수 있게 해준다.

public class CalculateCommand {
	private CalculateType calculateType; // ADD, MINUS, MULTIPLY, DIVIDE 중 하나만 대입 가능
	private int num1;
	private int num2;
	
	public CalculateCommand(CalculateType calculateType, int num1, int num2) {
		this.calculateType = calculateType;
		this.num1 = num1;
		this.num2 = num2;
	}
	
	public CalculateType getCalculateType() {
		return calculateType;
	}
	
	public int getNum1() {
		return num1;
	}
	
	public int getNum2() {
		return num2;
	}
}
public class Client {
	public int process(CalculateCommand calculateCommand) {
		CalculateType calculateType = calculateCommand.getCalculateType();
		int num1 = calculateCommand.getNum1();
		int num2 = calculateCommand.getNum2();
				
		// basic쪽과는 달리 client에 상세한 계산 로직이 포함되지 않음
		// 클라이언트는 어떤 연산을 수행할지만 알고, 해당 로직의 상세한 내용을 모름
		// 클라이언트는 해당 로직에 대한 책임이 없음
		// 수정이 필요할 때 클라이언트측 코드를 볼 필요가 없음
		int result = calculateType.calculate(num1, num2);
		return result;
	}
}
public class AdvExMain {

	public static void main(String[] args) {
		// 클라이언트가 요청할 때 calculateCommand 객체가 제공한 데이터를 사용
		CalculateCommand calculateCommand = new CalculateCommand(CalculateType.ADD,100,3);
		Client client = new Client();
		int result = client.process(calculateCommand);
		
		System.out.println(result);
	}
}

 

 

 

4. 예외

1) checked exception vs unchecked exception

- checked exception : Exception 객체를 상속한 예외, 컴파일 시 예외처리를 문법적으로 강제

- unchecked exception : RuntimeException 객체를 상속한 예외, 예외처리를 따로 강제하지 않음

 

2) 왜 checked exception을 쓰지 않는가?

- 대부분의 경우 로직만으로는 예외를 처리할 수 없는 경우가 많음

ex) 사용자에게 파일 이름을 입력받아 서버에서 리턴해주는 로직

파일 잘못 입력 시 FileNotFoundException이 발생하는데, 사용자에게 파일을 입력해달라는 말 말고 직접적으로 처리할 수 없는데 굳이 예외처리 구문을 사용할 필요가 없음

- checked exception의 경우 내부에서 어떤 동작을 하다가 예외를 발생시켰는지 알 수 있는 경우가 많음 → 캡슐화 원칙이 깨지는 문제

 

 

5. Object 클래스

모든 클래스의 부모이자 Object 클래스가 가진 메서드들을 오버라이딩하여 사용하는 경우가 많음

 

1) .equals()

- 동일성 : 두 대상이 "똑같은 대상"이어야 성립 → ==를 이용해 비교

- 동등성 : 대상은 다르지만 어떤 다른 기준에 의해 같음을 확인했을 때 성립 → equals() 활용

 

2) .hashCode()

- 주로 equals와 함께 사용

- map 자료 등에 대해 비교 시 주로 hashcode를 이용해 1차적인 필터링을 하고, 그 결과에 대해 equals로 조회

 

 

 

 

 

 

 

 

 

 

 

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

객체지향적 코드 작성  (0) 2024.01.29
프로세스와 쓰레드  (0) 2024.01.24
자바 API  (0) 2024.01.18
자바의 예외처리전략  (0) 2024.01.18
예외처리  (0) 2024.01.17

1. 프로세스와 쓰레드

1) 애플리케이션이란?

→ 하나의 프로그램 단위

 

2) 프로세스란?

→ 하드디스크에 저장되어 있다가 메모리에서 실행 중인 프로그램

→ 각 프로세스는 하나하나가 독립된 코드, 스태틱, 스택, 힙 영역을 가짐

 

3) 멀티 프로세스란?

→ 하나의 애플리케이션을 여러 개 실행했을 때 각각의 애플리케이션은 다중 프로세스를 생성

→ 애플리케이션 단위로 멀티태스킹을 할 수 있도록 도와줌

 

4) 쓰레드란?

→ 하나의 프로세스 내부에서 실행되는 하나의 코드 실행 단위

 

5) 싱글쓰레드와 멀티쓰레드

- 싱글쓰레드 : 순차적으로 실행되는 코드

- 멀티쓰레드 : 한 번에 여러 작업을 동시에 수행 → 코드, 스태틱, 힙은 공유, 스택은 쓰레드 하나당 하나씩 생성

 

6) 멀티프로세스와 멀티쓰레드

- 멀티프로세스 : 독립된 프로세스 영역이기 때문에 하나가 오류가 나도 다른 하나는 영향을 받지 않음

   ex) 워드와 엑셀 실행 시 워드가 오류가 나서 종류된다고 해도 엑셀은 영향을 받지 않음

- 멀티쓰레드 : 하나의 프로세스 내에서 여러 개의 쓰레드가 돌아가기 때문에 프로세스 자체에 문제 발생 시 모든 쓰레드가 영향을 받음

 

※ 쓰레드 = 경량 프로세스

→ 멀티쓰레드 프로그램이 멀티프로세스에 비해 자원을 훨씬 더 먹기 때문에 위와 같은 단점에도 불구하고 멀티쓰레드를 사용

 

 

2. 자바의 쓰레드

1) 자바의 쓰레드

- 모든 자바 애플리케이션은 메인쓰레드가 메인메서드를 실행하며 시작

- 메인메서드는 순차적으로 코드 실행, return이나 마지막 코드 실행 후 종료

- 메인쓰레드는 필요에 따라 보조 쓰레드를 추가로 실행 가능

- 작업쓰레드 : 메인쓰레드가 호출한 보조 쓰레드

- 싱글쓰레드에서는 메인쓰레드 종료 시 즉시 프로세스 종료

- 멀티쓰레드에서는 실행중인 쓰레드가 하나라도 남아있다면 프로세스 유지

 

2) 작업쓰레드 생성해보기

- 쓰레드에 대한 정의 시 클래스파일을 쓰레드 개수만큼 생성

- 쓰레드 정의를 목적으로 생성된 클래스는 java.lang.Thread 클래스의 인스턴스를 직접 생성

- Thread 객체 생성, Runnable을 파라미터로 갖는 생성자 호출, Runnable 인터페이스 구현, run() 메서드 작성

 

// 보조쓰레드에서 실행할 내용을 정의하기 위해
// 1. Runnable 인터페이스 구현
public class MultiThread implements Runnable{
	// 2. Runnable 인터페이스의 run() 메서드를 오버라이딩해 실행할 내용 적음
	@Override
	public void run() {
		// 지금까지 코드는 순차적으로 쉬는시간 없이 실행되었음
		// Thread.sleep(밀리초);는 해당 쓰레드실행을 입력한 초만큼 중단
		// 쓰레드 중지는 try~catch 블럭에 반드시 넣어야 함
		try {
			for(int i = 0; i < 500; i++) {
				System.out.println("보조쓰레드 실행 : " + i);
				Thread.sleep(200); // 0.2초
			}
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
}
public class MainThread {

	public static void main(String[] args) {
		// 보조쓰레드 실행을 위해서는 먼저 Thread 객체를 생성해야 함
		
		// 1. Runnable 구현체 생성
		Runnable trd = new MultiThread();
		
		// 2. Thread 클래스의 인스턴스 생성
		// 생성자 호출 시 위의 Runnable 구현체를 파라미터로 넘김
		Thread sTrd = new Thread(trd);
		
		System.out.println("보조쓰레드 준비 완료!");
		
		// 3. 보조쓰레드는 .start()로 호출 가능
		sTrd.start();
		System.out.println("먼저 끝나도 프로그램은 계속 돌아가는 메인메서드");
	}
}

메인쓰레드는 종료되어도 보조쓰레드는 계속 실행되고 있는 것을 확인할 수 있다.

 

- 활용 예시 코드

public class HighChef implements Runnable {

	@Override
	public void run() {
		System.out.println("후임들에게 일하라고 시킵니다.");
		try {
			Thread.sleep(1000);
			mainMenu();
		} catch(Exception e) {}
	}
	
	public void mainMenu() {
		System.out.println("주메뉴를 만들기 시작합니다.");
		try {
			Thread.sleep(5000);
			System.out.println("주메뉴 완성");
		} catch(Exception e) {}
	}
}
public class MiddleChef implements Runnable {

	@Override
	public void run() {
		subMenu();
		boil();
	}
	
	public void subMenu() {
		System.out.println("부찬을 만들기 시작합니다.");
		try {
			Thread.sleep(5000);
			System.out.println("부찬 완성");
		} catch(Exception e) {}
	}
	
	public void boil() {
		System.out.println("국을 끓이기 시작합니다.");
		try {
			Thread.sleep(6000);
			System.out.println("국 완성");
		} catch(Exception e) {}
	}
}
public class LowChef implements Runnable {

	@Override
	public void run() {
		side();
		rice();
		ingredient();
	}
	
	public void side() {
		System.out.println("반찬을 만들기 시작합니다.");
		try {
			Thread.sleep(2000);
			System.out.println("반찬 완성");
		} catch(Exception e) {}
	}
	
	public void rice() {
		System.out.println("밥을 짓기 시작합니다.");
		try {
			Thread.sleep(10000);
			System.out.println("밥 완성");
		} catch(Exception e) {}
	}
	
	public void ingredient() {
		System.out.println("재료 손질을 시작합니다.");
		try {
			Thread.sleep(3000);
			System.out.println("재료 손질 완성");
		} catch(Exception e) {}
	}
}
public class MultiRestaurant {
	public static void main(String[] args) {
		Runnable highR = new HighChef();
		Runnable middleR = new MiddleChef();
		Runnable lowR = new LowChef();
	
		Thread t1 = new Thread(highR);
		Thread t2 = new Thread(middleR);
		Thread t3 = new Thread(lowR);
		
		// 세 명의 요리사가 동시에 작업
		t1.start();
		t2.start();
		t3.start();
	}
}

위와 같이 여러 개의 쓰레드가 생성되어 코드가 순차적으로 실행되지 않고 동시에 여러 작업을 수행하는 것을 확인할 수 있다.

쓰레드 실행은 start() 함수를 이용하여 호출할 수 있다.

 

- Runnable과 Thread 객체를 따로 만드는게 불편하다면 아래와 같이 익명객체를 생성해 사용해도 된다.

Thread thread = new Thread(new Runnable(){
	public void run(){
    	// 해당 쓰레드가 실행할 코드
    }
});

 

 

3) 쓰레드의 우선순위

- 동시성 : 싱글코어 CPU를 이용한 멀티쓰레드는 실제로는 순차적으로 번갈아가며 실행하지만, 속도가 빨라서 우리가 보기엔 동시에 작업이 이뤄지는 것처럼 보임

- 병렬성 : 멀티코어 CPU를 이용한 멀티쓰레드는 진짜 동시에 여러 작업 수행

 

4) 쓰레드의 공유객체 문제

public class ThreadNotSafety extends Thread {

	// static으로 선언된 변수는 자동으로 0 할당
	static int share;
	
	public static void main(String[] args) {
		ThreadNotSafety t1 = new ThreadNotSafety();
		ThreadNotSafety t2 = new ThreadNotSafety();

		t1.start();
		t2.start();
	}
	
	@Override
	public void run() {
		for(int cnt = 0; cnt < 10; cnt++) {
			System.out.println(share++);
			
			try {Thread.sleep(10);}
			catch(Exception e) {}
		}
	}
}

위와 같이 순차적으로 진행되지 않고 2개의 쓰레드가 static 변수에 동시에 접근하여 의도와 다른 결과가 나올 확률이 있음

※ Race Conditon : 공용 자원을 두고 멀티쓰레드나 멀티프로세스가 경쟁상태에 돌입

 

∴ 특정 로직에 대해서는 동시에 하나의 쓰레드만 접근할 수 있도록 처리할 필요가 있음 → synchronized 키워드

public synchronized void 메서드(){
	// 실행문
}

위 메서드는 동시에 하나의 쓰레드만 접근 가능, 다른 메서드가 접근할 준비가 되어도 대기하기 때문에 공유객체문제 해결 가능

 

public void 메서드(){
	// 실행코드
    synchronized(공유자원){
    	// 단 하나의 쓰레드 단위로만 실행 가능한 실행코드
    }
    // 실행코드
}

위와 같이 특정 코드블럭만 임계영역으로 설정하는 것도 가능하다.

 

public class ThreadSafety extends Thread {
	
	static int share;
	
	public static void main(String[] args) {
		ThreadSafety t1 = new ThreadSafety();
		ThreadSafety t2 = new ThreadSafety();
	
		t1.start();
		t2.start();
	}
	
	// 쓰레드 안전을 위해서 사용하는 키워드
	public synchronized static void sharePlus() {
		System.out.println(share++);
	}
	
	@Override
	public void run() {
		for(int cnt = 0; cnt < 10; cnt++) {
			sharePlus(); // 동시성 제어가 되는 메서드로 1씩 증가
			try {Thread.sleep(1);}
			catch(Exception e) {}
		}
	}
}

위와 같이 동시에 접근해도 제어가 되어 1씩 잘 증가되는 것을 확인할 수 있다.

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

객체지향적 코드 작성  (0) 2024.01.29
객체지향 정리  (0) 2024.01.25
자바 API  (0) 2024.01.18
자바의 예외처리전략  (0) 2024.01.18
예외처리  (0) 2024.01.17

1. JAVA API(Application Programming Interface) 

→ 프로그램 개발에 자주 사용되는 클래스 및 인터페이스의 모음으로 라이브러리라고 부름

※ 자바 표준 API 문서 : https://docs.oracle.com/javase/8/docs/api

 

1) java.lang 패키지

자바의 기본적인 클래스들을 담고 있는 패키지로 import 구문 없이 사용 가능

① Object : 자바클래스의 최상위 클래스

Object 클래스는 모든 클래스의 부모클래스이다.

public class MainClass {

	public static void main(String[] args) {
		ObjectInformation oi = new ObjectInformation();
		
		System.out.println(oi);
	}
}

Object 클래스의 toString 메서드

Object 클래스를 확인해보면 toString 메서드가 이미 정의되어 있기 때문에 위와 같은 출력 결과를 확인할 수 있다. 

public class ObjectInformation {
	// toString 오버라이딩 후 System.out.println() 등으로 객체변수 조회 시
	// 해당 인스턴스의 클래스 경로와 주소값 대신 toString에서 리턴한 문자가 콘솔에 찍힘
	
	@Override 
	public String toString() {
		return "조회 시 이게 나올거임";
	}
}

ObjectInformation 클래스에 toString() 메서드를 오버라이딩하면 오버라이딩한 내용으로 바뀌어 출력되는 것을 확인할 수 있다.

 

② System : 표준입력장치(키보드)로부터 데이터를 입력받거나 표준출력장치(모니터)로 출력하기 위해 사용

- exit() : 현재 실행하고 있는 프로세스 강제 종료, 정상 종료일 경우 매개값으로 0을 줌

- currentTimeMills() : 컴퓨터의 시계로부터 현재 시간을 읽어서 밀리세컨드 단위와 나노세컨드 단위의 long 값 리턴

                                  → 프로그램의 실행 소요 시간 측정으로 성능을 테스트할 때 사용

public class SystemTimeEx {
	public static void main(String[] args) {
		// currentTimeMilllis()와 nanoTime() 메서드는 UNIX 시간 사용
		// UNIX 시간 : 1970/01/01 00:00:00을 기점으로 얼마나 시간이 지났는지 숫자로 표현
		// 현재까지의 시간을 1000분의 1초로 변환한 에폭시간과 10억분의 1초로 변환한 에폭시간을 long 타입으로 리턴
		
		long start = System.currentTimeMillis();
		System.out.println("시작시간 : " + start);
		long sum = 0;
		
		for(long i = 1; i < 10_000_000_000L; i++) {
			sum += i;
		}
		long end = System.currentTimeMillis();
		System.out.println("종료시간 : " + end);
		System.out.println("계산에 소요된 시간 : " + (end - start));
	}
}

위와 같이 해당 로직이 수행되는데 얼마나 시간이 걸리는지 알 수 있다.

- getProperty() : JVM이 시작할 때 자동 설정되는 시스템의 속성값 구함

- gc() : Garbage Collector 실행

 

③ Class : 클래스를 메모리에 로딩할 때 사용

 

④ String : 문자열을 저장하고 문자열의 여러 가지 정보를 얻을 때 사용

→ 생성자를 사용하여 객체를 만들 필요 없이 기초 데이터처럼 바로 초기화 가능

- 주요 메서드

charAt() 특정 인덱스 글자 리턴
indexOf() 특정 문자열의 시작 인덱스 반환, 해당 문자가 없으면 -1 리턴
length() 문자열 길이 리턴
replace() 특정 문자열 변경
substring() 인덱스를 기준으로 그 앞의 문자열을 잘라줌
ex) 매개값으로 인덱스 2개 주면 처음 매개값~두번째 매개값의 문자열을 제외하고 삭제
toUpperCase() 문자열을 대문자로 치환
toLowerCase() 문자열을 소문자로 치환
trim() 문자열의 앞, 뒤 공백 제거
equals() 문자열의 값 비교
valueOf() 기본 데이터 타입의 값들을 문자열로 변환

 

-charAt( ) 메서드 예시

public class StringCharAt {
	public static void main(String[] args) {
		// 일반 배열은 아래와 같이 인덱싱 가능
		int [] iArr = {1, 2, 3, 4};
		System.out.println(iArr[2]);
		
		// 문자열은 일반 인덱싱([ ]을 사용하는 인덱싱)이 불가능
		String str = "가나다라";
		//System.out.println(str[2]);
		System.out.println(str.charAt(2));
	}
}

 

- indexOf( ) 메서드 예시

public class StringIndexOf {
	public static void main(String[] args) {
		// 특정 문자열의 시작 인덱스값을 반환
		String to = "tomatos";
		
		// 단일 파라키터로 조회만 할 경우 0번에서 제일 가까운 단어 하나만 조회
		System.out.println(to.indexOf(to));

		// 두 번째 파라미터로 조회 시작 파라미터 지정 가능
		System.out.println(to.indexOf("to", 1)); // 0번부터가 아닌 1번부터 조회
		
		// 없는 단어 조회 시 -1
		System.out.println(to.indexOf("na"));
	}
}

 

- replace( ) 메서드 예시

public class StringReplace {

	public static void main(String[] args) {
		// 자바는 15버전부터 멀티라인 문자열 지원
		// 원래 자바 문자열은 여닫는 "를 한 줄에 작성해야했음
		// String a = "가나다라"; (허용) 
		// String a = "가나다라
		//					"마바사";	(허용 X)
		
		// 여닫는 따옴표가 다른 줄에 위치한 경우 " 세 개 이용
		// """를 사용한 줄에는 문자열을 작성할 수 없고, 다음 줄부터 작성가능
		String notice = """
						<공지사항>
						1. 복습 철저히 해주세요
						2. 회고록 작성 해주세요
				""";
		System.out.println(notice);
		
		// replace()는 String을 리턴하고, 첫 단어를 두 번째 단어로 바꿔줌
		String newNotice = notice.replace("해주세요", "해주십시오");
		System.out.println(newNotice);
	}
}

 

- substring( ) 메서드 예시

public class StringSubstring {

	public static void main(String[] args) {
		// 주민번호 양식
		String ssn = "010808-4987654";
		System.out.println(ssn);

		// substring() 메서드에 매개값으로 인덱스를 1개 지정 시 
		// 해당 인덱스부터 마지막 지점까지의 문자 추출
		String last = ssn.substring(7); // 7번부터
		System.out.println(last);
		
		// substring() 메서드에 매개값으로 인덱스를 2개 지정 시
		// 첫 번째 매개값 이상, 두 번째 매개값 미만 범위 문자 추출
		String first = ssn.substring(0, 6); // 0 1 2 3 4 5까지 조회, 6은 범위 X
		System.out.println(first);
	}
}

 

- trim() 메서드 예시

public class StringTrim {

	public static void main(String[] args) {
		// trim은 좌측, 우측에서 다른 단어가 나오기 직전까지 공백을 전부 제거
		String trimBefore = "              옆에   거슬     려요       ";
		System.err.println(trimBefore);
		String trimAfter = trimBefore.trim();
		System.out.println(trimAfter);
	}
}

 

- valueOf() 메서드 예시

public class StringValueOf {

	public static void main(String[] args) {
		int a = 10;
		double b = 8.79;
		System.out.println(a + b);
		
		String str1 = String.valueOf(a); // int -> String
		String str2 = String.valueOf(b); // double -> String
		System.out.println(str1 + str2);
	}
}

 

★ String 클래스 단점 : 처음 초기화된 데이터 변경 시 기존 객체 재활용이 아닌 새로운 객체 생성 → 메모리 과소비

위와 같이 String 클래스는 문자 내용이 변경될 때마다 새로운 영역에 다시 할당되어 속도가 느려지는 현상이 발생한다.

→ 속도적인 측면에서 개선된 StringBuffer와 StringBuilder 사용

 

⑤ StringBuffer, StringBuilder : 문자열을 저장하고 내부 문자열을 조작할 때 사용

- StringBuilder가 성능적으로는 좀 더 낫지만, StringBuffer는 Thread safety를 보장한다.

- 정말 세밀한 성능 이슈를 따지지 않는 환경에서는 StringBuffer를 쓰면 된다.

public class StringMemory {
	public static void main(String[] args) {
		// String의 경우 내용이 다른 문자는 항상 새롭게 할당
		//String a = "0";
		
		// StringBuilder, StringBuffer는 문자도 메모리 저장 시 변경 가능하게 저장
		// 따라서 문자 내용이 바뀌어도 새로운 할당이 잘 일어나지 않으므로 성능상 우위에 있음
		// StringBuilder는 Thread safety하지 않지만 StringBuffer보다 근소하게 성능이 좋음
		//StringBuilder sb = new StringBuilder("0");
		StringBuffer sb = new StringBuffer("0");
		
		long start = System.currentTimeMillis();
		
		for(int i = 0; i < 1_000_000; i++) {
			//a += "0"; // a 문자에 0을 100만번 더함 -> 힙 할당도 100만번
			sb.append("0"); // sb에 0을 100만번 더함 -> 힙 할당은 거의 새롭게 일어나지 않음
		}
		 
		long end = System.currentTimeMillis();
		System.out.println("소요시간(밀리초 : " + (end - start));
	}
}

 

- StringBuilder 클래스의 주요 메서드

append() 기존 문자열의 뒤에 문자열 추가
insert() 특정 위치에 문자열 추가
delete() 문자열 삭제
deleteCharAt() 특정 인덱스의 문자 하나 삭제
replace() 문자열의 일부분을 다른 문자열로 대체
setCharAt() 문자열에서 주어진 index의 문자를 다른 문자로 대체
toString() stringBuilder 객체의 값을 문자열로 반환
reverse() 문자열을 거꾸로 뒤집기
public class StringBuilderEx {
	public static void main(String[] args) {
		// 이 코드에서 StringBuilder는 전부 StringBuffer로 대체해도 잘 동작한다.
		// 둘의 차이는 쓰레드 안전이 보장되는지 아닌지의 여부뿐
		// 따라서 쓰레드 안전을 보장받고 싶다면 StringBuffer만 사용하면 됨
		//StringBuilder sb = new StringBuilder("JAVA");
		StringBuffer sb = new StringBuffer("JAVA");
		
		// 문자열 끝에 추가하는 메서드 append()
		sb.append(" Program Study");; // sb += " Program Study"와 동일
		System.out.println(sb);
		
		// 문자열을 특정 인덱스 위치에 삽입하는 메서드 insert()
		sb.insert(12, "ming");
		System.out.println(sb);
		
		// 특정 인덱스 범위 문자열을 교체하는 메서드 replace()
		sb.replace(5, 16, "book"); // 5~15번 인덱스 11글자를 book 4글자로 치환
		System.out.println(sb);
		
		// 문자열을 삭제하는 메서드 delete(begin, end)
		sb.delete(4, 9);
		System.out.println(sb);
		System.out.println(sb.length());
	}
}

 

☆ 매번 할당하지 않는 원리

StringBuilder와 StringBuffer는 생성 시 할당하는 저장용량 capacity(16)를 가지고 있으며, append 연산으로 저장 용량을 초과하는 경우 기존 저장 용량을 가진 객체를 재할당하게 된다.

 

⑥ Math : 수학 함수를 이용할 때 사용

- 주요 메서드

abs() 절대값
ceil() 올림값
floor() 내림값
max() 최대값
min() 최소값
random() 랜덤값 (0.0 <= 값 <1.0)
rint() 현재 수에서 가까운 정수를 실수 형태로 구함
round() 반올림값
public class MathEx {
	public static void main(String[] args) {
		// 수학 관련된 연산이나 혹은 상수를 저장해둔 Math 클래스는 정적 변수와 메서드를 가지므로
		// 굳이 Math 객체를 생성하지 않아도 활용 가능
		
		// 절대값 : 부호 무시
		System.out.println(Math.abs(-15.294));
		
		// 올림 : 소수점 아래자리가 존재하면 1 증가
		System.out.println(Math.ceil(10.0));
		System.out.println(Math.ceil(10.00001));
		
		// 내림 : 소수점 아래자리가 존재하면 없애버림
		System.out.println(Math.floor(10.0));
		System.out.println(Math.floor(10.99999));
		
		// 최대값 : 제시된 수 중 가장 큰 값 하나만 남김
		System.out.println(Math.max(99.9, 12.34));
		
		// 최소값 : 제시된 수 중 가장 작은 값 하나만 남김
		System.out.println(Math.min(99.9, 12.34));
		
		// 랜덤값 : 컴퓨터 시스템은 완벽한 난수가 아닌 시득닶에 따른 의사난수를 사용
		System.out.println(Math.random());
		
		// 가장 가까운 실수 구하기
		System.out.println(Math.rint(12.500001));
		
		// 반올림 : 소수점 아래값이 0.5 미만이면 정수를 그대로, 이상이면 1 증가
		System.out.println(Math.round(24.604));
        
        	// 혹은 Math 클래스 내에 정적변수로 자주 사용하는 상수값(원주율, 자연상수 등)도 제공
		System.out.println(Math.PI);
		System.out.println(Math.E);
	}
}

 

 

⑦ Wrapper(Byte, Short, Integer, Long, Float, Double, Boolean, Character) : 기본 데이터 타입의 객체를 만들 때 사용

 

 

 

 

 

 

 

 

 

 

 

 

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

객체지향 정리  (0) 2024.01.25
프로세스와 쓰레드  (0) 2024.01.24
자바의 예외처리전략  (0) 2024.01.18
예외처리  (0) 2024.01.17
인터페이스  (0) 2024.01.16

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. 인터페이스(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. 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

+ Recent posts