함수적 인터페이스(Functional Interface) 자바 8이상부터 @FuntionalInterface 어노테이션을 통해 함수적 인터페이스를 정의할 수 있습니다. 추상메서드가 1개라면, 함수적 인터페이스이지만 어노테이션을 붙이는 이유는 컴파일러가 컴파일 시에 검사할 수 있도록 하기 위함입니다.
람다식은 이러한 함수적 인터페이스를 implements 합니다.
implements하는 함수적 인터페이스를 람다식의 타겟 타입(Target Type)이라고 합니다.
람다식이 구현하는 익명함수는 익명구현객체와 비슷하지만 약간의 차이가 있습니다.
익명함수는 1개의 추상 메서드만 재정의하지만,
익명구현객체는 추상 메서드의 갯수에 대한 제한이 없습니다.
여기서 "익명"이란 이름이 없는 것이 아니라 이름을 모르는 것을 의미합니다.
그렇다면, 익명구현객체를 찍어낸 인터페이스는 추상메서드의 개수에 제한이 없습니다.
다음은 간단한 람다식 구현 코드입니다.
@FunctionalInterface
interface ICalculator {
public abstract int add(int number1, int number2) throws Exception; // 덧셈
} // end Interface
@Log4j2
public class App {
// ICalculator 인터페이스를 구현(Implements)
public static void main(String[] args) throws Exception {
ICalculator calc = (int number1, int number2) -> {
return number1 + number2;
};
} // main
} // end class
위의 실행 클래스는 생략 전 add() 메서드입니다.
람다식은 여기서 파라미터의 타입을 제거할 수 있습니다.
@Log4j2
public class App {
// ICalculator 인터페이스를 구현(Implements)
public static void main(String[] args) throws Exception {
// 생략 전
//ICalculator calc = (int number1, int number2) -> {
// return number1 + number2;
//};
ICalculator calc = (number1, number2) -> {
return number1 + number2;
};
} // main
} // end class
만약 파라미터가 1개라면 소괄호를 생략할 수 있습니다.
기존에 생성했던 함수적 인터페이스에서 추상 메서드의 파라미터 또한 수정해야 합니다.
@Log4j2
public class App {
// ICalculator 인터페이스를 구현(Implements)
public static void main(String[] args) throws Exception {
// 생략 전
// ICalculator calc = (int number1, int number2) -> {
// return number1 + number2;
// };
// 파라미터 타입 생략
// ICalculator calc = (number1, number2) -> {
// return number1 + number2;
// };
// 파라미터가 1개일 때, 소괄호 생략 가능
ICalculator calc = number1 -> {
return number1 + 100;
};
} // main
} // end class
여기서 중괄호 블록안에 return문만 있을 경우, 중괄호 블록과 return 키워드까지 생략해야합니다.
@Log4j2
public class App {
// ICalculator 인터페이스를 구현(Implements)
public static void main(String[] args) throws Exception {
// 생략 전
// ICalculator calc = (int number1, int number2) -> {
// return number1 + number2;
// };
// 파라미터 타입 생략
// ICalculator calc = (number1, number2) -> {
// return number1 + number2;
// };
// 파라미터가 1개일 때, 소괄호 생략
// ICalculator calc = number1 -> {
// return number1 + 100;
// };
// return키워드, 중괄호 블록 생략
ICalculator calc = number1 -> number1 + 100;
} // main
} // end class
이 컬렉션에 나오는 자료구조인 이진 트리의 내부 구조를 알고 있어야 API를 사용할 수 있습니다.
간단히 개념만 설명하면, 루트 노드를 정하고, 대소 비교를 통해 크면 오른쪽, 작으면 왼쪽으로 비교하며 위치하게 됩니다.
SQL에서 SELECT문을 통한 JOIN이나 복잡한 쿼리로 데이터를 조회할 때, 인덱스를 이용합니다.
이 때, 이진 트리의 구조와 개념을 제대로 알고 사용하면 많은 도움이 될 수 있습니다.
1. 트리 셋( TreeSet )
TreeSet은 이진 트리(binary tree)를 기반으로 한 Set 컬렉션입니다.
좌, 우 자식 노드를 참조하기 위한 두 개의 변수로 구성됩니다.
다음은 주요 메서드입니다.
특정 객체를 찾는 메서드 : fist(), last(), lower(), higher() 등
정렬 메서드 : descendingiterator(), descendingSet()
범위 검색 메서드 : headSet(), tailSet(), subSet()
숫자에 대한 TreeSet은 간단하므로, 다루지 않고 String으로 넘어가겠습니다.
다음은 간단한 예시 코드입니다.
@Log4j2
public class TreeSetExample {
public static void main(String[] args) {
TreeSet<String> treeSet = new TreeSet<>();
treeSet.add("apple");
treeSet.add("forever");
treeSet.add("description");
treeSet.add("ever");
treeSet.add("zoo");
treeSet.add("base");
treeSet.add("guess");
treeSet.add("cherry");
log.info("**** [c~f 사이의 단어 검색] ****");
// 부분범위(Sub-range Search) 검색 수행
NavigableSet<String> rangeSet = treeSet.subSet("cherry", true, "forever", true);
rangeSet.forEach(log::info);
} // main
} // end class
Map 컬렉션은 키(key)와 값(value)으로 구성된 Map 엔트리 객체를 저장하는 구조입니다.
구현 클래스는 'HashMap', 'HashTable', 'Properties' 등이 있습니다.
Map 컬렉션의 특징 및 주요 메서드입니다
V put(K key, V value) : 주어진 키와 값을 추가, 저장이 되면 값을 리턴
boolean containKey(Object key) : 주어진 키가 있는지 여부
boolean contains Value(Object value) : 주어진 값이 있는지 여부
Set<Map.Entry><K,V>> entrySet() : 키와 값의 쌍으로 구성된 모든 Map.Entry 객체를 Set에 담아 리턴
V get(Object key) : 주어진 키의 값을 리턴
boolean isEmpty(): 컬렉션이 비어있는지 여부
Set<K> keySet() : 모든 키를 Set 객체에 담아서 리턴
int size() : 저장된 키의 총 수를 리턴
Collection<V> values() : 저장된 모든 값 Collection에 담아서 리턴
void clear() : 모든 Map.Entry를 삭제
V remove(Object key) : 주어진 키와 일치하는 Map.Entry 삭제, 삭제가 되면 값을 리턴
1. 해시 맵( HashMap)
일반적으로 HashMap 보다는 HashTable을 많이 사용합니다.
map은 set과 다르게 키가 같다면, 기존 요소를 교체합니다.
먼저 Student 클래스입니다.
@AllArgsConstructor
@EqualsAndHashCode
public class Student {
public Integer sno; // 학번
public String name; // 이름
} // end class
Lombok의 어노테이션인 @EqualsAndHashCode를 이용해 메서드를 오버라이드하지 않고, 코드를 구현하였습니다.
다음은 실행 클래스입니다.
@Log4j2
public class HashMapExample {
public static void main(String[] args) {
// Map 컬렉션 생성
@Cleanup("clear")
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("신용권", 85);
map.put("홍길동", 90);
map.put("동장군", 80);
map.put("홍길동", 95);
log.info("총 엔트리 수 : {}", map.size());
log.info(map);
// 요소 객체 찾기
log.info("홍길동 : {}", map.get("홍길동"));
// Map 컬렉션 순회(Traverse)
Set<String> keySet = map.keySet();
Iterator<String> keyIterator = keySet.iterator();
while(keyIterator.hasNext()) {
String key = keyIterator.next();
Integer value = map.get(key);
log.info("\t + key : {}, value : {}", key, value);
} // while
} // main
} // end class
2. 해시 테이블( HashTable)
키 객체를 만드는 법은 HashMap과 동일합니다.
HashTable은 스레드 동기화(synchronization)가 된 상태입니다.
따라서, 멀티 스레드가 동시에 HashTable에 접근해서 객체를 추가, 삭제를 하더라도 thread-safe합니다.
3. 프로퍼티스( Properties, 속성 )
Properties는 키와 값을 String 타입으로 제한한 Map 컬렉션입니다.
Properties는 프로퍼티 파일(~.properties) 파일을 읽어 들일 때 주로 사용합니다.
이러한 프로퍼티 파일을 Resource Buncle(리소스 번들)이라고 합니다.
리소스 번들은 유지 보수를 편리하게 만들어 주고, ISQ-8859-1 문자셋으로 저장됩니다.
리소스 번들은 다국어를 고려하지 않고 만들었기 때문에, 한글은 유니코드(Unicode)로 변환되어 저장합니다.
간단한 리소스 번들입니다.
#database.properties
driver = oracle.jdbc.OracleDriver
url = jdbc:oracle:this:@localhost:1521:orcl
username = scott
password = tiger
# Set Your Name
yourname = \uD64D\uAE38\uB3D9
위의 리소스 번들을 기반으로 실행 코드를 만들었습니다.
@Log4j2
public class PropertiesExample {
public static void main(String[] args) throws Exception {
// Step.1 리소스 번들을 사용할 Map 객체릀 생성
Properties properties = new Properties();
// Step.2 실행 클래스 위치를 기준으로, 리소스 번들의 경로를
// 지정해서 완전한 절대경로를 얻음
String path =
PropertiesExample.
class.
getResource("/database.properties").
getPath();
log.info("1. path : {}", path);
// ---------------------------------------------
// Step.3 URL Encoding에 대한 URL Encoding 수행
// 위에서 얻은 리소스 번들에는 다국어 문자가
// 포함되어 있을 수도 있으므로, 이러한 경우
// 다국어 문자가 포함된 경로를 읽어 들이는데 문제가 없도록
// URL Decoding을 수행해야 함
path = URLDecoder.decode(path, "utf-8");
log.info("2. path : {}", path);
// ---------------------------------------------
// Step.4 Step.3에서 얻어낸 완전한 경로를 Properties의
// load 메서드에 지정해서 읽어들이게 함
properties.load(new FileReader(path));
log.info("2. properties : {}", properties);
// --------------------------------------------
// Step.5 리소스 번들이 이제 Map객체의 요소가 되었기 때문에
// 각 항목의 값을 얻어내고 싶을 때, getProperty(key) 메서드 사용
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
} // main
} // end class
위 코드에서 URL Encoding은 웹 브라우저가 인식 가능한 부호화된 문자열입니다.
이러한 URL Encoding을 사람이 읽을 수 있도록 역변환하는 것이 URL Decoding이며
코드에서는 URLDecoder.decode()로 진행합니다.
properties.load()는 path를 기반으로 리소스 번들을 읽어들입니다.
먼저 path1, path2, properties2 실행결과를 보면 다음과 같습니다.
리소스 번들 주소를 설정할 때, 소스 폴더는 패키지와 다르게 물리적인 폴더로서 인식되지 않습니다.