[Java] 스트림(Stream) 이란? - 특징과 사용법

2021. 4. 24. 17:05Java

스트림

- 다양한 데이터 소스를 표준화된 방법으로 다루기 위한 것
- 데이터 소스를 Stream 을 통해 순차적으로 처리 (데이터의 연속적인 처리)
- 컬렉션 프레임워크(List, Set, Map)은 각자 성격이 달라 사용방법이 다름. 그래서 표준화라고 하기에는 거리가 좀 멀다.
- JDK 1.8부터 Collection 그리고 Array 를 Stream 으로 만들어 표준화된 방법으로 다룰수 있음.


스트림의 작업순서

1. 스트림 만들기
2. 중간연산은 N번

  : 중간연산이란연산결과가 스트림반복 적용 가능능
3. 최종연산은 1번

  : 최종연산이란연산결과가 스트림이 아닌 연산이므로 단 한번만 적용 가능. (스트림의 요소를 소모)

 

스트림의 특징

- Stream 은 오직 ReadOnly 이다. (원본을 변경하지 않음)
- 1회용이다. 최종연산을 하면 요소가 소모 되다보니 필요하면 다시 스트림을 생성해야 한다.
- 지연 연산
  : 최종 연산 전까지 중간연산이 수행되지 않는다.
- 병렬로 처리
  : 멀티스레드 사용

  : 디폴트 값은 직렬 sequential()

  : 병렬 처리를 원한다면 키워드 병렬 parallel() 사용.

- 기본형 스트림
  : 오토박싱&언박싱의 비효율의 제거하기 위함
  : 데이터소스가 Primitive Type 일 경우에만 사용가능하다.
  : IntStream, LongStream, DoubleStream

 


연습

 

Collection 스트림 만들기

Collection Interface 에는 stream() 메소드가 존재한다.

그러므로 Collection Interface 자손인 List / Set 은 steam() 을 사용할 수 있다.

// 리스트 초기화
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        
// 리스트를 Stream 으로 반환
Stream<Integer> intStream = list.stream();

// (1) Stream 의 모든 요소 출력
intStream.forEach(System.out::println);     		// 메서드 참조

// (2) Stream 의 모든 요소 출력
// intStream.forEach(i -> System.out.println(i));  	// 람다식

//== 주석 이유: 스트림의 최종연산(forEach)으로 스트림이 닫혀, 재호출 시 에러 발생.

 

Array 스트림 만들기

// String 배열 Stream 초기화
Stream<String> strStream1 = Stream.of("a", "b", "c");
Stream<String> strStream2 = Stream.of(new String[] {"a", "b", "c"});
Stream<String> strStream3 = Arrays.stream(new String[] {"a", "b", "c"});

// Integer 배열 Stream
Stream<Integer> intStream = Arrays.stream(new Integer[] {1, 2, 3, 4, 5});

// int 배열 Stream (기본타입에는 최종연산 count() / sum() / average() 등 제공.)
IntStream intStream1 = Arrays.stream(new int[] {1, 2, 3, 4, 5});

 

Random 스트림 만들기

// Random Stream 초기화
IntStream randomStream2 = new Random().ints();			// 무한 스트림
IntStream randomStream2 = new Random().ints(8);			// 1 2 3 4 5 6 7 8
IntStream randomStream3 = IntStream.range(1, 8);		// 1 2 3 4 5 6 7
IntStream randomStream4 = IntStream.rangeClosed(1, 8);		// 1 2 3 4 5 6 7 8

 

무한 스트림 만들기

// 초기(seed)값 0을 시작으로 짝수 람다식을 사용해 무한스트림
Stream<Integer> evenStream = Stream.iterate(0, n->n+2);

// generate 는 seed 값을 사용하지 않는다.
Stream<Double> randomStream = Stream.generate(Math::random);   // 랜덤값 계속 출력
Stream<Integer> oneStream = Stream.generate(() -> 1);          // 1 계속 출력

 

iterate(T seed, UnaryOperator f)
iterate 의 매개변수 UnaryOperator 는 단항 연산자로 1개의 입력으로 1개의 출력이 나옴


generate(Supplier s)
generate 의 매개변수 Supplier 는 입력이 없고 출력만 있는 함수형 인터페이스(FunctionalInterface)

 

 

빈 스트림 만들기

Stream emptyStream = Stream.empty();

 

 



스트림의 중간 연산

중간연산의 종류는 다음과 같다.

 

- skip, limit, distinct, filter 연습

/* skip 과 limit 연습 */
IntStream intStream1 = IntStream.rangeClosed(1, 10);      // 1 2 3 4 5 6 7 8 9 10
intStream1.skip(3).limit(5).forEach(System.out::print);   // 4 5 6 7 8


/* distinct 연습 */
IntStream intStream2 = IntStream.of(1, 1, 2, 2, 3, 3);    // 1 1 2 2 3 3
intStream2.distinct().forEach(System.out::print);         // 1 2 3


/* filter 연습 */
IntStream intStream3 = IntStream.rangeClosed(1, 10);      // 1 2 3 4 5 6 7 8 9 10
intStream3.filter(i -> i%2==0)
        .forEach(System.out::print);                      // 2 4 6 8 10

IntStream intStream4 = IntStream.rangeClosed(1, 10);      // 1 2 3 4 5 6 7 8 9 10
intStream4.filter(i -> i%2!=0 && i%3!=0)
        .forEach(System.out::print);                      // 1 5 7

IntStream intStream5 = IntStream.rangeClosed(1, 10);      // 1 2 3 4 5 6 7 8 9 10
intStream5.filter(i -> i%2!=0)
        .filter(i -> i%3!=0)
        .forEach(System.out::print);                      // 1 5 7

 

- sort 연습

// 기본정렬-1 (대소문자 구별O)
Stream<String> strStream1 = Stream.of("a", "b", "B", "b",  "C", "d", "e");
strStream1
        .sorted()
        .forEach(System.out::print);      // B C a b b d e

// 기본정렬-2
Stream<String> strStream2 = Stream.of("a", "b", "B", "b",  "C", "d", "e");
strStream2
        .sorted(Comparator.naturalOrder())
        .forEach(System.out::print);      // B C a b b d e

// 기본정렬-3 (람다식 사용)
Stream<String> strStream3 = Stream.of("a", "b", "B", "b",  "C", "d", "e");
strStream3
        .sorted((s1, s2) -> s1.compareTo(s2))
        .forEach(System.out::print);      // B C a b b d e

// 기본정렬-4 (메서드참조 사용)
Stream<String> strStream4 = Stream.of("a", "b", "B", "b",  "C", "d", "e");
strStream4
        .sorted(String::compareTo)
        .forEach(System.out::print);      // B C a b b d e


// 역순정렬-1
Stream<String> strStream5 = Stream.of("a", "b", "B", "b",  "C", "d", "e");
strStream5
        .sorted(Comparator.reverseOrder())
        .forEach(System.out::print);        // e d b b a C B

// 역순정렬-2
Stream<String> strStream6 = Stream.of("a", "b", "B", "b",  "C", "d", "e");
strStream6
        .sorted(Comparator.<String>naturalOrder().reversed())
        .forEach(System.out::print);        // e d b b a C B



// 대소문자 구별하지 않고 정렬하기
Stream<String> strStream7 = Stream.of("a", "b", "B", "b",  "C", "d", "e");
strStream7
        .sorted(String.CASE_INSENSITIVE_ORDER)
        .forEach(System.out::print);        // a b B b C d e

// 대소문자 구별하지 않고 역순으로 정렬하기
Stream<String> strStream8 = Stream.of("a", "b", "B", "b",  "C", "d", "e");
strStream8
        .sorted(String.CASE_INSENSITIVE_ORDER.reversed())
        .forEach(System.out::print);        // e d C b B b a

// 스트링 길이로 정렬하기
Stream<String> strStream9 = Stream.of("a", "b", "B", "b",  "C", "d", "e");
strStream9
        .sorted(Comparator.comparing(String::length))
        .forEach(System.out::print);        // e d C b B b a

 

- map 연습

File[] fileArr = {
        new File("Ex1.java"),
        new File("Ex2.bak"),
        new File("Ex3.java"),
        new File("Ex4"),
        new File("Ex5.txt")};

// Stream<File> 생성
Stream<File> fileStream1 = Stream.of(fileArr);

// Stream<File> -> Stream<String> 으로 변환
Stream<String> fileName1 = fileStream1.map(File::getName);

// 출력
fileName1.forEach(System.out::println);


/**
 * 출력 결과
 * -------------
 * Ex1.java
 * Ex2.bak
 * Ex3.java
 * Ex4
 * Ex5.txt
 * -------------
 */

 

- peek 연습

File[] fileArr = {
        new File("Ex1.java"),
        new File("Ex2.bak"),
        new File("Ex3.java"),
        new File("Ex4"),
        new File("Ex5.txt")};


// Stream<File> 생성
Stream<File> fileStream2 = Stream.of(fileArr);
fileStream2
        .map(f -> f.getName())                      // 파일명 추출
        .filter(s -> s.indexOf(".") != -1)          // 확장자가 없는 파일명은 제외
        .peek(s -> System.out.printf("[디버깅] filename=%s%n", s))
        .map(s -> s.substring(s.indexOf(".")+1))    // 확장자 없이 파일명만 추출
        .peek(s -> System.out.printf("[디버깅] extension=%s%n", s))
        .map(String::toUpperCase)                   // 대문자로 변환
        .distinct()                                 // 중복제거
        .forEach(System.out::println);              // 최종연산 - 출력
        
        
/*
 * 출력 결과
 * -------------
 * [디버깅] filename=Ex1.java
 * [디버깅] extension=java
 * JAVA
 * [디버깅] filename=Ex2.bak
 * [디버깅] extension=bak
 * BAK
 * [디버깅] filename=Ex3.java
 * [디버깅] extension=java
 * [디버깅] filename=Ex5.txt
 * [디버깅] extension=txt
 * TXT
 * -------------
 */

 

- flatmap 연습

// 여러개의 스트림을 하나의 스트림에 넣기 (map)
Stream<String[]> strArrStream1 = Stream.of(
        new String[] {"abc", "def", "ghi"} ,
        new String[] {"ABC", "def", "jkl"}
);

Stream<Stream<String>> strStreamStream = strArrStream1.map(Arrays::stream);
strStreamStream
        .forEach(System.out::println);
/**
 * 출력결과
 * -------------------
 * java.util.stream.ReferencePipeline$Head@3b9a45b3
 * java.util.stream.ReferencePipeline$Head@7699a589
 * ------------------
 */



// 하나의 스트림으로 합치기 (flatMap)
Stream<String[]> strArrStream2 = Stream.of(
        new String[] {"abc", "def", "ghi"} ,
        new String[] {"ABC", "def", "jkl"}
);

Stream<String> strStream = strArrStream2.flatMap(Arrays::stream);
strStream
        .forEach(System.out::println);
/**
 * 출력결과
 * -------------------
 * abc
 * def
 * ghi
 * ABC
 * def
 * jkl
 * ------------------
 */



// 하나의 스트림으로 합치기 (flatMap)
String[] lineArr = {
        "Believe or not It is true",
        "Do or do not There is no try"
};

Stream<String> lineStream = Arrays.stream(lineArr);
lineStream.flatMap(l -> Stream.of(l.split(" +")))
        .map(String::toLowerCase)
        .forEach(System.out::println);
/**
 * 출력결과
 * -------------------
 * believe
 * or
 * not
 * it
 * is
 * true
 * do
 * or
 * do
 * not
 * there
 * is
 * no
 * try
 * ------------------
 */

 



스트림의 최종 연산

최종연산의 종류는 다음과 같다.

 

- forEachOrdered 연습

// 병렬 처리 시, 순서 보장이 안됨
IntStream
        .range(1, 10)
        .parallel()
        .forEach(System.out::print);            // 652178943

// 병렬 처리 시, 순서 보장을 위해 forEachOrdered 사용
IntStream
        .range(1, 10)
        .parallel()
        .forEachOrdered(System.out::print);    // 123456789

 

- 조건 검사  allMatch(), anyMatch(), noneMatch()  연습

// allMatch 은 모든 요소가 조건을 만족해야만 바로 true 반환
boolean all = IntStream.range(1, 10)
        .peek(s -> System.out.printf("[디버깅] 로그=%s%n", s))
        .allMatch(i -> i <= 5);
System.out.println(all);    // false

// anyMatch 은 한 요소라도 조건을 만족하다면 바로 true 반환
boolean any = IntStream.range(1, 10)
        .peek(s -> System.out.printf("[디버깅] 로그=%s%n", s))
        .anyMatch(i -> i <= 5);
System.out.println(any);    // true

// noneMatch 은 모든 요소가 조건을 만족시키지 않으면 바로 true 반환
boolean none = IntStream.range(1, 10)
        .peek(s -> System.out.printf("[디버깅] 로그=%s%n", s))
        .noneMatch(i -> i <= 5);
System.out.println(any);    // true


/**
 * 출력결과
 * --------------
 * [디버깅] 로그=1
 * [디버깅] 로그=2
 * [디버깅] 로그=3
 * [디버깅] 로그=4
 * [디버깅] 로그=5
 * [디버깅] 로그=6
 * false
 * [디버깅] 로그=1
 * true
 * [디버깅] 로그=1
 * true
 * -------------
 */

 

 

- 조건에 일치하는 요소 찾기  findFirst(), findAny()  연습

String[] strArr = {
    "Inheritance", "Java", "Lambda", "stream",
    "OptionalDouble", "IntStream", "count", "sum"
};


// findFirst 는 첫 번째 요소를 반환
Optional<String> first = Stream.of(strArr)
        .parallel()
        .peek(s -> System.out.printf("[디버깅] 로그=%s%n", s))
        .filter(s -> s.charAt(0) == 's')
        .findFirst();

System.out.println(first.get());    // stream 또는 sum (병렬이기에 출력이 매번 다름)


// findAny 는 아무거나 하나 반환
Optional<String> any = Stream.of(strArr)
        .peek(s -> System.out.printf("[디버깅] 로그=%s%n", s))
        .filter(s -> s.charAt(0) == 's')
        .findAny();

System.out.println(any.get());      // stream


/**
 * 출력결과
 * --------------
 * [디버깅] 로그=IntStream
 * [디버깅] 로그=Inheritance
 * [디버깅] 로그=OptionalDouble
 * [디버깅] 로그=stream
 * [디버깅] 로그=Java
 * [디버깅] 로그=Lambda
 * [디버깅] 로그=sum
 * [디버깅] 로그=count
 * sum
 * [디버깅] 로그=Inheritance
 * [디버깅] 로그=Java
 * [디버깅] 로그=Lambda
 * [디버깅] 로그=stream
 * stream
 * -------------
 */

 

- 스트림의 요소를 하나씩 줄여가며 누적연산 수행  reduce()  연습

  : identity 는 초기값

  : accumulator 는 이전 연산결과와 스트림의 요소에 수행할 연산

  : combiner 은 병렬처리된 결과를 합치는데 사용할 연산(병렬 스트림)

String[] strArr = {
    "Inheritance", "Java", "Lambda", "stream",
    "OptionalDouble", "IntStream", "count", "sum"
};


// Stream<String> -> Stream<Integer> 변환
Stream<Integer> integerStream = Stream.of(strArr).map(String::length);

// Stream<String> -> IntStream 변환
IntStream intStream = Stream.of(strArr).mapToInt(String::length);

// 카운터 구하기
int count = intStream.reduce(0, (a, b) -> a+1);

// 총 합 구하기
int sum = integerStream.reduce(0, (a, b) -> a+ b);

// 최대 값 구하기
OptionalInt max = Stream.of(strArr)
        .mapToInt(String::length)
        .reduce(Integer::max);

// 최소 값 구하기
OptionalInt min = Stream.of(strArr)
        .mapToInt(String::length)
        .reduce(Integer::min);

System.out.println(count);                  // 8 (스트림 개수)
System.out.println(sum);                    // 58 (문자의 총 길이 합)
System.out.println(max.getAsInt());         // 14
System.out.println(min.getAsInt());         // 3

 

 

- collect() 와 Collectors  연습

  : collect()는 Collector를 매개변수로 하는 스트림의 최종연산

  : Collector는 collect에 필요한 메서드를 정의 해놓은 인터페이스

  : Collectors는 Collector 인터페이스를 구현 클래스 (다양한 기능 제공)

  : collect() 메서드는... 5개의 매개변수가 들어가지만 친절하게도 모두 구현되어 있어서 Collector 하나만 이용하면 된다.

// 객체 생성
Student student1 = new Student("준");
Student student2 = new Student("상");

// 리스트 만들기
List<Student> student = new ArrayList<>();
student.add(student1);
student.add(student2);

// Stream<Student> 로 초기화
Stream<Student> studentStream = student.stream();

// List 로 변환  [Stream<Student> -> List<String>] 변환
List<String> names1 =
        student.stream()
        .map(Student::getName)          // 이름만 추출
        .collect(Collectors.toList());  // 리스트로 변환

// ArrayList 로 변환
List<String> names2 =
//          studentStream
        student.stream()
        .map(Student::getName)
        .collect(Collectors.toCollection(ArrayList::new));  // ArrayList로 변환

// Array 로 변환 - 매개변수에 꼭 변환 타입을 적어줘야한다.
Student[] studentArr1 =
        student.stream()
        .toArray(Student[]::new);

// Array 로 변환 - 매개변수가 없다면, 무조건 Object[] 타입이다.
Object[] studentArr2 =
        student.stream()
                .toArray();

// 카운트
long count1 = student.stream().count();                         // 전체 count
long count2 = student.stream().collect(Collectors.counting());  // 그룹별 count

// 접수 합계
long totScore1 = student.stream().mapToInt(Student::getScore).sum();
long totScore2 = student.stream().collect(Collectors.summingInt(Student::getScore));

// reducing - 스트림을 리듀싱


// joining - 문자열 스트림의 요소를 모두 연결
String student3 = student.stream()
        .map(Student::getName)
        .collect(Collectors.joining());

String student4 = student.stream()
        .map(Student::getName)
        .collect(Collectors.joining(","));

String student5 = student.stream()
        .map(Student::getName)
        .collect(Collectors.joining(",", "*", "%"));

System.out.println(names1);         // [준, 상]
System.out.println(names2);         // [준, 상]
System.out.println(count1);         // 2
System.out.println(count2);         // 2
System.out.println(totScore1);      // 0
System.out.println(totScore2);      // 0
System.out.println(student3);       // 준상
System.out.println(student4);       // 준,상
System.out.println(student5);       // *준,상%

 

 

- partitioningBy() / groupingBy() 연습

import java.util.*;
import java.util.stream.Collectors;

import static java.util.Comparator.comparingInt;
import static java.util.stream.Collectors.*;

public class Example {

    public static void main(String[] args) {


        /**
         * partitioningBy()
         *
         * - 스트림을 2분할
         */
        Student student1 = new Student("네오", "남", 150, "1", "A");
        Student student2 = new Student("프로도", "여", 140, "1", "B");
        Student student3 = new Student("콘", "남", 130, "1", "C");
        Student student4 = new Student("어피치", "여", 120, "1", "D");
        Student student5 = new Student("죠르디", "남", 110, "1", "A");
        Student student6 = new Student("앙몬드", "여", 100, "1", "B");
        Student student7 = new Student("라이언", "남", 90, "1", "C");
        Student student8 = new Student("스카피", "여", 80, "1", "D");
        Student student9 = new Student("무지", "남", 70, "1", "A");

        List<Student> studentList = new ArrayList<>();
        studentList.add(student1);
        studentList.add(student2);
        studentList.add(student3);
        studentList.add(student4);
        studentList.add(student5);
        studentList.add(student6);
        studentList.add(student7);
        studentList.add(student8);
        studentList.add(student9);


        //======= (1) 학생들을 성별로 2분할(남/녀)
        Map<Boolean, List<Student>> studentA = studentList
                .stream()
                .collect(Collectors.partitioningBy(Student::isMale));

        System.out.println("=======================");
        List<Student> maleStudent = studentA.get(true);      // 남학생 List
        maleStudent.stream().forEach(System.out::println);
        List<Student> femaleStudent = studentA.get(false);   // 여학생 List
        femaleStudent.stream().forEach(System.out::println);



        //======= (2) 학생들을 성별로 2분할(남/녀)로 나눈 후, 카운팅
        Map<Boolean, Long> studentB = studentList
                .stream()
                .collect(Collectors.partitioningBy(Student::isMale, Collectors.counting()));

        System.out.println("=======================");
        Long maleStudentCnt = studentB.get(true);      // 남학생 수
        System.out.println(maleStudentCnt);
        Long femaleStudentCnt = studentB.get(false);   // 여학생 수
        System.out.println(femaleStudentCnt);



        //======= (3) 학생들을 성별로 2분할(남/녀)로 나눈 후, 1등 뽑기
        Map<Boolean, Optional<Student>> studentC = studentList
                .stream()
                .collect(Collectors.partitioningBy(Student::isMale,
                        Collectors.maxBy(comparingInt(Student::getScore))));

        System.out.println("=======================");
        Optional<Student> maleStudentFirst = studentC.get(true);
        System.out.println(maleStudentFirst.get());                   // 남학생 1등
        System.out.println(maleStudentFirst.get());                   // 남학생 1등 (Optional 꺼내기)

        Optional<Student> femaleStudentFirst = studentC.get(false);
        System.out.println(femaleStudentFirst);                       // 여학생 1등
        System.out.println(femaleStudentFirst.get());                 // 여학생 1등 (Optional 꺼내기)



        //======= (4) 학생들을 성별로 2분할(남/녀)로 나눈 후, 다시 2분할(성적)으로 나눔
        Map<Boolean, Map<Boolean, List<Student>>> studentD = studentList
                .stream()
                .collect(Collectors.partitioningBy(Student::isMale,
                         Collectors.partitioningBy(s -> s.getScore() < 80)));

        System.out.println("=======================");
        List<Student> failMaleStudent = studentD.get(true).get(true);      // 불합격 남학생
        System.out.println(failMaleStudent);

        List<Student> failfemaleStudent = studentD.get(false).get(true);   // 불합격 여학생
        System.out.println(failfemaleStudent);



        /**
         * groupingBy()
         *
         * - 스트림을 N분할
         */

        //======= (1) 반별로 그룹화
        Map<String, List<Student>> studentE = studentList
                .stream()
                .collect(groupingBy(Student::getBan));

        System.out.println("=======================");
        for(List<Student > ban : studentE.values()) {
            for(Student studentDetail : ban) {
                System.out.println(studentDetail);
            }
        }



        //======= (2) 성적별로 그룹화
        Map<Object, List<Student>> studentF = studentList
                .stream()
                .collect(groupingBy(s -> {
                    if (s.getScore() >= 150)
                        return s.grade = "우수";
                    else if (s.getScore() >= 100)
                        return s.grade = "보통";
                    else
                        return s.grade = "미달";
                }));

        System.out.println("=======================");
        TreeSet keySet = new TreeSet(studentF.keySet());
        for(Object key : keySet) {
            System.out.println("[" + key + "]");

            for(Student studentDetail : studentF.get(key)) {
                System.out.println(studentDetail);
            }
        }



        //======= (3) 성적별 학생수 ??
        Map<Object, Long> studentG = studentList
                .stream()
                .collect(groupingBy(s -> {
                    if (s.getScore() >= 150)
                        return "우수";
                    else if (s.getScore() >= 100)
                        return "보통";
                    else
                        return "미달";
                }, Collectors.counting()));

        System.out.println("=======================");
        for(Object key : studentG.keySet()) {
            System.out.printf("[%s] - %s명\n", key, studentG.get(key));
        }



        //======= (4) 다중 그룹화 (학년별 / 반별 1등)
        Map<String, Map<String, Student>> studentI = studentList
                .stream()
                .collect(groupingBy(Student::getBan,
                        groupingBy(Student::getHak,
                                collectingAndThen(
                                        maxBy(comparingInt(Student::getScore))
                                , Optional::get
                                )
                        )
                ));

        System.out.println("=======================");
        for(Map<String, Student> ban : studentI.values()) {
            for(Student s : ban.values()) {
                System.out.println(s);
            }
        }
    }

    
    /**
     * 출력결과
     * 
     * =======================
     * Student{name='네오', sex='남', score=150, ban='A', grade='null'}
     * Student{name='콘', sex='남', score=130, ban='C', grade='null'}
     * Student{name='죠르디', sex='남', score=110, ban='A', grade='null'}
     * Student{name='라이언', sex='남', score=90, ban='C', grade='null'}
     * Student{name='무지', sex='남', score=70, ban='A', grade='null'}
     * Student{name='프로도', sex='여', score=140, ban='B', grade='null'}
     * Student{name='어피치', sex='여', score=120, ban='D', grade='null'}
     * Student{name='앙몬드', sex='여', score=100, ban='B', grade='null'}
     * Student{name='스카피', sex='여', score=80, ban='D', grade='null'}
     * =======================
     * 5
     * 4
     * =======================
     * Student{name='네오', sex='남', score=150, ban='A', grade='null'}
     * Student{name='네오', sex='남', score=150, ban='A', grade='null'}
     * Optional[Student{name='프로도', sex='여', score=140, ban='B', grade='null'}]
     * Student{name='프로도', sex='여', score=140, ban='B', grade='null'}
     * =======================
     * [Student{name='무지', sex='남', score=70, ban='A', grade='null'}]
     * []
     * =======================
     * Student{name='네오', sex='남', score=150, ban='A', grade='null'}
     * Student{name='죠르디', sex='남', score=110, ban='A', grade='null'}
     * Student{name='무지', sex='남', score=70, ban='A', grade='null'}
     * Student{name='프로도', sex='여', score=140, ban='B', grade='null'}
     * Student{name='앙몬드', sex='여', score=100, ban='B', grade='null'}
     * Student{name='콘', sex='남', score=130, ban='C', grade='null'}
     * Student{name='라이언', sex='남', score=90, ban='C', grade='null'}
     * Student{name='어피치', sex='여', score=120, ban='D', grade='null'}
     * Student{name='스카피', sex='여', score=80, ban='D', grade='null'}
     * =======================
     * [미달]
     * Student{name='라이언', sex='남', score=90, ban='C', grade='미달'}
     * Student{name='스카피', sex='여', score=80, ban='D', grade='미달'}
     * Student{name='무지', sex='남', score=70, ban='A', grade='미달'}
     * [보통]
     * Student{name='프로도', sex='여', score=140, ban='B', grade='보통'}
     * Student{name='콘', sex='남', score=130, ban='C', grade='보통'}
     * Student{name='어피치', sex='여', score=120, ban='D', grade='보통'}
     * Student{name='죠르디', sex='남', score=110, ban='A', grade='보통'}
     * Student{name='앙몬드', sex='여', score=100, ban='B', grade='보통'}
     * [우수]
     * Student{name='네오', sex='남', score=150, ban='A', grade='우수'}
     * =======================
     * [우수] - 1명
     * [미달] - 3명
     * [보통] - 5명
     * =======================
     * Student{name='네오', sex='남', score=150, ban='A', grade='우수'}
     * Student{name='프로도', sex='여', score=140, ban='B', grade='보통'}
     * Student{name='콘', sex='남', score=130, ban='C', grade='보통'}
     * Student{name='어피치', sex='여', score=120, ban='D', grade='보통'}
     */
}


class Student {
    String name;
    String sex;
    int score;
    String hak;
    String ban;
    String grade;

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

    public boolean isMale() {
        if (this.sex.equals("남"))
            return true;

        return false;
    }

    public void setGrade(String grade) {
        this.grade = grade;
    }

    public String getHak() { return hak; }
    public String getBan() { return ban; }
    public String getName() { return name;}
    public int getScore() { return score; }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", score=" + score +
                ", ban='" + ban + '\'' +
                ", grade='" + grade + '\'' +
                '}';
    }
}