18Days) Java 심화(1) -애너테이션, 람다식, 스트림, 파일 입출력

nacSeo (낙서)·2022년 11월 14일
0

◉ 학습목표

1. Annotation, Comment 간의 차이와 Annotation의 종류를 알고 활용할 수 있다.
2. 람다식과 함수형 인터페이스를 이해하고 스트림에 적용시킬 수 있다.
3. 스트림을 생성할 수 있고, 파이프라인 구조를 이용해 연산할 수 있다.
4. 파일 입출력에 대한 내용을 이해하고, 사용할 수 있다.
  1. 애너테이션 (Annotation)

⦿ 학습내용

☞ 주석 - 애너테이션 (Annotation)과 주석(Comment)

✔︎ 주석 : 개발자 스스로 직관적이고 빠르게 코드를 이해하고, 다른 개발자와 협업할 때 해당 코드를 설명하는 정보를 제공
✔︎ Annotation : 특정 코드를 사용하는 프로그램에게 정보를 전달
✔︎ comment : 소스 코드를 읽는 사람에게 정보를 제공

☞ 애너테이션

✔︎ 애너테이션의 역할
✓ 컴파일러에게 문법 에러를 체크하도록 정보를 제공
✓ 프로그램을 빌드할 때 코드를 자동으로 생성할 수 있도록 정보 제공
✓ 런타임에 특정 기능을 실행하도록 정보 제공
✔︎ 애너테이션의 종류
✓ 표준 애너테이션 : 자바에서 기본적으로 제공하는 애너테이션
✓ 메타 애너테이션 : 애너테이션의 애너테이션, 애너테이션을 정의하는 데 사용
✓ 사용자 정의 애너테이션 : 사용자가 직접 정의하는 애너테이션

☞ 표준 애너테이션

✔︎ @Override
✓ 메서드 앞에만 붙일 수 있음
✓ 상위 클래스의 메서드를 오버라이딩하는 메서드라는 것을 컴파일러에게 알려주는 역할
✔︎ @Deprecated
✓ 더 이상 사용하지 않는 필드나 메서드가 있는 경우, 대상이 새로운 것으로 대체되었으니 기존의 것을 사용하지 않을 것을 권장
✔︎ @SuppressWarnings
✓ 컴파일 경고 메세지가 나타나지 않도록 하는 역할※ 둘 이상의 경고를 한번에 묵인 가능
✔︎ @FunctionalInterface
✓ 함수형 인터페이스 선언 시 컴파일러가 선언이 바르게 되었는 지 확인하는 역할
✓ 함수형 인터페이스는 단 하나의 추상 메서드만을 가져야 함

☞ 메타 애너테이션

✔︎ @Target
✓ 애너테이션을 적용할 대상을 지정하는 데 사용
✔︎ @Documented
✓ 애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 하는 애너테이션 설정
✔︎ @Inherited
✓ 하위 클래스가 애너테이션을 상속받도록 하는 역할
✔︎ @Retention
✓ 애너테이션의 지속시간을 결정
✓ 애너테이션 유지 정책의 종류
✔︎ @Repeatable
✓ 애너테이션을 여러 번 붙일 수 있도록 허용
✓ 여러 애너테이션들을 하나로 묶어주는 애너테이션도 별도로 작성해야 함

☞ 사용자 정의 애너테이션

@interface 애너테이션명 {		// 인터페이스 앞에 @기호만 붙이면 애너테이션 정의 가능
	타입 요소명();			// 애너테이션 요소를 선언
}

※ 유의점 : 애너테이션은 다른 클래스나 인터페이스를 상속받을 수 없음

  1. 람다식 (Lambda Expression)

⦿ 학습내용

☞ 람다식 (Lambda Expression)

✔︎ 람다식
✓ 함수형 프로그래밍 기법을 지원하는 자바의 문법요소
✓ 메서드를 하나의 식(expression)으로 표현한 것
✓ 장점 : 코드를 매우 간결하면서 명확하게 표현할 수 있음
✓ 기본적으로 반환타입이름 생략 가능, 익명 함수(anonymous function)이라고도 불림

// 기존 메서드 표현 방식
int sum(int num1, int num2) {
	return num1 + num2;
}

// 람다식 표현 방식
(int num1, int num2) -> {	// 반환타입과 메서드명 제거 + 화살표 추가
	num1 + num2				// 반환값이 있는 메서드의 경우, return문과 세미콜론(;) 생략 가능
}

// 람다식 축약1
(int num1, int num2) -> num1 + num2
// 메서드 바디 문장이 실행문 하나만 존재할 때, 중괄호 생략 가능

// 람다식 축약2
(num1, num2) -> num1 + num2
// 매개변수 타입을 쉽게 유추할 수 있을 때, 매개변수의 타입 생략 가능

☞ 함수형 인터페이스

✔︎ 람다식은 객체이자 익명 클래스
✓ 익명 클래스 : 객체의 선언과 생성을 동시에 하여 오직 하나의 객체를 생성하고, 단 한번만 사용되는 일회용 클래스
✓ 익명 객체를 생성하여 참조변수에 담아준다 하더라도 해당 클래스에 메서드가 없기 때문에 사용 ❌
✔︎ 이를 해결하기 위해 함수형 인터페이스 등장
✓ 람다식 역시 하나의 객체이기에 인터페이스에 정의된 추상메서드 구현 가능
✔︎ 매개변수와 리턴값이 없는 람다식의 함수형 인터페이스

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

✔︎ 매개변수가 있는 람다식의 함수형 인터페이스

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

✔︎ 리턴값이 있는 람다식의 함수형 인터페이스

@FunctionalInterface
public interface MyFunctionalInterface {
	public int accept(int x, int y);
}

☞ 메서드 레퍼런스

✔︎ 메서드 참조
✓ 람다식에서 불필요한 매개변수를 제거할 때 주로 사용
✓ 인터페이스의 익명 구현 객체로 생성
✓ 인터페이스의 추상 메서드가 어떤 매개 변수리턴 타입이 무엇인가에 따라 달라짐

// 예시

(left, right) -> Math.max(left, right);
// 기존 (입력값과 출력값의 반환타입 쉽게 유추 가능)

// 클래스_이름::메서드_이름
Math :: max;	// 메서드 참조 이용

✔︎ 정적 메서드 참조

// 클래스 이름 뒤에 :: 기호를 붙이고 정적 메서드 이름을 기술
클래스 :: 메서드

✔︎ 인스턴스 메서드 참조

// 먼저 객체를 생성한 다음, 참조 변수 뒤에 :: 기호를 붙이고 인스턴스 메서드 이름을 기술
참조_변수 :: 메서드

✔︎ 생성자 참조 (→객체 생성을 의미)

// 예시
(a,b) -> {return new 클래스(a,b);};

// 생성자 참조 : 클래스 이름 뒤에 :: 기호를 붙이고 new 연산자 기술
클래스 :: new
  1. 스트림 (Stream)

⦿ 학습내용

☞ 스트림(Stream)의 정의와 특징

✔︎ 스트림 : 배열, 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자
✔︎ 스트림의 특징
✓ 선언형으로 데이터 소스 처리
✓ 람다식으로 요소 처리 코드 제공
내부 반복자를 사용해 병렬 처리가 쉬움
※ 외부 반복자 (external iterator)
: 개발자가 코드로 직접 컬렉션의 요소를 반복해서 가져오는 코드 패턴
※ 내부 반복자 (internal iterator)
: 컬렉션 내부에서 요소들을 반복시키고, 개발자는 요소당 처리해야할 코드만 제공하는 코드 패턴
✓ 중간 연산과 최종 연산 가능

☞ 파이프라인 구성 (.)

✔︎ 리덕션 (Reduction) : 대량의 데이터를 가공해서 축소하는 것
✔︎ 파이프라인 : 여러 개의 스트림이 연결되어 있는 구조
✔︎ 예시

// 기존 파이프라인 구성
Stream<Member> maleFemaleStream = list.stream();
Stream<Member> maleStream = maleFemaleStream.filter(m->m.getGender()==Member.MALE);
IntStream ageStream = maleStream.mapToInt(Member::getAge);
// Member객체를 age 값으로 매핑해 age를 요소로 하는 새로운 스트림 생성
OptionalDouble opd = ageStream.average();
// age요소의 평균을 OptionalDouble에 저장
double ageAve = opd.getAsDouble;
// OptionalDouble에 저장된 평균 값을 읽으려면 getAsDouble()메소드 호출

// 로컬 변수를 생략한 파이프라인 구성
double ageAve = list.stream()	// 오리지널 스트림
	.filter(m->m.getGender() == Member.MALE)	// 중간 연산 스트림
    .mapToInt(Member::getAge)	// 중간 연산 스트림
    .average()	// 최종 연산
    .getAsDouble;

☞ 스트림 생성

✔︎ 스트림 생성

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

// 배열로부터 스트림 생성 : Stream의 of 메서드 또는 Arrays의 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 범위 미포함

※ 이 외에도 int,long,double 등 원시 자료형을 위한 stream(IntStream,LongStream,DoubleStream) 사용 가능
✔︎ 스트림 사용 시 주의할 점
✓ 데이터 읽기만 할 뿐 변경 ❌ (Read-only)
✓ 스트림은 일회용, 한 번 사용하면 닫히고 필요하면 새로운 스트림 생성해야 함

☞ 중간 연산

① 필터링
distinct() : 중복 제거
filter() : 조건에 맞는 데이터만 정제하여 더 작은 컬렉션을 만듦, 매개값으로 조건이 주어지고 참인 요소만 필터링
② 매핑
map() : 기존 요소들을 대체하는 요소로 구성된 새로운 Stream 형성
mapToInt(), mapToLong(), mapToDouble()등의 메서드 존재
flatMap() : 요소를 대체하는 복수 개의 요소들로 구성된 새로운 Stream 리턴
※ map()과 flatMap()의 차이점
: map()은 스트림의 스트림 반환(리턴 타입 : Stream<Stream>), flatMap()은 스트림 반환(리턴 타입 : Stream)
③ 정렬
sorted()

// 파라미터로 Comparator 사용, 인자 없이 호출 시 오름차순 정렬
list.stream()
	.sorted(Comparator())

// Comparator의 reverseOrder 사용 시 내림차순 정렬
list.stream()
	.sorted(Comparator.reverseOrder())
    
// comparing() 메서드 통해 비교 가능
workersSteam.sorted(Comparator.comparing(Employee::getId))

④ 연산 결과 확인
peek() : 요소를 하나씩 돌면서 출력 가능
※ forEach와 기능적으로 동일하지만, 중간∙최종 연산 메서드란 점, 따라서 peek는 여러 번 사용 가능하다는 점에서 다름

☞ 최종 연산

① 연산 결과 확인
forEach() : 파이프라인 마지막에 요소를 하나씩 연산
② 매칭
match() : 특정한 조건을 충족하는지 검사할 때 사용
allMatch() : 모든 요소들이 매개값으로 주어진 조건을 만족하는 지 조사
anyMatch() : 최소 한 개의 요소가 매개값으로 주어진 조건을 만족하는 지 조사
noneMatch() : 모든 요소들이 매개값으로 주어진 조건을 만족하지 않는 지 조사
③ 기본 집계
sum(), count(), average(), max(), min()
reduce()
✓ 누적하여 하나로 응축하는 방식
✓ 최대 3개의 매개변수를 받을 수 있음
: Accmulator(각 요소 계산 중간 결과 생성), Identity(초기값), Combiner(병렬 스트림으로 나눠진 계산 결과를 합침)
collect()
✓ List나 Set, Map 등 다른 종류의 결과로 수집하고 싶은 경우

☞ Optional<T>

✔︎ null 값으로 인해 에러가 발생하는 현상(NullPointerException(NPE)을 객체 차원에서 효율적으로 방지하고자 도입
✔︎ Optional 클래스 : 모든 타입의 객체를 담을 수 있는 래퍼(Wrapper) 클래스
✔︎ 객체 생성을 위해 of() 또는 참조 변수가 null인 가능성이 있을 때는 ofNullable() 사용
✔︎ 참조 변수의 기본값을 초기화할 때 empty() 메서드 사용
✔︎ Optional 객체에 저장된 값을 가져올 때 get() 메서드 사용
✔︎ 참조 변수의 값이 null일 가능성이 있다면 orElse()메서드를 사용해 디폴트 값 지정 가능
✔︎ Optional 객체는 스트림과 유사하게 여러 메서드 연결하여 작성 가능(= 메서드 체이닝)

  1. 파일 입출력 (I/O)

⦿ 학습내용

☞ InputStream / OutputStream

✔︎ 바이트 기반 스트림
✔︎ File을 대상으로 다룰 때 FileInputStream / FileOutputStream 사용
✔︎ 프로세스를 대상으로 다룰 때 PipeInputStream / PipeOutputStream 사용
✔︎ BufferedInputStream이라는 보조 스트림을 사용해 성능 향상 가능

☞ FileReader / FileWriter

✔︎ 문자 기반 스트림
✔︎ InputStream이 Reader로, OutputStream이 Writer로 대응
✔︎ FileReader는 인코딩을 유니코드로 변환하고, FileWriter는 유니코드를 인코딩으로 변환

☞ File

File file = new File("./", "hello.txt");	// 파일 인스턴스 생성, (경로, 파일명)
file.createNewFile();						// 파일 생성

◉ 느낀 점

☞ 지금까지 자바를 사용하면서 접한 문법이 아닌 람다식을 이용한 스트림을 활용하니까 아직 어색하고 낯설기만 하다. 보다 간결하고 명확한 코드를 위해 사용한다고 했는데 내게는 굳이 왜 이렇게 이해가 안되는 식을 사용하는가 싶다 😂 아직 적응이 안돼서 그렇겠지..? 내일 스트림 문제풀이 시간과 개인적인 응용 공부를 반복하면서 연습하다보면 이래서 사용하는구나! 느낌이 올 것 같다!

◉ 내일의 키워드

・ Stream 연습문제 풀이
・ Thread
・ JVM
profile
백엔드 개발자 김창하입니다 🙇‍♂️

0개의 댓글