지난 번에 쓴 글에서 컬렉션과 제네릭의 기초에 대해 정리했다. 이번에는 HashMap과 LinkedList, 그리고 Collections 클래스 활용에 대해서 그리고 사용자가 직접 만드는 제네릭에 대해 알아보았다.
HashMap<K, V> 컬렉션
HashMap<K, V>컬렉션의 경로명은 java.util.HashMap이며 키와 값의 쌍으로 이루어졌다. K는 '키'로 사용할 데이터 타입을, V는 값으로 사용할 데이터 타입을 넣어주면 된다. 새로운 키-값 쌍을 넣어줄 때에는 put, 키에 대한 값을 불러올 때는 get을 이용해주면 된다.
HashMap<String, String> h = new HashMap<String, String>();
h.put("apple","사과");
String kor = h.get("apple");
System.out.println(kor);
해시맵은 해시 함수를 이용해서 각 키와 값이 저장되는 위치를 결정하므로, 사용자 입장에서는 그 위치를 알 수 없고 삽입되는 순서와 들어있는 위치도 관계가 없다. 이는 해시 구조에 대해서 배우면서도 사용한 개념을 미리 만들어둔 클래스라고 이해하면 된다.
해시맵의 장단점
해시맵은 Vector<E>나 ArrayList<E>와는 다른 점이 있다. 먼저 요소의 삽입과 삭제의 시간이 굉장히 빠르다. 요소의 위치를 결정하는 해시 함수가 간단한 코드로 이루어지며, Vector<E>나 ArrayList<E>와 달리 요소의 삽입 삭제 시 다른 요소들을 밀거나 당겨 움직일 필요가 없기 때문이다.
두번째로 요소 검색은 더욱 빠르다. get(key) 메소드가 호출되면 해시 함수가 key가 저장된 위치를 단번에 찾아내므로 Vector<E>나 ArrayList<E>에서처럼 모든 요소들을 하나씩 비교할 필요가 없다.
하지만 해시맵은 인덱스를 이용해서 요소를 찾을 수 없고 오직 '키'만을 이용해야 한다는 단점이 있다.
다음은 해시맵에 사용될 수 있는 메소드이다.
메소드 | 사용법 |
void clear() | 해시맵의 모든 요소 삭제 |
boolean conatinsKey(Object key) | 지정된 키를 포함하고 있으면 true |
boolean containsValue(Object value) | 지정된 값에 일치하는 키가 있으면 true |
V get(Object key) | 지정된 키의 값 리턴, 키가 없으면 null 리턴 |
boolean isEmpty() | 해시맵이 비어있으면 true 리턴 |
Set<K> keySet() | 해시맵의 모든 키를 담은 Set<K> 컬렉션 리턴 |
V put(K key, V value) | key와 value 쌍을 해시맵에 저장 |
V remove(Object key) | 지정된 키(key)를 찾아 키와 값 모두 삭제 |
int size() | HashMap에 포함된 요소의 개수 리턴 |
해시맵을 활용한 예시는 다음과 같이 될 수 있다.
import java.util.*;
class Student{
private int id;
private String tel;
public Student(int id, String tel) {this.id = id; this.tel = tel;}
public int getId() {return id;}
public String getTel() {return tel;}
}
public class practice_7 {
public static void main(String args[]){
var map = new HashMap<String, Student>();
map.put("김민규", new Student(1, "010-1111-1111"));
map.put("이석민", new Student(2, "010-2222-2222"));
map.put("윤정한", new Student(3, "010-3333-3333"));
map.put("홍지수", new Student(4, "010-4444-4444"));
Scanner scanner = new Scanner(System.in);
while(true){
System.out.print("검색할 이름?");
String name = scanner.nextLine();
if (name.equals("exit"))
break;
Student student = map.get(name);
if (student == null)
System.out.println(name + "은 없는 사람입니다");
else
System.out.println("id:"+student.getId() + ", 전화:" +student.getTel());
}
}
}
Collections 클래스 활용
java.util 패키지에 포함된 collections 클래스는 컬렉션을 다루는 유용한 메소드를 다음과 같이 지원한다.
sort() | 컬렉션에 포함된 요소들의 정렬 |
reverse() | 요소를 반대 순으로 정렬 |
max(), min() | 요소들의 최대값, 최소값 찾아내기 |
binarySearch() | 이진 검색 |
Collections 클래스의 메소드는 모두 static 타입이므로 Collections 객체를 생성할 필요는 없다. 이 유틸리티 메소드들은 인자로 컬렉션 객체를 전달받아 처리한다.
Collections 클래스의 활용 코드
import java.util.*;
public class practice_8 {
static void printList(LinkedList<String> l){
Iterator<String> iterator = l.iterator();
while(iterator.hasNext()){
String e = iterator.next();
String separator;
if (iterator.hasNext())
separator = "->";
else
separator = "\n";
System.out.print(e+separator);
}
}
public static void main(String args[]){
var myList = new LinkedList<String>();
myList.add("아이언맨2");
myList.add("스파이더맨");
myList.add("토르-라그나로크");
myList.add(0, "아이언맨1");
myList.add(2, "아이언맨3");
printList(myList);
Collections.sort(myList);
printList(myList);
Collections.reverse(myList);
printList(myList);
int index = Collections.binarySearch(myList, "아이언맨3") + 1;
System.out.println(index);
}
}
제네릭 만들기
제네릭 클래스를 프로그래머가 직접 작성하는 방법은 기존의 클래스 작성 방법과 비슷하다. 클래스 이름 다음에 일반화된 타입의 매개변수를 <> 사이에 추가한다는 차이가 있다. 다음은 간단한 제네릭 클래스 작성 예시이다.
public class MyClass<T>{ //T는 타입 매개변수
T val;
void set(T a){
val = a; // T 타입의 값 a를 val에 지정
}
T get(){
return val;
}
}
제네릭 클래스의 레퍼런스 변수를 선언할 때 다음과 같이 타입 매개변수에 구체적인 타입을 적으면 된다.
MyClass<String> s; // <T>를 String으로 구체화
제네릭 객체 생성할 때, 클래스에 구체적인 타입을 대입하여 구체적인 객체를 생성하는 과정을 구체화라고 한다.이 과정은 자바 컴파일러에 의해 이루어진다. MyClass<T>에 T에 구체적인 타입을 지정하여 객체를 작성하는 예는 다음과 같다.
MyClass<String> s = new MyClass<String>();
s.set("hello");
System.out.println(s.get()); // "hello" 출력
이런 코드를 이용하면 public class MyClass<T>의 정의된 부분에서 T가 String이라고 생각하면 된다. 마찬가지로 여기서도 기본 데이터 타입 (int 등)으로 사용할 수 없고 Integer 이런 식으로 표현해야 한다.
제네릭 클래스 생성 및 활용 예시 코드
class GStack<T>{
int tos;
Object [] stck;
public GStack(){
tos = 0;
stck = new Object[10];
}
public void push(T item){
if (tos == 10)
return;
stck[tos] = item;
tos++;
}
public T pop(){
if (tos == 0)
return null;
tos--;
return (T)stck[tos]; // 타입 매개변수 타입으로 캐스팅
}
}
public class practice_9{
public static <T> GStack<T> reverse (GStack<T> a){
GStack<T> s = new GStack<T>();
while(true){
T tmp;
tmp = a.pop();
if (tmp == null)
break;
else
s.push(tmp);
}
return s;
}
public static void main(String args[]){
var gs = new GStack<Double>();
for (int i=0; i<5; i++){
gs.push((double) i);
}
gs = reverse(gs);
for (int i=0; i<5; i++){
System.out.println(gs.pop());
}
}
}
제네릭은 동적으로 타입이 결정되지 않고 컴파일 시에 타입이 결정되므로 보다 안전한 프로그래밍이 가능하다는 장점이 있다. 또 런타임 타입 충돌 문제를 방지할 수 있고, 개발 시 타입 캐스팅 절차가 불필요하다.
'TIL > Java | Spring Boot' 카테고리의 다른 글
[Spring Boot] spring-boot-devtools 라이브러리 추가 (0) | 2022.01.03 |
---|---|
2022.1.1 TIL : [Java] 컬렉션과 제네릭 (Collection & Generic) - 마무리 (0) | 2022.01.01 |
2021.12.30 TIL : [Java] 컬렉션과 제네릭 (Collection & Generic) - 1 (0) | 2021.12.30 |
2021.8.12 TIL : [Java] 기초 문법2 - 객체지향 프로그래밍 응용(2) (0) | 2021.08.12 |
2021.8.3 TIL : [Java] 기초 문법2 - 객체지향 프로그래밍 응용(1) (0) | 2021.08.03 |
댓글