Nellie's Blog

[TIL-221214수] 항해99 31일차(Optional, Stream) 본문

회고록

[TIL-221214수] 항해99 31일차(Optional, Stream)

Nellie Kim 2022. 12. 14. 23:42
728x90

배운점

Optional

Java8에서는 Optional<T> 클래스를 사용해 NPE를 방지할 수 있도록 도와준다. 

Optional<T>는 null이 올 수 있는 값을 감싸는 Wrapper 클래스로, 참조하더라도 NPE가 발생하지 않도록 도와준다. Optional 클래스는 아래와 같은 value에 값을 저장하기 때문에 값이 null이더라도 바로 NPE가 발생하지 않으며, 클래스이기 때문에 각종 메소드를 제공해준다.

 

Optional 클래스는 내부에서 static 변수로 EMPTY 객체를 미리 생성해서 가지고 있다. 이러한 이유로 빈 객체를 여러 번 생성해줘야 하는 경우에도 1개의 EMPTY 객체를 공유함으로써 메모리를 절약하고 있다.

 

Optional은 null 또는 값을 감싸서 NPE(NullPointerException)로부터 부담을 줄이기 위해 등장한 Wrapper 클래스이다. Optional은 값을 Wrapping하고 다시 풀고, null 일 경우에는 대체하는 함수를 호출하는 등의 오버헤드가 있으므로 잘못 사용하면 시스템 성능이 저하된다.

그렇기 때문에 메소드의 반환 값이 절대 null이 아니라면 Optional을 사용하지 않는 것이 좋다.

즉, Optional은 메소드의 결과가 null이 될 수 있으며, null에 의해 오류가 발생할 가능성이 매우 높을 때 반환값으로만 사용되어야 한다.

 

public final class Optional<T> {

    private static final Optional<?> EMPTY = new Optional<>();
    private final T value;
    
    private Optional() {
        this.value = null;
    }

    ...

}

 

Optional.of() - 값이 Null이 아닌 경우

만약 어떤 데이터가 절대 null이 아니라면 Optional.of()로 생성할 수 있다. 만약 Optional.of()로 Null을 저장하려고 하면 NullPointerException이 발생한다.

// Optional의 value는 절대 null이 아니다.
Optional<String> optional = Optional.of("MyName");

 

Optional.ofNullbale() - 값이 Null일수도, 아닐수도 있는 경우

만약 어떤 데이터가 null이 올 수도 있고 아닐 수도 있는 경우에는 Optional.ofNullbale로 생성할 수 있다. 그리고 이후에 orElse 또는 orElseGet 메소드를 이용해서 값이 없는 경우라도 안전하게 값을 가져올 수 있다.

// Optional의 value는 값이 있을 수도 있고 null 일 수도 있다.
Optional<String> optional = Optional.ofNullable(getName());
String name = optional.orElse("anonymous"); // 값이 없다면 "anonymous" 를 리턴

 

Optional 사용법 예시

기존에는 아래와 같이 null 검사를 한 후에 null일 경우에는 새로운 객체를 생성해주어야 했다. 이러한 과정을 코드로 나타내면 다소 번잡해보이는데, Optional<T>와 Lambda를 이용하면 해당 과정을 보다 간단하게 표현할 수 있다.

// Java8 이전
List<String> names = getNames();
List<String> tempNames = list != null 
    ? list 
    : new ArrayList<>();

// Java8 이후
List<String> nameList = Optional.ofNullable(getNames())
    .orElseGet(() -> new ArrayList<>());

 

 

스트림(Stream)

스트림은 java8부터 추가된 배열이나 컬렉션의 요소(List, Map, Set)들을 더 편리하게 가공하고 처리하도록 해주는 반복자

 

람다식 사용과 연산 과정에서 불필요한 변수를 남기지 않기 때문에 코드가 간결해지고,

내부 반복자를 사용해 병렬처리가 쉽다는 장점이 있다.

 

스트림 사용법

- 스트림 생성 ->  가공 및 연산 -> 반환

 

일반적인 배열로 중복을 제거하고 내림차순으로 정렬한 뒤 List의 형태로 반환하기

  1. Set에 배열 값을 넣어 중복제거
  2. Set을 Iterator에 담기
  3. Iterator의 값을 리스트에 넣기
  4. List를 역정렬 후 반환

 

선언  - 배열, 컬렉션(list, set, map) 등을 스트림 형태로 만들기

Stream<데이터타입> stream명 = Arrays.stream(배열명);

Stream<데이터타입> stream명 = 리스트명.stream();

Stream<데이터타입> stream명 = Stream.of('값', '값'....);

 

각각 배열과 컬렉션을 사용하는 경우

아니면 직접 값을 넣어 사용하는 경우인데

꼭 stream을 선언한 후 값을 넣고 사용하는 것이 아니라

 

Arrays.stream(배열명).가공메소드...

리스트명.stream.가공메소드...      이런 식으로 바로 사용해도 된다.

 

주요 메소드

.count() 배열이나 컬렉션의 크기 리턴
.sorted()  /  .sorted(Comparator.reverseOrder()) 정렬 / 역정렬
.distince() 중복 삭제
.max(Type :: compare)  /  .min(Type :: compare)  최대, 최소 값
.average()  /  .sum() 평균 / 합계
.map((파라미터) -> 코드) 스트림 원소를 변환
.forEach((파라미터) -> {코드}) 각 인덱스의 값을 파라미터로 넘겨 코드를 반복 수행
filter(파라미터) -> {코드}) 조건에 부합하지 않는 값을 걸러낸다.
.reduce(값, 데이터타입::sum) 스트림의 값을 모두 하나로 합칠 때 사용
.collect(Collectors.toList()) / toList() -> toSet(), toMap()
스트림을 List(Set, Map) 타입으로 반환 
.getAsType() / Int, Long, Double, Float... 최종 값을 지정한 타입의 형태로 반환

 

 

예제 코드 1 - 40세 이하의 멤버 이름 추출하기

class Member {
	public static int MALE = 0;
	public static int FEMALE = 1;
	private String name;
	private int sex;
	private int age;

	public Member(String name, int sex, int age) {
		this.name = name; this.sex = sex; this.age = age;
	}
	public String getName() { return name; }
	public int getSex() { return sex; }
	public int getAge() { return age; }
	
}
List<Member> list = Arrays.asList(
		new Member("홍길동", Member.MALE, 30), 
		new Member("김나리", Member.FEMALE, 20),
		new Member("신용권", Member.MALE, 45), 
		new Member("박수미", Member.FEMALE, 27),
		new Member("홍길동", Member.MALE, 50));

 

List<String> arr = list.stream() // 스트림 변환
	.filter(a -> a.getAge() < 40) // 40세 이하의 멤버들만 필터링
	.map(Member::getName) // 이름만 가져오기
	.sorted() // 정렬
	.collect(Collectors.toList());  // List 타입으로 반환

arr.stream()
	.forEach(System.out::println); // arr의 요소들을 전부 출력

 

 

예제코드2

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));

for (String value : list) {

        if (StringUtils.equals(value, "b") {
        System.out.println("값 : " + value);
    }
}
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));
list.stream()
    .filter("b"::equals)    
    .forEach(System.out::println);

 

예제코드3

 

Arrays.stream(배열명).boxed()로 스트림을 생성한 뒤

.distinct()로 중복 다 떨어내고

.sorted(Comparator.reverseOrder())로 역정렬한 뒤

.collect(Collectors.toList())로 List 형태로 반환하면 끝.

 

 

 

느낀점

혼공자 스터디에서 컬렉션 부분 발표를 했다. 그 후에는 계속 시큐리티를 공부했다. 시큐리티는 최소 2주는 공부해야 할 것 같은데 프로젝트가 빡세긴하다. 갑자기 두통이 와서 저녁엔 제대로 공부를 못했다. 스트레스 받지 말자.

 

 

 

https://wakestand.tistory.com/419

https://hbase.tistory.com/171

https://lasbe.tistory.com/74

https://mangkyu.tistory.com/70