코드스테이츠 - 3회차 백엔드 부트캠프/Section 1

2022.09.15 목 - 애너테이션, 람다, 스트림, 파일 입출력

곰돌이볼 2022. 9. 15. 17:01

📄 학습내용

애너테이션(Annotation)

  • 애너테이션(annotation)
    • 다른 프로그램에게 정보 제공
    • 테스트를 수행하는 프로그램 외의 다른 프로그램에게는 영향을 주지 않음
  • 주석(comment) : 소스 코드를 읽는 사람에게 정보 제공
  • 역할
    • 컴파일러에게 문법 에러 체크하는 정보 제공
    • 빌드 시 코드를 자동으로 생성할 수 있는 정보 제공
    • 실행 시 특정 기능을 실행하는 정보 제공
  • 종류
    • 표준 애너테이션 : 기본으로 제공하는 애너테이션(자바 컴파일러에게 정보 제공하는 역할)
    • 메타 애너테이션 : 애너테이션을 정의하는 애너테이션
    • 사용자 정의 애너테이션 : 사용자가 직접 정의한 애너테이션
표준 애너테이션 메타 애너테이션 사용자 정의 애너테이션
@Override @Target  
@Deprecated @Documented  
@FunctionalInterface @Inherited  
@SuppressWarning @Retention  
  @Repeatable  

 

  • 표준 애너테이션
  • @Override
    • 하위 클래스에서 상위 클래스의 메서드를 오버라이딩할 때 컴파일러에게 알림
    • 상위 클래스에 동일한 메서드가 없다면 컴파일 에러 발생
  • @Deprecated
    • 새로운 것으로 대체되어서 @Deprecated가 붙은 것은 사용하지 않을 것을 권장
  • @Functionallnterface
    • 함수형 인터페이스 선언이 바르게 되었는지 확인
    • 잘못 선언한 경우 에러 발생
  • @SuppressWarnings
    • 컴파일 경고 메시지 무시
    • 둘 이상의 경고 무시 가능 : @SuppressWarning({"null", "deprescation"})
@SuppressWarnings("all") 모든 경고 무시
@SuppressWarnings("null") null 관련 경고 무시
@SuppressWarnings("deprecation") deprecation 관련 경고 억제

 

  • 메타 애너테이션
  • @Target
    • 애터테이션의 대상 타입 지정
    • java.lang.annotation.ElementType 에 대상 타입의 열거형이 정의되어 있음
    • 사용 : @Target({ElementType.TYPE, ElementType.METHOD})
      @Target(ElementType.METHOD)
대상 타입 범위
ANNOTATION_TYPE 애너테이션
CONSTRUCTOR 생성자
FIELD 필드
LOCAL_VARIABLE 지역변수
METHOD 메서드
PACKAGE 패키지
PARAMETER 매개변수
TYPE 타입(클래스, 인터페이스, 열거형)
TYPE_PARAMETER 타입 매개변수
TYPE_USE 타입이 사용되는 모든 대상
  • @Documented
    • javadoc으로 작성된 문서 포함
    • @Override와 @SuppressWarnings를 제외한 나머지는 @Documented가 적용됨
  • @Inherited
    • 하위 클래스가 애너테이션 상속
    • 상위 클래스에 애너테이션 선언하면 하위 클래스에 상위 클래스에 적용된 애너테이션 적용
  • @Retention
    • 애터네팅션 지속시간 설정
    • 유지정책(retention policy) : 애너테이션 유지 기간을 정하는 속성
      • Retention.SOURCE : 소스 파일 O, 클래스 파일 X
      • Retention.CLASS(기본값) : 클래스 파일 O, 실행 시 사용 X
      • Retention.RUNTIME : 클래스 파일 O, 실행 시 사용 O
  • @Repeatable
    • 애너테이션을 여러 번 사용할 수 있도록 허용
    • 여러 번 적용 가능해서 하나로 묶어주는 애너테이션 작성

 

  • 사용자 정의 애너테이션
    • java.lang.annotation 인터페이스를 상속받아서 다른 클래스나 인터페이스 상속 X
    • 정의
@interface 사용자_정의_애너테이션명 {
    타입 요소명();
}

 

람다(Lambda)

  • 람다식(Lambda)
    • 메서드를 하나의 식으로 표현해서 함수형 프로그래밍 기법을 지원하는 문법 요소
    • 함수형 인터페이스에 정의된 추상메서드를 구현해서 사용
    • 람다식 = 람다 함수 = 객체 = 익명 함수 = 익명 클래스
    • 익명 클래스란? 객체의 선언과 생성 동시에 한 번만 사용하는 일회용 클래스

 

  • 함수형 인터페이스(functional interface)
    • 단 하나의 추상 메서드를 가지는 인터페이스
    • 람다식 : 함수형 인터페이스 = 1 : 1 매칭
    • 사용 이유
      • 자바의 람다식은 함수형 인터페이스로만 접근 가능
      • 기존 자바 문법을 해치지 않고 함수형 프로그래밍 기법을 사용할 수 있도록 하기 위함

 

  • 람다식 표현 방법
    • 구현 : 함수형 인터페이스에 정의된 추상메서드를 구현해서 사용
    • 방법
      • 메서드의 반환타입과 메서드명 제거 + 화살표 추가
    • 특정 조건 만족 시 생략 가능한 것
      • 메서드 바디에 실행문이 하나만 있으면 중괄호 생략 가능
      • 매개변수 타입의 유추가 가능하면 매개변수 타입 생략 가능
      • 매개변수가 하나인 경우 괄호 생략 가능
// 메서드
String sum(String str1, String str2) {
    return str1 + str2;
}

// 람다식 : 메서드의 반환타입과 메서드명 제거 + 화살표 추가
(String str1, String str2) -> { return str1 + str2; }

// 람다식 : 중괄호 생략
(String str1, String str2) -> return str1 + str2;

// 람다식 : 매개변수 타입 생략
(str1, str2) -> return str1 + str2;

// 람다식 : 괄호 생략
str1 -> System.out.println(str1);

 

  • 함수형 인터페이스를 이용해서 람다식 표현 방법
  • 매개변수와 리턴값이 없는 람다식
// 매개변수와 리턴값이 없는 함수형 인터페이스
@FunctionalInterface
public interface Interface1 {
    public void method1();
}

// 람다식 구현
Interface1 inter1 = () -> System.out.println("inter1 구현");

// 람다식 사용
inter1.method1();
  • 매개변수가 있는 람다식
// 매개변수가 있는 함수형 인터페이스
@FunctionalInterface
public interface Interface2 {
    public void method2(int a, int b);
}

// 람다식 구현
Interface2 inter2 = (a, b) -> { 
        int sum = a + b;
        System.out.println(sum); 
    };

// 람다식 사용
inter2.method2(5, 10);
  • 매개변수와 리턴값이 있는 람다식
// 매개변수와 리턴값이 있는 함수형 인터페이스
@FunctionalInterface
public interface Interface3 {
    public int method3(int a, int b);
}

// 람다식 구현
Interface3 inter3 = (a, b) -> { return a - b; };
Interface3 inter3_1 = (a, b) -> a - b;

// 람다식 사용
inter3.method3(50, 10);

 

  • 메서드 레퍼런스
    • 메서드 참조(메서드 레퍼런스) : 메서드를 이용해서 람다식에서 불필요한 매개변수 제거에 활용
    • 참조하는 메서드
      • 정적 메서드 참조 : 클래스::메서드
      • 인스턴스 메서드 참조 : 참조변수::메서드
      • 생성자 참조 : 클래스::new
    • 객체 생성
// 함수형 인터페이스
@FunctionalInterface
interface Sum {
    public abstract String sum_method(int num1, int num2);
}

// 함수형 인터페이스
@FunctionalInterface
interface New {
    public abstract Calculator new_method(int a);
}

class Calculator {
    // 생성자
    public Calculator() {}

    public Calculator(int a) {
        System.out.println("생성자 호출");
    }

    // 정적 메서드
    public static String static_method(int num1, int num2) {
        int sum = num1 + num2;
        return "static_method = " + sum;
    }

    // 인스턴스 메서드
    public String instance_method(int num1, int num2) {
        int sum = num1 + num2;
        return "instance_method = " + sum;
    }
}

public class Test {
    public static void main(String[] args) {
        Sum sum;

        // 정적 메서드
        sum = Calculator::static_method;
        System.out.println(sum.sum_method(10, 20));

        // 인스턴스 메서드
        Calculator c = new Calculator();
        sum = c::instance_method;
        System.out.println(sum.sum_method(10, 20));

        // 생성자
        New new1 = Calculator::new;
        Calculator c1 = new1.new_method(10);
    }
}

 

스트림(Stream)

  • 스트림(stream) : 배열과 컬렉션의 요소들을 하나씩 참조해서 람다식으로 처리하는 반복자
  • 특징
    • 다양한 데이터로부터 스트림을 만들어 표준화된 방법으로 데이터를 다룸
    • 단방향 데이터를 연속적으로 전송
    • 일회용이고, 읽기만 가능
    • 선언형으로 데이터 소스 처리
      • 선언형 방식 : 코드의 내부 동작 원리를 몰라도 코드가 하는 일을 이해("무엇을")할 수 있는 방식 
      • 명령형 방식 : 코드의 내부 동작 원리를 이해해야 코드가 하는 일을 이해하는 방식
    • 람다식으로 요소 처리 코드 제공
      • Stream의 대부분 요소 처리 메서드는 함수형 인터페이스 매개타입 가짐
      • 인자값으로 람다식 또는 메서드 참조 가능
    • 내부 반복자로 인한 쉬운 병렬 처리
      • 외부 반복자(external iterator) : 직접 컬렉션 요소를 반복적으로 가져오는 코드 방식
      • 내부 반복자(internal iterator) : 컬렉션 내부에서 요소를 반복시켜서 요소 처리에만 집중할 수 있도록 하는 코드 방식
      • 순차적인 외부 반복자보다 효율적
      • parallel() 메서드를 이용해서 병렬 스트림 사용 가능
    • 중간 연산 & 최종 연산 가능
      • 중간 연산 : 매핑, 정렬 등 수행
      • 최종 연산 : 총합, 반복, 평균 등 집계 수행

 

  • 파이프라인(Pipeline)
    • 리덕션(reduction) : 큰 데이터를 처리해서 규모를 줄이는 것
    • 파이프파인을 이용해서 스트림의 중간 연산과 최종 연산 수행
    • 구조
      • 여러 개의 스트림이 연결된 구조
      • (컬렉션 or 배열).초기_스트림.(중간_연산_스트림).최종연산
    • 특징
      • 최종 연산이 시작 → 중간 연산 스트림에서 순차적으로 중간 연산 실행 후 최종 연산 수행

 

  • 스트림 과정
    • 배열 또는 컬렉션 객체 → 스트림 생성 → 중간 연산 → 최종 연산

 

  • 스트림 생성
  • Stream 생성 메서드
    • stream() : Collection 인터페이스의 객체(List, set 등) → Stream
    • Steam.of(배열 객체) : 배열 객체 → Stream
    • Arrays.stream(배열 객체) : 배열 객체 → Stream
// Collection 인터페이스의 객체(List, set 등) → Stream
List<Integer> list = Arrays.asList(1, 2, 3); // Arrays.asList() 메서드로 list 생성
Stream<Integer> stream1 = list.stream();

// 배열 객체 → Stream
Stream<String> stream2 = Stream.of("1", "2", "3");
Stream<String> stream3 = Stream.of(new String[]{"1", "2", "3"});
Stream<String> stream4 = Arrays.stream(new String[]{"1", "2", "3"});
  • 특수한 종류의 Stream 생성
    • 특수한 종류 Stream : IntStream, LongStream, DoubleStream
    • 메서드 : of(값들)
    • 원시 자료형 → 특수한 종류의 Stream(= 원시 Stream)
    • IntStream와 LongStream의 range(), rangeClosed() 함수를 통해서 원하는 범위의 숫자를 연속적으로 할당 가능
IntStream stream1 = IntStream.range(0, 10); //0 ~ 9의 정수를 Stream으로 변경
IntStream stream2 = IntStream.range(0, 10); //0 ~ 10의 정수를 Stream으로 변경
  • 메서드 공부하고 느낌점(주관적)
    • Arrays.stream() 메서드 : 배열 → Stream, IntStream, DoubleStream, LongStream ...
      • 인자값으로 배열 오기
      • 인자값에 따라서 Stream 또는 특수한 형태의 Stream이 반환됨
    • Stream.of() 메서드 : 값들 또는 배열 → Stream<>
      • 인자값으로 값들을 받아오기(Stream)
      • 특수한 형태의 Stream은 IntStream 클래스 등을 사용하기

 

  • 중간 연산
  • 필터링
    • distinct() :중복된 데이터 제거
    • filter(인자 -> 조건) : 조건이 참인 데이터 필터링
  • 매핑
    • map(인자 -> 요소 변경값) : Stream 요소들의 값을 새로운 Stream 요소들로 변경
    • mapToInt(), mapToLong(), mapToDouble() : Stream 객체  → 원시 Stream 객체
    • mapToObj(인자 -> 요소 변경값) : 원시 Stream 객체 → Stream 객체
    • boxed() : 원시 Stream 객체 → Stream 객체
    • flatMap(인자 -> 요소 변경값) : Stream의 요소를 복수 개로 구성된 새로운 Stream 객체로 변경
// map() : Stream<String> 객체 → Stream<Integer> 객체
List<String> strings = Arrays.asList("10", "1234");
List<Integer> list1 =
strings.stream() // Stream<String>
        .map(String::length) // Stream<Integer>
        .collect(Collectors.toList()); // List<Integer>
System.out.println(list1); // [2, 4]

// mapToObj() : 원시 Stream 객체(특수한 형태의 Stream 객체) → Stream 객체
List<String> list2 =
IntStream.range(0, 10) //IntStream
        .mapToObj(i -> ""+i) // Stream<String>
        .collect(Collectors.toList()); // List<String>
System.out.println(list2); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

// mapToInt() : Stream 객체 → IntStream 객체
strings.stream() // Stream<String>
        .mapToInt(String::length) //IntStream
        .forEach(System.out::println); // 2 4

// flatMap() : Stream<String[]> 객체 → Stream<String>
Stream<String[]> stream = Stream.of(
        new String[]{"1", "2", "3", "4"},
        new String[]{"a", "b", "c"});

stream // Stream<String[]>
	.flatMap(Arrays::stream); // Stream<String>
  • 정렬
    • sorted() : 오름차순 정렬(디폴트)
    • sorted(Comparator.reverseOrder()) : 내림차순 정렬
    • sorted(Comparator.comparing(기준값)) : 기준값을 오름차순 정렬, stream의 요소가 특수한 형태인 경우
  • 연산 결과 확인
    • peek(요소 -> 실행문) : 모든 요소를 돌면서 출력
    • forEach()와 유사하지만 중간 연산 메서드이므로 여러 번 사용 가능

 

  • 최종 연산
    • 스트림 요소를 사용해서 최종 연산은 딱 한 번만 가능
    • 반면 중간 연산은 여러 번 가능
  • 연산 결과 확인
    • forEach(요소 -> 실행문) : 파이프라인 마지막에서 요소를 하나씩 빼면서 출력
  • 매칭
    • 특정 조건을 검사해서 만족하면 true, 아니면 false를 반환
    • allMatch(함수형 인터페이스) : 모든 요소들이 조건 충족 시 true
    • anyMatch(함수형 인터페이스) : 최소한 한 요소가 조건 충족 시 true
    • noneMatch(함수형 인터페이스) : 모든 요소가 조건 불충족 시 true
  • 기본 집계
    • sum() : 합계
    • count() : 카운팅
    • average() : 평균값
    • max() : 최댓값
    • min() : 최솟값
    • findFirst() : 첫번째 요소값
함수 인자 타입(입력) → 반환 타입(출력)
count() Stream, IntStream, DoubleStream, LongStream   long
sum() IntStream → int
DoubleStream → double
LongStream 
  long
average() IntStream, DoubleStream, LongStream   OptionalDouble
max()
min()
findFirst()
IntStream → OptionalInt
DoubleStream → OptionalDouble
LongStream 
  OptionalLong
  • 집계 결과물
    • reduce(연산 함수(함수형 인터페이스나 함수 참조 가능)) : 요소들을 하나의 값으로 만드는 방식
    • reduce()의 매개변수(최대 3개)
      • Accumulator : 연산 함수
      • Identity : 초기값(초기값 설정 시 초기값과 요소1부터 연산 시작, 초기값 설정 없으면 요소1과 요소2부터 연산 시작)
      • Combiner : 병렬 스트림의 결과를 하나로 합치는 로직
  • collect()
    • collect()의 인자 : Collectors 인터페이스 이용
    • Collectors.toList() : Stream 객체 → List 객체
    • Collectors.toSet() : Stream 객체 → Set 객체
    • Collectors.toCollection() : List와 Set 이외의 형태로 변경할 때 정의해서 사용
      • ex) Collectors.toCollection(LinkedList::new)
        Collectors.toCollection()
  • 즈스트림 → 배열
    • toArray()의 인자 : 메서드 레퍼런드 이용(인자 생략 가능)
    • ex) .toArray(Integer[]::new);
      .toArray();

 

  • Optional 클래스
  • Optional 클래스
    • NullPointerException(NPE)를 방지하고자 도입된 클래스
    • 모든 타입의 객체를 담을 수 있는 래퍼 클래스
public final class Optional<T> {
	private final T value;
}

 

  • 선언
Optional<타입_매개변수> 참조변수 = Optinal.of(객체); // null이 아닌 객체만 인자로 받음
Optional<타입_매개변수> 참조변수 = Optinal.ofNullable(객체); // null인 객체도 받음
Optional<타입_매개변수> 참조변수 = Optinal.ofNullable(value); // null인 경우 value 반환

 

  •  메서드
    • ofNullalbe(객체 또는 초기값) : Object 객체 생성, 객체가 null인 경우에 초기값으로 객체 생성
    • of(객체) : null이 아닌 객체로 Object 객체 생성
    • empty() : 기본값으로 초기화
    • isPresent() : 참조변수가 null이면 false, null이 아니면 true 반환
    • get() : Optional 객체의 타입 매개변수 형태로 저장된 값 가져오기
    • orElse(디폴트) : Optional 객체에 저장된 값 가져오기, 만약 Optional 참조변수가 null인 경우 디폴트 반환
Optional<int[]> opt = Optional.of(new int[]{1, 2, 3});
int[] arr = opt.get();

 

  • OptionalInt, OptionalLong, OptionalDouble 형태의 값 가져오기
    • getAsInt()
    • getAsLong()
    • getAsDouble()

 

파일 입출력(File I/O)

  • 바이트 기반 스트림
  • 입출력 단위(byte)가 1byte인 입출력 스트림
  • FileReader
    • 파일 출력하는 바이트 기반 스트림
    • 성능이 개선된 보조 스트림 : BufferedInputStream
FileInputStream read = new FileInputStream(파일명); // file open
BufferedInputStream buffer = new BufferedInputStream(파일명);
while(정수변수 = read.read() != -1) { // 문자하나씩 file read
    System.out.print((char)정수변수);
}
read.close(); // file close
  • FileWriter
    • 파일 입력하는 바이트 기반 스트림
FileOutputStream write = new FileOutputStream(파일명); // file open
writer.write(저장할_ 문자열); // file write
write.close(); // file close

 

  • 문자 기반 스트림의 입출력
  • 입출력 단위(char)가 2byte인 입출력 스트림
  • 인코딩(encoding) : 문자 기반 스트림과 하위클래스가 사용하는 문자 처리 방식
  • 유니코드(UTF-16) : 자바에서 사용하는 문자 처리 방식
  • FileReader
    • 파일 출력하는 문자 기반 스트림
    • 인코딩 → 유니코드 변환
    • 성능이 개선된 보조 스트림 : BufferedReader
FileReader read = new FileWriter(파일명); // file open
BufferedReader buffer = new BufferedReader(파일명);
while(정수변수 = read.read() != -1) { // 문자하나씩 file read
    System.out.print((char)정수변수);
}
read.close(); // file close
  • FileWriter
    • 파일 입력하는 문자 기반 스트림
    • 유니코드 → 인코딩 변환
FileWirte write = new FileWriter(파일명); // file open
writer.write(저장할_ 문자열); // file write
write.close(); // file close

 

  • 파일 클래스(File Class)
    • 파일과 디렉터리 접근하해서 파일을 읽고 쓰는 클래스
    • 파일 인스턴스 생성
      • File file = new File("디렉터리 위치", "파일명");
      • File direct = new File("디렉터리 위치");
    • 함수
      • createNewFile() : 해당 디렉터리에 같은 파일명이 없다면 새로운 파일 생성
      • remateTo(file 인스턴스) : 파일명 변경
      • getName() : 파일명 출력

 

🧶 발생한 문제 및 해결방법

  • 문제점) ubuntu에서 자바 파일을 컴파일하려고 javac 명령어를 사용했지만 설치가 되어있지 않음
  • 해결방법) sudo apt install default-jdk
    ubuntu에서 자바파일 컴파일 및 실행 방법
$ javac 파일명.java // -> 컴파일
$ java 파일명.class // -> 실행

 

  • 문제점) FileInputStream에서 파일을 읽어오지 못함
    Error : java.io.FileNotFoundException: codestates

  • 해결방법 1 ) 파일명을 절대 경로로 지정해주기
String filename = "C:\\Users\\sinar\\IdeaProjects\\test00\\out\\production\\test00\\codestates";
FileInputStream fileInput = new FileInputStream(filename);
  • 해결방법 2 ) 자바 클래스 파일 properties에 설정된 default location 확인 후 그 위치에 파일 설정 또는 상대 경로 지정하기
String filename = ".\\src\\package00\\codestates"
// String filename = "./src/package00/codestates"
FileInputStream fileInput = new FileInputStream(filename);

 

 공부 난이도

애너테이션, 람다, 스트림 ☆☆★★★

파일 입출력 ☆☆☆☆★

 

🌕 느낀점

  뭔가 휘몰아치면서 하는 느낌이었다. 메서드 참조와 스트림을 합쳐서 사용하니까 정말 정신을 못차리겠다. 어떻게 동작하는지 잘 모르는 것을 보면 메서드 참조를 잘못 이해한 것 같다. 스트림도 연습이 많이 필요해보인다. 내일 연습문제 풀 때 많은 연습이 필요할 것으로 보인다.