자바12

  1. String 클래스에 새로운 메소드 추가

    1. indent()

      1. 각 라인마다 인덴트를 추가한다.
      2. 음수값을 넘길 경우 인덴트를 삭제한다.
      String text = "Hello Baeldung!\\nThis is Java 12 article.";
      
      text = text.indent(4);
      System.out.println(text);
      
      text = text.indent(-10);
      System.out.println(text);
      
      //    Hello Baeldung!
      //    This is Java 12 article.
      //
      //Hello Baeldung!
      //This is Java 12 article.
      
    2. transform()

      1. 문자열을 받는 용으로 1개 인자를 넘기는 함수이다.
      2. 반환값은 수신객체의 타입이여야 한다.
      3. 아래 코드는 글자를 반대로 바꾸는 함수이다.
      @Test
      public void givenString_thenRevertValue() {
          String text = "Baeldung";
          String transformed = text.transform(value ->
            new StringBuilder(value).reverse().toString()
          );
      
          assertEquals("gnudleaB", transformed);
      }
      
  2. File::mismatch 함수

    public static long mismatch(Path path, Path path2) throws IOException
    
    1. 두 파일을 비교해서 첫번째로 미스매치되는 위치를 찾는 함수이다.

    2. 반환값은 0L부터 시작하여 더 작은 크기를 갖는 파일의 byte 사이즈까지이며 동일한 파일의 경우 -1L을 반환한다.

    3. 아래 코드는 동일한 파일을 비교하는 예시이다. -1L을 반환한다.

      @Test
      public void givenIdenticalFiles_thenShouldNotFindMismatch() {
          Path filePath1 = Files.createTempFile("file1", ".txt");
          Path filePath2 = Files.createTempFile("file2", ".txt");
          Files.writeString(filePath1, "Java 12 Article");
          Files.writeString(filePath2, "Java 12 Article");
      
          long mismatch = Files.mismatch(filePath1, filePath2);
          assertEquals(-1, mismatch);
      }
      
    4. 아래 코드는 다른 파일을 비교하는 예시이다. 8L을 반환한다.

      @Test
      public void givenDifferentFiles_thenShouldFindMismatch() {
          Path filePath3 = Files.createTempFile("file3", ".txt");
          Path filePath4 = Files.createTempFile("file4", ".txt");
          Files.writeString(filePath3, "Java 12 Article");
          Files.writeString(filePath4, "Java 12 Tutorial");
      
          long mismatch = Files.mismatch(filePath3, filePath4);
          assertEquals(8, mismatch);
      }
      
  3. Teeing Collector

    1. 2개의 컬렉터와 1개의 함수를 인자로 받아 각 컬렉터의 기능을 실행하고 그 컬렉터의 결괏값을 기반으로 함수를 실항하는 복합 함수

    2. 아래 코드는 sum 컬렉터와 count 컬렉터의 반환값을 토대로 평균을 구하는 teeing 함수 예제다.

      @Test
      public void givenSetOfNumbers_thenCalculateAverage() {
          double mean = Stream.of(1, 2, 3, 4, 5)
            .collect(Collectors.teeing(Collectors.summingDouble(i -> i), 
              Collectors.counting(), (sum, count) -> sum / count));
          assertEquals(3.0, mean);
      }
      
  4. Compact Number Formatting

    public static NumberFormat getCompactNumberInstance(Locale locale, NumberFormat.Style formatStyle)
    
    1. 아래 예제는 2592를 미국식으로 short은 2.59K, long은 2.59 thousand로 표현하는 코드이다.

      @Test
      public void givenNumber_thenCompactValues() {
          NumberFormat likesShort = 
            NumberFormat.getCompactNumberInstance(new Locale("en", "US"), NumberFormat.Style.SHORT);
          likesShort.setMaximumFractionDigits(2);
          assertEquals("2.59K", likesShort.format(2592));
      
          NumberFormat likesLong = 
            NumberFormat.getCompactNumberInstance(new Locale("en", "US"), NumberFormat.Style.LONG);
          likesLong.setMaximumFractionDigits(2);
          assertEquals("2.59 thousand", likesLong.format(2592));
      }
      

자바14

  1. 확장 switch 식(expression)

    1. break; 키워드도 생략되는 문법
    2. yield 키워드를 통해 → 를 통한 단순 반환 말고 코드 블럭을 생성하여 복잡한 기능을 구현하고 값을 반환해줄 수도 있다.
    DayOfWeek dayOfWeek = LocalDate.now().getDayOfWeek();
    boolean freeDay = switch (dayOfWeek) {
        case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> false;
        case SATURDAY, SUNDAY -> true;
    };
    
    public class SwitchExpression {
        public static void main(String[] args) {
            int days = 0;
            Month month = Month.APRIL;
    
            days = switch (month) {
                case JANUARY, MARCH, MAY, JULY, AUGUST, OCTOBER, DECEMBER -> 31;
                case FEBRUARY -> 28;
                case APRIL, JUNE, SEPTEMBER, NOVEMBER -> 30;
                default -> throw new IllegalStateException();
            };
        }
    }
    
    //yield
    boolean freeDay = switch (dayOfWeek) {
        case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> {
          System.out.println("Work work work");
          yield false;
        }
        case SATURDAY, SUNDAY -> {
          System.out.println("Yey, a free day!");
          yield true;
        }
    		//코드 블록 외에서 사용하면 에러가 발생하니 주의
        default -> yield true;
    };
    
    public class SwitchExpression {
        public static void main(String[] args) {
            int days = 0;
            Month month = Month.APRIL;
    
            days = switch (month) {
                case JANUARY, MARCH, MAY, JULY, AUGUST, OCTOBER, DECEMBER -> {
                    System.out.println(month);
                    yield 31;
                }
                case FEBRUARY -> {
                    System.out.println(month);
                    yield 28;
                }
                case APRIL, JUNE, SEPTEMBER, NOVEMBER -> {
                    System.out.println(month);
                    yield 30;
                }
                default -> throw new IllegalStateException();
            };
        }
    }
    
  2. 더 나아진 NullPointerExceptions

    //기존엔 이 한줄은 어디서 에러가 났는지 디버거모드로 찍어보지 않으면 몰랐다.
    //이젠 “cannot invoke Person.getAddress()”. 라고 명확히 알려준다.
    company.getOwner().getAddress().getCity();
    
    int[] arr = null;
    arr[0] = 1;
    //Exception in thread "main" java.lang.NullPointerException
    //at com.baeldung.MyClass.main(MyClass.java:27)
    
    //이렇게 변했다.
    //java.lang.NullPointerException: Cannot store to int array because "a" is null
    

자바15

  1. Records

    1. 불변 데이터 객체를 생성하는 새로운 유형의 클래스

    2. 기존방식

      1. 모든 필드에 final을 사용한 명시적 정의
      2. 필드 값을 모두 포함한 생성자
      3. 게터와 세터
      4. 상속 방지를 위해 클래스 자체를 final로 선언하기도 함
      public class Person {
          private final String name;
          private final int age;
      
          public Person(String name, int age) {
              this.name = name;
              this.age = age;
          }
      
          public String getName() {
              return name;
          }
      
          public int getAge() {
              return age;
          }
      }
      
    3. 레코드 방식

      1. 컴파일러가 헤더를 통해 필드 추론
      2. 생성자, 게터, 세터, toString, equals, hashCode 자동 구현
      3. 암시적으로 final 클래스이기 때문에 상속불가, abstract 불가
      4. 클래스 상속(extends) 불가능, 인터페이스 구현(implements) 가능
      
      //시그니처만 구현해줘도 되고, 위 코드보다 짧고 간결하면서 생성자에 조건까지 추가할 수 있다.
      public record Person(String name, int age) {
          public Person {
              if(age < 0) {
                  throw new IllegalArgumentException("Age cannot be negative");
              }
          }
      }
      
  2. TextBlocks

    1. 텍스트블럭 선언 가능

      String myWallOfText = """
      ______         _   _           
      | ___ \\       | | (_)          
      | |_/ / __ ___| |_ _ _   _ ___ 
      |  __/ '__/ _ \\ __| | | | / __|
      | |  | | |  __/ |_| | |_| \\__ \\
      \\_|  |_|  \\___|\\__|_|\\__,_|___/
      """
      
      String myPoem = """
      Roses are red, violets are blue - \\
      Pretius makes the best software, that is always true
      """
      //아래와 동일한 결과
      String myPoem = "Roses are red, violets are blue - Pretius makes the best software, that still is true."
      
  3. Sealed Class

    1. 상속하거나 구현할 클래스를 지정하여 해당 클래스만 허용하는 키워드. 이를 통해 서브클래스의 범위를 명확히 인지할 수 있다.
    2. 허용된 클래스는 반드시 final, non-sealed, sealed 중 하나로 선언되어야 한다.
    3. non-sealed 키워드를 선언하면 허용되지 않은 클래스여도 똑같이 상속 구현할 수 있다. 이 키워드는 의도와 기능을 명확히 한 뒤에만 사용해야 한다.
    public abstract sealed class Person
        permits Employee, Manager {
     
        //...
    }
    
    public final class Employee extends Person {
    }
    
    public non-sealed class Manager extends Person {
    }
    

자바16

  1. Proxy 인스턴스에서인터페이스의 default 메소드를 리플렉션을 통해 실행할 수 있다.

  2. Day Period Support

    1. B, BBBB, BBBBB 를 통해 오전, 오후에 대한 자세한 설명을 출력할 수 있다.
    LocalTime date = LocalTime.parse("15:25:08.690791");
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("h B");
    assertThat(date.format(formatter)).isEqualTo("3 in the afternoon");
    
  3. Records (14 프리뷰, 15 정식이였던 기능을 개선한 버전)

    1. 기존방식 추가해야할 것

      1. toString 오버라이딩
      2. hashCode 오버라이딩
      3. equals 오버라이딩
      public class Person {
          private final String name;
          private final int age;
          
          public Person(String name, int age) {
              this.name = name;
              this.age = age;
          }
          
          public String getName() {
              return name;
          }
          
          public int getAge() {
              return age;
          }
      }
      
    2. 레코드방식

      1. toString, equals, hashCode 자동 구현
      public record Person(String name, int age) {
      	//나이 제한하기 등 동작을 재정의할 수 있음, 생성자이기 때문에 () 사용하지 말것
      	public Person{
              if(age < 0) {
                  throw new IllegalArgumentException("Age cannot be negative");
              }
          }
      }
      
      public class RecordDemo {
          public static void main(String[] args){
              Person person = new Person("Ted", 100);
              System.out.println("이름:"+ person.name() + " 나이:"+person.age());
              System.out.println("객체 정보:"+person.toString());
              Person person2 = new Person("Ted", 100);
              Person person3 = new Person("Dean", 200);
              
              if (person.equals(person2)) System.out.println("person, person2는 같은 사람");
              else System.out.println("person, person2는 다른 사람");
              
              if (person.equals(person3)) System.out.println("person, person3는 같은 사람");
              else System.out.println("person, person3는 다른 사람");
          }
      }
      
    3. 이너클래스에서도 사용할 수 잇다.

      class OuterClass {
          class InnerClass {
              Book book = new Book("Title", "author", "isbn");
          }
      }
      
  4. instanceof 패턴 매칭

    1. instanceof 구문에서 true일 경우 바로 변수 할당을 할 수 있다.
    2. if 문에 할당한 변수를 사용할 수도 있다.
    if (obj instanceof MyObject) {
      MyObject myObject = (MyObject) obj;
      // … further logic
    }
    
    if (obj instanceof MyObject myObject) {
      // … the same logic
    }
    
    if (obj instanceof MyObject myObject && myObject.isValid()) {
      // … the same logic
    }
    
    //또는
    public class PatternMatching {
        public static double price(Vehicle v) {
            if (v instanceof Car c) {
                return 10000 - c.kilomenters * 0.01 -
                        (Calendar.getInstance().get(Calendar.YEAR) -
                                c.year) * 100;
            } else if (v instanceof Bicycle b) {
                return 1000 + b.wheelSize * 10;
            } else throw new IllegalArgumentException();
        }
    }