Notice
Recent Posts
Recent Comments
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
10-11 00:15
Archives
Today
Total
관리 메뉴

Developer_Neo

[코드스테이츠 백엔드 2기(40기) SEB BE] 18일차 본문

코드스테이츠

[코드스테이츠 백엔드 2기(40기) SEB BE] 18일차

_Neo_ 2022. 7. 18. 15:38
반응형

오늘 나의 학습 목표는 무엇인가요?

- Enum의 개념을 이해하고 설명한다.
- 애너테이션의 개념을 이해하고 설명하며 사용자 정의 애너테이션을 작성해보자
- 람다식이 무엇이고, 다루는 방법을 이해하고 설명할 수 있다.
- 스트림의 특징과 사용 목적을 이해하며 주요메서드를 사용해보자

Enum

- 여러 상수들을 보다 편리하게 선언할 수 있도록 만들어진 자바의 문법요소

- 상수명의 중복을 피하고, 타입에 대한 안정성을 보장

( 상수는 변하지 않는 값이며 final 키워드를 사용하여 선언)

// 여러 상수를 정의하기 위한 예전 방식 
public static final int SPRING = 1;
public static final int SUMMER = 2;
public static final int FALL = 3;
public static final int WINTER = 4;

원래 위와 같이 final을 통해 전역변수로 상수를 설정을 해왔었습니다. 하지만 이렇게 하는 경우 상수명이 중복되는 경우가 있다면 컴파일 에러가 발생합니다.

 

만약 SPRING에 해당하는 것이 계절의 봄과 프레임워크의 SPRING을 나타내야한다고 해봅시다. 위의 경우로 final을 통해 선언하는 경우 상수명 중복으로 인해 컴파일에러가 발생합니다. 왜냐하면 상수명이 중복되면 변하지 않는 값인데 니증에 똑같은 상수명으로 선언해 값을 바꾸었기 때문이고, 어떤 값을 사용해야하는지 모르기 때문이다.

 

그래서 이러한 문제를 해결하기 위해 interface를 사용해 일차적으로 해결할 수 있다. 하지만 문제점이 있다.

interface Seasons {
	int SPRING = 1, SUMMER = 2, FALL = 3, WINTER = 4;
}

interface Frameworks {
	int DJANGO = 1, SPRING = 2, NEST = 3, EXPRESS = 4;
}

if (Seasons.SPRING == Frameworks.SPRING) {...생략...}

이렇게 진행할 경우 중복상수명의 문제는 피해집니다. 하지만 타입 안정성이라는 문제가 생깁니다.

 타입안정성이라는 문제가 생기는 이유는 모르겠다.
한가지 떠오르는 건 서로 다른 인터페이스로써 안에 있는 변수들을 비교하는 것인데 서로 다른 인터페이스라는 것이 타입이 되며 서로 다른 타입에 있는 변수들을 비교해서 타입 안정성이 생긴다고 생각했다.

if문을 비교하면 에러가 나지 않습니다. 여기에서 타입안정성이 떨어진다는 것입니다. 서로 다른 인터페이스에 선언이 되어 있는 것으로 비교하여 타입이 다른것이 비교라고 인식해 컴파일 에러가 발생해야하는데 발생하지 않기 때문입니다.

 

이 타입안정성이 해결하기 위해 서로 다른 객체로 만들어서 실행을 하면 문제가 사라집니다.

class Seasons {
    public static final Seasons SPRING = new Seasons();
    public static final Seasons SUMMER = new Seasons();
    public static final Seasons FALL   = new Seasons();
    public static final Seasons WINTER = new Seasons();
}

class Frameworks {
    public static final Frameworks DJANGO  = new Frameworks();
    public static final Frameworks SPRING  = new Frameworks();
    public static final Frameworks NEST    = new Frameworks();
    public static final Frameworks EXPRESS = new Frameworks();
}

이렇게 객체를 생성한다면 상수명 중복과 타입안정성 문제를 해결할 수 있습니다만 코드가 길어지고 사용자 정의 타입이라서 switch문에는 사용하기 어렵다는 것입니다.

(switch문의 조건은 char, byte, short, int, Character, Byte, Short, Integer, String, enum 타입만 가능하다.)

따라서 이것들을 효과적으로 선언하기 위해서 enum이 만들어졌습니다.

 

enum을 활용한 상수정의

enum Seasons { SPRING, SUMMER, FALL, WINTER }
enum Frameworks { DJANGO, SPRING, NEST, EXPRESS }

이것은 switch문에서도 사용이 가능하다.

 

enum Seasons {SPRING, SUMMER, FALL, WINTER}

public class Main {
    public static void main(String[] args) {
        Seasons seasons = Seasons.SPRING;
        switch (seasons) {
            case SPRING:
                System.out.println("봄");
                break;
            case SUMMER:
                System.out.println("여름");
                break;
            case FALL:
                System.out.println("가을");
                break;
            case WINTER:
                System.out.println("겨울");
                break;
        }
    }
}

//출력값 
봄

 

구체적인 사용법

enum 열거형이름 { 상수명1, 상수명2, 상수명3, ...}

으로 정의하게 되면 안의 상수명은 대문자로 작성해야하며, 상수명1부터 자동적으로 0부터 시작하는 정수값이 할당됩니다.

 

 

열거형 안의 상수명 접근 방법

// 열거형이름.상수명
enum Seasons { SPRING, SUMMER, FALL, WINTER }

public class EnumExample {
    public static void main(String[] args) {
        System.out.println(Seasons.SPRING); // SPRING
    }
}


// Seasons.SPRING 을 Seasons 타입의 참조변수에 할당
enum Seasons { SPRING, SUMMER, FALL, WINTER }

public class EnumExample {
    public static void main(String[] args) {
        Seasons favoriteSeason = Seasons.SPRING;
        System.out.println(favoriteSeason); // SPRING
    }
}

 

 

열거형에서 사용할 수 있는 메서드

 

타입 메소드(매개변수) 설명
String name() 열거 객체가 가지고 있는 문자열을 리턴하며, 리턴되는 문자열은 열거타입을 정의할 때 사용한 상수 이름과 동일합니다.
int ordinal() 열거 객체의 순번(0부터 시작)을 리턴합니다.
int compareTo(비교값) 주어진 매개값과 비교해서 순번 차이를 리턴합니다.
열거 타입 valueOf(String name) 주어진 문자열의 열거 객체를 리턴합니다.
열거 배열 values() 모든 열거 객체들을 배열로 리턴합니다.

valueOf() 메서드를 활용하여 지정된 열거형에서 이름과 일치하는 열거형 상수를 반환

 


Annotation(애너테이션)

- 주석과 유사하게 “정보 전달"에 주요한 목적이지만 개발자의 이해를 돕기 위한 주석과는 다르게 다른 프로그램에게 유용한 정보를 제공하기 위해 만들어진 것

 

주요 역할

1.  컴파일러에게 문법 에러를 체크하도록 정보를 제공합니다.

2. 프로그램을 빌드할 때 코드를 자동으로 생성할 수 있도록 정보를 제공합니다.

3. 런타임에 특정 기능을 실행하도록 정보를 제공합니다.

 


종류

1. 표준 애너테이션  - 자바에서 기본적으로 제공

2. 메타 애너테이션  - 에너테이션을 정의하는 데 사용되는 ‘애너테이션의 애너테이션

 

표준 애너테이션

표준 애너테이션 설명
@Override 컴파일러에게 메서드를 오버라이딩하는 것이라고 알림
@Deprecated 앞으로 사용하지 않을 대상을 알릴 때 사용
@FunctionalInterface 함수형 인터페이스라는 것을 알
@SuppressWarning 컴파일러가 경고메세지를 나타내지 않음

 

메타 애너테이션

메타 애너테이션 설명
@Target 애너테이션을 정의할 때 적용 대상을 지정하는데 사용한다.
@Documented 애너테이션 정보를 javadoc으로 작성된 문서에 포함시킨다.
@Inherited 애너테이션이 하위 클래스에 상속되도록 한다.
@Retention 애너테이션이 유지되는 기간을 정하는데 사용한다.
@Repeatable 애너테이션을 반복해서 적용할 수 있게 한다.

 


@Override

- 메서드 앞에만 붙일 수 있는 애너테이션

- 선언한 메서드가 상위 클래스의 메서드를 오버라이딩하는 메서드라는 것을 컴파일러에게 알려주는 역할

- @Override가 붙어있는 메서드명과 동일한 이름의 메서드를 찾을 수 없다면 컴파일러가 컴파일 에러를 발생시킨다.

class Super {
	void run() {}
}
class Sub extends Super {
	@Override
	void rnu() {} // 컴파일 에러 발생, 오타가 난 것을 발견할 수 있음.
}

 

@Deprecated

- 기존 메서드를 하위 버전 호환성 문제로 삭제하기 곤란해 남겨두어야만 하지만 더 이상 사용하는 것을 권장하지 않을 때사용

- 기존에 사용하던 인스턴스 변수와 메서드가 새로운 것으로 대체되어 더이상 사용하는 것을 권장하고 있습니다.

 

@Depreacated가 붙은 대상을 사용하는 코드를 작성하면, 컴파일할 때

Note: 파일명.java uses or overrides a deprecated API.
Note: Recomplie with -Xlint:deprecation for details.

와 같은 메시지가 나타난다.

 

@SuppressWarnings

- 컴파일 경고 메시지가 나타나지 않도록 한다.

- 경우에 따라서 경고가 발생할 것이 충분히 예상됨에도 묵인해야 할 때 주로 사용

 

@SuppressWarnings 뒤에 괄호를 붙이고 그 안에 억제하고자 하는 경고메세지를 지정해줄 수 있다.

애너테이션 설명
@SuppressWarings(”all”) 모든 경고를 억제
@SuppressWarings(”deprecation”) Deprecated 메서드를 사용한 경우 나오는 경고 억제
@SuppressWarings(”fallthrough”) switch문에서 break 구문이 없을 때 경고 억제
@SuppressWarings(”finally”) finally 관련 경고 억제
@SuppressWarings(”null”) null 관련 경고 억제
@SuppressWarings(”unchecked”) 검증되지 않은 연산자 관련 경고 억제
@SuppressWarings(”unused”) 사용하지 않는 코드 관련 경고 억제

예시

@SuppressWarnings({"deprecation", "unused", "null"}) //둘 이상의 경고를 한 번에 묵인

 

@FunctionalInterface

- 함수형 인터페이스를 선언할 때, 컴파일러가 함수형 인터페이스의 선언이 바르게 선언되었는 지 확인하는 것

- 만약 바르게 선언되지 않은 경우, 에러를 발생시킴

- 에너테이션을 붙이지 않아도 함수형 인터페이스를 선언할 수는 있지만 코드 작성과정에서 실수를 방지하기 위한 확인용 애너테이션

함수형 인터페이스 단 하나의 추상 메서드만을 가져야하는 제약이 있다

@FunctionalInterface
public interface Runnable {
	public abstract void run (); // 하나의 추상 메서드
}

@Target

- 애너테이션을 적용할 “대상"을 지정하는 데 사용

 

@Target 애너테이션을 사용하여 지정할 수 있는 대상의 타입 (java.lang.annotation.ElementType 이라는 열거형에 정의되어 있다.)

대상 타입 적용범위
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;
}

에너테이션을 정의하는 것으로 CustomAnnotaion의 에너테이션의 적용대상은 FIELD, TYPE, TYPE_USE라는 것이다.

 

 

@Documented

- 애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 하는 애너테이션 설정

(표준 애너테이션과 메타 애너테이션 중 @Override@SuppressWarnings를 제외하고는 모두 @Documented가 적용되어 있다)

@Documented
@Target(ElementType.Type)
public @interface CustomAnnotation { }

 

@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

- 애너테이션을 여러 번 붙일 수 있도록 허용한다는 의미

@Repeatable(Works.class) // ToDo 애너테이션을 여러 번 반복해서 쓸 수 있게 한다.  
@interface Work{  
    String value();  
}
@Work("코드 업데이트")  
@Work("메서드 오버라이딩")  
class Main{  
	... 생략 ...
}
//@Repeatable 애너테이션을 사용하여 이것을 여러번 사용할 수 있도록 함

 

사용자 정의 애너테이션

- 사용자가 직접 애너테이션을 정의해서 사용하는 것

interface 애너테이션명 { // 인터페이스 앞에 @기호만 붙이면 애너테이션을 정의할 수 있습니다. 
	타입 요소명(); // 애너테이션 요소를 선언
}

- 애너테이션은 java.lang.annotation 인터페이스를 상속받기 때문에 다른 클래스나 인터페이스를 상속 받을 수 없다

 


Lambda(람다)

- 함수형 프로그래밍 기법을 지원하는 자바의 문법요소

- 메서드를 하나의 ‘식(expression)’으로 표현한 것으로, 코드를 매우 간결하면서 명확하게 표현할 수 있다는 큰 장점이 있다.

- 람다식 또한 사실은 객체입니다. 더 정확히는 이름이 없기 때문에 익명 클래스라 할 수 있습니다. (자바에서 함수는 반드시 클래스 안에서 정의되어야 하기 때문에 메서드가 독립적으로 있을 수 없고 반드시 클래스 객체를 먼저 생성한 후 생성한 객체로 메서드를 호출해야 합니다.)

- 람다 함수를 익명 함수라고 부르기도 한다.

 

int sum(int num1, int num2) {
	return num1 + num2;
}

1. 반환타입과 메서드 명을 생략

(int num1, int num2) -> { // 반환타입과 메서드명 제거 + 화살표 추가
	return num1 + num2;
}

2. 화살표 추가 (반환 값이 있는 메서드의 경우 ; 세미클로 생략 가능)

(int num1, int num2) -> {
	num1 + num2
}

3. 특정 조건인 메서드 바디에 문장이 하나만 존재할 때 중괄호 생략 가능

(int num1, int num2) -> num1 + num2

 

예시

// --------------------------------------------------------------------//
//기존 메서드 표현 방식
void sayhello() {
	System.out.println("HELLO!")
}

//위의 코드를 람다식으로 표현한 식
() -> System.out.println("HELLO!")
// --------------------------------------------------------------------//

// --------------------------------------------------------------------//
// 기존 방식
int example2() {
	return 10;
}

// 람다식
() -> {return 10;}
// --------------------------------------------------------------------//

 

사실 람다식 또한 객체이고 이름이 없기 때문에 익명 클래스입니다.

익명 클래스객체의 선언과 생성을 동시에 하여 오직 하나의 객체를 생성하고, 단 한번만 사용되는 일회용 클래스입니다.

// sum 메서드 람다식
(num1, num2) -> num1 + num2

// 람다식을 객체로 표현
new Object() {
	int sum(int num1, int num2) {
		return num1 + num1;
	}
}

 

람다식이 객체이기에 객체에 접근하고 사용하기 위한 참조변수가 필요합니다. 그래서 Object타입으로 참조변수를 선언해서 하더라도 위의 sum메서드를 사용할 수 있는 방법은 없습니다. 왜냐하면 위에서는 Object클래스에는 sum이 젖의되었지만 기존에 객체를 생성할 때 만들었던 Object클래스에는 sun메서드가 업식 때문이다.

그래서 함수형 인터페이스라는 것이 만들어졌다.


함수형 인터페이스

- 단 하나의 추상 메서드만 선언될 수 있다.

(람다식과 인터페이스의 메서드가 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 {
		public abstract int sum(int num1, int num2);
}

// 출력값
25

 

매개변수와 리턴값이 없는 람다식

@FunctionalInterface
public interface MyFunctionalInterface {
    public void accept();
}

->

MyFunctionalInterface example = () -> { ... };

// example.accept(); 람다식이 대입된 인터페이스의 참조 변수의 accpet호출
public class MyFunctionalInterfaceExample {
	public static void main(String[] args) throws Exception {
		MyFunctionalInterface example;
		example = () -> {
			String str = "첫 번째 메서드 호출!";
			System.out.println(str);
		};
		example.accept();

		example = () -> System.out.println("두 번째 메서드 호출!");
		//실행문이 하나라면 중괄호 { }는 생략 가능
		example.accept();
	}
}

// 출력값
첫 번째 메서드 호출!
두 번째 메서드 호출!

매개변수가 있는 람다식

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

 


스트림(Stream)

- 배열, 컬렉션의 저장 요소하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자

- List, Set, Map, 배열 등 다양한 데이터 소스로부터 스트림을 만들 수 있고, 이를 표준화된 방법으로 다룰 수 있다.

-다량의 데이터에 복잡한 연산을 수행하면서도, 가독성과 재사용성이 높은 코드를 작성할 수 있다.

- 선언형으로 데이터 소스를 처리한다는 특징

- 내부 반복자를 사용하므로 병렬 처리가 쉽다.

- 중간 연산과 최종 연산을 할 수 있다.

 

스트림으로 바꾸기전 사용하는 방식

import java.util.List;

public class ImperativeProgrammingExample {
    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);
    }
}

 

스트림을 사용

import java.util.List;

public class DeclarativeProgramingExample {
    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);
    }
}

 

Stream이 제공하는 대부분의 요소 처리 메서드는 함수형 인터페이스 매개타입을 가지기 때문에 람다식 또는 메서드 참조를 이용해서 요소 처리 내용을 매개값으로 전달할 수 있다.

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

public class StreamLambdaExample {
    public static void main(String[] args) throws Exception {
        List<Student> list = Arrays.asList(
            new Student("김코딩", 95),
            new Student("이자바", 92)
        );

        Stream<Student> stream = list.stream();
        stream.forEach( s -> {
            String name = s.getName();
            int score = s.getScore();
            System.out.println(name+ " - " +score);
        });
    }
}

public class Student {
    private String name;
    private int score;

    public Student(String name, int score){
        this.name = name;
        this.score = score;
    }

    public String getName(){
        return name;
    }

    public int getScore(){
        return score;
    }
}
/*
김코딩 - 95
이자바 - 92
*/

 

 

내부 반복자를 사용하므로 병렬 처리가 쉽다.

(외부반복자(external iterator)란 개발자가 코드로 직접 컬렉션의 요소를 반복해서 가져오는 코드 패턴을 말합니다. index를 사용하는 for문, Iterator를 이용하는 while문은 모두 외부 반복자를 이용하는 형태)

 

내부 반복자(internal iterator)는 컬렉션 내부에서 요소들을 반복시키고 개발자는 요소당 처리해야할 코드만 제공하는 코드 패턴 -> 컬렉션 내부에서 어떻게 요소를 반복시킬 것인가는 컬렉션에게 맡겨두고, 개발자는 요소 처리 코드에만 집중할 수 있다는 점이 장점이다.

 

 

중간 연산과 최종 연산을 할 수 있다.

- 스트림은 컬렉션의 요소에 대해 중간 연산최종 연산을 수행할 수 있는데, 중간 연산에서는 매핑, 필터링, 정렬 등을 수행하고 최종 연산에서는 반복, 카운팅, 평균, 총합 등의 집계를 수행한다.

- 중간 스트림이 생성될 때 요소들이 바로 중간 연산(필터링, 매핑, 정렬)되는 것이 아니라 최종 연산이 시작되기 전까지는 지연됩니다. 최종 연산이 시작되면 비로소 컬렉션의 요소가 하나씩 중간 스트림에서 연산되고 최종 연산까지 오게 됩니다.

 

 

1. 스트림 생성

// List로부터 스트림을 생성
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> listStream = list.stream();

- stream()을 사용하면 해당 Collection의 객체를 소스로 하는 Stream을 반환

 

// 배열로부터 스트림을 생성
Stream<String> stream = Stream.of("a", "b", "c"); //가변인자
Stream<String> stream = Stream.of(new String[] {"a", "b", "c"});
Stream<String> stream = Arrays.stream(new String[] {"a", "b", "c"});
Stream<String> stream = Arrays.stream(new String[] {"a", "b", "c"}, 0, 3); //end 범위 미포함

// 4이상 10미만의 숫자를 갖는 IntStream
IntStream stream = IntStream.range(4, 10);

주의점 

1. 스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐 변경하지 않습니다(Read-only).

2. 스트림은 일회용입니다. 한 번 사용하면 닫히므로, 필요하다면 새로운 스트림을 만들어야 합니다.

 

 

2. 중간 연산

중간 연산은 연산 결과를 스트림으로 반환하기 때문에, 연속해서 여러 번 수행할 수 있다.

2 - 1. 필터링 - filter(), distinct()

distinct() : Stream의 요소들에 중복된 데이터가 존재하는 경우, 중복을 제거하기 위해 사용합니다.

filter() : Stream에서 조건에 맞는 데이터만을 정제하여 더 작은 컬렉션을 만들어냅니다. filter() 메서드에는 매개값으로 조건(Predicate)이 주어지고, 조건이 참이 되는 요소만 필터링합니다.

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(n -> System.out.println(n));
        System.out.println();

        names.stream()
						 .filter(n -> n.startsWith("김")) //필터링
						 .forEach(n -> System.out.println(n));
        System.out.println();

        names.stream()
						 .distinct() //중복제거
						 .filter(n -> n.startsWith("김")) //필터링
						 .forEach(n -> System.out.println(n));
    }
}
/*
김코딩
이자바
김인기

김코딩
김인기
김코딩

김코딩
김인기
*/

 

2 - 2. 매핑 - map()

- map은 기존의 Stream 요소들을 대체하는 요소로 구성된 새로운 Stream을 형성하는 연산

List<String> names = Arrays.asList("kimcoding", "javalee", "ingikim", "kimcoding");
    names.stream()
				 .map(s -> s.toUpperCase())
				 .forEach(n->System.out.println(n));
/*
KIMCODING
JAVALEE
INGIKIM
KIMCODING
*/

flatMap(): flatMap은 요소를 대체하는 복수 개의 요소들로 구성된 새로운 스트림을 리턴합니다.

flatMap()과 map()의 차이점은, map()은 스트림의 스트림을 반환하는 반면, flatMap()은 스트림을 반환한다는 것입니다.

 

2 - 3. 정렬 - sorted()

- 정렬하기 위해

- 파라미터로 Comparator를 넘길 수 있다.

- Comparator 인자 없이 호출할 경우에는 오름차순으로 정렬이 되며, 내림차순으로 정렬하기 위해서는 Comparator의 reverseOrder를 이용합니다.

List<String> list = Arrays.asList("Java", "Scala", "Groovy", "Python", "Go", "Swift");

        list.stream()
            .sorted()
            .forEach(n -> System.out.println(n));
        System.out.println();

/*
Go, Groovy, Java, Python, Scala, Swift
*/
        list.stream()
            .sorted(Comparator.reverseOrder())
            .forEach(n -> System.out.println(n));

/*
Swift, Scala, Python, Java, Groovy, Go
*/

Comparable을 기본적으로 구현한 클래스가 아니라면, 다음과 같이 comparing() 메서드를 사용해 비교할 수 있습니다.

Comparator.comparing(~~~~~)

 

2 - 4. 연산 결과 확인 - peek()

- 주로 연산 중간에 결과를 확인하여 디버깅하고자 할 때 사용

- peek(), forEach()는 요소를 하나씩 돌면서 출력한다는 기능에서는 동일하지만, 동작 방식은 다릅니다.

peek은 중간 연산 메서드이고, forEach는 최종 연산 메서드입니다.

forEach는 스트림의 요소를 소모하므로 한 번만 호출할 수 있지만(재호출하려면 새로운 스트림을 생성해야 합니다), peek은 중간 연산이므로 하나의 스트림에 여러 번 사용할 수 있습니다.

intStream
	.filter(a -> a%2 ==0)
	.peek(n-> System.out.println(n))
	.sum();

 

3. 최종 연산

- 연산 결과가 스트림이 아니므로, 한 번만 연산이 가능

 

3 - 1. 연산 결과 확인 - forEach()

- 파이프라인 마지막에서 요소를 하나씩 연산

- 출력할 때도 사용하지만, 이메일 발송, 스케줄링 등 리턴 값이 없는 작업에서도 많이 사용

intStream
	.filter(a -> a%2 ==0)
	.forEach(n -> System.out.println(n));

 

3 - 2. 매칭 - match()

- 특정한 조건을 충족하는지 검사

- 검사 결과를 boolean으로 반환

  • llMatch() : 모든 요소들이 매개값으로 주어진 Predicate의 조건을 만족하는지 조사
  • anyMatch() : 최소한 한 개의 요소가 매개값으로 주어진 Predicate의 조건을 만족하는지 조사
  • noneMatch() : 모든 요소들이 매개값으로 주어진 Predicate의 조건을 만족하지 않는지 조사
import java.util.Arrays;

public class MatchesExample {
    public static void main(String[] args) throws Exception {
        int[] intArr = {2,4,6};
        boolean result = Arrays.stream(intArr).allMatch(a->a%2==0);
        System.out.println("모두 2의 배수인가? " + result);

        result = Arrays.stream(intArr).anyMatch(a->a%3==0);
        System.out.println("하나라도 3의 배수가 있는가? " + result);

        result = Arrays.stream(intArr).noneMatch(a->a%3==0);
        System.out.println("3의 배수가 없는가? " + result);
    }
}

/*
모두 2의 배수인가? true
하나라도 3의 배수가 있는가? true
3의 배수가 없는가? false
*/

 

3 - 3. 기본 집계 - sum(), count(), average(), max(), min()

import java.util.Arrays;

public class AggregateExample {
    public static void main(String[] args) throws Exception {
        int[] intArr = {1,2,3,4,5};

        long count = Arrays.stream(intArr).count();
        System.out.println("intArr의 전체 요소 개수 " + count);

        long sum = Arrays.stream(intArr).sum();
        System.out.println("intArr의 전체 요소 합 " + sum);

        double avg = Arrays.stream(intArr).average().getAsDouble();
        System.out.println("전체 요소의 평균값 " + avg);

        int max = Arrays.stream(intArr).max().getAsInt();
        System.out.println("최대값 " + max);

        int min = Arrays.stream(intArr).min().getAsInt();
        System.out.println("최소값 " + min);

        int first = Arrays.stream(intArr).findFirst().getAsInt();
        System.out.println("배열의 첫번째 요소 " + first);

    }
}

/*
intArr의 전체 요소 개수 5
intArr의 전체 요소 합 15
전체 요소의 평균값 3.0
최대값 5
최소값 1
배열의 첫번째 요소 1
*/

 

3 - 4. reduce()

- 누적하여 하나로 응축(reduce)하는 방식으로 동작

reduce 메서드는 최대 3개의 매개변수를 받을 수 있습니다.

  1. Accumulator: 각 요소를 계산한 중간 결과를 생성하기 위해 사용
  2. Identity: 계산을 수행하기 위한 초기값
  3. Combiner: 병렬 스트림(Parlallel Stream)에서 나누어 계산된 결과를 하나로 합치기 위한 로직
import java.util.Arrays;

public class ReduceExample {
    public static void main(String[] args) throws Exception {
        int[] intArr = {1,2,3,4,5};

        long sum = Arrays.stream(intArr).sum();
        System.out.println("intArr의 전체 요소 합 " + sum);

        int sum1 = Arrays.stream(intArr)
                        .map(el -> el*2)
                        .reduce((a,b) -> a+b)
                        .getAsInt();
        System.out.println("초기값 없는 reduce " + sum1);

        int sum2= Arrays.stream(intArr)
                        .map(el -> el*2)
                        .reduce(0, (a,b) -> a+b);
        System.out.println("초기값 존재하는 reduce " + sum2)
    }
}

/*
intArr의 전체 요소 합 15
초기값 없는 reduce 30
초기값 존재하는 reduce 30
*/

 

3 - 5. collect()

- Stream의 요소들을 List나 Set, Map, 등 다른 종류의 결과로 수집하고 싶은 경우
- 어떻게 Stream의 요소들을 수집할 것인가를 정의한 Collector 타입을 인자로 받는다.

 

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class CollectExample {
    public static void main(String[] args) throws Exception {
        List<Student> totalList = Arrays.asList(
            new Student("김코딩", 10, Student.Gender.Male),
            new Student("김인기", 8, Student.Gender.Male),
            new Student("이자바", 9, Student.Gender.Female),
            new Student("최민선", 10, Student.Gender.Female)
        );

        List<Student> maleList = totalList.stream()
        .filter(s -> s.getGender() == Student.Gender.Male)
        .collect(Collectors.toList());

        maleList.stream().forEach(n->System.out.println(n.getName()));

        Set<Student> femaleSet = totalList.stream()
        .filter(s -> s.getGender() == Student.Gender.Female)
        .collect(Collectors.toCollection(HashSet :: new));

        femaleSet.stream().forEach(n->System.out.println(n.getName()));
    }
}

public 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 Gender getGender(){
        return gender;
    }

    public String getName(){
        return name;
    }

    public int getScore(){
        return score;
    }
}

 

 


오늘 학습 내용 중 새롭게 배운 내용은 무엇인가요?
       -  

오늘 새롭게 학습한 내용을 다른 사람에게 설명할 수 있나요?
      - 네

오늘 학습한 내용 중 아직 이해되지 않은 불확실한 내용은 무엇인가요?
     -  없습니다.

이해되지 않은, 불확실한 내용을 보완하기 위해서 나는 무엇을 할 수 있을까요?
     -  구글링을 해본다.

나의 오늘 학습 만족도는 몇 점인가요?
     - 90점 (아는 내용들이라고 설렁설렁 넘어간 것들이 있는 것같다.)
반응형
Comments