- 2014년 3월에 정식으로 릴리즈 되었다
- 인터페이스 디폴트 메서드
- 람다식 Lamda 을 통한 함수형 프로그래밍
- 스트림 Stream API 추가

함수형 프로그래밍에는 명령형과 선언형 프로그래밍으로 나눠진다
- 명령형 프로그래밍 Imperative
명령형 프로그래밍은 특정 기능을 수행하기 위해 어떻게 집중하는 방식을 의미- 선언형 프로그래밍 Declarative
선언형 프로그래밍은 특정 기능을 수행하기 위해 무엇에 집중하는 방식을 의미함수형 프로그래밍은 선언형 프로그래밍을 따르는 대표적인 프로그래밍 패러다임이다

first-class citizen(object) 일급객체
- 익명의 literal로 생성가능하다 = 동적으로 생성이 가능하다
- 객체가 변수나 자료구조에 저장이 가능
- 일급객체를 다른 함수의 인자로 전달이 가능하다
- 함수의 리턴값으로 객체를 사용할 수 있다
- 자바스크립트 함수는 일급객체
- callback function → 고차함수
지역, 성별을 검색해서 유저를 조회할수 있는 프로그래밍
public class Customer {
private String id;
private String location;
private Gender gender;
private int age;
public Customer(String id, String location, Gender gender, int age) {
this.id = id;
this.location = location;
this.gender = gender;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public Gender getGender() {
return gender;
}
public void setGender(Gender gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Customer{" +
"id='" + id + '\'' +
", location='" + location + '\'' +
", gender=" + gender +
", age=" + age +
'}';
}
}
import java.util.ArrayList;
import java.util.List;
public class CustomerService {
private List<Customer> customers;
public CustomerService() {
this.customers = new ArrayList<>();
}
// 손님 추가
public void addCustomer(Customer newCustomer){
this.customers.add(newCustomer);
}
// 지역으로 검색
public List<Customer> searchCustomerByLocation(String location){
List<Customer> foundCustomers = new ArrayList<>();
//손님들을 꺼내서 같은지 확인 후 같으면 추가
for (Customer customer : customers){
if (customer.getLocation().equals(location)){
foundCustomers.add(customer);
}
}
return foundCustomers;
}
//성별로 검색
public List<Customer> searchCustomerByGender(Gender gender){
List<Customer> foundCustomers = new ArrayList<>();
for (Customer customer : customers){
if (customer.getGender().equals(gender)){
foundCustomers.add(customer);
}
}
return foundCustomers;
}
}
import java.util.List;
public class FunctionalAssist {
public static void main(String[] args) {
CustomerService service = new CustomerService();
initData(service);
//지역으로 검색
List<Customer> result = service.searchCustomerByLocation("Seoul");
for (Customer customer: result){
System.out.println(customer);
}
//성별로 검색
}
private static void initData(CustomerService service){
service.addCustomer(new Customer("CUS01", "Seoul", Gender.Male, 30));
service.addCustomer(new Customer("CUS02", "Busan", Gender.Male, 30));
service.addCustomer(new Customer("CUS03", "Busan", Gender.Male, 30));
service.addCustomer(new Customer("CUS04", "Seoul", Gender.Male, 30));
service.addCustomer(new Customer("CUS05", "Seoul", Gender.Male, 30));
service.addCustomer(new Customer("CUS06", "Gwangju", Gender.Male, 30));
}
}
검색조건에 따라 서비스에서 메소드를 계속 만드는것은 비효율 적이다. 모든 조건을 충족 시킬 수 있는 효율적인 코드는 무엇일까?
EnumClass를 사용하여 검색조건과 검색 값을 받아 처리한다
location seoul
//하나의 메소드로 처리하는 방법 검색조건 , 검색값
public List<Customer> searchCustomersBy(SearchOption searchOption, String searchValue){
List<Customer> foundCustomers = new ArrayList<>();
for (Customer customer : customers){
//지역검색
if (searchOption.equals(SearchOption.Location)) {
if (customer.getLocation().equals(searchValue)) {
foundCustomers.add(customer);
}
//성별 검색
}else if (searchOption.equals(SearchOption.Gender)) {
if (customer.getGender().name().equals(searchValue)) {
foundCustomers.add(customer);
}
}
}
return foundCustomers;
}
public interface SearchFilter {
boolean isMatched(Customer customer);
}
package step2;
import java.util.ArrayList;
import java.util.List;
public class CustomerService {
private List<Customer> customers;
public CustomerService() {
this.customers = new ArrayList<>();
}
// 손님 추가
public void addCustomer(Customer newCustomer){
this.customers.add(newCustomer);
}
//인터페이스를 통한 검색 메소드
public List<Customer> searchCustomers(SearchFilter filter){
List<Customer> foundCustomers = new ArrayList<>();
for (Customer customer: customers){
if (filter.isMatched(customer)){
foundCustomers.add(customer);
}
}
return foundCustomers;
}
}
package step2;
import java.util.ArrayList;
import java.util.List;
public class FunctionalAssist {
public static void main(String[] args) {
CustomerService service = new CustomerService();
initData(service);
List<Customer> result = new ArrayList<>();
result = service.searchCustomers(new SearchFilter() {
@Override
public boolean isMatched(Customer customer) {
if (customer.getLocation().equals("Seoul")){
return true;
}else{
return false;
}
}
});
for (Customer customer : result){
System.out.println(customer);
}
}
private static void initData(CustomerService service){
service.addCustomer(new Customer("CUS01", "Seoul", Gender.Male, 30));
service.addCustomer(new Customer("CUS02", "Busan", Gender.Male, 30));
service.addCustomer(new Customer("CUS03", "Busan", Gender.Male, 30));
service.addCustomer(new Customer("CUS04", "Seoul", Gender.Male, 30));
service.addCustomer(new Customer("CUS05", "Seoul", Gender.Male, 30));
service.addCustomer(new Customer("CUS06", "Gwangju", Gender.Male, 30));
}
}
- Static Class
static이 붙었을 경우 중첩 클래스라고 한다- Non-Static Class (InnerClasses)
static이 붙지 않았을 경우 멤버 클래스, 로컬 클래스, 익명 클래스 3가지로 나뉘어진다

내부 클래스는 클래스 내에 선언된 클래스이다. 클래스에 다른 클래스를 선언하는 이유는 두 클래스가 서로 긴밀한 관계가 있기 때문
이다. 한 클래스를 다른 클래스의 내부 클래스로 선언하면 두 클래스의 멤버들 간에 서로 쉽게 접근할 수 있다는 장점이 있다.
- 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근 할 수 있다.
- 코드의 복잡성을 줄일 수 있다. (캡슐화)
익명 중첩 클래스
- 클래스의 선언과 객체의 생성이 동시에 이루어진다
- 한번만 사용되고 하나의 객체만 생성하는 일회용 클래스
- java8에서는 람다Lamda식으로 대체 가능하다


- 람다 표현식은 익명 구현 클래스를 생성하고 객체화 한다
- 익명 구현 클래스로 생성된 람다 표현식은 인터페이스로 대입 가능하며 이 인터페이스를 함수형 인터페이스라고 한다
- 하나의 추상 메소드를 갖는 인터페이스는 모두 함수형 인터페이스가 될 수 있다
- 다수의 디폴트 메소드를 갖는 인터페이스라도 추상 메소드가 하나라면 함수형 인터페이스이다
- 함수형 인터페이스를 정의할 때 @FunctionalInterface 어노테이션을 이용해 컴파일 검사를 진행할 수 있다.
- 함수형 인터페이스의 추상메소드 시그니처를 함수 디스크립터 function descriptor라고 한다




- Stream 객체를 생성하는 방법은 Collection 객체를 통한 방법과 스트림 빌더를 통한 방식 두가지가 있다.
- Collection 인터페이스는 stream()메소드를 default 메소드로 정의하고 있다
- 이 메소드는 해당 컬렉션이 가지고 있는 항목들에 대해 스트림 처리가 가능한 Stream 객체를 반환한다
- 한번 생성한 스트림은 사용 후 다시 사용할 수 없으며 전체 데이터에 대한 처리가 이루어지면 종료된다
Collection 객체를 통한 생성
- 처리할 데이터가 존재하고 이를 처리하기 위한 일반적인 생성방식
Stream.Builder를 이용한 Stream 객체 생성
- 직접 데이터를 추가 삭제 해야할때
- 스트림 자체적으로 데이터를 생성하고 처리할 수 있다.
- Stream.Builder 인터페이스는 Consumer 인터페이스를 상속하고 있으며 데이터를 추가하는 accept(), add() 메소드와 데이터의 추가 작업을 완료하고 Stream을 반환하는 build()메소드를 정으하고 있다
- 스트림을 이용한 연산은 각 연산의 연결을 통해 파이프라인을 구성할 수 있다.
- 파이프라인을 구성할 수 있다는 것은 스트림 대상 데이터에대한 다양한 연산을 조합할 수 있다.
- 스트림을 이용한 연산 처리는 스트림 객체의 생성부터 중간 연산, 그리고 최종 연산 단계로 구분 할 수 있다.
- 스트림 객체가 제공하는 다양한 연산을 이해하고 연산에 필요한 람다 표현식을 이해하고 적용하는게 중요하다
중간 연산 intermediate operation
- filter, map과 같은 연산으로 Stream을 반환
- 중간 연산은 연속해서 호출하는 메소드 체이닝 Method Chanining으로 구현 가능하다
- 최종연산이 실행되어야 중간 연산이 처리되므로 중간 연산들로만 구성된 메소드 체인은 실행되지 않는다.
최종 연산 terminal operation
- forEach, collect와 같은 연산으로 void를 반환하거나 컬렉션 타입을 반환한다
- 중간 연산을 통해 가공된 스트림은 마지막으로 최종연산을 통해 각 요소를 소모하여 결과를 출력한다
- 지연Lazy 되었던 모든 중간 연산들이 최종연산시 모두 수행되는 것이다
- 최종 연산 후에는 한번 생성해서 소모한 스트림은 닫히게 되고 재사용이 불가능하다

- (중간 연산)filter, sort는 Stream객체로 반환됨
이때의 타입은 List의 <> 타입- lsit<>의 다른 타입으로 반환하고 싶을때 사용하는게 map
참고자료
https://butter-shower.tistory.com/86
https://blog.itthis.kr/56
https://youtu.be/7cVPlgyx3d8