# Java 기본 확장 자바는 버전이 올라갈수록 새로운 기능이 추가되었다. 이런 확장된 기능들을 알아보자. ## 자바의 Try-with-Resources에 대하여 자바 프로그래밍 언어에서 예외 처리는 코드를 안정적으로 만드는 중요한 부분이다. `try-catch` 블록을 사용하여 예외를 처리하는 것은 매우 일반적인 방법이다. 하지만, 자원을 사용하는 경우(파일 입출력, 네트워크 소켓 연결 등) 자원을 올바르게 해제하지 않으면 리소스 누수가 발생할 수 있다. 자바 7 이전까지는 이러한 문제를 해결하기 위해 `finally` 블록에서 자원 해제 코드를 명시적으로 작성해야 했다. 자바 7부터 도입된 `Try-with-Resources`는 이러한 문제를 보다 우아하게 해결해 준다. ### Try-with-Resources란? `Try-with-Resources`는 `try` 문을 사용하여 하나 이상의 리소스를 선언하는 데 사용되는 문법이다. 이 문법을 사용하면, 리소스를 사용한 후 자동으로 해제(자원을 정리)한다는 장점이 있다. 여기서 말하는 리소스란, 자동으로 닫힐 수 있는 어떤 객체를 말하며, 이는 `java.lang.AutoCloseable` 인터페이스 또는 그 자손인 `java.io.Closeable` 인터페이스를 구현해야 한다. ### Try-with-Resources 기본 사용법 기본적인 `Try-with-Resources` 문의 사용법은 다음과 같다. ```java try (선언할 리소스1; 선언할 리소스2; ...) { // 리소스와 함께 실행할 코드 } catch (예외타입 변수명) { // 예외 처리 코드 } ``` 이 구조에서 `try` 블록 안에 리소스를 선언한다. 이 리소스들은 **`try` 블록이 종료될 때 자동으로 `close()` 메소드가 호출**되어 자원이 해제된다. 이를 통해 개발자는 리소스 해제를 위한 코드를 직접 작성할 필요가 없게 되며, 코드의 가독성과 안정성이 높아진다. ### 예제: 파일 읽기 (Try-Catch-Finally) ```java import java.io.*; public class Main { public static void main(String[] args) { FileInputStream fi = null; try { fi = new FileInputStream("test.txt"); byte[] bytes = fi.readAllBytes(); System.out.println(new String(bytes)); } catch (Exception e) { e.printStackTrace(); } finally { try { fi.close(); } catch (IOException e) { e.printStackTrace(); } } } } ``` ### 예제: 파일 읽기 (Try-with-Resources) ```java import java.io.*; public class Main { public static void main(String[] args) { try (FileInputStream fi = new FileInputStream("test.txt")) { byte[] bytes = fi.readAllBytes(); System.out.println(new String(bytes)); } catch (Exception e) { e.printStackTrace(); } } } ``` - `FileInputStream`은 `AutoCloseable` 인터페이스를 구현하고 있으므로, `Try-with-Resources`를 사용할 수 있다. ### 장점 - **자원 해제를 위한 코드를 직접 작성할 필요가 없다**: 이는 코드의 양을 줄여주고, 가독성을 향상시킨다. - **리소스 누수 방지**: 모든 리소스가 올바르게 닫히도록 보장한다. - **코드의 안정성 향상**: 예외가 발생하더라도 리소스가 안정적으로 닫히므로 안정성이 향상된다. ### 단점 - **Try 범위 제한**: `try` 블록 안에서 선언된 리소스는 `try` 블록이 종료될 때 닫히므로, `try` 블록 밖에서 리소스를 사용할 수 없다. - **Java 7 이상에서만 사용 가능**: Java 7 이상에서만 사용할 수 있다. - **리소스를 선언하는 부분이 복잡해질 수 있다**: 여러 리소스를 사용하는 경우, `try` 문 안에서 모두 선언해야 하므로 코드가 복잡해질 수 있다. - **사용 불가능한 리소스**: `AutoCloseable` 인터페이스를 구현하지 않은 리소스는 `Try-with-Resources`를 사용할 수 없다. > 단, 위의 단점들을 모두 합쳐도 `Try-with-Resources`의 장점이 더 크다고 할 수 있기 때문에 사용을 권장한다. ### 주의사항 - `Try-with-Resources`를 사용할 때 리소스는 `try` 문 안에서 선언되어야 한다. 따라서, `try` 블록 바깥에서 선언된 리소스는 자동으로 닫히지 않는다. - 여러 리소스를 사용하는 경우, 리소스는 선언된 반대 순서로 닫힌다. 즉, 마지막에 선언된 리소스부터 첫 번째 리소스까지 역순으로 닫힌다. ## 익명클래스 - 이름이 없는 클래스 - 한번만 사용되며, 클래스를 선언하고 동시에 인스턴스화 할 때 사용된다. - 주 GUI 구성 요소를 정의하거나, 간단한 인터페이스 구현에 사용되며, 코드를 더욱 간단하게 만들수 있도록 한다 ### 익명클래스를 사용하는 이유 - 간결성: 익명 클래스는 주로 한 곳에서만 사용되며, 따라서 별도의 클래스 파일을 작성하는 것에 비해 코드를 간결하게 만들 수 있다. - 지역적인 사용: 익명 클래스는 메소드 내부에서 정의하고 사용할 수 있어, 그 사용 범위를 한정할 수 있다. 이는 코드의 가독성과 유지 보수성을 향상시킨다. - 코드의 캡슐화: 익명 클래스는 그것을 사용하는 코드에 가깝게 위치할 수 있어, 특정 동작을 캡슐화하는데 유용하다. 예를 들어, 버튼 클릭 리스너와 같은 GUI 이벤트 처리 코드에서 종종 볼 수 있다. ### 익명클래스 사용 시 주의사항 - 재사용성 부족: 익명 클래스는 이름이 없기 때문에 재사용할 수 없다. 따라서, 같은 코드를 반복해서 사용해야 하는 경우에는 적합하지 않다. - 복잡성: 비교적 단순한 동작을 가진 클래스를 정의하는 경우에는 익명 클래스가 유용할 수 있지만, 너무 많은 동작을 가진 클래스를 익명 클래스로 만드는 것은 코드를 이해하고 유지 보수하는데 어려움을 초래할 수 있다. - 구문 제한: 익명 클래스에서는 일부 구문 (예: 생성자, static 멤버 등)을 사용할 수 없다. 또한, 익명 클래스는 자신을 둘러싼 블록의 final 또는 effectively final 변수에만 접근할 수 있다. - 가독성: 익명 클래스의 사용은 코드의 가독성을 떨어뜨릴 수 있다. 특히, 여러 개의 중첩된 익명 클래스는 코드를 읽고 이해하는데 어려움을 줄 수 있다. ### 익명클래스 구현 방법 - 익명클래스는 클래스 선언과 객체의 생성을 동시에 수행한다. - 주로 `new` 키워드 뒤에 인터페이스나 클래스 이름을 명시하고, 그 뒤에 중괄호 `{}`를 추가하여 클래스 본문을 작성한다. - 중괄호 안에는 해당 인터페이스나 클래스를 오버라이딩하는 메서드를 포함시켜 익명클래스를 완성시킨다 ### 익명클래스 예시 #### 예시 1 ##### 익명 클래스 사용 전 ```java interface HelloWorld { public void greet(); } class EnglishGreeting implements HelloWorld { public void greet() { System.out.println("Hello world!"); } } class Main { public static void main(String[] args) { HelloWorld englishGreeting = new EnglishGreeting(); englishGreeting.greet(); } } ``` ##### 익명 클래스 사용 후 ```java interface HelloWorld { public void greet(); } class Main { public static void main(String[] args) { HelloWorld englishGreeting = new HelloWorld() { public void greet() { System.out.println("Hello world!"); } }; englishGreeting.greet(); } } ``` #### 예시 2 ##### Runnable 인터페이스의 익명 클래스 사용 예 ```java Runnable runnable = new Runnable() { @Override public void run() { System.out.println("익명 클래스의 run 메소드"); } }; Thread thread = new Thread(runnable); thread.start(); ``` > 위의 코드에서, `new Runnable() { ... }` 부분이 Runnable 인터페이스를 구현하는 익명 클래스이다. Runnable 인터페이스의 run 메소드를 오버라이드하여 구현하고 있다. ##### Thread의 익명 클래스 사용 예 ```java Thread thread = new Thread() { @Override public void run() { System.out.println("익명 클래스의 run 메소드"); } }; thread.start(); ``` > Thread 클래스를 상속받는 익명 클래스. 이 클래스도 마찬가지로 run 메소드를 오버라이드하여 구현하고 있다. ## 가변인자 가변인자는 JDK 1.5부터 추가된 기능이다. 가변인자를 사용하면 메서드의 매개변수를 동적으로 지정할 수 있다. - 가변인자는 메서드의 매개변수를 동적으로 지정할 수 있게 해준다. - 가변인자는 메서드의 매개변수에 `타입... 변수명` 형식으로 선언한다. ### 가변인자 사용 전 ```java public void print(String[] args) { for (String arg : args) { System.out.println(arg); } } ``` ### 가변인자 사용 후 ```java public void print(String... args) { for (String arg : args) { System.out.println(arg); } } ``` ### 가변인자 사용 시 유의사항 #### 가변인자를 사용한 메서드를 호출할 때 인자를 전달하지 않아도 된다 ```java import java.util.Arrays; public class Main { public static void main(String[] args) { print(); } public static void print(String... args) { System.out.println(Arrays.toString(args)); } } ``` #### 가변인자를 사용한 메서드를 호출할 때 인자를 배열로 전달할 수도 있다 ```java import java.util.Arrays; public class Main { public static void main(String[] args) { print(new String[] {"Hello", "World"}); } public static void print(String... args) { System.out.println(Arrays.toString(args)); } } ``` #### 모호한 호출을 방지하기 위해 가변인자는 다음과 같은 규칙을 따라야 한다 - 가변인자는 하나만 사용할 수 있다. - 가변인자는 매개변수 중 가장 마지막에 위치해야 한다. ```java public void print(String... args, int num) { // 컴파일 에러 //... } ``` ```java public void print(String... args, int... nums) { // 컴파일 에러 //... } ``` ## 자바의 Enhanced For문 자바는 다양한 반복문을 제공하며, 그 중 하나인 Enhanced For문은 코드의 가독성을 높이고 개발자가 컬렉션 및 배열을 보다 쉽게 순회할 수 있도록 도와준다. ### Enhanced For문의 기본 구조 Enhanced For문, 또는 "for-each"문은 자바 5부터 도입된 반복문이다. 배열이나 컬렉션의 모든 요소를 순회하는 데 사용된다. 기본 구조는 다음과 같다. ```java for (타입 변수명 : 배열 또는 컬렉션) { // 실행할 코드 } ``` 여기서 타입은 배열 또는 컬렉션에 저장된 요소의 타입을 의미하며, 변수명은 순회하는 동안 각 요소에 접근하기 위해 사용되는 변수의 이름이다. 배열 또는 컬렉션은 순회할 대상이다. ### Enhanced For문의 작동 원리 Enhanced For문은 내부적으로 주어진 배열이나 컬렉션의 `iterator`를 사용하여 순회를 수행한다. 개발자가 명시적으로 `iterator`를 사용하는 코드를 작성할 필요가 없어 코드가 간결해지며, 순회 과정에서 발생할 수 있는 오류를 최소화한다. 반복마다 배열 또는 컬렉션의 현재 요소가 지정된 변수에 할당된다. 이 변수를 통해 반복문 내에서 현재 요소에 접근하고, 필요한 연산을 수행할 수 있다. ### Enhanced For문 사용 예제 #### 배열 사용 예제 ```java int[] numbers = {1, 2, 3, 4, 5}; for (int number : numbers) { System.out.println(number); } ``` 위 예제는 정수 배열 `numbers`의 모든 요소를 순회하며 출력한다. #### 컬렉션 사용 예제 ```java ArrayList<String> fruits = new ArrayList<>(); fruits.add("Apple"); fruits.add("Banana"); fruits.add("Cherry"); for (String fruit : fruits) { System.out.println(fruit); } ``` 이 예제는 `String` 타입의 `ArrayList`인 `fruits`에 저장된 모든 요소를 순회하며 출력한다. ### Enhanced For문의 장점 - **가독성 향상**: 코드가 간결해져 이해하기 쉽다. - **오류 감소**: 순회 과정에서의 실수를 줄일 수 있다. - **컬렉션 및 배열 사용 용이**: 배열과 컬렉션 모두에 사용 가능하다. ### Enhanced For문 주의사항 - **수정 제한**: Enhanced For문을 사용하여 순회하는 동안 컬렉션의 요소를 추가, 삭제하는 것은 허용되지 않는다. - **성능 이슈**: 매우 큰 데이터셋을 순회할 때는 Enhanced For문보다는 iterator를 직접 사용하는 것이 성능상 이점이 있을 수 있다. ## 타입 추론 자바는 전통적으로 강타입 언어이다. 이는 모든 변수, 매개변수, 반환 값 등이 명시적인 타입을 가져야 한다는 것을 의미한다. 그러나, 자바 10 버전부터 `var` 키워드를 도입하여 로컬 변수에 대한 타입 추론(type inference) 기능을 제공하기 시작했다. 이 기능은 개발자로 하여금 코드의 가독성을 향상시키고, 더 빠르게 코드를 작성할 수 있게 한다. > - 강타입 언어 : 변수의 타입이 컴파일 시점에 결정되며, 한 번 정해지면 변경할 수 없는 언어를 의미. (자바, C, C++ 등) > - 약타입 언어 : 변수의 타입이 런타임 시점에 결정되며, 동적으로 타입을 변경할 수 있는 언어를 의미. (파이썬, 자바스크립트 등) ### `var` 키워드란? `var` 키워드는 자바 10 이상에서 로컬 변수를 선언할 때 사용하는 타입 추론 기능이다. 이를 사용하면 컴파일러가 변수의 초기값을 기반으로 해당 변수의 타입을 자동으로 결정한다. ### `var` 기본 사용법 `var`를 사용한 변수 선언은 다음과 같은 형태를 취한다. ```java var 변수명 = 초기값; ``` 예를 들어, 다음과 같이 사용할 수 있다. ```java var message = "Hello, Java!"; var list = new ArrayList<String>(); var map = new HashMap<String, Integer>(); ``` 여기서 중요한 점은 `var`로 선언된 변수는 반드시 초기값을 가지고 있어야 한다는 것이다. 초기값이 없는 경우에는 `var`를 사용할 수 없다. ### `var`와 타입 추론의 원리 자바 컴파일러는 `var`로 선언된 변수의 초기값을 분석하여 그 타입을 결정한다. 이 과정에서 컴파일러는 변수의 초기값을 바탕으로 정확한 타입 정보를 유추한다. ```java var number = 10; // int로 추론 var message = "Hello, World!"; // String으로 추론 ``` ```java var list = new ArrayList<String>(); // ArrayList<String>으로 추론 var map = new HashMap<String, Integer>(); // HashMap<String, Integer>으로 추론 ``` ```java var stream = list.stream(); // Stream<String>으로 추론 ``` ### `var` 사용 시 장점 - 코드의 간결성 : `var` 키워드를 사용하면, 특히 타입 이름이 길거나 복잡할 때 코드를 더 간결하게 만들 수 있다. - 가독성 향상 : 타입 추론을 통해 코드의 주요 목적에 더 집중할 수 있게 하며, 이는 전체적인 코드의 가독성을 향상시킨다. ### `var` 사용 시 주의사항 - 로컬 변수에만 사용 가능 : `var`는 메서드의 매개변수, 반환 타입, 필드(클래스의 멤버 변수)에는 사용할 수 없다. 오직 로컬 변수에만 사용할 수 있다. - 명확한 타입이 필요한 경우 : `var`를 사용하면 타입이 명시적이지 않기 때문에, 코드를 읽는 사람이 해당 변수의 타입을 직접 추론해야 할 수도 있다. 따라서, 타입이 명확히 드러나는 것이 중요한 경우에는 `var`의 사용을 피해야 할 수 있다. - 초기값이 없는 경우 사용 불가 : `var`로 선언된 변수는 반드시 초기값을 가져야 한다. 초기값 없이 `var`를 사용하여 변수를 선언하려고 하면 컴파일 오류가 발생한다. ## LocalDate, LocalTime, LocalDateTime 자바에서 현재 시간을 알아오는 방법은 여러 가지가 있다. 전통적으로 자바에서 날짜와 시간을 다루는 방법은 다음과 같다. - `java.util.Date` 클래스 - `java.util.Calendar` 클래스 - `System.currentTimeMillis()` 메소드 이는 자바 8 이전에 사용되던 방법이며, 이러한 방법은 날짜와 시간을 다루는 데 불편하고 복잡하다는 단점이 있다. 자바 8에서는 이러한 문제를 해결하기 위해 새로운 API를 도입했다. 그 중에서도 `LocalDate`, `LocalTime`, `LocalDateTime` 클래스를 사용하면 날짜와 시간을 쉽게 다룰 수 있다. 이 클래스들은 공통적으로 불변(immutable)하며, 스레드에 안전하기에 자바 8 이후에는 이 클래스들을 사용하는 것이 권장된다. ### LocalDate `LocalDate`는 날짜 정보를 다루는 클래스이다. 이 클래스는 년, 월, 일을 나타내는데 사용된다. `LocalDate` 객체를 사용하면 날짜를 생성하고 조작하는 다양한 메소드를 사용할 수 있다. #### LocalDate 생성하기 `LocalDate` 객체는 다양한 방법으로 생성할 수 있다. 가장 일반적인 방법은 현재 날짜를 기준으로 객체를 생성하는 것이다. ```java LocalDate today = LocalDate.now(); ``` 특정 날짜로 `LocalDate` 객체를 생성하려면 `of` 메소드를 사용한다. ```java LocalDate specificDate = LocalDate.of(2077, 12, 25); ``` #### 날짜 정보 얻기 `LocalDate` 객체에서 년, 월, 일과 같은 정보를 얻으려면 다음과 같은 메소드를 사용한다. ```java LocalDate today = LocalDate.now(); int year = today.getYear(); int month = today.getMonthValue(); int day = today.getDayOfMonth(); ``` #### 날짜 조작하기 LocalDate에는 날짜를 조작하는 다양한 메소드가 있다. 다음은 `plusDays`, `plusMonths` 메소드를 사용하여 날짜를 조작하는 예제이다. ```java LocalDate today = LocalDate.now(); LocalDate tomorrow = today.plusDays(1); LocalDate nextMonth = today.plusMonths(1); ``` - 이전 날짜를 구하려면 `minusDays`, `minusMonths` 메소드를 사용한다. > `LocalDate` 객체는 `String`과 마찬가지로 불변이기 때문에, 날짜를 조작하는 메소드는 기존 객체를 변경하는 대신 새 객체를 반환하는 메소드를 통해 날짜를 조작한다. #### 날짜 비교하기 두 `LocalDate` 객체를 비교하기 위해 `isBefore`, `isAfter`, `isEqual` 메소드를 사용한다. ```java LocalDate today = LocalDate.now(); LocalDate specificDate = LocalDate.of(2022, 12, 25); boolean isBefore = today.isBefore(specificDate); boolean isAfter = today.isAfter(specificDate); ``` ### LocalTime `LocalTime`은 시간 정보를 다루는 클래스이다. 이 클래스는 시, 분, 초, 밀리초 등의 정보를 나타내는 데 사용된다. `LocalTime` 객체를 사용하면 시간을 생성하고 조작하는 다양한 메소드를 사용할 수 있다. #### LocalTime 생성하기 `LocalTime` 객체도 `LocalDate`와 유사한 방법으로 생성할 수 있다. 현재 시간을 기준으로 객체를 생성하는 것이 일반적이다. ```java LocalTime now = LocalTime.now(); ``` 특정 시간을 지정하여 `LocalTime` 객체를 생성할 때는 `of` 메소드를 사용한다. ```java LocalTime specificTime = LocalTime.of(14, 30, 0); // 시, 분, 초 ``` #### 시간 정보 얻기 `LocalTime` 객체에서 시, 분, 초 등의 정보를 얻으려면 다음과 같은 메소드를 사용한다. ```java LocalTime now = LocalTime.now(); int hour = now.getHour(); int minute = now.getMinute(); int second = now.getSecond(); ``` #### 시간 조작하기 `LocalTime` 객체의 시간을 조작하려면, 마찬가지로 새 객체를 반환하는 메소드를 사용한다. ```java LocalTime now = LocalTime.now(); LocalTime inOneHour = now.plusHours(1); LocalTime in30Minutes = now.plusMinutes(30); ``` #### 시간 비교하기 `LocalTime` 객체 간의 비교도 `LocalDate`와 유사하게 `isBefore`, `isAfter`, `isEqual` 메소드를 통해 수행한다. ```java LocalTime now = LocalTime.now(); LocalTime specificTime = LocalTime.of(14, 30, 0); boolean isEarlier = now.isBefore(specificTime); boolean isLater = now.isAfter(specificTime); ``` ### LocalDateTime 때로는 날짜와 시간을 함께 다뤄야 할 때가 있다. 이 경우 `LocalDateTime` 클래스를 사용할 수 있다. `LocalDateTime`은 `LocalDate`와 `LocalTime`의 기능을 모두 포함한다. #### LocalDateTime 생성하기 `LocalDateTime` 객체는 다음과 같은 방법으로 생성할 수 있다. ```java LocalDateTime dateTime = LocalDateTime.now(); ``` 특정 날짜와 시간을 지정하여 `LocalDateTime` 객체를 생성하려면 `of` 메소드를 사용한다. ```java LocalDateTime specificDateTime = LocalDateTime.of(2022, 12, 25, 14, 30, 0); // 년, 월, 일, 시, 분, 초 ``` #### 날짜와 시간 조작하기 `LocalDateTime` 객체에서 날짜와 시간을 조작하는 방법도 `LocalDate`와 `LocalTime`과 유사하다. ```java LocalDateTime dateTime = LocalDateTime.now(); LocalDateTime tomorrow = dateTime.plusDays(1); LocalDateTime nextMonth = dateTime.plusMonths(1); LocalDateTime inOneHour = dateTime.plusHours(1); LocalDateTime in30Minutes = dateTime.plusMinutes(30); ``` #### 날짜와 시간 비교하기 `LocalDateTime` 객체 간의 비교도 `LocalDate`와 `LocalTime`과 동일한 방법으로 수행한다. ```java LocalDateTime now = LocalDateTime.now(); LocalDateTime specificDateTime = LocalDateTime.of(2024, 4, 1, 14, 30, 0); boolean isEarlier = now.isBefore(specificDateTime); boolean isLater = now.isAfter(specificDateTime); ```