본문 바로가기
Java

자바의 다양한 기능 / 스트림, 예외처리

by 리잼 2022. 12. 7.
반응형

5. 스트림 ( Stream )

  • 자료의 대상과 관계없이 동일한 연산을 수행
    배열, 컬렉션을 대상으로 연산을 수행
    자료 처리에 대한 추상화가 구현되었다고 함
  • 한번 생성하고 사용한 스트림은 재사용이 불가
    자료에 대한 스트림을 생성하여 연산을 수행하면 스트림은 소모됨
    다른연산을 수행하기 위해서는 스트림을 다시 생성해야 함
  • 스트림 연산은 기존 자료를 변경하지 않음
    자료에 대한 스트림을 생성하면 스트림이 사용하는 메모리 공간은 별도로 생성되므로 연산이 수행돼도 
    기존 자료에 대한 변경은 발생하지 않음
  • 스트림 연산은 중간 연산과 최종 연산으로 구분됨
    스트림에 대해 중간 연산은 여러 개의 연산이 적용될 수 있지만 최종연산은 마지막에 한 번만 적용됨
    최종연산이 호출돼야 중간 연산에 대한 수행이 이루어 지고 그 결과가 만들어짐
    따라서 중간 연산에 대한 결과를 연산중에 알 수 없음 이를 "지연 연산" 이라고 함

스트림 생성하고 사용하기

import java.util.Arrays;
import java.util.stream.IntStream;

public class InArrayStreamTest {

	public static void main(String[] args) {

		int[] arr = {1,2,3,4,5};
		
		for ( int num : arr ) {
			System.out.println(num);
		}
		System.out.println(); //1,2,3,4,5
		
		IntStream is = Arrays.stream(arr);
		is.forEach(n->System.out.println(n)); // 1,2,3,4,5
		
        // 이미 스트림이 한 번 소모되어 다시 생성
		int sum = Arrays.stream(arr).sum();
		System.out.println(sum); // 15
		
	}

}

중간 연산과 최종 연산

  • 중간 연산 : filter(), map(), sorted() 등
    조건에 맞는 요소를 추출( filter )하거나 요소를 반환 함 ( map )
  • 최종 연산이 호출될 때 중간 연산이 수행되고 결과가 생성 됨

문자열 리스트에서 문자열의 길이가 5 이상인 요소만 출력하기

  sList.stream().filter(s->s.length() >= 5).forEach(s->System.out.println(s));
  • filter() 는 중간 연산이고, forEach() 는 최종 연산임

고객 클래스 배열에서 고객 이름만 가져오기

customerList.stream().map(c->c.getName()).forEach(s->System.out.println(s));
  • map() 는 중간 연산이고, forEach()는 최종 연산임

중간 연산과 최종 연산에 대한 구현은 람다식을 활용

최종 연산의 예 - forEach(), count(), sum() 등

스트림이 관리하는 자료를 하나씩 소모해가며 연산이 수행된다

최종 연산 후 스트림은 더 이상 다른 연산을 적용할 수 없음

  • forEach() : 요소를 하나씩 꺼내옴
  • count() : 요소의 개수
  • sum() : 요소의 합

ArrayList 객체에 스트림 생성하고 사용

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class ArrayListStreamTest {

	public static void main(String[] args) {

		List<String> sList = new ArrayList<String>();
		sList.add("Tomas");
		sList.add("Edward");
		sList.add("Jack");
		
		Stream<String> stream = sList.stream();
		stream.forEach(s->System.out.println(s));
		
		sList.stream().sorted().forEach(s->System.out.print(s+"\t"));
		System.out.println();
		sList.stream().map(s->s.length()).forEach(n->System.out.println(n+"\t"));
		System.out.println();
		sList.stream().filter(s->s.length() >= 5).forEach(s->System.out.println(s));
		
	}

}
  • 새로운 연산을 수행하기 위해서는 기존의 스트림은 재사용 할 수 없고 stream()메서드를 호출하여 스트림 다시 생성

정수 자료에 대한 여러가지 연산

public class IntArrayStreamTest {

	public static void main(String[] args) {
		int[] arr = {1,2,3,4,5};
		Arrays.stream(arr).forEach(n->System.out.print(n + "\t"));
		System.out.println();
					
		int sum  = Arrays.stream(arr).sum();
		System.out.println("sum: " + sum);
		
		List<Integer> list = new ArrayList<Integer>();
		list.add(1);
		list.add(2);
		list.add(3);
		list.add(4);
		list.add(5);
		int sum2 = list.stream().mapToInt(n->n.intValue()).sum();
		System.out.println(sum2);
	}

}


6. 연산 수행에 대한 구현을 할 수 있는 reduce() 연산

reduce() 연산

  • 정의된 연산이 아닌 프로그래머가 직접 구현한 연산을 적용
T reduce(T idenfity, BinaryOperator<T> accumulator)
  • 최종 연산으로 스트림의 요소를 소모하며 연산을 수행

배열의 모든 요소의 합을 구하는 reduce() 연산 구현 예

Arrays.stream(arr).reduce(0, (a,b)-> a+b));
  • reduce() 메서드의 두 번째 요소로 전달되는 람다식에 따라 다양한 기능을 수행할 수 있음
  • 람다식을 직접 구현하거나 람다식이 긴 경우 BinaryOperator를 구현한 클래스를 사용함

배열에 여러 문자열이 있을 때 길이(byte)가 가장 긴 문자열 찾기

import java.util.Arrays;
import java.util.function.BinaryOperator;

class CompareString implements BinaryOperator<String>{

	@Override
	public String apply(String s1, String s2) {
		if(s1.getBytes().length >= s2.getBytes().length) return s1;
		else return s2;
	}
	
}

public class ReduceTest {

	public static void main(String[] args) {

		String greetings[] = {"안녕하세요.", "hello", "good morning", "ㅎㅇ"};
		
		System.out.println(Arrays.stream(greetings).reduce("", (s1, s2)->
				{if(s1.getBytes().length >= s2.getBytes().length) return s1;
				else return s2;}
				));
		
		String str = Arrays.stream(greetings).reduce(new CompareString()).get();
		System.out.println(str);
		
		
	}

}

7. 스트림을 활용하여 패키지 여행 비용 계산

문제 정의

여행사에 패키지 여행 상품이 있습니다. 여행 비용은 15세 이상은 100만원, 그 미만은 50만원 입니다. 
고객 세 명이 패키지 여행을 떠난다고 했을 때 비용 계산과 고객 명단 검색등에 대한 연산을 스트림을 활용하여 구현해 봅니다.
고객에 대한 클래스를 만들고 ArrayList로 고객을 관리 합니다. 

고객 정보는 다음과 같습니다.

CustomerLee 
이름 : 이순신
나이 : 40
비용 : 100

CustomerKim
이름 : 김유신
나이 : 20 
비용 : 100

CustomerHong
이름 : 홍길동
나이 :13
비용 : 50

고객 클래스

public class TravelCustomer {

	private String name;
	private int age;
	private int price;
	
	public TravelCustomer(String name, int age, int price) {
		this.name = name;
		this.age = age;
		this.price = price;
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public int getPrice() {
		return price;
	}
	public void setPrice(int price) {
		this.price = price;
	}
	
	public String toString() {
		return "name : " + name + "age: " + age + "price: " + price; 
	}
	
	
}

스트림을 활용한 연산 수행

import java.util.ArrayList;
import java.util.List;

public class TravelCustomerTest {

	public static void main(String[] args) {

		TravelCustomer customerLee = new TravelCustomer("이순신", 40, 100);
		TravelCustomer customerKim = new TravelCustomer("김유신", 20, 100);
		TravelCustomer customerHong = new TravelCustomer("홍길동", 13, 50);

		List<TravelCustomer> customerList = new ArrayList<>();
		customerList.add(customerLee);
		customerList.add(customerKim);
		customerList.add(customerHong);
		
		System.out.println("== 고객 명단 추가된 순서대로 출력 ==");
		customerList.stream().map(c->c.getName()).forEach(s->System.out.println(s));
		
		int total = customerList.stream().mapToInt(c->c.getPrice()).sum();
		System.out.println("총 여행 비용은: " + total + "입니다.");
		
		System.out.println("== 20세 이상 고객 명단 정렬하여 출력 ==");
		customerList.stream().filter(c->c.getAge() >= 20).map(c->c.getName()).sorted().forEach(s->System.out.println(s));
	}

}


8. 예외처리

프로그램에서의 오류

컴파일 오류 ( compile error )

  • 프로그램 코드 작성중 발생하는 문법적 오류
  • 최근에는 개발 환경에서 대부분의 컴파일 오류는 detection 됨

실행 오류 ( runtime error )

  • 실행중인 프로그램이 의도하지 않는 동작(bug)을 하거나 프로그램이 중지되는 오류
  • 실행 오류는 비정상 종료가 되는 경우 시스템의 심각한 장애를 발생할 수 있음

예외 처리의 중요성

  • 프로그램의 비정상 종료를 피하여 시스템이 원할하게 실행되도록 함
  • 실행 오류가 발생한 경우 오류의 과정을 재현하는 것은 현실적으로 힘들다
  • 오류가 발생한 경우 log를 남겨 추후 log 분석을 통해 그 원인을 파악하여 bug를 수정하는 것이 중요

오류와 예외 클래스

시스템 오류 ( error )

  • 가상 머신에서 발생, 프로그래머가 처리 할 수 없는 오류임
  • 동적 메모리가 없는 경우, 스택 메모리 오버플로우 등

예외 ( Exception )

  • 프로그램에서 제어할 수 있는 오류
  • 읽어들이려는 파일이 존재하지 않거나, 네트웍이나 DB연결이 안되는 경우 등

자바는 안전성이 중요한 언어로 대부분 프로그램에서 발생하는 오류에 대해 문법적으로 예외 처리를 해야함

예외

예외 클래스들

모든 예외 클래스의 최상위 클래스는 Exception 클래스

자바에서는 다양한 예외들에 대해 그 처리를 위한 클래스가 제공됨

Arithmetic Exception : 정수를 0으로 나눈 경우 발생

NullPointerException : 초기화 되지 않은 Object를 사용하는 경우

Dog d = null;
System.out.println(dog);

 

  • ArrayIndexOutOfBoundsException :배열의 크기를 넘어선 위치를 참조하려는 경우
  • FileNotFoundException :참조하는 파일이 지정된 위치에 존재하지 않는 경우
  • ClassNotFoundException :
Class.forName(“sis.studentinfo.Student”); //클래스가 로드되지 않은 경우

 

  • InterruptedException :Thread.sleep(), join(). Object의 wait()로 non-runnable 상태인 thread를 Runnable하게 만들 수 있도록 사용할 수 있음

9. 예외 처리하기와 미루기

try-catch 문

  • try 블록에는 예외가 발생할 가능성이 있는 코드를 작성하고 try블록 안에서 예외가 발생하는 경우
    catch 블록이 수행됨

  • 프로그래머가 예외를 처리해줘야 하는 예 ( 배열 오류의 처리 )
public class ArrayIndexException {

	public static void main(String[] args) {

		int[] arr = {1,2,3,4,5};
		
		
		try {
		for (int i=0; i<=5; i++) {
			System.out.println(arr[i]);
			}
		} catch(ArrayIndexOutOfBoundsException e) {
			System.out.println(e.getMessage());
			System.out.println(e.toString());
		}
		System.out.println("here");
	}

}

try-catch-finally 문

  • finally 블럭에서 파일을 닫거나 네트웍을 닫는 등의 리소스 해제 구현을 함
  • try{} 블럭이 수행되는경우, finally{} 블럭은 항상 수행됨
  • 여러개의 예외 블럭이 있는 경우 각각에서 리소스를 해제하지 않고 finally 블록에서 해제하도록 구현함

컴파일러에 의해 예외가 처리되는 예 ( 파일 에러 처리 )

public class FileExceptionHandling {

	public static void main(String[] args) {
		FileInputStream fis = null;
		try {
			fis = new FileInputStream("a.txt");
		} catch (FileNotFoundException e) {
			System.out.println(e);
			//return;
		}finally{
			if(fis != null){
				try {
					fis.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println("항상 수행 됩니다.");
		}
		System.out.println("여기도 수행됩니다.");
	}
}

try-with-resources문

  • 리소스를 사용하는 경우 close()하지 않아도 자동으로 헤제 되도록 함
  • 자바7 부터 제공되는 구문
  • 리소스를 try() 내부에서 선언해야만 함
  • close()를 명시적으로 호출하지 않아도 try{} 블록에서 열린 리소스는
    정상적인 경우나 예외가 발생한 경우 모두 자동으로 헤제됨
  • 해당 리소스 클래스가 AutoCloseable 인터페이스를 구현해야 함
  • FileInputStream의 경우에는 AutoCloseable을 구현하고 있음
  • 자바9 부터 리소스는 try() 외부에서 선언하고 변수만을 try(obj)와 같이 사용할 수 있음

AutoCloseable 인터페이스 실습

public class AutoCloseableObj implements AutoCloseable {

	@Override
	public void close() throws Exception {
		System.out.println("closing.");		
	}

}
public class AutoCloseTest {

	public static void main(String[] args) {

		AutoCloseable obj = new AutoCloseableObj();
		
		try(obj) {
			throw new Exception();
			
		} catch (Exception e) {
			System.out.println("exception");
		}
		System.out.println("end");
	}

}

예외처리 미루기

  • 예외처리는 예외가 발생하는 문장에서 try-catch 블록으로 처리하는 방법과
    이를 사용하는 부분에서 처리하는 방법 두가지가 있다
  • throws를 이용하면 예외가 발생할 수 있는 부분을 사용하는 문장에서 예외를 처리할 수 있음
public class ThrowsException {

	public Class loadClass(String fileName, String className) throws FileNotFoundException, ClassNotFoundException{
		FileInputStream fis = new FileInputStream(fileName); //FileNotFoundException 발생
		Class c = Class.forName(className);  //ClassNotFoundException 발생
		return c;
	}

	public static void main(String[] args) {

		ThrowsException test = new ThrowsException();
		
		try {
			test.loadClass("a.txt", "java.lang.String");
		
		}catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}catch (Exception e) {
			e.printStackTrace();
		}
	}
}

하나의 try{}블록에서 예외가 여러개 발생하는 경우

  • 여러개의 예외가 발생하는 경우 예외를 묶어서 하나의 방법으로 처리할 수도 있고,
	try {
	test.loadClass("a.txt", "java.lang.String");
	} catch (FileNotFoundException | ClassNotFoundException e) {
		e.printStackTrace();
	}
  • 각각의 예외를 따로 처리할 수도 있음
try {
			test.loadClass("a.txt", "java.lang.String");
		} catch (ClassNotFoundException e) {
			System.out.println(e);
		} catch (FileNotFoundException e) {
			System.out.println(e);
		} catch (Exception e) { // catch문 최하단에 작성해야함
			
		}

Exception 클래스를 활용하여 defualt 처리를 할 때 Exception 블록은 맨 마지막에 위치해야 함


10. 사용자 정의 예외 클래스

사용자 정의 예외 클래스 구현하기

  • 자바에서 제공되는 예외 클래스외에 프로그래머가 직접 만들어야 하는 예외가 있을 수 있음
  • 기존 예외 클래스 중 가장 유사한 예외 클래스에서 상속 받아 사용자 정의 예외 클래스를 만든다.
  • 기본적으로 Exception 클래스를 상속해서 만들 수 있음

패스워드에 대한 예외처리 하기

  • 비밀번호는 null 일 수 없다
    비밀번호 길이는 5 이상
    비밀번호는 문자로만 이루어져서는 안됨 ( 하나이상의 숫자나 특수문자 포함 ) 
public class PassWordException extends Exception {
	
	public PassWordException(String message) {
		super(message);
	}

}
public class PassWordTest {

	private String password;
	
	public String getPassword() {
		return password;
	}

	public void setPassword(String password) throws PassWordException {
	
		if (password == null) {
			throw new PassWordException("비밀번호는 null일 수 없습니다.");
		}
		else if (password.length() < 5) {
			throw new PassWordException("비밀번호는 5자 이상이어야 합니다");
		}
		else if (password.matches("[a-zA-Z]+")) {
			throw new PassWordException("비밀번호는 숫자나 특수문자를 포함해야 합니다");
		}
		
		this.password = password;
	}

	public static void main(String[] args) {

		PassWordTest test = new PassWordTest();
		
		String password = null;
		try {
			test.setPassword(password);
			System.out.println("오류없음1");
		} catch (PassWordException e) {
			System.out.println(e.getMessage());
		}
		
		password = "abc";
		try {
			test.setPassword(password);
			System.out.println("오류없음2");
		} catch (PassWordException e) {
			System.out.println(e.getMessage());
		}
		
		password = "abcde";
		try {
			test.setPassword(password);
			System.out.println("오류없음3");
		} catch (PassWordException e) {
			System.out.println(e.getMessage());
		}
		
		password = "abcde1#";
		try {
			test.setPassword(password);
			System.out.println("오류없음4");
		} catch (PassWordException e) {
			System.out.println(e.getMessage());
		}
	}

}


11. 오류 로그 남기기

logging

  • 시스템 운영에 대한 기록
  • 오류가 발생했을 때 그 오류에 대한 기록을 남겨 디버깅을 용이하게 함
  • 로그 파일에 기록하는 코드를 추가하여 필요한 정보가 로그로 남을 수 있도록 한다
  • 디버깅, 시스템 에러 추적, 성능, 문제점 향상들을 위해 사용
  • 어느정도 까지 로그를 남길 것 인가?
    너무 적은 로그 : 정확한 시스템의 상황을 파악하기 어려움
    너무 많은 로그 : 빈번한 file I/O의 오버헤드와 로그 파일의 백업 문제등.

java.util.logging

  • 자바에서 기본적으로 제공되는 log package
  • 파일이나 콘솔에 로그 내용을 출력할 수 있음
  • jre/lib/logging.properties 파일을 편집하여 로그의 출력방식 로그 레벨을 변경할 수 있음
  • logging 패키지에서 제공하는 로그 레벨은 severe, warning, info, config, fine, finer, finest
  • 오픈소스로는 log4j를 많이 사용중

로그 만들기

  • 시나리오
    학생 정보 시스템에 로그를 기록하도록 한다.
    학생의 이름에 오류가 있는 경우 예외 처리를 하고 예외 상황을 로그로 남긴다.
    학생의 이름은 null 이거나 중간에 space가 3개 이상인 경우 오류가 발생한다.
  • 구현하기
    Logger 인스턴스를 생성한다.
    로그를 남기기 위한 FileHandler를 생성한다.
    FileHandler의 level을 지정하고
    Logger에 생성된 addHandler()메서드로 FileHandler를 추가한다.

MyLogger.java

public class MyLogger {
	
	Logger logger = Logger.getLogger("mylogger");
	private static MyLogger instance = new MyLogger();
	
	public static final String errorLog = "log.txt";
	public static final String warningLog = "warning.txt";
	public static final String fineLog = "fine.txt";
	
	private FileHandler logFile = null;
	private FileHandler warningFile = null;
	private FileHandler fineFile = null;

	private MyLogger(){
	
			try {
				logFile = new FileHandler(errorLog, true);
				warningFile = new FileHandler(warningLog, true);
				fineFile = new FileHandler(fineLog, true);
				
			} catch (SecurityException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	
			logFile.setFormatter(new SimpleFormatter());
			warningFile.setFormatter(new SimpleFormatter());
			fineFile.setFormatter(new SimpleFormatter());
			
			logger.setLevel(Level.ALL);
			fineFile.setLevel(Level.FINE);
			warningFile.setLevel(Level.WARNING);
			
			logger.addHandler(logFile);
			logger.addHandler(warningFile);
			logger.addHandler(fineFile);
	}	
	
	
	public static MyLogger getLogger(){
		return instance;
	}

	
	public void log(String msg){
		
		logger.finest(msg);
		logger.finer(msg);
		logger.fine(msg);
		logger.config(msg);
		logger.info(msg);
		logger.warning(msg);
		logger.severe(msg);
		
	}
	
	public void fine(String msg){
		logger.fine(msg);
	}
	
	public void warning(String msg){
		logger.warning(msg);
	}
}

LoggerTest.java

public class LoggerTest {

	public static void main(String[] args) {

		MyLogger logger = MyLogger.getLogger();
		logger.log("log Test");
		
	}

}

StudentNameFormatException.java

public class StudentNameFormatException extends IllegalArgumentException{

	public StudentNameFormatException(String message){
		super(message);
	}
}

Student.java

public class Student {

	private String studentName;
	MyLogger myLogger = MyLogger.getLogger();
	
	public Student(String studentName){

		if(studentName == null){
		
			throw new StudentNameFormatException("name must not be null");
		}
		if( studentName.split(" ").length > 3)
			throw new StudentNameFormatException("이름이 너무 길어요");
		
		this.studentName = studentName;
	}

	
	public String getStudentName() {
		
		myLogger.fine("begin getStudentName()");
		
		return studentName;
	}
}

StudentTest.java

public class StudentTest {
	
	public static void main(String[] args) {
	
		MyLogger myLogger = MyLogger.getLogger();
		
		String name = null;
		try{
			Student student = new Student(name);
			
		}catch( StudentNameFormatException e ){
			myLogger.warning(e.getMessage());
		}
		
		try{
			Student student = new Student("Edward Jon Kim Test");
		}catch ( StudentNameFormatException e){
			myLogger.warning(e.getMessage());
		}
		
		Student student = new Student("James");
	}
	
}

반응형