- Interface : 인터페이스를 사이로 앞, 뒤의 개발 코드가 서로 통신하는 접점. 앞, 뒤의 통신 규약 → 동시 개발 가능
 
interface Practice {
	// 상수
	(final/static : 지우라고뜸) 타입 상수명(대문자 convention) = 값;
	String HI = "Hi~";
	// 추상 메서드
	List<String> findAllName();
	// Default 메소드
	default 타입 메소드명(파라미터,...) {...}
	default void printHi() {
		System.out.println(HI);
	}
	// static 메소드
	static void printHi() {
		System.out.println(HI);
	}
}
1) 상수만 가능 → 클래스 로드 시점에 초기화
public static final 가 컴파일 시점에 붙는다.2) 추상메서드로 구현체에게 구현 강제
3) 기본 메서드 제공
→ 자바는 단일 상속, 다중 구현을 제공 → 만약 두개의 인터페이스를 구현했는데, 두 인터페이스의 메서드 명이 똑같다면 어떻게 되나?? → 충돌나는 메서드 시그니처의 오버라이딩을 강제화!
→ public 생략시 컴파일 과정에서 자동으로 붙여줌
4) Static메소드 → 헬퍼 또는 유틸리티 메소드를 제공할 때 사용
public interface Practice {
    String HI = "HI~"; // 상수 정의 가능 컴파일 시 앞에 static finald이 붙는다고 생각하기!
    default void printHi(){
    //java 8 이상부터 기본 메소드 사용가능
        System.out.println("default "+ HI);
    }
    static void printHI(){
    //static 메소드 사용가능
        System.out.println("static "+ HI);
    }
}
public class Example implements Practice{
    public void print(){
        System.out.println(HI);
    }
    public void defaultMethod(){
    //원래 인터페이스는 함수 껍대기만 있기 때문에 구현해서 완성 시키는 것 이였는데, 이렇게 기본메서드를
    //제공한다.
        printHi();
    }
    public void staticMethod(){
        Practice.printHI();
    }
    public static void main(String[] args) {
        Example ex = new Example();
        ex.print();
        ex.defaultMethod();
        ex.staticMethod();
           // HI~
           // default HI~
           // static HI~
    }
}
1) 인터페이스 -> 다중 구현 / 추상클레스 -> 단일 상속
2) 인터페이스 -> 어떤 공통된 속성을 가지고 구현한다 / 추상클레스 -> 속성을 상속받아 확장시키는 것
3) 인터페이스의 접근제어자는 public만 가능 -> 공개 목적이기 때문!!
- Java에서는 같은 타입끼리의 연산을 제공한다.
 - Ex) Int + Int = true ;
 
Ex) int + Double = false ; -> 이럴 때 형변환 필요하다!!!
- 이런식으로 작은 데이터 타입 -> 큰 데이터 타입으로의 변환은 자동으로 형변환이 일어난다.
 - long 타입의 메모리는 8byte인데 float로 자동 형변환이 가능한 이유는 float가 표현범위가 더 크기 때문이다.
 - byte 타입 -> char 타입 : 자동 형변환 불가
 - float 타입 -> long 타입 : 자동 향변환 불가
 
- 인터페이스로 보는 자동 형변환
 
-> 인터페이스 변수 = 구현객체; ← 자동 타입 변환
-> Interface clazz = new InterfaceImplementClass();
-> List< > arr = new ArrayList< >( ); 이것 또한 List인터페이스로 자동 형변환인가?
👉 큰 데이터 타입 = 작은 데이터 타입 -> 자동 형변환이 이루어짐
상위 인터페이스, 클래스, 추상 클래스로 Upcasting 가능
→ 모든 클래스는extends Object가 생략되어있다.
→ 모든 클래스는 Object로 Upcasting 가능
    int i = 10;
    double j = 5.5;
    double result = i + j; // 15.5 
@FuntionalInterface애노테이션을 인터페이스에 선언하면 컴파일 시점에서 추상메소드를 하나만 갖는지 체크해 준다.
@FunctionalInterface
public interface Sum {
    int intSum(int x, int y);
}
- 메소드를 하나의 식으로 표현한 것( 쉽게 이야기 하자면 )
 
List<String> list = new ArrayList();
list.add("Element1");
list.add("Element2");
list.add("Element3");
list.forEach(x -> System.out.println(x))
// 위 코드는 list.forEach(System.out::println) 으로 축약할 수 있음
// 원래라면 for문 돌려야 함!..
- 스트림은 데이터를 변경하지 않는다.
 - 스트림 연산이 끝난 후 재 사용할 수 없다.
 
- 0 ~ N 개의 중간 연산과 1개의 종료 연산으로 구성.
 - 내가 사용했을 때의 경험은 중간 filter,map...등등이 여러개 붙은 후 forEach()로 마무리 했던 경험
 
- Stream을 리턴
 
- Stream을 리턴하지 않는다.
 
- 필터링 :
 filter,distinct- 변환 :
 map,faltMap- 제한 :
 limit,skip- 정렬 :
 sort
- 요소 출력 :
 forEach- 요소 검색 :
 findFirst,findAny- 요소 통계 :
 count,min,max- 요소 연산 :
 sum,average- 요소 수집 :
 collect
- NPE( Null Point Exception ) 예외를 Optional이 제공하는 메소드로 간단히 회파할 수 있다.
 - Optional 개념 쉬운 예제로 설명한 블로그 : https://ynzu-dev.tistory.com/entry/JAVA-Optional-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%98%88%EC%A0%9C
 
- NPE는 물론, NoSuchElementException이 발생함.
 - 잘못된 Optional사용으로 새로운 문제들이 발생함
 - 코드의 가독성을 파괴
 - 시간, 공간적 비용이 증가함
 
- Null을 반환하면 오류가 발생할 가능성이 매우 높은 경우에 "결과없음"을 명확하게 드러내기 위해 메소드의 반환 타입으로 사용되도록 매우 제한적인 경우로 설계 됨.
 
🚨 Optional은 메소드의 반환형으로만 사용가능!!!!! 🚨
Optional<String> opt = Optional.ofNullable("Optional은 Wrapper Class");
System.out.println(opt.get());
- empty( )
 
import java.util.Optional;
public class Example {
   Optional<String> empty = Optional.empty();
   
    public static void main(String[] args) {
        Example ex = new Example();
        System.out.println(ex.empty.isPresent()); // false
    }
}
- of( )
 
🚨 of( ) 는 null이 아님을 확신할 때 사용.
import java.util.Optional;
public class Example {
    Optional<String> empty = Optional.of("assert NotNull");
    
    public static void main(String[] args) {
        Example ex = new Example();
        System.out.println(ex.empty.isPresent());
    }
}
- ofNullalbe( )
 
import java.util.Optional;
public class Example {
    Optional<String> empty = Optional.ofNullable(null);
    
    public static void main(String[] args) {
        Example ex = new Example();
        System.out.println(ex.empty.isPresent());
    }
}
- ifPresent( )
 
👉 Optional에서 꺼낸 객체가 존재한다면, 구문수행
import java.util.Optional;
public class Example {
    String name = null;
    Optional<String> opt = Optional.ofNullable(name);
    
    public static void main(String[] args) {
        Example ex = new Example();
        ex.opt.ifPresent(s -> System.out.println(s)); // null이기 때문에 아무 값도 없음 
    }
}
- OrElse( )
 
👉 Optional에서 꺼낸 객체가 존재한다면 꺼내고 그렇지 않으면,orElse의 인자값을 반환
import java.util.Optional;
public class Example {
    String name = null;
    Optional<String> opt = Optional.ofNullable(name);
    public static void main(String[] args) {
        Example ex = new Example();
        System.out.println(ex.opt.orElse("값이 없어요!!"));//값이 없어요!! 출력
    }
}
- OrElseGet( )
 
👉 OrElse( ) 와 비슷하지만, 인자값으로 람다 표현식의 결과값을 출력
import java.util.Optional;
public class Example {
    String name = null;
    Optional<String> opt = Optional.ofNullable(name);
    public static void main(String[] args) {
        Example ex = new Example();
        System.out.println(ex.opt.orElseGet(()->"값이 없는데요?"));// 값이 없는데요? 출력
    }
}
- orElseThrow( )
 
👉 Optional에서 꺼낸 객체가 존재한다면 꺼내고, 그렇지 않다면? Exception 던지기
import java.util.Optional;
public class Example {
    String name = null;
    Optional<String> opt = Optional.ofNullable(name);
    public static void main(String[] args) {
        Example ex = new Example();
        System.out.println(ex.opt.orElseThrow(IllegalAccessError::new));//IllegalAccessError터짐
    }
}
mport java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Example {
    List<Person> person = Arrays.asList(
            new Person("park",20),
            new Person("kim",30),
            new Person("Lee", 40)
    );
    List<String> personName = person.stream().map(Person::getName).collect(Collectors.toList());
    public static void main(String[] args) {
        Example ex = new Example();
        ex.personName.stream().forEach(System.out::println);
    }
}
👉 이름과 나이를 가진 Person클레스를 객체로 가진 List 타입의 person에서 Name필드만 뽑아와서 따로 List로 저장해주는 부분
👉 원래 같았으면 for 반복문을 person.length만큼 돌려주면서 get(i).getName()해온 것을 String 타입을 가진 personName List에 add 해줘야 하는데 코드도 길어지고, 가독성이 떨어질 수 있다.
👉 이러한 Stream을 사용함으로써 간단한 코드 한줄로 위의 반복작업을 간단하게 해결할 수 있다.
- 정수형 문자 배열에서 중간 수를 가져오기
 - 만약 문자열의 길이가 짝수일 경우 두개를 가져오게 된다.
 
 import java.util.Arrays;
 
 class Solution {
     //가운데 글자 가져오기
     // 가운데 글자를 가져오기
     // 단어 길이가 홀수 = 가운데 한글자만 5 -> 3 7 -> 4 9 -> 5
     // 단어 길이가 짝수 = 가운데 두글자만
 
     public String solution(String s) {
 
         char[] arr = s.toCharArray();
         String answer;
 
         if(arr.length%2==0){
           char a = arr[arr.length/2-1];
           char b = arr[(arr.length/2)];
            answer = String.valueOf(a)+String.valueOf(b);
         }else{
            char a = arr[(arr.length/2)];
            answer = String.valueOf(a);
         }
         return answer;
 
     }
 
     public static void main(String[] args) {
         Solution sol = new Solution();
         System.out.println(sol.solution("qwer"));
    }
}
- 정수형 배열이 주어진다.
 - [1,1,3,3,0,1] -> [1,3,0,1] 이 출력되어야 하며 순서가 보장되어야 함
 - Set자료구조는 못쓰겠구나~
 
import java.util.*;
public class Solution {
    //0~9까지의 숫자로 이루어진 int 배열 arr이 주어진다.
    // 오케이 확인
    public List<Integer> solution(int []arr) {
        List<Integer> answer = new ArrayList<>();
       for(int i=0;i<arr.length-1;i++){
           if(i==0){
               answer.add(arr[i]);
           }
           if(arr[i]!=arr[i+1]){
               answer.add(arr[i+1]);
           }
       }
        return answer;
    }
    public static void main(String[] args) {
        Solution sol = new Solution();
        sol.solution(new int[]{1,1,3,3,0,1,1}).stream().forEach(s-> System.out.println(s));
    }
}
- 중복되지 않는 자연수를 가진 정수형 배열과, 나눌 자연수가 주어진다.
 - 나누는 수로 나눠떨어지는 숫자들을 오름차순으로 출력하기
 - 만약 나눠 떨어지는 숫자가 없다면 [-1] 반환하기
 
import java.util.Arrays;
class Solution {
    //arr 의 각 요소중에서 divisor로 나눠떨어지는 값을 오른차순으로 정렬한 배열을 반환해라
    public int[] solution(int[] arr, int divisor) {
        int count = 0;
        for(int i=0;i<arr.length;i++){
            if(arr[i]%divisor==0){
                count++;
            }
        }
        if(count==0){
            return new int[]{-1};
        }
        int [] answer = new int[count];
        for(int j=0;j<arr.length;j++){
            if(arr[j]%divisor==0){
                answer[count-1] = arr[j];
                count--;
            }
        }
        Arrays.sort(answer);
      return answer;
    }
    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(Arrays.toString(sol.solution(new int[]{2,36,1,3},1)));
    }
}