
(부모) 동물 클래스를 상속받은 (자식) 개 클래스를 만들어서 사용하고 싶다면, 기명 클래스와 익명 클래스 방법 존재
class Animal {
public String cry() {
return "(울음)";
}
}
class Dog extends Animal {
public String cry() {
return "멍멍";
}
}
Animal schnauzer = new Dog();
Animal poodle = new Dog();
Animal maltese = new Dog();
Animal chihuahua = new Animal() {
public String cry() {
return "왈왈";
}
}
(부모) 동물 인터페이스를 구현한 개자식 클래스를 만들어 사용하고 싶다면, 기명 클래스와 익명 클래스 방법 존재
interface IAnimal {
public String cry();
}
class Dog implements IAnimal {
public String cry() {
return "멍멍";
}
}
IAnimal schnauzer = new Dog();
IAnimal poodle = new Dog();
IAnimal maltese = new Dog();
IAnimal chihuahua = new IAnimal() {
public String cry() {
return "왈왈";
}
}
왜 Java 는 익명 구현 객체 문법을 만들었을까?
Member[] members = {
new Member(12, "Aaron"),
new Member(11, "Baron"),
new Member(13, "Caron"),
new Member(10, "Daron"),
};
Arrays.sort(members, 익명 함수 = 어떤걸 기준으로 나열할지)
Arrays.sort(members, new Comparator<Member>() {
@Override
public int compare(Member m1, Member m2) {
return Integer.compare(m1.getId(), m2.getId());
}
})
Arrays.sort(members, (Member m1, Member m2) -> { return Integer.compare(m1.getId(), m2.getId()); });
Arrays.sort(members, (Member m1, Member m2) -> Integer.compare(m1.getId(), m2.getId()) );
Arrays.sort(members, Comparator.comparingInt(each -> each.getId()));
Arrays.sort(members, Comparator.comparingInt(Member::getId));
익명 함수는 어떤 형태들이 될 수 있나?
- 익명 구현 객체는 우리가 어떤 형태의 익명 함수를 쓸지에 대해 종류들을 인터페이스로 나눠놓은 것
- 파라미터가 없고, 결과값이 있는 경우 : ( ) ⇒ (R) = Supplier 라는 인터페이스를 만들어둠
- 파라미터가 있고, 결과값이 없는 경우 : (T) ⇒ ( ) = Consumer 라는 인터페이스를 만들어둠
익명 함수가 될 수 있는 형태들을 미리 정의하여 제공 = 익명 구현 객체의 Syntatic Sugar
| 함수명 | 함수 형태 (파라미터, 반환) | 익명 함수명 | |
|---|---|---|---|
| Function | (T) → (R) | apply | |
| Predicate | (T) → (BOOLEAN) | test | |
| UnaryOperator | (T) → (T) | apply | |
| Supplier | () → (R) | get | |
| Consumer | (T) → () | accept |
| 함수명 | 함수 형태 (파라미터, 반환) | 익명 함수명 | |
|---|---|---|---|
| BiFunction | (T, U) → (R) | apply | |
| BiPredicate | (T, U) → (BOOLEAN) | test | |
| BiUnaryOperator | (T, T) → (T) | apply | |
| 파라미터가 없어서 제외 | |||
| BiConsumer | (T, U) → () | accept |
Function<Integer, Integer> multiplier = (Integer integer) -> integer * 10;
public static int multiply(int operand, Function<Integer, Integer> multiplier) {
return multiplier.apply(operand);
}
Function<Integer, Integer> multiplier = (Integer integer) -> integer * 10;
Integer calculated = multiply(3, multiplier)
System.out.println(calculated);
public static Function<Integer, Integer> create(int operand) {
return (Integer integer) -> integer * operand;
}
Function<Integer, Integer> multiplier = createMultiplier(10);
System.out.println(multiply(3, multiplier));
System.out.println(calculated);
“Mapping 함수 (값 → 값) 적용 방식”을 갖는 “데이터 구조”
ex) List
즉, 값의 데이터 구조에서 → 새 값의 데이터 구조가 나오는것

“Mapping 함수 (값 → 값 with 상태) 적용 방식”을 갖는 “데이터 구조”
ex) Optional
즉, 값의 데이터구조에서 → 오류 상태를 포함한 값의 데이터구조가 나오는 것



왜 Optional 이 등장하였는가? (일반적으로) 발생한 Null 을 외부적으로 처리하는것에 대한 불편함 해결
[Optional 이 없던 시절, Java에서 Null 을 처리하는 법]
/* 주문 */
public class Order {
public Long id;
public Date date;
public Member member;
}
/* 회원 */
public class Member {
public Long id;
public String name;
public Address address;
}
/* 주소 */
public class Address {
public String street;
public String city;
public String zipcode;
}
if (order) {
Member member = order.member;
if (member) {
Address address = member.address;
if (address) {
return address.city;
} else {
return "Seoul";
}
}
}
[JavaScript 에서 Null 을 처리하는 법]
order?.member?.address?.city ?? "Seoul"
// order?.member ?.address ?.city ?? "Seoul"
Optional.ofNullable(order).map((order) -> order.member).map((member) -> member.address).map((address) -> address.city).orElse("Seoul")
Null 을 외부에서 처리했을때 어떤 문제가 있는가?
NPE(NullPointerException) 발생에 대한 처리도 외부에 위치
위에서 본 바와 같이 Null 체크 로직이 외부에 위치하기에 로직의 유지보수성과 가독성 저하
위에서 살펴본 Null 을 외부에서 처리하는 단점 커버를 위해 아닌 내부에서 처리하기 위해서 Optional 등장
Optional<Member> : Generic 제네릭을 통해 어떤 타입의 값을 가질 수 있는지 명시Optional.ofNullable() : 값이 없을수도 있는 = 값이 없는 / 값이 있는 OptionalOptional.of() : 값이 있는 Optional | null 을 Optional.of() 안에 넣어 호출하면 오류 발생Optional.empty() : 값이 없는 Optional.of() .empty() .ofNullable() 모두 정적 팩토리 메서드에 해당Optional<Member> optionalMember = Optional.of(member); // = 성공(값이 있다) 상태 + Member 객체
Optional<Member> optionalMember = Optional.empty(); // = 실패(값이 없다) 상태 + Null 객체
Optional<Member> optionalMember = Optional.ofNullable(member); // = 값이 있을수도, 없을수도 상태 + 객체
Optional.ofNullable()Optional<Member> memberExist = Optional.ofNullable(member);
Optional<Member> memberNotExist = Optional.ofNullable(null);
memberExist = Optional.ofNullable(**member**)Member member = optionalMember.get();
memberNotExist = Optional.ofNullable(**null**).orElse(T other) : 기본값을 가져온다 (Failover 값).orElse**Get**(Supplier<? extends T> other) : 기본값을 반환하는 함수 (Failover 반환).orElse**Throw**(Supplier<? extends X> exceptionSupplier) : Exception 발생 함수Member member = optionalMember.orElse(new Member(0, "Empty"));
Member member = optionalMember.orElseGet(() -> { return new Member(0, "Empty"); });
Member member = optionalMember.orElseThrow(() -> { return new RuntimeException("유저 미존재"); });
Optional 을 정상적인 방법으로 사용하는 사례 : 사용 목적에 맞게 이렇게 개발
int length = Optional.ofNullable(getText()).map((string) -> string.length()).orElse(0);
int length = Optional.ofNullable(getText()).map(String::length).orElse(0);
.map() : Optional 안에 있는 값을 바꾸기/* 주문을 한 회원이 살고 있는 도시를 반환한다 */
public String getCityOfMemberFromOrder(Order order) {
return Optional.ofNullable(order)
.map((order) -> order.getMember())
.map((member) -> member.getAddress())
.map((address) -> address.getCity())
.orElse("Seoul");
}
return Optional.ofNullable(order)
.map(Order::getMember)
.map(Member::getAddress)
.map(Address::getCity)
.orElse("Seoul");
/* 만약 Optional 을 사용하지 않았다면 다음과 같았을것 */
public String getCityOfMemberFromOrder(Order order) {
if (Objects.nonNull(order)) {
Member member = order.member;
if (Objects.nonNull(member)) {
Address address = member.address;
if (Objects.nonNull(address)) {
return address.city;
} else {
return "Seoul";
}
}
}
}
.ofNullable() : 파라미터로 받은 Order 객체가 null 일수도 있기에 of() 대신 ofNullable() 사용.map() : 연쇄 호출(Chaining)을 통해 Optional 객체를 3번 변환Optional<Order> → Optional<Member> → Optional<Address> → Optional<String>.orElse() : 3번이나 변환을 마친 결과 Optional 이 비어있을 경우 Failover 값으로 사용할 "Seoul"Optional<String> Optional 이 비어있을 경우 = Optional 내 String 이 없을 때.filter() : Optional 안에 있는 값을 검사/분류하기/* 특정 시간 이내 주문을 한 회원만 반환한다 */
public Optional<Member> getMemberIfOrderWithin(Order order, int min) {
return Optional.ofNullable(order)
.filter(o -> o.getDate().getTime() > System.currentTimeMillis() - min * 1000)
.map(Order::getMember);
}
Map<Integer, String> cities = new HashMap<>();
cities.put(1, "Seoul");
cities.put(2, "Busan");
cities.put(3, "Daejeon");
String city = cities.get(4); // returns null
int length = city == null ? 0 : city.length(); // null check
System.out.println("length: " + length);
Optional<String> maybeCity = Optional.ofNullable(cities.get(4)); // Optional
int length = maybeCity.map(String::length).orElse(0); // null-safe
System.out.println("length: " + length);
List<String> cities = Arrays.asList("Seoul", "Busan", "Daejeon");
String city = null;
try {
city = cities.get(3); // throws exception
} catch (ArrayIndexOutOfBoundsException e) {
// ignore
}
int length = city == null ? 0 : city.length(); // null check
System.out.println(length);
public static <T> Optional<T> getAsOptional(List<T> list, int index) {
try {
return Optional.of(list.get(index));
} catch (ArrayIndexOutOfBoundsException e) {
return Optional.empty();
}
}
Optional<String> maybeCity = getAsOptional(cities, 3); // Optional
int length = maybeCity.map(String::length).orElse(0); // null-safe
System.out.println("length: " + length);
.ifPresent() : Optional 안에 있는 값이 존재할 경우 함수 실행ifPresent(Consumer<? super T> consumer) : 정확히는 Consumer 함수형 인자를 받는걸 볼 수 있음.isPresent() : Optional 안에 있는 값이 존재하는지 여부만 판단Optional<String> maybeCity = getAsOptional(cities, 3); // Optional
maybeCity.ifPresent(city -> {
System.out.println("length: " + city.length());
});

List<**Integer**> 에 각 요소에 대해 10을 곱한 뒤 문자열로 변환하는 로직을 Stream 으로 구현List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
List<String> strings = integers
/* List<Integer> → Stream<Integer> */.stream()
/* Stream<Integer> → Stream<String> */.map((integer) -> String.valueOf(integer * 10))
/* Stream<String> → List<String> */.toList();
List<**Integer**> : 시작Stream<**Integer**> : 중간 연산을 위한 StreamStream<**String**> : 연산을 모두 마친 StreamList<**String**> : 끝왜 Stream 이 등장하였는가? Collection 각각 요소에 대한 연산을 위해
for,while로 외부에서 처리
- Collection 밖에서 Element 요소 처리 : Loop 통해 외부에서 처리 (외부 반복 External Iteration)
- Collection 안에서 Element 요소 처리 : Stream 통해 내부에서 처리 (내부 반복 Internal Iteration)
[Stream 이 없던 시절 Java 에서 Collection 내 Element 요소 처리하는 법]
for (String string : list) {
if(string.contains("a")) {
return true;
}
}
boolean isExist = list.stream().anyMatch(element -> element.contains("a"));
Stream<String> : Generic 제네릭을 통해 어떤 타입의 요소를 가질 수 있는지 명시String[] arr = new String[]{"a","b","c"};
Stream<String> stream = Arrays.stream(arr);
List<String> lists = Arrays.asList("a", "b", "c");
Stream<String> stream = lists.stream();
Strean<String> stream = Stream.of("a", "b", "c");
Intermediate Operations : 연쇄 호출(Chaining) = 정확히는 Stream 반환
.map : Stream 내 각각의 Element 요소를 다른 값으로 변환 (다른 타입으로도 가능).filter : Stream 내 각각의 Element 요소에 대해 검사/분류Terminal Operations : 값 반환
- Matching - 조건에 따른 Boolean 값 반환
- .anyMatch
- .allMatch 


- **`.noneMatch`**
- Reduction - Stream 내 다수 요소로부터 단일 값 추출 및 반환 **`.reduce`** :
- Collecting - Stream 내 다수 요소를 Collection 에 담아 반환 **`.collect`**
- **`.max`**
- **`.min`**