ArrayList
는 List
인터페이스를 구현하고, List
는 Collection
인터페이스를 확장합니다. 만약 코드에서 ArrayList
, List
, Collection
을 모두 사용한다면, 각 타입을 개별적으로 import해야 합니다. 다음은 예시입니다:java코드 복사
import java.util.ArrayList; // ArrayList를 사용하기 위한 import
import java.util.List; // List를 사용하기 위한 import
import java.util.Collection; // Collection을 사용하기 위한 import
public class CollectionExample {
public static void main(String[] args) {
// ArrayList 생성
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("Apple");
arrayList.add("Banana");
// List로서 ArrayList를 참조
List<String> list = arrayList;
list.add("Cherry");
// Collection으로서 List를 참조
Collection<String> collection = list;
collection.add("Date");
// 모든 요소 출력
for (String fruit : collection) {
System.out.println(fruit);
}
}
}
이 예제에서 ArrayList
, List
, Collection
타입을 모두 사용하고 있으므로, 각각을 import했습니다. 각 타입을 사용하기 위해 별도로 import해야 합니다. 즉, 상속 관계에 있는 타입을 모두 사용하려면 필요한 타입마다 각각 import 구문을 추가해야 합니다. 다만, 해당 타입의 하위 클래스만 사용한다면 하위 클래스만 import하면 됩니다. 예를 들어, ArrayList
만 사용한다면 ArrayList
만 import하면 됩니다:java코드 복사
import java.util.ArrayList;
public class SimpleArrayListExample {
public static void main(String[] args) {
// ArrayList 생성
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("Apple");
arrayList.add("Banana");
// 모든 요소 출력
for (String fruit : arrayList) {
System.out.println(fruit);
}
}
}
이 경우 ArrayList
만 import하면 됩니다. 4o주의! JDK 1.5부터 자동 박싱/언박싱으로 기본 타입 값을 객체로 자동 변환
예시
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
int primitiveValue = 10;
// 자동 박싱
list.add(primitiveValue);
// 리스트에서 값 가져오기 (자동 언박싱)
int result = list.get(0);
System.out.println("리스트의 첫 번째 값: " + result);
}
}
객체들의 컨테이너라고도 불림
고정 크기의 배열을 다루는 어려움 해소
다양한 객체들의 삽입, 삭제, 검색등의 관리 용이
개념적으로 Collection과 Map 모두 Collection이다.
Stack은 Collection과 List를 구현하고 있고, Vector를 상속함을 알 수 있다.
public void printVector(Vector v)
컴파일러의 타입 추론 기능 추가
<>에 타입 매개변수 생략
주의! 심지어 <>도 생략 가능하다!
vat 키워드 도입, 컴파일러의 지역 변수 타입 추론 가능
Vector의 특성
Vector 컬렉션 내부 구성
Vector v =new Vector();
주의!
그냥 add로 삽입하면 맨 뒤에 삽입하게 된다!
자동 박싱과 언박싱이 이루어져서 기본 자료형도 삽입 및 검색이 된다.
타입 매개 변수 사용하지 않는 경우 경고 발생! import도 해주어야한다!
증요 Set 컬렉션도 알아두기, Set은 중복된 것은 제외시켜서 저장한다. 순서 및 인덱스의 개념을 가지고 있지 않다!
중요 Vector 클래스의 주요 메소드
중요 addElement, elementAt, removeElement, first/lastElement 등의 함수는 벡터에서만 사용하는 레거시 함수이다. 이전 버전과의 호환성을 위해서 여전히 사용하지만, 상위 인터페이스 List등에 의해 똑같은 기능을 하는 함수 add, get, remove등이 있다.
boolean add(E element) 함수는 벡터의 맨뒤에 요소를 추가하고 ture를 리턴한다. 대부분의 컬렉션 함수에서 동일하게 작동한다.
void add(int index, E element) 함수는 벡터의 인덱스 자리에 요소를 추가한다. 원래 그 인덱스에 있던 요소는 한칸 뒤로 밀린다.
boolean remove(Object o) 함수는 객체를 검색하여 있으면 가장 낮은 인덱스에 있는 객체를 삭제하고 true를 리턴, 없으면 false를 리턴함, 주의! remove시 Object는 equals가 정의되어있어야 올바른 검색이 가능함
E remove(int index) 함수는 인덱스에 객체를 삭제하고 그 객체를 리턴함, 인덱스가 size를 벗어나면 예외를 throw함 → 주의! 기본적으로 int형 인덱스를 받기 때문에 int값을 넣어주면 인덱스로 인식한다. Integer 등으로 자동 박싱되지 않는다!! Integer 형을 넣어줘야 객체를 삭제하는 remove함수 실행
주의! 기본적으로 capacity가 아닌 size를 인덱스가 벗어나면 런타임 오류이고 예외를 던진다! ArrayIndexOutOfBoundsException!
마지막 요소를 lastElement() 함수로 나타낼 수 있다.
removeAllElements() 함수로 모든 요소를 삭제할 수 있다.
정수만 다루는 Vector 컬렉션 활용
import java.util.Vector;
public class VectorEx {
public static void main(String[] args) {
Vector<Integer> v = new Vector();
v.add(5);
v.add(4);
v.add(-1);
v.add(2,100);
System.out.println("벡터 내의 요소 객체 수: "+v.size());
System.out.println("벡터의 현재 용량 : "+v.capacity());
for(int i=0;i<v.size();i++){
int n = v.get(i);
System.out.println(n);
}
int sum=0;
for(int i=0;i<v.size();i++){
int n = v.elementAt(i);
sum+=n;
}
System.out.println("벡터에 있는 정수 합: "+sum);
}
}
출력 결과:
벡터 내의 요소 객체 수: 4
벡터의 현재 용량 : 10
5
4
100
-1
벡터에 있는 정수 합: 108
Point 클래스만 다루는 Vector 컬렉션 활용 예시
import java.util.Vector;
class Point {
private int x,y;
public Point(int x, int y){
this.x = x;
this.y = y;
}
public String toString(){
return "("+x+","+y+")";
}
}
public class PointVectorEx {
public static void main(String[] args) {
Vector<Point> v=new Vector<>();
v.add(new Point(2,3));
v.add(new Point(-5,20));
v.add(new Point(30,-8));
v.remove(1);
for(int i=0;i<v.size();i++){
Point p = v.get(i);
System.out.println(p);
}
}
}
출력 결과:
(2,3)
(30,-8)
ArrayList의 특성
Array 컬렉션의 내부 구성
ArrayList al = new ArrayList();
Vector와 Element 메소드들 제외하고 동일한 메소드를 가지고 있음
주의! Vector와 다르게 capacity 메소드는 제공하지 않는다
주의! Vector와 다르게 removeAllElement등의 함수를 제공하지 않는다!
문자열 입력받아 ArrayList에 저장하는 예시
import java.util.*;
public class ArrayListEx {
public static void main(String[] args) {
ArrayList<String> a = new ArrayList();
Scanner sc = new Scanner(System.in);
for(int i=0;i<4;i++){
System.out.print("이름을 입력하세요>>");
String s = sc.next();
a.add(s);
}
for(int i=0;i<a.size();i++){
String name = a.get(i);
System.out.print(name+" ");
}
int longestIndex = 0;
for(int i=0;i<a.size(); i++){
if(a.get(longestIndex).length() < a.get(i).length())
longestIndex=i;
}
System.out.println("\n가장 긴 이름은 :"+a.get(longestIndex));
sc.close();
}
}
출력 결과:
이름을 입력하세요>>Mike
이름을 입력하세요>>Jane
이름을 입력하세요>>Ashley
이름을 입력하세요>>Helen
Mike Jane Ashley Helen
가장 긴 이름은 :Ashley
HashMap<K,V>
HashMap<String, String>의 내부 구성
HashMap<String,String> map=new HashMap<String,String>();
키 값은 중복되지 않고 put메소드에서 이미 키값이 존재한다면, 값의 갱신을 해준다!
HashMap<K,V>의 주요 메소드
중요 원래 HashMap은 Iterable하지 않은데, Set keySet() 메소드를 활용해서 키값을 Set으로 받은 후에 키값을 통한 값의 접근으로 Iterator를 사용해줄 수 있다!
중요 remove시에 키 값을 검색하고 있으면 값과 함께 삭제한다! 그 문장은 삭제한 매핑의 값을 리턴한다. 키 값이 없다면 null을 리턴한다.
HashMap을 이용하여 (영어,한글) 단어 쌍의 저장 검색 예시
import java.util.*;
public class HashMapDIcEx {
public static void main(String[] args) {
HashMap<String,String> dic = new HashMap<String,String>();
dic.put("baby","아기");
dic.put("love","사랑");
dic.put("apple","사과");
Scanner sc=new Scanner(System.in);
while(true){
System.out.print("찾고 싶은 단어는? ");
String eng = sc.next();
if(eng.equals("exit")){
System.out.println("종료합니다..");
break;
}
String kor = dic.get(eng);
if(kor==null)
System.out.println(eng+"는 없는 단어입니다.");
else
System.out.println(kor);
}
sc.close();
}
}
출력 결과:
찾고 싶은 단어는? apple
사과
찾고 싶은 단어는? aodj
aodj는 없는 단어입니다.
찾고 싶은 단어는? exit
종료합니다..
HashMap을 이용하여 자바 과목의 이름과 점수 관리
import java.util.*;
public class HashMapScoreEx {
public static void main(String[] args) {
HashMap<String,Integer> javaScore = new HashMap<String,Integer>();
javaScore.put("김성동",97);
javaScore.put("황기태",88);
javaScore.put("김남윤",98);
javaScore.put("이재문",70);
javaScore.put("한원선",99);
System.out.println("HashMap의 요소 개수 :"+javaScore.size());
Set<String> keys = javaScore.keySet();
Iterator<String> it = keys.iterator();
while(it.hasNext()){
String name = it.next();
int score = javaScore.get(name);
System.out.println(name+" : "+score);
}
}
}
출력 결과:
HashMap의 요소 개수 :5
이재문 : 70
한원선 : 99
김남윤 : 98
김성동 : 97
황기태 : 88
HashMap에 객체 저장, 학생 정보 관리
import java.util.*;
class Student {
int id;
String tel;
public Student(int id, String tel){
this.id=id;
this.tel=tel;
}
int getId(){
return id;
}
String getTel(){
return tel;
}
}
public class HashMapStudenEx {
public static void main(String[] args) {
HashMap<String, Student> map = new HashMap<String, Student>();
map.put("김성동",new Student(1,"010-111-1111"));
map.put("황기태",new Student(2,"010-222-2222"));
map.put("김남윤",new Student(3,"010-333-3333"));
Scanner sc= new Scanner(System.in);
while(true){
System.out.print("검색할 이름? ");
String name = sc.next();
if(name.equals("exit"))
break;
Student student = map.get(name);
if(student==null)
System.out.println(name+"은 없는 사람입니다.");
else
System.out.println("id: "+student.getId()+", 전화: "+student.getTel());
}
sc.close();
}
}
출력 결과:
검색할 이름? 김남윤
id: 3, 전화: 010-333-3333
검색할 이름? 이재문
이재문은 없는 사람입니다.
검색할 이름? exit
HashMap
클래스의 내부 구현에서 특정 경우에는 입력된 순서대로 요소를 반환할 수 있습니다. 이는 Java 8부터 HashMap
클래스가 키-값 쌍을 저장하는 방식이 변경되어, 내부적으로 링크드 리스트를 사용하여 요소들을 연결하기 때문입니다. 이를 "bucket chaining"이라고 합니다. 링크드 리스트를 사용하면 동일한 해시 값(버킷)을 가지는 요소들이 연결 리스트로 연결되어 저장됩니다. 따라서 동일한 버킷에 속한 요소들의 순서는 입력된 순서와 동일합니다. 이런 방식으로 요소들이 저장되면서 순서가 보장되므로, keySet()
메소드로 얻은 Set
이나 entrySet()
메소드로 얻은 Set
을 순회할 때 요소들이 입력된 순서대로 반환될 수 있습니다. 따라서 Java의 구현체에 따라서는 HashMap
의 순서가 보존될 수 있지만, 이것은 명시된 보장된 동작이 아닙니다. 따라서 순서가 중요한 경우에는 명시적으로 순서를 유지하는 LinkedHashMap
을 사용하는 것이 좋습니다.Iterable
과 Iterator
는 상속 관계가 있는 것이 아니라, 서로 다른 인터페이스입니다. Iterable
은 Iterator
를 반환하는 메소드를 정의하고, Iterator
는 컬렉션을 순회하는 메소드를 정의합니다. 이를 통해 두 인터페이스는 함께 작동하며, 특정한 상속 관계 없이 서로를 호출하는 형태로 동작합니다.Iterable
인터페이스:Iterable
인터페이스는 하나의 메소드 iterator()
를 정의합니다.Iterator
객체를 반환하여, 컬렉션을 순회할 수 있게 합니다.Iterator
인터페이스:Iterator
인터페이스는 컬렉션의 요소를 순회하는 데 필요한 메소드(hasNext()
, next()
, remove()
)를 정의합니다.Iterator를 이용하여 Vector의 모든 요소를 출력하고 합 구하는 예시
import java.util.Vector;
import java.util.Iterator;
public class IteratorEx {
public static void main(String[] args) {
Vector<Integer> v = new Vector();
v.add(5);
v.add(4);
v.add(-1);
v.add(2,100);
Iterator<Integer> it = v.iterator();
while(it.hasNext()){
int n = it.next();
System.out.println(n);
}
int sum=0;
it = v.iterator();
while(it.hasNext()){
int n = it.next();
sum += n;
}
System.out.println("벡터에 있는 정수 합: "+sum);
}
}
출력 결과:
5
4
100
-1
벡터에 있는 정수 합: 108
Collections 클래스의 활용 예시
import java.util.*;
public class CollectionsEx {
static void printList(LinkedList<String> l){
Iterator<String> iterator = l.iterator();
while(iterator.hasNext()) {
String e = iterator.next();
String separator;
if (iterator.hasNext())
separator = "->";
else
separator = "\n";
System.out.print(e + separator);
}
}
public static void main(String[] args) {
LinkedList<String> myList = new LinkedList<String>();
myList.add("트랜스포머");
myList.add("스타워즈");
myList.add("매트릭스");
myList.add(0,"터미네이터");
myList.add(2,"아바타");
printList(myList);
Collections.sort(myList);
printList(myList);
Collections.reverse(myList);
printList(myList);
int index = Collections.binarySearch(myList, "아바타")+1;
System.out.println("아바타는 "+index+"번째 요소입니다.");
}
}
출력 결과:
터미네이터->트랜스포머->아바타->스타워즈->매트릭스
매트릭스->스타워즈->아바타->터미네이터->트랜스포머
트랜스포머->터미네이터->아바타->스타워즈->매트릭스
아바타는 3번째 요소입니다.
Object를 사용하면 캐스트를 해주어야 올바르게 사용가능
클래스나 인터페이스의 서브 타입으로 제한하여서 타입을 받는다! 제한하지 않으면 Object가 가진 메소드만 사용가능함
구체적인 메소드(최상위 타입의 또는 오버라이딩한)를 사용가능하게 된다!
여러 타입을 Bounded 할 수 있는데, 만약 클래스와 인터페이스를 둘 다 bounded한다면 클래스를 맨 첫번째에 써줘야함 ← 인터페이스끼리면 상관 X
Object에 Integer → OK
Number 매개변수에 Integer와 Double → OK
Number는 숫자로 된 Wrapper 클래스의 상위타입; Character Boolean 제외
Number 타입 매개변수에 Integer와 Double → OK
주의! Number 타입 매개변수를 가진 제네릭 타입에 Integer 타입 매개변수를 가진 객체 → Compile error!!
Run time에 치환한 정보(객체)가 전달되지 않기 때문이다!!
같은 타입 매개변수를 가지면 클래스끼리 원래 가지고 있던 상속관계가 유지됨
이 상속관계는 다형성을 나타냄. 타입별로 가리킬 수 있는 관계를 나타내주는 것!
ArrayList가 가리킬 수 있는 것은 List, Collection도 가리킬 수 있다는 것임
다른 타입 매개변수를 가지면 상속관계가 유지되지 않음
이 두 경우가 합쳐져서 위의 그림처럼 PayLoadList끼리는 상속관계를 유지하지 않고, String으로 타입이 같은 상속관계는 유지된다. 호출하는 범위(상속관계)는 P가 뭐로 치환되는지랑 관계없음
변수 선언시에만 사용가능, 매개변수에서 가장 많이 사용됨
extends가 붙으면 Upper Bounded Wildcards
super가 붙으면 Lower Bounded Wildcards
그냥 쓰이면 Unbounded Wildcards
내 객체에 변화 x
할 수 있는 일이 줄어듬
아래타입으로 위타입 가리킬 수 없음
구체적 특정 x
안될수도 있으니 쓰기를 허용하지 않음
읽기 전용!
예를 들어 Double이 들어올수도 Character가 들어올수도 있는데 특정이 되지 않기 때문에 쓰기를 할 수 없음 ← Number를 Double로 받을 수 없기 때문이다, 최상위 클래스에서 할 수 있는 일이 가장 적기에 안정성이 떨어진다.
내 객체에 변화 O
위타입으로 아래타입 가리킬 수 있음
구체적으로 특정됨
쓰기전용!
예를 들어 Number여도 Integer 삽입 가능하고 Object여도 Integer 받아서 삽입 가능하다! ←Number와 Object 모두 Integer를 가리킬 수 있기 때문이다, 최하위클래스에서 가장 할수있는 일이 많기에 안정성이 확보된다.
extends
키워드를 사용하여 정의됩니다.java코드 복사
public class Box<T extends Number> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public static void main(String[] args) {
Box<Integer> intBox = new Box<>();
intBox.setValue(10);
Box<Double> doubleBox = new Box<>();
doubleBox.setValue(10.5);
// Box<String> stringBox = new Box<>(); // 컴파일 오류: String은 Number의 서브타입이 아님
}
}
위의 예제에서 Box<T extends Number>
는 Number
클래스 또는 그 서브클래스 (Integer
, Double
등)만 타입 매개변수로 사용할 수 있음을 의미합니다.super
키워드를 사용하여 정의됩니다. 하한 경계는 주로 와일드카드(? super T
)와 함께 사용됩니다.java코드 복사
import java.util.ArrayList;
import java.util.List;
public class LowerBoundedExample {
public static void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
}
public static void main(String[] args) {
List<Number> numberList = new ArrayList<>();
addNumbers(numberList);
List<Object> objectList = new ArrayList<>();
addNumbers(objectList);
// List<Double> doubleList = new ArrayList<>(); // 컴파일 오류: Double은 Integer의 슈퍼타입이 아님
}
}
위의 예제에서 addNumbers(List<? super Integer> list)
는 Integer
의 슈퍼타입 (Number
, Object
)을 요소로 가질 수 있는 리스트만을 인수로 받습니다.java코드 복사
import java.util.ArrayList;
import java.util.List;
public class UpperBoundedExample {
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number number : list) {
sum += number.doubleValue();
}
return sum;
}
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
intList.add(3);
List<Double> doubleList = new ArrayList<>();
doubleList.add(1.1);
doubleList.add(2.2);
doubleList.add(3.3);
System.out.println("Sum of intList: " + sumOfList(intList)); // 6.0
System.out.println("Sum of doubleList: " + sumOfList(doubleList)); // 6.6
}
}
java코드 복사
import java.util.ArrayList;
import java.util.List;
public class LowerBoundedExample {
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 5; i++) {
list.add(i);
}
}
public static void main(String[] args) {
List<Number> numberList = new ArrayList<>();
addNumbers(numberList);
System.out.println("Number list: " + numberList);
List<Object> objectList = new ArrayList<>();
addNumbers(objectList);
System.out.println("Object list: " + objectList);
}
}
상한 경계 (<T extends SomeClass>
): 타입 매개변수를 특정 클래스나 인터페이스의 서브타입으로 제한.
하한 경계 (<? super SomeClass>
): 타입 매개변수를 특정 클래스의 슈퍼타입으로 제한.
상한 경계는 주로 읽기 전용 작업에 유용하고, 하한 경계는 쓰기 작업에 유용합니다. 이를 통해 제네릭 타입의 사용을 더욱 안전하고 강력하게 만들 수 있습니다.
클래스 상속: 일반적으로 클래스 B가 클래스 A를 상속받으면, B는 A의 서브타입입니다.
제네릭 타입: 제네릭 타입은 특정 타입을 파라미터로 받아서 동작합니다. 예를 들어, List<T>
는 타입 파라미터 T를 사용하는 제네릭 타입입니다.
제네릭 타입의 서브타입 관계는 기본 클래스 상속과 다르게 작동합니다. 제네릭 타입 A와 B의 관계는 각 타입 파라미터가 관계에 영향을 미치게 됩니다.
일반적인 클래스 상속 관계가 있다고 가정할 때:
만약 Dog
가 Animal
을 상속한다면, Dog
는 Animal
의 서브타입입니다.
하지만 List<Dog>
는 List<Animal>
의 서브타입이 아닙니다.
예를 들어:
java코드 복사
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = dogs; // 컴파일 오류
위의 코드에서 List<Dog>
는 List<Animal>
의 서브타입이 아닙니다. 이것은 제네릭의 불공변성(invariance) 때문입니다. 즉, T
가 S
의 서브타입이라도 List<T>
는 List<S>
의 서브타입이 아닙니다.
제네릭 타입의 서브타입 관계를 더욱 유연하게 하기 위해 와일드카드를 사용할 수 있습니다.
java코드 복사
List<? extends Animal> animals = new ArrayList<Dog>();
위의 코드에서 List<? extends Animal>
는 Animal
의 서브타입인 모든 리스트를 참조할 수 있습니다. 이는 List<Dog>
, List<Cat>
등도 참조할 수 있음을 의미합니다. 그러나, 이 경우 리스트에 요소를 추가할 수 없습니다.
java코드 복사
List<? super Dog> animals = new ArrayList<Animal>();
위의 코드에서 List<? super Dog>
는 Dog
의 슈퍼타입인 모든 리스트를 참조할 수 있습니다. 이는 List<Animal>
, List<Object>
등을 참조할 수 있음을 의미합니다. 이 경우 리스트에 Dog
또는 Dog
의 서브타입의 객체를 추가할 수 있습니다.
제네릭 클래스 자체가 상속 관계에 있을 때의 예제를 보겠습니다.
java코드 복사
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
class Box<T> {}
class DogBox extends Box<Dog> {}
class CatBox extends Box<Cat> {}
여기서 DogBox
는 Box<Dog>
를 상속하지만, Box<Dog>
와 Box<Cat>
사이에는 아무런 상속 관계가 없습니다.
와일드카드를 활용하여 제네릭 메소드와 상속 관계를 더 유연하게 사용할 수 있습니다.
java코드 복사
import java.util.List;
class Animal {
void eat() {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
@Override
void eat() {
System.out.println("Dog is eating");
}
}
class Cat extends Animal {
@Override
void eat() {
System.out.println("Cat is eating");
}
}
public class WildcardExample {
// 상한 경계 와일드카드 사용
public static void feedAnimals(List<? extends Animal> animals) {
for (Animal animal : animals) {
animal.eat();
}
}
// 하한 경계 와일드카드 사용
public static void addDog(List<? super Dog> animals) {
animals.add(new Dog());
}
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
feedAnimals(dogs); // 가능
List<Animal> animals = new ArrayList<>();
addDog(animals); // 가능
}
}
위의 예제에서 feedAnimals
메소드는 List<? extends Animal>
을 매개변수로 받아 Dog
, Cat
등 Animal
의 서브타입 리스트를 인수로 받을 수 있습니다. 반면, addDog
메소드는 List<? super Dog>
를 매개변수로 받아 Dog
의 슈퍼타입 리스트를 인수로 받을 수 있으며, Dog
객체를 추가할 수 있습니다.
제네릭 불공변성: List<Dog>
는 List<Animal>
의 서브타입이 아닙니다.
상한 경계 와일드카드 (<? extends T>
): T
의 서브타입을 허용합니다.
하한 경계 와일드카드 (<? super T>
): T
의 슈퍼타입을 허용합니다.
제네릭 클래스 상속: 제네릭 클래스의 서브클래스는 타입 파라미터와 별도로 상속 관계를 가집니다.
이러한 개념을 통해 제네릭 타입을 더욱 유연하고 안전하게 사용할 수 있습니다.
자바의 제네릭에서 와일드카드(wildcard)와 서브타이핑(subtyping)은 중요한 개념입니다. 이를 통해 제네릭 타입을 더욱 유연하게 사용할 수 있으며, 특히 메소드의 인수와 반환 타입을 정의할 때 유용합니다. 와일드카드는 주로 세 가지 형태로 사용됩니다: 무제한 와일드카드, 상한 경계 와일드카드, 하한 경계 와일드카드.
무제한 와일드카드 (<?>
)
상한 경계 와일드카드 (<? extends T>
)
하한 경계 와일드카드 (<? super T>
)
<?>
)무제한 와일드카드는 어떤 타입이든 허용됩니다. 이는 타입 안정성을 유지하면서 다양한 타입을 받아들일 수 있습니다.
java코드 복사
public void printList(List<?> list) {
for (Object elem : list) {
System.out.println(elem);
}
}
위의 예제에서 printList
메소드는 어떤 타입의 리스트든 받아들일 수 있습니다.
<? extends T>
)상한 경계 와일드카드는 특정 타입 T
의 서브타입만 허용합니다. 이는 주로 읽기 전용으로 사용할 때 유용합니다.
java코드 복사
public void processAnimals(List<? extends Animal> animals) {
for (Animal animal : animals) {
animal.eat();
}
}
위의 예제에서 processAnimals
메소드는 Animal
의 서브타입(Dog
, Cat
등)의 리스트를 인수로 받을 수 있습니다.
<? super T>
)하한 경계 와일드카드는 특정 타입 T
의 슈퍼타입만 허용합니다. 이는 주로 쓰기 전용으로 사용할 때 유용합니다.
java코드 복사
public void addDogs(List<? super Dog> animals) {
animals.add(new Dog());
}
위의 예제에서 addDogs
메소드는 Dog
의 슈퍼타입(Animal
, Object
등)의 리스트를 인수로 받을 수 있으며, Dog
객체를 추가할 수 있습니다.
java코드 복사
import java.util.ArrayList;
import java.util.List;
public class WildcardExample {
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.println(elem);
}
}
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("World");
List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
printList(stringList);
printList(intList);
}
}
java코드 복사
import java.util.ArrayList;
import java.util.List;
class Animal {
void eat() {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
@Override
void eat() {
System.out.println("Dog is eating");
}
}
public class UpperBoundedWildcardExample {
public static void feedAnimals(List<? extends Animal> animals) {
for (Animal animal : animals) {
animal.eat();
}
}
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
feedAnimals(dogs);
}
}
java코드 복사
import java.util.ArrayList;
import java.util.List;
class Animal {}
class Dog extends Animal {}
public class LowerBoundedWildcardExample {
public static void addDogs(List<? super Dog> animals) {
animals.add(new Dog());
}
public static void main(String[] args) {
List<Animal> animalList = new ArrayList<>();
addDogs(animalList);
}
}
와일드카드를 사용하면, 제네릭 타입 간의 서브타입 관계를 더 유연하게 정의할 수 있습니다. 예를 들어:
List<? extends T>
는 List<T>
의 서브타입이지만, List<T>
는 List<? extends T>
의 서브타입이 아닙니다.
List<? super T>
는 List<T>
의 슈퍼타입이지만, List<T>
는 List<? super T>
의 슈퍼타입이 아닙니다.
와일드카드를 사용할 때 특정 타입으로 캡처하여 사용할 수 있습니다. 이를 와일드카드 캡처라고 합니다.
java코드 복사
public <T> void reverse(List<T> list) {
List<T> tempList = new ArrayList<>(list);
Collections.reverse(tempList);
for (int i = 0; i < list.size(); i++) {
list.set(i, tempList.get(i));
}
}
public void reverseWildcard(List<?> list) {
reverse(list); // 컴파일 오류
}
위의 예제는 와일드카드 캡처를 사용하지 않은 코드입니다. 이를 해결하기 위해서는 다음과 같이 와일드카드 캡처를 사용합니다.
java코드 복사
public <T> void reverse(List<T> list) {
List<T> tempList = new ArrayList<>(list);
Collections.reverse(tempList);
for (int i = 0; i < list.size(); i++) {
list.set(i, tempList.get(i));
}
}
public void reverseWildcard(List<?> list) {
reverseHelper(list);
}
private <T> void reverseHelper(List<T> list) {
reverse(list);
}
무제한 와일드카드 (<?>
): 어떤 타입이든 허용.
상한 경계 와일드카드 (<? extends T>
): 특정 타입 T
의 서브타입만 허용.
하한 경계 와일드카드 (<? super T>
): 특정 타입 T
의 슈퍼타입만 허용.
와일드카드와 서브타이핑을 통해 제네릭 타입을 더욱 유연하게 사용할 수 있으며, 적절한 와일드카드 사용은 코드의 타입 안정성을 높여줍니다.
와일드카드 캡처는 와일드카드를 특정 타입으로 캡처하여 사용하는 기술입니다.
<?>
)와 일반적인 타입 매개변수(T
) 차이무제한 와일드카드 (<?>
)와 일반적인 타입 매개변수(T
)는 자바 제네릭에서 다르게 동작하며, 그 용도와 의미도 다릅니다. 두 개념을 구분하여 이해하는 것이 중요합니다.
<?>
)무제한 와일드카드는 제네릭 타입이 어떤 타입이든 허용한다는 의미입니다. 이는 주로 제네릭 타입의 요소를 읽기만 하고, 요소를 추가하지 않을 때 유용합니다. 무제한 와일드카드는 타입 안정성을 유지하면서도, 메소드나 클래스가 다양한 타입을 처리할 수 있게 해줍니다.
예제:
java코드 복사
import java.util.List;
public class UnboundedWildcardExample {
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.println(elem);
}
}
public static void main(String[] args) {
List<String> stringList = List.of("A", "B", "C");
List<Integer> intList = List.of(1, 2, 3);
printList(stringList);
printList(intList);
}
}
위의 예제에서 printList
메소드는 List<?>
를 인수로 받기 때문에, 어떤 타입의 리스트든 인수로 받을 수 있습니다.
T
)타입 매개변수 T
는 제네릭 클래스나 메소드의 선언부에 사용되며, 해당 타입으로 특정 타입을 지정할 수 있습니다. 타입 매개변수는 무제한 와일드카드와 달리, 특정 타입을 지정할 수 있어 더 많은 타입 정보를 제공합니다.
예제:
java코드 복사
import java.util.List;
public class GenericTypeExample<T> {
private T element;
public void setElement(T element) {
this.element = element;
}
public T getElement() {
return element;
}
public void printElement() {
System.out.println(element);
}
public static void main(String[] args) {
GenericTypeExample<String> stringExample = new GenericTypeExample<>();
stringExample.setElement("Hello");
stringExample.printElement(); // 출력: Hello
GenericTypeExample<Integer> intExample = new GenericTypeExample<>();
intExample.setElement(123);
intExample.printElement(); // 출력: 123
}
}
위의 예제에서 GenericTypeExample
클래스는 타입 매개변수 T
를 사용하여 특정 타입을 지정할 수 있습니다. 이는 클래스가 String
또는 Integer
등 다양한 타입으로 작동할 수 있게 해줍니다.
타입 정보 제공:
<?>
(무제한 와일드카드): 어떤 타입이든 허용하지만, 구체적인 타입 정보를 제공하지 않음.T
(타입 매개변수): 특정 타입을 지정하며, 해당 타입 정보를 제공함.사용 위치:
<?>
: 주로 메소드 매개변수나 반환 타입에서 사용되어, 다양한 타입을 처리할 수 있음.T
: 제네릭 클래스나 메소드 선언부에서 사용되어, 클래스나 메소드가 특정 타입으로 동작하도록 함.읽기와 쓰기:
- <?>
: 읽기 전용으로 사용될 때 유용. 리스트에서 요소를 읽을 수 있지만, 요소를 추가할 수는 없음.
- T
: 읽기와 쓰기 모두 가능. 리스트에서 요소를 읽고 쓸 수 있음.
다음은 <?>
와 T
를 사용한 차이점을 명확히 보여주는 예제입니다:
java코드 복사
import java.util.List;
public class WildcardAndGenericExample {
// 무제한 와일드카드 사용 - 읽기 전용
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.println(elem);
}
}
// 타입 매개변수 사용 - 읽기 및 쓰기 가능
public static <T> void addAndPrint(List<T> list, T element) {
list.add(element); // 요소 추가 가능
for (T elem : list) {
System.out.println(elem);
}
}
public static void main(String[] args) {
List<String> stringList = new java.util.ArrayList<>(List.of("A", "B", "C"));
List<Integer> intList = new java.util.ArrayList<>(List.of(1, 2, 3));
// 무제한 와일드카드 사용
printList(stringList);
printList(intList);
// 타입 매개변수 사용
addAndPrint(stringList, "D");
addAndPrint(intList, 4);
}
}
위의 예제에서 printList
메소드는 무제한 와일드카드를 사용하여 리스트의 요소를 출력할 수 있지만, 요소를 추가할 수는 없습니다. 반면, addAndPrint
메소드는 타입 매개변수를 사용하여 리스트에 요소를 추가하고, 요소를 출력할 수 있습니다.
무제한 와일드카드 (<?>
)는 어떤 타입이든 허용하지만, 요소를 추가할 수 없는 읽기 전용 용도로 사용됩니다.
타입 매개변수 (T
)는 특정 타입을 지정하며, 요소를 추가하고 읽을 수 있는 용도로 사용됩니다.
무제한 와일드카드는 주로 메소드의 매개변수나 반환 타입에서 사용되고, 타입 매개변수는 제네릭 클래스나 메소드 선언부에서 사용됩니다.
List<Integer>
와 List<? extends Integer>
는 자바에서 서로 다른 의미를 가지며, 같은 상황에서 사용될 수 없습니다. 두 타입의 차이를 이해하는 것은 제네릭 타입과 와일드카드를 올바르게 사용하는 데 중요합니다.
List<Integer>
List<Integer>
는 Integer 타입의 요소를 포함하는 리스트입니다. 이 리스트는 다음과 같은 작업이 가능합니다:
요소 추가 (add
메소드)
요소 제거 (remove
메소드)
요소 읽기 (get
메소드)
예제:
java코드 복사
List<Integer> integers = new ArrayList<>();
integers.add(1);
integers.add(2);
integers.add(3);
List<? extends Integer>
List<? extends Integer>
는 Integer 타입의 서브타입을 요소로 가지는 리스트입니다. 이는 리스트의 요소 타입이 Integer이거나 Integer의 서브타입임을 보장하지만, 구체적으로 어떤 타입인지는 알 수 없습니다. 따라서, 이 리스트에서는 다음과 같은 제한이 있습니다:
요소 추가 불가 (add
메소드 사용 불가): 컴파일러가 리스트에 추가할 요소의 정확한 타입을 알 수 없기 때문에, 요소를 추가할 수 없습니다.
요소 읽기 가능 (get
메소드 사용 가능): 리스트의 요소를 읽을 수 있으며, 읽은 요소는 Integer 타입으로 처리됩니다.
예제:
java코드 복사
List<? extends Integer> integers = new ArrayList<>();
integers.add(1); // 컴파일 오류
Integer num = integers.get(0); // 가능
List<Integer>
List<Integer>
는 요소를 추가하고 읽는 모든 작업을 수행할 수 있습니다.
java코드 복사
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
intList.add(1); // 요소 추가 가능
intList.add(2);
intList.add(3);
for (Integer num : intList) {
System.out.println(num); // 요소 읽기 가능
}
}
}
List<? extends Integer>
List<? extends Integer>
는 읽기 전용으로 사용됩니다.
java코드 복사
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
intList.add(3);
printList(intList); // List<Integer>를 List<? extends Integer>로 사용할 수 있음
}
public static void printList(List<? extends Integer> list) {
// list.add(4); // 컴파일 오류 - 요소 추가 불가
for (Integer num : list) {
System.out.println(num); // 요소 읽기 가능
}
}
}
List<Integer>
: Integer 타입의 요소를 추가, 제거, 읽기 모두 가능.
List<? extends Integer>
: Integer 또는 그 서브타입의 요소를 읽기만 가능, 요소 추가 불가.
List<? extends Integer>
는 요소를 추가하지 않고 읽기만 하는 상황에서 사용됩니다. 이는 메소드 파라미터나 반환 타입에서 제네릭 타입을 제한할 때 유용합니다. 반면, List<Integer>
는 리스트에 요소를 추가하거나 수정할 때 사용됩니다. 따라서 두 타입은 같은 의미로 사용될 수 없습니다. 각각의 용도에 맞게 적절히 사용해야 합니다.
List<T extends Integer>
와 List<? extends Integer>
는 자바 제네릭에서 서로 다른 상황에서 사용됩니다. 이들은 비슷한 의미를 가지지만, 사용되는 문맥과 목적이 다릅니다. 자세히 살펴보겠습니다.
List<T extends Integer>
List<T extends Integer>
는 제네릭 타입 매개변수 T
가 Integer
의 서브타입임을 명시합니다. 이는 제네릭 클래스나 메소드에서 사용되며, 해당 클래스나 메소드 내에서 T
타입의 변수를 사용할 수 있습니다.
예제:
java코드 복사
public class MyClass<T extends Integer> {
private List<T> list;
public MyClass(List<T> list) {
this.list = list;
}
public void addElement(T element) {
list.add(element);
}
public T getElement(int index) {
return list.get(index);
}
}
위의 예제에서 MyClass<T extends Integer>
는 T
가 Integer
의 서브타입임을 보장합니다. 이 제네릭 클래스는 T
타입의 요소를 리스트에 추가하고 읽을 수 있습니다.
List<? extends Integer>
List<? extends Integer>
는 와일드카드 타입으로, 이 리스트가 Integer
또는 그 서브타입을 포함할 수 있음을 나타냅니다. 이는 주로 메소드 파라미터나 반환 타입에서 사용되며, 리스트의 요소 타입이 Integer
또는 그 서브타입임을 보장합니다. 그러나 이 리스트에는 요소를 추가할 수 없고, 요소를 읽기만 할 수 있습니다.
예제:
java코드 복사
public void printList(List<? extends Integer> list) {
for (Integer num : list) {
System.out.println(num);
}
}
위의 예제에서 printList
메소드는 List<? extends Integer>
를 파라미터로 받아, 리스트의 요소를 읽기만 할 수 있습니다.
제네릭 클래스/메소드 선언에서의 T extends Integer
:
T
를 정의하고, 이를 제네릭 클래스나 메소드 내에서 사용합니다.T
는 Integer
의 서브타입임을 보장받으며, 해당 타입을 사용하여 요소를 추가하거나 읽을 수 있습니다.java코드 복사
public class MyClass<T extends Integer> {
private List<T> list;
public MyClass(List<T> list) {
this.list = list;
}
public void addElement(T element) {
list.add(element);
}
public T getElement(int index) {
return list.get(index);
}
}
메소드 파라미터/반환 타입에서의 ? extends Integer
:
- 제네릭 타입을 유연하게 받아들이기 위해 사용됩니다.
- Integer
또는 그 서브타입을 포함하는 리스트를 받아들이지만, 요소 추가는 불가하고 읽기만 가능합니다.
- 사용 예:
```java
java코드 복사
public void printList(List<? extends Integer> list) {
for (Integer num : list) {
System.out.println(num);
}
}
```
List<T extends Integer>
는 제네릭 클래스나 메소드 선언에서 사용되며, T
가 Integer
의 서브타입임을 명시합니다. 이 타입은 클래스나 메소드 내에서 요소 추가와 읽기가 모두 가능합니다.
List<? extends Integer>
는 와일드카드 타입으로, 주로 메소드 파라미터나 반환 타입에서 사용됩니다. 이 리스트는 Integer
또는 그 서브타입을 포함할 수 있지만, 요소 추가는 불가하고 읽기만 가능합니다.
따라서, 두 개념은 같은 의미로 사용될 수 없습니다. List<T extends Integer>
는 제네릭 타입 매개변수를 정의하는 데 사용되며, List<? extends Integer>
는 다양한 서브타입을 처리할 때 유용하게 사용됩니다.
interface Iterable<T>
의 메소드에는 Iterator<T> iterator()
메소드가 있다. 이 함수는 iterator를 반환하는 역할을 한다for(E element: list){element호출}
식의 Enhanced for문은 for(Iterator<E> i = list.iterator(); i.hasNext();){i.next()호출}
식으로 자동으로 변환되어 컴파일 된다!public interface ListIterator extends Iterator
ArrayList
, LinkedList
등 List
인터페이스를 구현하는 클래스의 내부 클래스로 구현됨ListIterator 메소드
중요 ListIterator의 cursor 개념
ListIterator 사용 예시
import java.util.List;
import java.util.ArrayList;
import java.util.ListIterator;
import java.util.Arrays;
public class ListIteratorEx {
public static void main(String[] args) {
List<String> list = Arrays.asList("Apple","Orange","Mango","Strawberry");
list = new ArrayList<>(list);
ListIterator<String> litr = list.listIterator();
while(litr.hasNext()){
String str = litr.next();
System.out.print(str+'\t');
if(str.equals("Strawberry"))
litr.add("After_Strawberry");
}
System.out.println();
while(litr.hasPrevious()){
String str = litr.previous();
System.out.print(str+'\t');
if(str.equals("Apple"))
litr.add("Before_Apple");
}
System.out.println();
for(String str: list)
System.out.print(str + '\t');
System.out.println();
}
}
출력 결과:
Apple Orange Mango Strawberry
After_Strawberry Strawberry Mango Orange Apple Before_Apple
Before_Apple Apple Orange Mango Strawberry After_Strawberry
중요
Arrays는 Collections처럼 Array를 다루기 위한 클래스이다. sort, search, equals, copyOf, fill, toString, asList등을 지원한다.
→ asList는 List를 초기화할 때 배열을 리스트로 변환하여 새로운 List 레퍼런스를 저장할 수 있도록한다.
List list이기 때문에 list는 new ArrayList<>(list)를 받을 수 있다. Collection이 최상위로 묶여있기 떄문에 다른 구조여도 요소들을 옮겨줄 수 있기 때문이다!
모든 Collection들의 생성자에는 Collection을 매개변수로 받을 수 있도록 해놓았음!
add시에 previous를 진행시 추가한 객체를 방문하게 될 수 있음을 주의하자!
List 타입을 문자열 배열로 매개변수를 전달해야하는 상황에 toArray를 통해서 배열 레퍼런스를 전달한다
여기서 매개변수로 배열을 넣어줬는데, 오직 Type 전달의 목적으로 써주는 것이다! 그래서 아무 의미도 없는 new String[0]을 전달해준 것. 이 매개변수를 통해 Array의 타입을 추론한다!
Set
HashSet
HashSet은 내부적으로 배열을 생성함
HashSet은 HashMap을 이용함
key값: hashCode()%n을 수행 → 0~n-1의 index 생성
만약 같은 key값이 나오면 연결리스트 생성 (체이닝)
그 후 equals()로 검사하여 연결리스트를 돌면서 겹치면 저장 x

⇒ 내가 근거한 같은 값을 통해 hash값을 생성하게 해야함
같은 값일 때, 같은 값이 나오게 하기는 쉽지만, 좋은 해시함수는 분포가 균일하게 나와야함 (key값 쏠림 방지)
중요 hash()함수는 들어온 모든 값을 primitive 값으로 바꿈, 만약 reference를 넣어주면 그 reference의 haseCode()함수를 실행함 → class의 reference를 hash()에 넣어주려면 hashCode() 오버라이딩!
중요 HashSet의 사용자형 클래스 타입의 사용방법
- HashSet을 사용할 때, 사용자형 class를 저장하고 싶을 때, equals()와 HashCode()함수를 오버라이딩 해주어야함
- 좋은 해싱을 위해서 Objects.hash() 함수를 통해 hash 함수 사용
- int hash(Object …)인데 int형을 넣어줘도 되는 이유는 Integer로 박싱되기 때문이다!
hash메소드의 사용 예시
import java.util.HashSet;
import java.util.Objects;
class Point{
int x,y;
public Point(int x, int y){
this.x=x;
this.y=y;
}
@Override
public String toString(){
return "Point( "+x+", "+y+" )";
}
@Override
public boolean equals(Object obj){
Point p = (Point)obj; //Point 클래스를 사용하기 위해서 타입캐스팅 필요!
return (x==p.x&&y==p.y);
}
@Override
public int hashCode(){
return Objects.hash(x,y); //hash함수에 int넣어도 좋음 어짜피 전부 레퍼런스 타입을 ㅗ변경함
}
}
class Circle {
Point center;
int radius;
Circle(Point center, int radius){
this.center=center;
this.radius=radius;
}
@Override
public String toString(){
return "Circle (center: "+center+", radius: "+radius+" )";
}
@Override
public boolean equals(Object obj){
Circle c = (Circle) obj; //Circle 클래스를 사용하기 위해서 타입캐스팅 필요!
return (center.equals(c.center)&&radius==c.radius);
}
@Override
public int hashCode(){
return Objects.hash(center,radius);
}
}
public class HashCodeEx {
public static void main(String[] args) {
HashSet<Circle> set = new HashSet<>();
set.add(new Circle(new Point(1,2),2));
set.add(new Circle(new Point(1,2),2));
set.add(new Circle(new Point(3,4),2));
set.add(new Circle(new Point(4,5),2));
set.add(new Circle(new Point(5,6),2));
System.out.println("size: "+set.size());
for(Circle c:set)
System.out.println(c.toString()+'\t');
}
}
출력 결과:
size: 4
Circle (center: Point( 1, 2 ), radius: 2 )
Circle (center: Point( 3, 4 ), radius: 2 )
Circle (center: Point( 4, 5 ), radius: 2 )
Circle (center: Point( 5, 6 ), radius: 2 )
중요 Point와 Circle 클래스에서 hashCode()를 오버라이딩하여서 Circle에서 hash에 center를 넣어주면 Point의 hashCode로 가서 hash값을 리턴하므로 문제 없게 된다!
NavigableSet
인터페이스는 다양한 탐색 및 범위 조작 메소드를 제공합니다. 다음은 주요 메소드들입니다: lower(E e)
: 지정된 요소보다 작은 가장 큰 요소를 반환합니다. 없으면 null
을 반환합니다.
floor(E e)
: 지정된 요소보다 작거나 같은 가장 큰 요소를 반환합니다. 없으면 null
을 반환합니다.
ceiling(E e)
: 지정된 요소보다 크거나 같은 가장 작은 요소를 반환합니다. 없으면 null
을 반환합니다.
higher(E e)
: 지정된 요소보다 큰 가장 작은 요소를 반환합니다. 없으면 null
을 반환합니다.
pollFirst()
: 첫 번째(가장 작은) 요소를 제거하고 반환합니다. 집합이 비어 있으면 null
을 반환합니다.
pollLast()
: 마지막(가장 큰) 요소를 제거하고 반환합니다. 집합이 비어 있으면 null
을 반환합니다.
descendingSet()
: 역순으로 정렬된 집합을 반환합니다.
subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive)
: 지정된 범위의 요소를 포함하는 부분 집합을 반환합니다.
headSet(E toElement, boolean inclusive)
: 지정된 요소보다 작은 요소를 포함하는 부분 집합을 반환합니다.
tailSet(E fromElement, boolean inclusive)
: 지정된 요소보다 큰 요소를 포함하는 부분 집합을 반환합니다.
사용자형 클래스 타입을 사용할 경우 그 타입은 Comparable로 캐스팅이 가능해야한다! → 2가지 방법!
기본적으로 compareTo(), compare() 메소드는 같으면 0을 리턴, x>y이거나 y>x이면 음수나 양수를 리턴함
#1 클래스의 Comparable 구현 및 compareTo 오버라이딩
1) 클래스에서 Comparable 인터페이스 구현
2) compareTo() 오버라이딩 → 객체와 매개변수 비교
#2 Comparator 구현 및 사용자형 Comparator 만들어주기
1) Comparator 정의 및 Comparator 인터페이스 구현
2) compare() 메소드 정의 → 매개변수끼리 비교
3) TreeSet의 생성자에 정의한 Comparator의 객체 넣어줌
고정 사이즈의 Queue를 사용시에 비정상적으로 사용시에 예외를 발생시킬지 특별한 값(true/false or null등)을 return할지 스타일 별로 선택하면 된다

- java.util.concurrent.LinkedBlockingQueue
ㄴ 생성자에 사이즈 넣어준다!
- Insert시 용량을 초과한다면 예외를 발생 or false 리턴
→ 평상시에는 true 리턴 동일
- Remove, Examine시 empty이면 예외를 발생 or null리턴
→ 평상시에는 객체 리턴 동일
Queue의 메소드 사용 예시
LinkedBlockingQueue에서 offer 실패시 false를 리턴하고, peek과 poll실패시 null를 리턴함을 알 수 있다!
Deque는 Stack을 구성한다!
Deque은 앞 뒤에서 모두 삽입, 삭제, 탐색 연산이 가능함
Deque stack = new ArrayDeque<>();
- Deque를 사용하여 stack 구현, 실 객체는 ArrayDeque나 LinkedList 사용; pop, push, peek이 구현되어있음!
https://believed-poinsettia-f0f.notion.site/7-a85f1887b7a246df86d7cb33d79678d1?pvs=4