HJW's IT Blog

[JAVA] Collections Framework 1 (Generic, ArrayList) 본문

JAVA

[JAVA] Collections Framework 1 (Generic, ArrayList)

kiki1875 2024. 10. 13. 17:40

Generic

  • Type 을 포괄적으로 일단 정의하겠다
  • 클래스나 메서드에서 타입을 미리 정하지 않고 외부에서 사용할 때 타입을 정하도록 하는 문법적 장치
  • 왜 사용하는가 → 어떠한 클래스를 만들었는데, 거의 똑같은 클래스를 또 만들어야 한다면 중복되는 부분이 많고 비효율적이다 → 중복을 최소화 하자!
class Data{

    int obj;
    Data(int obj){
        this.obj = obj;
    }

    int getObj(){ return obj; }
    void printInfo(Integer obj){
        System.out.println(obj.getClass().getName());
    }
}

class Data2{

    String obj;
    Data2(String obj){
        this.obj = obj;
    }
    String getObj(){ return obj; }
    void printInfo(String obj){
        System.out.println(obj.getClass().getName());
    }
}
  • 위와 같은 두개의 클래스가 있다고 가정해 보자
  • 거의 같은 클래스이다 → 불필요한 중복이 들어가 있다
  • 이럴때 제네릭을 사용한다
class Data{
    Object obj;
    Data(Object obj){
        this.obj = obj;
    }
    Object getObj(){ return obj; }
    void printInfo(Object obj){
        System.out.println(obj.getClass().getName());
    }
}
        Integer foo = Integer.valueOf(100);
        Data d = new Data(foo);

        System.out.println(d.getObj());
        d.printInfo(d);

        Integer a = d.getObj();
  • 위와 같이 Integer a 에 d의 값을 할당하려고 하면 오류가 발생한다 → 형변환이 일어났기 때문
  • Integer a = (Integer) d.getObj(); → 컴파일 단계에서 오류 x
  • 하지만 타입 안정성이 떨어지며 명시적인 타입 캐스팅이 필요하다
  • 그렇기에 다음 방법을 사용하여 컴파일 단계에서 오류를 잡아낼 수 있다
class Data<T>{
    T obj;
    Data(T obj){
        this.obj = obj;
    }
    T getObj(){ return obj; }
    void printInfo(){
        System.out.println(obj.getClass().getName());
    }
}

매개변수를 여러개 받는 제네릭 클래스

class Data<T1, T2>{
    private T1 foo;
    private T2 bar;

    public void set(T1 t1, T2 t2){
        foo = t1;
        bar = t2;
    }

    public void printInfo(){
        System.out.println(foo.getClass().getName() + " " + bar.getClass().getName());
    }

    public void printInfo2(){
        System.out.println(foo + " : " + bar);
    }
}

제네릭 클래스의 매개변수 타입 제한

  • 예를 들어 Number (Int, Double, …) 만으로 제한하고 싶다면 다음과 같이 선언하면 된다
class Data<T extends Number>{
    private T foo;
    
    Data(T t){
        foo = t;
    }
    
    public T getFoo(){
        return foo;
    }
    
    public String toString(){
        return foo.getClass().getName();
    }
}
  • 위와 같이 선언을 한 뒤, 객체를 String 으로 생성하게 되면 오류 발생
  • Data<String> d = new Data<String>();

제네릭과 배열리스트

public class Main {
    public static void main(String[] args) {
        AnimalList<Birds> animals = new AnimalList<>();

        animals.set(new Eagle());
        animals.set(new Owl());
        animals.set(new Dog());
    }
}

abstract class Birds { abstract void cry(); }

class Eagle extends Birds{
    void cry(){
        System.out.println("Eagle");
    }
}

class Owl extends Birds{
    void cry(){
        System.out.println("Owl");
    }
}

class Dog{
    void cry(){
        System.out.println("Dog");
    }
}

class AnimalList<T>{
    ArrayList<T> alist = new ArrayList<>();

    void set(T obj){
        alist.add(obj);
    }

    T get(int idx){
        return alist.get(idx);
    }

    int getSize(){
        return alist.size();
    }
}

  • 위 예시는 제네릭을 활용한 클래스 생성 예시이다
  • 추상 클래스 Birds 를 상속받는 클래스 Eagle, Owl 이 있고, 상속받지 않는 클래스 Dog 가 있다
  • AnimalList 클래스는 지정된 타입의 객체들만 저장할 수 있는 리스트를 구현한다
  • 그렇기 때문에 Bird를 상속받지 않은 Dog 를 추가하려 하였을 때, 컴파일 에러가 발생한다

제너릭 메서드

  • 메서드에 대해서 제너릭 문법 적용
  • 메서드의 선언부에 타입 파라미터를 선언하여 메서드의 매개변수와 리턴 타입을 지정 타입으로
  • 메서드를 호출할 때 매개변수 타입과 리턴 타입을 결정하겠다는 의미
class DataCheck {
    public static <T> T showData(T data){
        if(data instanceof Integer){
            System.out.println("INT");
        }else if(data instanceof String){
            System.out.println("STRING");
        }

        return data;
    }
}

제너릭 메서드 제약 걸기

public static <T extends CharSequence> void showFirstChar(T param){
        System.out.println(param.charAt(0));
    }

 

ArrayList

  • 배열과 비슷하지만, 더 좋아진, 업그레이드 된 배열
  • List 가 내부적으로 배열을 이용한다
  • 배열과 다르게, 길이가 가변적 이다
  • null 값이 허용되며, 요소가 추가된 순서를 유지한다
  • List 는 인터페이스 이기 때문에 객체를 생성할 수 없다 → 이를 상속 구현한 구현체(ArrayList) 를 통해 객체를 생성하고 사용한다

주요 메서드

  • add() : 요소 추가
  • remove() : 요소 삭제
  • size() : 길이
  • set() : 수정
  • get() : 출력
  • Collections.sort( 컬렉션 명) : 정렬 (collections.reverseOrder() 사용시 역방향)
  • clear() : 모든 요소 삭제
  • contains(Object o) : 특정 요소가 있는지 확인
  • isEmpty() : 리스트가 비어있는지
  • indexOf(Object o) : 특정 요소의 첫 인덱스 반환
  • lastIndexOf(Object o) : 특정 요소가 여러개 있을때 마지막 인덱스 반환
  • Collections.freqency(컬렉션명, 요소명) : 특정 요소가 포함된 개수
    • Collections.frequency(list, 100)
  • list1.retainAll(list2) : 교집합만 남겨두고 삭제

사용자 정의 객체 정렬

  • Comparable 인터페이스Comparator 인터페이스, 람다 표현식을 사용해야 한다
  • Comparable 의 경우, 자연 순서를 정의한다,
  • 객체 자체에서 정렬 기준을 제공해야 하며, compareTo 메서드를 구현해야 한다
class Person implements Comparable<Person> {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // compareTo 메서드 구현 (나이 순으로 정렬)
    @Override
    public int compareTo(Person other) {
        return this.age - other.age;  // 나이가 적을수록 앞에 오도록
    }
}

public class Main {
    public static void main(String[] args) {
        ArrayList<Person> people = new ArrayList<>();
        people.add(new Person("Kevin", 25));
        people.add(new Person("Alice", 22));
        people.add(new Person("Bob", 30));

        Collections.sort(people);  // Comparable에 정의된 정렬 기준에 따라 정렬

    }
}
  • Comparator 의 경우, 외부에서 정렬기준을 정의하고, compare 메서드를 통해 두 객체를 비교한다
class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
 }
 
 class NameComparator implements Comparator<Person> {
    @Override
    public int compare(Person p1, Person p2) {
        return p1.name.compareTo(p2.name);  // 이름 순으로 정렬
    }
}

public class Main {
    public static void main(String[] args) {
        ArrayList<Person> people = new ArrayList<>();
        people.add(new Person("Kevin", 25));
        people.add(new Person("Alice", 22));
        people.add(new Person("Bob", 30));

        Collections.sort(people, new NameComparator());  // NameComparator를 사용하여 이름순으로 정렬
    }
}

  • 람다 표현식
    • Collections.sort(people, (p1, p2) -> p2.age - p1.age); 혹은,
Comparator<String> cl = (s1, s2) -> s1.length() - s2.length();
Arrays.sort(people, cl);