• 일급 객체

    • 일급 객체란
      • 사용할 때 다른 요소들과 아무런 차별이 없음을 의미
      • 즉, 요소들이 사용되는 환경이 구분되지 않아야함(메서드와 함수의 차이)
    • 일급 객체의 조건
      • 모든 일급 객체는 변수나 데이터에 담을 수 있어야 한다
      • 모든 일급 객체는 함수의 파라미터로 전달할 수 있어야 한다
      • 모든 일급 객체는 함수의 리턴값으로 사용할 수 있어야 한다
    • 각 조건의 예제
      • 모든 일급 객체는 변수나 데이터에 담을 수 있어야 한다

        • 자바의 메서드는 변수에 할당하는 등의 사용 불가능
        • 반면, 자바스크립트의 경우 함수 표현식으로 자유롭게 대입 가능
        public class main {
        	public static void hello() {
        		System.out.println("Hello World");
        	}
        	public static void main(String[] args) {
        		// java에서는 아래 코드처럼 특정 변수에 메서드 할당이 불가능
        		Object a = hello;
        	}
        }
        
        // 반면 js에서는 변수에 메서드 할당 가능
        const hello = function() {
        	console.log("Hello World");
        }
        
      • 함수의 파라미터로 전달할 수 있어야 한다

        • 자바의 메서드는 메서드의 파라미터로 전달이 불가능
        • 반면, 자바스크립트의 경우 콜백 함수 형태로 자유롭게 전달 가능
        public class Main {
        	public static void hello() {
        		System.out.println("Hello World");
        	}
        	// 아래처럼 특정 메서드를 받는 것 자제가 불가능
        	public static void print(Object func) {
        		func();
        	}
        	// 특정 메서드를 파라미터로 받는 것 자체가 불가능하므로 전달하여 사용도 불가능
        	public static void main(String[] args) {
        		print((Object) hello);
        	}
        }
        
        const hello = function() {
        	console.log("Hello World");
        }
        
        function print(func) {
        	func();
        }
        
        // js에서는 메서드 파라미터로 메서드를 전달하여 사용 가능
        print(hello);
        
      • 함수의 리턴값으로 사용할 수 있어야 한다

        • 자바의 메서드의 리턴값을 메서드 자체로 하는 것은 불가능
        • 반면, 자바스크립트에서는 클로저(Closure) 기법을 통해 구성 가능
        const hello = function() {
        	console.log("Hello World");
        	return function() {
        		console.log("Hello World 22");
        	}
        }
        
        // js에서는 return을 함수로 가능하도록 함
        const hello2 = hello();
        hello2();
        
  • 자바 람다 함수의 일급 객체

    • 자바의 람다식, 익명 클래스는 변수나 매개변수로 사용할 수 있고 리턴값으로 사용할 수 있기 때문에 일급객체 조건을 만족

    • 변수나 데이터에 담을 수 있어야 한다

      public class Main {
      	public static void main(String[] args) {
      		// 아래처럼 작성 후 Consumer 인터페이스 타입을 받는 파라미터로 사용 가능
      		Consumer<String> c = (t) -> System.out.println(t);
      		c.accept("Hello World");
      	}
      }
      
    • 함수의 파라미터로 전달할 수 있어야 한다

      public class Main {
      	public static void print(Consumer<String> c, String str) {
      		c.accept(str);
      	}
      	public static void main(String[] args) {
      		// 아래의 경우 Consumer 함수형 인터페이스 파라미터를 받는 print 메서드 사용
      		// Consumer 함수형 인터페이스를 람다식으로 전달해서 사용
      		print((t) -> System.out.println(t), "Hello World");
      	}
      }
      
    • 함수의 리턴값으로 사용할 수 있어야 한다

      public class Main {
      	// return을 Consumer 함수형 인터페이스 타입으로 하여 호출한 부분에서 사용 가능
      	public static Consumer<String> hello() {
      		return (t) -> {
      			System.out.println(t);
      		}
      	}
      	public static void main(String[] args) {
      		Consumer<String> c = hello();
      		c.accept("Hello World");
      	}
      }
      
  • 내부/외부 반복

    • 외부 반복이란
      • 사용자가 직접 요소를 반복하는 것을 의미
      • 외부 반복에서 병렬성을 사용하려면 스스로(사용자) 관리해야함
    • 내부 반복이란
      • 반복을 알아서 처리하고 결과 스트림값을 어딘가에 저장해주는 것을 의미
      • 내부 반복을 사용하면 병렬로 처리하거나 최적화된 다양한 순서로 가능
    • 즉, 외부 반복을 사용하면 How중심의 코드를 작성할 수 있지만, 내부 반복을 사용하면 What 중심의 코드를 작성할 수 있다???????ㅋ
  • default method

    • 기본적으로 인터페이스에서는 기능에 대한 선언만 이뤄지기 때문에 메서드에 로직을 작성할 수는 없다
    • 하지만 Java8에서의 기능으로 가능해졌고 이를 default method라고 함
    • 접근 제어자 default와의 차이
      • 접근 제어자에서는 아무것도 명시하지 않아도 default라고 함
      • 반면, 인터페이스의 default method는 default라는 키워드를 명시해야 함
    • default method 사용 이유
      • 인터페이스는 기본적으로 기능의 구현보다는 선언에 초점을 맞춤

      • 예시로 기존의 인터페이스를 구현한 클래스를 사용하던 중 인터페이스 보완 과정에서 추가적으로 구현해야 할, 필수적인 부분이 존재, 이 인터페이스를 구현한 클래스와의 호환성이 떨어짐 → 이 경우에 default method를 이용해서 하위 호환성을 유지하고 인터페이스의 보완 가능

      • 예시 코드

        public interface Vehicle {
        	default void print(String str) {
        		System.out.println("I want to " + str);
        	}
        }
        
        public class Car implements Vehicle {}
        
        Car car = new Car();
        car.print("Car");
        // result -> I want to Car 출력
        
  • Optional

    • NPE(Null Pointer Exception)란

      • 개발할 때 가장 많이 발생하는 예외 중 하나로 null을 참조할 떄의 오류

      • null에 대한 검사를 해서 예방이 가능하나 코드가 복잡해짐

      • 이에 따라 null에 민감한 코드는 초깃값을 사용하는 것이 권장됨

      • 예시 코드

        List<String> names = getNames();
        names.sort(); // names가 null이라면 NPE 발생
        
        List<String> names = getNames();
        // NPE 예방을 위한 NULL 검사
        if(names != null) {
        	names.sort();
        }
        
    • Optional이란

      • Optional<T> 클래스를 이용하면 NPE 방지가능

      • Optional<T>은 NULL 가능성을 가진 값을 감싸는 Wrapper 클래스

      • Optional 클래스는 내부에서 static 변수로 EMPTY 객체를 미리 생성하고 가짐

      • 이러한 이유로 빈 객체를 여러번 생성하는 경우에도 1개의 EMPTY 객체를 공유하여 메모리를 절약

      • 예시 코드

        • Optional.empty() → 값이 NULL인 경우

          Optional<String> optional = Optional.empty();
          
          System.out.println(optional); // Optional.empty 출력
          System.out.println(optional.isPresent()); // false 출력
          
        • Optional.of() → 값이 NULL이 아닌 경우

          // 만약 어떤 데이터가 절대 null이 아닌 경우 Optional.of()로 생성 가능
          // Optional.of()로 null을 저장하려고 하면 NPE발생
          Optional<String> optional = Optional.of("MyName);
          
        • Optional.ofNullable() → 값이 Null가능성을 가지는 경우

          // Optional 값이 null 가능성을 가지는 경우 사용
          // orElse, orElseGet 메서드를 이용해서 null의 경우라도 안전하게 가져올 수 있음
          Optional<String> optional = Optional.ofNullable(getName());
          Stringn name = optional.orElse("anonymous");
          
      • Optional 실전 사용법

        • 기존에 null 검사를 한 후에 null인 경우에는 새로운 객체를 생성해야 했음

        • Optional<T> Lambda를 이용하면 간단하게 표현 가능

          // java 8 이전
          List<String> names = getName();
          List<String> tempNames = list != null
          	? list
          	: new ArrayList<>();
          
          // java 8 이후
          List<String> nameList = Optional.ofNullable(getName())
          	.orElseGet(() -> new ArrayList<>());
          
        • 객체의 객체를 사용하는 경우 null 검사 때문에 복잡해짐

          // java 8 이전
          public String findPostCode() {
          	UserVO userVO = getUser();
          	if(userVO != null) {
          		Address address = userVO.getAddress();
          		if(address != null) {
          			String postCode = address.getPostCode();
          			if(postCode != null) {
          				return postCode;
          			}
          		}
          	}
          	return "우편번호 없음";
          }
          
          // java 8 이후
          public String findPostCode() {
          	// Optional로 펼친 경우
          	Optional<UserVO> userVO = Optional.ofNullable(getUser());
          	Optional<Address> address = Optional.ofNullable(userVO.getAddress());
          	Optional<String> postCode = Optional.ofNullable(address.getPostCode());
          	String result = postCode.orElse("우편번호 없음");
          	// 축약형
          	String result = user.map(UserVO::getAddress)
          		.map(Address::getPostCode)
          		.orElse("우편번호 없음");
          }
          
        • 예외 처리의 불필요

          // java 8 이전
          String name = getName();
          String result = "";
          
          try {
          	result = name.toUpperCase();
          } catch (NullPointerException e) {
          		throw new CustomUpperCaseException();
          }
          
          // java 8 이후
          // null인 값을 Optional로 가져왔을 때 null인 경우 예외 발생
          Optional<String> nameOpt = Optional.ofNullable(getName());
          String result = nameOpt.orElseThrow(CustomUpperException::new)
          	.toUpperCase();
          
        • orElse, orElseGet의 차이

          • orElse: 파라미터로 값을 받음 → 값이 미리 존재하는 경우에만 사용

          • orElseGet: 파라미터로 함수형 인터페이스를 받음 → 값이 미리 존재하지 않는 거의 대부분의 경우에 사용

          • 예시 코드

            // Optional.ofNullable로 "EMPTY"를 갖는 Optional 객체 생성
            // getUserEmail()가 실행되어 반환겂을 orElse파라미터로 전달
            // orElse가 호출됨, "EMPTY"가 null이 아니므로 "EMPTY"를 그래도 가짐
            // 즉 orElse는 파라미터로 함수형 인터페이스르 받지 못해 함수가 작성되면 무조건 실행
            public void findUserEmailOrElse() {
            	String userEmail = "Empty";
            	String result = Optional.ofNullable(userEmail)
            		.orElse(getUserEmail());
            	System.out.println(reuslt);
            }
            // Optional.ofNullalbe로 "EMPTY"를 갖는 Optional 객체 생성
            // getUserEmail 함수 자체를 orElseGet 파라미터로 전달
            // orElseGet이 호출, "EMPTY"가 null이 아니므로 "EMPTY"를 그대로 가지며 getUserEmail()이 호출되지 않음
            public void findUserEmailOrElseGet() {
            	String userEmail = "Empty";
            	String result = Optional.ofNullable(userEmail)
            		.orElseGet(this::getUserEmail);
            	System.out.println(result);
            }
            private String getUserEmail() {
            	System.out.println("getUserEmail() called");
            	return "[email protected]";
            }
            
      • 정리

        • 기본적으로 Wrapping을 하고 다시 풀고를 반복하는 상황이므로 잘못 사용하면 성능을 저하시킬 수 있음
        • 따라서 반환값이 절대 null이 아니라면 Optional을 사용하지 않는 것이 바람직
  • Optional을 사용할 때의 위험성

    • 올바르게 사용하는 경우 Optional의 장점이 발휘됨
    • 하지만, 올바르게 사용하지 못하는 경우 부작용 발생
    • Optional의 부작용
    • 부작용 예시 코드
  • 올바른 Optional 사용법

  • DRY(Don’t Repeat Yourself)

  • 전략 디자인 패턴

  • 익명 클래스