15. 컬렉션
15.6 LIFO와 FIFO 컬렉션
후입선출(LIFO Last In First Out)은 나중에 넣은 객체가 빠져나가고
선입선출(FIFO First In First Out)은 먼저 넣은 객체가 빠져나가는 구조이다.
컬렉션 프레임 워크는 LIFO자료구조를 제공하는 스택(Stack) 클래스와
FIFO자료구조를 제공하는 큐(Queue) 인터페이스를 제공한다.
스택을 응용한 대표적인 예는 JVM의 스택메모리이고 큐를 응용한 것은 스레드풀의 작업큐이다.
15.6.1 Stack
Stack는 LIFO 자료구조를 구현한 클래스이다.
Stack<E> stack = new Stack<>();
리턴타입| 메소드 | 설명
E | push(E item) | 주어진객체를 스택에 넣음
E | pop() | 스택의 맨위 객체를 빼낸다.
public class StackExample {
public static void main(String[] args) {
//Stack 컬렉션 생성
Stack<Coin> coinBox = new Stack<>();
//동전넣기
coinBox.push(new Coin(100));
coinBox.push(new Coin(50));
coinBox.push(new Coin(500));
coinBox.push(new Coin(10));
//동전 하나씩 꺼내기
while (coinBox.isEmpty()) {
Coin coin = coinBox.pop();
System.out.println("꺼내온 동전: " + coin.getValue() + "원");
}
}
}꺼내온 동전: 10원
꺼내온 동전: 500원
꺼내온 동전: 50원
꺼내온 동전: 100원
15.6.2 Queue
Queue는 FIFO자료구조이다.
Queue 인터페이스를 구현한 대표적인 클래스는 LinkedList이다.
그래서 LinkedList객체를 Queue인터페이스 변수에 대입할 수 있다.
Queue<E> queue = new LinkedList<>();
리턴타입 | 메소드 | 설명
boolean | offter(E e) | 주어진객체를 큐에 넣음
E | poll() | 큐에서 객체를 빼낸다.
public class QueueExample {
public static void main(String[] args) {
// Queue 컬렉션 생성
// Queue<Message> messageQueue1 = new Queue<>(); 없음
Queue<Message> messageQueue = new LinkedList<>();
// 메시지 넣기
messageQueue.offer(new Message("sendMail", "홍길동"));
messageQueue.offer(new Message("sendSMS", "신용권"));
messageQueue.offer(new Message("sendKakatalk", "김자바"));
// 메시지를 하나식 꺼내어 처리
while (!messageQueue.isEmpty()) {
Message message = messageQueue.poll();
switch (message.command) {
case "sendMail":
System.out.println(message.to + "님에게 메일을 보냅니다.");
break;
case "sendSMS":
System.out.println(message.to + "님에게 SMS을 보냅니다.");
break;
case "sendKakatalk":
System.out.println(message.to + "님에게 카카오톡을 보냅니다.");
break;
}
}
}
}15.7 동기화된 컬렉션
컬렉션 프레임워크의 대부분클래스들은 싱글 스레드 환경에서 사용할수 있도록 설계되었다.
그래서 여러 스레드가 동시에 컬렉션에 접근하면 의도하지 앟ㄴ게 요소가 변경될 수 있다.
Vector과 Hashtable은 동기화된 메소드로 구성되어 있어 멀티스레드 환경에서 안전하게 요소를 처리할 수 있다.
하지만 다른 컬렉션들은 안전하지 않다.
경우에 따라서 ArrayList, HashSet, HashMap을 멀티스레드 환경에서 사용하고 싶을때가 있다.
이럴때 대비해서 컬렉션 프레임워크는 비동기화된 메소드를 동기화된 메소드로 래핑하는 Collections의 synchronizedXXX()메소드를 제공한다.
리턴타입 |메소드 |설명
List<T> |sysnchronizxedList(List<T> list) |List를 동기화된 List로 리턴
Map<K,V> |sysnchronizxedMap(Map<K,V> m) |Map을 동기화된 Map으로 리턴
Set<T> |sysnchronizxedSet(Set<T> s) |Set을 동기화된 Set으로 리턴
List<T> list = sysnchronizxedList(new ArrayList<T>());
public class SynchronizedMapExample {
public static void main(String[] args) {
// Map 컬렉션 생성
Map<Integer, String> map = Collections.synchronizedMap(new HashMap<>());
//작업스레드 객체 생성
Thread threadA = new Thread() {
@Override
public void run() {
//객체 100개추가
for(int i=1 ; i <=1000 ; i++) {
map.put(i, "내용"+i);
}
}
};
//작업스레드 객체 생성
Thread threadB = new Thread() {
@Override
public void run() {
//객체 100개추가
for(int i=1001 ; i <=2000 ; i++) {
map.put(i, "내용"+i);
}
}
};
//작업스레드 시행
threadA.start();
threadB.start();
//작업스레드들이 모두 종료될때까지 메인 스레드를 기다리게함.
try {
threadA.join();
threadB.join();
} catch (Exception e) {}
//저장된 총객체수 읽기
int size = map.size();
System.out.println("총 객체 수:" + size); //2000
}
}15.8 수정할 수 없는 컬렉션
수정할수 없는(unmodifiable) 컬렉션이란 요소를 추가 삭제할 수 없는 컬렉션을 말한다.
컬렉션 생성시 저장된 요소를 변경하고 싶지 않을때 유용하다.
먼저 첫번째 방법으로는 List, Set, Map인터페이스의 정적메소드인 of()로 생성가능하다.
두번째 방법은 List, Set,Map 인터페이스의 정적메소드인 copyOf()를 이용해 기존 컬렉션을 복사하여 수정할 수 없는 컬렉션을 만드는 것이다.
세번째 방법은 배열로부터 수정할 수 없는 List컬렉션을 만들 수 있다.
public class ImmutableExample {
public static void main(String[] args) {
// of() 메소드 사용
// List
List<String> immutabeList1 = List.of("A", "B", "C");
// immutabeList1.add("D") 불가능
// Set
Set<String> immutabeSet1 = Set.of("A", "B", "C");
// immutabeSet1.remove("A") 불가능
// Map
Map<Integer, String> immutabeMap1 = Map.of(
1, "A",
2, "B",
3, "C");
// immutabeMap1.put(4, "D") 불가능
// 불변컬렉션으로 복사
// List
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
List<String> immutabeList2 = List.copyOf(list);
// Set
Set<String> set = new HashSet<>();
set.add("A");
set.add("B");
set.add("C");
Set<String> immutabeSet2 = Set.copyOf(set);
// Map
Map<Integer, String> map = new HashMap<>();
map.put(1, "A");
map.put(2, "B");
map.put(3, "C");
Map<Integer, String> immutabeMap2 = Map.copyOf(map);
// 배열로브터 List불변컬렉션생성
String[] arr = { "A", "B", "C" };
List<String> immutabeList3 = Arrays.asList(arr);
}
}15.9 확인문제
15.9.1 7번
BoardDao 객체의 getBoardList메소드호출하면 컬렉션리턴한다.
public class ListExample {
public static void main(String[] args) {
BoardDao dao = new BoardDao();
List<Board> list = dao.getBoardLIst();
for (Board board : list) {
System.out.println(board.getTitle() + "-" + board.getContent());
}
}
}public class BoardDao {
public List<Board> getBoardLIst() {
List<Board> list = new LinkedList<>();
list.add(new Board("제목1","내용1"));
list.add(new Board("제목2","내용2"));
list.add(new Board("제목3","내용3"));
return list;
}
}15.9.2 8번
번호가 같으면 같은 학생으로 처리하기 equals와 hashcode재정의
public class Student {
public int studentNum;
public String name;
public Student(int studentNum, String name) {
this.studentNum = studentNum;
this.name = name;
}
//여기에 코드를 작성하세요
@Override
public int hashCode() {
return studentNum;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Student)) return false;
Student student = (Student) obj;
if (studentNum != student.studentNum) return false;
return true;
}
}public class HashSetExample {
public static void main(String[] args) {
Set<Student> set = new HashSet<>();
set.add(new Student(1, "홍길동"));
set.add(new Student(2, "신용권"));
set.add(new Student(1, "조민우"));
System.out.println("저장된 객체 수: " + set.size());//2
}
}15.9.3 9번
평균점수, 최고점수, 최고점수를 받은 아이디
public class MapExample {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("blue", 96);
map.put("hong", 86);
map.put("white", 92);
String name = null;
int maxScore = 0;
int totalScore = 0;
//여기에 코드를 작성하시오
Set<Entry<String, Integer>> entrySet = map.entrySet();
for (Entry<String, Integer> entry : entrySet) {
if (entry.getValue() > maxScore) {
maxScore = entry.getValue();
name = entry.getKey();
}
totalScore += entry.getValue();
}
int avg = totalScore / map.size();
System.out.println("평균점수: " + avg);
System.out.println("최고점수: " + maxScore);
System.out.println("최고점수를 받은 아이디: " + name);
}
}15.9.4 10번
정렬하기
public class Student implements Comparable<Student> {
public String id;
public int score;
public Student(String id, int score) {
this.id = id;
this.score = score;
}
@Override
public int compareTo(Student o) {
if (score < o.score) {
return -1;
} else if (score == o.score) {
return 0;
} else {
return 1;
}
}
}16. 람다식
16.1 람다식이란?
함수형 프로그래밍이란 함수를 정의하고 이 함수를 데이터 처리부로 보내 데이터를 처리하는 기법을 말한다.
데이터 처리부는 데이터만 가지고 이을 뿐 처리방법이 정해져있지 않아 외부에서 제공된 함수에 의존한다.
데이터 처리부는 제공된 함수의 입력값으로 데이터를 넣고 함수에 정의된 처리 내용을 실행한다.
동일한 데이터라도 함수마다 다른 처리결과를 낳을 수 있다. 이것이 함수형 프로그래밍의 특징으로 데이터 처리의 다형성이라고도 볼 수 잇다.
자바는 함수형 프로그래밍을 위해 자바 8부터 람다식을 지원한다. 람다식은 위 그림과 같이 데이터 처리부에 제공되는 함수 역할을 하는 매개변수를 가진 중괄화 블록이다.
람다식 : {매개변수...} -> {처리내용}
자바는 람다식을 익명 구현객체로 변환한다.
예를들어 Calcuable이라는 인터페이스가 있다고 가정해보자.
이 인터페이스의 익명 구현객체는 다음처럼 구현될 수있다.
new Calcuable() {
@Override
public void calculate(int x, int y) {처리내용}
};
이것을 람다식으로 표현하면 (x,y) -> {처리내용}이 된다.
람다식은 인터페이스의 익명 구현객체이므로 인터페이스 타입의 매개변수에 대입될 수 있다.
Calcuable매개변수를 가지고 있는 action()메ㅗ드가 있다고 가정해보자
public void action(Calcuable calcuable){
int x = 10;
int y = 4;
calcuable.calculate(x,y); //데이터를 제공하고 추상메소드를 호출
}
action()메소드를 호출할때 매개값으로 람다식을 제공할 수 있다.
action()메소드에서 calcuable.calculate(x,y) 메소드를 실행하면 람다식의 중괄호 브록이 실행되면서 데이터가 처리된다.
여기서 action()메소드는 제공된 람다식을 이용해서 내부 데이터를 처리하는 처리부 역할을 한다.
action(x,y) ->{
int result = x+y;
System.out.println(result);
});
인터페이스의 익명 구현객체를 람다식으로 표현하려면 인터페이스가 단 하나의 추상 메소드를 가져야 한다.
단 하나의 추상메소드를 가질때 이 인터페이스를 함수형 인터페이스라고 한다.
인터페이스가 함수형 인터페이스 임을 보장하기 위해서는 @FunctionalInterface 어노테이션을 붙이면 된다.
선택사항이지만 추상메소드가 하나인지 검사하기 때문에 정확한 함수형 인터페이스를 작성하게 도와주는 역할을 한다.
@FunctionalInterface
public interface Calculable {
//추상메소드
void calculate(int x , int y);
}public class LambdaExample {
public static void main(String[] args) {
action((x, y) -> {
int result = x + y;
System.out.println(result);
});
action((x, y) -> {
int result = x - y;
System.out.println(result);
});
}
public static void action(Calculable calculable) {
// 데이터
int x = 10;
int y = 4;
// 데이터처리
calculable.calculate(x, y);
}
}16.2 매개변수가 없는 람다식
함수형 인터페이스의 추상메소드에 매개변수가 없을 경우람다식은 다음과 같이 작성할 수 있다.
() -> {
실행문;
실행문;
}
() -> 실행문;
실행문이 두개 이상일 경우에는 중괄호를 생략할 수 엇ㅂ고 하나일 경우에는 생략 가능하다.
@FunctionalInterface
public interface Workable {
void work();
}public class Person {
public void action(Workable workable) {
workable.work();
}
}public class LambdaExample {
public static void main(String[] args) {
Person person = new Person();
//실행문 두개이상인 경우 중괄호 필요
person.action(() -> {
System.out.println("출근을 합니다.");
System.out.println("프로그래밍을 합니다.");
});
//실행문이 하나일 경우
person.action(() -> System.out.println("퇴근합니다."));
}
}9.6에서 실습한 익명 구현객체 예제를 수정해보자.
Ok버튼 객체에 ClickListener 익명구현객체 주입
btnOk.setClickListener(new Button.ClickListener() {
@Override
public void onClick() {
System.out.println("Ok 버튼을 클릭했습니다.");
}
});btnOk.setClickListener(() -> {
System.out.println("Ok 버튼을 클릭했습니다.");
});스레드 생성할때 new Runnable 익명구현객체를 넣었다.
이것도 이런식으로 변경이 가능해보인다.
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("스레드생성");
}
});Thread thread2 = new Thread(() -> {
System.out.println("스레드생성");
});16.3 매개변수가 있는 람다식
함수형 인터페이스의 추상메소드에 매개변수가 있을 경우 람다식은 다음과 같이 작성할 수있다.
(타입 매개변수) -> {실행문}
타입 대신 var을 사용할 수도 잇다. 하지만 생략을 하는 것이 일반적이다.
매개변수가 하나일때는 괄호를 생략할 수도 있다.
@FunctionalInterface
public interface Workable {
void work(String name, String job);
}@FunctionalInterface
public interface Speakable {
void speak(String content);
}public class Person {
public void action1(Workable workable) {
workable.work("홍길동", "프로그래밍");
}
public void action2(Speakable speakable) {
speakable.speak("안녕하세요");
}
}public class LambdaExample {
public static void main(String[] args) {
Person person = new Person();
//매개변수가 두개일 경우
person.action1((name, job)->{
System.out.print(name +"이");
System.out.println(job +"을 합니다.");
});
person.action1((name, job)->{System.out.println(name
+ "이"
+ job
+ "을 하지 않습니다.");
});
//매개변수가 한개
person.action2((word)-> {
System.out.print("\"" + word + "\"");
System.out.println("라고 말합니다.");
});
person.action2(word -> System.out.println(word + "라고 말합니다"));
}
}16.4 리턴값이 있는 람다식
함수형 인터페이스의 추상메소드에 리턴값이 있을 경우 람다식은 다음과 같이 작성할 수 있다.
(매개변수) -> {
실행문;
return 값;
}
return문 하나만 잇을경우 중괄호와 함께 return 키워드를 생략할 수 있다.
@FunctionalInterface
public interface Calcuable {
double calc(double x, double y);
}public class Person {
public void action(Calcuable calcuable) {
double result = calcuable.calc(10, 4);
System.out.println("결과: " + result);
}
}public class LambdaExample {
public static void main(String[] args) {
Person person = new Person();
// 실행문이 두개 이상
person.action((x, y) -> {
double result = x + y;
return result;
});
// 리턴문이 하나만 잇을경우(연산식)
person.action((x, y) -> {
return x + y;
});
person.action((x, y) -> (x + y));
// 리턴문이 하나만 잇을경우 메소드 호출
person.action((x, y) -> {
return sum(x, y);
});
person.action((x, y) -> sum(x, y));
}
public static double sum(double x, double y) {
return (x + y);
}
}16.5 메소드 참조
메소드 참조는 말그대로 메소드를 참조해서 매개변수의 정보 및 리턴 타입을 알아내 람다식에서 불필요한 매개변수를 제거하는 것을 목적으로 한다.
예를 들어 두개의 값을 받아 큰 수를 리턴하는 Math클래스의 max()정적 메소드를 호출하는 람다식은 다음과 같다.
(left, right) -> Math.max(left, right);
람다식은 단순히 두개의 값을 Math.max() 메소드의 매개값으로 전달하는 역할하기때문에 다소 불편해보인다.
이경우 메소드 참조를 이용하면 깔끔하게 처리가능하다.
Math :: max;
16.5.1 정적메소드와 인스턴스 메소드 참조
정적 메소드를 참조할 경우에는 클래스 이름 뒤에 :: 기호를 붙이고 ㅈ어적 메소드 이름을 기술한다.
클래스 :: 메소드
인스턴스 메소드일 경우 먼저 객체를 생성한다음 참조변수뒤에 :: 기호를 붙이고 인스턴스 이름을 기술한다.
@FunctionalInterface
public interface Calcuable {
double calc(double x, double y);
} public class Person {
public void action(Calcuable calcuable) {
double result = calcuable.calc(10, 4);
System.out.println("결과: " + result);
}
}public class Computer {
public static double staticMethod(double x, double y) {
return x + y;
}
public double instanceMethod(double x, double y) {
return x + y;
}
}public class MethodReferenceExample {
public static void main(String[] args) {
Person person = new Person();
//정적메소드일경우
//람다식
person.action((x,y) -> Computer.staticMethod(x, y));
//메소드참조
person.action(Computer :: staticMethod);
//인스턴스메소드
Computer com = new Computer();
//람다식
person.action((x,y) -> com.instanceMethod(x, y));
//메소드참조
person.action(com :: instanceMethod);
}
}16.5.2 매개변수의 메소드 참조
람다식에서 제공되는 a 매개변수의 메소드를 호출해서 b 매개변수를 매개값으로 사용하는 경우도있다.
(a,b) -> {a.instaceMethod(b)}
이것을 메소드참조로 표현하면 클래스이름 뒤에 :: 기호룰 붙이고 메소드이름을 기술한다.
정적메소드 참조와 동일하지만 a의 인스턴스 메소드가 사용된다는 점에서 다르다.
@FunctionalInterface
public interface Comparable {
int compare(String a, String b);
}public class Person {
public void ordering(Comparable comparable) {
String a = "홍길동";
String b = "김길동";
int result = comparable.compare(a, b);
if (result <0 ) {
System.out.println(a + "은 " + b + "보다 앞에 옵니다");
} else if(result == 0) {
System.out.println(a + "은 " + b + "와 같습니다.");
} else {
System.out.println(a + "은 " + b + "보다 뒤에 옵니다");
}
}
}public class MethodReferecneExample {
public static void main(String[] args) {
Person person = new Person();
person.ordering(String :: compareToIgnoreCase);
}
}16.6 생성자 참조
생성자를 참조한다는 것은 객체를 생성하는 것을 말한다.
람다식이 단순히 객체를 생성하고 리턴하도록 구성된다면 람다식을 생성자 참조로 대치할 수 있다.
(a,b) -> {return new 클래스(a,b)}
이것을 생성자 참조로 표현하면 클래스 이름 뒤에 :: 붙이고 new연산자를 기술하면된다.
클래스 :: new
생성자가 오버로딩되어 여러개 잇을경우 컴파일러는 함수형 인터페이스의 추상메소드와 동일한 매개변수 타입과 개수를 가진 생성자를 찾아 실행한다.
존재하지 않으면 컴파일 오류가 발생한다.
@FunctionalInterface
public interface Createable1 {
public Member create(String id);
}@FunctionalInterface
public interface Createable2 {
public Member create(String id, String name);
}public class Person {
public Member getMember1(Createable1 creatable) {
String id = "winter";
Member member = creatable.create(id);
return member;
}
public Member getMember2(Createable2 creatable) {
String id = "winter";
String name = "한겨울";
Member member = creatable.create(id, name);
return member;
}
}public class ConstructorRefereneExample {
public static void main(String[] args) {
Person person = new Person();
Member m1 = person.getMember1(Member :: new);
System.out.println(m1); //{id: winter, name:null }
System.out.println();
Member m2 = person.getMember2(Member :: new);
System.out.println(m2); //{id: winter, name:한겨울 }
}
}2023.02.24
메소드 참조부분은 다시봐도 이해가 잘 안된다. 물론 헷갈린다면 사용하지 않아도 된다곤 하지만 이해를 해놔야 남의 코드를 해석할 수 잇다.
학원에서 강의할때 질문을 많이 해야할 것 같다.
자주 사용하지 않아 까먹는다 기억상으로 스트림을 사용할때 더 쉽게 이해가 됫 던 것 같다.
스레드, 스레드풀 runnalbe객체를 람다식으로 구현하면 코드가 가벼워진다는 것을 알았다.
람다식을 먼저 공부하고 앞을 이어서 공부햇으면 좋을 것 같다.
'기초단계 > JAVA' 카테고리의 다른 글
| 2023.02.27 Java복습 (0) | 2023.02.27 |
|---|---|
| 2023.02.26 Java복습 (0) | 2023.02.27 |
| 2023.02.23 Java복습 (0) | 2023.02.23 |
| 2023.02.21-2 Java복습 (0) | 2023.02.21 |
| 2023.02.21-1 Java복습 (0) | 2023.02.21 |