애너테이션(annotation)
소스 코드가 컴파일되거나 실행될 때 컴파일러 및 다른 프로그램에게 필요한 정보를 전달해주는 문법 요소이다.
public class ExampleClass implements ExampleInterface {
@Override
public void example() {
}
}
여기서 보이는 @Override 가 바로 애너테이션이다.
애너테이션 종류
- 표준 애너테이션 : JDK에 내장되어 있는 일반적인 애너테이션이다.
@Override
메서드 앞에만 붙일 수 있는 애너테이션으로, 선언한 메서드가 상위 클래스의 메서드를 오버라이딩하거나 추상 메서드를 구현하는 메서드라는 것을 컴파일러에게 알려주는 역할을 한다.
class SuperClass {
public void example() {
System.out.println("example() of SuperClass");
}
}
class SubClass extends SuperClass {
@Override
public void exapmle() { // 메서드 이름에 오타가 있습니다.
System.out.println("example() of SubClass");
}
}
만약 위 코드에서 @Override 가 없다면 실행 시 런타임 에러가 발생하고, 어디에서 발생했는지 원인을 찾기가 어렵다.
그러나 애너테이션을 사용하면 오버라이딩 메서드라는 것을 컴파일러가 인지하고 상위 클래스에 exapmle()이 존재하는지 확인하기 때문에 이러한 상황을 방지할 수 있다.
@Deprecated
기존에 사용하던 기술이 다른 기술로 대체되어 기존 기술을 적용한 코드를 더 이상 사용하지 않도록 유도하는 경우에 사용한다.
class OldClass {
@Deprecated
private int oldField;
@Deprecated
int getOldField() { return oldField; }
}
위 코드에서 OldClass를 인스턴스화하여 getOldField()를 호출하면 취소선이 뜨면서 경고 메세지를 출력해준다.
또한 컴파일해보면 경고 메세지가 출력된다.
즉, 기존의 코드를 다른 코드와의 호환성 문제로 삭제하기 곤란해 남겨두어야만 하지만 더 이상 사용하는 것을 권장하지 않을 때에 @Deprecated를 사용한다.
@SuppressWarnings
컴파일 경고 메시지가 나타나지 않도록 한다.
경우에 따라 경고가 발생할 것이 충분히 예상됨에도 묵인해야 할 때 주로 사용한다.
@SuppressWarnings({"deprecation", "unused", "null"})
이렇게 여러 개의 경고 유형을 나열함으로써 여러 개의 경고를 한 번에 묵인하게 할 수 있다.
@FunctionalInterface
함수형 인터페이스를 선언할 때, 컴파일러가 함수형 인터페이스의 선언이 바르게 선언되었는지 확인하도록 한다.
- 메타 애너테이션 : 애너테이션을 정의하는 데에 사용되는 애너테이션으로, 애너테이션의 적용 대상 및 유지 기간을 지정하는 데에 사용된다.
@Target
애너테이션을 적용할 대상을 지정하는 데 사용된다.
대상 타입 | 적용 범위 |
ANNOTATION_TYPE | 애너테이션 |
CONSTRUCTOR | 생성자 |
FIELD | 필드(멤버변수, 열거형 상수) |
LOCAL_VARIABLE | 지역변수 |
METHOD | 메서드 |
PACKAGE | 패키지 |
PARAMETER | 매개변수 |
TYPE | 타입(클래스, 인터페이스, 열거형) |
TYPE_PARAMETER | 타입 매개변수 |
TYPE_USE | 타입이 사용되는 모든 대상 |
import static java.lang.annotation.ElementType.*;
//import문을 이용하여 ElementType.TYPE 대신 TYPE과 같이 간단히 작성할 수 있습니다.
@Target({FIELD, TYPE, TYPE_USE}) // 적용대상이 FIELD, TYPE
public @interface CustomAnnotation { } // CustomAnnotation을 정의
@CustomAnnotation // 적용대상이 TYPE인 경우
class Main {
@CustomAnnotation // 적용대상이 FIELD인 경우
int i;
}
@Documented
애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 하는 애너테이션 설정이다.
자바에서 제공하는 표준 애너테이션과 메타 애너테이션 중 @Override 와 @SuppressWarnings 를 제외하고 모두 적용되어 있다.
@Documented
@Target(ElementType.Type)
public @interface CustomAnnotation { }
@Inherited
하위 클래스가 애너테이션을 상속받도록 한다.
상위 클래스에 @Inherited 애너테이션을 붙이면 하위 클래스도 동일하게 적용이 된다.
@Inherited // @SuperAnnotation이 하위 클래스까지 적용
@interface SuperAnnotation{ }
@SuperAnnotation
class Super { }
class Sub extends Super{ } // Sub에 애너테이션이 붙은 것으로 인식
@Retention
애너테이션의 지속 시간을 결정하는 데에 사용한다.
유지 정책 | 설명 |
SOURCE | 소스 파일에 존재, 클래스파일에는 존재하지 않음 |
CLASS | 클래스 파일에 존재, 실행시에 사용불가, 기본값 |
RUNTIME | 클래스 파일에 존재, 실행시에 사용가능 |
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
//오버라이딩이 제대로 되었는지 컴파일러가 확인하는 용도
//클래스 파일에 남길 필요 없이 컴파일시에만 확인하고 사라짐
public @interface Override(){ }
@Repeatable
애너테이션을 여러 번 붙일 수 있도록 허용한다는 의미를 가지고 있다.
@interface Works { // 여러개의 Work애너테이션을 담을 컨테이너 애너테이션 Works
Work[] value();
}
@Repeatable(Works.class) // 컨테이너 애너테이션 지정
@interface Work {
String value();
}
같은 이름의 애너테이션이 여러번 적용될 수 있기 때문에 애너테이션들을 하나로 묶어주는 애너테이션도 별도로 작성해야 한다.
람다(Lamda)
람다식이란 함수형 프로그래밍 기법을 지원하는 자바의 문법요소이다.
간단히 말해서 메서드를 하나의 ‘식(expression)’으로 표현한 것으로 코드를 매우 간결하고 명확하게 표현할 수 있다는 장점이 있다.
람다식의 기본 문법
//기존 메서드 표현 방식
void sayhello() {
System.out.println("HELLO!")
}
//위의 코드를 람다식으로 표현한 식
() -> System.out.println("HELLO!")
람다식은 기본적으로 반환 타입과 이름을 생략할 수 있다.
그래서 람다식을 이름이 없는 함수, 즉 익명 함수라고 부르기도 한다.
또한 특정 조건이 충족되면 람다식을 더욱 축약하여 표현할 수 있다.
메서드 바디에 실행문이 하나만 존재할 때 중괄호와 return 문을 생략할 수 있으며, 이 경우 세미콜론까지 생략해야 한다.
// 기존 방식
int sum(int num1, int num2) {
return num1 + num2;
}
// 람다식
(int num1, int num2) -> {
num1 + num2
}
// 중괄호, return문 생략
(int num1, int num2) -> num1 + num2
함수형 인터페이스
함수는 반드시 클래스 안에서 정의되어야 하기 때문에 메서드가 독립적으로 있을 수 없고, 반드시 클랙스 객체를 먼저 생성한 후 생성한 객체로 메서드를 호출해야 한다.
이 말은 즉, 람다식 또한 사실은 객체라는 것이다.
더 정확히는 이름이 없기 때문에 익명 객체라고 할 수 있다.
익명 객체는 익명 클래스를 통해 만들 수 있는데, 익명 클래스란 객체의 선언과 생성을 동시에 하여 오직 하나의 객체를 생성하고 단 한번만 사용되는 일회용 클래스이다.
람다식이 객체라고 한다면 이 객체에 접근하고 사용하기 위한 참조변수가 필요하다.
그런데 기존에 객체를 생성할 때 만들었던 Object 클래스에는 sum 이라는 메서드가 없기 때문에 Object 타입의 참조변수에 담는다고 하더라도 sum 메서드는 사용할 수 없다.
public class LamdaExample1 {
public static void main(String[] args) {
// 람다식 Object obj = (num1, num2) -> num1 + num2; 로 대체 가능
Object obj = new Object() {
int sum(int num1, int num2) {
return num1 + num1;
}
};
obj.sum(1, 2);
}
}
출력 결과
java: cannot find symbol
symbol: method sum(int,int)
location: variable obj of type java.lang.Object
이러한 문제를 해결하기 위해 사용하는 자바의 문법 요소가 함수형 인터페이스이다.
함수형 인터페이스에는 단 하나의 추상 메서드만 선언될 수 있는데, 이는 람다식과 인터페이스의 메서드가 1:1로 매칭되어야 하기 때문이다.
public class LamdaExample1 {
public static void main(String[] args) {
/* Object obj = new Object() {
int sum(int num1, int num2) {
return num1 + num1;
}
};
*/
ExampleFunction exampleFunction = (num1, num2) -> num1 + num2;
System.out.println(exampleFunction.sum(10,15));
}
@FunctionalInterface // 컴파일러가 인터페이스가 바르게 정의되었는지 확인하도록 합니다.
interface ExampleFunction {
int sum(int num1, int num2);
}
// 출력값
25
함수형 인터페이스 ExampleFunction에 추상 메서드 sum()을 정의했다.
이 인터페이스는 람다식을 참조할 참조변수를 선언할 때, 타입으로 사용하기 위해 필요하다.
매개변수와 리턴값이 없는 람다식
@FunctionalInterface
interface MyFunctionalInterface {
void accept();
}
public class MyFunctionalInterfaceExample {
public static void main(String[] args) throws Exception {
MyFunctionalInterface example = () -> System.out.println("accept() 호출");
example.accept();
}
}
// 출력값
accept() 호출
매개변수가 있는 람다식
@FunctionalInterface
public interface MyFunctionalInterface {
void accept(int x);
}
public class MyFunctionalInterfaceExample {
public static void main(String[] args) throws Exception {
MyFunctionalInterface example;
example = (x) -> {
int result = x * 5;
System.out.println(result);
};
example.accept(2);
example = (x) -> System.out.println(x * 5);
example.accept(2);
}
}
// 출력값
10
10
리턴값이 있는 람다식
@FunctionalInterface
public interface MyFunctionalInterface {
int accept(int x, int y);
}
public class MyFunctionalInterfaceExample {
public static void main(String[] args) throws Exception {
MyFunctionalInterface example;
example = (x, y) -> {
int result = x + y;
return result;
};
int result1 = example.accept(2, 5);
System.out.println(result1);
example = (x, y) -> { return x + y; };
int result2 = example.accept(2, 5);
System.out.println(result2);
example = (x, y) -> x + y;
//return문만 있을 경우, 중괄호 {}와 return문 생략가능
int result3 = example.accept(2, 5);
System.out.println(result3);
example = (x, y) -> sum(x, y);
//return문만 있을 경우, 중괄호 {}와 return문 생략가능
int result4 = example.accept(2, 5);
System.out.println(result4);
}
public static int sum(int x, int y){
return x + y;
}
}
//출력값
7
7
7
7
메서드 레퍼런스
메서드 참조는 람다식에서 불필요한 매개변수를 제거할 때 주로 사용된다.
아래처럼 람다식은 종종 기존 메서드를 단순히 호출만 하는 경우가 있다.
(left, right) -> Math.max(left, right)
이럴 경우 다음과 같이 메서드 참조를 이용하면 깔끔하게 처리할 수 있다.
IntBinaryOperator operato = Math :: max;
정적 메서드와 인스턴스 메서드 참조
정적 메서드 참조
클래스 :: 메서드
인스턴스 메서드 참조 (객체를 먼저 생성해야 한다)
참조 변수 :: 메서드
//Calculator.java
public class Calculator {
public static int staticMethod(int x, int y) {
return x + y;
}
public int instanceMethod(int x, int y) {
return x * y;
}
}
import java.util.function.IntBinaryOperator;
public class MethodReferences {
public static void main(String[] args) throws Exception {
IntBinaryOperator operator;
/*정적 메서드
클래스이름::메서드이름
*/
operator = Calculator::staticMethod;
System.out.println("정적메서드 결과 : " + operator.applyAsInt(3, 5));
/*인스턴스 메서드
인스턴스명::메서드명
*/
Calculator calculator = new Calculator();
operator = calculator::instanceMethod;
System.out.println("인스턴스 메서드 결과 : "+ operator.applyAsInt(3, 5));
}
}
/*
정적메서드 결과 : 8
인스턴스 메서드 결과 : 15
*/
생성자 참조
생성자를 참조한다는 것은 객체 생성을 의미한다.
단순히 객체를 생성하고 리턴하도록 구성된 람다식은 생성자 참조로 대치가 가능하다.
클래스 :: new
//Member.java
public class Member {
private String name;
private String id;
public Member() {
System.out.println("Member() 실행");
}
public Member(String id) {
System.out.println("Member(String id) 실행");
this.id = id;
}
public Member(String name, String id) {
System.out.println("Member(String name, String id) 실행");
this.id = id;
this.name = name;
}
public String getName() {
return name;
}
public String getId() {
return id;
}
}
import java.util.function.BiFunction;
import java.util.function.Function;
public class ConstructorRef {
public static void main(String[] args) throws Exception {
Function<String, Member> function1 = Member::new;
Member member1 = function1.apply("kimcoding");
BiFunction<String, String, Member> function2 = Member::new;
Member member2 = function2.apply("kimcoding", "김코딩");
}
}
/*
Member(String id) 실행
Member(String name, String id) 실행
*/
function, BiFunction 은 자바에서 기본 제공하는 함수형 인터페이스이다.
스트림(Stream)
스트림의 개념
배열 및 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 하는 반복자이다.
스트림을 도입하기 전 저장된 데이터들에 반복적으로 접근하여 가공하기 위해 for문과 Iterator를 활용해왔다.
for문과 Iterator를 사용하는 경우 코드가 길고 복잡해질 수있다는 단점이 있다.
//Iterator 사용
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class PrintNumberOperator {
public static void main(String[] args) {
// 각 숫자를 배열화
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
// Iterator 생성
Iterator<Integer> it = list.iterator();
// 리스트를 순회하며 값 출력
while (it.hasNext()) {
int num = it.next();
System.out.print(num);
}
}
}
//출력값
12345
//Stream 사용
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class PrintNumberOperatorByStream {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = list.stream();
stream.forEach(System.out::print);
}
}
//출력값
12345
스트림을 사용하면 선언형 프로그래밍 방식으로 데이터를 처리할 수 있어 보다 직관적인 코드 작성이 가능하다.
선언형 프로그래밍은 “어떻게”가 아닌 “무엇”에 집중하여 코드를 작성하는 방법론을 의미한다.
즉, 내부의 동작 원리를 모르더라도 어떤 코드가 어떤 역할을 하는지 직관적으로 이해할 수 있다.
//명령형 프로그래밍 방식
import java.util.List;
public class ImperativeProgramming {
public static void main(String[] args){
// List에 있는 숫자들 중에서 4보다 큰 짝수의 합계 구하기
List<Integer> numbers = List.of(1, 3, 6, 7, 8, 11);
int sum = 0;
for(int number : numbers){
if(number > 4 && (number % 2 == 0)){
sum += number;
}
}
System.out.println("명령형 프로그래밍을 사용한 합계 : " + sum);
}
}
//출력값
명령형 프로그래밍을 사용한 합계 : 14
//선언형 프로그래밍 방식
import java.util.List;
public class DeclarativePrograming {
public static void main(String[] args){
// List에 있는 숫자들 중에서 4보다 큰 짝수의 합계 구하기
List<Integer> numbers = List.of(1, 3, 6, 7, 8, 11);
int sum =
numbers.stream()
.filter(number -> number > 4 && (number % 2 == 0))
.mapToInt(number -> number)
.sum();
System.out.println("선언형 프로그래밍을 사용한 합계 : " + sum);
}
}
//출력값
선언형 프로그래밍을 사용한 합계 : 14
또 하나의 장점으로 데이터 소스가 무엇이냐에 관계없이 같은 방식으로 데이터를 가공/처리 할 수 있다는 것이다.
예를 들면, 기존 방식으로 정렬 기능을 수행하는 과정에서 배열의 경우 Arrays.sort()를, List의 경우 Collections.sort()를 사용해야 한다는 단점을 들 수 있다.
스트림의 특징
- 스트림 처리 과정은 생성, 중간 연산, 최종 연산 세 단계의 파이프라인으로 구성될 수 있다.
- 스트림은 원본 데이터 소스를 변경하지 않는다.(read-only)
- 스트림은 일화용이다.(onetime-only)
- 스트림은 내부 반복자이다.
외부 반복자란 개발자가 코드로 직접 컬렉션의 요소를 반복해서 가져오는 코드 패턴을 의미하며 for문, while문이 대표적이다.
반면 내부 반복자는 데이터 처리 코드만 컬렉션 내부로 주입해서 그 안에서 모든 데이터 처리가 이루어지도록 한다.
스트림의 생성
- 배열 스트림 생성
Arrays 클래스의 stream() 메서드 또는 Stream 클래스의 of() 메서드를 사용한다.
//Arrays.stream() 사용
public class StreamCreator {
public static void main(String[] args) {
// 문자열 배열 선언 및 할당
String[] arr = new String[]{"김코딩", "이자바", "박해커"};
// 문자열 스트림 생성
Stream<String> stream = Arrays.stream(arr);
// 출력
stream.forEach(System.out::println);
}
}
// 출력값
김코딩
이자바
박해커
//Stream.of() 사용
import java.util.stream.Stream;
public class StreamCreator {
public static void main(String[] args) {
// 문자열 배열 선언 및 할당
String[] arr = new String[]{"김코딩", "이자바", "박해커"};
// 문자열 스트림 생성
Stream<String> stream = Stream.of(arr);
// 출력
stream.forEach(System.out::println);
}
}
// 출력값
김코딩
이자바
박해커
Arrays 클래스에는 int, long, double 과 같은 기본형 배열을 데이터 소스로 스트림을 생성하는 메서드도 있다.
이 중 IntStream의 경우 일반적인 Stream 클래스에는 없는 숫자와 관련한 여러 메서드들이 정의되어 있다.
주의해야 할 점은 숫자 연산과 관련된 대부분의 메서드는 최종 연산자이기 대문에 최초 사용 시 스트림이 닫히게 된다는 것이다.
예를 들면 sum() 메서드를 호출한 이후 다시 average() 메서드를 호출하면 에러가 발생한다.
- 컬렉션 스트림 생성
컬렉션 타입(List, Set 등)의 경우, Collection에 정의된 stream() 메서드를 사용하여 스트림을 생성할 수 있다.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class StreamCreator {
public static void main(String[] args) {
// 요소들을 리스트
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
Stream<Integer> stream = list.stream();
stream.forEach(System.out::print);
}
}
//출력값
1234567
- 임의의 수 스트림 생성
import java.util.Random;
import java.util.stream.IntStream;
public class StreamCreator {
public static void main(String[] args) {
// 난수 생성
IntStream ints = new Random().ints();
ints.forEach(System.out::println);
}
}
위 코드를 실행해보면 int 형의 범위에서 출력값이 무한대로 생성되는데 이렇게 스트림의 크기가 정해지지 않은 것을 무한 스트림 이라고 한다.
무한 스트림은 보통 limit() 메서드와 함께 사용하거나 매개변수로 스트림의 사이즈를 전달해서 범위를 제한한다.
IntStream ints = new Random().ints(5);
IntStream ints = new Random().ints().limit(5);
range()나 rangeClosed() 메서드를 사용하면 특정 범위의 정수 값을 스트림으로 생성해서 반환하는 것도 가능하다.
IntStream intStream = IntStream.range(1, 10);
IntStream intStream = IntStream.rangeClosed(1, 10);
(int start, int end) 라고 할 때 range()의 경우 end는 포함되지 않고 rangeClosed()는 end가 포함되는 차이점이 있다.
스트림의 중간 연산
스트림의 중간 연산에서는 필터링, 매핑, 정렬 등이 이루어진다.
- 필터링(filter(), distinct())
조건에 맞는 데이터들만 정제하는 역할을 한다.
import java.util.Arrays;
import java.util.List;
public class FilteringExample {
public static void main(String[] args) throws Exception {
List<String> names = Arrays.asList("김코딩", "이자바", "박해커", "김코딩", "박해커");
names.stream()
.distinct() //중복 제거
.forEach(element -> System.out.println(element));
System.out.println();
names.stream()
.filter(element -> element.startsWith("김")) // 김씨 성을 가진 요소만 필터링
.forEach(element -> System.out.println(element));
System.out.println();
names.stream()
.distinct() //중복제거
.filter(element -> element.startsWith("김")) // 김씨 성을 가진 요소만 필터링
.forEach(element -> System.out.println(element));
}
}
// 출력값
김코딩
이자바
박해커
김코딩
김코딩
김코딩
- 매핑 (map())
원하는 필드만 추출하거나 특정 형태로 변환할 때 사용하는 중간연산자이다.
//예시 1
names.stream()
.map(element -> element.toUpperCase()) // 요소들을 하나씩 대문자로 변환
.forEach(element->System.out.println(element));
//예시 2
list.stream().map(number -> number * 3).forEach(System.out::println);
- 정렬 (sorted())
//오름차순
List<String> animals = Arrays.asList("Tiger", "Lion", "Monkey", "Duck", "Horse", "Cow");
animals.stream().sorted().forEach(System.out::println);
//내림차순
List<String> animals = Arrays.asList("Tiger", "Lion", "Monkey", "Duck", "Horse", "Cow");
// 인자값에 Comparator 인터페이스에 규정된 메서드 사용
animals.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);
- 그 외 자주 쓰이는 중간 연산자 (skip(), limit(), peek())
//skip()
import java.util.stream.IntStream;
public class IntermediateOperationExample {
public static void main(String[] args) {
// 1~10 범위의 정수로 구성된 스트림 생성
IntStream intStream = IntStream.rangeClosed(1, 10);
// 앞의 5개의 숫자를 건너뛰고 숫자 6부터 출력
intStream.skip(5).forEach(System.out::println);
}
}
// 출력값
6
7
8
9
10
//limit()
import java.util.stream.IntStream;
public class IntermediateOperationExample {
public static void main(String[] args) {
// 1~10 범위의 정수로 구성된 스트림 생성
IntStream intStream = IntStream.rangeClosed(1, 10);
// 앞에서부터 5개의 숫자만 출력
intStream.limit(5).forEach(System.out::println);
}
}
// 출력값
1
2
3
4
5
//peek()
import java.util.stream.IntStream;
public class IntermediateOperationExample {
public static void main(String[] args) {
// 요소들을 사용하여 IntStream 생성
IntStream intStream3 = IntStream.of(1, 2, 2, 3, 3, 4, 5, 5, 7, 7, 7, 8);
// 짝수만 필터링하여 합계 구하기
int sum = intStream3.filter(element -> element % 2 == 0)
.peek(System.out::println)
.sum();
System.out.println("합계 = " + sum);
}
}
// 출력값
2
2
4
8
합계 = 16
peek()의 경우 forEach()와 마찬가지로 요소들을 순회하며 특정 작업을 수행한다.
forEach()는 최종 연산자이기 때문에 마지막 단 한번만 사용할 수 있다.
그래서 디버깅 용도로 peek()을 종종 사용한다.
스트림의 최종 연산
- 기본 집계 (sum(), count(), average(), max(), min())
import java.util.Arrays;
public class TerminalOperationExample {
public static void main(String[] args) {
// int형 배열 생성
int[] intArray = {1,2,3,4,5};
// 카운팅
long count = Arrays.stream(intArray).count();
System.out.println("intArr의 전체 요소 개수 " + count);
// 합계
long sum = Arrays.stream(intArray).sum();
System.out.println("intArr의 전체 요소 합 " + sum);
// 평균
double average = Arrays.stream(intArray).average().getAsDouble();
System.out.println("전체 요소의 평균값 " + average);
// 최대값
int max = Arrays.stream(intArray).max().getAsInt();
System.out.println("최대값 " + max);
// 최소값
int min = Arrays.stream(intArray).min().getAsInt();
System.out.println("최소값 " + min);
// 배열의 첫 번째 요소
int first = Arrays.stream(intArray).findFirst().getAsInt();
System.out.println("배열의 첫번째 요소 " + first);
}
}
// 출력값
intArr의 전체 요소 개수 5
intArr의 전체 요소 합 15
전체 요소의 평균값 3.0
최대값 5
최소값 1
배열의 첫번째 요소 1
이 때, 최종 연산자 뒤에 getAsInt() 혹은 getAsDouble()메서드가 붙을 수 있는 이유는 객체로 반환되는 값을 다시 기본형으로 변환하기 위해 사용되는 메서드로 스트림 파이프라인과는 관계가 없기 때문이다.
- 매칭 (allMatch(), anyMatch(), noneMatch())
스트림의 각 데이터 요소들이 특정한 조건을 충족하는지 검사하며 그 결과를 boolean 값으로 반환한다.
import java.util.Arrays;
public class TerminalOperationExample {
public static void main(String[] args) throws Exception {
// int형 배열 생성
int[] intArray = {2,4,6};
// allMatch()
boolean result = Arrays.stream(intArray).allMatch(element-> element % 2 == 0);
System.out.println("요소 모두 2의 배수인가요? " + result);
// anyMatch()
result = Arrays.stream(intArray).anyMatch(element-> element % 3 == 0);
System.out.println("요소 중 하나라도 3의 배수가 있나요? " + result);
// noneMatch()
result = Arrays.stream(intArray).noneMatch(element -> element % 3 == 0);
System.out.println("요소 중 3의 배수가 하나도 없나요? " + result);
}
}
// 출력값
요소 모두 2의 배수인가요? true
요소 중 하나라도 3의 배수가 있나요? true
요소 중 3의 배수가 하나도 없나요? false
- 요소 소모 (reduce())
첫 번째와 두 번째 요소를 가지고 연산을 수행하고, 그 결과와 다음 세 번째 요소를 가지고 또 다시 연산을 수행하는 식으로 연산이 끝날 때까지 반복한다.
import java.util.Arrays;
public class TerminalOperationExample {
public static void main(String[] args) throws Exception {
int[] intArray = {1,2,3,4,5};
// sum()
long sum = Arrays.stream(intArray).sum();
System.out.println("intArray 전체 요소 합: " + sum);
// 초기값이 없는 reduce()
int sum1 = Arrays.stream(intArray)
.map(element -> element * 2)
.reduce((a , b) -> a + b)
.getAsInt();
System.out.println("초기값이 없는 reduce(): " + sum1);
// 초기값이 있는 reduce()
int sum2= Arrays.stream(intArray)
.map(element -> element * 2)
.reduce(5, (a ,b) -> a + b);
System.out.println("초기값이 있는 reduce(): " + sum2);
}
}
// 출력값
intArray 전체 요소 합: 15
초기값이 없는 reduce(): 30
초기값이 있는 reduce(): 35
- 요소 수집 (collect())
중간 연산을 통한 요소들의 데이터 가공 후 요소들을 수집하는 최종 처리 메서드이다.
스트림의 요소들을 List, Set, Map 등 다른 타입의 결과로 수집하고 싶은 경우에 사용한다.
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class TerminalOperationExample {
public static void main(String[] args) {
// Student 객체로 구성된 배열 리스트 생성
List<Student> totalList = Arrays.asList(
new Student("김코딩", 100, Student.Gender.Male),
new Student("박해커", 80, Student.Gender.Male),
new Student("이자바", 90, Student.Gender.Female),
new Student("나미녀", 60, Student.Gender.Female)
);
// 스트림 연산 결과를 Map으로 반환
Map<String, Integer> maleMap = totalList.stream()
.filter(s -> s.getGender() == Student.Gender.Male)
.collect(Collectors.toMap(
student -> student.getName(), // Key
student -> student.getScore() // Value
));
// 출력
System.out.println(maleMap);
}
}
class Student {
public enum Gender {Male, Female};
private String name;
private int score;
private Gender gender;
public Student(String name, int score, Gender gender) {
this.name = name;
this.score = score;
this.gender = gender;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
public Gender getGender() {
return gender;
}
}
// 출력값
{김코딩=100, 박해커=80}
'CodeStates_BE_44 > TIL' 카테고리의 다른 글
Day 18. [Java] 스레드, JVM (0) | 2023.03.10 |
---|---|
Day 17. [Java] 파일 입출력 (0) | 2023.03.09 |
Day 15. [Java] 컬렉션 (1) | 2023.03.07 |
Day 12. [Java] 객체지향 프로그래밍 심화_다형성, 추상화 (0) | 2023.02.28 |
Day 11. [Java] 객체지향 프로그래밍 심화_상속, 캡슐화 (0) | 2023.02.27 |