제너릭 (Generics)

2023. 11. 29. 15:05
728x90

1. Generic Type Reference

- Generic : 클래스, 인터페이스, 메서드를 좀더 일반적인 상황에서 활용하고 재사용성을 높이는 방법.

- 다양한 자료형에 대해 동일한 동작을 할 수 있는 방법을 제공.

ex. 정수형 배열을 속성으로 가지고, 그 최댓값과 평균을 관리하는 클래스가 있다면..?

똑같은 기능을 실수로 만들고 싶다! > 그러면 또 똑같은 형태의 클래스를 만들거나 클래스 내부에 double[]을 담는 속성/메서드 모두 구현해야한다.... ㅠㅡㅠ

public class IntArrayWrapper {
    private final int[] intArray;

    public IntArrayWrapper(int[] intArray) {
        this.intArray = intArray;
    }

    // 1. intArray의 길이를 반환하는 메서드
    public int length() {
        return this.intArray.length;
    }

    // 2. 주어진 int target이 intArray에 존재하는지 반환하는 메서드
    public boolean contains(int target) {
        for (int number : this.intArray) {
            if (target == number) return true;
        }
        return false;
    }

    // 3. intArray의 원소 중 최댓값
    public int max() {
        int max = this.intArray[0];
        for (int number : this.intArray) {
            // Math: 여러 수학 작업이 존재하는 클래스
            max = Math.max(number, max);
        }
        return max;
    }
}

 

이때 Generic을 활용하자!

 

How ?

>> 클래스 이름 옆에 <T>를 넣어보자. 

T: Type Parameter 클래스를 매개변수로 받게 해준다 /  T를 타입처럼 쓸 수 있다. 

- Comparable를 내부에 구현 가능

- 여러 클래스나 인터페이스를 &로 연결 가능

public class ArrayWrapper<T extends Comparables<T>> { 
// extends Comparable<T>를 쓰면 비교가 가능한 자료형만으로 제한 가능
    private final T[] array;

    public ArrayWrapper(T[] array) {
        if (array == null)
            throw new IllegalArgumentException("null array provided");
        this.array = array;
    }

    // 1. intArray의 길이를 반환하는 메서드
    public int length() {
        return array.length;
    }

    // 2. 주어진 int target이 intArray에 존재하는지 반환하는 메서드
    public boolean contains(T target) {
        for (T item : array) {
            if (item == null) continue;
            if (item.equals(target)) return true;
        }
        return false;
    }
    
    // 3. intArray의 원소 중 최댓값
    public T max() {
        T max = this.array[0];
        for (T item : this.array) {
            if (item==null) continue;
            if (max==null) max = item;
            // Math: 여러 수학 작업이 존재하는 클래스
            // 비교가 가능한 것만 구현해야 한다.
            else if (item.compareTo(max)>0) max = item;
        }
        return max;
    }
}

 

2. Optional

: null이 나올 수 있는 결과를 Optional로 덮어쓸 수 있다.

public static String nullOrString() {
    Random random = new Random();
    if (random.nextInt() % 2 == 0) 
        return "not null string";
    else return null;
} // 랜덤값은 null을 반환할 수도 있다.

 

 

- 이 때 Optional을 활용할 수 있다. 

isPresent : null이 아닌 값이 들어올 때 true

isEmpty : null일 때 true

Optional<String> optionalString = Optional.ofNullable(nullOrString()); 
//어떤 메서드든 Optional.ofNullable()메서드를 통해 결과 반환하게 한다.

if (optionalString.isPresent()) { // 결과가 존재하면 실행될 코드
// get으로 실제 값을 반환받아 사용
	System.out.println(optionalString.get());
} else { // 결과가 존재하지 않으면 실행될 코드
	System.out.println("got: null");
}

 

- .orElse(T obj)로 null일때 기본값을 활용할 수도 있다. 

String resultString = optionalString.orElse("got: null");
System.out.println(resusltString);
// 값이 존재하면 존재하는 값을, 없으면 기본값을 반환

 

- 아예 메서드가 Optional을 반환하게 만들 수도 있다.

public static Optional<String> nullOrStringOpt() {
	Random random = new Random();
    // Optional.of로 Optional을 만든다.
    if (random.nextInt() %2 == 0) return Optional.of("concrete string");
    // null결과는 Optional.empty()
    else return Optional.empty();
}

 

 

3. Collections Framework

: 자바에서 만들어준 데이터 정리용 클래스.- 인터페이스를 활용해 사용방식을 통일하고, 구현 클래스를 통해 실제 자료구조를 달리 할 수 있게 하는 도구 모음집.

1) List<E>

: 순서가 있는 데이터를 나타내는 인터페이스

- 중복 허용

- 원소를 일렬로 세워 추가 제거 조회 등을 위해 많이 활용

- index 방식으로 가져올 수 있음.

  • ArrayList : 배열을 관리해줌. 동적으로 배열의 크기를 바꿔가며 활용, 임의의 위치에서 원소 가져오기 좋음
  • LinkedList : 연결 리스트 형태. 데이터를 관리, 원소를 추가 제거하는데 유리
boolean add(E element) element를 List의 끝에 넣어준다. 성공하면 true 반환
void add(int index, E element) element를 index의 위치에 넣어주고 원래 원소들을 뒤로 밀어준다
E get(int index) index에 있는 원소 반환
int indexOf(Object o) o가 있으면 그 index를, 없으면 -1을 반환
E remove(int index) index에 있는 원소 제거하고 반환, 뒤의 남은 원소들을 앞으로 당겨줌
boolean remove(Object o) o가 있다면 제거

 

2) Set<E>

: 순서 보장하지 않는 원소 집합을 나타내는 인터페이스

- 중복 허용 X

- 데이터 모음 중 중복된 데이터를 제거하거나 어떤 원소가 있는지 없는지를 확인하는데 유용

- HashSet : 객체를 hashCode로 구분한다. (hashSet 쓸 때 hashCode() 오버라이딩 필요)

boolean add(E element) element를 추가. 이미 있는 데이터면 false 반환.
boolean addAll(Collection<? extends E> c) 또다른 Collection 인터페이스 원소들 중 없는 원소 추가. 추가한 원소가 하나라도 존재하면 true 반환
boolean remove(Object o) o가 있다면 제거. 제거했다면 true 반환
boolean removeAll(Collection<? extends E> c) 또다른 Collection 인터페이스 원소들 중 있는 원소 제거. 제거한 원소가 존재하면 true 반환
void clear() Set을 비움.
Set<String> skillSet = new HashSet<>();

skillSet.add("java");
skillSet.add("kotlin");
skillSet.add("java"); //중복 원소는 들어가지 않음.

List<String> skillList = new ArrayList<>();
skillList.add("java");
skillList.add("git");
skillList.add("md");

skillSet.addAll(skillList); //true
System.out.println(skillSet); // 결과값은 java, kotlin, git, md (중복 제거)

skillSet.removeAll(skillList); //true
System.out.println(skillSet); // 결과값은 java, kotlin (skillSet에서 skillList빼기)

 

 

3) Iterable 

: List와 Set의 기초가 되는 인터페이스

- 반복 가능하다는 뜻! foreach로 순회가 가능.

   cf) foreach문 사용중 대상 리스트가 변경/제거되면 ConcurrentModificationException이 발생할 수 있음.

- 추상 메서드 iterator가 정의되어 있고, 구현 클래스의 원소들을 검사하는 Iterator 객체를 돌려주는 메서드.

- Iterator라는 객체를 활용하여 Iterable이 담고 있는 데이터를 하나씩 확인할 수 있다.

// Iterator
List<String> skillList = new ArrayList<>();
Iterator<String> iter = skillList.iterator();
while (iter.hasNext()) { // 아직 남은 원소가 있는지 판단
    String element = iter.next(); // 다음 원소를 가져온다. 없으면 오류
    if (element.equals("python") iter.remove(); // python이 제거된다.
}
System.out.println(skillList);

// foreach 구문으로 사용
for (String skill: skillList) {
	System.out.println(skill);
    skillList.remove("python"); //오류남 (ConcurrentModificationException)
}
boolean hasNext() 다음 원소가 존재하는지 여부를 반환
T next() 호출할 때마다 다음 차례의 원소를 반환
void remove() 현재 위치의 원소를 제거. next()를 사용한 다음 사용해야 하며, next()로 한 번 확인할 때마다 한번만 사용 가능.

 

 

4) Collection

: Iterable을 상속받으며 List와 Set의 실제 동작이 정의된 인터페이스.

- 표준 생성자는 아무것도 받지 않는 생성자와, Collection을 받아 그 원소들을 다 포함하는 Collection을 만드는 생성자.

- 즉, 집합을 쓰다가 리스트로, 리스트를 쓰다가 집합으로 변환 가능. 

int size() Collection의 원소의 갯수 반환
boolean isEmpty() Collection이 비어있는지 여부를 반환
boolean contains(Object o) Collection에 o가 있는지 반환
Set<String> skillSet = new HashSet<>();
List<String> listFromSet = new ArrayList<>(skillSet);

 

5) Map<K, V>

: key와 value쌍으로 이루어진 인터페이스.

- 데이터와 그 데이터를 찾기 위한 용도의 키를 함께 저장. > 데이터의 값이 필요할 때 키를 바탕으로 찾음.

- put(k, v)으로 키와 값을 동시에 전달

- get(k)으로 키로 값을 가지고 옴.

- Collection 인터페이스 구현하지 않음!! 

- HashMap : 키를 hashCode로 구분.

V put (K key, V value)  Key에 Value를 할당. 이미 존재하는 Key면 이전 Value를 반환
V get(Object key) key에 해당하는 값을 반환하거나 null 반환
boolean containsKey(Object key) key와 일치하는 key가 맵에 존재하면 true 반환
boolean containsValue(Object value) value와 일치하는 value가 맵에 하나라도 존재하면 true 반환
V remove(Object key) key에 해당하는 값을 제거하고 반환. 없으면 null 반환
Set<K> keySet() 모든 key로 구성된 Set을 반환
Set<Map.Entry<K, V>> entrySet() 맵의 key, value 쌍으로 구성된 Entry 객체의 Set 반환

 

 

- keySet(), entrySet()을 활용하면 맵의 모든 키와 값들을 순회할 수 있다.

>> Map.Entry형 객체가 담긴 집합을 반환. (getKey(), getValue() 사용)

Map<String, String> gitConfig = new HashMap<>();
gitConfig.put("user.email", "email@gmail.com");
gitConfig.put("user.name", "ys");

for(String key: gitConfig.keySet()) {
	System.out.println(String.format("key: %s", key));
    System.out.println(String.format("value: %s", gitConfig.get(key)));
}

for (Map.Entry<String, String> entry : gitConfig.entrySet()) {
	System.out.println(String.format("key: %s", entry.getKey()));
    System.out.println(String.format("value: %s", entry.getValue()));
}

 

 

 

 

 

728x90

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

File I/O  (1) 2023.12.03
Stream API  (1) 2023.11.30
예외처리 (Exception Handling)  (1) 2023.11.28
객체지향 프로그래밍  (1) 2023.11.28
클래스 (Class)  (1) 2023.11.28

BELATED ARTICLES

more