[JAVA] 개념 정리 - 6

leesoyeong·2024년 1월 7일

Java

목록 보기
7/12
post-thumbnail

교재 Must Have 이재환의 자바 프로그래밍 입문을 학습하며 정리한 내용입니다.

20. 제너릭

20.1 제너릭의 필요성

제너릭을 적용하기 이전의 코드는 객체를 돌려받을 때 형변환을 잊지 말고 해야한다는 불편함이 있고, 코드 진행상 개발자가 실수를 해도 실수가 드러나지 않을 수도 있다는 잠재적 위험이 존재한다.

20.2 제너릭 기반의 클래스 정의하기

제너릭은 클래스, 메서드에서 사용할 자료형을 나중에 확정하는 기법이다. 클래스나 메서드를 선언할 때가 아닌 사용할 때, 즉 객체를 생성할 때나 메서드를 호출할 때 정한다는 의미다.

객체 생성시 결정이 되는 자료형의 정보를 T로 대체한다. 그리고 다이아몬드 연산자를 통해 자료형을 전달한다.

  • 제너릭을 사용하지 않는 코드

    class Camp {
    private Object unit;
    public void set(Object unit) {
      this.unit = unit;
      }
    public Object get() {
       return unit;
       }
    }  
    
  • 제너릭을 사용하는 코드

    class Camp<T>{
    private Object unit;
    public void set(T unit) {
      this.unit = unit;
      }
    public T get() {
       return unit;
       }
    }

Camp<NPC>(매개변수화타입) human = new Camp<NPC>(타입인수)();
Camp<NPC> human = new Camp<>();  // 뒤쪽은 추론가능하므로 생략
  • 제너릭 관려 ㄴ변수 용어
    | 용어 | 대상 |
    | 타입 매개변수(type parameter) | Camp(<)T(>)에서 T |
    | 타입 인수(type argument) | Camp(<)T(>)에서 Npc |
    | 매개변수화 타입(parameterized type) | Camp(<)Npc>
  • 타입 매개변수의 이름 규칙
    • 보통 한 문자로 이름을 짓는다.
    • 대문자로 이름을 짓는다.

20.3 제너릭 기반의 코드로 개선한 결과

  • 중복된 코드의 결합 & 간소화
  • 데이터를 가져올 때 형변환 없이 가져올 수 있음
  • 데이터 대입 시 다른 자료형이 대입되는 것 방지 -> 강한 자료형 체크

20.4 매개변수가 여러 개일때 제너릭 클래스의 정의

class Camp<T1, T2> {
    private T1 param1;
    private T2 param2;
}
.
.
public static void main(String[] args) {
   Camp<String, Integer> camp = new Camp<>();
.
.
}

20.5 제너릭 클래스의 매개변수 타입 제한하기

상속관계를 표시하여 매개변수의 타입을 제한 할 수도 있다.

  • 매개변수 타입을 제한하지 않은 경우
    class Camp<T> {
     private T ob;
    .
    .
    public int toIntValue() {
       return ob.intValue();  // ERROR
    }
    } 
  • 매개변수 타입을 제한하는 경우
    class Camp<T extends Number> {
     private T ob;
    .
    .
    public int toIntValue() {
       return ob.intValue();  // OK
    }
    } 

매개변수 타입을 제한하지 않은 경우, 아무 자료형이나 들어올 수 있기에 래퍼 클래스의 메서드를 호출하면 에러가 발생한다. 그러나 매개변수 타입을 제한하는 경우 한정을 했기 때문에 intValue() 메서드를 사용할 때 에러 걱정을 할 필요가 없다.

20.6 제너릭 메서드의 정의

클래스 전부가 아닌 메서드 하나에 대해서도 제너릭으로 정의할 수 있다.

public static <T> T showData(T data) // 앞의 T의 자료형이 뒤의 매개변수의 자료형
을 결정한다. 
// 제너릭 메서드의 T는 메서드 호출 시점에 결정된다. 
클래스명.<String>showData("");
// 타입 인수는 생략이 가능하다. 생략된 인수는 매개변수로 들어온 데이터의 자료형으로
추론한다.   
클래스명.showData("");

21. 컬렉션 프레임워크

21.1 자료구조

대량의 데이터를 효율적으로 관리하는 메커니즘을 자료구조라고 한다.
자료구조로는 배열, 리스트, 스택, 큐, 트리 등이 있다.

배열

배열은 크기가 고정되어 있어 데이터를 추가하거나 삭제할 수 없다.

리스트

리스트는 원소가 원소를 가리켜서 관리하는 자료구조이다. 데이터가 추가되거나 삭제될 때 연결하는 정보만 바꾸면 쉽게 추가, 삭제가 된다.

스택

스택은 한 쪽 끝에서만 자료를 넣거나 뺄 수 있는 선형구조(LIFO, Last In First Out)로 되어있다. 자료를 넣는 것을 '밀어 넣는다'라 하여 푸시(push)라고 하고 반대로 넣어둔 자료를 꺼내는 것을 팝(pop)이라고 한다.

큐(queue)먼저 집어넣는 데이터가 먼저 나오는 FIFO(First In First Out) 구조로 저장하는 자료구조르 ㄹ말한다.

트리 구조

부모 노드 밑에 여러 자식 노드가 연결되고, 자식 노드 각각에 다시 자식 노드가 연결되는 형태의 자료구조를 트리라고 부른다.
자식노드에서 부모 쪽으로 계속해서 타고 올라가다 보면 결국 부모가 없는 하나의 노드로 이어지게 되는데, 이 노드를 루트 노드라고 부른다. 루트 노드를 중심으로 뻗어나가는 모습이 나무의 구조와 비슷하여 트리라는 이름이 붙었다.

21.2 컬렉션 프레임워크의 구조

자바에서는 자료구조를 개발자가 편리하게 사용할 수 있도록 컬렉션 프레임워크를 제공한다.

  • 컬렉션 프레임워크에 속하는 인터페이스

    인터페이스설명구현 클래스
    List<E,>순서가 있는 데이터 집합이다. 추가된 데이터 순서도 유지되며, 데이터 중복도 허용된다.ArrayList, LinkedList, Vector, Stack
    Set<E,>중복된 데이터가 제거되는 등 추가된 데이터의 순서가 유지되지 않는 데이터 집합이다. 데이터 중복이 허용되지 않는다.HashSet, TreeSet
    Map<K,V>키(key)와 값(Value)으로 이루어진 데이터들의 집합이다. 키는 중복을 허용하지 않지만 값은 중복될 수 있다. 출석부에 1번 홍길동과 2번 홍길동(동명이인)이 있는 경우를 들 수 있다.HashMap, TreeMap, Hashtable,Properties
    Queue<E,>순서가 있는 데이터 집합이다. 추가되 ㄴ데이터의 순서도 유지되며, 데이터 중복도 허용된다.LinkedList
    • Vector, Stack, Hashtable, Properties와 같은 클래스들은 컬렉션 프레임 워크가 만들기 전부터 존재하던 것이기 때문에 기존 코드와의 호환을 위해 남겨져있을뿐 현재는 사용하지 않는다.

21.3 List<E,> 인터페이스를 구현하는 컬렉션 클래스들

  • ArrayList<E,> : 배열 기반 자료구조, 배열을 이용하여 객체를 저장한다.
  • LinkedList<E,>: 연결 기반 자료구조, 앞의 자료구조에서 본 리스트를 구성하여 객체를 저장한다.

공통 특성

  • 데이터의 저장 순서가 유지된다.
  • 동일 데이터의 중복 저장을 허용한다.

ArrayList

// 객체 생성 
List<String> list = new ArrayList<>();
// 객체 저장 : 순서 있음, 중복 허용
list.add("");
.
.
//객체 참조
for(int i = 0; i<list.size(); i ++) >
    System.out.print(list.get(i) + '\t');
                                             
// 객체 삭제
   list.remove(0);                                         

ArrayList vs LinkedList

ArrayList는 배열은 아니지만 배열 기반이라 데이터의 추가, 삭제보다는 참조가 LinkedList보다 빠르다는 장점이 있다. 반면 LinkedList는 리스트 기반이라 데이터 참조 속도보다는 데이터의 추가, 삭제가 ArrayList보다 쉽다는 장점이 있다.
그래서 만드는 시점에 추가될 데이터 성격을 생각해서 두가지 중 하나를 선택하여 만들고 사용하 ㄹ때는 그냥 List로 사용하면 된다.

단점장점
ArrayList<E,>- 객체가 추가될 때 저장 공간을 늘리는 과정에서 시간이 비교적 많이 소요된다.. - 객체의 삭제 과정에서 많은 연산이 필요할 수 있다. 따라서 느릴 수 있다.저장된 객체의 참조가 LinkedList보다 빠르다.
LinkedList<E,>저장된 객체의 참조과정이 배열에 비해 복잡하다.저장 공간을 늘리는 과정이 간단하다.

ArrayList<E,>에서 데이터 추가

LinkedList<E,>에서 데이터 추가

배열과 비교하여 리스트에서 데이터의 추가는 다음과 같이 연결 정보만 만들어준다.

Iterator 사용하기

Iterable 인터페이스를 구현했기 때문에 저장된 인스턴스의 순차적 접근에 향상된 기능의 for문이나 Iterator 반복자를 이용할 수 있다.

//향상된 기능의 for문으로 객체 참조
for(String s : list)

// 반복자 획득
Iterator<String> itr = list.iterator();

//반복자를 이용한 순차적 참조
String str;
while(itr.hasNext()) {
   str = itr.next();
.
.
   if(str.equals("dd"))
      itr.remove();

리스트 형식 바꾸기

리스트는 배열처럼 선언과 동시에 초기화가 불가능하다. 그러나 Arrays 클래스의 유틸 메서드를 사용해서 다음과 같이 사용할 수 있다.
List<String,> list = Arrays.asList("","","","");

인수로 전달된 객체들은 저장한 컬렉션 객체를 생성 및 반환하다. 이렇게 생성된 리스트 객체는 객체에 요소를 추가하거나 삭제할 수 없는 객체이다.
List<String,> list = Arrays.asList("","","","");
list = new ArrayList<>(list);

데이터의 성격에 따라 list를 선택하고 사용하고는 List로 정의하는데, 사용중에도 성격을 바꿀 수 있다.
list = new ArrayList<>(list);
list = new LinkedList<>(list);

컬렉션 프레임워크에 기본 자료형을 데이터로 사용하기

컬렉션 프레임워크는 제너릭을 사용하여 자료형을 제한한다. 이때 제너릭 부분에 클래스 타입을 지정해주어야한다. 기본 자료형을 직접 적어줄수는 없다.
List<Integer,> list = new LinkedList<>(); // O
List<int,> list new LinkedList<>(); //X
하지만 앞서 배웠던 래퍼 클래스들은 오토 박싱과 오토 언박싱이 되기 때문에 자료형만 래퍼 클래스로 적어줄 뿐 기본 자료형을 사용하는데 제약사항은 없다.
LinkedList<Integer,> list = new LinkedList<>()'
//저장과정에서 오토 박싱
list.add(10);
for(Iterator<Integer,> itr = list.iterator(); itr.hasNext(); ) {
int n = itr.next(); // 오토 언박싱
}

21.4 Set<E,> 인터페이스를 구현하는 컬렉션 클래스들

특성

  • 저장 순서가 유지되지 않는다.
  • 데이터 중복 저장을 허용하지 않는다.

HashSet 사용하기

set<String> set = new HashSet<>();
  set.add("dd");
  
  // 반복자를 이용한 전체 출력
  for(Iterator<String> itr = set.iterator(); itr.hasNext(); )
  
  //향상된 기능의 for문을 이용한 전체 출력
  for(String s : set)

HashSet으로 지정하여 객체를 만들고 사용은 set으로 한다. add() 메서드를로 추가한다. Iterator을 이용하여 반복자를 구해올 수 있다. hasNext() 메서드로 다음 반복이 가능한지 알아온다. next() 메서드로 Set 컬렉션 프레임워크안의 요소를 가져올 수 있다. Set 컬렉션 프레임워크는 Iterable을 구현했기 때문에 향상된 기능의 for문을 사용할 수 있다.

hash와 hashCode() 메서드

Set<E,> 인터페이스를 구현하는 컬렉션 클래스들은 데이터 중복 저장을 허용하지 않는다고 했다. 중복 저장을 방지하려면 데이터가 입력되기 전 이미 있는 데이터인지 검색해보아야한다. 이때 사용하는 알고르지므이 해시이다.

해시는 정보를 저장하거나 검색할 때 사용하는 알고리즘이다.
분류 대상을 정하고, 해시 알고리즘을 적용하면서 분류를 해놓아 탐색속도가 빨라진다.

Object 클래스의 hashCode 메서드는 이렇듯 객체들을 부류하는 역할으르 한다. 그래서 HashSet에서 중복 저장을 막으려면 hashCode() 메서드에서 반환하는 해시 코드값에 해당하는 내부 목록을 찾는다. 그러면 비교해야할 탐색의 대상이 확 줄어든다.

HashSet의 중복 비교

hashCode() 메서드를 제공해준다.
public int hashCode() {
return java.util.Objects.hash(가변 인수);
}

TreeSet

컬렉션 프로그램워크에서 Tree로 시작하는 클래스는 데이터를 추가한 후 결과를 출력하면 결괏값이 정렬된다. 그러므로 TreeSet은 자료의 중복을 허용하지 ㅇ낳으면서 출력값을 정렬하는 클래스이다.
TreeSet<String,> tree = new TreeSet<>();

TreeSet의 정렬

자바는 TreeSet의 정렬을 구현하기 위해 이진탐색트리를 사용한다.
트리 자료구조에서 각 자료가 들어가는 공간을 노드라고 한다. 그리고 위아래로 연결된 노드의 관계를 부모-자식 노드라고 한다. 이진 탐색 트리는 노드에 저장되는 자료의 중복을 허용하지 않고, 부모가 가지는 자식 노드의 수가 2개 이하이다.

  • 각 노드의 왼쪽 서브 트리에는 해당 노드의 값보다 작은 값을 가진 노드들로 이루어져있다.
  • 각 노드의 오른쪽 서브 트리에는 해당 노드의 값보다 큰 값을 지닌 노드들로 이루어져있다.
  • 중복된 노드가 없어야한다.
  • 왼쪽 서브 트리, 오른쪽 서브 트리 또한 이진 탐색 트리다.

따라서 어떤 특정 값을 찾으려 할 때 한 노드와 비교해 배교한 노드보다 작은 값이면 왼쪽 자식 노드 방향으로, 그렇지 않으면 오른쪽 자식 노드 방향으로 이동한다. 따라서 비교 범위가 평균 2분의 1만큼씩 줄어들어 효과적으로 자료를 거색할 수 있다.

예시

이렇게 만들어진 이진 탐색 트리를 맨 왼쪽 노드부터 시작해서 왼쪽 -> 부모 -> 오른쪽(뭉치) 순으로 순회하면 오름차순이된다. 순회하다가 노드의 끝을 만나면 부모 노드로 올라간다.

어떤 기준으로 비교할지는 프로그래머가 직접 구현을 해야한다.
interface Comparable<T,>
-> int compareTo(T o)

  • 인수로 전달된 o가 작다면 양의 정수 반환
  • 인수로 전달된 o가 크다면 음의 정수 반환
  • 인수로 전달된 o와 같다면 0을 반환

Comparable<T,> 인터페이스의 구현 결과를 근거로 객체의 크기 비교가 이루어진다. 따라서 TreeSet<T,>에 저장할 객체들은 모두 Comparable<T,> 인터페이스를 반드시 구현할 클래스의 객체여야한다. 아니면 예외가 발생한다.
class Student implements Comparable<Student,> {
pulblic int compareTo(Student s) {
return this.age - s. age;
}

comparator<T,> 인터페이스

Comparator<T,> 역시 정렬을 구현하는데 사용하는 인터페이스로 compare() 메서드를 구현해야한다. 기존 클래스가 Comparable<T,> 인터페이스를 구현하여 이미 정렬 조건이 있다고 하더라도 새로운 정렬 조건을 주고 싶을 때 사용할 수 있다.

String 클래스는 Comparable<E,> 인터페이스를 구현하여 이미 사전순으로 정렬되도록 만들어져 있다. 이 String 클래스의 정렬 조건을 변경하려면
Set<String,> tree = new TreeSet<>(new MyStringComparator());
로 개발자가 만든 클래스 안의 compare() 메서드로 하겠다는 의미이다.

응용:중복된 객체 삭제

// set의 특성을 이용하여 중복 제거
HashSet<String,> set = new HashSet<>(list)
//다시 set을 list로 반환
list = new ArrayList<>(set);

21.5 Queue<E,> 인터페이스를 구현하는 컬렉션 클래스들

Queue의 구현

LinkedList<E,>는 List<E,>와 동시에 Queue<E,>를 구현하는 컬렉션 클래스이다. 따라서 어떠한 타입의 참조 변수로 참조하느냐에 따라 '리스트'로도 '큐'로도 동작할 수 있다.

Queue<String> que = new LinkedList<>();

//데이터 저장
que.offer("A");
.
.
//무엇이 다음에 나올지 확인
System.out.println("next : " +que.peek());
//첫번째 객체 꺼내기
System.out.println(que.poll());
System.out.println(que.size());
.
.

Stack의 구현

자바에서 Deque를 기준으로 스택을 구현한다.
Deque<String,> deq = new ArrayDeque<>();
Deque<String,> deq = new LinkedList<>();

다만 Deque를 이용하면 메서드를 사용하는 방법에 따라 자료구조를 큐처럼 사용할 수도 있고, 스택처럼 사용할 수도 있다.

//둘다 사용 가능
  Deque<String,> deq = new ArrayDeque<>();
  //Deque<String,> deq = new LinkedList<>();
  
  // 앞으로 넣고
  deq.offerFirst("A");
  
  //앞에서 꺼내기
  System.out.println(deq.pollFirst());
  //뒤에서 꺼내기
  System.out.println(deq.pollLast());
  //뒤로 넣기
  deq.offerLast("A");

21.6 Map<K, V> 인터페이스를 구현하는 컬렉션 클래스들

Map 인터페이스에는 Key-Value 방식의 데이터를 관리하는 데 필요한 메서드가 정의되어있다. 객체의 Key값은 유일하며 Value값은 중복될 수 있다.

  • HashMap 클래스 : 내부적으로 해시 알고리즘에 의해 구현되어 있다.
  • TreeMap 클래스 : TreeSet과 마찬가지로 이진 탐색 트리로 구현되어있다. key값으로 정렬하므로 key값에 해당하는 클래스에 Comparable이나 Comparator 인터페이스가 구현되어있다.

    HashMap<K, V> 클래스

    HashMap<K, V> 클래스를 사용하여 Key-Value 방식으로 데이터를 저장한다.
    HashMap<String, String> map = new HashMap<>();
    // key-value 기반 데이터 저장
    map.put("홍길동", "010-1234-1443");
    //데이터 탐색
    System.out.println("홍길동 :" + map.get("홍길동"));
    //데이터 삭제
    map.remove("");

HashMap<K, V>의 순차적 접근 방법

HashMap<K, V> 클래스는 Iterable<T,>인터페이스를 구현하지 않았기에 향상된 기능의 for문을 통해서, 또는 '반복자'를 얻어서 순차적 접근을 할 수 없다.
대신 다음 keyset() 메서드 호출을 통해서 key를 따로 모아놓은 컬렉션 객체를 얻을 수 있다. 그리고 이때 반환된 컬렉션 객체 대상으로 반복자를 얻을 수 있다.

Map<String, Integer> map = new HashMap<>();
// key-value 기반 데이터 저장
map.put("홍길동",20);
//Key만 담고 있는 컬렉션 객체 생성
Set<String> ks = map.keySet();
// 전체 key 출력 (향상된 기능의 for문 기반)
for(String s : ks)
..
// 전체 value 출력(향상된 기능의 for문 기반)
for(String s : ks)
.
.
//전체 Value 출력(반복자 기반)
for(Iterator<String> itr = ks.iterator(); itr.hasNext();)
.
.

21.7 컬렉션 기반 알고리즘

정렬

List<E,>를 구현한 컬렉션 클래스들은 저장된 개체를 정렬된 상태로 유지하지 않고 입력된 순서대로 유지하고 있다. 그래서 정렬을 해야 한다면 Collections.sort() 메서드를 사용할 수 있다. 이때 객체 크기를 비교해야 정렬할 수 있으므로 객체의 클래스는 Comparable<T,> 인터페이스를 구현한 상태이어야한다.
String 클래스는 Comparable 인터페이스를 구현한 상태이므로 바로 쓸 수 있다.
Collections.sort(list);

해당 객체의 Comparable 인터페이스 구현에 의한 정렬뿐 아니라 다른 정렬 방법을 제공할 수도 있다. 이때도 Comparator 인터페이스를 구현한 객체를 사용할 수 있다. 기본적으로 String 클래스는 사전순으로 정렬이 된다.
// 정렬 : 오름차순
Collections.sort(list);

StringDesc cmp = new StringDesc();
// 정렬 : 내림차순
Collections.sort(list,cmp);

검색

이진 탐색 기능을 이용하여 리스트 안에 데이터가 있는지 확인할 수 있다. 다만 이진 탐색을 이용하려면 데이터가 먼저 정렬되어 있어야 한다.
// 정렬
Collections.sort(list);
//탐색
int idx1 = Collections.binarySearch(list, "");
System.out.println(idx1);

복사

//수정 가능한 리스트 생성
List<String,> dst = new ArrayList<>(src);
// 정렬
Collections.sort(dst);
// src의 데이터를 dst로 복사
Collections.copy(dts,src)

profile
초보 개발자

0개의 댓글