Optional이란?

고동현·2024년 3월 6일
0

JAVA

목록 보기
1/22

먼저 Optional을 도대체 왜 쓸까?

나 또한 예전에는 Optional은 있을수도 있고, 없을수도 있는 그런 필드에 사용해야지.
단순히 이렇게만 생각하고, 넘어갔던적이 있었다.
또한, 대부분의 사람들은 Null을 반환하게 되면 NPE가 터지니까 Optional을 이용해서 null을 감싸서 사용해요.
라고 생각하는 사람도 있을 것이다.

  • NullPointerException이란 무엇인가
    NPE란 토니 호어라는 영국 컴퓨터 과학자가 프로그래밍 언어를 설계할때 처음으로 null 참조가 등장했다. 그는 당시 null 참조를 만든 이유를

    "존재하지 않는 값을 표현할 수 있는 방법 중 가장 구현하기가 쉬운 방법이 null 참조였어요"

그 후 몇년뒤, JAVA 개발자들은 컴파일에서는 오류가 안나오다가, 실행할때 터지는 NPE때문에 골머리를 썩었다. 이 NPE를 가리키며 "1조짜리 실수"라고 하였다.

  • 그렇다면 java 8 이전에 Optional이 없을때는 그럼 NPE를 어떻게 방지했을까?
    간단하게, if문으로 막았을까?=>정답이다...
public class Member {
    private  Child;
    // getter,setter구현
}

/* child */
public class  Child{
    private String name;
    // getters,setters구현
}
/* 이코드는 자식의 이름을 반환하는 메서드이다. */
public String getChildName(Member member) {
    return member.getChild().getName();
}

만약에 Member에서 주민등록, 성별,나이등은 등록할 수 있지만, 자녀가 없는 사람이라면? 그런데 자녀의 이름에 접근한다면?
그렇다면, return값이 Null이므로 NPE가 터진다.
그래서 Optional전에는

public String getChildName(Member member) {
    if(member != null) {
        Child child = member.getChild();
        if(child != null) {
            return child.getName();
        }
    }
    return "No child";
}

이런식으로 member가 null인지 아닌지, 또 member에 child가 있는지 없는지를 if문으로 확인을 하면서 로직을 구현했다.
그런데 이렇게 if문을 일일히 쓰면 까먹기라도 하는날에는, 아마 NPE를 만날 가능성이 높을것이다.

이러한 NPE를 해결하기위해서 Optional 클래스를 사용하였다.

Optional의 정의와 사용법

optional은 존재할 수도 있지만 안할 수도 있는 객체이다.
이말이 무엇이냐, 일반 다른 객체는 Null이 들어가서는 안되지만, Option은 객체가 Null이 될수도 있다는것을 고려하고 있는 클래스이다.
그럼 아까 코드에서는 어떻게 이용할까?

public class Member {
    private  Optional<Child> child;
    // getter,setter구현
}

그런데 이렇게 필드에 Optional을 사용하는것은 좋지않다
왜그런지는 뒤에서 설명하겠다.

Optional 객체 만들기

Optional.empty()=> Optional을 만들기위한 정적 팩토리 메서드다.

그럼 정적 팩토리 메서드란 무엇일까?(이런식으로 타고타고 꼬리로 생각하면서 질문하는게 면접과 실력향상에 도움이된다.)
정적 팩토리 메서드란, 생성자로 인스턴스를 생성하지 않고 static Method를 통해 인스턴스를 생성하는것이다.
생성자로 인스턴스를 생성하는것은 다 알테니, static Method로 인스턴스를 생성하는것을 알아보면

public class Member {
    private String name;
 
    private Member(String name) {
    }
 
    public static withStatic(String name) {
        return new Member(name);
    }
}
    public static void main(String[] args) {
        Member member = Member.withStatic("donghyun");
    }

이렇게 생성자가아닌 메서드로 인스턴스를 생성하는것이다.
그러면 여기서 질문이 또 생긴다. 왜? 생성자로 만들면 되지 굳이 메서드로 만드나?
장점
1. 이름을 가질수있다.
일반적인 생성자로 객체를 생성한다면, 매개변수가 무슨 의미를 가진는지 어렵다.
그러나 메서드를 사용해서 짓는다면, 객체의 특성을 잘 묘사할수있다.

Member member1= new Member(“donghyun”);
Member member2 = Member.createByName(“donghyun”);

멤버는 사람의 이름으로 등록한다는 특징을 잘 알 수 있다.

  1. 인스턴스 통제 클래스
    호출 될 때 마다 인스턴스를 또 안 만들어도 된다.
    new를 사용하면 무조건 객체가 새롭게 만들어진다. 많이 만들꺼 같은 인스턴스는 미리 클래스 내부에서 만들고 반환하면 코드가 최적화 될것이다.
    고로, 불변클래스(한번생성하면 변경할 수 없는 클래스-String,Integer등등)는 인스턴스를 미리 만들어놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.

    예를들어,
    int a=10; a=20; 가능할까? 가능하다.
    그러나 int 변수인 a는 변수에 할당된 스텍 메모리에 값을 저장하고 있기 때문에 값을 할당하고 변경하고 할때 메모리에있는 값에서 변경을 한다. 고로 가변적이다.

    반면에 String str="s"; str="news"; 가능할까? 가능하다.
    그러나 문자열 데이터는 스택메모리에 바로저장되는것이아니다.

    이 그림과 같이 스택 메모리에 저장되는것이 아니라, Heap영역중 String constant pool이라는 곳에 메모리를 할당받아 거기에서 값을 저장하고 str은 그 주소값을 참조한는것이다.
    고로, 처음에 str="s"에서 str="news"가 실행될때, 참조하는 주소값이 0x11에서 0x22로 바뀌더라도 str변수의 주소값이 바뀌는것이 이미 저장되어있는 "s"의 0x11주소의 데이터가 바뀌는것이아니다.
    이게 무슨말인지 알겠는가? int a는 더이상 10 이없다. 20으로 바꿨으니까
    다만 string은 0x11의 "s"가 바뀌는게 아니고 새로운 주소를 가르키는것이다.

따라서 이러한 불변클래스를 객체 생성에 비용이 아주큰데 자주 요청이 될때 사용한다면 성능을 상당히 끌어올릴 수 있을것이다.

이러한 정적 팩토리 방식의 클래스는 언제 어느 인스턴스를 살아 있게 할지를 철저히 통제할 수 있다.

어떻게할까? 동일한 객체를 여러번 요청하게 되면, 이전에 생성한 객체를 재사용하게 한다. 그러면 메모리 사용량도 줄이고, 성능도 향상된다.
=>이러한 방법이 플라이 웨이트 패턴이다.
여기서 간단히 설명하자면,

public class FlyweightFactory {
    private static final Map<String, Flyweight> flyweights = new HashMap<>();

    public static Flyweight getFlyweight(String key) {
        if (!flyweights.containsKey(key)) {
            flyweights.put(key, new Flyweight(key));
        }
        return flyweights.get(key);
    }
}

public class Flyweight {
    private String data;

    public Flyweight(String data) {
        this.data = data;
    }

    public String getData() {
        return data;
    }
}

getFlyweight메소드를 통해 객체를 요청받으면, 해당 키의 객체가 있는지 확인하고 만약 객체가 없으면 새로운 Flyweight객체를 생성하여 맵에 저장하고, 있다면 해당 객체를 반환한다.
이렇게하면 동일한 객체를 여러번 요청해도 한번만생성하고, 이후로는 생성한 객체를 재사용하게된다.

단점

  1. 하위클래스를 만들수가없다.
    이 말이무엇이냐면, 인스턴스를 통제하려면 user가 new로 객체생성을 막아야한다. 그러면 생성자를 private로 설정해야할것이다.
    그런데 문제는 생성자가 private이면 상속이 불가능하다는것이다. 즉 자식 클래스를 만들수가 없다는말이다.
    하지만, 이러한제약은 장점으로도 작용할 수 있다. 왜냐하면 이는 사실상 불변성을 보장하기 때문이다.=> 하위클래스가 없다는말은 상속을 통한 오버라이딩이 불가능하므로 클래스의 동작이 변하지않고 일관적이다.

  2. 정적 팩토리 메소드는 프로그래머가 찾기 어렵다.
    다른 메소드와 함께 구분없이 정적 팩토리 메소드가 클래스 내부에 존재하므로, 인스턴스화 할 방법을 프로그래머가 찾아야하는데 쉽지않다는 단점이있다.

이렇게 정적 팩토리 메소드에 대한 내용을 마치기로하고,

결국 위로 올라가보면
Optional optmemer = Optional.empty() 인데
empty()라는 정적팩터리 메서드로 Optional 객체를 생성해서 반환된 빈 객체를 얻을수있다는말이다.
지금까지 길게 설명한것이 허무할 수 있으나.
단순히 empty()하면 메서드내에서 new로 생성하거나 이미 있는객체를 반환한대 라고 암기하는것보단 정적팩터리 메서드가 뭔지 처음부터 보는게 좋을것이다.

다른 방법으로는 Optional optmemer = Optional.of(member);
null이 아닌값을 넣을수있는 Optional을 만들수있다. null이 들어가면 NPE가 터진다.

Optional객체에 접근하기위해서는

  • get()=>객체를 가져오기위함

  • orElse()=> optional.orElse("defalt value") =>비어있을때 defaltvalue출력

  • orElseThrow()=> optional.orElseThrow()=>optinal이 비어있을때 예외를 발생시키는데 사용 try catch문을 사용
    try {
    System.out.println(optional.orElseThrow());
    // NoSuchElementException 발생
    } catch (NoSuchElementException e) {
    e.printStackTrace();
    }

  • ifPresent=>비어있지 않을때 특정 동작 수행
    option = Optional.of("Hello, World");
    optional.ifPresent(sout::println);

    여기까지 결국 Optinal이 무엇인가? 사용법은? 정적 팩터리 메소드? 알아보았다.

    그래서 Optional을 왜쓰는데? 라고 물어보신다면,
    Optional을 만든 BrianGoetz는 이렇게 말했다. Optional은 메서드의 반환값이 '없음'을 나타내는 것이 주 목적이며, 사람들의 기대와 다른 의도로 만들어졌다.

    이말은 고로, NPE를 막기위해서 null을 담을수있는 Optional을 사용하는 것이다. 라는 대부분의 대답과는 달리,
    메서드에 null을 반환하면 오류가 발생할 가능성이 매우 높은 경우에 '결과없음'을 명확하게 드러내기 위해서 매우 제한적으로 Optional을 설계한것이다.

    고로, 맨앞에서 내가 Optional은 필드에 적지 않는것이 좋다. 라고 설명했던것이 기억이나는가?
    애초에 메소드의 반환값으로 null을 피하려고 명시적으로 결과없음을 드러내려고 만들었는데 optional을 필드에 사용하면 이러한 Optional의 의도와는 다른 방식으로 사용되는것이다.

    또, 직렬화 문제가 있는데, Optional클래스는 Serializable인터페이스를 구현하지 않았다.
    그러므로 Optional필드를 가진 클래스를 직렬화하려고 시도하면 문제가 발생한다. 이 내용은 또 길어지니까 다른 포스트에서 직렬화와 Optional의 상관관계를 설명해보도록 하겠다.

    -THE END-

profile
항상 Why?[왜썻는지] What?[이를 통해 무엇을 얻었는지 생각하겠습니다.]

0개의 댓글