[Java] 명품 Java Programming 정리 - 7장

근이의 개발일기·2024년 8월 19일
0
post-thumbnail

7장

  • 상속관계의 import Java에서 상속 관계에 있는 컬렉션을 사용할 때, 사용하려는 타입을 import하면 해당 타입의 모든 상위 타입도 함께 사용할 수 있습니다. 하지만, 상위 타입이나 다른 관련 타입을 명시적으로 사용할 필요가 있다면, 해당 타입도 import해야 합니다. 예를 들어, ArrayListList 인터페이스를 구현하고, ListCollection 인터페이스를 확장합니다. 만약 코드에서 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(Java 5)에서 도입
  • 모든 종류의 데이터 타입을 다룰 수 있도록 일반화된 타입 매개 변수로 클래스나 메소드를 작성하는 기법 ㄴ C++의 템플릿과 동일한 개념이다 Untitled
  • 컬렉션과 제네릭
    • 컬렉션의 요소는 객체만 가능
      • 기본적으로 int, char, double 등의 기본 타입 사용 불가
        • 주의! 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);
              }
          }
    • 컬렉션은 제네릭(generics) 기법으로 구현됨
    • 제네릭
      • 특정 타입만 다루지 않고, 여러 종류의 타입으로 변신할 수 있도록 클래스나 메소드를 일반화시키는 기법
        • , , : 타입 매개변수
      • 제네릭 클래스 사례
        • 제네릭 스택: Stack
          • E에 특정 타입으로 구체화
          • 정수만 다루는 스택 → Stack
          • 문자열만 다루는 스택 → Stack

컬렉션의 개념

  • 컬렉션
    • 요소 객체들의 저장소
      • 객체들의 컨테이너라고도 불림

        • 요소의 개수에 따라 크기 자동 조절
        • 요소의 삽입, 삭제에 따른 요소의 위치 자동 이동
      • 고정 크기의 배열을 다루는 어려움 해소

      • 다양한 객체들의 삽입, 삭제, 검색등의 관리 용이

        Untitled

  • 컬렉션을 위한 자바 인터페이스와 클래스 Untitled
    • 개념적으로 Collection과 Map 모두 Collection이다.

      Untitled

    • Stack은 Collection과 List를 구현하고 있고, Vector를 상속함을 알 수 있다.

컬렉션의 특징

  • 컬렉션의 자동 박싱/언박싱
    • 컬렉션과 자동 박싱 / 언박싱
      • JDK 1.5 이전
        • 기본 타입 데이터를 Wrapper 클래스를 이용하여 객체로 만들어 사용 Untitled
        • 컬렉션으로부터 요소를 얻어올 때, Wrapper 클래스로 캐스팅 필요 Untitled
      • JDK 1.5부터
        • 자동 박싱/언박싱이 작동하여 기본 타입 값 사용 가능 Untitled
          • 주의! 제네릭의 타입 매개 변수를 기본 타입으로 구체화 할 수는 없음 Untitled
  • 컬렉션을 매개변수로 받는 메소드
    • 컬렉션을 매개변수로 받는 메소드의 원형
      • public void printVector(Vector v)

        Untitled

  • 자바의 타입 추론 기능의 진화
    • Java 7 이전 Untitled
    • Java 7 이후
      • 컴파일러의 타입 추론 기능 추가

      • <>에 타입 매개변수 생략

      • 주의! 심지어 <>도 생략 가능하다!

        Untitled

    • Java 10 이후
      • vat 키워드 도입, 컴파일러의 지역 변수 타입 추론 가능

        Untitled

중요 Vector

  • Vector의 특성

    • java.util.Vector
      • 에서 E대신 요소로 사용할 특정 타입으로 구체화
    • 여러 객체들을 삽입, 삭제, 검색하는 컨테이너 클래스
      • 배열의 길이 제한 극복
      • 원소의 개수가 넘쳐나면 자동으로 길이 조절
    • Vector에 삽입 가능한 것
      • 객체, 주의! null
      • 기본 타입은 Wrapper 객체로 만들어 저장
    • Vector에 객체 삽입
      • 벡터의 맨 뒤에 객체 추가
      • 벡터 중간에 객체 삽입
    • Vector에서 객체 삭제
      • 임의의 위치에 있는 객체 삭제 가능: 객체 삭제 후 자동 자리 이동
  • Vector 컬렉션 내부 구성

    • Vector v =new Vector();

      Untitled

      주의!

    • 그냥 add로 삽입하면 맨 뒤에 삽입하게 된다!

    • 자동 박싱과 언박싱이 이루어져서 기본 자료형도 삽입 및 검색이 된다.

    • 타입 매개 변수 사용하지 않는 경우 경고 발생! import도 해주어야한다!

    • 증요 Set 컬렉션도 알아두기, Set은 중복된 것은 제외시켜서 저장한다. 순서 및 인덱스의 개념을 가지고 있지 않다!

  • 중요 Vector 클래스의 주요 메소드

    Untitled

    • 중요 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함수 실행

      Untitled

    • 주의! 기본적으로 capacity가 아닌 size를 인덱스가 벗어나면 런타임 오류이고 예외를 던진다! ArrayIndexOutOfBoundsException!

      Untitled

    • 마지막 요소를 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

  • 생성시에 new Vector()로 생성가능! 자동으로 타입 맞춰준다!
  • 중요 컬렉션의 상속: List는 제공되는 method만으로 자체 iteration이 가능하다. 이를 상속하는 Vector도 마찬가지이다. get()함수는 List 인터페이스에서 정의되었지만, Vector에서 상속하므로 사용가능함. elementAt()함수는 벡터에서만 사용가능함.

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)

  • 생성자를 통해서 객체를 add 함수에 대입해주고 있다.
  • v.remove(1)은 1을 삭제하는 것이 아니라 인덱스 1을 삭제하는 것이다.

중요 ArrayList

  • ArrayList의 특성

    • java.util.ArrayList
      • 에서 E대신 요소로 사용할 특정 타입으로 구체화
    • 가변 크기 배열을 구현한 클래스
    • ArrayList에 삽입 가능한 것
      • 객체, null
      • 기본 타입은 박싱/언박싱으로 Wrapper 객체로 만들어 저장
    • ArrayList에 객체 삽입/삭제
      • 리스트의 맨 뒤에 객체 추가
      • 리스트의 중간에 객체 삽입
      • 임의의 위치에 있는 객체 삭제 가능
    • 벡터와 달리 스레드 동기화 기능 없음
      • 다수 스레드가 동시에 ArrayList에 접근할 때 동기화 되지 않음
      • 개발자가 스레드 동기화 코드 작성해주어야함
      • 동기화 오버헤드가 없기 때문에 단일 스레드 환경에서 더 빠르게 동작함
  • Array 컬렉션의 내부 구성

    • ArrayList al = new ArrayList();

      Untitled

  • ArrayList 클래스의 주요 메소드 Untitled
    • Vector와 Element 메소드들 제외하고 동일한 메소드를 가지고 있음

      Untitled

    • 주의! Vector와 다르게 capacity 메소드는 제공하지 않는다

      Untitled

    • 주의! 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<K,V>

    • java.util.HashMap
    • 키와 값의 쌍으로 구성되는 요소를 다루는 컬렉션
      • K는 키로 사용할 요소의 타입, V는 값으로 사용할 요소의 타입 지정
      • 키와 값이 한 쌍으로 삽입
      • 키는 해시맵에 삽입되는 위치 결정에 사용 → 해쉬코드
      • 값을 검색하기 위해서는 반드시 키 이용
    • 삽입, 삭제, 검색이 빠른 특징
      • 요소 삽입 : put() 메소드 → 키와 값을 모두 넣어줌
      • 요소 검색 : get() 메소드 → 키를 통해 값을 얻음
    • 예) HashMap<String, Stirng> 생성, 삽입, 검색 Untitled
      • 키값을 get에 넣으면 값을 리턴함 ← 키값에 string 사용함
  • HashMap<String, String>의 내부 구성

    • HashMap<String,String> map=new HashMap<String,String>();

      Untitled

    • 키 값은 중복되지 않고 put메소드에서 이미 키값이 존재한다면, 값의 갱신을 해준다!

  • HashMap<K,V>의 주요 메소드

    Untitled

    • 중요 원래 HashMap은 Iterable하지 않은데, Set keySet() 메소드를 활용해서 키값을 Set으로 받은 후에 키값을 통한 값의 접근으로 Iterator를 사용해줄 수 있다!

      Untitled

    • 중요 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
종료합니다..

  • 해시맵에서 찾을 수 없으면 null 리턴하기 때문에 null로 검사

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은 iterator 사용할 수 없음 → HashMap의 keySet을 사용해서 Set의 iterator를 이용해 접근해야한다!
  • 문자열 키만 Set으로 받고, Set의 iterator 선언, 다시 문자열 키 값을 get함수에 넣어서 Iterator를 간접적으로 사용한다!

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에 사용자 정의형 클래스를 넣을 수 있음

중요 LinkedList

  • LinkedList의 특성
    • java.util.LinkedList
      • E에 요소로 사용할 타입 지정하여 구체화
    • List 인터페이스를 구현한 컬렉션 클래스
    • Vector, ArrayList 클래스와 매우 유사하게 작동
    • 요소 객체들은 양방향으로 연결되어 관리됨 → 역순 iterator가 존재함
    • 요소 객체는 맨 앞, 맨 뒤에 추가 가능
    • 요소 객체는 인덱스를 이용하여 중간에 삽입 가능
    • 맨 앞이나 맨 뒤에 요소를 추가하거나 삭제할 수 있어 스택이나 큐로 사용 가능 → pop과 push지원
      • 이외에 add, get, remove, offer, peek, poll을 지원
      • first, last에 대해서 전부 지원한다
  • LinkedList의 내부 구성과 add(), get() 메소드 Untitled
    • Vector와 동일한 함수 사용 가능

중요 컬렉션의 순차 검색을 위한 Iterator

  • Iterator 인터페이스
    • Vector, ArrayList, LinkedList가 상속받는 인터페이스
      • 리스트 구조의 컬렉션에서 요소의 순차 검색을 위한 메소드 포함 → 향상된 for문 사용 조건!
  • Iterator 인터페이스 메소드 Untitled
    • iterator() 메소드 : Iterator 객체 반환
      • Iterator 객체를 이용하여 인덱스 없이 순차적 검색 가능 Untitled Untitled
      • 주의! iterator를 사용해서 모든 요소를 방문한 후 반복자를 다시 사용하려면 새로 생성해야 한다! 만약 모두 방문 후에 next() 사용시 런타임 오류 발생! NoSuchElementException!
  • 중요 HashMap과 Set의 iterator를 활용한 출력 HashMap의 keySet()을 이용해 Set을 초기화 해주고, Set의 iterator를 사용해서 출력하면 HashMap에서 입력받은 순서와 같게 나오는데 왜그런지 설명해줄 수 없어? ChatGPT HashMap 클래스의 내부 구현에서 특정 경우에는 입력된 순서대로 요소를 반환할 수 있습니다. 이는 Java 8부터 HashMap 클래스가 키-값 쌍을 저장하는 방식이 변경되어, 내부적으로 링크드 리스트를 사용하여 요소들을 연결하기 때문입니다. 이를 "bucket chaining"이라고 합니다. 링크드 리스트를 사용하면 동일한 해시 값(버킷)을 가지는 요소들이 연결 리스트로 연결되어 저장됩니다. 따라서 동일한 버킷에 속한 요소들의 순서는 입력된 순서와 동일합니다. 이런 방식으로 요소들이 저장되면서 순서가 보장되므로, keySet() 메소드로 얻은 Set이나 entrySet() 메소드로 얻은 Set을 순회할 때 요소들이 입력된 순서대로 반환될 수 있습니다. 따라서 Java의 구현체에 따라서는 HashMap의 순서가 보존될 수 있지만, 이것은 명시된 보장된 동작이 아닙니다. 따라서 순서가 중요한 경우에는 명시적으로 순서를 유지하는 LinkedHashMap을 사용하는 것이 좋습니다.
  • 중요 Iterable 인터페이스와 Iterator 인터페이스 IterableIterator는 상속 관계가 있는 것이 아니라, 서로 다른 인터페이스입니다. IterableIterator를 반환하는 메소드를 정의하고, 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

  • 모두 순회하고 나면 Iterator 객체 다시 얻어야함!

중요 Collections 클래스 활용

  • Collections 클래스
    • java.util 패키지에 포함
    • 컬렉션에 대해 연산을 수행하고 결과로 컬렉션 리턴
      • 컬렉션을 다뤄주는 메소드를 제공한다.
    • 모든 메소드는 static 타입
      • static으로 제공하기 때문에 그냥 사용이 가능하다
    • 주요 메소드
      • 컬렉션에 포함된 요소들을 정렬하는 sort() 메소드
      • 요소의 순서를 반대로 하는 reverse() 메소드
      • 요소들의 최대, 최솟값을 찾아내는 max(), min() 메소드
      • 특정 값을 검색하는 binarySearch() 메소드
        • swap, rotate, shuffle 등등

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번째 요소입니다.

  • add함수는 마찬가지로 삽입동작하여 인덱스 위치로 삽입한다. 원래 그 인덱스에 있던 원소는 한칸 뒤로가게 된다.
  • sort를 통해 사전순으로 정렬하였다. 문자열이기 때문에 비교가능하여 실행된다.
  • binarySearch는 정렬하지 않아준 상태에서도 작동한다! 찾지 못하면 -1을 리턴한다.

Generic추가강의Generic 추가 강의

  • 제네릭의 사용 이유 ← Object 사용과 비교
    • Compile time에서의 강한 타입 체크 → 안정성, 가독성↑
      • Object를 사용하면 Run time에서 오류가 난다
    • 캐스트의 제거
      • Object를 사용하면 캐스트를 해주어야 올바르게 사용가능

        Untitled

    • Object 사용 예시 Untitled
      • 모든 객체 타입을 받고 캐스팅해주어서 런타임 오류 발생이 가능함
    • Generic 사용 예시 Untitled
      • 컴파일 단계에서 타입을 보장해준다 → 치환 정보를 컴파일러가 사용한다. 컴파일러가 오류 안난다고 확정한다면, JVM은 똑같이 Object 객체라고 생각함
      • 제네릭 객체 생성 시 생성 부분에서 type 추론해주기 때문에 타입 매개변수 넣어주지 않아도 괜찮음
  • 제네릭 타입
    • 타입 매개변수를 갖는 class 혹은 interface
      • raw type Box class Untitled
      • generic type Box class Untitled → JVM은 둘을 같다고 인식한다! 실행 중에 T를 Object로 인식함
    • 제네릭 타입은 치환해주어야 사용이 가능 Untitled
      • 상속과 구현부를 제외하고 치환해주어야 사용가능하다.
      • 중요 상속과 구현부는 클래스의 타입 매개변수를 보고 동일하게 사용한다고 생각함
  • 타입 매개변수 (Type Parameter)
    • Naming Conventions에 따라
      • E - Element
      • K - Key
      • N - Number
      • T - Type
      • V - Value
  • 중요 제네릭 메소드
    • 제네릭 메소드: return 타입 앞에 <>이 있어야함
      • 매개변수형에서 타입 매개변수 사용시 → static void method(T t)
      • 리턴형에서 타입 매개변수 사용시 → R method(int arg)
      • 리턴형과 매개변수형에서 타입매개변수 사용시 → <T,R> R method(T t)
    • 제네릭 메소드 사용 주의점 Untitled
      • 주의! method1에서 클래스의 타입 매개변수 T와 제네릭 메소드의 타입매개변수 T를 컴파일러는 구분한다! 다른 타입끼리 대입연산이어서 컴파일 오류가 발생함
      • method2에서의 T는 클래스의 타입 매개변수 T와 동일한 변수이고, 이는 제네릭 메소드가 아니다.
    • 제네릭 메소드 호출 방법 Untitled
      • 클래스에서 K,V는 자동으로 pair의 K,V와 같다고 취급함
      • 이전의 제네릭 타입(클래스, 인터페이스)와 다르게 호출할 때 메소드 앞에 타입을 명시 해주어야함
      • 타입의 추론이 가능하기도 하지만, 안되는 경우도 있으니 타입을 명시해줄 것
  • Bounded Type Parameters
    • 중요 Bounded Type Parameters: 타입 매개변수가 특정 클래스나 인터페이스의 서브타입으로 제한될 수 있음 Untitled
      • 클래스나 인터페이스의 서브 타입으로 제한하여서 타입을 받는다! 제한하지 않으면 Object가 가진 메소드만 사용가능함

        Untitled

      • 구체적인 메소드(최상위 타입의 또는 오버라이딩한)를 사용가능하게 된다!

        Untitled

      • 여러 타입을 Bounded 할 수 있는데, 만약 클래스와 인터페이스를 둘 다 bounded한다면 클래스를 맨 첫번째에 써줘야함 ← 인터페이스끼리면 상관 X

  • Generics, Inheritance, and Subtypes
    • Generic과 Inheritance: 상위 타입으로 다형성 형성하는 것처럼 Generic에서도 다형성이 나타난다 Untitled
      • Object에 Integer → OK

        Untitled

      • Number 매개변수에 Integer와 Double → OK

      • Number는 숫자로 된 Wrapper 클래스의 상위타입; Character Boolean 제외

        Untitled

      • Number 타입 매개변수에 Integer와 Double → OK

        Untitled

      • 주의! Number 타입 매개변수를 가진 제네릭 타입에 Integer 타입 매개변수를 가진 객체 → Compile error!!

      • Run time에 치환한 정보(객체)가 전달되지 않기 때문이다!!

    • 주의! 치환된 객체가 전달되지 않는 이유! Untitled
      • Box를 기대하는데, Box가 넘어간다는 것은 Box을 Box가 상속한다는 뜻임 → 허용하지 않는 이유!
      • 그래서 Box과 Box을 같은 타입으로 보게 됨 → Object를 상속하게 된다는 것
      • 아예 상관없는 타입으로 둬서 안정성을 높인 것이다
      • 치환된 것이 다르면 아무 상관 없는 타입이다! ⇒ 이는 제네릭의 불공변성(invariance) 때문임. 즉, T가 S의 서브타입이라도 List는 List의 서브타입이 아니다.
    • Generic의 상속관계와 서브 타입의 관계 Untitled
      • 같은 타입 매개변수를 가지면 클래스끼리 원래 가지고 있던 상속관계가 유지됨

      • 이 상속관계는 다형성을 나타냄. 타입별로 가리킬 수 있는 관계를 나타내주는 것!

      • ArrayList가 가리킬 수 있는 것은 List, Collection도 가리킬 수 있다는 것임

        Untitled

      • 다른 타입 매개변수를 가지면 상속관계가 유지되지 않음

      • 이 두 경우가 합쳐져서 위의 그림처럼 PayLoadList끼리는 상속관계를 유지하지 않고, String으로 타입이 같은 상속관계는 유지된다. 호출하는 범위(상속관계)는 P가 뭐로 치환되는지랑 관계없음

  • 중요 Wildcards and Subtyping
    • ? 치환: Wildcard라고 부름 Untitled
      • 변수 선언시에만 사용가능, 매개변수에서 가장 많이 사용됨

        Untitled

      • extends가 붙으면 Upper Bounded Wildcards

      • super가 붙으면 Lower Bounded Wildcards

      • 그냥 쓰이면 Unbounded Wildcards

        Untitled

    • Upper Bounded Wildcards
      • 내 객체에 변화 x

      • 할 수 있는 일이 줄어듬

      • 아래타입으로 위타입 가리킬 수 없음

      • 구체적 특정 x

      • 안될수도 있으니 쓰기를 허용하지 않음

      • 읽기 전용!

      • 예를 들어 Double이 들어올수도 Character가 들어올수도 있는데 특정이 되지 않기 때문에 쓰기를 할 수 없음 ← Number를 Double로 받을 수 없기 때문이다, 최상위 클래스에서 할 수 있는 일이 가장 적기에 안정성이 떨어진다.

        Untitled

    • Lower Bounded Wildcards
      • 내 객체에 변화 O

      • 위타입으로 아래타입 가리킬 수 있음

      • 구체적으로 특정됨

      • 쓰기전용!

      • 예를 들어 Number여도 Integer 삽입 가능하고 Object여도 Integer 받아서 삽입 가능하다! ←Number와 Object 모두 Integer를 가리킬 수 있기 때문이다, 최하위클래스에서 가장 할수있는 일이 많기에 안정성이 확보된다.

        Untitled

    • Unbounded Wildcards Untitled
      • 전부 다 받는다.
  • 중요 Bounded 총정리

    Bounded Type Parameters

    상한 경계 (Upper Bounded Type Parameters)

    상한 경계는 타입 매개변수를 특정 클래스나 인터페이스의 서브타입으로 제한합니다. 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 등)만 타입 매개변수로 사용할 수 있음을 의미합니다.

    하한 경계 (Lower Bounded Type Parameters)

    하한 경계는 타입 매개변수를 특정 클래스의 슈퍼타입으로 제한합니다. 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>): 타입 매개변수를 특정 클래스의 슈퍼타입으로 제한.

      상한 경계는 주로 읽기 전용 작업에 유용하고, 하한 경계는 쓰기 작업에 유용합니다. 이를 통해 제네릭 타입의 사용을 더욱 안전하고 강력하게 만들 수 있습니다.

      제네릭과 상속관계에 따른 서브타입의 관계

      기본 개념

    1. 클래스 상속: 일반적으로 클래스 B가 클래스 A를 상속받으면, B는 A의 서브타입입니다.

    2. 제네릭 타입: 제네릭 타입은 특정 타입을 파라미터로 받아서 동작합니다. 예를 들어, List<T>는 타입 파라미터 T를 사용하는 제네릭 타입입니다.

      제네릭 타입과 서브타입 관계

      제네릭 타입의 서브타입 관계는 기본 클래스 상속과 다르게 작동합니다. 제네릭 타입 A와 B의 관계는 각 타입 파라미터가 관계에 영향을 미치게 됩니다.

      1. 제네릭 타입의 서브타입 관계

      일반적인 클래스 상속 관계가 있다고 가정할 때:

    • 만약 DogAnimal을 상속한다면, DogAnimal의 서브타입입니다.

    • 하지만 List<Dog>List<Animal>의 서브타입이 아닙니다.

      예를 들어:

      java코드 복사
      List<Dog> dogs = new ArrayList<>();
      List<Animal> animals = dogs; // 컴파일 오류
      

      위의 코드에서 List<Dog>List<Animal>의 서브타입이 아닙니다. 이것은 제네릭의 불공변성(invariance) 때문입니다. 즉, TS의 서브타입이라도 List<T>List<S>의 서브타입이 아닙니다.

      2. 와일드카드 사용

      제네릭 타입의 서브타입 관계를 더욱 유연하게 하기 위해 와일드카드를 사용할 수 있습니다.

      상한 경계 와일드카드 (Upper Bounded Wildcards)

      java코드 복사
      List<? extends Animal> animals = new ArrayList<Dog>();
      

      위의 코드에서 List<? extends Animal>Animal의 서브타입인 모든 리스트를 참조할 수 있습니다. 이는 List<Dog>, List<Cat> 등도 참조할 수 있음을 의미합니다. 그러나, 이 경우 리스트에 요소를 추가할 수 없습니다.

      하한 경계 와일드카드 (Lower Bounded Wildcards)

      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> {}
      

      여기서 DogBoxBox<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, CatAnimal의 서브타입 리스트를 인수로 받을 수 있습니다. 반면, addDog 메소드는 List<? super Dog>를 매개변수로 받아 Dog의 슈퍼타입 리스트를 인수로 받을 수 있으며, Dog 객체를 추가할 수 있습니다.

      요약

    • 제네릭 불공변성: List<Dog>List<Animal>의 서브타입이 아닙니다.

    • 상한 경계 와일드카드 (<? extends T>): T의 서브타입을 허용합니다.

    • 하한 경계 와일드카드 (<? super T>): T의 슈퍼타입을 허용합니다.

    • 제네릭 클래스 상속: 제네릭 클래스의 서브클래스는 타입 파라미터와 별도로 상속 관계를 가집니다.

      이러한 개념을 통해 제네릭 타입을 더욱 유연하고 안전하게 사용할 수 있습니다.

      Wildcard and Subtyping

      자바의 제네릭에서 와일드카드(wildcard)와 서브타이핑(subtyping)은 중요한 개념입니다. 이를 통해 제네릭 타입을 더욱 유연하게 사용할 수 있으며, 특히 메소드의 인수와 반환 타입을 정의할 때 유용합니다. 와일드카드는 주로 세 가지 형태로 사용됩니다: 무제한 와일드카드, 상한 경계 와일드카드, 하한 경계 와일드카드.

      와일드카드의 종류

    1. 무제한 와일드카드 (<?>)

    2. 상한 경계 와일드카드 (<? extends T>)

    3. 하한 경계 와일드카드 (<? super T>)

      1. 무제한 와일드카드 (<?>)

      무제한 와일드카드는 어떤 타입이든 허용됩니다. 이는 타입 안정성을 유지하면서 다양한 타입을 받아들일 수 있습니다.

      java코드 복사
      public void printList(List<?> list) {
          for (Object elem : list) {
              System.out.println(elem);
          }
      }
      

      위의 예제에서 printList 메소드는 어떤 타입의 리스트든 받아들일 수 있습니다.

      2. 상한 경계 와일드카드 (<? extends T>)

      상한 경계 와일드카드는 특정 타입 T의 서브타입만 허용합니다. 이는 주로 읽기 전용으로 사용할 때 유용합니다.

      java코드 복사
      public void processAnimals(List<? extends Animal> animals) {
          for (Animal animal : animals) {
              animal.eat();
          }
      }
      

      위의 예제에서 processAnimals 메소드는 Animal의 서브타입(Dog, Cat 등)의 리스트를 인수로 받을 수 있습니다.

      3. 하한 경계 와일드카드 (<? super T>)

      하한 경계 와일드카드는 특정 타입 T의 슈퍼타입만 허용합니다. 이는 주로 쓰기 전용으로 사용할 때 유용합니다.

      java코드 복사
      public void addDogs(List<? super Dog> animals) {
          animals.add(new Dog());
      }
      

      위의 예제에서 addDogs 메소드는 Dog의 슈퍼타입(Animal, Object 등)의 리스트를 인수로 받을 수 있으며, Dog 객체를 추가할 수 있습니다.

      와일드카드와 서브타이핑의 예제

      예제 1: 무제한 와일드카드

      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);
          }
      }
      

      예제 2: 상한 경계 와일드카드

      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);
          }
      }
      

      예제 3: 하한 경계 와일드카드

      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 등 다양한 타입으로 작동할 수 있게 해줍니다.

      차이점 요약

    1. 타입 정보 제공:

      • <?> (무제한 와일드카드): 어떤 타입이든 허용하지만, 구체적인 타입 정보를 제공하지 않음.
      • T (타입 매개변수): 특정 타입을 지정하며, 해당 타입 정보를 제공함.
    2. 사용 위치:

      • <?>: 주로 메소드 매개변수나 반환 타입에서 사용되어, 다양한 타입을 처리할 수 있음.
      • T: 제네릭 클래스나 메소드 선언부에서 사용되어, 클래스나 메소드가 특정 타입으로 동작하도록 함.
    3. 읽기와 쓰기:
      - <?>: 읽기 전용으로 사용될 때 유용. 리스트에서 요소를 읽을 수 있지만, 요소를 추가할 수는 없음.
      - 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와 List<? extends Integer>의 차이

      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); // 가능
      

      사용 예제와 차이점

      예제 1: 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); // 요소 읽기 가능
              }
          }
      }
      

      예제 2: 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와 List<? extends Integer>의 차이

      List<T extends Integer>List<? extends Integer>는 자바 제네릭에서 서로 다른 상황에서 사용됩니다. 이들은 비슷한 의미를 가지지만, 사용되는 문맥과 목적이 다릅니다. 자세히 살펴보겠습니다.

      List<T extends Integer>

      List<T extends Integer>는 제네릭 타입 매개변수 TInteger의 서브타입임을 명시합니다. 이는 제네릭 클래스나 메소드에서 사용되며, 해당 클래스나 메소드 내에서 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>TInteger의 서브타입임을 보장합니다. 이 제네릭 클래스는 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>를 파라미터로 받아, 리스트의 요소를 읽기만 할 수 있습니다.

      사용 문맥의 차이

    1. 제네릭 클래스/메소드 선언에서의 T extends Integer:

      • 제네릭 타입 매개변수 T를 정의하고, 이를 제네릭 클래스나 메소드 내에서 사용합니다.
      • TInteger의 서브타입임을 보장받으며, 해당 타입을 사용하여 요소를 추가하거나 읽을 수 있습니다.
      • 사용 예:
        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);
            }
        }
        
    2. 메소드 파라미터/반환 타입에서의 ? extends Integer:
      - 제네릭 타입을 유연하게 받아들이기 위해 사용됩니다.
      - Integer 또는 그 서브타입을 포함하는 리스트를 받아들이지만, 요소 추가는 불가하고 읽기만 가능합니다.
      - 사용 예:

          ```java
          java코드 복사
          public void printList(List<? extends Integer> list) {
              for (Integer num : list) {
                  System.out.println(num);
              }
          }
          
          ```
          

      요약

    • List<T extends Integer>는 제네릭 클래스나 메소드 선언에서 사용되며, TInteger의 서브타입임을 명시합니다. 이 타입은 클래스나 메소드 내에서 요소 추가와 읽기가 모두 가능합니다.

    • List<? extends Integer>는 와일드카드 타입으로, 주로 메소드 파라미터나 반환 타입에서 사용됩니다. 이 리스트는 Integer 또는 그 서브타입을 포함할 수 있지만, 요소 추가는 불가하고 읽기만 가능합니다.

      따라서, 두 개념은 같은 의미로 사용될 수 없습니다. List<T extends Integer>는 제네릭 타입 매개변수를 정의하는 데 사용되며, List<? extends Integer>는 다양한 서브타입을 처리할 때 유용하게 사용됩니다.

Collection추가강의Collection 추가 강의

  • Iterable → Iterator : Enhanced for문의 사용 Untitled
    • interface Iterable<T>의 메소드에는 Iterator<T> iterator() 메소드가 있다. 이 함수는 iterator를 반환하는 역할을 한다
    • 중요 for(E element: list){element호출}식의 Enhanced for문은 for(Iterator<E> i = list.iterator(); i.hasNext();){i.next()호출}식으로 자동으로 변환되어 컴파일 된다!
  • 중요 ListIterator
    • public interface ListIterator extends Iterator

      • ListIterator 인터페이스는 Iterator 인터페이스를 상속받아 여러 기능을 추가한 인터페이스이다. Iterator는 컬렉션의 요소에 접근할 때 한 방향으로만 이동할 수 있지만, ListIterator 인터페이스는 컬렉션 요소의 양방향 이동을 지원한다.
      • 현재 요소의 위치가 아닌 요소 사이사이에 커서로 위치를 나타낸다.
      • 직접 구현되는 경우는 드물고, 대신 ArrayList, LinkedListList 인터페이스를 구현하는 클래스의 내부 클래스로 구현됨
      • 중요 ListIterator에서 iterator를 통해 요소 삽입, 제거, 갱신이 가능하다. 그래서 add, remove, set을 iterator를 통해서 사용가능하다
    • ListIterator 메소드

      Untitled

      • ListIterator를 사용하여서 add, remove, set, nextIndex, previousIndex등을 사용한다.
    • 중요 ListIterator의 cursor 개념

      • cursor는 요소들 사이사이에 위치함 Untitled
        • cursor와 add함수: 항상 cursor의 앞에 요소를 삽입한다. 만약 역방향이어도 순방향인채로 cursor의 앞에 요소를 삽입한다. 주의! 이는 역방향 순회시 add후 previous가 삽입한 요소를 가리키는 것을 의미한다!!
        • cursor와 remove, set함수: 항상 마지막으로 방문했던 요에게 적용한다. 커서가 지나온 사이의 요소를 의미한다!
    • 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를 진행시 추가한 객체를 방문하게 될 수 있음을 주의하자!

  • Arrays의 public static List asList(T… a) 메소드와 List의 public T[] toArray(T[] a) 메소드
    • Arrays의 asList 메소드
      • 주의! Arrays.asList는 fixed-size의 list를 리턴한다! Untitled
        • Arrays.asList로 받은 List가 크기 변화를 허용하지 않고 예외를 내는 모습이다. → 다시 그 list를 생성자를 통해서 크기 변화를 허용해주거나, 배열처럼 크기 변화(연산)를 허용하지 않고 사용해야한다!
      • asList는 매개변수로 Variable Arguments → 가변인자는 매개변수 나열(,로 나열)이거나 배열을 전달해주면 된다
    • List의 toArray 메소드
      • 주의! List객체의 toArray는 매개변수로 배열을 받는다! Untitled
        • List 타입을 문자열 배열로 매개변수를 전달해야하는 상황에 toArray를 통해서 배열 레퍼런스를 전달한다

          Untitled

        • 여기서 매개변수로 배열을 넣어줬는데, 오직 Type 전달의 목적으로 써주는 것이다! 그래서 아무 의미도 없는 new String[0]을 전달해준 것. 이 매개변수를 통해 Array의 타입을 추론한다!

  • 중요 순서를 유지하지 않는 class HashSet (interface Set)
    • Set

      Untitled

      Untitled

      • Set 집합은 중복을 허용하지 않는다. null 객체가 2개였는데, 하나로 출력됨으로 확인할 수 있다.
      • Set 집합은 순서개념이 없다. 순서가 바뀔 수 있고 HashSet은 순서를 지켜주지 않는다.
      • 사용자형 클래스 타입의 Set을 정의한다면 equals()와 hashCode() 메소드를 오버라이딩해야 정상적으로 작동한다! 오버라이딩 하지 않으면 기본을 호출한다
      • equals() → 중복검사
      • hashCode() → Objects 클래스의 hash() 메소드에 클래스의 필드값을 넣어서 hash값을 리턴한다! 해시테이블을 사용하여 요소를 저장하기때문에 꼭 필요하다.
    • HashSet

      Untitled

      • 특징
        • HashSet은 내부적으로 배열을 생성함

        • HashSet은 HashMap을 이용함

        • key값: hashCode()%n을 수행 → 0~n-1의 index 생성

        • 만약 같은 key값이 나오면 연결리스트 생성 (체이닝)

        • 그 후 equals()로 검사하여 연결리스트를 돌면서 겹치면 저장 x

          ![Untitled](https://prod-files-secure.s3.us-west-2.amazonaws.com/d48a8b3a-0902-461a-8e65-1e3bd3ed9699/886d5a58-8f05-4909-babb-b3b7ee1203b6/Untitled.png)

          ⇒ 내가 근거한 같은 값을 통해 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값을 리턴하므로 문제 없게 된다!

  • 중요 순서를 유지하는 class TreeSet
    • TreeSet Untitled
      • TreeSet은 NavigableSet 인터페이스를 구현함
      • 내부적으로는 NavigableMap 인터페이스를 구현한 TreeMap을 사용하여 요소를 저장하고 정렬함
      • 정렬된 순서를 유지한다
      • 동일한 요소를 중복하지 않는다
      • NavigableSet 인터페이스를 통해 다양한 탐색 및 정렬, 부분 집합 반환에 대한 함수를 사용할 수 있음
        • 중요 NavigableSet 주요 메소드 NavigableSet 인터페이스는 다양한 탐색 및 범위 조작 메소드를 제공합니다. 다음은 주요 메소드들입니다: Untitled
          • lower(E e): 지정된 요소보다 작은 가장 큰 요소를 반환합니다. 없으면 null을 반환합니다.

          • floor(E e): 지정된 요소보다 작거나 같은 가장 큰 요소를 반환합니다. 없으면 null을 반환합니다.

          • ceiling(E e): 지정된 요소보다 크거나 같은 가장 작은 요소를 반환합니다. 없으면 null을 반환합니다.

          • higher(E e): 지정된 요소보다 큰 가장 작은 요소를 반환합니다. 없으면 null을 반환합니다.

          • pollFirst(): 첫 번째(가장 작은) 요소를 제거하고 반환합니다. 집합이 비어 있으면 null을 반환합니다.

          • pollLast(): 마지막(가장 큰) 요소를 제거하고 반환합니다. 집합이 비어 있으면 null을 반환합니다.

            Untitled

          • descendingSet(): 역순으로 정렬된 집합을 반환합니다.

            Untitled

          • subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive): 지정된 범위의 요소를 포함하는 부분 집합을 반환합니다.

          • headSet(E toElement, boolean inclusive): 지정된 요소보다 작은 요소를 포함하는 부분 집합을 반환합니다.

          • tailSet(E fromElement, boolean inclusive): 지정된 요소보다 큰 요소를 포함하는 부분 집합을 반환합니다.

      • AbstractSet은 Set을 구현한 클래스임, Cloneable은 클론 복제가 가능한지 여부를 나타냄. Serializable은 직렬화가 가능함을 나타냄
    • 중요 TreeSet의 사용자형 클래스 타입의 사용방법 Untitled
      • 사용자형 클래스 타입을 사용할 경우 그 타입은 Comparable로 캐스팅이 가능해야한다! → 2가지 방법!

      • 기본적으로 compareTo(), compare() 메소드는 같으면 0을 리턴, x>y이거나 y>x이면 음수나 양수를 리턴함

        #1 클래스의 Comparable 구현 및 compareTo 오버라이딩

        1) 클래스에서 Comparable 인터페이스 구현

        2) compareTo() 오버라이딩 → 객체와 매개변수 비교

        Untitled

        #2 Comparator 구현 및 사용자형 Comparator 만들어주기

        1) Comparator 정의 및 Comparator 인터페이스 구현

        2) compare() 메소드 정의 → 매개변수끼리 비교

        3) TreeSet의 생성자에 정의한 Comparator의 객체 넣어줌

        Untitled

        Untitled

  • interface Queue와 interface Deque
    • Queue의 주요 메소드 Untitled
      • 고정 사이즈의 Queue를 사용시에 비정상적으로 사용시에 예외를 발생시킬지 특별한 값(true/false or null등)을 return할지 스타일 별로 선택하면 된다

        ![Untitled](https://prod-files-secure.s3.us-west-2.amazonaws.com/d48a8b3a-0902-461a-8e65-1e3bd3ed9699/58d7f808-9973-461d-a175-fa77832e1682/Untitled.png)
        
        - java.util.concurrent.LinkedBlockingQueue
            
            ㄴ 생성자에 사이즈 넣어준다!
            
        - Insert시 용량을 초과한다면 예외를 발생 or false 리턴
            
            → 평상시에는 true 리턴 동일
            
        - Remove, Examine시 empty이면 예외를 발생 or null리턴
            
            → 평상시에는 객체 리턴 동일
            

        Queue의 메소드 사용 예시

        Untitled

      • LinkedBlockingQueue에서 offer 실패시 false를 리턴하고, peek과 poll실패시 null를 리턴함을 알 수 있다!

    • Queue, Deque, Stack메소드 비교
      • Deque는 Stack을 구성한다!

      • Deque은 앞 뒤에서 모두 삽입, 삭제, 탐색 연산이 가능함

        • First와 Last를 붙여서 사용할 수 있음
      • Deque stack = new ArrayDeque<>();
        - Deque를 사용하여 stack 구현, 실 객체는 ArrayDeque나 LinkedList 사용; pop, push, peek이 구현되어있음!

        Untitled

원본 노션 링크

https://believed-poinsettia-f0f.notion.site/7-a85f1887b7a246df86d7cb33d79678d1?pvs=4

0개의 댓글