메소드 참조와 Optional

황상익·2023년 12월 15일

열혈 자바

목록 보기
24/30

“이미 정의 되어 있는 메소드가 있다면, 메소드의 정의가 람다식을 대신?”

메소드 참조의 4가지 유형과 메소드 참조의 방법
Static 메소드 참조
참조변수를 통한 인스턴스 메소드 참조
클래스 이름을 통한 인스턴스 메소드 참조
생성자 참조
코드를 줄이면, 코드의 생산성도 향상, -> 가독성 개선으로 이어진다. “메소드 참조는 람다식으로 줄이는 코드 양을 더 줄일 수 있게 된다.

Static 메소드의 참조
컬렉션 인스턴스에 저장된 인스턴스의 저장 순서를 뒤집는다는 람다식이 작성
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;

public class Main1 {
public static void main(String[] args) {
List list = new ArrayList<>(Arrays.asList(1, 3, 5, 7, 9));
list = new ArrayList<>(list);

    Consumer<List<Integer>> consumer = a -> Collections.reverse(a);
    consumer.accept(list);
    System.out.println(list);
}

}

Consumer<List> consumer = a -> Collections.reverse(a);
consumer.accept(list);
 순서 뒤집기 진행
 이미 저장되어 있는 메소드를 사용하는 것에는 큰 의미 있다.
 그리고 :: 은 메소드 참조를 의미하는 연산자

메소드 참조에서 람다식에서 있는 인자 전달에 대한 정보를 생략. Accept 메소드 호출 시 전달되는 인자를 reverse 메소드를 호출하면서 그대로 전달.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;

public class Main2 {
public static void main(String[] args) {
List ls = new ArrayList<>(Arrays.asList(1, 3, 5, 7, 9));
ls = new ArrayList<>(ls);

    Consumer<List<Integer>> c = Collections::reverse;
    c.accept(ls);
    System.out.println(ls);
}

}

Consumer<List> c = Collections::reverse;
c.accept(ls);
 1s 인자를 reverser에 그대로 전달 하게 된다.

인스턴스 메소드 참조1
Static 메소드를 참조하듯, 인스턴스 메소드도 참조 할 수 있다.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;

class JustSort {
public void sort(List<?> lst) {
Collections.reverse(lst);
}
}

public class Main3 {
public static void main(String[] args) {
List list = new ArrayList<>(Arrays.asList(1, 3, 5, 7, 9));
list = new ArrayList<>(list);
JustSort sort = new JustSort();

   // Consumer<List<Integer>> c = e -> sort.sort(e);
    Consumer<List<Integer>> c = sort :: sort;
    c.accept(list);
    System.out.println(list);
}

}

class JustSort {
public void sort(List<?> lst) {
Collections.reverse(lst);
}
}
 인스턴스 메소드
JustSort sort = new JustSort();

// Consumer<List> c = e -> sort.sort(e);
 같은 지역 내 선언된 참조변수 sort에 접근. 그러나 람다식이 인스턴스 생성으로 이어진다는 것을 고려한다면, 특이한 일.
 결론만 말하자면, 람다식 같은 지역 내 선언된 참조변수에 접근 가능
람다식에서 접근 가능한 참조변수는 final로 선언되었거나 effectively final이어야 한다
변수가 effectively final이라는 것은 사실상 final 선언이 된 것과 다름 없음. 다른 인스턴스를 참조하게 되면, final이 아니다. 또한 null 대입을 해도, 오류로 이어진다.
람다식으로 생성된 인스턴스 내에서도 final로 선언되지 않았거나, effectively final이 아닌 참조변수를 참조하게 하는 것은 논리적 혼란을 일으키거나, 예측 불가한 상황으로 이어질 수 있으므로, 제한을 둔다.

Consumer<List> c = sort :: sort;
 ReferenceName :: instanceMethodName
Foreach 메소드
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Main4 {
public static void main(String[] args) {
List list = Arrays.asList("Box", "Toy");
list.forEach(s -> System.out.println(s));
list.forEach(System.out::println);
}
}
Collection 인터페이스는 Iterable를 상속. 따라서 Iterable를 대부분 구현. 디폴트 메소드가 정의.
Default void foreach(Consumer<? Super T> action{
For(T t : this)
Action.accept(t);
}
위 메소드가 호출되면 컬랙션 인스턴스에 저장된 모든 인스턴스들을 대상으로 다음 문장을 실행. Action.accpet(t) -> t는 저장되어 있는 인스턴스 각각을 의미. Foreach 메소드 호출을 위해서 consumer 인터페이스에 대한 람다식 또는 메소드 참조를 전달.
Void accept(T t) -> 반환하지 않고, 전달된 인자를 대상으로 결과를 보임
Accept와 반환형 및 매개변수 선언이 동일. (물론 accpet의 T가 string일 경우 매개변수 동일)

인스턴스 메소드 참조2
import java.util.function.ToIntBiFunction;

class IBox{
private int n;
public IBox(int i){
n = i;
}

public int larger(IBox b){
    if (n > b.n)
        return n;
    else
        return b.n;
}

}

public class Main5 {
public static void main(String[] args) {
IBox ibox = new IBox(5);
IBox ibox2 = new IBox(7);

    ToIntBiFunction<IBox, IBox> bf = (b1, b2) -> b1.larger(b2);
    int bigNum = bf.applyAsInt(ibox, ibox2);
    System.out.println(bigNum);
}

}

ToIntBiFunction<IBox, IBox> bf = (b1, b2) -> b1.larger(b2);
 메소드 larger가 첫 인자로 전달된 메소드
 ToIntBiFunction<IBox, IBox> bf = IBox :: larger;
Static 메소드 참조 처럼 보인다. (= 인스턴스 메소드 참조방식)
Bf가 참조하는 메소드는 IBox :: lager. 첫번째 전달 인자를 대상으로 이 메소드를 호출하기로 약속.

<문제>
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main6 {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("robot");
list.add("Lambda");
list.add("box");
Collections.sort(list, String::compareToIgnoreCase);
Collections.sort(list, (s1, s2) -> s1.compareToIgnoreCase(s2));
System.out.println(list);
}
}

생성자 참조
import java.util.function.Function;

public class Main7 {
public static void main(String[] args) {
Function<char[], String> f = ar -> {
return new String(ar);
};

    char[] sc = {'R', 'o', 'b', 'o', 't'};
    String str = f.apply(sc);
    System.out.println(str);
}

}
인스턴스를 생성하고 있는데 참조 값을 반환 해야 하는 경우 -> 메소드 참조 방식을 쓸 수 있다.
Function<char[], String> f = ar -> {
return new String(ar);
};
이를 줄일 수 있는데 줄이게 되면,
Function<char[], String> f = ar -> new String(ar)
 생성자 참조 방식
단순히 인스턴스 생성 및 참조 값의 반환일 경우 -> className::new
ar의 참조 대상이 String :: new 이므로, ar은 String의 생성자를 강조. 참조변수 f의 자료형이 Function<char[], String> 이므로, 매개변수형이 char[]인 다음 생성자를 참조

<문제>
class Box<T, U>{
private T id;
private U con;

public Box(T i, U c){
    id = i;
    con = c;
}

public void showIt(){
    System.out.println(id + " " + con);
}

}

public class Main8 {
public static void main(String[] args) {
BiFunction<Integer, String, Box<Integer, String>> bf = (a, b) -> new Box<>(a, b);

    Box<Integer, String> b1 = bf.apply(1, "Toy");
    Box<Integer, String> b2 = bf.apply(2, "Robot");
    b1.showIt();
    b2.showIt();
}

}

Optional 클래스
NullPointerException 예외를 접할 수 있다.
NullPointerException 예외 발생 상황
모든 인스턴스 변수를 항상 유효한 값으로 채우지 않는다.
class Friend{
String name;
Company cmp;

public Friend(String name, Company cmp){
    name = name;
    cmp = cmp;
}

public String getName() {
    return name;
}

public Company getCmp() {
    return cmp;
}

}

class Company{
String cName;
ContInfo cInfo;

public Company(String cName, ContInfo ci){
    cName = cName;
    cInfo = ci;
}

public String getcName() {
    return cName;
}

public ContInfo getcInfo() {
    return cInfo;
}

}

class ContInfo{
String phone;
String adrs;

public ContInfo(String phone, String adrs) {
    this.phone = phone;
    this.adrs = adrs;
}

public String getPhone() {
    return phone;
}

public String getAdrs() {
    return adrs;
}

}

public class Main9 {
public static void showCompAddr(Friend f){
String addr = null;

    if (f != null){
        Company com = f.getCmp();
        if (com != null){
            ContInfo info = com.getcInfo();
            if (info != null){
                addr = info.getAdrs();
            }
        }

        if (addr != null)
            System.out.println(addr);
        else
            System.out.println("No address");
    }
}

public static void main(String[] args) {
    ContInfo ci = new ContInfo("123456789", "Korea");
    Company cp = new Company("YaHo", ci);
    Friend f = new Friend("Hwang", cp);
    showCompAddr(f);
}

}

if (f != null){
Company com = f.getCmp();
if (com != null){
ContInfo info = com.getcInfo();
if (info != null){
addr = info.getAdrs();
}
}

if (addr != null)
    System.out.println(addr);
else
    System.out.println("No address");

}
 nullPointerException을 if 문을 통해 막을 수는 있지만 -> 코드 개선 가능

Optional 클래스의 기본적 사용 방법
public final class Opitonal extends Object
 private final T value; (이 참조변수를 통해 저장)
Optional value에 인스턴스를 저장하는 일종의 WrapperClass 이다.
import javax.swing.text.html.Option;
import java.util.Optional;

public class Main10 {
public static void main(String[] args) {
Optional o1 =Optional.of(new String("Toy"));
Optional o2 = Optional.ofNullable(new String("Toy2"));

    if (o1.isPresent())
        System.out.println(o1.get());

    if (o2.isPresent()){
        System.out.println(o2.get());
    }
}

}

Optional o1 =Optional.of(new String("Toy"));
 String 인스턴스를 저장한 Optional 인스턴스를 생성, of 메소드 호출
Optional o2 = Optional.ofNullable(new String("Toy2"));
 String 인스턴스를 저장한 Optional 인스턴스 생성, ofNullable 매소드 호출
of와 ofNullable의 차이점은 null의 허용여부. ofNullable의 인자로는 null 전달 가능. 즉 비어 있는 Optional 인스턴스를 생성. 반면, of 메소드에는 null을 인자로 전달할 수 있다. null을 전달할 경우, nullPointerException이 발생한다.
import javax.swing.text.html.Option;
import java.util.Optional;

public class Main11 {
public static void main(String[] args) {
Optional op = Optional.of(new String("Toy"));
Optional op2 = Optional.ofNullable(new String("Toy2"));

    op.ifPresent(s -> System.out.println(s));
    op2.ifPresent(System.out::println);
}

}
여기서 호출 하고 있는 메소드 ifPresent의 매개변수 형은 Consumer이다.

public void ifPresent(Consumer < ? super T> consumer)
따라서 다음 메소드 accept 구현에 해당하는 람다식을 또는 ifPresent 호출 시 인자로 전달해야만 한다. ifPresent 호출되었을때 Optional 인스턴스가 저장하고 있는 내용물을 보면, accept 메소드가 호출.

Optional 클래스를 사용하면 if ~ else문을 대신할 수 있다.
Optional 사용시 if 문 if ~ else문을 사용하지 않을 수도 있다.
class Info{
String phone;
String adrs;

public Info(String phone, String ards){
    phone = phone;
    adrs = adrs;
}

public String getPhone(){
    return phone;
}

public String getAdrs(){
    return adrs;
}

}

public class Main12 {
public static void main(String[] args) {
Info info = new Info(null, "Korea");
String phone;
String addr;

    if (info.phone != null){
        phone = info.getPhone();
    } else {
        phone = "No number";
    }

    if (info.adrs != null){
        addr = info.getAdrs();
    } else {
        addr = "No address";
    }

    System.out.println(phone);
    System.out.println(addr);
}

}

if & if ~ else문 사용 하지 않고 쓰는 방법
public class Main13 {
public static void main(String[] args) {
Optional op = Optional.of("Optional String");
Optional op1 = op.map(s -> s.toUpperCase());
System.out.println(op1.get());

    Optional<String> op2 = op1.map(s -> s.replace(' ' , '-'))
            .map(s -> s.toLowerCase());
    System.out.println(op2);
}

}
map 메소드의 매개변수형은 다음과 같이 Function. 또한 map은 제네릭 클래스 정의된 제네릭 메소드임을 알수 있다.
public Optional map (Function < ? super T, ? extends U> mapper)
apply 구현에 해당하는 람다식을 map 호출 시 인자로 전달.
Function<T, U> U apply(T t)
Optional의 인스턴스를 대상으로 map 메소드를 호출. 메소드의 구현에 대한 람다식 작성. map 메소드는 에 대한 제네릭 메소드, U는 이 메소드를 호출하는 시점에서 결정. 그렇다면 main 메소드는?
“apply 메소드가 반환하는 대상을 Optional 인스턴스에 담아서 반환”
map이 호출되는 순간 반환형 U가 String으로 결정되어 람다식은 다음 apply 메소드의 몸체를 구성.
 제네릭 클래스에 정의된 제네릭 메소드
Optional는 제네릭 클래스이다. 따라서 Optional의 인스턴스 메소드인 map은
public Optional map (Function ? super String, ? extends U> mapper) 그리고 이는 제네릭 메소드이므로 U는 메소드 호출시 결정.

Optional 클래스 orElse 메소드 소개
Optional 인스턴스에 저장된 내용물을 반환하는 get 메소드가 존재. 또한 유사 기능 orElse. orElse 메소드도 Optional 인스턴스에 저장된 내용물을 반환. / get 메소드와 차이가 있다.
import java.util.Optional;
import java.util.OptionalInt;

public class Main14 {
public static void main(String[] args) {
Optional op = Optional.empty();
Optional op1 = Optional.of("So Basic");

    String s1 = op.map(s -> s.toString())
            .orElse("empty");

    String s2 = op1.map(s-> s.toString())
            .orElse("empty");

    System.out.println(s1);
    System.out.println(s2);
}

}
empty 메소드를 호출하면 저장하고 있는 내용물이 빈 Optional 인스턴스가 생성 그리고 반환.
String s1 = op.map(s -> s.toString()).orElse(“empty”)
 op를 참조하는 Optional 인스턴스는 비어있다. 이 경우 map은 빈 Optional 인스턴스를 생성하여 반환. 빈 Optional 인스턴스를 대상으로, orElse 메소드를 호출.

Optional 클래스의 map과 orElse를 사용하여 if else를 대신한 결과.
class Friend1 {
String name;
Company1 cmp;

public Friend1(String name, Company1 cmp) {
    name = name;
    cmp = cmp;
}

public String getName() {
    return name;
}

public Company1 getCmp() {
    return cmp;
}

}

class Company1 {
String cName;
ContInfo1 cInfo;

public Company1(String cName, ContInfo1 ci) {
    cName = cName;
    cInfo = ci;
}

public String getcName() {
    return cName;
}

public ContInfo1 getcInfo() {
    return cInfo;
}

}

class ContInfo1 {
String phone;
String adrs;

public ContInfo1(String phone, String adrs) {
    this.phone = phone;
    this.adrs = adrs;
}

public String getPhone() {
    return phone;
}

public String getAdrs() {
    return adrs;
}

}

public class Main16 {
public static void showCompAddr(Optional f) {
String addrs = f.map(Friend1::getCmp)
.map(Company1::getcInfo)
.map(ContInfo1::getAdrs)
.orElse("No info");

    System.out.println(addrs);
}

public static void main(String[] args) {
    ContInfo1 ci = new ContInfo1("123456", "Korea");
    Company1 cp = new Company1("Naver", ci);
    Friend1 ff = new Friend1("Hwang", cp);

    showCompAddr(Optional.of(ff));
}

}

Optional 클래스의 fltaMap 메소드
import java.util.Optional;

public class Main17 {
public static void main(String[] args) {
Optional op = Optional.of("Optional String");
Optional op1 = op.map(s -> s.toUpperCase());
System.out.println(op1.get());

    Optional<String> op2 = op1.flatMap(s -> Optional.of(s.toLowerCase()));
    System.out.println(op2);
}

}

Optional op2 = op1.flatMap(s -> Optional.of(s.toLowerCase()));
Optional op1 = op.map(s -> s.toUpperCase());
map과 flatMap 모두 Optional 인스턴스를 반환. 다만 map은 람다식 반환하는 내용물을 Optional 인스턴스로 감싸는 일을 알아서 해주지만, flatMap은 알아서 해주지 않기에, 람다식을 포함하고 있어야 한다. Optional 인스턴스를 클래스 맴버로 두는 경우에 사용 가능.

class ContInFo{
Optional phone;
Optional adrs;

public ContInFo(Optional<String> ph, Optional<String> ad){
    phone = ph;
    adrs = ad;
}

public Optional<String> getPhone() {
    return phone;
}

public Optional<String> getAdrs() {
    return adrs;
}

}

public class Main18 {
public static void main(String[] args) {
Optional ci = Optional.of(
new ContInFo(Optional.ofNullable(null), Optional.of("Korea"))
);

    String phone = ci.flatMap(c-> c.getPhone())
            .orElse("NO Num");

    String addr = ci.flatMap(c -> c.getAdrs())
            .orElse("No Addr");

    System.out.println(phone);
    System.out.println(addr);
}

}
위와 같은상황에서는 Optional을 사용해야 할 때 map 보다는 flatmap을 사용해야 효과적. map을 사용하면 get으로 가져와야 하는 상황 거쳐야 하기 때문.

import javax.swing.text.html.Option;
import java.util.Optional;
import java.util.OptionalInt;

class Friends {
String name;
Optional cmp;

public Friends(String name, Optional<Com> cmp) {
    name = name;
    cmp = cmp;
}

public String getName() {
    return name;
}

public Optional<Com> getCmp() {
    return cmp;
}

}

class Com {
String cName;
Optional ci;

public Com(String cName, Optional<CompInfo> ci) {
    cName = cName;
    ci = ci;
}

public String getcName() {
    return cName;
}

public Optional<CompInfo> getCi() {
    return ci;
}

}

class CompInfo {
Optional phone;
Optional addr;

public CompInfo(Optional<String> phone, Optional<String> addr) {
    this.phone = phone;
    this.addr = addr;
}

public Optional<String> getPhone() {
    return phone;
}

public Optional<String> getAddr() {
    return addr;
}

}

public class Main19 {
public static void showComAddr(Optional f) {
String addr = f.flatMap(Friends::getCmp)
.flatMap(Com::getCi)
.flatMap(CompInfo::getAddr)
.orElse("NO Info");

    System.out.println(addr);
}

public static void main(String[] args) {
    Optional<CompInfo> info = Optional.of(
            new CompInfo(Optional.ofNullable(null), Optional.of("Kroea"))
    );
    Optional<Com> cp =Optional.of(new Com("Hwang", info));
    Optional<Friends> fc = Optional.of(new Friends("Hwang", cp));
    showComAddr(fc);
}

}

OptionalInt, OptionalLong, OptionalDouble
Optional과의 기능적 차이는 제한성이다.
import java.util.Optional;

public class Main20 {
public static void main(String[] args) {
Optional op = Optional.of(3);
Optional op1 = Optional.empty();

    System.out.println("[step 1]");
    op.ifPresent(i -> System.out.println(i));
    op1.ifPresent(i -> System.out.println(i));

    System.out.println("[step 2]");
    System.out.println(op.orElse(100));
    System.out.println(op1.orElse(100));
    System.out.println();

}

}

OptionalInt로 수정
import java.util.OptionalInt;

public class Main21 {
public static void main(String[] args) {
OptionalInt op = OptionalInt.of(3);
OptionalInt op1 = OptionalInt.empty();

    System.out.println("[step 1]");
    op.ifPresent(i -> System.out.println(i));
    op1.ifPresent(i -> System.out.println(i));
    System.out.println();

    System.out.println("[step 2]");
    System.out.println(op.orElse(100));
    System.out.println(op1.orElse(100));
    System.out.println();
}

}

import java.util.OptionalInt;

public class Main21 {
public static void main(String[] args) {
OptionalInt op = OptionalInt.of(3);
OptionalInt op1 = OptionalInt.empty();

    System.out.println("[step 1]");
    op.ifPresent(i -> System.out.println(i));
    op1.ifPresent(i -> System.out.println(i));
    System.out.println();

    System.out.println("[step 2]");
    System.out.println(op.orElse(100));
    System.out.println(op1.orElse(100));
    System.out.println();
}

}

profile
개발자를 향해 가는 중입니다~! 항상 겸손

0개의 댓글