<T>
Enum
은 열거형으로 서로 관련있는 것들을 모아 번호를 모아 놓은 것을 의미합니다. 관련잇는 것들은 열거형으로 만들 수 있습니다.
enum을 정의하는 방법
enum 열거형이름 {상수명1, 상수명2, 상수명3, ...}
사계절로 예를 들어 정의 한다면
enum Seasons {SPRING, SUMMER, FALL, WINTER} //상수는 관례적으로 모두 대문자
enum
은 static
을 사용하는 것과 동일하게 열거형이름.상수명
으로 참조 가능합니다.
package JAVA_Effective;
enum Seasons {SPRING, SUMMER, FALL, WINTER} //열거형 정의
public class EnumExample {
public static void main(String[] args) {
System.out.println(Seasons.SPRING); //열거형명.상수명
}
}
//Console
//Spring
또 enum
은 열거형이름
타입의 변수에 할당 가능합니다.
Seasons myBestSeason = Seasons.WINTER;
리턴 타입 | 메서드(매개변수) | 설명 |
---|---|---|
String | name() | 열거 객체가 가지고 있는 문자열을 리턴하며, 리턴되는 문자열은 열거타입을 저의 할때 사용한 상수 이름과 동일합니다. |
int | ordinal() | 열거 객체의 순번을 리턴합니다. |
int | compareTo(비교 상수) | 비교 상수와 비교해서 순번 차이 리턴 |
열거 타입 | 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;
public static final int NEST = 3;
public static final int EXPRESS = 4;
인터페이스의 상수로 선언하여 임시로 구별할 수도 있지만 실수로 비교시 에러가 발생하지 않아 안정성의 문제가 있으므로 열거형을 사용 합니다.
enum Seasons {SPRING,SUMMER,FALL,WINTER}
enum Frameworks {DJANGO,SPRING,NEST,EXPRESS}
switch문은 기본 리터럴 타입과, enum
열거형만 구분할 수 있습니다. 사용자가 임의로 정의한 클래스는 구분할 수 없습니다.
package JAVA_Effective;
enum Seasons2 {SPRING, SUMMER, FALL, WINTER}
public class EnumExample3 {
public static void main(String[] args) {
Seasons2 seasons = Seasons2.FALL; //가을
switch(seasons){ //enum 열거형은 switch 구분 가능
case SPRING:
System.out.println("봄");
break;
case SUMMER:
System.out.println("여름");
break;
case FALL:
System.out.println("가을");
break;
case WINTER:
System.out.println("겨울");
break;
}
}
}
//Console
가을
enum
의 장점
에노테이션은 소스코드에는 아무런 영향을 미치지 않으면서 정보를 제공합니다. 누구에게 정보를 제공하는가
에는 주석과 차이가 있습니다.
@에노테이션 //아래 메서드가 대상임을 프로그램에게 알리는 에노테이션
접근지정자 반환타입 메서드(매개변수) {...}
에노테이션은 프로그램에게 정보를 제공하기 위해 사용됩니다.
표준 에너테이션
: 자바에서 기본적으로 제공하는 에너테이션입니다.표준 에너테이션 | 설명 |
---|---|
@Override | 컴파일러에게 메서드를 오버라이딩하는 것이라고 알림 |
@Deprecated | 앞으로 사용하지 않을 대상을 알릴 때 사용 |
@FunctionalInterface | 함수형 인터페이스라는 것을 알림 |
@SuppressWarning | 컴파일러가 경고메세지를 나타내지 않음 |
@Override
는 메서드 앞에만 붙일 수 있는 에너테이션이며, 상위 클래스의 메서드를 오버라이딩 한 메서드라는 것을 컴파일러에게 알려줍니다. 상위 클래스에서 @Override
한 메서드와 이름이 동일한 메서드를 찾을 수 없다면 컴파일 에러를 발생 시킵니다.(메서드 이름 오타로 인한 버그 방지)
class Super{
void run(){}
}
class Sub extends Super{
@Override
void rnu(){}
}
@Deprecated
는 업데이트의 이유로 더 이상 사용하지 않는 필드나 메서드가 생겼을 때, 더 이상 사용하지 않음을 표시합니다. 호환의 이유로 삭제는 곤란하지만 권장하지 않을 때 @Deprecated
를 사용합니다.
class Old{
@Deprecated
int oldField;
@Deprecated
int getOldField(){return oldField;}
}
//Console
Note: 파일명.java uses or overrides a deprecated API.
Note: Recomplie with -Xlint:deprecation for details.
@SuppressWarning
는 컴파일 경고가 나타나지 않도록 합니다. 억제하고자 하는 경고메세지를 지정해 줄 수도 있습니다.
@SuppressWarnings("경고메시지")
all
: 모든 경고를 억제deprecation
: @Deprecated 메서드를 사용한 경우 나오는 경고 억제fallthrough
: switch문에서 break 구문이 없을 때 경고 억제finally
: finally 관련 경고 억제null
: null 관련 경고 억제unreached
: 검증 되지 않은 연산자 관련 경고 억제unused
: 사용하지 않는 코드 관련 경고 억제@FunctionalInterface
는 함수형 인터페이스를 선언 할 때 올바르게 선언하였는지 확인합니다. 실수 방지확인 용이며 함수형 인터페이스는 추상메서드가 1개만 있어야 하는데 2개 이상인 경우 에러를 발생 시킵니다.
메타 에너테이션
: 에너테이션에 붙이는 에너테이션으로 에너테이션을 정의하는 데 사용메타 에너테이션 | 설명 |
---|---|
@Target | 에너테이션을 정의할 때 적용 대상을 지정하는데 사용 |
@Documented | 에너테이션 정보를 javadoc으로 작성된 문서에 포함 |
@Inherited | 에너테이션이 하위 클래스에 상속되도록 함 |
@Retention | 에너테이션이 유지되는 기간을 지정 |
@Repeatable | 에너테이션ㅇ르 반복해서 적용할 수 있게 함 |
@Target
에너테이션으로 적용범위를 정할 수 있습니다. 열거형으로 대상타입들을 복수 지정 가능합니다.
import static java.lang.annotation.ElementType.*;
//import 문을 이용하여 ElementType 생략 가능
@Target({FIELD,TYPE,TYPE_USE}) //적용 대상이 FIELD, TYPE
public @interface CustomAnnotation {} //CustomAnnotation 정의
@CustomAnnotation //적용대상이 TYPE인 경우
class Min{
@CustomAnnotation //적용대상이 FIELD인 경우
int i;
}
@Dcumented
@Documented
@Target(ElementType.Type)
public @interface CustomAnnotation{}
@Inherited
는 하위 클래스가 에너테이션을 상속받도록 합니다. 상위클래스에 @Inherited
에너테이션을 작성하면, 하위 클래스도 상위 클래스의 에너테이션이 적용됩니다.
@Inherited //상속 받는 하위클래스도 영향
@Interface SuperAnnotation{}
@SuperAnnotation
class Super{}
class Sub extends Super{} //@SuperAnnotation도 상속받음
@Retention
에너테이션은 유지 정책입니다. 유지 정책은 에너테이션이 유지되는 기간을 정하는 속성입니다.
유지 정책 | 설명 |
---|---|
SOURCE | .java 소스 파일까지는 에너테이션이 존재, 컴파일 시 클래스 파일이 되면 사라짐 |
CLASS | .class 파일까지는 에너테이션이 존재, 런타임에서 사라짐 |
RUNTIME | 런타임 실행시까지 에너테이션이 남아있음 |
@Repeatable
에너테이션이 붙은 에너테이션은 여러번 작성할 수 있습니다.
@Repeatable(ToDos.class) //Todos 에너테이션을 여러번 반복해서 사용할 수 있게 합니다.
@interface ToDo{
String value()
}
//일반적인 에너테이션과 달리 여러번 적용 될 수 있기 때문에 에너테이션들을 하나로 묶어 주는 별도의 에너테이션도 별도로 작성해야 합니다.
@interface ToDos{ //여러개의 ToDo를 담는 컨테이너 에너테이션 ToDos
ToDo[] value(); //ToDo 에너테이션 배열 타입의 요소 선언, 이름은 반드시 value여야함
}
@ToDo("update test codes.")
@ToDo("override test method")
class Main{
}
대상 타입 | 적용 범위 |
---|---|
ANNOTATION_TYPE | 에너테이션 |
CONSTRUCTOR | 생성자 |
FIELD | 필드(멤버,열거형) |
LOCAL_VARIABLE | 지역변수 |
METHOD | 메서드 |
PACKAGE | 패키지 |
PARAMETER | 매개변수 |
TYPE | 타입 |
TYEP_PARAMETER | 타입매개변수 |
TYPE_USE | 타입이 사용되는 모든 대상 |
사용자 정의 에너테이션
: 사용자가 직접 정의하는 에너테이션 입니다. 에너테이션을 정의하는 방법은 java.lang.annotation
인터페이스를 상속 받기 때문에 다른 클래스나 인터페이스를 상속받을 수 없으며, 인터페이스를 정의하는 것과 비슷 한 방법으로 정의할 수 있습니다.@interface 에너테이션명{ //@interface 에너테이션으로 에너테이션을 정의할 수 있습니다.
타입 요소명(); //에너테이션 요소 선언합니다.
}
람다식은 객체 지향보단 함수 지향 언어에 가깝습니다. 객체지향인 자바에서 함수 지향인 람다식을 사용하는 이유는 매우 깔끔하고 간결한 코드를 작성할 수 있기 때문입니다. 또 요소를 필터링 하거나 매핑해서 원하는 결과를 집계할 수도 있습니다.
클래스타입 참조_변수 = new 클래스타입(매개변수){
접근제어자 반환타입 메서드명(매개변수){...}
}
객체지향의 객체를 생성하는 전형적인 코드입니다.
클래스타입 참조_변수 = (매개변수) -> {...};
으로 람다식 (매개변수) -> {실행코드}
형태로 작성되는데 마치 함수 정의 형식을 띄지만 런타임때 익명 구현 객체로 생성됩니다. 어떤 인터페이스를 구현할 것인가는 대입되는 인터페이스에 달립니다.
//기본 방식
(매개변수) -> {...}
//매개변수가 1개일 때 괄호() 생략 가능
매개변수 -> {...}
//매개변수가 2개이상이고 리턴문만 존재할 때는 return 생략 가능
(매개변수1, 매개변수2,...) -> 리턴값
(num1, num2) -> num1 + num2; //return 생략, 중괋호{}도 생략
//매개변수가 2개이상, 실행문 실행후 결과값 리턴
(매개변수1,매개변수2,...) -> {...}
(타입 매개변수)
로 필요한 값을 제공하고 {실행문}
으로 실행훈 리턴값이 있으면 리턴합니다. 각각 타입 매개변수가 1개이면 괄호()
를 생략할 수 있고 실행문에서 return
과 중괄호{}
도 생략할 수 있습니다.
자바는 새로운 함수 문법을 정의하는 대신, 인터페이스 문법을 활용해 람다식을 표현합니다. 단 하나의 춧아 메서드만을 포함하는 인터페이스를 함수형 인터페이스라고 합니다.
람다식은 하나의 추상메서드만 존재하는 인터페이스만 타겟이 될 수 있습니다. 다라서 반드시 하나의 추상메서드만 선언되어야 하며 2개이상의 추상메서드가 있다면 람다식으로 사용하려 할때 에러를 발생 시켜야 합니다. @FunctionalInterface
에너테이션 사용시 추상메서드가 2개 잇아이면 컴파일 시 오류를 발생시킵니다.
@FunctionalInterface
public interface MyFunctionalInterface{
public void one(); //추상 메서드 1개 가능
//public void another(); //추상 메서들 2개이상은 함수형 인터페이스 불가능
}
MyFunctionalInterface example = () -> {...} //람다식으로 구현한 객체
매개변수는 인터페이스의 추상 메서드의 매개변수와 일치 시켜야 합니다.
만약 매개변수가 존재한다면
@FunctionalInterface
interface MyFunctionalInterface{
publci void one(int x); //int형 매개변수 1개
}
매개변수를 고려하여 추상메서드를 선언해야 하고
MyFunctionalInterface example = (x) -> {
int result = x * 5;
};
example.one(2);
(매개변수)
에 자료형 없이 매개변수를 입력 하여야 사용할 수 있습니다.
(left,right) -> Math.max(left,right);
람다식은 두개의 매개변수로 단순히 Math.max() 메서드의 매개값으로 전달하는 역할만 하기 때문에 비효율적으로 보입니다. 이럴때 메서드 레퍼런스로 깔금하게 처리 간으합니다.
MyOperator operato = Math :: max; //메서드 레퍼런스
클래스(정적) 메서드를 참조할 경우
클래스 :: 메서드
인스턴스 메서드를 참조할 경우
참조_변수 :: 메서드
IntBinaryOperator operator; //2개의 매개변수를 가지는 메서드 레퍼런스를 담는 변수
Calculator calculator = new Calculator();
operator = calculator :: instanceMethod; //메서드 레퍼런스 받아 옵니다.
System.out.println("인스턴스 메서드 결과 : " + operator.applyAsInt(3,5)); //메서드 실행
생성자를 참조하는 경우
(a,b) -> {return new 클래스(a,b);};
단순히 객체를 생성하고 리턴하도록 구성된 람다식은 생성자 참조로 대치 가능합니다.
클래스 :: new
함수형 인터페이스의 추상메서드와 동일한 매개변수 타입과 갯수를 가진 생성자를 찾아 실행합니다. 만약 해당 생성자가 존재하지 않는다면 컴파일 에러를 발생시킵니다.
Function<T t,G g> 참조_변수 = 클래스 :: new; //매개변수 t를 g에 매핑
G g1 = 참조_변수.apply(매개변수); //객체 생성
BiFunction<T t,S s,G g> 참조_변수 = 클래스 :: new; //매개변수 t와s를 g에 매핑
G g2 = 참조_변수.apply(매개변수1,매개변수2); //객체 생성
스트림(Stream)은 배열 컬랙션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자 입니다. 스트림을 사용하면 List,Set,Map,배열등 다양한 데이터 소스로부터 스트림을 만들 수 있고, 이를 표준화된 방법으로 다룰 수 있습니다.
package JAVA_Effective;
import java.util.List;
public class DeclarativeProgrammingExample {
public static void main(String[] args) {
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);
}
}
//Console
선언형 프로그램잉 : 14
.stream()
: 컬렉션을 스트림 객체로 생성합니다..filter(요소 -> 조건)
: 특정 조건에 부합하는 요소들로만 걸러낼 수 있습니다..mapToInt(요소 -> 요소)
: 스트림을 Integer 타입에서 int타입으로 바꾸어줍니다..sum()
: 스트림 내 모든 요소들의 합을 반환합니다.package JAVA_Effective;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class StreamLambdaExample {
public static void main(String[] args) {
List<Student> list = Arrays.asList( //리스트 생성
new Student("김코딩",95),
new Student("이자바",92)
);
Stream<Student> stream = list.stream(); //스트림 반환
stream.forEach(s -> { //for-each문 (내부 반복)
String name = s.getName();
int score = s.getScore();
System.out.println(name + " - " + score);
});
}
}
//Console
김코딩 - 95
이자바 - 92
.forEach(s -> {})
: 이 스트림의 각 요소에 대해 for-each문을 수행합니다.스트림의 데이터를 바로 가공해서 결과물로 바로 낼 수 없을 때 중간 연산들의 결과들을 이어 붙혀 최종 결과물을 얻는 것을 파이프라인이라 합니다. 필터링, 매핑, 그루핑 등을 중간연산이라 합니다.
예를 들어 남자의 평균 나이를 구한다고 가정합시다.
그러면 먼저 남자만으로 구성되도록 필터링을 해야하고, 그 다음 남자들의 나이를 구하는 매핑을 해야하고 마지막으로 평균을 구하는 집계처리를 수행해야 할 것입니다. 일련의 순서들을 이어 붙히는걸 파이프라인이라고 합니다.
double ageAvg = list.stream() //오리지날
.filter(m -> m.getGender() == Member.MALE) //필터링
.mapToInt(Member::getAge) //매핑
.average() //집계 처리(최종)
.getAsDouble();
Collection 인터페이스에는 stream()
메서드가 정의되어있기 때문에 Collection 인터페이스를 구현한 객체들은 모두 stream()
메서드를 이용하여 스트림을 ㅅ생성할 수 있습니다. stream()
메서드는 스트림 객체를 반환합니다.
//List에서 스트림을 생성
List<String> list = Arrays.asList("a","b","c");
Stream<String> listStream = list.stream();
listStream.forEach(System.out::println); //스트림의 모든 요소 출력
스트림을 생성하기 위해서는 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);
distinct()
: 스트림 내 요소들에서 중복된 데이터가 존재하는 경우 중복을 제거합니다.filter(요소->조건)
: 스트림 내에서 조건을 만족하는 요소만 남기고 나머지는 필터링합니다.map000()
: map은 기존의 Stream요소들을 대체하는 새로운 요소들로 구성된 Stream을 형성합니다.대문자로 바뀌는 매핑
컬랙션.stream().map(s->s.toUpperCase()).forEach(n->System.out.println(n));
map()
이외에도 mapToInt()
, mapToLong()
, mapToDouble()
등의 메서드가 있습니다.
Stream의 요소들을 정렬하기 위해서는 .sorted()
를 사용해야 하며 파라미터로 Comparator
를 넘길 수 있습니다. Comparator
인자 없이 호출할 경우 오름차순으로 정렬이 되며, 내림차순 정렬을 위해선 Comparator의 reverseOrder
를 이용합니다.
컬렉션.stream().sorted().forEach(n->System.out.println(n)); //오름차순 정렬
컬렉션.stream().sorted(Comparator.reverseOrder()).forEach(n->System.out.println(n)); //내림차순
컬랙션.stream().sorted(Comparator.comparing(클래스::getter)).forEach(n->System.out.println(n)); //오름차순 정렬
.peek()
: 최종 연산 메서드가 아니고 중간 연산 메서드입니다. 다시 스트림을 반환합니다. 주로 중간 연산 디버깅용으로 많이 사용됩니다..forEach()
: 최종 연산 메서드입니다. 스트림을 반환하지 않습니다.Stream의 요소들이 특정한 조건을 만족하는지 확인하고 싶은 경우에 000match()
메서드를 이용합니다. match()
메서드는 크게 3가지가 존재합니다.
allMatch()
: 모든 요소들이 매개값으로 주어진 조건을 만족하는 지 조사anyMatch()
: 한개 이상의 요소들이 매개값으로 주어진 조건을 만족하는 지 조사noneMatch()
: 모든 요소들이 매개값으로 주어진 조건을 만족하지 않는지 조사package JAVA_Effective;
import java.util.Arrays;
public class MatchesExample {
public static void main(String[] args) {
int[] intArr = {2,4,6}; //배열
boolean result = Arrays.stream(intArr).allMatch(a->a%2==0);//모든요소가 true인지
System.out.println("모두 2의 배수 : " +result);
result = Arrays.stream(intArr).anyMatch(a->a%3==0); //하나이상이 true인지
System.out.println("한개이상 3의 배수 : " + result);
result = Arrays.stream(intArr).noneMatch(a->a%3==0); //모두 false인지
System.out.println("어떤것도 3의 배수 아님 : " + result);
}
}
//Console
모두 2의 배수 : true
한개이상 3의 배수 : true
어떤것도 3의 배수 아님 : false
최종 연산 기능으로 주로 평소에 많이 사용하던 count()
, sum()
, average()
, max()
, min()
등을 하나의 값으로 산출하는 것을 의미합니다.
stream의 요소들을 다른 종류의 결과로 수집하고 싶은 경우에 collect메서드를 이용할 수 있습니다. collect 메서드는 어떻게 stream의 요소들을 수집할 것인가를 정의한 Collector 타입의 인자로 받습니다.
package JAVA_Effective;
public class Student2 {
public enum Gender {Male, Female};
private String name;
private int score;
private Gender gender;
public Student2(String name, int score, Gender gender) {
this.name = name;
this.score = score;
this.gender = gender;
}
public Gender getGender(){
return gender;
}
public String getName(){
return name;
}
public int getScore(){
return score;
}
}
package JAVA_Effective;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class CollectExample {
public static void main(String[] args) {
List<Student2> totalList = Arrays.asList( //리스트 생성
new Student2("김코딩", 10, Student2.Gender.Male),
new Student2("김인기", 8, Student2.Gender.Male),
new Student2("이자바", 9, Student2.Gender.Female),
new Student2("최민선", 10, Student2.Gender.Female)
);
List<Student2> maleList = totalList.stream() //스트림 생성
.filter(s -> s.getGender() == Student2.Gender.Male) //남자만 필터링
.collect(Collectors.toList()); //리스트로 반환
maleList.stream().forEach(n-> System.out.println(n.getName())); //출력
Set<Student2> femaleSet = totalList.stream() //스트림 생성
.filter(s -> s.getGender() == Student2.Gender.Female) //여자만 필터링
.collect(Collectors.toCollection(HashSet::new));
femaleSet.stream().forEach(n-> System.out.println(n.getName())); //출력
}
}
//Console
김코딩
김인기
이자바
최민선
<T>
연산 결과를 Optional에 담아서 반환하면, 따로 조건문을 작성해 주지 않아도 NPE가 발생하지 않도록 코드를 작성할 수 있습니다.
Optional 클래스는 모든 타입의 객체를 담을 수 있는 래퍼(Wrapper)클래스입니다.
public final class Optional<T>{
private final T value; //T타입의 참조변수
}
Optional 객체를 생성하려면 of()
나 ofNullable()
메서드를 사용해서 생성합니다.
Optional<String> opt1 = Optional.ofNullable(null);
Optional<String> opt2 = Optional.ofNullable("123");
System.out.println(opt1.isPresent());
System.out.println(opt2.isPresent());
참조 변수의 기본값을 초기화하려면 empty()
메서드를 호출합니다.
Optional<String> opt3 = Optional.<String>empty();
get()
: 객체에 저장된 값을 가져옵니다.orElse()
: null일때 디폴트 값을 지정합니다.Optional<String> optString = Optional.of("코딩자투리");
System.out.println(optString);
System.out.println(optString.get());
String nullName = null
String name = Optional.getNullable(nullName).orElse("코딩자투리");
System.out.println(name);
(Class<?> InfoExample) //질문하기
InfoExample.getDeclaredFields(); //클래스의 필드 목록 반환
Field //클래스에 있는 변수들의 정보를 제공하기 위해 사용
Field.isAnnotationPresnent(에노테이션 타입) //특정 에노테이션 타입인지 확인
Field.getAnnotation(BackendFramework.class) //특정 에노테이션 타입이 있으면 해당 에노테이션 반환 없으면 null
IntBinaryOperator operator; //2개의 매개변수를 가지는 메서드 레퍼런스를 담는 변수
operator.applyAsInt(3,5) //메서드 실행
알고리즘 문제를 푸는 싸이트에서 문제를 풀다보면 Collection을 사용하다가 int나 double 배열로 반환해야 하는 경우가 많다. 그럴때 마다 스트림을 사용하였지만 정작 스트림이 어떻게 동작하는지, 또 내부에서 람다식이 어떻게 동작하는지 모르고 썼었지만 오늘 제대로 알차게 알게 되어 너무 뿌듯하다.
https://github.com/ds02168/CodeStates/tree/main/src/JAVA_Effective