본문 바로가기
Study/Java Study

자바스터디 10주차 (1)

by hongchii 2021. 7. 12.
728x90
반응형

2021.07.05 - [Study/Java Study] - 자바스터디 9주차 (1)

 

자바스터디 9주차 (1)

2021.07.04 - [Study/Java Study] - 자바스터디 8주차 (2) 자바스터디 8주차 (2) 2021.06.28 - [Study/Java Study] - 자바스터디 8주차 (1) 자바스터디 8주차 (1) 2021.06.23 - [Study/Java Study] - 자바스터디 7..

hong-chii.tistory.com

 

람다식이란?

메서드를 하나의 식으로 표현한 것.

메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로, 익명함수(anonymous function)라고도 한다.

int[] arr = new int[5];
Arrays.setAll(arr, (i) -> (int)(Math.random()*5)+1);

 

람다식 작성하기

메서드에서 이름과 반환타입을 제거하고 매개변수 선언부와 몸통{} 사이에 ->를 추가한다.

반환타입 메서드이름 (매개변수 선언) {
           문장들
}

반환타입 메서드이름 (매개변수 선언) {
          문장들
}

함수형 인터페이스

package Ch14;

@FunctionalInterface
interface MyFunction {
	void run();
}

class LambdaEx1 {
	static void execute(MyFunction f) {
		f.run();
	}

	static MyFunction getMyFunction() {
		MyFunction f = () -> System.out.println("f3.run()");
		return f;
	}

	public static void main(String[] args) {

		MyFunction f1 = () -> System.out.println("f1.run()");

		MyFunction f2 = new MyFunction() {
			public void run() {
				System.out.println("f2.run()");
			}
		};

		MyFunction f3 = getMyFunction();

		f1.run();
		f2.run();
		f3.run();

		execute(f1);
		execute(() -> System.out.println("run()"));
	}

}

 

스트림이란?

스트림은 데이터 소스를 추상화하고, 데이터를 다루는데 자주 사용되는 메서드들을 정의해 놓았다. 데이터 소스를 추상화 하였다는 것은, 데이터 소스가 무엇이던 간에 같은 방식으로 다룰 수 있게 되었다는 것과 코드의 재사용성이 높아진다는 것을 의미한다.

  1. 스트림은 데이터 소스를 변경하지 않는다.
  2. 스트림은 일회용이다.
  3. 스트림은 작업을 내부 반복으로 처리한다.

스트림의 연산

  1. 중간 연산 : 연산 결과가 스트림인 연산. 스트림에 연속해서 중간 연산할 수 있음
  2. 최종 연산 : 연산 결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 단 한번만 가능

지연된 연산

스트림 연산에서 한가지 중요한 점은 최종 연산이 수행되기 전까지는 중간 연산이 수행되지 않는다는 것이다. 중간 연산을 호출하는 것은 단지 어떤 작업이 수행되어야하는지를 지정해주는 것일 뿐이다. 최종 연산이 수행되어야 비로소 스트림 요소들이 중간 연산을 거쳐 최종 연산에서 소모된다.

병렬 스트림

스트림으로 데이터를 다룰 때의 장점 중 하나가 바로 병렬 처리가 쉽다는 것이다. 병렬 스트림은 내부적으로 이 프레임워크를 이용해서 자동적으로 연산을 병렬로 수행한다. 우리가 할 일은 그저 스트림에 parallel()이라는 메서드를 호출해서 병렬로 연산을 수행하도록 지시하면 될 뿐이다. 반대로 병렬로 처리되지 않게 되려면 sequential()을 호출하면 된다. 모든 스트림은 기본적으로 병렬 스트림이 아니므로 sequential()을 호출할 필요가 없다. 이 메서드는 parallel()을 호출한 것을 취소할 때만 사용한다.

 

스트림 만들기

컬렉션

Stream<T> Collection.stream()

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); // 가변 인자
Stream<Integer> intStream = list.stream(); // list를 소스로 하는 컬렉션 생성

배열

배열을 소스로 하는 스트림을 생성하는 메서드는 다음과 같이 Stream과 Arrays에 static 메서드로 정의되어 있다.

Stream<T> Stream.of(T... values) // 가변 인자
Stream<T> Stream.of(T[]) 

특정 범위의 정수

IntStream.range(int begin, int end) // end 범위 포함 X 
IntStream.rangeClosed(int begin, int end) // end 범위 포함 O

임의의 수

IntStream ints()
LongStream longs()
DoubleStream doubles()

int = new Random().ints(); // 무한 스트림
IntStream.limit(5).forEach(System.out::println); // 5개의 요소만 출력

// 지정된 범위 난수 발생 스트림, end 포함 안됨
IntStream ints(int begin, int end)

람다식 - iterate(), generate()

이 람다식에 의해 계산되는 값들을 요소로 하는 무한 스트림 생성

static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
statif <T> Stream<T> generate(Supplier<T> s)

스트림의 중간 연산

스트림 자르기 - skip(), limit()

스트림의 일부를 잘라낼 때 사용한다. skip(3)은 3개의 요소를 건너뛰고, limit(5)는 스트림의 요소를 5개로 제한한다.

Stream<T> skip(long n)
Stream<T> limit(long maxSize)

스트림의 요소 걸러내기 - filter(), distinct()

disinct()는 스트림에서 중복된 요소들을 제거하고, filter()는 주오진 조건에 맞지 않는 요소들을 걸러낸다.

Stream<T> filter(Predicate<? super T> predicate)
Stream<T> distinct()

정렬 (sorted)

Stream<T> sorted() 
Stream<T> sorted(Comparator<? super T> comparator)

변환 - map()

스트림의 요소에 저장된 값 중에서 원하는 필드만 뽑아내거나 특정 형태로 변환해야 할 때가 있다. 이 때 사용하는 것이 바로 map()이다. 이 메서드의 선언부는 아래와 같으며 매개 변수는 T 타입을 R타입으로 변환해서 반환하는 함수를 지정해야 한다.

Stream<R> map(Function<? super T, ? extends R> mapper)

// 예시
fileStream.map(File::getName) // Stream<File> -> Stream<String>
	.filter(s -> s.indexOf('.') != -1) // 확장자가 없는 것은 제외
	.map(s -> s.substring(s.indexOf('.') + 1)) // Stream<String> -> Stream<String>
	.map(String::toUpperCase) // 모두 대문자로 변환
	.distinct() // 중복 제거
	.forEach(System.out::print); 

조회 - peek()

연산이 올바르게 처리되었는지 확인하고 싶다면 peek()을 사용

.peek(s -> s.substring(s.indexOf('.') + 1))

mapToInt(), mapToLong(), mapToDouble()

Stream<T>타입의 스트림을 기본형 스트림으로 반환할 때 사용하는 것이 아래의 메서드들이다.

mapToDouble(ToDoubleFunction<? super T> mapper)
mapToInt(ToIntFunction<? super T> mapper)
mapToLong(ToLongfunction<? super T> mapper)

Optional<T> 와 OptionalInt

Optional<T>은 제네릭 클래스로 'T 타입의 객체'를 감싸는 래퍼 클래스이다. 그래서 Optional타입의 객체에는 모든 타입의 참조변수를 담을 수 있다.

public final class Optional<T> {
	private final T value; // T타입의 참조변수 
	...
}

최종 연산의 결과를 그냥 반환하는게 아니라 Optional 객체에 담아서 반환하는 것이다. 이처럼 객체에 담아서 반환을 하면, 반환된 결과가 null인지 매번 if문으로 체크하는 대신 Optional에 정의된 메서드를 통해서 간단히 처리할 수 있다. 이를 통해 null 체크를 위한 if문 없이도 NullPointerException이 발생하지 않는 보다 간절하고 안전한 코드를 작성하는 것이 가능해졌다.

스트림의 최종 연산

최종 연산은 스트림의 요소를 소모해서 결과를 만들어낸다. 그래서 최종 연산후에는 스트림이 닫히게 되고 더 이상 사용할 수 없다. 최종 연산의 결과는 스트림 요소의 합과 같은 단일 값이거나, 스트림의 요소가 담긴 배열 또는 컬렉션일 수 있다.

 

forEach()

스트림의 요소를 소모하는 최종연산이다. 반환타입이 void이므로 스트림의 요소를 출력하는 용도로 많이 사용된다.

void forEach(Consumer<? super T> action)

 

조건 검사 - allMatch(), anyMatch(), noneMatch(), findFirst() , findAny()

스트림의 요소에 대해 지정된 조건에 모든 요소가 일치하는지, 일부가 일치하는지 아니면 어떤 요소도 일치하지 않는지 확은하는데 사용할 수 있는 메서드들이다. 이 메서드들은 모두 매개변수로 Predicate를 요구하며, 연산 결과로 boolean을 반환한다.

boolean allMtch(Predicate<? super T> predicate)
boolean anyMatch(Predicate<? super T> predicate)
boolean noneMatch(Predicate<? super T> predicate)

 

통계 - count(), sum(), averate(), max(), min()

기본형 스트림이 아닌 경우에는 통계와 관련된 메서드들이 아래의 3개 뿐이다.

long count()
Optional<T> max(Comparator<? super T> comparator)
Optional<T> min(Comparator<? super T> comparator)

 

리듀싱 - reduce()

redue()는 스틤의 요소를 줄여나가면서 연산을 수행하고 최종 결과를 반환한다. 그래서 매개변수의 타입이 BinaryOperator<T>인 것이다. 처음 두 요소를 가지고 연산한 결과를 가지고 그 다음 요소와 연산한다. 이 과정에서 스트림의 요소를 하나씩 소모하게 되며, 스트림의 모든 요소를 소모하게 되면 그 결과를 반환한다.

Optional<T> reduce(BinaryOperator<T> accumulator)

 

collect()

스트림의 최종 연산 중에서 가장 복잡하면서도 유용하게 활용될 수 있는 것이 collect()이다.

collect() 스트림의 최종연산, 매개변수로 컬렉터를 필요로한다.
Collector 인터페이스, 컬렉터는 이 인터페이스를 구현해야 한다.
Collectors 클래스, static메서드로 미리 작성된 컬렉터를 제공한다. 

 

스트림을 컬렉션과 배열로 변환 - toList(), toSet(), toMap(), toCollection(), toArray()

스트림의 모든 요소르 컬렉션에 수집하려면, Collectors 클래스의 toList()와 같은 메서드를 사용하면 된다. List나 Set이 아닌 특정 컬렉션을 지정하려면, toCollection()에 해당 컬렉션의 생성자 참조를 매개변수로 넣어주면 된다.

List<String> names = stuStream.map(Student::getName).collect(Collectors.toList());
ArrayList<String> list = names.stream().collect(Collectors.toCollection(ArrayList::new));
Map<String, Person> map = personStream.collect(Collectors.toMap(p -> p.getRegId(), p -> p));

저장된 요소들을 T[] 타입으로 변환하려면, toArray()를 사용하면 된다. 

Student[] stuNames = studentStream.toArray(Student[]::new);

 

문자열 결합 - joining()

문자열 스트림의 모든 요소를 하나의 문자열로 연결해서 반환한다. 구분자를 지정해줄 수도 있고, 접두사와 접미사로 지정 가능하다. 스트림의 요소가 String이나 StringBuffer처럼 CharSequence의 자손인 경우에만 결합이 가능하므로 스트림의 요소가 문자열이 아닌 경우에는 먼저 map()을 이용해서 스트림의 요소를 문자열로 변환해야된다.

String studentNames = stuStream.map(Student::getName).collect(joining());

// Student의 toString()으로 결합
String studentInfo = stuStream.collect(joining(","));

 

그룹화와 분할 - groupingBy(), partitioningBy()

그룹화는 스트림의 요소를 특정 기준으로 그룹화하는 것을 의미하고, 분할은 스트림의 요소를 두가지 지정된 조건에 일치하는 그룹과 일치하지 않는 그룹으로의 분할을 의미한다.

// 학생들을 성별로 분할
Map<Boolean, List<Student>> stuBySex = stuStream.collect(partitioningBy(Student::isMale)); 

// stuStream을 반 별로 그룹지어 Map에 저장하는 방법
Map<Integer, List<Student>> stuByBan = stuStream.collect(groupingBy(Student::getBean));

 

Collector 구현하기

public interface Collector<T, A, R> {
	Supplier<A> supplier();
	BiConsumer<A, T> accumulator();
	BinaryOperaor<A> combiner();
	Function<A, R> finisher();
	Set<Characteristics> characteristics(); // 컬렉터의 특성이 담긴 Set 반환
	...
}

supplier() 작업 결과를 저장할 공간을 제공
accumulator() 스트림의 요소를 수집할 방법을 제공
combiner() 두 저장공간을 병합할 방법을 제공 (병렬 스트림)
finisher() 결과를 최종적으로 변환할 방법을 제공 

characteristics()는 컬렉터가 수행하는 작업의 속성에 대한 정보를 제공하기 위한 것

Characteristics.CONCURRENT 병렬로 처리할 수 있는 작업
Characteristics.UNORDERED 스트림의 요소의 순서가 유지될 필요가 없는 작업
Characteristics.IDENTITY_FINISH finisher()가 항등함수인 작업

위 3가지 속성 중에서 해당하는 것을 Set에 담아 반환하도록 구현하면 된다. 

 

 

 

 

 

자바의 정석을 토대로 공부 후 정리한 내용입니다.

728x90
반응형

'Study > Java Study' 카테고리의 다른 글

자바스터디 12주차 (1)  (0) 2021.08.01
자바스터디 11주차 (1) - 입출력IO  (0) 2021.07.25
자바스터디 9주차 (1)  (0) 2021.07.05
자바스터디 8주차 (2)  (0) 2021.07.04
자바스터디 8주차 (1)  (0) 2021.06.28

댓글