제네릭<>
제네릭 타입(class<T>, interface<T>)
public class 클래스명<T> {...} public interface 인터페이스명<T> {...}
제네릭의 장점 - 컴파일시 강한 타입 체크로 실헹 시 에러를 사전에 방지. 타입변환(casting)을 제거
public class Box { private Object object; public void set(Object object) { this.object = object; } public Object get() { return object; } }
public class BoxExample { public static void main(String[] args) { Box box = new Box(); box.set("홍길동"); String name = (String) box.get(); System.out.println(name); box.set(555); int number = (int) box.get(); System.out.println(number); } }
타입 변환을 사용해야함
public class Box<T> { private T t; public void set(T t) { this.t = t; } public T get() { return t; } }
public class BoxExample { public static void main(String[] args) { // TODO Auto-generated method stub Box<String> box1 = new Box<String>(); box1.set("홍길동"); String name = box1.get(); System.out.println(name); Box<Integer> box2 = new Box<Integer>(); box2.set(555); int number = box2.get(); System.out.println(number); } }
타입 변환 없이 사용
멀티 타입 파라미터(class<K, V, ....>, interface<K, V, ....>)
public class Product<T, M> { private T kind; private M model; public T getKind() { return kind; } public void setKind(T kind) { this.kind = kind; } public M getModel() { return model; } public void setModel(M model) { this.model = model; } }
public class ProductExample { public static void main(String[] args) { Product<Tv, String> product1 = new Product<Tv, String>(); //자바 7부터 Product<Tv, String> product2 = new Product<>(); product1.setKind(new Tv()); product1.setModel("스마트Tv"); Tv tv = product1.getKind(); String tvModel = product1.getModel(); } }
자바 7부터는 축약 사용 가능
제네릭 메소드(<T,R> R method(T t)) - 매개 변수 타입과 리턴 타입으로 타입 파라미터를 갖는 메소드, 리턴 타입 앞에 <> 기호를 추가하고 타입 파라미터를 기술한 다음, 리턴 타입과 매개 타입으로 타입 파라미터를 사용
public <타입파라미터, ...> 리턴타입 메소드명(매개변수, ...){ ...} public <T> Box<T> boxing(T t) {...}
제네릭 메소드 호출 방법
리턴타입 변수 = <구체적타입> 메소드명(매개값); //명시적으로 구체적 타입을 지정 리턴타입 변수 = 메소드명(매개값); //매개값을 보고 구체적 타입을 추정 Box<Integer> box = <Integer>boxing(100);//타입 파라미터를 명시적으로 Integer로 지정 Box<Integer> box = boxing(100); //타입 파라미터를 Integer로 추정
public class Util2 { public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) { boolean keyCompare = p1.getKey().equals(p2.getKey()); boolean valueCompare = p1.getValue().equals(p2.getValue()); return keyCompare && valueCompare; } }
public class Pair<K, V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public void setKey(K key) { this.key = key; } public void setValue(V value) { this.value = value; } public K getKey() { return key; } public V getValue() { return value; } }
public class CompareMethodExample { public static void main(String[] args) { Pair<Integer, String> p1 = new Pair<Integer, String>(1, "사과"); Pair<Integer, String> p2 = new Pair<Integer, String>(1, "사과"); //구체적인 타입을 명시적으로 지 boolean result1 = Util2.<Integer, String>compare(p1, p2); if(result1) { System.out.println("논리적으로 동등한 객체입니다."); }else { System.out.println("논리적으로 동등하지 않는 객체입니다."); } Pair<String, String> p3 = new Pair<>("user1", "홍길동"); Pair<String, String> p4 = new Pair<>("user2", "홍길동"); //구체적인 타입을 추정 boolean result2 = Util2.compare(p3, p4); if(result2) { System.out.println("논리적으로 동등한 객체입니다."); }else { System.out.println("논리적으로 동등하지 않는 객체입니다."); } } }
제한된 타입 파라미터(<T extends 최상위타입>) - 구체적인 타입을 제한할 때 ex) 숫자 연산 메소드는 매개값으로 Number 또는 하위 클래스 타입(Byte, Short, Integer, Long, Double)의 인스턴스만 가져야 할때 extends 키워드를 붙이고 상위 타입을 명시하고 인터페이스도 가능한데 implements를 안쓰고 extends를 사용
public <T extends 상위타입> 리턴타입 메소드(매개변수, ...) { ...}
메소드의 중괄호 {} 안에서 타입 파라미터 변수로 사용한 것은 상위 타입의 멤버(필드, 메소드)로 제한
public class Util3 { public static <T extends Number> int compare(T t1, T t2) { double v1 = t1.doubleValue(); //Number의 doubleValue() 메소드 사 double v2 = t2.doubleValue(); return Double.compare(v1, v2); } }
public class BoundedTypeParameterExample { public static void main(String[] args) { //String str = Util3.compare("a", "b");Number 타입이 아님 //int -> Integer int result1 = Util3.compare(10, 20); System.out.println("result1 : " + result1); //double -> Double int result2 = Util3.compare(4.5, 3); System.out.println("result2 : " + result2); } }
와일드카드 타입(<?>, <? extends ...>, <? super ...>)
- 제네릭타입<?> : 타입 파라미터를 대치하는 구체적인 타입으로 모든 클래스나 인터페이스 타입이 올 수 있음(제한 없음)
- 제네릭타입<? extends 상위타입> : 타입 파라미터를 대치하는 구체적인 타입으로 상위타입이나 하위 타입만 올 수 있음(상위 클래스 제한)
- 제네릭타입<? super 하위타입> : 타입 파라미터를 대치하는 구체적인 타입으로 하위 타입이나 상위 타입이 옴(하위 클래스 제한)
public class Course<T> { private String name; private T[] students; public Course(String name, int capacity) { this.name = name; students = (T[]) (new Object[capacity]); System.out.println(Arrays.toString(students)+"끄억"+capacity); } public String getName() { return name; } public T[] getStudents() { return students; } //배열에 비어있는 부분을 찾아서 수강생을 추가 public void add(T t) { for(int i=0; i<students.length; i++) { students[i] = t; break; } } }
public class WildCardExample { public static void main(String[] args) { Course<Person> personCourse = new Course<Person>("일반인과정", 5); personCourse.add(new Person("일반인")); personCourse.add(new Worker("직장인")); personCourse.add(new Student("학생")); personCourse.add(new HighStudent("고등학생")); Course<Worker> workerCourse = new Course<Worker>("직장인과정", 5); workerCourse.add(new Worker("직장인")); Course<Student> studentCourse = new Course<Student>("학생과정", 5); studentCourse.add(new Student("학생")); studentCourse.add(new HighStudent("고등학생")); Course<HighStudent> highStudentCourse = new Course<HighStudent>("고등학생과정", 5); highStudentCourse.add(new HighStudent("고등학생")); //모든 과정 등록 가 registerCourse(personCourse); registerCourse(workerCourse); registerCourse(studentCourse); registerCourse(highStudentCourse); //registerCourseStudent(personCourse); 학생이 아니므로 //registerCourseStudent(workerCourse); registerCourseStudent(studentCourse); registerCourseStudent(highStudentCourse); registerCourseWorker(personCourse); registerCourseWorker(workerCourse); //registerCourseWorker(studentCourse; 학생 이므로 //registerCourseWorker(highStudentCourse); } //모든 과정 public static void registerCourse(Course<?> course) { System.out.println(course.getName() + "수강생 : " + Arrays.toString(course.getStudents())); } //학생 과정 public static void registerCourseStudent(Course<? extends Student> course) { System.out.println(course.getName() + "수강생 : " + Arrays.toString(course.getStudents())); } //직장인과 일반인 과정 public static void registerCourseWorker(Course<? super Worker> course) { System.out.println(course.getName() + "수강생 : " + Arrays.toString(course.getStudents())); } }
제네릭 타입의 상속과 인터페이스 구현도 가능하다
람다식
람다식 기본 문법 - (타입 매개변수, ...) -> { 실행문; ...}
(int a) -> {System.out.println(a);} //매개 변수 타입은 런타임 시에 대입되는 값에 따라 자동으로 인식되기에 타입을 제거 (a) -> {System.out.println(a);} //하나의 매개 변수만 있으면 괄호()를 생략, 하나의 실행문만 있다면 중괄호{}도 생략 a -> System.out.println(a) //매개 변수가 없다면 람다식에서 매개 변수 자리가 없어지므로 ()를 반드시 사용 () -> {실행문; ...} //중괄호{}를 실행하고 결과값을 리턴해야 할때는 (x,y) -> {return x+y;}; //중괄호{}에 return문만 있을 경우, return문 생략 (x,y) -> x+y
함수적 인터페이스(@FunctionalInterface) - 두 개 이상의 추상 메소드가 선언된 인퍼테이스가 아닌 하나의 추상 메소드가 선언된 인터페이스, @FunctionalInterface어노테이션을 붙이면 두 개 이상의 추상 메소드가 선언되면 오류가뜸
클래스 멤버와 로컬 변수 사용 - 클래스의 멤버(필드와 메소드)는 제약 사항없이 사용 가능하지만, 로컬 변수는 제약 사항이 따름
클래스의 멤버 사용 - 익명 객체 내부에서 this는 익명 객체의 참조지만, 람다식에서 this는 람다식을 실행한 객체의 참조
로컬 변수 사용 - 바깥 클래스의 필드나 메소드는 제한 없이 사용 가능하지만, 메소드의 매개 변수 또는 로컬 변수를 사용하면 이 두 변수는 final 특성을 가져야 함, 따라서 매개 변수 또는 로컬 변수를 람다식에서 읽는 것은 허용 되지만, 람다식 내부 또는 외부에서 변경할 수 없음
표준 API의 함수적 인터페이스 - 함수적 인터페이스는 java.util.function표준 API 패키지로 제공함
Consumer 함수적 인터페이스 - 리턴값이 없는 accept() 메소드를 가짐Supplier 함수적 인터페이스 - 매개 변수가 없고 리턴값이 있는 getXXX() 메소드를 가짐 Function 함수적 인터페이스 - 매개값과 리턴값이 있는 applyXXX() 메소드를 가짐 Operator 함수적 인터페이스 - Function과 동일하게 매개 변수와 리턴값이 있는 applyXXX()메소드를 가지지만, 이 메소드들은 매개값을 이용해서 연산을 수행 후 동일한 타입으로 리턴값을 제공 Predicate 함수적 인터페이스 - 매개 변수와 boolean 리턴값이 있는 testXXX() 메소드를 가짐
andThen()과 compose() 디폴트 메소드 - Consumer, Function, Operator 종류의 함수적 인터페이스는 andThen()과 compose() 디폴트 메소드가 있음, 첫 번째 처리 결과를 두 번째 매개값으로 제공해서 최종 결과값을 얻을 때 사용
andThen() - 인터페이스A.andThen(인터페이스B) : 인터페이스A부터 처리하고 결과를 인터페이스B의 매개값으로 제공해서 인터페이B는 최종 결과를 리턴
and(), or(), negate() 디폴트 메소드 - Predicate 종류의 함수적 인터페이스는 and(), or(), negate() 디폴트 메소드를 가짐(&&, ||, !와 같음)
Predicate<Object> predicate = Predicate.isEqual(targetObject); boolean result = predicate.test(sourceObject);minBy(), maxBy() 정적 메소드 - BinaryOperator<T> 함수적 인터페이스는 매개값으로 제공되는 Comparator를 이용해서 최대 T와 최소 T를 얻는 BinaryOperator<T>를 리턴
(left, right) -> Math.max(left, right); //메소드 참조(Math :: max) IntBinaryOperator operator = Math :: max;정적 메소드 참조 - 정적 메소드 참조시 클래스 이름 뒤에 :: 기호를 붙이고 정적 메소드 이름을 기술
생성자 참조 - 객체를 생성하고 리턴하도록 구성된 람다식은 생성자 참조로 대치, 클래스 이름 뒤에 :: 기호를 붙이고 new 연산자를 기술
클래스 :: new
컬렉션 프레임워크
List 컬렉션 - 객체를 인덱스로 관리(저장 순서를 유지)하고 null도 저장 가능
list.add("홍길동"); //맨끝에 객체 추가 list.add(1, "신용권"); //지정된 인덱스에 객체 삽입 String str = list.get(1); //인덱스로 객체 찾기 list.remove(0); //인덱스로 객체 삭제 list.remove("신용권"); //객체 삭제
Set<String> set = ...; Iterator<String> iterator = set.iterator(); while(iterator.hasNext()){ String str = iterator.next(); //삭제 if(str.equals("홍길동")){ iterator.remove(); } }
map.put("홍길동", 30); //객체 추가 int score = map.get("홍길동"); //객체 찾기 map.remove("홍길동"); //객체 삭제
//keySet() 메소드로 모든 키를 Set 컬렉션으로 얻은 다음, 반복자를 통해 키를 하나씩 얻고 get() 메소드를 통한 방법 Set<K> keySet = map.keySet(); Iterator<K> keyIterator = keySet.iterator(); while(keyIterator.hasNext()){ K key = keyIterator.next(); V value = map.get(key); } //entrySet() 메소드로 모든 Map.Entry를 Set 컬렉션으로 얻은 다음, 반복자를 통해 Map.Entry를 하나씩 얻고 getKey()와 getValue() 메소드를 이용해 키와 값을 얻음 Set<Map.Entry<K, V>> entrySet = map.entrySet(); Iterator<Map.Entry<K, V>> entryIterator = entrySet.iterator(); while(entryIterator.hasNext()) { Map.Entry<K, V> entry = entryIterator.next(); K key = entry.getKey(); V value = entry.getValue(); }HashMap - 키로 사용할 객체는 hashCode()와 equals() 메소드를 재정의해서 동등 객체가 될 조건을 정해야함(hashCode()의 리턴값이 같고, equals() 메소드가 true를 리턴), 키와 값의 타입은 기본 타입(byte, short, int, float, double, boolean, char)을 사용할 수 없고 클래스 및 인터페이스 타입만 가능
//내림차순 정렬 NavigableSet<E> descendingSet = treeSet.descendingSet(); //오름차순 정렬 descendingSet() 메소드를 두번 호출 NavigableSet<E> ascendingSet = descendingSet.descendingSet();
//내림차순 정렬 NavigableSet<E> descendingSet = treeSet.descendingSet(); //오름차순 정렬 descendingSet() 메소드를 두번 호출 NavigableSet<E> ascendingSet = descendingSet.descendingSet();
//c <= 검색단어 <= f NavigableSet<String> rangeSet = treeSet.subSet("c", true, "f", true);
//내림차순 정렬 NavigableSet<E> descendingMap = treeMap.descendingMap(); //오름차순 정렬 descendingMap() 메소드를 두번 호출 NavigableSet<E> ascendingMap = descendingMap.descendingMap();
//c~f 사이의 단어 검색 NavigableMap<String, Integer> rangeMap = treeMap.subMap("c", true, "f", true);
//오름차순 정렬 TreeSet<E> treeSet = new TreeSet<E>(new AscendingComparator()); //내림차순 정렬 TreeMap<K, V> treeMap = new TreeMap<K, V>(new DescendingComparator());
List<T> list = Collections.synchronizedList(new ArrayList<T>()); Set<E> set = Collections.synshronizedSet(new HashSet<E>()); Map<K, V> map = Collections.synchronizedMap(new HashMap<K, V>());
Map<K, V> map = new ConturrentHashMap<K, V>(); Queue<E> queue = new ConcurrentLinkedQueue<E>();
댓글
댓글 쓰기