코드스테이츠 수강 5주차 2일차에는 애너테이션, 람다, 스트림, 파일 입출력에 대해 배웟다.
1. 애너테이션
우리는 코딩 할 때 모든걸 손으로 치긴해도, 모든걸 기억할 순 없다.
그래서 주석으로 이 코드가 뭘 하는지 뭘 의미하는지 적어놓는데, 애너테이션도 비슷한 맥락이다.
애너테이션은 정보를 전달하고자 하는 대상이 사람이 아닌 다른프로그램(콤퓨타)인 것에 차이가 있다.
애너테이션의 대표적인 역할은
- 컴파일러에게 문법체크를 하도록 정보제공
- 프로그램을 빌드 시 코드를 자동으로 생성하기 위한 정보 제공
- 런타임(실행 중에) 특정 기능을 실행하도록 정보 제공
예를들어, 애너테이션은 PC에게 "빌드할 때 이거 한번 문제있는지 봐라!" 라고 말해주는 것과 비슷하다.
꼭 빌드가 아니여도 뭐 좀 유심히 봐달라 라고 말해주는 것과 같다.
애너테이션의 종류
에너테이션에는 크게 3가지 종류가 있는데, 그 중에서 우리는 주로 2가지 (표준 애너테이션, 메타 애너테이션)을 사용한다.
- 표준 애너테이션: 자바에서 기본적으로 제공하는 애너테이션
@Override | 컴파일러에게 메서드를 오버라이딩하는 것이라고 알림 |
@Deprecated | 앞으로 사용하지 않을 대상을 알릴 때 사용 |
@FunctionalInterface | 함수형 인터페이스라는 것을 알 |
@SuppressWarning | 컴파일러가 경고메세지를 나타내지 않음 |
- 메타 애너테이션: 애너테이션에 붙이는 애너테이션으로, 애너테이션을 정의하는 데에 사용
@Target | 애너테이션을 정의할 때 적용 대상을 지정하는데 사용한다. |
@Documented | 애너테이션 정보를 javadoc으로 작성된 문서에 포함시킨다. |
@Inherited | 애너테이션이 하위 클래스에 상속되도록 한다. |
@Retention | 애너테이션이 유지되는 기간을 정하는데 사용한다. |
@Repeatable | 애너테이션을 반복해서 적용할 수 있게 한다. |
위의 두개가 주로 사용도는 애너테이션이고, 나머지 하나는 사용자 애너테이션으로, 사용자가 직접 정의하여 사용한다.
표준 애너테이션
표준 에너테이션은 자바가 기본적으로 제공하는 에너테이션이다.
대표적으로 많이 사용되는것 몇가지만 살펴보자
@Override
@Override는 메서드 앞에만 붙일 수 있는 애너테이션으로 선언한 메서드가 상위 클래스의 메서드를 오버라이딩 하는 메서드라는 것을 컴파일러에게 알려주는 역할을 한다.
종종 오버라이딩 할 때 오타 등의 이유로 메서드의 이름을 잘못 작성하는 경우가 있다.
class Super {
void run() {}
}
class Sub extends Super {
void rnu() {} // 오버라이딩을 하려고 했으나 오타가 발생함.
}
run()메서드를 rnu으로 오타 낸 상황.
이때는 서로 이름이 다르니 오버라이딩이 아니고, 그냥 새로운 메서드가 생겻나보다 하고 말아버린다.
이를 방지하기 위해 @Override를 사용하는 것이다.
class Super {
void run() {}
}
class Sub extends Super {
@Override
void rnu() {} // 컴파일 에러 발생, 오타가 난 것을 발견할 수 있음.
}
위의 코드를 보면, @Override 가 밑의 rnu 메서드가 오타난 것을 감지하기 위해 넣은것을 볼 수 있다.
@Override 는 해당 메서드(rnu으로 잘못 친 run)가 상위클래스(Super)의 메서드(run)을 오버라이딩 한 메서드 이며, 오버라이딩이 잘 된건지, 안된건지 검사해달라고 요청하는 애너태이션 인 것이다.
(이 상황에서는 오타 + 오버라이딩 한것이라고 알려준 것이니 컴파일러가 "님 실수함 ㅎ!" 하고 컴파일 에러를 알려준다. )
@Deprecated
@Deprecated는 모종의 이유로 더이상 사용되지 않는 필드, 메서드가 있는 경우에 사용한다.
이 애너테이션을 붙이면, "이거는 더이상 사용하지 않으니 주의해라" 라고 말해주는 것이다.
코드 예시
class OldClass {
@Deprecated
int oldField;
@Deprecated
int getOldField() { return oldField; };
}
위의 코드를 보면 oldField 변수와 getOldField()메서드가 이제는 사용되지 않는다고 말해주는 것이다.
"왜 굳이 이렇게 남겨놓나? 그냥 삭제하면 되는데"
구버젼으로도 돌아갈 수 있게 하려고 이전에 되던것들을 남겨둘 때 사용될 수 있는 것이다.
@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은 함수형 인터페이스를 선언할 때 컴파일러가 함수형 인터페이스가 바르게 선언되었는지 확인하는 것이다.
코드 예시
@FunctionalInterface
public interface Runnable {
public abstract void run (); // 하나의 추상 메서드
}
(함수형 인터페이스는 단 하나의 추상 메서드만 가져야 하는 제약이 있다.)
@FunctionalInterface없이도 함수형 인터페이스를 선언 가능 하지만, 실수방지용으로 넣어두는 것이다.
메타 애너테이션
우리가 이전에 메타인지가 생각을 위한 생각 이라고 했듯이, 메타 애너테이션은 애너테이션을 위한 애너테이션이다.
대표적인것 일부만 보자.
@Target
@Target은 애너테이션을 적용할 대상을 지정할 때 사용한다.
코드 예시
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;
}
위의 코드에서 @Target뒤에 필드, 타입 등을 지정 한 것을볼 수 있다.
지정할 수 있는 타입은 아래와 같다.
ANNOTATION_TYPE | 애너테이션 |
CONSTRUCTOR | 생성자 |
FIELD | 필드(멤버변수, 열거형 상수) |
LOCAL_VARIABLE | 지역변수 |
METHOD | 메서드 |
PACKAGE | 패키지 |
PARAMETER | 매개변수 |
TYPE | 타입(클래스, 인터페이스, 열거형) |
TYPE_PARAMETER | 타입 매개변수 |
TYPE_USE | 타입이 사용되는 모든 대상 |
@Documented
@Documented은 애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 하는 설정이다.
표준, 메타 애너테이션 중 @Override와 @SuppressWarnings를 제외하고 모두 @Documented가 적용 되어 있다.
코드 예시
@Documented
@Target(ElementType.Type)
public @interface CustomAnnotation { }
@Inherited
@Inherited는 하위 클래스가 애너테이션을 상속받도록 하는 애너테이션이다.
해당 애너테이션을 상위클래스에 붙이면 하위 클래스도 상위 클래스에 붙은 애너테이션들이 동일하게 적용된다.
코드 예시
@Inherited // @SuperAnnotation이 하위 클래스까지 적용
@interface SuperAnnotation{ }
@SuperAnnotation
class Super { }
class Sub extends Super{ } // Sub에 애너테이션이 붙은 것으로 인식
위의 코드에서 Super를 상속받은 Sub에서도 @SuperAnnotation에 정의된 내용들을 적용 받는다.
사용자 정의 애너테이션
사용자가 직접 정의하는 애너테이션이다.
인터페이스를 정의하는것과 비슷하다.
@interface 애너테이션명 { // 인터페이스 앞에 @기호만 붙이면 애너테이션을 정의할 수 있습니다.
타입 요소명(); // 애너테이션 요소를 선언
}
사용자 정의 애너테이션은 다른 클래스나 인터페이스를 상속 받을 수 없다.
2.람다
람다는 메서드를 하나의 식으로 표현한 것이다.
메서드를 일일히 선언해서 만들지 않아도 간단한 수학식처럼 간결하고 명확하게 표현이 가능하다.
람다식 만들기
두 수를 더하는 메서드가 있다고 하자
int sum(int num1, int num2) {
return num1 + num2;
}
이 메서드를 일단 람다식으로 만들면
(int num1, int num2) -> { // 반환타입과 메서드명 제거 + 화살표 추가
return num1 + num2;
}
위와 같이 메서드 명이 없어지고, 화살표(중요, 얘 있어야 댐)가 추가되고, 중괄호 안에 동작 내용(실행문)이 적힌다.
여기서 반환값이 있는 메서드는 return과 세미콜론(;)을 생략 가능하다.
(int num1, int num2) -> {
num1 + num2
}
여기서 또 중괄호 없애고, 바로 반환값을 줄 수 있다. (실행문이 하나만 있기 때문)
(int num1, int num2) -> num1 + num2
여기서 또또 매개변수 타입이 쉽게 유추되는 경우면 (사실, 숫자 두개를 단순히 더하는거니까) 매개변수 타입을 생략 가능하다.
(num1, num2) -> num1 + num2
뭔가 엄청 축약된 람다식 완성
함수형 인터페이스
자바에서 함수는 클래스 안에서 정의 되어야 하기 때문에 메서드 독립으로 있을 수 없다.
그렇기 때문에 클래스 객체를 생성 후 이객체로 메서드를 호출해야 한다.
그래서, 람다식도 사실 객체이다.
위에서 메서드 이름을 지운것도 볼 수 있는데, 이름이 없는 상태이기 때문에 이를 익명클래스 라고 한다.
코드 예시
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
위의 코드에서 ExamplFunction 을 이용해서 객체를 만들고 그 안에 람다식을 넣엇다.
그리고, 해당 객체에 sum메서드를 호출해서 10, 15를 넣은 것을 볼 수 있다.
(기존의 인터페이스 문법을 활용하여 람다식을 다루는 것이다. )
이처럼 람다식과 인터페이스의 메서드가 1:1로 매칭되어 동작해야 한다.
매서드 래퍼런스
람다식으로 메서드를 호출 할 수 있다.
만약, 두 개의 수 중 큰 수를 내놓는 메서드가 선언 되어 있다고 하면,
코드 예시
(left, right) -> Math.max(left, right);
위와 같이 작성이 가능하다.
이 경우, 입력값과 출력값의 반환타입을 쉽게 유추 할 수 있기 때문에 입력, 출력값을 적어주지 않아도 된다.
// 클래스이름::메서드이름
Math :: max; // 메서드 참조
고러면 이렇게 또 간단한 람다식이 생겨난다.
IntBinaryOperator 인터페이스는 두 개의 int 매개값을 받아 int 값을 리턴하므로, Math::max 메서드 참조를 대입할 수있다.
IntBinaryOperator operato = Math :: max; //메서드 참조
정적 메서드를 참조 할 때
클래스 :: 메서드
인스턴스 메서드를 참조 할 때
참조 변수 :: 메서드
생성자 참조
메서드 참조는 생성자 참조도 포함된다.
생성자 참조 = 객체생성
객체를 생성하고 반환하도록 구성된 람다식은 생성자 참조로 대체 가능하다.
코드 예시
(a,b) -> {return new 클래스(a,b);};
//생성자 참조 문법
클래스 :: new
생성자가 오버로딩되서 어러개 있으면, 함수형 인터페이스의 추상 메서드와 동일한 매개 변수 타입과 개수를 가지고 있는 생성자를 찾아 실행한다.
3. 스트림
스트림은 배열, 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자이다.
스트림 특징
선언형 프로그래밍이라고 하는데, 쉽게 말하면 "어떻게 하는지는 관심 없고, 뭘 하는지가 중요한 프로그래밍 패러다임"이다.
선풍기에 전기가 얼만큼, 어느 시간동안 들어가는게 중요 한게 아니고, 전원 키면 바람 잘 나오는게 중요한거 처럼 말이다.
스트림은 메서드는 함수형 인터페이스 매개타입을 가지기 때문에 람다식 또는 메서드 참조를 이용해서 요소 처리 내용을 매개값으로 전달할 수 있다.
스트림은 내부 반복자를 시용 가능하다.
컬렉션 내부에서 요소들을 반복시키기 때문에 그냥 요소당 처리할 코드만 제공하면 된다.
외부 반복자는 뭐냐고? 우리가 사용하던 for, while문이다.
외부 반복자를 사용하면 컨렉션의 데이터를 가져오고, 그거 처리하고, 또 가져오고...를 반복하는데
내부 반복자를 사용하면 "그냥 이렇게해라" 라고 던져주면 컬렉션 내에서 알아서지지고 볶아서 결과값을 내준다.
일일히 확인하고 커리하지 말고 그냥 짬때리면 알잘딱깔센으로 내놓는다 이거다.
이렇게 짬때리면 연산도 줄고, 내가 직접 뚜지 않아도 된다.
이러한 특성으로 병렬작업이 가능하여(여러명한테 짬때리는거) CPU활용도가 높아진다.
중간연산과 최종연산을 할 수 있다.
스트림을 진행하면서 나오는 중간의 결과들을 조회 가능하고, 당연히 최종값도 볼 수 있다. (뭐가 어떻게 흘러가는지 알 수 있다!)
파이프라인
스트림은 중간연산과 최종연산으로 동작한다.
파이프라인은 여러개의 스트림이 연결되어 있는 구조를 의미한다.
(필터링, 매핑, 정렬, 그루핑 등의 중간 연산스트림, 합계, 평균, 카운팅, 최대 / 최소값 등의 최종 연산 스트림이 연결되어 있다.)
파이프라인에서 최종 연산을 제외하고는 모두 중간 연산 스트림이다.
(시작)오리지널스트림 -> 필터링 -> 매핑 -> 최종 연산(끝) 으로 생각할 수 있는데, 사실 최종 연산이 시작되기 전까지는 중간연산이 지연된다.
(시작)(최종 (오리지널스트림 -> 필터링 -> 매핑) 연산 )(끝)의 형태를 띄는 것이다.
여기서 주의 할 것은 중간 연산은 스트림을 반환하고, 최종 연산은 결과값(int, String등의 데이터)를 내보낸다.
스트림 생성
Collection 인터페이스에는 stream()이 정의되어 있어 Collection 인터페이스를 구현한 객체들은 모두 stream()을 사용하여 스트림을 생성가능하다.
( stream()으로생성 시 스트림을 반환 )
코드 예시
// List로부터 스트림을 생성
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> listStream = list.stream();
listStream.forEach(System.out::prinln); //스트림의 모든 요소를 출력.
위의 코드에서 stream()으로 스트림을 반환 후, 최종연산(forEach)를 통해 스트림의 "요소"를 출력
배열의 원소를 소스로 하는 스트림은 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 범위 미포함
중간 연산, 최종연산
앞서 스트림은 중간연산 , 최종연산으로 연결된다고 했다.
코드 예시
intStream
.filter(a -> a%2 ==0)
.forEach(n -> System.out.println(n));
중간연산 filter와 최종연산 forEach를 통해 intStream의 원소를 출력아는 것을 볼 수 있다.
중산연산과 최종연산에 대한 메서드는 굉장히 많다.
모두를 다룰 순 없어서, 후에 번외로 올리거나, 일부만 다시 수정하여 올릴 듯 하다.
Optional
Optional은 null 값으로 인해 에러가 발생하는 현상을 객체 차원에서 효율적으로 방지하고자 도입되었다.
Optional클래스는 모든 타입의 캑체를 담을 수 있는 래퍼클래스이다.
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());
Optional 타입의 참조변수를 기본값으로 초기화하려면 empty() 메서드를 사용한다.
Optional<String> opt3 = Optional.<String>empty();
Optional 객체에 저장된 값을 가져올 때는 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");
//초기값을 kimcoding으로 지정하였다. null들어오면 이게 나옴
System.out.println(name);
Optional 객체는 스트림처럼 여러 메서드를 연결해서 작성 가능하다. 이를 메서드 체이닝이라고 한다.
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
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);
}
}
4. 파일입출력
FileInputStream으로 파일 내용을 볼 수 있고, OutputStream으로 파일의 내용을 바꾸고, 파일로 만들 수 있다.
FileReader 으로 파일 내용을 볼 수 있고, FileWriter 으로 파일의 내용을 바꾸고, 파일로 만들 수 있다.
위와 아래의 차이는 입출력 단위다. FileInputStream, OutputStream은 1바이트 단위로 입출력되고, FileReader ,FileWriter 는 2바이트 단위로 입출력된다.
자바에서는 char가 2바이트 이므로, 1바이트만 사용하는FileInputStream, OutputStream을 해결 하기 위해서 FileReader ,FileWriter 문자 기반 스트림을 사용한다.
- 바이트기반 : FileInputStream, OutputStream
- 문자기반 :FileReader ,FileWriter
오늘도 힘들엇다.
뒤로갈수록 지치고, 손목이 이제는 많이 삐그덕 거린다.
아마 키보드, 마우스에 손이 많이 부하가 오는거 같다.
좀 자세히 쓰려다가 욕심인거 같다... 요약해서 쓰도록 하면서 요약을 하기위해 공부도 하면서 써야겟다.
'백엔드 > 코드스테이츠 수강' 카테고리의 다른 글
[회고]코드스테이츠 수강_6주차_1일차_부트캠프 1달 경과 (1) | 2022.09.19 |
---|---|
코드스테이츠 수강_5주차_3~4일차_JAVA_심화(스레드, 자바 가상 머신) (0) | 2022.09.16 |
코드스테이츠 수강_5주차_1일차_JAVA_컬렉션(열거형, 제네릭, 예외 처리, 컬렉션 프레임워크) (0) | 2022.09.15 |
코드스테이츠 수강_4주차_3~4일차_JAVA_객체지향 프로그래밍 심화(다형성, 추상화) (1) | 2022.09.07 |
코드스테이츠 수강_4주차_2일차_JAVA_객체지향 프로그래밍 심화 (상속, 캡슐화) (0) | 2022.09.06 |