컬렉션 프레임워크 ( Collection Framework)

컬렉션

사전적 의미로 요소(객체)를 수집해서 저장하는 것

배열의 문제점

저장할 수 있는 객체가 배열을 생성할 때 결정
     ->불특정 다수의 객체를 저장하기에는 문제


객체를 삭제했을 때 해당 인덱스가 비게됨
-> 낱알이 덤성덤성 빠진 옥수수가 될 수 있다.
-> 객체를 저장 하려면 어디가 비어있는지 확인


컬렉션 프레임워크

객체들을 효율적으로 추가, 삭제, 검색할 수 있도록 제공되는 컬렉션 라이브러리

java.util 패키지에 포함

인터페이스를 통해서 정형화된 방법으로 다양한 컬렉션 클래스를 이용



컬렉션 프레임워크의 주요 인터페이스


List

배열과 유사하게 인덱스로 관리

Set

집합과 유사

Map

키와 값의 쌍으로 관리


인터페이스의 분류 

특징 

구현 클래스 

 Collection

List 계열

- 순서를 유지하고 저장

- 중복 저장 가능 

ArrayList, Vector,

LinkedList 

Set 계역 

- 순서를 유지하지 않고 저장

- 중복 저장 안됨 

HashSet, TreeSet 

Map계열 

- 키와 값의 쌍으로 저장

- 키는 중복 저장 안됨

HashMap, Hashtable,

TreeMap, Properties 


메소드 참조(Method references)

메소드를 참조해서 

매개변수의 정보 및 리턴타입을 알아내어 람다식에서 불필요한 매개변수를 제거하는 것이 목적이다.


종종 람다식은 기존 메소드를 단순하게 호출만 하는 경우가 있다.

 (left, right) -> Math.max(left,right);           
    --->  
 Math :: max  -----메소드 참조


메소드 참조도 람다식과 마찬가지로 인터페이스의 익명 구현 객체로 생성됨.

타겟 타입에서 추상메소드의 매개변수 및 리턴 타입에 따라 메소드 참조도 달라진다.

예)  IntBinayOperator 인터페이스는 두개의 int  매개값을 받아 int 값을 리턴 하므로
 동일한 매개값과 리턴타입을 갖는 Math 클래스의 max() 메소드를 참조할 수 있다.

IntBinaryOperator operator = Math :: max;  -----메소드 참조



정적 메소드와 인스턴스 메소드 참조

정적 메소드 참조

클래스 :: 메소드

인스턴스 메소드 참조

참조변수 :: 메소드


Exam

public class Calculator {
public static int staticMethod(int x, int y) {
return x + y;
}

public int instanceMethod(int x, int y) {
return x + y;
}

}
import java.util.function.IntBinaryOperator;

public class MethodReferencesExample {
public static void main(String[] args) {
IntBinaryOperator operator;

//정적 메소드 참도
operator = (x, y) -> Calculator.staticMethod(x, y);
System.out.println("결과1 : " + operator.applyAsInt(1, 2));
//결과1 : 3

operator = Calculator::staticMethod;
System.out.println("결과2 : " + operator.applyAsInt(3, 4));
//결과2 : 7

//인스턴스 메소드 참조
Calculator obj = new Calculator();
operator = (x,y) -> obj.instanceMethod(x, y);
System.out.println("결과3 : " + operator.applyAsInt(5,6));
//결과3 : 11

operator = obj::instanceMethod;
System.out.println("결과3 : " + operator.applyAsInt(7,8));
//결과4 : 15
}
}



매개변수의 메소드 참조

(a,b) -> {a.instanceMethod(b);}   --->   클래스 :: instanceMethod



ToIntBiFunction<String, String> function;


function = (a,b) -> a.compareToIgnoreCase(b);

print(function.applyAsInt( "Java8", "JAVA8"));


---->


function = String :; compareToIgnoreCase;

print(function.applyAsInt( "Java8", "JAVA8"));


exam

import java.util.function.ToIntBiFunction;

public class ArgumentMethodReferencesExample {
public static void main(String[] args) {
ToIntBiFunction<String, String> function;

function = (a, b) -> { return a.compareToIgnoreCase(b); };
print(function.applyAsInt("java8", "JAVA8"));
//동일한 문자열입니다.

function = String::compareToIgnoreCase;
print(function.applyAsInt("java8", "JAVA8"));
//동일한 문자열입니다.
}

private static void print(int order) {
if (order < 0) {System.out.println("사전 순으로 먼저 옵니다."); }
else if (order == 0) { System.out.println("동일한 문자열입니다."); }
else {System.out.println("사전순으로 나중에 옵니다."); }
}
}



생정자 참조

(a, b)  -> {return new 클래스(a,b);}   --- > 클래스 :: new

Exam

public class Member {
private String name;
private String id;

public Member() {
System.out.println("Member() 실행");
}

public Member(String id) {
System.out.println("Member(String id) 실행");
this.id = id;
}

public Member(String name, String id) {
System.out.println("Member(String name, String id) 실행");
this.name = name;
this.id = id;
}
}
import java.util.function.BiFunction;
import java.util.function.Function;

public class ConstructorReferencesExample {
public static void main(String[] args) {
Function<String, Member> function1 = Member::new;
Member member1 = function1.apply("angel");
// Member(String id) 실행

BiFunction<String, String, Member> function2 = Member::new;
Member member2 = function2.apply("김나박", "angel");
//Member(String name, String id) 실행
}
}


한 개의 추상메소드를 가지는 인터페이스들은 모두 람다식 사용 가능

예: Runnable 인터페이스

public class RunnableExample {
public static void main(String[] args) {
// how1
Runnable runnable = () ->{
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
};
Thread thread = new Thread(runnable);
thread.start();

//how2
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
});
thread2.start();
}
}




자바 8부터  표준 API로 제공되는 함수적 인터페이스

java.util.function 패키지에 포함되어 있다.

매개타입으로 사용되어 람다식을 매개값으로 대입할 수 있도록 해준다.

method( Consummer con ){ ... };
method(    (x) -> { ...}   );

종류

Consumer 함수적 인터페이스류 :      데이터를 소비

매개값만 있고 리턴값이 없는 추상 메소드를 가지고 있다.

Supplier 함수적 인터페이스류 :         데이터를 공급

매개값은 없고 리턴값만 있는  추상 메소드를 가지고 있다.

Function 함수적 인터페이스류 :       A를 B로 변환

매개값과  리턴값이 모두 있는 추상 메소드를 가지고 있다.
주로 매개값을 리턴값으로 매핑(타입변환)할 경우에 사용

Operator 함수적 인터페이스류 :     데이터를 연산

매개값과 리턴값이 모두 있는 추상 메소드를 가지고 있다.
주로 매개값을 연산하고 그 결과를 리턴할 경우에 사용

Predicate 함수적 인터페이스류 :        매개 값을 조사해서 true & false 리턴

매개값을 조사해서 true 또는 false를 리턴할 때 사용




Consumer 함수적 인터페이스

Consumer 함수적 인터페이스의 특징        :        리턴값이 없는 accept() 메소드를 가지고 있다.

accept()메소드는 단지 매개값을 소비하는 역할만 한다..     소비한다는 말은 사용만 할 뿐  리턴값이 없다는 뜻.


Consumer인터페이스와 추상메소드 참고 -->    https://docs.oracle.com/javase/8/docs/api/java/util/function/package-frame.html                       


Exam

import java.util.function.*;

public class ConsumerExample {
public static void main(String[] args) {
Consumer<String> consumer =
s -> System.out.println("s + \"8\" = " + s + "8");
consumer.accept("JAVA");

BiConsumer<String, String> biConsumer =
(s, s2) -> System.out.println("s + s2 = " + s + s2);
biConsumer.accept("Java","8");

DoubleConsumer doubleConsumer =
value -> System.out.println("Java" + value);
doubleConsumer.accept(7.0);

ObjIntConsumer<String> objIntConsumer =
(s, value) -> System.out.println("s + value = " + s + value);
objIntConsumer.accept("JAVA", 8);
}
}


Supplier 함수적 인터페이스

Supplier 함수적 인터페이스 특징    :         매개변수가 없고, 리턴값이 있는 getXXX() 메소드를 가지고 있다.

         이들 메소드는 실행 후 호출한 곳으로 데이터를 리턴(공급)하는 역할을 한다.


Supplier 인터페이스와 추상메소드  참고 -->    https://docs.oracle.com/javase/8/docs/api/java/util/function/package-frame.html              

Exam

import java.util.function.IntSupplier;

public class SupplierExample {
public static void main(String[] args) {

IntSupplier intSupplier = () -> {
int num = (int) (Math.random() * 6) + 1;
return num;
};
System.out.println("주사위 1~6중의 수: = " + intSupplier.getAsInt());
System.out.println("주사위 1~6중의 수: = " + intSupplier.getAsInt());
System.out.println("주사위 1~6중의 수: = " + intSupplier.getAsInt());
//외에 DoubleSupplier, BooleanSupplier, LongSupplier 등등이 있다.
}
}


Function 함수적 인터페이스

Function 함수적 인터페이스 특징   :         매개변수와 리턴값이 있는 applyXXX(), XXXToXXFuncion() , ToXXFuntion() 메소드를 가지고 있다.
이들 메소드는 매개값을 리턴값으로 매핑(타입변환)하는 역할을 한다. 

   A      --->  function   ---> B
매개값 --- >  function  ---> 리턴값

Exam

public class Student {
private String name;
private int englishScore;
private int mathScore;

public Student(String name, int englishScore, int mathScore) {
this.name = name;
this.englishScore = englishScore;
this.mathScore = mathScore;
}

public String getName() {
return name;
}

public int getEnglishScore() {
return englishScore;
}

public int getMathScore() {
return mathScore;
}
}

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.ToIntFunction;

public class FunctionExample1 {
//두개의 Student를 가진 리스트 생성
private static List<Student> list = Arrays.asList(
new Student("홍길동", 90, 96),
new Student("김나박", 95, 93)
);

public static void printString(Function<Student,String > function) {
for (Student student : list) {
System.out.print(function.apply(student) + " ");
}
System.out.println();
}

public static void printInt(ToIntFunction<Student> function) {
for (Student student : list) {
System.out.print(function.applyAsInt(student) + " ");
}
System.out.println();
}

public static void main(String[] args) {
System.out.println("[학생 이름]");



printString(t -> t.getName());

System.out.println("[영어 점수]");
printInt( t -> t.getEnglishScore());

System.out.println("[수학 점수]");
printInt( t -> t.getMathScore());
}
}

Exam2

import java.util.Arrays;
import java.util.List;
import java.util.function.ToIntFunction;

public class FunctionExample2 {
//두개의 Student를 가진 리스트 생성
private static List<Student> list = Arrays.asList(
new Student("홍길동", 90, 96),
new Student("김나박", 95, 93)
);
//평균을 구하는..
public static double avg(ToIntFunction<Student> function) {
int sum = 0;
for (Student student : list) {
sum += function.applyAsInt(student);
}
double avg = (double)sum / list.size();
return avg;
}

public static void main(String[] args) {
double englishAvg = avg(t -> t.getEnglishScore());
System.out.println("영어 평균 점수= " + englishAvg);

double mathAvg = avg(t -> t.getMathScore());
System.out.println("수학 평균 점수 = " + mathAvg);
}
}



Operator 함수적 인터페이스

Operator 함수적 인터페이스 특징      :             Function 과 동일하게 매개변수와 리턴값이 있는     applyXXX() 메소드를 가지고 있다.
하지만 이들 메소드는 매개값을 리턴값으로 매핑(타입변환)하는  역할보다는 
매개 값을  이용해서 연산을 수행한 후 동일한 타입으로 리턴 값을 제공하는 역할을 한다.


Operator 인터페이스와 추상메소드  참고 -->    https://docs.oracle.com/javase/8/docs/api/java/util/function/package-frame.html              



Exam

import java.util.function.IntBinaryOperator;

public class OperatorExample {
private static int[] scores = {92, 95, 87};

public static int maxOrMin(IntBinaryOperator operator) {
int result = scores[0];
for (int score : scores) {
result = operator.applyAsInt(result, score);
}
return result;
}

public static void main(String[] args) {
//쵀대값
int max = maxOrMin(
(a, b) -> {
if (a >= b) return a;
else return b;
}
);
System.out.println("최대값: " + max);

//최소값
int min = maxOrMin(
(a, b) -> {
if (a <= b) return a;
else return b;
}
);
System.out.println("최소값: " + min);
}
}





Predicate 함수적 인터페이스

Predicate 함수적 인터페이스 특징     :         매개변수와 boolean 리턴값이 있는 testXXX() 메소드를 가지고 있다.
  이들 메소드는 매개 값을 조사해서 true, false 를 리턴하는 역할을 한다.
매개변수 타입과 수에 따라서 아래와 같은 Predicate 함수적 인터페이스들이 있다.


Predicate 인터페이스와 추상메소드  참고 -->    https://docs.oracle.com/javase/8/docs/api/java/util/function/package-frame.html              



Exam


import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class PredicateExample {
private static List<Student> list = Arrays.asList(
new Student("홍길동", "남자", 90),
new Student("김나박", "여자", 90),
new Student("김범수", "남자", 95),
new Student("박정현", "여자", 92)
);

public static double avg(Predicate<Student> predicate) {
int count = 0;
int sum = 0;
for (Student student : list) {
if (predicate.test(student)) {
count++;
sum += student.getScore();
}
}
return (double)sum / count;
}

public static void main(String[] args) {
//남자 평균 점수
double maleAvg = avg(t -> t.getSex().equals("남자"));
System.out.println("남자 평균 점수 : " + maleAvg);

//여자 평균 점수
double femaleAvg = avg(t -> t.getSex().equals("여자"));
System.out.println("여자 평균 점수 : " + femaleAvg);
}
}





andThen()과 compose() 디폴트 메소드

함수적 인터페이스가 가지고 있는 디폴트 메소드이다.

두 개의 함수적 인터페이스를  순차적으로 연결해서 실행한다.

첫번째 리턴값을 두번째 매개값으로 제공해서 최종 결과값 리턴

andThen()과 compose()의 차이점은 어떤 함수적 인터페이스부터 처리하느냐이다.


andThen() 디폴트 메소드    

인터페이스AB  = 인터페이스A.andThen(인터페이스B)
최종결과 = 인터페이스 AB.method();

인터페이스AB method()  ---> 인터페이스A(람다식)  ---> 인터페이스B(람다식) ---> 최종결과

compose() 디폴트 메소드

인터페이스AB  = 인터페이스A.compose(인터페이스B)
최종결과 = 인터페이스 AB.method();


최종결과   <--- 인터페이스A(람다식)  <--- 인터페이스B(람다식) <--- 인터페이스AB method()





Consumer의 순차적 연결

     Consumer 종류의 함수적인터페이스는 처리 결과를 리턴하지 않기 때문에 andThen()과 compose()디폴트 메소드는 함수적 인터페이스의 호출 순서만 정한다.


exam

public class Member {
private String name;
private String id;
private Address address;

public Member(String name, String id, Address address) {
this.name = name;
this.id = id;
this.address = address;
}

public String getName() {
return name;
}

public String getId() {
return id;
}

public Address getAddress() {
return address;
}
}
public class Address {
private String country;
private String city;

public Address(String country, String city) {
this.country = country;
this.city = city;
}

public String getCountry() {
return country;
}

public String getCity() {
return city;
}
}
import java.util.function.Consumer;

public class ConsumerAndThenExample {
public static void main(String[] args) {
Consumer<Member> consumerA = m -> {
System.out.println("consumerA: " + m.getName());
};

Consumer<Member> consumerB = m -> {
System.out.println("consumerB: " + m.getId());
};

Consumer<Member> consumerAB = consumerA.andThen(consumerB);
consumerAB.accept(new Member("홍길동", "IdHong", null));
// 실행 결과
// consumerA: 홍길동
// consumerB: IdHong

Consumer<Member> consumerAC = consumerB.andThen(consumerA);
consumerAC.accept(new Member("홍길동", "IdHong", null));
// 실행 결과
// consumerB: IdHong
// consumerA: 홍길동
}
}



Function의 순차적 연결

Function과 Operator종류의 함수적 인터페이스는 
먼저 실행한 함수적 인터페이스의 결과 를 다음 함수적 인터페이스의 매개값 으로 넘겨주고 , 최종 처리결과를 리턴한다.


결과적으로 Address로 String을 얻는 결과
Function<Member,Address> + Function<Address, String>    = Function<Member, String>


Exam

import java.util.function.Function;

public class FunctionAndThenComposeExample {
public static void main(String[] args) {
Function<Member, Address> functionA;
Function<Address, String> functionB;
Function<Member, String> functionAB;

functionA = m -> m.getAddress();
functionB = a -> a.getCity();

functionAB = functionA.andThen(functionB);

String city =
functionAB.apply(new Member("홍길동", "IdHong", new Address("한국", "서울")));
System.out.println("거주도시 = " + city);
// 실행 결과
// 거주도시 = 서울

functionAB = functionB.compose(functionA);
city = functionAB.apply(new Member("홍길동", "IdHong", new Address("한국", "서울")));
System.out.println("거주도시 = " + city);
// 실행 결과
// 거주도시 = 서울
}
}



and() , or(), negate() 디폴드 메소드와 isEqual() 정적 메소드

and(), or(), negate() 디폴트메소드

Predicate 함수적 인터페이스의 디폴트 메소드

and():  &&와 대응 - 두 Predicate가 모두 true 를 리턴하면 최종적으로 true를 리턴
PredicateAB = predicateA.and(predicateB);

or():     ||와 대응 - 두 Predicate 중 하나만 true를 리턴하면 최종적으로 true를 리턴
       PredicateAB = predicateA.or(predicateB);

negate(): !과 대응  - Predicate의 결과가 true면 false,   false면 true를 리턴
      PredicateAB = predicateA.negate();

Exam

import java.util.function.IntPredicate;

public class PredicateAndOrNegateExample {
public static void main(String[] args) {
//2의 배수를 검사
IntPredicate predicateA = a -> { return (a % 2 == 0); };

//3의 배수를 검사
IntPredicate predicateB = a ->{ return (a % 3 == 0);};

IntPredicate predicateAB;
boolean result;

//and()
predicateAB = predicateA.and(predicateB);
result = predicateAB.test(9);
System.out.println("9는 2와 3의 배수입니까? " + result);

//or()
predicateAB = predicateA.or(predicateB);
result = predicateAB.test(9);
System.out.println("9는 2와 3의 배수입니까? " + result);

//negate
predicateAB = predicateA.negate();
result = predicateAB.test(9);
System.out.println("9는 false 입니까? " + result);
}
}


isEqual() 정적 메소드

Predicate<T>의 정적 메소드



Exam

import java.util.function.Predicate;

public class PredicateIsEqualExample {
public static void main(String[] args) {
Predicate<String > predicate;
predicate = Predicate.isEqual(null);
System.out.println("null, null: " + predicate.test(null));
//null과 null은 true

predicate = Predicate.isEqual("Java8");
System.out.println("null, Java8: " + predicate.test(null));
//false

predicate = Predicate.isEqual("Java8");
System.out.println("null, Java8: " + predicate.test(null));
//false

predicate = Predicate.isEqual("null");
System.out.println("null, Java8: " + predicate.test("Java8"));
//false

predicate = Predicate.isEqual("Java8");
System.out.println("Java8, Java8: " + predicate.test("Java8"));
//true
}
}




minBy(), maxBy 정적 메소드

BinaryOperator<T> 함수적 인터페이스의 정적 메소드

Comparator를 이용해서  최대 T와 최소 T를 얻는 BinaryOperator<T>를 리턴한다.

리턴타입 

정적 메소드 

BinaryOperator<T> 

minBy(Compareator<? super T> comparator) 

BinaryOperator<T>  

maxBy(Compareator<? super T> comparator) 


Comparator<T> 는 다음과 같이 선언된 함수적 인터페이스이다. 


o1과 o2를 비교해서 o1이 작으면 음수를,

 o1 과 o2를 비교해서 동일하면  0,    

 o1이 크면 양수를 리턴해야하는 compara() 메소드가 선언되어 있다.



@functionalInterface

public interface Comparator<T> {

public int compare(T o1, T o2);

}


Comparator<T>를 타겟 타입으로하는 람다식은 다음과 같이    작성할 수 있다.


(o1, o2) -> { ...; return int 값 ;}


만약 o1 와 o2가 int 타입이라면 다음과 같이 Integer.compare(int, int) 메소드를 이용 할 수 있다.


(o1, o2) -> Integer.compare(o1, o2);




Exam

public class Fruit {
public String name;
public int price;

public Fruit(String name, int price) {
this.name = name;
this.price = price;
}
}
import java.util.function.BinaryOperator;

public class OperatorMinByMaxByExample {
public static void main(String[] args) {

BinaryOperator<Fruit> binaryOperator;

Fruit fruit;
binaryOperator = BinaryOperator.minBy((f1, f2) -> Integer.compare(f1.price, f2.price));
fruit = binaryOperator.apply(new Fruit("딸기", 6000), new Fruit("수박", 10000));
System.out.println("fruit.name = " + fruit.name);
// fruit.name = 딸기

binaryOperator = BinaryOperator.maxBy((f1, f2) -> Integer.compare(f1.price, f2.price));
fruit = binaryOperator.apply(new Fruit("딸기", 6000), new Fruit("수박", 10000));
System.out.println("fruit.name = " + fruit.name);
// fruit.name = 수박
}
}


+ Recent posts