JAVA 08 : 제네릭, 컬렉션

LeeWonjin·2022년 7월 19일

2022 백엔드스터디

목록 보기
8/20

Do it! 자바 프로그래밍 입문 12장

제네릭(Generic)

  • 여러 타입을 받아 처리할 수 있는 프로그래밍 기법. 일반화 프로그래밍(제네릭 프로그래밍) 한다고 표현.
  • 클래스 또는 메소드 뒤에 <T>와 같은 내용을 붙임
    • < >는 다이아몬드 연산자
    • T는 타입 매개변수

타입 매개변수

타입 매개변수는 레퍼런스 타입만 가능 (int불가, Integer가능)
일반적으로 아래와 같은 매개변수 이름 사용

<T> Type
<K> Key
<N> Number
<E> Element
<V> Value
class DoublePrinter <T> {
	private T target;
	public DoublePrinter(T target) {
		this.target = target;
	}
	
	public void print() {
		System.out.println(target);
		System.out.println(target);
	}
}

public class Test {
	public static void main(String[] args) {
		DoublePrinter<Integer> p1 = new DoublePrinter<Integer>(53); 
		p1.print(); // 53 53
		
		// 앞에 <Character>를 붙이지 않아도 타입 추론 가능
		DoublePrinter p2 = new DoublePrinter<Character>('a');
		p2.print(); // a a
	}
}

여러 개의 타입 매개변수

<>연산자 안에 콤마,로 구분하여 여러 개의 타입매개변수 입력 가능

class Printer <T, U> {
	private T target1;
	private U target2;
	
	public Printer(T target1, U target2) {
		this.target1 = target1;
		this.target2 = target2;
	}
	
	public void print() {
		System.out.println(target1);
		System.out.println(target2);
	}
}

public class Test {
	public static void main(String[] args) {
		Printer p1 = new Printer<Float, Float>(5.3f, 53.53f);
		p1.print(); // 5.3 53.53
		
		Printer p2 = new Printer<Boolean, Byte>(true, (byte)53);
		p2.print(); // true 53
	}
}

제네릭 메소드

제네릭 클래스가 아니더라도 제네릭 메소드를 가질 수 있음

class Printer {
	public <T, U> void print(T t, U u) {
		System.out.println(t);
		System.out.println(u);
	}
}

public class Test {
	public static void main(String[] args) {
		Printer p1 = new Printer();
		p1.<Integer, Double>print(53, 5.3); // 53 5.3
	}
}

아래와 같이 <T>가 두 번 등장할 수 있다.
여기서 클래스 Printer에 대한 <T>와 메소드 print()에 대한 <T>는 다른 T이다.

class Printer <T> { 
	private T variable_For_Class;
	public <T> void print(T variable_For_This_Method) {
		System.out.println(variable_For_This_Method);
	}
}

타입 제한

TCP School 타입변수의 제한
<T extends Something>과 같이 T를 특정 타입으로 제한 가능

class뿐 아니라 interface도 타입 매개변수를 제한할 때 implements가 아닌 extends사용

아래 코드에서 타입변수 T는 Language를 상속한 클래스만 올 수 있도록 제한됨

class CodeRunner <T extends Language> {
	private T target;
	
	public CodeRunner(T target) {
		this.target = target;
	}
	
	public void run() {
		System.out.println(target.toString());
	}
}

abstract class Language {
	protected int runCnt;
	public Language() {
		runCnt = 0;
	}
}

class Python extends Language {
	@Override
	public String toString() {
		runCnt++;
		return "Python is running / cnt : " + runCnt;
	}
}

class Java extends Language {
	@Override
	public String toString() {
		runCnt++;
		return "Java is running / cnt : " + runCnt;
	}
}

class Gorani {
	// something
}

public class Test {
	public static void main(String[] args) {
		CodeRunner pyRunner = new CodeRunner<Python>(new Python()); 
		CodeRunner javaRunner = new CodeRunner<Java>(new Java());
		pyRunner.run(); // Python is running / cnt : 1
		javaRunner.run(); // Java is running / cnt : 1
		javaRunner.run(); // Java is running / cnt : 2
		javaRunner.run(); // Java is running / cnt : 3
	
		// compile error (Gorani타입을 받을 수 없음)
		// CodeRunner goraniRunner = new CodeRunner<Gorani>(new Gorani());
	}
}

컬렉션(Collection)

─ 개요

** tree가 붙는 클래스는 Red-Black Tree로 구현된 BST사용

계열

두 계열의 클래스 모두 iterator를 사용한 순회 가능

  • Collection계열 : 단일 데이터 핸들링
    • List류 (순서가 있고 중복을 허용하는 선형 자료구조)
    • Set류 (순서가 없고 중복을 허용하지 않는 자료구조)
  • Map계열 : 쌍으로 구성된 데이터 핸들링 (key-value pair)

인터페이스

  • List (Collection인터페이스 상속)
  • Set (Collection인터페이스 상속)
  • Map

─ List의 구현클래스

JAVA 18 DOCS : Interface List

ArrayList, Vector

  • ArrayList : 동기화가 없는 배열객체의 구현
    • List변수 = Collections.synchronizedList(ArrayList의 인스턴스)로 받으면 동기화 가능
  • Vector : 동기화를 지원하는 배열객체의 구현
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
import java.util.Vector;

class Gorani {
	int id;
	String name;
	
	public Gorani (int id, String name) {
		this.id = id;
		this.name = name;
	}
	
	@Override
	public String toString() {
		return name+"#"+id;
	}
}

public class Test {
	public static void main(String[] args) {
		Gorani g1 = new Gorani(123, "a");
		Gorani g2 = new Gorani(35, "b");
		Gorani g3 = new Gorani(8999, "c");
		
		// ArrayList
		ArrayList list = new ArrayList<Gorani>();
		list.add(g1); list.add(g2); list.add(g3);
		System.out.println(list); // [a#123, b#35, c#8999]
	
		// 동기화 기능을 추가한 ArrayList
		List<Gorani> syncList = Collections.synchronizedList(new ArrayList<Gorani>());
		syncList.add(g1); syncList.add(g2);
		System.out.println(syncList); // [a#123, b#35]
		
		// Vector (동기화 지원)
		Vector v = new Vector<Gorani>();
		v.add(g1); v.add(g3);
		System.out.println(v); // [a#123, c#8999]
	}
}

Stack

JAVA 18 DOCS : Class Stack

import java.util.Stack;
public class Test {
	public static void main(String[] args) {
		Stack<String> s = new Stack<String>();
		s.push("a"); s.push("b"); s.push("c");
		System.out.println(s.empty()); // false
		System.out.println(s.peek()); // c
		System.out.println(s.pop()); // c
		System.out.println(s.pop()); // b
		System.out.println(s.size()); // 1
	}
}

Queue

실제 Queue라는 이름의 클래스는 없고, 인터페이스가 존재한다.
JAVA 18 DOCS : Interface Queue

아래 포스트에서 Queue<E> queue = new LinkedList<E>(); 꼴로 큐처럼 사용하는 예시를 볼 수 있다.
DevAndy : Java Collection - Queue

ArrayList로 구현할 수도 있다.
Github easyspubjava/JAVA_LAB/ ... /QueueTest.java

List의 Iterator

아래 순회방법 예시 1~3은 모두 같은 결과를 낸다.

import java.util.ArrayList;
import java.util.Iterator;
public class Test {
	public static void main(String[] args) {
		ArrayList<String> ar = new ArrayList<String>();
		ar.add("aa"); ar.add("bb"); ar.add("abcd");
		
		// 순회방법 예시 1 : enhanced for loop
		for(String item : ar)
			System.out.println(item); // aa bb abcd
		
		// 순회방법 예시 2 : for loop with get()
		for(int i=0; i<ar.size(); i++)
			System.out.println(ar.get(i)); // aa bb abcd
		
		// 순회방법 예시 3 : iterator
		Iterator<String> itr = ar.iterator();
		while(itr.hasNext())
			System.out.println(itr.next()); // aa bb abcd
	}
}

─ Set의 구현클래스

HashSet

해싱으로 구현하는 Set

타입매개변수에 직접 정의한 클래스를 넣는 경우 equals()hashCode()를 재정의 해야 해싱의 특성상 정상적으로 동작함

import java.util.HashSet;
import java.util.Iterator;

class Gorani {
	int id;
	String name;
	
	public Gorani (int id, String name) {
		this.id = id;
		this.name = name;
	}
	
	@Override
	public String toString() {
		return name+"#"+id;
	}
	
	@Override
	public boolean equals(Object obj) {
		if(obj instanceof Gorani) {
			Gorani g = (Gorani)obj;
			if(this.id == g.id)
				return true;
		}
		return false;
	}
	
	@Override
	public int hashCode() {
		return id;
	}
}

public class Test {
	public static void main(String[] args) {
		// String HashSet
		HashSet<String> hs1 = new HashSet<String>();
		Iterator<String> itr1;
		hs1.add("aa");
		hs1.add("bb");
		hs1.add("cc");
		
		itr1 = hs1.iterator();
		while(itr1.hasNext())
			System.out.println(itr1.next()); // aa bb cc
		
		hs1.add("bb"); // 이미 같은 원소가 존재하므로 추가되지 않음
		itr1 = hs1.iterator();
		while(itr1.hasNext())
			System.out.println(itr1.next());  // aa bb cc
		
		// Gorani HashSet
		HashSet<Gorani> hs2 = new HashSet<Gorani>(); // id가 유니크한 값
		Iterator<Gorani> itr2;
		hs2.add(new Gorani(123, "xx"));
		hs2.add(new Gorani(335, "yy"));
		hs2.add(new Gorani(975, "zz"));
		
		itr2 = hs2.iterator();
		while(itr2.hasNext())
			System.out.println(itr2.next()); // xx#123 yy#335 zz#975
		
		hs2.add(new Gorani(123, "xyzyzyz")); // 이미 같은 원소가 존재하므로 추가되지 않음
		itr2 = hs2.iterator();
		while(itr2.hasNext())
			System.out.println(itr2.next()); // xx#123 yy#335 zz#975
	}
}

TreeSet

객체의 정렬에 사용. Red-Black 기반의 BST로 구현되어있음.

타입매개변수에 직접 정의한 클래스를 넣는 경우 Case1, Case2 둘 중 하나를 수행해야한다.
( 클래스 식별자가 C라고 가정 )

  • Case1
    • Comparable<C> 인터페이스 구현
    • CompareTo(C obj)메소드 오버라이드
    • TreeSet생성자의 인수 없음
  • Case2
    • Comparator<C> 인터페이스 구현
    • Compare(C obj1, C obj2)메소드 오버라이드
    • TreeSet생성자의 인수로 new C()전달
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

class Gorani implements Comparable<Gorani>, Comparator<Gorani> {
	int id;
	String name;
	
	public Gorani() { }
	public Gorani (int id, String name) {
		this.id = id;
		this.name = name;
	}
	
	@Override
	public int compareTo(Gorani g) { // id기준 오름차순
		return this.id - g.id;
	}
	
	@Override
	public int compare(Gorani g1, Gorani g2) { // id기준 내림차순 (-1을 곱했기 때문)
		return (g1.id - g2.id) * (-1);
	}	
	
	@Override
	public String toString() {
		return name+"#"+id;
	}
}

public class Test {
	public static void main(String[] args) {
		// String TreeSet
		TreeSet<Integer> ts1 = new TreeSet<Integer>();
		ts1.add(53); ts1.add(999); ts1.add(1); ts1.add(68);
		
		Iterator<Integer> itr1 = ts1.iterator();
		while(itr1.hasNext())
			System.out.println(itr1.next()); // 1 53 68 999
		
		
		// Gorani TreeSet : Case1
		// ---> Comparable<E>가 구현 위임한 compareTo(E e)를 호출
		// ---> compareTo메소드는 오름차순 정렬하도록 오버라이드 하였음
		TreeSet<Gorani> ts2 = new TreeSet<Gorani>();
		ts2.add(new Gorani(123, "a")); ts2.add(new Gorani(11, "b")); ts2.add(new Gorani(99, "c"));
		
		Iterator<Gorani> itr2 = ts2.iterator();
		while(itr2.hasNext())
			System.out.println(itr2.next()); // b#11 c#99 a#123
		
		
		// Gorani TreeSet : Case2
		// ---> Comparator<E>가 구현 위임한 compare(E e1, E e2)를 호출
		// ---> compare메소드는 내림차순 정렬하도록 오버라이드 하였음
		TreeSet<Gorani> ts3 = new TreeSet<Gorani>(new Gorani());
		ts3.add(new Gorani(123, "a")); ts3.add(new Gorani(11, "b")); ts3.add(new Gorani(99, "c"));
		
		Iterator<Gorani> itr3 = ts3.iterator();
		while(itr3.hasNext())
			System.out.println(itr3.next());  // a#123 c#99 b#11
	}
}

─ Map의 구현클래스

JAVA 18 DOCS : Interface Map<K,V>

객체의 유일성(unique함)을 판단하기 위해 equals()hashCode() 재정의 필요(오버라이드)

HashMap

key와 value를 받는 클래스
key를 해싱해서 value저장

import java.util.HashMap;
import java.util.Iterator;

class Gorani {
 	int id;
	String name;
	
	public Gorani() { }
	public Gorani (int id, String name) {
		this.id = id;
		this.name = name;
	}
	
	@Override
	public String toString() {
		return name+"#"+id;
	}
	
	@Override
	public boolean equals(Object obj) {
		if(obj instanceof Gorani) {
			Gorani g = (Gorani)obj;
			if(this.id == g.id)
				return true;
		}
		return false;
	}
	
	@Override
	public int hashCode() {
		return id;
	}
}

public class Test {
	public static void main(String[] args) {
		// key로 Gorani의 id (int) 사용
		// value로 Gorani인스턴스 사용
		HashMap<Integer, Gorani> hashmap = new HashMap<Integer, Gorani>();
		hashmap.put(123, new Gorani(123, "aaaaa"));
		hashmap.put(666, new Gorani(666, "zdfdfd"));
		hashmap.put(999, new Gorani(999, "0o0o0o0o0"));
		
		var tmp = hashmap.get(123);
		System.out.println(tmp.name); // aaaaa
		
		tmp = hashmap.remove(666);
		System.out.println(tmp.name); // zdfdfd
		
		// 순회
		System.out.println();
		Iterator<Integer> itr = hashmap.keySet().iterator();
		while(itr.hasNext()) {
			var key = itr.next();
			System.out.println( hashmap.get(key) ); // 0o0o0o0o0#999 aaaaa#123
		}	
	}
}

Hashtable

HashMap과 유사하고 동기화 지원 (ArrayList-Vector의 관계와 유사)
JAVA 18 DOCS : Class Hashtable<K,V>

TreeMap

iterator로 원소를 순회하면 key값을 기준으로 정렬되어 나온다.
Set계열 클래스 중 TreeSet과 유사한 원리로 대소비교.
(key값에 대해 비교 인터페이스 구현 + 비교 메소드 오버라이드 필요)

사용법은 HashMap과 동일

import java.util.Iterator;
import java.util.TreeMap;

class Gorani {
 	int id;
	String name;
	
	public Gorani() { }
	public Gorani (int id, String name) {
		this.id = id;
		this.name = name;
	}
	
	@Override
	public String toString() {
		return name+"#"+id;
	}
	
	@Override
	public boolean equals(Object obj) {
		if(obj instanceof Gorani) {
			Gorani g = (Gorani)obj;
			if(this.id == g.id)
				return true;
		}
		return false;
	}
	
	@Override
	public int hashCode() {
		return id;
	}
}

public class Test {
	public static void main(String[] args) {
		// key로 Gorani의 id (int) 사용
		// value로 Gorani인스턴스 사용
		TreeMap<Integer, Gorani> treemap = new TreeMap<Integer, Gorani>();
		treemap.put(123, new Gorani(123, "a"));
		treemap.put(666, new Gorani(666, "d"));
		treemap.put(999, new Gorani(999, "ee"));
		treemap.put(2, new Gorani(2, "ccc"));
		treemap.put(53, new Gorani(53, "ee"));
		
		var tmp = treemap.get(999);
		System.out.println(tmp.name); // ee
		
		tmp = treemap.remove(999);
		System.out.println(tmp.name); // ee
		
		// 순회
		System.out.println();
		Iterator<Integer> itr = treemap.keySet().iterator();
		while(itr.hasNext()) {
			var key = itr.next();
			System.out.println( treemap.get(key) ); // ccc#2 ee#53 a#123 d#666
		}	
	}
}
profile
노는게 제일 좋습니다.

0개의 댓글