람다식& 스트림&I/O

InSeok·2022년 7월 20일
0

TIL

목록 보기
15/51

목차


  1. enum
  2. 애너테이션
  3. 람다
  4. 스트림
  5. 파일 입출력

배운 내용


Enum

  • 열거형(Enumerated Type)

기능

  • 여러 상수들을 보다 편리하게 선언하고 관리할 수 있게한다.
  • 상수명의 중복을 피하고, 타입에 대한 안정성을 보장
  • 더 간결하고 가독성이 좋은 코드를 작성
  • switch문에서도 작동이 가능

문법

  • enum 열거형이름 { 상수명1, 상수명2, 상수명3, ...}
  • 관례적으로 대문자로 작성
  • 각각의 열거 상수들은 객체이다.
  • 자동적으로 0부터 시작하는 정수값이 할당되어 각각의 상수를 가리킨다.
  • 상수에 접근하는 방법 : 열거형이름.상수명

메서드

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

활용 예시

// 상수 
public static final int SPRING = 1;
public static final int SUMMER = 2;
public static final int FALL   = 3;
public static final int WINTER = 4;

public static final int DJANGO  = 1;
public static final int SPRING  = 2; // 계절의 SPRING과 중복 발생!
public static final int NEST    = 3; // 컴파일에러 발생!
public static final int EXPRESS = 4;

-------------------------------------

// 인터페이스를 사용해 상수를 구분함으로써 일차적 해결

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

interface Frameworks {
	int DJANGO = 1, SPRING = 2, NEST = 3, EXPRESS = 4;
}
// but, 타입 안정성 문제 생김
if (Seasons.SPRING == Frameworks.SPRING)
// Seasons의 SPRING과 Frameworks의 SPRING은 의미적으로 다른 개념임에도 불구하고, 이 둘을 비교하면 에러가 발생하지 않기 때문에 타입 안정성이 떨어진다.

// 해당 문제를 해결위해 서로다른 객체를 생성해주면 해결되나 코드 복잡
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();
}

----------------------------------------------------------------
// 이모든것을 효과적으로 해결위한게 Enum 
enum Seasons { SPRING, SUMMER, FALL, WINTER }
enum Frameworks { DJANGO, SPRING, NEST, EXPRESS }

// 활용 예시 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
    }
}

// enum으로 정의한 상수는 switch문에서도 사용가능
//switch문의 조건은 char, byte, short, int, Character, Byte, Short, Integer, String, enum 타입만 가능

// enum 활용한 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;
        }
    }
}
// 출력값 

애너테이션

기능

  • 주석은 소스 코드를 읽는 ‘사람’에게 정보를 제공하는 반면, 애너테이션은 특정 코드를 사용하는 ‘프로그램’에게 정보를 전달
  • 정보 전달과 다른 프로그램에게 유용한 정보를 제공
  • 타겟 외의 다른 프로그램들에게는 아무런 영향을 주지 않는다.

역할

  • 주로, 자바 컴파일러에게 어떤 정보를 제공하기 위한 역할
  • 컴파일러에게 문법 에러를 체크하도록 정보를 제공합니다.
  • 프로그램을 빌드할 때 코드를 자동으로 생성할 수 있도록 정보를 제공합니다.
  • 런타임에 특정 기능을 실행하도록 정보를 제공합니다.

종류

표준 애너테이션

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

메타 애너테이션

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

//@Target

import static java.lang.annotation.ElementType.*;
//import문을 이용하여 ElementType.TYPE 대신 TYPE과 같이 간단히 작성할 수 있다.

@Target({FIELD, TYPE, TYPE_USE})	// 적용대상이 FIELD, TYPE

@Retention - 애넡테이션 지속싯간

유지 정책설명
SOURCE소스 파일에 존재, 클래스파일에는 존재하지 않음
CLASS클래스 파일에 존재, 실행시에 사용불가, 기본값
RUNTIME클래스 파일에 존재, 실행시에 사용가능

**@Repeatable**

  • 같은 이름의 애너테이션이 여러번 적용될 수 있기 때문에, 이 애너테이션들을 하나로 묶어주는 애너테이션도 별도로 작성해야한다.
@interface Works {  // 여러개의 ToDo애너테이션을 담을 컨테이너 애너테이션 ToDos
    Work[] value();
}

@Repeatable(Works.class) // 컨테이너 애너테이션 지정
@interface Work {
	String value();
}

사용자 정의 애너테이션

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

람다(Lambda)

  • 함수형 프로그래밍 기법을 지원하는 자바의 문법요소
  • 메서드를 하나의 ‘식(expression)’으로 표현한 것으로, 코드를 매우 간결하면서 명확하게 표현할 수 있다
  • 공통기능을 일반적인 함수처럼 독립적으로 만든 후 모든 클래스에서 공통적으로 사용할 수있게 하기위해
  • 기본적으로 반환타입과 이름을 생략가능
  • 람다식 또한 사실은 객체이다 → 익명 이너 클래스의 축약된 형태
  • 익명 클래스객체의 선언과 생성을 동시에 하여 오직 하나의 객체를 생성하고, 단 한번만 사용되는 일회용 클래스입니다.

특정조건 충족시

  • return문과 문장 뒤에 오는 세미콜론(;) 생략가능
  • 실행문이 하나만 존재할 때 중괄호를 생략가능
  • 매개변수 타입을 쉽게 유추할 수 있는 경우에는 매개변수의 타입을 생략가능
// 기존 방식
int sum(int num1, int num2) {
	return num1 + num2;
}

// 람다식
(int num1, int num2) -> {
	num1 + num2
}

//실행문 하나일 때
(int num1, int num2) -> num1 + num2

// 매개변수타입 유츄가능할때
(num1, num2) -> num1 + num2

------------------------------------

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

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

함수형 인터페이스(Functional Interface)

  • 기존의 인터페이스 문법을 활용하여 람다식을 다루는 것
  • 람다식도 결국 하나의 객체이기 때문에 인터페이스에 정의된 추상메서드를 구현
    할 수 있기 때문
  • 단 하나의 추상메서드만 선언될 수있다
    • 람다식과 인터페이스의 메서드가 1:1로 매칭되어야 하기 때문

메서드 레퍼런스

  • 불필요한 매개변수를 제거할때 주로 사용
  • 람다식으로 간단해진 익명객체를 더욱더 간단하게 사용하고 싶을때 사용
  • 입력값과 출력값의 반환타입을 쉽게 유추할 수있을때, 단순히 메서드 호출로 구성된 람다식을 메서드 참조로 대치

**정적 메서드와 인스턴스 메서드 참조**

  • 정적 메서드 : 클래스 :: 메서드
    • A a = B::bcd;
    • 인터페이스 A의 객체를 생성할 때 구현해야 하는 abc() 메서드를 B.bcd()와 동일하게 하라.
  • 인스턴스 메서드 : 참조 변수 :: 메서드
    • B b = new B();
    • A a = b::bcd;
    • A 인터페이스 내부의 abc()메서드는 참조변수 b 객체 내부의 인스턴스 메서드 bcd()와 동일하다는 의미
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));
  }
}

**생성자 참조**

  • 생성자를 참조한다는 것은 객체 생성을 의미
  • 단순히 메서드 호출로 구성된 람다식을 메서드 참조로 대치할 수 있듯이, 단순히 객체를 생성하고 리턴하도록 구성된 람다식은 생성자 참조로 대치 가능
  • 클래스 :: new
  • 생성자가 오버로딩 되어 여러 개가 있을 경우 컴파일러는 함수형 인터페이스의 추상 메서드와 동일한 매개 변수 타입과 개수를 가지고 있는 생성자를 찾아 실행

스트림(Stream)

정의

  • 배열, 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자
  • 데이터를 연속적으로 전달하는 통로

특징

  • 선언형으로 데이터 소스를 처리
  • 선언형 프로그래밍 : “어떻게" 수행하는지보다는 “무엇을” 수행하는 지에 중점을두는 프로그래밍
  • 내부 동작 원리를 모르더라도 코드가 무슨 일을 하는지 이해가능
  • Stream이 제공하는 대부분의 요소 처리 메서드는 함수형 인터페이스 매개타입을 가지기 때문에 람다식 또는 메서드 참조를 이용해서 요소 처리 내용을 매개값으로 전달할 수 있습니다.
  • **내부 반복자를 사용하므로 병렬 처리가 쉽다.**
    • 외부반복자(external iterator)
      • 개발자가 코드로 직접 컬렉션의 요소를 반복해서 가져오는 코드 패턴
      • for문, Iterator를 이용한 while문
    • 내부 반복자(internal iterator)
      • 컬렉션 내부에서 요소들을 반복시키고 개발자는 요소당 처리해야할 코드만 제공하는 코드 패턴
  • Iterator는 컬렉션의 요소를 가져오는 것에서부터 처리하는 것까지 모두 개발자가 작성해야 하지만 스트림은 람다식으로 요소 처리 내용만 전달할 뿐, 반복은 컬렉션 내부에서 일어납니다.

활용 예시

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);
    }
}

//  컬렉션에 저장된 Student를 하나씩 출력하도록 forEach()메서드에 매개값으로 람다식을 주는코드
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);
        });
    }
}

파이프라인 구성

리덕션(Reduction)

  • 대량의 데이터를 가공해서 축소하는 것
  • 데이터의 합계, 평균값, 카운팅, 최대, 최소값 등

파이프라인

  • 여러개의 스트림이 연결되어 있는 구조
  • 파이프라인에서 최종 연산을 제외하고는 모두 중간 연산 스트림

Untitled

Stream<Member> maleFemaleStream = list.stream();
Stream<Member> maleStream = maleFemaleSTream.filter(m -> m.getGender() == Member.MALE);
IntStream ageStream = maleStream.mapToInt(Member::getAge);
OptionalDouble opd = ageStream.average();
double ageAve = opd.getAsDouble();
  • .filter(m-> m.getGender() == Member.MALE) 는 남자 Member 객체를 요소로 하는 새로운 스트림을 생성한다.
  • .mapToInt(Member::getAge) 는 Member 객체를 age 값으로 매핑해서 age를 요소로 하는 새로운 스트림을 생성한다.
  • average() 메소드는 age 요소의 평균을 OptionalDouble에 저장합니다. OptionalDouble에 저장된 평균 값을 읽으려면 getAsDouble() 메소드를 호출 하면 된다.

**스트림 생성, 중간 연산, 최종 연산**

**스트림 생성**

  • Collection 인터페이스에는 stream()이 정의되어 있기 때문에, 컬렉션은 모두 이 메서드를 이용해 스트림을 생성
  • stream()을 사용하면 해당 Collection의 객체를 소스로 하는 Stream을 반환
  • 스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐 변경하지 않는다.(Read-only).
  • 스트림은 일회용, 필요하다면 새로운 스트림을 다시 만들어야 한다.
// List로부터 스트림을 생성
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> listStream = list.stream();
listStream.forEach(System.out::prinln); //스트림의 모든 요소를 출력.
  • 배열 원소들을 소스로하는 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 범위 미포함

**중간 연산**

  • 연산 결과를 스트림으로 반환하기 때문에, 연속해서 여러 번 수행가능

**필터링(filter(), distinct())**

  • distinct() : Stream의 요소들중 중복데이터를 제거하기 위해 사용
  • filter() : Stream에서 조건에 맞는 데이터만을 정제하여 더 작은 컬렉션을 생성. 매개값으로 조건(Predicate)이 주어지고, 조건이 참이 되는 요소만 필터링한다.

매핑(map())

  • map() : 기존의 Stream 요소들을 대체하는 요소로 구성된 새로운 Stream을 형성하는 연산
  • 일반적인 Stream 객체를 원시 Stream으로 바꾸거나 그 반대로 하는 작업이 필요한 경우에 사용
  • flatMap() : 요소를 대체하는 복수 개의 요소들로 구성된 새로운 스트림을 리턴

**정렬(sorted())**

  • 정렬하기 위해 사용, 파라미터로 Comparator 사용가능
    • 인자없이 호출시 오름차순, 내림차순은 reverseOrder사용

**연산 결과 확인(peek())**

  • 요소를 하나씩 돌면서 출력
  • 연산 중간에 결과를 확인하여 디버깅하고자 할 때 사용

최종 연산

연산 결과 확인**(forEach())**

**매칭(match())**

  • Stream의 요소들이 특정한 조건을 충족하는지 검사
  • 함수형 인터페이스 Predicate를 받아서 해당 조건을 만족하는지 검사하고, 검사 결과를 boolean으로 반환
  • allMatch() : 모든 요소들이 조건을 만족하는지 조사
  • anyMatch() : 최소한 한 개의 요소가 조건을 만족하는지 조사
  • noneMatch() : 모든 요소들이 조건을 만족하지 않는지 조사

기본집계**(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);

    }
}

**reduce()**

  • 누적하여 하나로 응축(reduce)하는 방식
  • 두 요소의 연산 결과를 바탕으로 다음 요소와 연산
  • 최대 3개의 매개변수를 받을 수 있다.
    1. Accumulator : 각 요소를 계산한 중간 결과를 생성하기 위해 사용
    2. Identity: 계산을 수행하기 위한 초기값
    3. Combiner: 병렬 스트림(Parlallel Stream)에서 나누어 계산된 결과를 하나로 합치기 위한 로직

**collect()**

  • Stream의 요소들을 List나 Set, Map, 등 다른 종류의 결과로 수집하고 싶은 경우 사용
// Stream -> List 
List<Student> maleList = totalList.stream()
        .filter(s -> s.getGender() == Student.Gender.Male)
        .collect(Collectors.toList());
// Stream -> Set
Set<Student> femaleSet = totalList.stream()
        .filter(s -> s.getGender() == Student.Gender.Female)
        .collect(Collectors.toCollection(HashSet :: new));

Optional

  • null 값으로 인해 에러가 발생하는 현상을 객체 차원에서 효율적으로 방지하고자 도입

Optional

  • 모든 타입의 객체를 담을 수 있는 래퍼(Wrapper) 클래스
  • Stream 처럼 사용하기 :Optional을 제대로 사용하려면, Optional을 최대 1개의 원소를 가지고 있는특별한 Stream이라고 생각하면좋다.
public final class Optional<T> {
	private final T value; // T타입의 참조변수
}
  • Optional 객체를 생성하려면 of()또는 ofNullable()을 사용
  • 참조변수의 값이 null일 가능성이 있다면, ofNullable()을 사용
  • 스트림과 유사하게 여러 메서드를 연결해서 작성할 수 있다.(메서드 체이닝)
Optional<String> opt1 = Optional.ofNullable(null);
Optional<String> opt2 = Optional.ofNullable("123");
System.out.println(opt1.isPresent()); //Optional 객체의 값이 null인지 여부를 리턴
System.out.println(opt2.isPresent());

// empty() : optional 타입의 참조변수를 기본값으로 초기화
Optional<String> opt3 = Optional.<String>empty();

//Optional 객체에 저장된 값을 가져오려면 get()을 사용
Optional<String> optString = Optional.of("codestates");
System.out.println(optString);
System.out.println(optString.get());

//참조변수의 값이 null일 가능성이 있다면 orElse()메서드를 사용해 디폴트 값을 지정
Optional<String> optString = Optional.of("codestates");
System.out.println(optString);
System.out.println(optString.get());

String nullName = null;
String name = Optional.ofNullable(nullName).orElse("kimcoding");
System.out.println(name);

// 여러 메서드를 연결해서 사용
public class OptionalExample {
    public static void main(String[] args) {
        List<String> languages = Arrays.asList(
                "Ruby", "Python", "Java", "Go", "Kotlin");
        Optional<List<String>> listOptional = Optional.of(languages);

        int size = listOptional
                .map(List::size)
                .orElse(0);
        System.out.println(size);
    }
}

파일 입출력(I/O)

  • 스트림은 단방향으로만 데이터를 전송할 수 있기에, 입력과 출력을 동시에 처리하기 위해서는 각각의 스트림이 필요

**FileInputStream**

// BufferedInput Stream 사용
public class FileInputStreamExample {
    public static void main(String args[])
    {
        try {
            FileInputStream fileInput = new FileInputStream("codestates.txt");
						BufferedInputStream bufferedInput = new BufferedInputStream(fileInput);
            int i = 0;
            while ((i = bufferedInput.read()) != -1) {
                System.out.print((char)i);
            }
            fileInput.close();
        }
        catch (Exception e) {
            System.out.println(e);
        }
    }
}

**FileOutputStream**


// 같은 디렉토리내에 code문자열이 입력된 codestates.txt 파일 생성
public class FileOutputStreamExample {
    public static void main(String args[]) {
        try {
            FileOutputStream fileOutput = new FileOutputStream("codestates.txt");
            String word = "code";

            byte b[] = word.getBytes();
            fileOutput.write(b);
            fileOutput.close();
        }
        catch (Exception e) {
            System.out.println(e);
        }
    }
}

FileReader

  • 문자 기반 스트림
  • FileReader는 인코딩을 유니코드로 변환하고, FileWriter는 유니코드를 인코딩으로 변환
public class FileReaderExample {
    public static void main(String args[]) {
        try {
            String fileName = "codestates.txt";
            FileReader file = new FileReader(fileName);

            int data = 0;

            while((data=file.read()) != -1) {
                System.out.print((char)data);
            }
            file.close();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
}

FileWriter

public class FileWriterExample {
    public static void main(String args[]) {
        try {
            String fileName = "codestates.txt";
            FileWriter writer = new FileWriter(fileName);

            String str = "written!";
            writer.write(str);
            writer.close();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
}

File

  • File 클래스로 파일과 디렉토리에 접근
  • 일을 생성하기 위해서는 파일 인스턴스를 생성할 때 다음과 같이 첫 번째 인자에 경로를, 두 번째 인자에 파일명을 작성하고, createNewFile()메서드를 호출해주어야 한다.
File file = new File("./", "newCodestates.txt");
file.createNewFile();

//현재 디렉토리(.)에서 확장자가 .txt인 파일만을 대상으로, 파일명 앞에 “code”라는 문자열을 붙여주는 예제
public class FileClassExample {
    public static void main(String[] args) {

        File parentDir = new File("./");
        File[] list = parentDir.listFiles();

        String prefix = "code";

        for(int i =0; i <list.length; i++) {
            String fileName = list[i].getName();

						if(fileName.endsWith("txt") && !fileName.startsWith("code")) {
                list[i].renameTo(new File(parentDir, prefix + fileName));
            }
        }
    }
}

어려운 내용(에러)


문제

해결

profile
백엔드 개발자

0개의 댓글