코드스테이츠 - 컬렉션(Collection)

kwak woojong·2022년 5월 17일
0

코드스테이츠

목록 보기
14/36
post-thumbnail

1. 제네릭

1. 제네릭이란?

public class 클래스명<타입 매개변수>
public interface 인터페이스명<타입 매개변수>

이렇게 한다.

즉 <> 이게 있으면 제네릭 타입을 사용했다고 보면 된다.

제네릭은 Map, List, Set 같은 컬렉션 프레임워크를 사용할 때, 필수적이다.

상기 컬렉션 프레임워크(Map, List, Set 등)에는 다양한 자료형이 들어올 수 있다.

이 때, 제네릭에 타입을 선언하지 않는다면, 무적권 Object로 들어올 수 있다.

이러면 향후 자료를 활용할 때, 형변환을 계속 해야 한다.

제네릭은 지정된 자료형태만 들어오게 하므로, 이런 형변환을 하지 않아도 된다.

제네릭은 지정된 자료 형태만을 받겠다! 라고 해당 객체를 일반화 하는 것이라 볼 수 있다.

ArrayList<Integer> arrayList = new ArrayList<>();

이런 식으로 작성하면 Integer 형식의 자료만 arrayList에 들어오게 된다. (뒤 <>의 내용은 생략해도 된다. 넣어도 됨)

2. 타입 매개변수

타입인자 설명
<T> Type
<E> Element
<K> Key
<N> Number
<V> Value
<R> Result

3. 제네릭 클래스 사용

class Sample <T> //Type가 들어오는 클래스 객체 생성

  //field
  private T obj;

  //Constructor
  Sample(T obj) {
  this.obj = obj;
  }

  //Method
  public T get() {
	return obj;
  }
  public void set(T obj) {
  	this.obj = obj;
  }

이런 식으로 만든다. T는 타입을 넣으라는 지시사항인거임
그 T에 맞게 객체를 넣어서 메서드를 만들어준다.
문제는 String이 들어올지, Integer가 들어올지 뭐가 올지 아무도 모르기 때문에, length()같은 특정 클래스 메서드는 사용할 수 없다.

Sample<String> sample = new Sample<String>("껄껄껄 아무도 날 막을 수 없으셈")
System.out.println(sample.get()); // "껄껄껄 아무도 날 막을 수 없으셈"
sample.set("헐!!!");
System.out.println(sample.get()); // "헐!!!"

와일드카드 ?

제한을 두지 않는 기호를 말한다.
다이아몬드 연산자에 ?를 넣으면 매개 변수로 모든 타입을 사용한다는 뜻임.

<?> // 타입 매개변수에 모든 타입 사용
<? extends T> //T타입과 T타입을 상속받는 하위 클래스 타입만 사용
<? super T> // T타입과 T타입이 상속받은 상위 클래스 타입만 사용

4. 제네릭 메서드

그냥 일반적인 클래스에 내부 메서드만 제네릭을 선언할 수 있다. 이 경우 제네릭 메서드라고 한다.
클래스를 쓸 땐 선언시(객체를 생성시)에 타입을 지정하지만, 얘는 클래스는 이미 생성 된 상태에서 메서드를 호출하는 시점에서 제네릭 타입을 지정한다.
중요한 점은 메서드에 어떤 타입이 들어올지 모르므로, Object 는 죄다 이용가능하지만, 다른 클래스의 메서드는 불가하다.

class Sample2 {
	public <T> T obj(T obj) {
    	return obj;
        }
    public <K, V> void getOld(K k, V v) {
    	System.out.println(k + "는 " + v + "살?");
    }
}

public class Main {
	public static void main(String[] args) {
    
    	Sample2 sample2 = new Sample();
        
        String hi = sample2.<String>obj("안녕하세요"); //인자가 String이 확실하다면(유추되면) 생략 가능
        System.out.println(hi); // "안녕하세요"
        
        sample2.<String, Integer>getOld("뽀로로", 13); // 뽀로로는 13살?     
    }
}

Object 메서드

제어자 및 타입 메서드 설명
protected Object clone() 해당 객체의 복제본을 생성해서 반환함
boolean equals(Object obj) 해당 객체와 전달받은 객체가 같은지 여부를 반환
protected void finalize() 해당 객체를 더는 아무도 참조하지 않아 가비지 컬렉터가 객체의 리소스를 정리하기 위해 호출함.
Class<T> getClass() 해당 객체의 클래스 타입을 반환함
int haxhCode() 해당 객체의 해시 코드값을 반환함
void notify() 해당 객체를 대기하고 있는 하나의 스레드를 다시 실행할 때 호출함
void notifyAll() 해당 객체의 대기 하고 있는 모든 스레드를 다시 실행할 때 호출함
String toString() 해당 객체의 정보를 문자열로 반환함
void wait() 해당 객체의 스레드가 notify(), notifyAll() 메소드를 실행할 때까지 현재 스레드를 일시적으로 대기 시킴
void wait(long timeout) 해당 객체의 스레드가 notify(), notifyAll() 메소드를 실행하거나, 전달받은 시간이 지날 때까지 현재 스레드를 일시적으로 대기 시킬때 호출함
void wait(long timeout, int nanos) 해당 객체의 스레드가 notify(), notifyAll() 메소드를 실행하거나, 전달받은 시간이 지나거나, 다른 스레드가 현재 스레드를 인터렙트 할 때까찌 현재 스레드를 일시적으로 대기 시킬때 호출

2. 컬렉션 프레임워크

java는 정적배열이라 다량의 데이터를 관리하기가 어렵다.
javascript는 동적배열에, key,Value 형 자료형태도 있다.

내가 유독 그런지 모르겠는데 javaScript는 하고 와서 그런지 java의 정적배열은 매우 불편하게 느껴진다.
물론 java가 더 안좋은 언어라는게 아니라 좀 더 엄밀하게 사용해야 한다는 거임. Js는 대충 후려 갈겨도 되는 언어니까...

알고리즘 풀 때 Stack이니 뭐니 하는 자료구조가 뭔가 싶었는데, java를 공부하면서 알게 됐음.

  • List : 순서를 가짐.
  • Set : 순서는 없는데, 데이터의 중복을 허용치 않음. (TreeSet은 자동 정렬을 해준다)
  • Map : Key, Value의 쌍으로 이루어진 데이터의 집합, Key의 중복은 허용치 않는다. Value는 가능

저 3개가 대표적이고, 기능에 따라 조금씩 다르다. 나는 주로 ArrayList를 많이 쓰긴 함.


1. List

객체를 일렬로 늘어놓은 구조를 가지고 있다. 인덱스가 있어서 인덱스로 꺼내오거나, 값을 찾아서 인덱스를 반환할 수도 있다. 일반적인 배열인데, 좀 더 기능이 추가되고, 동적이라고 볼 수 있다.

1. ArrayList

기능적으론 Vector와 동일하지만 Vector보단 주로 얘를 쓴다.

ArrayList<String> arrayList = new ArrayList<String>();

String만 받는 ArrayList를 선언했다. 메서드를 사용해서 안에 값을 하나씩 늘려나갈 수 있다. 그러면 배열처럼 인덱스 0 ~ 쭉쭉 늘어나게 된다. 중간에 값을 삭제하면 이 인덱스가 하나씩 다 빠지게 됨. 만약 엄청 많이 바뀐다 싶으면 LinkedList가 더 좋은 선택일 수 있다.

ArrayList<String> arrayList = new ArrayList<String>(); // 생성자에서 직접 list를 박아서 add해도 된다.
arrayList.add("A");
arrayList.add("B");

for(String s : arrayList) {
	System.out.println(s);
}
// A
// B

System.out.println(arrayList.get(0)); // 0번째 인덱스인 A가 나옴

2. LinkedList

ArrayList는 인덱스를 통해 서로 연속적으로 존재하는데, 얘는 그렇진 않다. 그냥 순서 없이 메모리에 박히는데, 다음에 올 녀석이 누구인지만 메모리가 저장되어 있다가, 끊거나 중간에 새로 뭔가 들어오게 되면, 다음에 올 녀석 값만 바꿔준다.
ArrayList보다 빠르다.

LinkedList<String> linkedList = new LinkedList<>(); // 뒷 제네릭은 생략 가능
linkedList.add("boy");
linkedList.add("meet");
linkedList.add("girl");
// boy meet girl 이 들어가 있는 상태

linkedList.add(0, "cool");
// 맨 앞에 cool을 넣어준 상태 cool -> boy -> meet -> girl 순서로 연결되어 있다.

linkedList.set(3, "Kim-egg");
// girl을 Kim-egg로 바꿈

if(linkedList.contains("girl")) { // linkedList에 girl이 있으면 true, 없으면 false 반환
	System.out.println("설마? 운동 안하고 연애 할라고?"); //girl이 사라졌기 때문에, 동작 하지 않음
} else {
	System.out.println(linkedList.indexOf("girl")); //girl이 있는 인덱스를 반환, 여긴 없으니까 -1을 반환
}

3. Iterator

ㄹㅇ 무적권 순서대로 읽는 컬렉션이다. 처음 봤을때 꽤 신선했다.

Iterator<String> iterator = linkedList.iterator();
	while (iterator.hasNext()) { // 다음 애가 있냐? true or false
		 System.out.println(iterator.next());
	}
    
//cool
//boy
//meet
//Kim-egg

상기 LinkedList를 가지고 Iterator을 생성하자.
while문에 iterator.hasNext()는 다음 애가 있냐? 라고 물어보는 메서드다. boolean임
있으면 true니까 iterator.next()를 실행하러 간다. 얘는 다음으로 넘기는 메서드인데, 값을 반환해줌.

  • 시작부터 끝방향까지 단방향 탐색만 됨
  • 모든 컬렉션 프레임워크에 다 사용가능

만약 역박향도 하고 싶다면 ListIterator을 따로 사용해야한다.

        ListIterator<String> iterator = linkedList.listIterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        while (iterator.hasPrevious()) {
            System.out.println(iterator.previous());
        }

hasPrevious()와 previous()가 생겼다.

list의주요 메서드

리턴 타입 메서드 설명
boolean add(Object o), addAll(Collection c) 주어진 객체 / 컬렉션의 객체들을 추가
boolean contains(Object o), containsAll(Collection c) 주어진 객체 / 컬렉션이 저장되어 있는지 여부
Iterator iterator() 컬렉션의 iterator을 반환
boolean equals(Object o) 컬렉션이 동일한지 여부
boolean isEmpty() 컬렉션이 비어있는지 여부
int size() 저장되어 있는 객체수를 반환 (length랑 비슷)
void clear() 컬렉션을 싹~다 비운다. (청소)
boolean remove(Object o), removeAll(Collection c) 주어진 객체/컬렉션을 삭제하고 성공 여부를 반환 (인덱스를 넣으면 인덱스 값을 삭제)
boolean retainAll(Collection c) 주어진 컬렉션을 제외한 모든 객체를 컬렉션에서 삭제하고 컬렉션에 변화가 있는지 여부를 반환
Object[] toArray() 컬렉션에 저장된 객체를 객체배열로 반환 (Object[])
Object[] toArray(Object[] a) 주어진 배열에 컬렉션의 객체를 저장해서 반환

2. Set

set은 요소의 중복을 허용하지 않고, 저장 순서를 유지하지 않는다.

나는 주로 중복 제거시 사용을 했었다. set에 넣고 빼면 중복 요소가 삭제돼서 나온다.

1. HashSet

Set의 대표적인 클래스다. 중복저장이 없고, 순서 유지도 없다. 그냥 평범한 Set

        HashSet<String> set = new HashSet<>();
        set.add("후");
        set.add("후");
        set.add("후");
        set.add("후");
        set.add("후");
        set.add("하");
        set.add("후");
        set.add("후");

        Iterator who = set.iterator();

        while (who.hasNext()) {
            System.out.println(who.next());
        }

set에 "후"를 많이 "하"를 1개 추가했다.
Iterator을 통해 돌려보면 "후", "하" 단 2개 밖에 없다.
그 많이 추가한 "후"는 다 사라지고 단 1개만 들어온거임

2.TreeSet

Set인데 이진 탐색 트리 형태로 데이터를 저장한다. 저장 순서를 유지 안하고, 중복저장이 없지만 왠지 모르게 정렬되어 튀어나온다.
그게 이진 탐색 트리 방식으로 저장해서 그렇다.

        TreeSet<Integer> set = new TreeSet<>();
        set.add(5);
        set.add(2);
        set.add(7);
        set.add(3);
        set.add(1);
        set.add(9);
        set.add(100);
        set.add(3);

        Iterator who = set.iterator();

        while (who.hasNext()) {
            System.out.println(who.next());
        }

TreeSet을 만들고 무작위로 숫자를 넣었다. 그리고 Iterator로 불렀음.

오름차순으로 정렬되어 있음을 알 수 있다.

        System.out.println("higher 7 = " + set.higher(7));
        System.out.println("lower 7 = " + set.lower(7));
        System.out.println("first = " + set.first());
        System.out.println("last = " + set.last());

각종 메서드를 통해 주변 탐색도 된다.


3. Map

map는 key와 value로 저장되는 컬렉션임.
key는 중복저장이 되지 않는다. value는 됨
만약 동일한 값으로 key를 저장하면 기존 값이 없어지고 덮어 씌워진다.
순서를 보장하지 않기 때문에 보통 key값을 기준으로 관리를 한다.

id를 기준으로 Member 클래스를 관리한다거나 할 수 있겠지?

    private static Map<Long, Member> store = new HashMap<>(); //static 사용
    private static long sequence = 0L;

HashMap

대표적인 Map을 구현한 클래스다. 일반적으로 key, value를 가진다는 느낌으로 생각하면 된다.
아마 Js를 다뤘었다면 거기서도 객체형을 다뤘을 거다. 그게 이거랑 거의 비슷함.
key값은 기본타입을 사용할 수 없다.

    private static Map<Long, Member> store = new HashMap<>(); //static 사용
    private static long sequence = 0L;

store에 Hashmap을 사용했다. 키는 Long, 값은 Member라는 클래스를 사용한거임.
이렇게 되면 키로 멤버 클래스를 저장할 수 있다.
만약 key로 정수로된 id를 사용하고 저장해둔다면 뽑아 쓸 때 편하겠지?

얘는 Iterator를 호출할 수 없다. key랑 value랑 같이 붙어있으니까 hasNext를 적용하기 힘들겠지?

대신 keySet이나 entrySet 메서드를 이용해 set형태로 반환된 컬렉션을 이용할 수 있다.


3. Comparable와 Comparator

Comparable와 Comparator은 둘 다 컬렉션을 정렬하기 위해 쓰이는 인터페이스다.

Comparable은 비교 대상 (매개 변수)와 자기 자신을 비교한다.
Comparator은 매개 변수인 두 객체를 비교한다.

1. Comparalbe

  • 기본적으로 적용되는 정렬 기준이 되는 메서드를 정의하는 인터페이스
  • 때문에 정렬이 가능한 클래스는 모두 Comparable 인터페이스를 구현하고 있다.
  • 정렬할 객체에 Comparable Interface를 implements하고 compareTo()를 오버라이드 하면 된다.
    - 현재 객체 (this) < 매개 변수 객체 : 음수
    • 현재 객체 == 매개 변수 객체 : 0
    • 현재 객체 > 매개변수 객체 : 양수
  • return 값이 음수, 0이면 제자리에, 양수면 두 객체의 자리가 바뀐다.
public class Member implements Comparable<Member>{

    private int id;
    private String name;

	// 게터세터 생략

    @Override
    public int compareTo(Member o) {
        if(this.id > o.id) {
            return 1; //id 기준 오름차순
        } else if(this.id == o.id) {
            return 0;
        }
        return -1;
    }
}

Member 클래스를 상기처럼 만든다. compareTo로 id 기준으로 정렬하겠다고 로직을 짜두었음

public class AlgorithmApp {
    public static void main(String[] args) {
        //test 하는 곳

        List<Member> memberList = new ArrayList<>();
        memberList.add(new Member(3, "김왕건"));
        memberList.add(new Member(1, "이성계"));
        memberList.add(new Member(6, "박수달"));

        Collections.sort(memberList);

        for (Member member : memberList) {
            System.out.println("Id = " + member.getId() + " Name = " + member.getName());
        }
    }
}

List를 만들고 거기에 3,1,6 순서대로 Member를 넣어줬다.
그리고 Collections.sort를 썻음

add한 순서와 상관 없이 정렬이 된 것을 볼 수 있다.

만약 내림차순으로 만들고 싶다면

    @Override
    public int compareTo(Member o) {
        if(this.id > o.id) {
            return -1;
        } else if(this.id == o.id) {
            return 0;
        }
        return 1;
    }

오버라이드 부분을 반대로 해주면 id기준 내림차순으로 정리 된다.
만약 정리 기준이 2개일 경우 (int가 2개라고 하자)

    @Override
    public int compareTo(Member o) {
        if(this.id > o.id) {
            return -1;
        } else if(this.id == o.id) {
        	if(this.secondId < o.secondId) {
            return 1; //2번째 id 값으로 내림차순
            }
            return 0;
        }
        return 1;
    }

이런 식으로 기준을 2개로 만들 수 도 있다. id가 첫번째고 같으면 secondId 변수를 기준으로 삼는거다.

2. Comparator

  • 정렬 가능한 클래스 (Comparable 인터페이스를 구현한 클래스)들의 기본 정렬 기준과 다르게 정렬 하고 싶을 때
  • 내림차순으로 정렬하라 때 많이 사용한다.
  • Comparator interface를 implements 후 compare()메서드를 오버라이드한 class를 작성한다.
    - 첫 번째 매개변수 < 두 번째 매개변수 : 음수 리턴
    • 첫 번째 매개변수 == 두 번째 매개변수 : 0 리턴
    • 첫 번째 매개변수 > 두 번째 매개변수 : 양수 리턴
  • 똑같이 음수, 0이면 자리유지, 양수인 경우 두 객체의 자리가 변경 된다.
public class MyComparator implements Comparator<Member> {

    @Override
    public int compare(Member o1, Member o2) {
        if(o1.getId() > o2.getId()) {
            return 1;
        } else if(o1.getId() == o2.getId()) {
            return 0;
        }
        return -1;
    }
}

인터페이스를 implements할 때 꼭 제네릭을 써주자. 안그러면 얘가 오버라이드 한지도 모르고 계속 에러 뱉음 ㅋㅋㅋ 메서드 구현하라고

하여튼 로직 자체는 같다.

        MyComparator myComparator = new MyComparator();
        Collections.sort(memberList, myComparator);

        for (Member member : memberList) {
            System.out.println("Id = " + member.getId() + " Name = " + member.getName());
        }

myComparator 객체를 생성해준다. (리스트는 상기 Comparable와 같음. 그 아래 작성했음)
Collections.sort(memberList, 정렬기준) 을 넣으면 된다.

오름차순으로 잘 정렬된 것을 확인할 수 있다.

상기 Comparable을 내림차순으로, Comparator를 올림차순으로 정렬해서 호출하면

이렇게 된다.

아래는 main의 풀 코드

    public static void main(String[] args) {
        //test 하는 곳

        List<Member> memberList = new ArrayList<>();
        memberList.add(new Member(3, "김왕건"));
        memberList.add(new Member(1, "이성계"));
        memberList.add(new Member(6, "박수달"));

        Collections.sort(memberList);

        for (Member member : memberList) {
            System.out.println("Id = " + member.getId() + " Name = " + member.getName());
        }

        System.out.println("-".repeat(20));

        MyComparator myComparator = new MyComparator();
        Collections.sort(memberList, myComparator);

        for (Member member : memberList) {
            System.out.println("Id = " + member.getId() + " Name = " + member.getName());
        }

    }

뭐가 더 편리한지는 각자 상황에 따라 다르겠지?

profile
https://crazyleader.notion.site/Crazykwak-36c7ffc9d32e4e83b325da26ed8d1728?pvs=4<-- 포트폴리오

0개의 댓글