[KOSTA JAVA] #Day 13&14 (Lambda, Collection Framework)

0f1c04·2021년 3월 5일
0

KOSTA JAVA TIL

목록 보기
11/11
post-thumbnail

람다식

람다식

자바는 함수적 프로그래밍을 위해 자바 8부터 람다식(Lambda Expression)을 지원한다. 람다식은 익명함수(anonymous function)을 생성하기 위한 식으로 객체지향 언어 보다는 함수지향 언어에 가까워 기존 자바 개발과 다르게 생각될 수 있다. 자바에서 람다식을 수용한 이유는 자바 코드가 매우 간결해지고, 컬렉션의 요소를 필터링하거나 매핑해서 원하는 결과를 쉽게 집계할 수 있기 때문이다. 람다식의 형태는 매개 변수를 가진 코드 블록이지만, 런타임시에는 익명 구현 객체를 생성한다.

(람다식 -> 매개 변수를 가진 코드 블록 -> 익명 구현객체)

//Runnable 인터페이스의 익명 구현 객체 생성하는 코드
Runnable runnable = new Runnable() {
    public void run();
};
//Runnable 인터페이스의 익명 구현 객체 생성 람다식
Runnable runnable = () -> {...};

람다식의 기본 문법

  • 함수적 스타일의 람다식을 작성하는 방법
(타입 매개변수, ...) -> {실행문; ...} // 매개 변수의 이름은 사용자 정의
  • 람다식 기본 문법
//기본
(int a) -> {System.out.println(a);}

//매개 변수가 1개 라면 ()와 타입을 생략 가능, 매개 변수 타입은 대입되는 값에 따라 자동으로 인식될 수 있기 때문에 람다식에서는 매개 변수의 타입을 일반적으로 언급하지 않음
a -> {System.out.prinln(a);}

//실행문이 하나일 때 {}를 생략 가능
a -> System.out.println(a)

//만약 매개 변수가 없다면 빈 괄호를 사용  
() ->{실행문; ..}

//리턴하는 실행문은 다음과 같이 작성 가능
(x,y) -> {return x+y;}

//중괄호에 return문만 있을 경우 다음과 같이 작성 가능
(x,y) -> x+y

타겟 타입과 함수적 인터페이스

람다식의 형태는 매개 변수를 가진 코드 블록이기 때문에 마치 자바의 메소드를 선언하는 것처럼 보이나, 자바는 메소드를 단독으로 선언할 수 없고 항상 클래스의 구성 멤버로 선언하기 때문에 람다식은 단순히 메소드를 선언하는 것이 아니라 이 메소드를 가지고 있는 객체를 생성한다.

  인터페이스 변수 = 람다식; //람다식으로 인터페이스의 익명 구현 객체를 생성
//(타겟 타입)
  • 람다식은 대입될 인터페이스의 종류에 따라 작성 방법이 달라지기 때문에 람다식이 대입될 인터페이스를 람다식의 타겟 타입(target type)

함수적 인터페이스(@FunctionalInterface)

  • 람다식이 하나의 메소드를 정의하므로, 두 개 이상의 추상 메소드가 선언된 인터페이스는 람다식을 이용해서 구현 객체를 생성할 수 없다. 이렇듯 하나의 추상메소드 만이 선언된 인터페이스를 함수적 인터페이스(functional interface)라고 한다.
  • 함수적 인터페이스를 작성할 때 두 개 이상의 추상 메소드가 선언되지 않도록 컴파일러가 체킹하도록 하기 위하여 인터페이스 선언 시 @FunctionalInterface 어노테이션을 붙일 수 있다. 추상 메소드가 두 개 이상이면, 컴파일 오류가 발생된다.
@FunctionalInterface
public interface MyFunctionalInterface {
    public void method();
    public void otherMethod(); // 컴파일 오류 발생
}

매개 변수와 리턴값이 없는 람다식

  • 매개 변수와 리턴값이 없는 추상 메소드를 가진 함수적 인터페이스는 다음과 같이 사용될 수 있다.
//함수적 인터페이스
@FunctionalInterface
public interface MyFunctionalInterface {
    public void method();
}
//위 인터페이스를 타겟 타입으로 갖는 람다식
MyFunctionalInterface fi = () -> {...}

//참조 변수로 메소드를 호출하여 람다식의 중괄호를 실행시킴
fi.method();
public class MyFunctionalInterfaceEx{
    public static void main(String[]args){
        MyFunctionalInterface fi;
        
        fi= () -> {
            String str = "method call1";
            System.out.println(str);
        };
        fi.method();
        
        fi = () -> System.out.println("mehod call2");
        fi.method();
    }
}

매개 변수가 있는 람다식

  • 매개 변수가 있고 리턴값이 없는 추상 메소드를 가진 함수적 인터페이스는 다음과 같이 사용될 수 있다.
@FunctionalInterface
public interface MyFunctionalInterface{
    public void method(int x);
}
//람다식
MyFunctionalInterface fi = x -> {...};

//메소드 호출
fi.method(5);

리턴값이 있는 람다식

  • 매개 변수가 있고 리턴값이 있는 추상 메소드를 가진 함수적 인터페이스는 다음과 같이 사용될 수 있다.
@FunctionalInterface
public interface MyFunctionalInterface{
    public int method(int x, int y);
}
//람다식
MyFunctionalInterface fi = (x, y) -> {...; return; };

//메소드 호출
fi.method(2,4);

클래스 멤버와 로컬 변수 사용

람다식의 실행 블록에는 클래스의 멤버(필드와 메소드) 및 로컬 변수를 사용할 수 있다.

클래스의 멤버는 제약 사항 없이 사용 가능하지만, 로컬 변수는 제약이 따른다.

클래스의 멤버 사용

  • 람다식 실행 블록에는 클래스의 멤버인 필드와 메소드를 제약 없이 사용할 수 있으나, this 키워드의 사용에는 주의가 필요하다.
  • 일반적으로 익명 객체 내부에서 this는 익명 객체의 참조이지만, 람다식에서 this는 내부적으로 생성되는 익명 객체의 참조가 아니라 람다식을 실행한 객체의 참조이다.
//함수적 인터페이스
@FunctionalInterface
public interface MyFunctionalInterface{
    public void method();
}
//this 사용
public class UsingThis{
        public int outter =10;
        
        class Inner{
            int inner =20;
            
            void method(){
                //람다식
                MyFunctionalInterface fi = () -> {
                    System.out.println("outter: "+ outter);
                    System.out.println("outter: "+ UsingThis.this.outter+"\n");
         			//바깥 객체의 참조 얻기 위해서는 클래스명.this 사용           
                    System.out.println("inner: "+ inner);
                    System.out.println("inner: "+ this.inner +"\n");//내부 객체는 this 사용 가능
                };
                fi.method;
            }
        }
}
//실행 클래스
public class UsingThisEx{
    public static void main(String[]args){
        UsingThis usingThis = new UsingThis();
        UsingThis.Inner inner = new usingTHis.new Inner();
        inner.method();
    }
}

로컬 변수 사용

  • 람다식은 메소드 내부에서 주로 작성되기 때문에 로컬 익명 구현 객체를 생성시킨다고 봐야 한다. 이 때 람다식에서 바깥 클래스의 필드나 메소드는 제한 없이 사용할 수 있으나, 메소드의 매개 변수 또는 로컬 변수를 사용하면 이 두 변수는 final 특성을 가져야 한다.
  • 따라서 매개 변수 또는 로컬 변수를 람다식에서 읽는 것은 허용되지만, 람다신 내부 또는 외부에서 변경할 수 없다.
  • (9.5.3 익명 객체의 로컬 변수 사용 참고, 람다식 내부 변수는 변경 가능)

표준 API의 함수적 인터페이스

자바 8부터는 빈번하게 사용되는 함수적 인터페이스(functional interface)는 java.util.function 표준 API로 제공한다.

이 패키지에서 제공하는 함수적 인터페이스의 목적은 메소드 또는 생성자의 매개 타입으로 사용되어 람다식을 대입할 수 있도록 하기 위해서이다.

java.util.function 패키지의 함수적 인터페이스는 크게 Consumer, Supplier, Function, Operator, Predicate로 구분 되며, 구분 기준은 인터페이스에 선언된 추상 메소드의 매개값과 리턴값의 유무이다.

14.5.1 Consumer 함수적 인터페이스

  • Consumer 함수적 인터페이스는 단지 매개값을 소비하며 리턴값이 없는 accept() 메소드를 가진다.
  • 매개 변수의 타입과 수에 따른 Consumer
인터페이스명추상 메소드설명
Consumervoid accept(T t)객체를 T를 받아 소비
BiConsumer<T,U>void accept(T t, U u)객체 T, U를 받아 소비
DoubleConsumervoid accept(double value)double 값을 받아 소비
intConsumervoid accept(int value)int 값을 받아 소비
LongConsumervoid accept(long value)long 값을 받아 소비
ObjDoubleConsumervoid accept(T t, double value)객체 T와 double 값을 받아 소비
ObjIntConsumervoid accept(T t, int value)객체 T와 int 값을 받아 소비
ObjLongConsumervoid accept(T t, long value)객체 T와 long 값을 받아 소비
  • 예제
//Consumer<T> 인터페이스를 타겟 타입으로 하는 람다식
Consumer<String> consumer = t -> {t를 소비하는 실행문;};

//ObjIntConsumer<T> 인터페이스를 타겟 타입으로 하는 람다식
ObjIntConsumer consumer = <t,i> -> {t와 i를 소비하는 실행문}
//Consumer 함수적 인터페이스
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.DoubleConsumer;
import java.util.function.ObjIntConsumer;

public class ConsumerEx {

	public static void main(String[] args) {
		Consumer<String> consumer = t-> System.out.println(t+"8");
		consumer.accept("java");
		
		BiConsumer<String, String> biConsumer = (t, u) -> System.out.println(t+u);
		biConsumer.accept("java","8");
		
		DoubleConsumer doubleConsumer = d -> System.out.println("java"+d);
		doubleConsumer.accept(8.0);
		
		ObjIntConsumer oiConsumer = (t, u) -> System.out.println(t+u);
		oiConsumer.accept("java", 8);
	}
}

Supplier 함수적 인터페이스

  • Supplier 함수적 인터페이스는 매개 변수가 없고 리턴값이 있는 getXXX() 메소드를 가진다. 이 메소드들은 실행 후 호출한 곳으로 데이터를 리턴(공급)하는 역할을 한다.

  • 리턴 타입에 따른 Supplier 함수적 인터페이스

    인터페이스명추상 메소드설명
    SupplierT get()T 객체를 리턴
    BooleanSupplierboolean getAsBoolean()boolean 값을 리턴
    DoubleSupplierdouble getAsDouble()double 값을 리턴
    IntSupplierint getAsInt()int 값을 리턴
    LongSupplierlong getAsLong()long 값을 리턴
  • 예제 : 주사위 수 랜덤

import java.util.function.IntSupplier;

public class SupplierEx {

	public static void main(String[] args) {
		IntSupplier intSupplier = () -> {
			int num = (int) (Math.random()* 6) + 1;
			return num;
		};	// 람다식
		
		int num = intSupplier.getAsInt();
		System.out.println("눈의 수: " +num);
	}

}

Function 함수적 인터페이스

  • Function 함수적 인터페이스는 매개값과 리턴값이 있는 appyXXX() 메소드를 가지며, 이 메소드들은 매개값을 리턴값으로 매핑(타입 변환)하는 역할을 한다.
  • 매개 변수와 리턴 타입에 따른 Function 함수적 인터페이스
인터페이스명추상메서드설명
Function<T,R>R apply(T t)객체 T를 객체 R로 매핑
BiFunction<T,U,R>R apply(T t, U u)객체 T와 U를 객체 R로 매핑
DoubleFunctionR apply(double val)double 를 객체 R로 매핑
IntFunctionR apply(int val)int 를 객체 R로 매핑
IntToDoubleFunctiondouble applyAsdouble(int val)int를 double로 매핑
IntToLongFunctionlong applyAsLong(int val)int를 long로 매핑
LongToDoubleFunctiondouble applyAsdouble(long val)long을 double로 매핑
LongToIntFunctionint applyAsInt(long val)long을 int로 매핑
ToDoubleBiFunction<T,U>double applyAsDouble(T t, U u)객체 T와 U를 double 로 매핑
ToDoubleFunctiondouble applyAsdouble(T t)객체 T를 double로 매핑
ToIntBiFunction<T,U>int applyAsInt(T t, U u)객체 T와 U를 int로 매핑
ToIntFunctionint applyAsInt(T t)객체 T를 int로 매핑
ToLongBiFunction<T,U>long applyAsLong(T t, U u)객체 T와 U를 long으로 매핑
ToLongFunctionlong applyAsLong(T t)객체 T를 long으로 매핑
  • 예제 : Student 이름, 점수 출력
//Student 클래스
public class Student {
	private String name;
	private int engScore;
	private int korScore;
	
	public Student(String name, int engScore, int korScore) {
		this.name=name;
		this.engScore=engScore;
		this.korScore=korScore;
	}
	
	public String getName() {
		return this.name;
	}
	public int getEngScore() {
		return this.engScore;
	}
	public int getKorScore() {
		return this.korScore;
	}
}
//FunctionEx
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.ToIntFunction;

public class FunctionEx {
	private static List <Student> list = Arrays.asList(
			new Student("홍길동", 90,58),
			new Student("신용권", 10,30)
			);
	
	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.getEngScore());
		
		System.out.println("[국어 점수]");
		printInt(t-> t.getKorScore());
	}
}

Operator 함수적 인터페이스

  • Operator 함수적 인터페이스는 Function과 동일하게 매개 변수와 리턴값이 있는 applyXXX()메소드를 가지고 있으나, Function 처럼 매핑하는 것이 아닌 매개값을 이용해 연산을 수행한 후 동일한 타입으로 리턴값을 제공하는 역할을 한다.
  • Operator 함수적 인터페이스
인터페이스명추상메서드설명
BinaryOperatorBiFunction<T,U,R>의 하위 인터페이스T와 U를 연산한 후 R 리턴
UnaryOperatorFunction<T,R>의 하위 인터페이스T를 연산한 후 R 리턴
DoubleBinaryOperatordouble applyAsDouble(double, double)두 개의 double 연산
DoubleUnaryOperatordouble applyAsDouble(double)한 개의 double 연산
IntBinaryOperatorint applyAsInt(int,int)두 개의 int 연산
IntUnaryOperatorint applyAsInt(int)한 개의 int 연산
LongBinaryOperatorlong applyAsLong(long, long)두 개의 long 연산
LongUnaryOperatorlong applyAsLong(long)한 개의 long 연산
  • 예제 : 최소값, 최대값 구하기
import java.util.function.IntBinaryOperator;

public class OperatorEx {
	private static int[] scores = {92, 30,90};
	
	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 함수적 인터페이스
인터페이스명추상 메소드설명
Predicateboolean test(T t)객체 T를 조사
BiPredicate<T, U>boolean test(T t, U u)객체 T와 U를 비교 조사
DoublePredicateboolean test(double value)double 값을 조사
IntPredicateboolean test(int value)int 값을 조사
LongPredicateboolean test(long value)long 값을 조사
  • 예제: 남자 여자 학생들의 평균 점수 출력
//Student2 클래스
public class Student2 {
	private String name;
	private String sex;
	private int score;
	
	public Student2(String name, String sex, int score) {
		this.name=name;
		this.sex=sex;
		this.score=score;
	}
	
	public String getName() {
		return this.name;
	}
	public String getSex() {
		return this.sex;
	}
	public int getScore() {
		return this.score;
	}
}
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class PredicateEx {


		private static List<Student2> list = Arrays.asList(
				new Student2("홍길동", "남자", 90),
				new Student2("김길동", "남자", 80),
				new Student2("김자바", "여자", 97),
				new Student2("박자바", "여자", 70)
				);

		public static double avg(Predicate<StudentP> predicate) {
			int count=0, sum=0;
			for(Student2 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() 디폴트 메소드

  • 디폴트 및 정적 메소드는 추상 메소드가 아니기 때문에 함수적 인터페이스에 선언되어도 여전히 함수적 인터페이스의 성질을 잃지 않는다.

  • Consumer, Function, Operator 종류의 함수적 인터페이스는 andThen()과 compose() 디폴트 메소드를 가지고 있다. 둘 다 두 개의 함수적인 인터페이스를 순차적으로 연결하고 , 첫 처리 결과를 두번째 매개값으로 제공해서 최종 결과값을 얻는다.

    인터페이스AB = 인터페이스A.andThen(인터페이스B);
    최종결과 = 인터페이스AB.method(); // andThen()은 A처리 ->A결과로 B처리
    
    인터페이스AB = 인터페이스A.compose(인터페이스B);
    최종결과 = 인터페이스AB.method(); // andThen()은 B처리 ->B결과로 A처리
  • andThen()과 compose() 디폴트 메소드

종류함수적 인터페이스andThen()compose()
ConsumerConsumerO
BiConsumer<T, U>O
DoubleConsumerO
IntConsumerO
LongConsumerO
FunctionFunction<T, R>OO
BiFunction<T, U, R>O
OperatorBinaryOperatorO
DoubleUnaryOperatorOO
IntUnaryOperatorOO
LongUnaryOperatorOO
  • Consumer()의 andThen은 처리 결과를 리턴하지 않으므로 호출 순서만 정한다.
  • 예제: andThen
import java.util.function.Consumer;

public class ConsumerAndThenEx {

	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("홍길동", "hong", null) );

	}

}
//Member 클래스
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;
	}
}
//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;
	}
}
  • Function과 Operator는 매개값을 통한 첫 처리결과를 다음 인터페이스의 매개 값으로 넘겨주고, 최종 처리결과를 리턴
import java.util.function.Function;

public class FuntionAndThenComposeEx {
	public static void main(String[]args) {
		Function<Member, Address> functionA;
		Function<Address, String> functionB;
		Function<Member, String> functionAB;
		String city;
		
		functionA = (m)->m.getAddress();
		functionB = (a)->a.getCity();
		
		functionAB = functionA.andThen(functionB);
		
		city = functionAB.apply(
			new Member("홍길동", "hong", new Address("한국", "서울"))
		);
		System.out.println("거주 도시 : "+city);
		
		functionAB = functionB.compose(functionA);
		
		city = functionAB.apply(
				new Member("홍길동", "hong", new Address("한국", "서울"))
				);
		System.out.println("거주 도시: " +city);
	}
				
}

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

  • Predicate 종류의 함수적 인터페이스는 and(), or(), negate() 디폴트 메소드를 가지고 있으며, 이들은 각각 &&,||,!와 대응된다고 볼 수 있다.
  • 예제: 2의 배수, 3의 배수 조사하기
import java.util.function.IntPredicate;

public class PredicteAndOrNegateEx {

	public static void main(String[] args) {
		// 2배수 검사
		IntPredicate predicateA = a-> a%2 ==0;
		
		// 3배수 검사
		IntPredicate predicateB = b-> b%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();// 원래 결과 true이면 false
		result = predicateAB.test(9);
		System.out.println("9는 홀수입니까?" +result);
		
	}

}
  • Predicate함수적 인터페이스는 isEqual() 정적 메소드를 추가로 제공
Predicate<Object> predicate = Predicate.isEqual(targetObeject);
boolean result = predicate.test(sourceObject);//Objects.equals(sourceObject, targetObject) 실행
sourceObjecttargetObject리턴값
nullnulltrue
not nullnullfalse
nullnot nullfalse
not nullnot nullObjects.equals(sourceObject, targetObject)의 리턴값
//예제
import java.util.function.Predicate;

public class PredicateIsEqualEx {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Predicate<String> predicate;
		
		predicate = Predicate.isEqual("java8");
		System.out.println("java8, null : " +predicate.test(null));
		
		predicate = Predicate.isEqual(null);
		System.out.println("null, null : " +predicate.test(null));
		
	}

}

minBy(), maxBy() 정적 메소드

  • BinaryOperator 함수적 인터페이스는 minBy()와 maxBy() 정적 메소드를 제공한다. 이 두 메소드는 매개값으로 제공되는 Comparator를 이용해서 최대 T와 최소 T를 얻는 BinaryOperator를 리턴한다.
리턴 타입정적 메소드
BinaryOperatorminBy(Comparator<? super T> comparator)
BinaryOperatormaxBy(Comparator<? super T> comparator)
@FunctionalInterface
public interface Comparator<T>{
    public int compare(T o1, T o2);
}
(o1, o2) -> {...; return int;} //람다식
  • 예제: 과일 가격 비교
import java.util.function.BinaryOperator;

public class OperatorMinByMaxByEx {

	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("딸기", 1000), new Fruit("사과",2000));
		System.out.println(fruit.name);
    }
}

메소드 참조

메소드 참조(Method Reference)는 메소드를 참조해서 매개 변수의 정보 및 리턴 타입을 알아내어, 람다식에서 불필요한 매개 변수를 제거하는 것이 목적이다.

메소드 참조도 람다식과 마찬가지로 인터페이스의 익명 구현 객체로 생성되므로 타겟 타입인 인터페이스의 추상 메소드가 어던 매개 변수를 가지고, 리턴 타입이 무엇인가에 따라 달라진다.

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

  • 정적(static) 메소드를 참조할 경우에는 클래스 이름 뒤에 ::기호를 붙이고 정적 메소드 이름을 기술
클래스 :: 메소드
  • 인스턴스 메소드일 경우에는 먼저 객체를 생성한 다음 참조 변수 뒤에 ::기호를 붙이고 인스턴스 메소드를 기술
참조변수 :: 메소드
  • 예제: 정적 및 인스턴스 메소드 참조
public class MehodReferencesEx {

	public static void main(String[] args) {
		IntBinaryOperator operator;
		
		//정적 메소드 참조
		operator = Calculator :: staticMethod;
		System.out.println("결과 : " + operator.applyAsInt(1, 2));
		
		Calculator obj = new Calculator();
		operator = obj :: instanceMethod;
		System.out.println("결과 : " +operator.applyAsInt(2, 4));

	}

}

매개 변수의 메소드 참조

  • 메소드는 람다식 외부의 클래스 멤버일 수도 있고, 람다식에서 제공되는 매개 변수의 멤버일 수도 있다. 다음과 같이 람다식에서 제공되는 a 매개 변수의 메소드를 호출해서 b 매개 변수를 매개값으로 사용하는 경우도 있다.
(a,b) -> {a.instanceMethod(b);}
//클래스 :: instanceMethod
  • 예제
import java.util.function.ToIntBiFunction;

public class ArgumentMethodReferencesEx {

	public static void main(String[] args) {
		ToIntBiFunction<String,String> function;
		
		function = (a,b) -> a.compareToIgnoreCase(b);
		print(function.applyAsInt("java8", "JAVA8"));
		
		function = String :: compareToIgnoreCase;
		print(function.applyAsInt("java8", "JAVA8"));

	}
	
	public 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 로 대치 가능
  • 생성자가 오버로딩되어 여러 개 있을 경우, 컴파일러는 함수적 인터페이스의 추상 메소드와 동일한 매개 변수 타입과 개수를 가지고 있는 생성자를 찾아 실행한다. 만약 해당 생성자가 존재하지 않으면 컴파일 오류가 발생한다.
  • 예제
import java.util.function.BiFunction;
import java.util.function.Function;

public class ConstructorReferenceEx {

	public static void main(String[] args) {
		Function<String, MemberC> function1 = MemberC :: new;
		MemberC member1 = function1.apply("angel");
		
		BiFunction<String,String, MemberC> function2 = MemberC :: new;
		MemberC member2 = function2.apply("강강", "angel");
	}
}

컬렉션

컬렉션 프레임워크

배열은 쉽게 생성하고 사용할 수 있지만, 저장할 수 있는 객체 수가 배열을 생성할 때 결정되기 때문에 붙특정 다수의 객체를 저장하기에는 문제가 있다. 자바는 이러한 문제점을 해결하고 널리 알려져 있는 자료구조를 바탕으로 객체들을 효율적으로 추가, 삭제, 검색할 수 있도록 java.utill 패키지에 컬렉션과 관려된 인터페이스와 클래스인 컬렉션 프레임워크(Collection Framework)을 포함시켜 놓았다.

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

인터페이스 분류특징구현클래스
Collection (List)- 순서를 유지하고 저장
- 중복 저장 가능
ArrayList, Vector, LinkedList
Collection (Set)- 순서를 유지하지 않고 저장
- 중복 저장 안됨
HashSet, TreeSet
Map- 키와 값의 쌍으로 저장HashMap, Hashable,TreeMap, Properties

List 컬렉션

List 컬렉션은 객체를 일렬로 늘어놓은 구조를 가지고 있다. 객체를 인덱스로 관리하기 때문에 객체를 저장하면 자동 인덱스가 부여되고 인덱스로 객체를 검색, 삭제할 수 있는 기능을 제공한다.

List 컬렉션에는 ArrayList, Vector, LinkedList 등이 있다.

주요 메소드

기능메소드설명
객체 추가boolean add(E e)주어진 객체를 맨 끝에 추가
void add(int index, E element)주어진 인덱스에 객체 추가
E set(int index, E element)주어진 인덱스에 저장된 객체를 주어진 객체로 바꿈
객체 검색boolean contains(Object o)주어진 객체가 저장되어 있는지 여부
E get(int index)주어진 인덱스에 저장된 객체를 반환
boolean isEmpty()컬렉션이 비어있는지 반환
int size(0)저장되어 있는 전체 객체 수 반환
객체 삭제void clear()저장된 모든 객체를 삭제
E remove(int index)주어진 인덱스에 저장된 객체를 삭제
boolean remove(Object o)주어진 객체를 삭제

ArrayList

ArrayList는 List 인터페이스의 구현 클래스로, ArrayList에 객체를 추가하면 객체가 인덱스로 관리된다는 점은 배열과 비슷하지만, 배열은 생성할 때 크기가 고정적이고 사용 중에 변경 불가능하지만, ArrayList는 저장용량이 가변적으로 늘어난다.

  • ArrayLIst 생성 방법

    `List<String> list = new ArrayList<String>;

    기본 생성자로 생성 시 10개의 초기 용량(capacity)가 설정되어있다.

    List<String> list = new ArrayList<String>(20)

    매개값으로 초기 용량을 설정할 수 있다.

  • ArrayList에 객체를 추가하면 인덱스 0부터 차례대로 저장된다. 특정 인덱스의 객체를 제거하면 마지막 인덱스부터 앞으로 1칸씩 당겨진다. 반대로 삽입을 하면 1칸씩 밀려난다.

  • 객체 삭제와 삽입이 많이 일어나는 로직에서는 ArrayList 사용이 맞지 않다.

  • 예제

    private static void method1() { //ArrayList
      //List 인터페이스를 구현한 class: ArrayList
      //순서가 있다. 중복을 허용한다.
      List<String> list = new ArrayList<>(3);
      String[] days = {"월요일", "화요일", "수요일", "목요일", "금요일", "토요일", "일요일"};
      for(String s:days) {
        list.add(s);
      }
      //함수연습
      //get()
      String s = list.get(6);
      System.out.println("6번쨰 값: " + s);
      //contains(), clear()
      if(list.contains("화요일")) { //검색
        System.out.println("화요일이 있습니다.");
      }
    
      for(int i = 0; i < list.size(); i++) {
        System.out.println(i + "번째-->" + list.get(i));
      }
    }

Vector

Vector는 ArrayList와 동일한 내부 구조이다. Vector를 생성하기 위해서 저장할 객체타입을 타입 파라미터로 표기하고 기본 생성자를 호출하면 된다.

  • Vector 생성 방법

    List<E> list = new Vector<E>();

  • ArrayList와 차이점은 동기화(synchronized) 메소드로 구성되어 있어 멀티 스레드가 동시에 실행할 수 없고 하나의 스레드가 실행을 완료해야만 다른 스레드를 실행할 수 있으므로, 멀티 스레드 환경에서 안전하게 객체를 추가, 삭제할 수 있다.

  • 예제

    private static void method3() { //Vector
      //List 인터페이스를 구현한 class: Vector
      //순서가 있다. 중복을 허용한다.
      List<String> list = new Vector<>();
      String[] days = {"월요일", "화요일", "수요일", "목요일", "토요일", "토요일", "일요일"};
      for(String s:days) {
        list.add(s);
      }
      for(String s:list) {
        System.out.print(s + " ");
      }
    }

LinkedList

LinkedList는 List 구현 클래스로 ArrayList와 사용 방법은 똑같으나 내부 구조가 다르다. ArrayList는 저장된 객체를 인덱스로 관리하지만, LinkedList는 인접 참조를 링크하여 체인처럼 관리한다. 특정 인덱스의 객체를 제거하더라도 앞뒤 링크만 변경되고 나머지 링크는 변경되지 않는다. 객체 삽입과 삭제가 많이 일어나는 로직에서 ArrayList보다 좋은 성능을 보인다.

  • LinkedList 생성 방법

    List<E> list = new LinkedList<E>();

  • 예제

    private static void method2() { //LinkedList
      //List 인터페이스를 구현한 class: LinkedList
      //순서가 있다. 중복을 허용한다. 체인처럼 관리
      List<String> list = new LinkedList<>();
      String[] days = {"월요일", "화요일", "수요일", "목요일", "토요일", "토요일", "일요일"};
      for(String s:days) {
        list.add(s);
      }
      for(String s:list) {
        System.out.print(s + " ");
      }
    }

Set 컬렉션

List 컬렉션은 저장 순서를 유지하지만, Set 컬렉션은 저장 순서가 유지되지 않는다. 객체 중복 저장이 불가능하며 null도 하나만 저장이 가능하다. 수학으로 비유하면 집합에 가깝다.

Set 컬렉션은 HashSet, LinkedHashSet, TreeSet 등이 있다. 인덱스로 관리하지 않기 떄문에 인덱스를 매개값으로 갖는 메소드가 없다.

주요 메소드

기능메소드설명
객체 추가boolean add(E e)주어진 객체를 저장, 객체가 저장되면 true 리턴하고 중복 객체면 false리턴
객체 검색boolean contains(Object o)주어진 객체가 저장되어 있는지 여부
isEmpty()컬렉션이 비어 있는지 조사
Iterator iterator()저장된 객체를 한 번씩 가져오는 반복자 리턴
int size()저장되어있는 전체 객체 수 리턴
객체 삭제void clear()저장된 모든 객체를 삭제
boolean remove(Object o)주어진 객체를 삭제

Iterator

Set 컬렉션은 인덱스로 객체를 검색하는 메소드가 없기 때문에 전체 객체를 대상으로 한 번씩 가져오는 반복자(Iterator)를 제공한다. 반복자는 Iterator 인터페이스를 구현한 객체이다.

  • 반복자 호출 방법

    Set<String> Set = ...;
    Iterator<String> iterator = set.iterator();
  • Iterator 인터페이스 메소드

    리턴 타입메소드설명
    booleanhasNext()가져올 객체가 있으면 true, 없다면 false 리턴
    Enext()컬렉션에서 하나의 객체를 반환
    voidremove()Set 컬렉션에서 객체를 제거
  • 예제

    private static void method4() {
      String[] days = {"월요일", "화요일", "수요일", "목요일", "토요일", "토요일", "일요일"};
      //배열을 List로 변경
      List<String> list = Arrays.asList(days);
      print(list);
      System.out.println();
      System.out.println(list.contains("일요일") ? "있음" : "없음");
    
      //3.Iterator 반복자 이용해서 읽기
      Iterator<String> it = list.iterator();
      while(it.hasNext()) {
        String s = it.next();
        System.out.print(s + " ");
      }
      System.out.println("모두 읽음");
    }

    Iterator를 사용하지 않아도 확장 for문을 활용하여 전체 객체를 반복할 수 있다.

    for(String s:set) { System.out.print(s + " "); }

HashSet

HashSet은 Set 인터페이스의 구현 클래스 중 하나로, 객체들을 순서 없이 저장하고 중복 저장하지 않는다. 객체를 저장하기 전에 먼저 객체의 hashCode() 메소드를 호출하여 해시코드를 얻어내 이미 저장되어 있는 객체들과 비교하여 중복 저장여부를 판단한다.

  • HashSet 생성 방법

    Set<E> set = new HashSet<E>();

  • 문자열을 HashSet에 저장할 경우, 같은 문자열을 갖는 String 객체는 동등한 객체로 간주된다. 그 이유는 String 객체가 가지고 있는 hashCode()equals() 의 리턴값이 true 로 설정되어 있기 때문이다.

  • 예제

    private static void method7() {
      Set<CustomerDTO> data = new HashSet<>();
    
      CustomerDTO c1 = new CustomerDTO(100, "홍길동", "010-1234-5678", "서울");
      CustomerDTO c2 = new CustomerDTO(100, "홍길동", "010-4342-7812", "광주");
      System.out.println(c1.hashCode());
      System.out.println(c2.hashCode());
      System.out.println(c1.equals(c2));
      data.add(c1);
      data.add(c2);
      data.add(new CustomerDTO(300, "박길동", "010-4434-5645", "울산"));
      data.add(new CustomerDTO(400, "최길동", "010-2234-5741", "경기"));
      data.add(new CustomerDTO(200, "홍길동", "010-1234-5678", "서울"));
      //Set은 중복을 허용하지 않는데 위에 마지막 홍길동과 첫 번째 홍길동은 중복이 허용되버림
      //주소값이 다르기 때문
      //그래서 DTO에 중복체크 코드를 추가 (equals(), hashCode() Override ㄱ ㄱ)
    
      System.out.println(data);
      for(CustomerDTO c : data) {
        System.out.println(c);
      }
    }
    
    private static void method6() {
      Set<String> set = new HashSet<>();
      set.add("월요일");
      set.add("화요일");
      set.add("수요일");
      set.add("토요일");
      set.add("토요일");
      set.add("일요일");
      System.out.println(set.size() + "개"); //중복을 허용하지 않는다.
      System.out.println(set); //toString() override
    }
    
    private static void method5() { //Set
      //Set interface를 구현한 class: HashSet, TreeSet, LinkedHashSet
      //순서가 없다. 중복이 불가하다. 키로 사용된다.
      Set<String> set = new HashSet<>();
    
      set.add("월요일");
      set.add("화요일");
      set.add("토요일");
      set.add("토요일");
      set.add("일요일");
      System.out.println(set.size() + "개"); //중복을 허용하지 않는다.
    
      //1.일반 for로 읽기 ... 불가
      //2.확장 for로 읽기 ... 가능 (순서는 없음)
      for(String s:set) { System.out.print(s + " "); }
      System.out.println();
      //3.Iterator로 읽기(반복자)
      Iterator<String> iterator = set.iterator();
      while (iterator.hasNext()) {
        iterator.next();
        System.out.print(iterator.next() + " "); //읽을게 없으면 NoSuchElementException 발생
      }
    }

Map 컬렉션

Map 컬렉션은 키(Key)와 값(Value)로 구성된 Entry 객체를 저장하는 구조이다. 키와 값 모두 객체이며, 키는 중복될 수 없지만 값은 중복 저장될 수 있다. 만약 기존에 저장된 키와 동일한 키로 값을 저장하면 새로운 값으로 대치된다.

Map 컬렉션은 HashMap, HashTable, LinkedHashMap, Propertiess, TreeMap 등이 있다.

주요 메소드

기능메소드설명
객체 추가V put(K key, V value)주어진 키와 값을 추가, 저장이 되면 값을 리턴
객체 검색boolean containsKey(Object key)주어진 키가 있는지 여부
boolean containsValue(Object value)주어진 값이 있는지 여부
Ser<Map.Entry<K, V>> entrySet()키와 값의 쌍으로 구성된 모든 Map.Entry 객체를 Set에 담아서 리턴
V get(Object key주어진 키의 값을 리턴
boolean isEmpty()컬렉션이 비어있는지 여부
Set keySet()모든 키를 Set 객체에 담아서 리턴
int size()저장된 키의 총 수를 리턴
Collection values()저장된 모든 값 Collection에 담아서 리턴
객체 삭제void clear()모든 Map.Entry(키와 값)를 삭제
V remove(Object key)주어진 키와 일치하는 Map.Entry를 삭제, 삭제가 되면 값을 리턴

HashMap

HashMap은 Map 인터페이스를 구현한 대표적인 Map 컬렉션으로 HashMap의 키로 사용할 객체는 hashCode()equals() 메소드를 재정의해서 동등 객체가 될 조건을 정해야한다.

주로 키 타입은 String을 많이 사용하는데 String은 문자열이 같을 경우 동등 객체가 될 수 있도록 hashCode()equals()메소드가 재정의되어 있기 때문이다.

  • HashMap 생성 방법

    Map<String, Integer> map = new HashMap<String, Integer>();

    키와 값의 타입은 기본 타입을 사용할 수 없고 클래스 및 인터페이스 타입만 가능하다.

  • 예제

    private static void method2() {
      Map<String, Integer> map = new HashMap<>();
      map.put("홍길동", 100);
      map.put("홍길동", 90);
      map.put("박길동", 100);
      map.put("최길동", 100);
      map.put("고길동", 100);
    
      //키가있는지?
      System.out.println(map.containsKey("박길동2"));
      //값이있는지?
      System.out.println(map.containsValue(90));
    
      Collection<Integer> scores = map.values();
      int sum = 0;
      for(Integer score : scores) {
        sum+=score;
      }
      System.out.println("총점 : " + sum);
    }
    
    private static void method1() { //HashMap
      //Map interface 구현 class: HashMap, TreeMap, Properties ...
      //키와 값의 쌍 (Map.Entry)
      //키는 중복 저장될 수 없지만 값은 중복 저장 가능
      //만약 기존에 저장된 키와 동일한 키로 값을 저장하면 기존값 삭제 후 새로운 값 대체 (덮어쓰기)
      Map<String, Integer> map = new HashMap<>();
      map.put("홍길동", 100);
      map.put("홍길동", 90);
      map.put("박길동", 100);
      map.put("최길동", 100);
      map.put("고길동", 100);
      System.out.println(map);
    
      //키를 이용하여 값을 획득
      int value = map.get("홍길동");
      System.out.println("value=" + value);
    
      //모든키를 얻기
      Set<String> keys = map.keySet();
      for (String key : keys) {
        System.out.println(key + "-->" + map.get(key));
      }
    
      //Entry 얻기
      //Iterator 사용
      Set<Map.Entry<String, Integer>> entrys = map.entrySet();
      Iterator<Map.Entry<String, Integer>> iterator = entrys.iterator();
      while (iterator.hasNext()) {
        Map.Entry<String, Integer> entry = iterator.next();
        System.out.println("---------");
        System.out.println("키: " + entry.getKey());
        System.out.println("값: " + entry.getValue());
      }
      System.out.println("=============================");
      //확장 for문 사용
      for(Map.Entry<String, Integer> entry:map.entrySet()) {
        System.out.println("---------");
        System.out.println("키: " + entry.getKey());
        System.out.println("값: " + entry.getValue());
      }
    }

HashTable

HashTable은 HashMap과 동일한 내부 구조이다. 차이점은 동기화(sychronized) 메소드로 구성되어 있기 때문에 멀티 스레드 환경에서 유리하다는 점이다.

  • HashTable 생성 방법

    Map<String, Integer> map = new HashTable<String, Integer<();

  • 예제

    private static void method3() { //HashTable
      Map<Student2, CustomerDTO> map = new Hashtable<>();
      map.put(new Student2("학생1", "남", 100), 
              new CustomerDTO(1, "고객1", "02-1234-5678", "Seoul"));
      map.put(new Student2("학생2", "남", 100), 
              new CustomerDTO(2, "고객2", "02-1234-5678", "Seoul"));
      map.put(new Student2("학생3", "남", 100), 
              new CustomerDTO(3, "고객3", "02-1234-5678", "Seoul"));
    
      //제거하기 (전체)
      //map.clear();
      //제거하기 (1개)
      CustomerDTO cust = map.remove(new Student2("학생3", "남", 100));
      System.out.println(cust + " 삭제");
    
      //출력
      for(Map.Entry<Student2, CustomerDTO> entry:map.entrySet()) {
        System.out.println("key: " + entry.getKey());
        System.out.println("value: " + entry.getValue());
        System.out.println("==========================");
      }
    }

Properties

Properties는 HashTable 하위 클래스로 HashTable의 모든 특징을 갖고 있다. 차이점은 HashTable은 키와 값을 다양한 타입으로 지정이 가능한데 비해 Properties는 키와 값을 String 타입으로 제한한 컬렉션이다.

Properties는 애플리케이션의 옵션 정보, 데이터베이스 연결 정보, 다국어 정보가 저장된 프로퍼티(~.properties) 파일을 읽을 떄 주로 사용한다.

  • Properties 생성 방법

    Properties properties = new Properties();
    properties.load(new FileReader("~/경로"));
  • 예제

    package com.kosta.day14.chapter15_Collection;
    
    import java.io.FileReader;
    import java.io.IOException;
    import java.util.Map.Entry;
    import java.util.Properties;
    
    public class PropertiesExample {
        public static void main(String[] args) throws IOException {
            method1();
        }
    
        private static void method1() throws IOException {
            Properties pro = new Properties();
            String fsrc = "src/com/kosta/day14/chapter15_Collection/oracleInfo.properties";
            pro.load(new FileReader(fsrc));
            for(Entry entry:pro.entrySet()) {
                System.out.println("key: " + entry.getKey());
                System.out.println("value: " + entry.getValue());
                System.out.println("==========================");
            }
        }
    }

검색 기능을 강화시킨 컬렉션

컬렉션 프레임워크는 검색 기능을 강화시킨 TreeSet과 TreeMap이 제공된다. 각각 Set과 Map 컬렉션으로 이 컬렉션들은 이진 트리를 이용하여 계층적 구조(트리 구조)를 가진다.

TreeSet

TreeSet은 이진트리 기반으로한 Set 컬렉션으로 하나의 노드는 노드값인 value와 왼쪽과 오른쪽 자식 노드를 참조하기 위한 두 개의 변수로 구성되어있다. TreeSet에 객체를 저장하면 자동으로 정렬되고 부모값과 비교하여 낮은 것은 왼쪽 자식 노드, 높은 것은 오른쪽 자식 노드에 저장된다.

  • 주요 메소드

    리턴 타입메소드설명
    Efirst()제일 낮은 객체 리턴
    Elast()제일 높은 객체 리턴
    Elower(E e)주어진 객체보다 바로 아래 객체 리턴
    Ehigher(E e)주어진 객체보다 바로 위 객체를 리턴
    Efloor(E e)주어진 객체와 같으면 리턴, 없다면 바로 아래 객체 리턴
    Eceiling(E e)주어진 객체와 같으면 리턴, 없다면 바로 위 객체 리턴
    EpollFirst()제일 낮은 객체를 꺼내오고 컬렉션에서 제거
    EpollLast(0)제일 높은 객체를 꺼내오고 컬렉션에서 제거
  • TreeSet 생성 방법

    TreeSet<E> treeSet = new TreeSet<E>();

    String 객체를 저장하는 TreeSet 선언 방법은

    TreeSet<String> treeSet = new TreeSet<String>();

  • 예제

    package com.kosta.day14.chapter15_Collection;
    
    import com.kosta.day13.chapter15_Collection.model.CustomerDTO;
    
    import java.util.NavigableSet;
    import java.util.Set;
    import java.util.TreeSet;
    
    public class CollectionTest_TreeSet {
      public static void main(String[] args) {
        method5();
      }
    
      private static void method5() { //정렬 바꾸기
        TreeSet<CustomerDTO> data = new TreeSet<>();
        data.add(new CustomerDTO(1, "고객1", "010-111-1111", "서울"));
        data.add(new CustomerDTO(2, "고객2", "010-121-1111", "부산"));
        data.add(new CustomerDTO(3, "고객3", "010-131-1111", "서울"));
        data.add(new CustomerDTO(4, "고객4", "010-141-1111", "부산"));
        data.add(new CustomerDTO(4, "고객4", "010-141-1111", "서울"));
    
        NavigableSet<CustomerDTO> descendingSet = data.descendingSet();
        for(CustomerDTO cust:descendingSet) {
          System.out.println(cust);
        }
    
        NavigableSet<CustomerDTO> ascendingSet = descendingSet.descendingSet();
        for(CustomerDTO cust:ascendingSet) {
          System.out.println(cust);
        }
    
      }
    
      private static void method4() {
        TreeSet<CustomerDTO> data = new TreeSet<>();
        data.add(new CustomerDTO(1, "고객1", "010-111-1111", "서울"));
        data.add(new CustomerDTO(2, "고객2", "010-121-1111", "부산"));
        data.add(new CustomerDTO(3, "고객3", "010-131-1111", "서울"));
        data.add(new CustomerDTO(4, "고객4", "010-141-1111", "부산"));
        data.add(new CustomerDTO(4, "고객4", "010-141-1111", "서울"));
    
        //pollFirst: 제일 낮은 객체를 가져오고 컬렉션 제거
        while(!data.isEmpty()) {
          CustomerDTO cust = data.pollFirst();
          System.out.println(cust);
          System.out.println(data.size() + "명");
        }
    
        //pollLast: 제일 높은 객체를 가져오고 컬렉션 제거 //위에서 다 제거했으므로 의미없음
        while(!data.isEmpty()) {
          CustomerDTO cust = data.pollLast();
          System.out.println(cust);
          System.out.println(data.size() + "명");
        }
      }
    
      private static void method3() {
        TreeSet<CustomerDTO> data = new TreeSet<>();
        data.add(new CustomerDTO(1, "고객1", "010-111-1111", "서울"));
        data.add(new CustomerDTO(2, "고객2", "010-121-1111", "부산"));
        data.add(new CustomerDTO(3, "고객3", "010-131-1111", "서울"));
        data.add(new CustomerDTO(4, "고객4", "010-141-1111", "부산"));
        data.add(new CustomerDTO(4, "고객4", "010-141-1111", "서울"));
    
        //첫 번째 노드 구하기
        System.out.println("첫 번째 노드: " + data.first());
        //마지막 노드 구하기
        System.out.println("마지막 노드: " + data.last());
    
        for(CustomerDTO cust:data) {
          System.out.println(cust);
        }
      }
    
      private static void method2() {
        Set<String> data = new TreeSet<>();
        data.add("월");
        data.add("화");
        data.add("수");
        data.add("목");
        data.add("목"); //중복허용x
        for(String s:data) {
          System.out.println(s);
        }
      }
    
      private static void method1() { //TreeSet
        //TreeSet은 값을 넣을때 이진노드(binary tree)이용, 크기를 비교해서 크면 오른쪽 노드, 작으면 왼쪽 노드에 저장
        TreeSet<Integer> data = new TreeSet<>();
        data.add(100);
        data.add(50);
        data.add(70);
        data.add(30);
        data.add(30); //중복허용x
    
        //first: 첫 번째 노드 구하기
        System.out.println("첫 번째 노드: " + data.first());
        //last: 마지막 노드 구하기
        System.out.println("마지막 노드: " + data.last());
    
        //lower: 기준보다 바로 밑 노드
        System.out.println("기준보다 바로 아래의 노드: " + data.lower(50));
    
        //higher: 기준보다 바로 위 노드
        System.out.println("기준보다 바로 위의 노드: " + data.higher(new Integer(50)));
    
        //floor: 주어진 객체와 동등한 객체가 있으면 리턴, 없으면 바로 아래 리턴턴
        System.out.println("기준보다 같거나 아래의 노드: " + data.floor(50));
    
        //ceiling: 주어진 객체와 동등한 객체가 있으면 리턴, 없으면 바로 위 리
        System.out.println("기준보다 같거나 위의 노드: " + data.ceiling(50));
    
        for(Integer i:data) {
          System.out.println(i);
        }
      }
    }

TreeMap

TreeMap은 이진 트리를 기반으로 한 Map 컬렉션으로 TreeSet과 차이점은 키와 값이 저장된 Map.Entry를 저장한다는 점이다. TreeMap에 객체를 저장하면 자동으로 정렬되고 부모 키값과 비교하여 낮은 것은 왼쪽 자식 노드, 높은 것은 오른쪽 자식 노드에 Map.Entry 객체를 저장한다.

  • 주요 메소드

    리턴 타입메소드설명
    Map.Entry<K, V>firstEntry()제일 낮은 Map.Entry 리턴
    Map.Entry<K, V>lastEntry()제일 높은 Map.Entry 리턴
    Map.Entry<K, V>lowerEntry()주어진 키보다 바로 아래 Map.Entry 리턴
    Map.Entry<K, V>higherEntry()주어진 키보다 바로 위 Map.Entry 리턴
    Map.Entry<K, V>floorEntry()키와 같은 키가 있으면 Map.Entry 리턴, 없다면 바로 아래의 Map.Entry 리턴
    Map.Entry<K, V>ceilingEntry()키와 같은 키가 있으면 Map.Entry 리턴, 없다면 바로 위의 Map.Entry 리턴
    Map.Entry<K, V>pollFirstEntry()제일 낮은 Map.Entry를 꺼내오고 컬렉션에서 제거
    Map.Entry<K, V>pollLastEntry()제일 높은 Map.Entry를 꺼내오고 컬렉션에서 제거
  • TreeMap 생성 방법

    TreeMap<String, Integer> treeMap = new TreeMap<String, Integer>();

    Map 인터페이스 타입 변수에 대입해도 되지만 TreeMap 클래스 타입에 대입한 이유는 특정 객체를 찾거나 범위 검색과 관련된 메소드를 사용하기 위해서다.

  • 예제

    package com.kosta.day14.chapter15_Collection;
    
    import com.kosta.day13.chapter15_Collection.model.CustomerDTO;
    import com.sun.javaws.IconUtil;
    
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.NavigableMap;
    import java.util.TreeMap;
    
    public class CollectionTest_TreeMap {
      public static void main(String[] args) {
        method2();
      }
    
      private static void method2() {
        //HashMap: hashCode(), equals()를 구현해야 동작함
        //TreeMap: Comparable.compareTo()를 구현해야 동작
        TreeMap<Car, CustomerDTO> data = new TreeMap<>();
        data.put(new Car("ABC", 1000),
                 new CustomerDTO(1, "고객1", "010-1234-5678", "서울"));
        data.put(new Car("DDD", 5000),
                 new CustomerDTO(2, "고객2", "010-1235-5572", "부산"));
        data.put(new Car("EEE", 2000),
                 new CustomerDTO(3, "고객3", "010-4574-3412", "가산"));
        data.put(new Car("ZZZ", 7000),
                 new CustomerDTO(4, "고객4", "010-3222-8976", "나주"));
    
        {
          Entry<Car, CustomerDTO> entry = data.firstEntry();
          System.out.println("key: " + entry.getKey());
          System.out.println("Value: " + entry.getValue());
          System.out.println("==============================================");
    
          entry = data.lowerEntry(new Car("EEE모델", 2000));
          System.out.println("lower: " + entry);
    
          System.out.println("==============================================");
        }
    
        for(Entry<Car, CustomerDTO> entry:data.entrySet()) {
          System.out.println("key: " + entry.getKey());
          System.out.println("Value: " + entry.getValue());
          System.out.println("----------------------------------------------");
        }
    
        NavigableMap<Car, CustomerDTO> desc = data.descendingMap();
        for(Entry<Car, CustomerDTO> entry:desc.entrySet()) {
          System.out.println("key: " + entry.getKey());
          System.out.println("Value: " + entry.getValue());
          System.out.println("----------------------------------------------");
        }
      }
    
      private static void method1() {
        Map<String, Integer> data = new TreeMap<>();
        data.put("홍길동", 100);
        data.put("고길동", 90);
        data.put("김길동", 80);
        data.put("최길동", 70);
        data.put("박길동", 60);
    
        for(Map.Entry<String, Integer> entry:data.entrySet()) {
          System.out.println("key: " + entry.getKey());
          System.out.println("value: " + entry.getValue());
          System.out.println("==========================");
        }
      }
    }

Comparable과 Comparator

profile
라면 먹고 싶다. 두 개 끓여서 혼자 먹고 싶다. - 임덕배 (1997. 06 ~ )

0개의 댓글