사이먼's 코딩노트

[Java] 제네릭 / HashMap / 정리 본문

Java/Java

[Java] 제네릭 / HashMap / 정리

simonpark817 2024. 3. 18. 23:30

[제네릭]

  • 문제를 풀어보면서 제네릭에 대해 조금 더 자세히 알아봅시다.
  • 문제의 목표는 제네릭을 이용해서 클래스 3개를 1개로 줄여보는 것입니다.
public class Main {
    public static void main(String[] args) {
        Int저장소 a저장소1 = new Int저장소();

        a저장소1.setData(30);
        int a = a저장소1.getData();

        System.out.println(a);

        Double저장소 a저장소2 = new Double저장소();

        a저장소2.setData(5.5);
        double b = a저장소2.getData();

        System.out.println(b);


        사과저장소 a저장소3 = new 사과저장소();

        a저장소3.setData(new 사과());
        사과 c = a저장소3.getData();

        System.out.println(c);
    }
}

class Int저장소 {
    Object data;

    int getData() {
        return (int)data;
    }

    void setData(Object inputedData) {
        this.data = inputedData;
    }
}

class Double저장소 {
    Object data;

    double getData() {
        return (double)data;
    }

    void setData(Object inputedData) {
        this.data = inputedData;
    }
}

class 사과 {
}

class 사과저장소 {
    Object data;

    사과 getData() {
        return (사과)data;
    }

    void setData(Object inputedData) {
        this.data = inputedData;
    }
}

 

  • 아래 코드는 클래스 3개를 1개로 줄여서 재작성한 코드입니다.
public class Main {
    public static void main(String[] args) {
        저장소<Integer> a저장소1 = new 저장소();

        a저장소1.setData(30);
        int a = a저장소1.getData();

        System.out.println(a);

        저장소<Double> a저장소2 = new 저장소();

        a저장소2.setData(5.5);
        double b = a저장소2.getData();

        System.out.println(b);


        저장소<사과> a저장소3 = new 저장소();

        a저장소3.setData(new 사과());
        사과 c = a저장소3.getData();

        System.out.println(c);
    }
}

class 저장소<T> {
    Object data;

    T getData() {
        return (T)data;
    }

    void setData(Object inputedData) {
        this.data = inputedData;
    }
}

class 사과 {
}
  • 기존에 있던 Int저장소, Double저장소, 사과저장소 클래스는 제거하고, 모두를 담을 수 있는 저장소 클래스 하나를 생성한다.
  • 저장소 클래스는 어떤 타입이든 객체든 모두 연결될 수 있기 때문에 클래스 뒤에 <> 제네릭을 써준다.
  • 이 때 <T>에서 T는 Type의 약자로 사용자 임의로 정한 약어라고 생각하면 좋다.
  • 제네릭을 썼기 때문에 getData() 메서드의 타입도 T로 바꿔주고, return 해주는 data 값도 앞에 각 타입에 맞는 강제 형변환이 아닌 (T)를 쓰면서 모든 타입의 데이터를 불러올 수 있게 해준다.
  • Main 메서드에서도 당연히 각 저장소 별로 <Integer>, <Double>, <사과>를 붙혀 각 타입별로 저장소 객체와 연결될 수 있도록 한다.

 

[HashMap]

  • HashMap은 데이터를 저장할 때 키(key)와 밸류(value)가 짝을 이루어 저장된다.
  • 데이터를 저장할 때는 키값으로 해쉬함수를 실행한 결과를 통해 저장 위치를 결정한다.
  • 따라서 HashMap은 특정 데이터의 저장위치를 해시함수를 통해 바로 알 수 있기 때문에 데이터의 추가, 삭제, 특히 검색이 빠르다는 장점이 있다.
  • 이러한 이유로 HashMap은 키값을 통해서만 검색이 가능하며, HashMap의 키값은 중복될 수 없고, 밸류값은 키값이 다르다면 중복은 가능하다.
  • 아래 코드는 저번 ArrayList 클래스를 직접 만들어 본 것처럼 HashMap 클래스를 간단하게 직접 구현해본 것이다. 이또한 import 해서 불러쓰면 되지만 간단히 이해를 돕기 위한 부분이라고 생각하면 좋을 것 같다.
public class Main {
    public static void main(String[] args) {
        HashMap<String, Integer> ages = new HashMap<>();

        ages.put("영희", 22);
        ages.put("철수", 23);
        ages.put("민서", 25);
        ages.put("철수", 27);
        ages.remove("영희");
        ages.put("광수", 27);

        for ( String name : ages.keySet() ) {
            System.out.println("이름 : " + name + ", 나이 : " + ages.get(name));
        }
    }
}

class HashMap<K, V> {
    private Object[] keys;
    private Object[] values;
    private int lastIndex;

    HashMap() {
        lastIndex = -1;
        keys = new Object[1];
        values = new Object[1];
    }

    private boolean isArrayFull() {
        return lastIndex >= keys.length - 1;
    }

    private void extendArraySizeIfFull() {
        if ( isArrayFull() ) {
            extendArraySize();
        }
    }

    private void extendArraySize() {
        Object[] newKeys = new Object[keys.length * 2];
        Object[] newValues = new Object[values.length * 2];

        for ( int i = 0; i < keys.length; i++ ) {
            newKeys[i] = keys[i];
            newValues[i] = values[i];
        }

        System.out.println("내부 배열의 사이즈가 증가합니다. " + keys.length + " => " + newKeys.length);

        keys = newKeys;
        values = newValues;
    }
    // 데이터 넣기 or 변경하기
    void put(K key, V value) {
        int keyIndex = getIndexOfKey(key);

        if ( keyIndex >= 0 ) {
            values[keyIndex] = value;
        }
        else {
            extendArraySizeIfFull();

            lastIndex++;
            keys[lastIndex] = key;
            values[lastIndex] = value;
        }
    }

    void remove(K key) {
        int keyIndex = getIndexOfKey(key);
        if ( keyIndex >= 0 ) {
            remove(keyIndex);
        }
    }

    void remove(int index) {
        for ( int i = index; i < lastIndex; i++ ) {
            keys[i] = keys[i + 1];
            values[i] = values[i + 1];
        }

        lastIndex--;
    }

    private int getIndexOfKey(K key) {
        for ( int i = 0; i <= lastIndex; i++ ) {
            if ( keys[i].equals(key) ) {
                return i;
            }
        }

        return -1;
    }

    Set<K> keySet() {
        Set<K> keySet = new HashSet<>();

        for ( int i = 0; i <= lastIndex; i++ ) {
            keySet.add((K)keys[i]);
        }
        return keySet;
    }

    V get(K key) {
        int keyIndex = getIndexOfKey(key);
        if ( keyIndex >= 0 ) {
            return (V)values[keyIndex];
        }
        return null;
    }
}
  • HashMap의 생성 방법은 [HashMap<String, Integer> ages = new HashMap<>()] 와 같다.
  • 제네릭 안의 key값과 value값은 항상 String, Integer 타입만 받는 것은 아니고 모든 타입과 객체를 받을 수 있다.
  • HashMap에서 데이터를 입력하고 싶다면 put(), 제거할 때는 remove(), 데이터를 얻을 때는 get() 메서드를 사용한다.

 

[정리]

  • 앞에서 다룬 배열, ArrayList, HashMap를 아래 표를 통해 비교하면서 최종 정리해봅시다.

배열, ArrayList, HaspMap 비교

 

  • 배열, ArrayList, HashMap의 클래스 생성 모습은 약간씩 다르다.
  • 유연성이란, 데이터를 얼마나 자유롭게 늘릴 수 있는지를 나타낸다.
  • 각 클래스별로 데이터를 넣고, 가져오고, 삭제하고, 수정할 때 쓰이는 문법과 메서드가 다르니 주의해야한다.
반응형

'Java > Java' 카테고리의 다른 글

[Java] 예외처리 / 접근제한자  (0) 2024.03.19
[Java] 인터페이스  (2) 2024.03.19
[Java] ArrayList  (2) 2024.03.18
[Java] 배열 / toString / equals / StringBuilder  (2) 2024.03.14
[Java] 형변환 문제풀이  (0) 2024.03.13