제네릭스 and 컬렉션 프리임워크

이영광·2022년 2월 8일

자바

목록 보기
10/34
post-thumbnail

제네릭스 는 자바언어의 매우 크게 영향을 끼친 문법이다. 제네릭스가 자바에 추가 되면서 자바 api변화가 있었으며 융통성 있는 코드를 작성할수 있게 되었다.

제네릭스가 무엇이고 왜 중요한지 실제 코딩에서 어떻게 적용할 수 있는지 알아보고 제네릭스가 가장 많이 적용되는 부분인 컬렉션 프레임워크에 대해 공부해보자

제네릭스 기본

제네릭스는 "매개변수화된 자료형(paramterized type)"이다. 제네릭스는 클래스, 인터페이스 또는 메소드에 적용될 수 있는데, 클래스에 적용되면 제네릭 클래스, 인터페이스에 적용되면 제네릭 인터페이스, 적용되면 제네릭 인터페이스, 그리고 메소드에 적용되면 제네릭 메소드라고 부른다.

제네릭 메소드에 대해 생각해보자, 메소드에 인수를 넘길 떄는 반드시 해당하는 매개변수의 자료형과 일치되는 자료형을 갖는 인수를 넘겨야한다. 즉, 정수 매개변수에는 정수 인수를 넣어야된다. 그런데 제네릭스를 이용하면 매개변수에 다양한 자료형의 데이터를 넘길수있다. 예를 들어, 어떤 메소드가 void set(Integer x)라고 한다면, 매개변수 x자리에는 정수를 넣어야 한다, 만약에 여기에서 x 자리에 Integer 객체 뿐 아니라 다른 타입의 개체를 인수로 넣고 싶다면 Integer 자리 자체를 매개변수로 취급하면 된다. 제네릭스 개념이 여기서부터 시작된다.

ex

void sort(Integer[] x){
...
}

void sort(Double[] x){
...

-> void sort(T[] x){

}
}

Integer 대신에 T라고 넣었는데 T에는 어떤 객체도 올수 있다는 의미이다.

void set(Integer x){
System.out.println(x);
}
//매개변수에는 Integer 객체를 넣을수 있다

=>

void set(T x){
  System.out.println(x);
}

//매개변수 T자리에는 자료형이 와야하는데, T라고 적은것은 어떤 자료형도 T에 넣을수 있다는 뜻

정렬 프로그램과 같이 많은 프로그램들이 자료형에 관계없이 같은 코드를 사용해야 하는 경우가 있다. 이러한 경우에 제네릭스가 매우 유용하다, 즉 제네릭스는 코드 재사용성을 높여준다. 제네릭스를 사용할때 주의점은 레퍼런스형(클래스)만 제네릭스로 사용할 수 있다는 것이다. 즉 int, double등과 같은 기본자료형은 제네릭스로 사용할수가 없다.

제네릭 클래스

제네릭 클래스는 일반적으로 아래 왼쪽의 형태를 갖는다. 그리고 이 클래스의 객체를 생성할때에는 <>기호를 사용하여 어떤 타입의 인수를 넣을지 알려줘야 한다(아래 오른쪽)

클래스명<매개변수 타입 리스트>{...}클래스명<타입> 객체명 = new 클래스명<타입>()

이해를 하기 위해 간단한 제네릭스 사용예제

package generics;

class Data<T>{
    // 클래스명 옆에 제네릭 기호 <> 를 적고 그안에 매개변수 기술

    T obj; //인스턴스 변수 obj의 자료형은 T이다
    Data(T ob){//constructor BasicGeneric 은 자료형이 T인 인수 한개를 입력받는다.

         obj = ob;
    }

    T getObj(){//인스턴스 변수 obj의 자료형은 T임
        return obj;
    }

    void showType(){
        System.out.println("Type of T: " + obj.getClass().getName());
// getClass() :Object클래스안에 정의 리턴값은클래스
// getName() :Class클래스 안에 정의 리턴값은 클래스명
    }
}


public class BasizGeneric<T> { 
    
    public static void main(String[] args){
        Data<Integer> d1 = new Data<Integer>(100);//정수 100인수
        System.out.println(d1.getObj());
        d1.showType();

        Data<String> d2 = new Data<String>("JAVA");//문자열 'JAVA' 인수
        System.out.println(d2.getObj());
        d2.showType();
    }
}

Data클래스는 제네릭 클래스이고 , getObj()메소드는 제네릭 메소드라고 한다.

  • 제네릭에 이용할 수 있는 자료형

    • 제네릭으로 사용할 수 있는 자료형은 레퍼런스 형이어야 한다. int, Double 등과 같은 자바의 기본 자료형은 제네릭으로 사용할수 없다. 만약 기본 자료형을 제네릭으로 사용하고자 한다면, wrapper클래스인 Integer, Double 등을 이용해야한다.

      [ https://velog.io/@bahar-j/String-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%99%80-Wrapper-%ED%81%B4%EB%9E%98%EC%8A%A4

      #자바에서 문자열은 String 클래스의 객체를 만드는 방식으로 구현된다. 즉, 문자열은 다른 데이터들과는 다르게 참조형 데이터 타입(그 중에서도 object 데이터 타입)을 가진다.

    • Wrapper 클래스
      기본형 데이터 타입과 참조형 데이터 타입 간의 형 변환을 할 수 있도록 해주는 매개체가 래퍼 클래스이다. 객체 지향에서는 모든 것이 객체로 다뤄져야하는데, 자바의 기본형 데이터들은 편의성을 위해 이러한 객체 지향의 개념 밖에 존재한다. 그러나 때로는 이것들을 객체로 다뤄야할 경우가 있는데, 이를 위해 존재하는 것이 래퍼 클래스이다.]

  • 제네릭 타입도 엄격하게 문법에 따라야 한다.

    • 위의 예에서 다음과 같이 제네릭 타입끼리 비교하려고 한다면 에러가 발생한다.
Data<Integer> d1 = new Data<Integer>(100);
Data<String> d2 = new Data<String>("100");

if(d1 == d2){// 에러발생
  System.out.println("same data")
에러문장은 "Incompatible operand types Data<Integer> and Data<String>'입니다
즉, 제네릭을 사용하는 경우에는 제네릭까지 포함해서 Data<Integer>,Data<String>이 자료형이되고, 
따라서 d1 은 Data타입이아니라 Data<Integer>타입이며, d2는 Data<String> 타입이 되는것이다.
두타입은 분명히 다르다.

[예제] 제네릭 타입을 2개 사용

package generics;


class TwoGenerics<T,V>{
    T data1;
    V data2;

    TwoGenerics(T d1, V d2){
        data1 = d1;
        data2 = d2;
    }

    void showGenericType(){
        System.out.println("Type of T : " + data1.getClass().getName());
        System.out.println("Type of V : " + data2.getClass().getName()); 
        }
    T getData1(){return data1;}
    V getData2(){return data2;}

}


public class Basiz2 {

    public static void main(String[] args){
        TwoGenerics<Integer,String> x = new TwoGenerics<Integer, String>(100,"hello");

        x.showGenericType();
        int y = x.getData1();

        System.out.println("value: " + y);
        String z = x.getData2();
        System.out.println("value " + z);
    }
    
}

클래스는 인티저와 스트링 2개의 타입을 가지며 컨스트럭터에 정수와 문자를 넣었을때 인티저와 스트링으로 할당이된다
값을 출력할때는 인트같은 기본자료형에 다시 형변환이 되었다. integer로 해도 정상출력된다 자료형만 다르다.

제한된 제네릭 타입

제네릭을 제한된 형태로 사용해야하는 경우

<T extends V>

위는 제네릭 T자리에는 클래스 타입이 V이거나 V클래스의 하위 클래스 타입만 올수 있다는 뜻

class Parent{
...
}

class Child! extends Parent{
...
}

class Child2 extends Parent{
...
}

-----------------------------------
  
class Data<T extends Parent(이자리에는 부모클래스또는 그 하위 클래스 만 넣을수있따)> { 
  ... 
}
(
  ok
Data<Parent>
Data<Child1>
Data<Child2>
 )

Data<String> -> err

[예제3]

package generics;

class Datas<T extends Number> {
	
    T obj;

	Datas(T ob) {
		obj = ob;
	}
	
	int calcMultiple(int n) {
		return obj.intValue( ) * n;
	}
}
public class Test3 {
    public static void main(String[] args){
        Datas<Integer> d = new Datas<Integer>(100);
        int result = d.calcMultiple(5);
        System.out.println(result);

        Datas<Double> e = new Datas<Double>(17.5);
        int result2 = e.calcMultiple(5);
        System.out.println(result2);
    }
}


타입은 넘버를 상속 받았고 하위로 인티저나 더블이 있다 제네릭에 인티저와 더블을 사용해 콘스트럭트에 값을 넣어주고
인스턴스 변수에는 기록이 된다 메소드를 호출시 intValue가 integer에서 값을 뽑아주고 출력이된다.

와일드 카드 인수

와일드카드는 "?" 로 나타내며 와일드 카드 자리에는 어떤 클래스 타입도 올 수 있다는 의미이다.

[예제4]

package generics;


class WithWild<T extends Number>{
    T data;
    WithWild(T d){
        data = d;
    }
    boolean same(WithWild<?> x){
        if(Math.abs(data.doubleValue()) == Math.abs(x.data.doubleValue())){
            return true;
        }
        return false;
    }
}


public class WildeCard {
    public static void main(String[] args){
        WithWild<Integer> a = new WithWild<Integer>(6);
        WithWild<Double> b = new WithWild<Double>(-6.0);
        WithWild<Long> c = new WithWild<Long>(5L);
        
        if(a.same(b)){
            System.out.println("a and b are equal");
        }else{
            System.out.println("a and b are different");
        }
        if(a.same(c)){
            System.out.println("a and c are equal");
        }else{
            System.out.println("a and c are different");
        }

    }
    
}
같은 자리에 오버로드 같이 인수들이 다양하게 들어간다

컬렉션 프레임워크

프로그램을 작성할때 중요한 것은 데이터 처리하는것이다. 예를들어, 10000명 항생의 성적 처리 프로그램을 작성한다면 10000명의 성적을 어떤 형태로든 컴퓨터 메모리에 저장해야 될 것이며, 저장한 데이터 1만개 데이터중 가장 큰값 적은값으로 평균을 구할수 있을것이다. 1만개의 데이터를 가장 쉽게 저장하는방법은 자바에서 배열을 생각해 볼 수가 있을것이다. 자바의 배열을 이용하면 1만개의 데이터를 저장할 수 있는 공간을 한꺼번에 만들수 있다(메모리 위치 1개당 일일이 저장x) 배열처럼 많은 데이터를 저장할수 있는 공간을 자료 구조라고 합니다. 즉 자료를 저장하는 메모리 상의 구조 라는 의미

자바에서는 배열 외에도 많은 데이터를 일정한 구조로 저장할 수 있도록 해 주는 클래스들이 여러 개 잇다. 그러한 클래스들을 모아서 컬렉션 프레임워크라고 하며 여기에서는 제제릭이 의미가 잇다. 메모리 공간에 어떤 객체라도 저장하려면 다양한 객체를 받을 수 있는 제네릭을 사용해야 하기 떄문이다.

  • 자료구조
    컴퓨터 메모리에 데이터를 저장하는 형태를 말한다. 대표적으로는 배열이 있고, 배열 이외에도 ArrayList, LinkesList, Stack ,Queue 등과 같은 자료 구조가 자바 Api에서 제공되고 있다 . 컬렉션 프레임 워크는 이러한 자료 구조 패키지들을 말한다.

컬렉션 프레임워크에는 여러 인터페이스와 클래스가 있으며, 각 인터페이스와 클래스의 특징과 어떻게 활용하는지를 잘 알아 둬야 효과적인 코딩을 할수 있다. 컬렉션 프레임워크가 어떤 인터페이스와 클래스로 구성되있는지 보자!

https://imbf.github.io/interview/2021/03/03/NAVER-Practical-Interview-Preparation-5.html

List,Set , Map 이 중요하며, 각 인터페이스들의 특징은 다음과 같다

인터페이스특징
List순서가 있는 데이터 집합으로 데이터 중복을 허용, 구현클래스:ArrayList, LinkedList,Stack,Vector등
Set순서가 없는 데이터 집합으로 데이터 중복을 허용하지 않음, 구현클래스 : HashSet,TreeSet 등
Map<key,value>쌍으로 이루어진 데이터 집합으로 순서가 없음, 키는 중복될 수 없고, 값은 중복가능함,구현클래스:HashMap, TreeMap,Hashtable,Propoerties등

위의 클래스에는 레퍼런스 타입의 데이터인 객체만 저장할 수 있으며, 기본 자료형을 저장하려면 Wrapper클래스를 이용해야 한다.

List,Set,Map 인터페이스 모두 Collection 인터페이스를 상속 받고 있고 따라서 Collection 인터페이스에 어떤 메소드들이 있는지 잘 알아 두어야 한다. Collection 인터페이스에서 많이 사용하는 메소드들은 다음과 같다.

[collection 인터페이스의 주요 메서드]

메소드설명
boolean add(E e),boolean addAll(Collection<? extends E>cCollection에 객체를 추가함
void clear()Collection에 저장된 모든 객체를 삭제함
boolean contains(Object o),boolean containsAll(Collection<?>c)Collection에 객체 또는 다른 Collection 이 포함되어 있는지 판단함
Iterator,iterator()Collection을 순환할 반복자(iterator)를 반환
boolean remove(Object o),boolean removeAll(Collection<?>c)Collection에서 객체를 삭제
int size()Collection에 포함된 원소의 개수 반환
- Iterator<E>  4번칸 참조

List 인터페이스

List 인터페이스는 객체가 저장되는 순서가 있고 중복된 데이터를 가질 수 있도록 허용한다. List 인터페이스를 구현한 주요 하위 클래스로는 ArrayList, Vector,LinkedList 등이 있는데, ArrayList와 LinkedList에 대해서 알아보자

ArrayList

ArrayList는 배열과 같은 구조를 갖고 있지만 훨씬 융통성있게 활용 가능하다. 자바에서 배열은 생성할 때 배열의 크기를 분명히 주어야한다. 또한 프로그램이 끝날 때까지 그 크기를 유지해야 한다. 이것이 배열의 단점이다. 다음과 같이 크기 5인 정수 배열을 만들었다면 데이터를 5개 까지만 저장할 수 있다.

int[] A = new int[5]

ArrayList에는 객체만 넣을 수 있습니다.


int[] A = new int[5]

A[0][1][2][3][4] 

배열 A의 크기는 늘리거나 줄일수 없으며 더많은 데이터를 넣으려면 배열을 새로 만들어야 합니다.

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

B.add(10);
B.add(20);
B.add(30);

ArrayList는 add메소드를 이용하여 얼마든지 원하는 만큼 데이터를 저장할수있다. 자바스크립트의 push 같은 의미

즉 변경 가능한 배열로 데이터를 얼마든지 저장할수 있으며, 다음 예제를 통해 ArrayList를 이해해 보자 Collection 인터페이스에 있는 메소드들이 오버라이딩되어 제공되기 떄문에 add(),size()등의 메소드를 바로 사용할수 있다.

package listinterface;
import java.util.ArrayList;

public class ArrayLists {
    public static void main(String[] args){

        ArrayList<String> number = new ArrayList<String>();
        number.add("one");
        number.add("two");
        number.add("three");
        number.add("four");

        for(int i = 0 ; i<number.size(); i++){
            System.out.print(number.get(i));
        }


    }
}
onetwothreefour

    for(String x: number){
         System.out.print(x);
     }
for-each구문으로도 가능하지만

  for(int i = 0 ; i< number.size(); i++){
        System.out.println(number[i]);
    }
이것처럼 배열 루프 를 수행할수 는 없다

[예제]

package listinterface;
import java.util.ArrayList;
public class ArrayList1 {
    public static void main(String[] args){
        ArrayList<String> list = new ArrayList<String>();

        list.add("C");
        list.add("Java");
        list.add("HTML5");
        list.add(1, "C++");
        list.set(0,"Fortran");
        list.remove(2);
        list.remove("C++");

        for(int i = 0 ; i< list.size() ; i++){
            String s = list.get(i);
            
            System.out.println(s);
             // ["C"]["Java"]["HTML5"]

        }
    }
    
}

ArrayList에 루프를 수행할수 있는 또 다른 방법이 있다. 다음과 같이 이터레이터(iterator)를 이용하여 자동으로 반복이 수행될 수 있도록 하는 방법이다.

package listinterface;
import java.util.ArrayList;
import java.util.Iterator;

public class ArrayList3 {
    public static void main(String[] args){
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(100);
        list.add(200);
        list.add(50);
        list.add(1,55);
        list.add(3,77);

        Iterator<Integer> iter = list.iterator();//iterator(반복자)를 얻는다
        while(iter.hasNext()){
            Integer t = iter.next();
            System.out.println(t);
        }
    }
}
반복자 메서드를 붙여준다음에 

while문을 통해 hasNext()다음 반복대상이 있을경우 true or false 로 구분해 t 에
next()로 할당해나간다

LinkedList

LinkedList는 배열이나 ArrayList와는 약간 다르다. 데이터의 순서가 있고 중복된 데이터를 저장해야 한다는 점은 같지만 내부구성이 다르다 구현해보자

package listinterface;
import java.util.Iterator;
import java.util.LinkedList;


public class LinkedList1 {
    public static void main(String[] args){
        LinkedList<String> list = new LinkedList<String>();

        list.add("red");
        list.add("blue");
        list.add("purple");
        list.add("yello");
        list.add("green");

        for(String s : list){
            System.out.println(s);
        }
        System.out.println("_______________________-");

        Iterator<String> iter = list.iterator();
        while(iter.hasNext()){
            String t = iter.next();
            System.out.println(t);
        }


    }
    
}
red
blue
purple
yello
green
_______________________-
red
blue
purple
yello
green

ListArray 종류는 객체를 저장 삭제 하는거라 add 와 remove를 썻다.
앞에 push와 같다고 썻는데 성격이 다른구조이다 원리는 비슷하다

Stack

[0,1,2,3,4] 맨오른쪽 이나 맨 왼쪽 끝에서만 데이터의 저장/삭제가 이루어지는 자료구조

데이터저장 (push)
데이터삭제 (pop)

[Stack 생성자]

Stack은 다음과 같이 디폴트 생성자 한개를 갖는다

  • Stack() : 빈스택을 생성함.

[Stack 메소드]

메소드 : 설명

  • boolean empty() : 스택이 비어있는지 판단함
  • E peek() : 스택 탑에 있는 원소를 반환함.
  • E pop() : 스택 탑에 있는 원소를 삭제하고 반환함.
  • E push(E item) 스택 탑에 원소 item을 추가함.
  • int search(Object o) 스택에서 객체 o를 찾아서 있으면 위치를 반환함

[예제]


package stack;
import java.util.Stack;

public class Stack1 {

    public static void main(String[] args){
        Stack<Integer> stk = new Stack<Integer>();//스택은 정수스타일을 받는다

        stk.push(10);
        stk.push(20);
        stk.push(30);

        Integer data = stk.pop();
        System.out.println("You popped: " + data );
        stk.push(40);

        while(!stk.empty())
        
        System.out.println(stk.pop());

        System.out.println(stk);
    }
    
}
You popped: 30
40
20
10
[]

peek() 메소드

package stack;
import java.util.Stack;

public class StackPeek {
    public static void main(String[] args){
        Stack<Integer> stk = new Stack<Integer>();

        stk.push(10);
        stk.push(20);
        stk.push(30);

        Integer data = stk.peek();

        System.out.println("you peeked: " + data);

        stk.push(40);

        int index = stk.search(40);
        System.out.println("data 40 is at " + index);

        while(!stk.empty()){
            System.out.println(stk.pop());
        }
        System.out.println(stk);
    }
    //search()함수의 경우 최상단이 1로 간주된다
}
you peeked: 30
data 40 is at 1
40
30
20
10
[]

Queue

Queue는 한 쪽에서 데이터가 추가되고 반대 쪽에서 데이터를 삭제하는 구조이다. 일반적으로 줄 서는 구조를 생각하면되며, 드라이브 쓰루를 생각하면된다 선입선출(먼저들어온 것이 먼저나간다) 구조를 Queue 라고한다

  • 큐는 한쪽에서 데이터가 저장되고 반대쪽에서는 삭제가 되는 자료구조이다
  • 데이터가 삭제되는곳을 '헤드'라고 부른다

Queue는 인터페이스이기 때문에 생성자가 없다. 따라서 Queue를 구현하려면 Queue의 하위 클래스를 이용해야 한다. Queue의 하위 클래스 중에서 LinkedList를 이용해서 Queue를 구현해 보자

[Queue 메소드]

메소드 :설명

  • boolean add(E e) : 원소 e를 큐에 추가함 , 공간이 부족하면 lllegalStateExceiption 발생
  • E element() : 큐의 헤드에 있는 원소를 반환함. 큐에서 삭제되지는 않음.
  • boolean offer(E e) : 원소 e를 큐에 추가함.
  • E peek() : 큐의 헤드에 있는 원소를 반환함. 큐에서 삭제되지는 않음.
  • E poll() : 큐의 헤드에 있는 원소를 반환하고 삭제함, 만약에 큐가 비어있으면 null반환
  • E remove() : 큐의 헤드에 있는 원소를 반환하고 삭제함.
package queue;
import java.util.Queue;
import java.util.LinkedList;

public class Que {
 public static void main(String[] args){
     Queue<Integer> q = new LinkedList<Integer>();

     q.add(10);
     q.add(20);
     q.add(30);
   
    Integer data =  q.poll();
     System.out.println("you polled :" + data);
     q.add(40);
     System.out.println(q);

     while(!q.isEmpty()){
         System.out.println(q.poll());
     }
 }   
}
you polled :10
[20, 30, 40]
20
30
40

Iterator 와 ListIterator

이터레이터는 컬렉션 프레임워크에서 순환자 역할을 합니다. 자동으로 원소를 하나씩 순환해 나가는 특별한 인터페이스이다. Iterator의 메소드들을 정리해보자

  • 메소드 : 설명
  • boolean hasNext() : 다음 원소가 더 있는지 판단 , 있다면 true
  • E next() : 다음 원소 반환

[ListIterator 메소드]

  • 메소드 : 설명
  • boolean hasNext() : 다음 원소가 더 있는지 판단 , 있다면 true
  • boolean hasPrevious() : 이전에 원소가 있는지 판단함. 원소가 있다면 true반환
  • E next() : 다음 원소 반환
  • E previous() : 이 리스트 반복자가 해당 리스트를 역방향으로 순회할 때 다음 요소를 가지고 있으면 true를 반환하고, 더 이상 다음 요소를 가지고 있지 않으면 false를 반환함.

ArrayList 에 ListIterator를 적용해보자

  • ArrayList는 객체만되고 루프를 실행시킬수없기때문에 ListIterator로 시도한다

[예제]

package iteratorandlistiterator;
import java.util.ArrayList;
import java.util.ListIterator;
public class Test1 {

    public static void main(String[] args){
        ArrayList<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");

        ListIterator<String> it = list.listIterator();

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

        while(it.hasPrevious()){
            System.out.print(it.previous());
        }
    }
    
}
12345
54321%      

iterator는 재사용이 안됩니다 그래서 한번 작동하고 난다음에 다시 하고 싶으면 또 다시 만들어서 새로 시작해야 됩니다

Set 인터페이스

Set 인터페이스는 집합을 의미한다. 그래서 집합의 특징 그대로 원소의 순서가 없고 중복된 원소를 넣지 않는다 Set 인터페이스를 구현한 대표적인 클래스로는 TreeSet과 HashSet 두 개가 있습니다.

TreeSet

TreeSet은 트리 구조를 갖추고 있다. 트리 구조는 특별한 자료 구조인데, 자바의 TreeSet 클래스는 트리 구조 중에서 이진 트리 구조 형태를 취한다. 이진 트리 구조는 데이터를 저장하면서 자동으로 정렬의 형태를 취하게 된다. 트리 구조 데이터를 저장하고 필요한 작업을 예제로 해보자

package treeset;
import java.util.Iterator;
import java.util.TreeSet;

public class TreeSet1 {
    public static void main(String[] args){
        int A[] = {4,6,1,9,8,10,5,2,3,7};
        TreeSet<Integer> ts = new TreeSet<Integer>();

        for(int i = 0 ; i<A.length; i++){
            ts.add(A[i]);
        
        }
        System.out.println(ts);
        Iterator<Integer> itr = ts.iterator();
        while(itr.hasNext()){
            System.out.print(itr.next() + " ");
        }
    }
 
}
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
1 2 3 4 5 6 7 8 9 10 %    

위의 예제에서 보면 1부터 10까지의 정수가 저장된 배열 A에 반복문을 돌려 TreeSet에 저장한다. TreeSet에 저장될 때는 A에 저장된 순서도 들어가는데 TreeSet이 정렬된 형태로 보관을 하고 있어서 이터레이터를 돌렸을 때 정렬된 결과가 나오는 것을 알수 있다. 이렇게 TreeSet은 데이터가 어떻게 입력이 되더라도 정렬된 형태로 데이터를 저장하게 된다.

이진 탐색 트리(binary search tree)와 중위 탐색(inorder traversal)알고리즘을 알면 자바의 TreeSet 구조를 좀더 쉽게 이해할수 있다.

이진탐색 트리는 특별하게 구성되는 트리구조이다. 이진 트리는 자신보다 하위에 두 데이터를 두는 구조를 말하며, 이때 왼쪽에 있는 데이터를 '왼쪽 자식 데이터' , 오른쪽 자식 데이터로 나뉜다

이진 탐색 트리에 이진트리의 특별한 형태로 다음의 조건을 만족해야한다

    1. 저장되는 데이터는 모두 다른 값들이어야 한다.
    1. 왼쪽 자식 데이터는 부모보다 작은 값이어야 한다.
    1. 오른쪽 자식 데이터는 부모보다 큰값이어야 한다.

TreeSet에 이터레이터를 이용하여 순차적으로 루프를 돌리면 정렬된 순서로 출력이 되는데. 정렬된 결과가 나오는 것은 "중위순위탐색"과정을 거치기 떄문이다. 중위순위 탐색이라는것은 '왼쪽 자식데이터 - 부모데이터 - 오른쪽 자식 데이터의 순서로 데이터에 접근하는것이다.

1 2 3 4 5 6 7 이라고 봣을때

4가 부모이고 왼쪽 3개 자식 오른쪽 3개자식이고

2가 그다음 부모로써 왼쪽에 1 오른쪽에 3

6이 부모로써 왼쪽 5 오른쪽 7 형태로 된것이다.

앞의 예제에서는 TreeSet에 Integer 객체를 저장하였는데. 이번에는 누군가 만든 클래스 객체를 TreeSet에 저장해 보자. Person 클래스는 아이디와 성적을 필드로 가지고 있다. Person 객체 여러 개를 만들어서 이 객체들을 TreeSet에 저장해 보자. Person 객체를 다섯 개 생성하고 아이디는 1부터 5까지의 정수를 갖는다고 하자.

막간을 이용해서 디폴트 생성자는 없다면 자바가 자동으로 생성해준다 하지만 하나라도 생성자가 있다면 디폴트생성자를 만들어주지 않았을때 컴파일 에러가 발생하며 디폴트생성자를 하나 의도적으로 추가해줘야한다.

[예제]

package treeset;
import java.util.TreeSet;


class Person{
    private int id;
    private int score;
    Person(){}
    Person(int id, int score){
        this.id = id;
        this.score = score;
    
    }
    public String toString(){
        return "[id=" + id +", score="+ score +"]";
    }
}

public class TreeSet2 {


    public static void main(String[] args){
        TreeSet<Person> ts = new TreeSet<>();
        ts.add(new Person(3,83));
        ts.add(new Person(5,90));
        ts.add(new Person(1,93));
        ts.add(new Person(2,88));
        ts.add(new Person(4,70));

        System.out.println(ts);
    }

    
}
컴파일에러

컴파일에러가 발생한다 왜 발생할까? 좀더 위의 예제에서는 Integer를 저장할 때에는 문제가 없었다, 근데 다른 사람이 만든 Person 객체를 저장할때는 문제가 발생하는데, TreeSetdp 넣는 데이터는 대소 비교가 가능해야한다. Integer객체들은 숫자들 간에 비교가 가능하다, 즉 위에건 안된다는 말이다 그래서 대소 비교가 가능할 수 있도록 만들어 줘야하는데 이를 위해서 Comparable 인터페이스나 Comparator 인터페이스를 활용한다.

!TreeSet에는 대소비교 가능한 객체만 저장가능
사용자가 만든 Person 객체에 대소 개념을 넣으려면

  • Comparable 인터페이스
  • Comparator 인터페이스
    둘중 하나를 사용해야함

Comparable 인터페이스

Comparable 인터페이스를 구현한 클래스들은 같은 타입의 인스턴스끼리 비교할 수 있다. wrapper 클래스(boolean 제외), String, Date, File 클래스 등은 Comparable 인터페이스가 구현되어 제공되기 때문에 이러한 객체들은 Tree에 넣을 때 알아서 오름차순으로 정렬된다. 하지만 Person 클래스와 같이 Comparable을 구현하지 않은 클래스의 인스턴스를 TreeSet에 담으면, 실행시 예외가 발생한다. Comparable 인터페이스에는 compareTo 메소드가 있는데, 이메소드를 오버라이딩 하는것이 중요하다

interface Comparable{ //java.lang패키지에 존재
	int compareTo(T o)
}

위의 Person 클래스에 Comparable 인터페이스를 구현해 보자

package treeset;
import java.util.Iterator;
import java.util.TreeSet;

class Personf implements Comparable<Personf>{

    private int id;
    private int score;
    Personf(){}
    Personf(int id, int score){ this.id = id; this.score = score;}
    public String toString(){return "[id=" + id + ", score = "+ score +"]";}

    public int compareTo(Personf p){
		return this.id - p.id;
	}
    
}


public class Comparableif {

    public static void main(String[] args){
        TreeSet<Personf> ts = new TreeSet<>( );
		ts.add(new Personf(3, 83));
		ts.add(new Personf(5, 90));
		ts.add(new Personf(1, 93));
		ts.add(new Personf(2, 88));
		ts.add(new Personf(4, 70));
		Iterator<Personf> itr = ts.iterator( ); // iterator �̿��Ͽ� ����ϱ�
		while (itr.hasNext( ))
			System.out.println(itr.next( ));
	}
    
}
[id=1, score = 93]
[id=2, score = 88]
[id=3, score = 83]
[id=4, score = 70]
[id=5, score = 90]
compareTo 를 넣어줘서 자동으로 대소비교를 한후 오름자순 정렬

Comparator 인터페이스

Comparator 인터페이스는 다음고 같다.

interface Comparator{
	int compare(T o1, T o2);
boolean equals (Object obj;
}

Comparator를 통하여 순서 개념을 지정하려면 클래스를 따로더 만들어야 한다. 역시 위의 Personf 클래스에 Comparator인터페이스를 구현해 보자

package treeset;
import java.util.Comparator;
import java.util.TreeSet;
import java.util.Iterator;

class Persson extends Personf{

   

    
	
 

    public Persson(int id, int score){
       super(id,score);
    }

    int getId() {
     
        return id;
    }
    
  
}

class PersonComparator implements Comparator<Persson>{


@Override
public int compare(Persson o1, Persson o2) {
   
    return o1.getId() - o2.getId();
}
    
}



public class Comparatorif {

    public static void main(String[] args){
        TreeSet<Persson> ts = new TreeSet<>(new PersonComparator());

        ts.add(new Persson(3,83));
        ts.add(new Persson(5,90));
        ts.add(new Persson(1,93));
        ts.add(new Persson(2,88));
        ts.add(new Persson(4,70));

      Iterator<Persson> itr = ts.iterator();
      while(itr.hasNext()){
          System.out.println(itr.next());
      }


     
	
    }
   
}
[id=1, score = 93]
[id=2, score = 88]
[id=3, score = 83]
[id=4, score = 70]
[id=5, score = 90]

Comparator 인터페이스

int compare(T o1,T o2);
boolean equals(Object obj);

Coparator 를 통하여 순서 개념을 지정하려면 클래스를 따로 더 만들어야 한다. 역시 위어 person 클래스에 Comparator를 구현해보자

package treeset;
import java.util.Comparator;
import java.util.TreeSet;
import java.util.Iterator;

class Persson extends Personf{

   

    
	
 

    public Persson(int id, int score){
       super(id,score);
    }

    int getId() {
     
        return id;
    }
    
  
}

class PersonComparator implements Comparator<Persson>{


@Override
public int compare(Persson o1, Persson o2) {
   
    return o1.getId() - o2.getId();
}
    
}



public class Comparatorif {

    public static void main(String[] args){
        TreeSet<Persson> ts = new TreeSet<>(new PersonComparator());

        ts.add(new Persson(3,83));
        ts.add(new Persson(5,90));
        ts.add(new Persson(1,93));
        ts.add(new Persson(2,88));
        ts.add(new Persson(4,70));

     for(Persson p :ts){
         System.out.println(p);
     }
	
	}
   
}

HashSet

HashSet 구조 역시 Set의 일종이기 때문에 데이터의 순서가 없고 중복된 데이터를 같은 HashSet에 넣을 수 없다. HashSet에서 중요한 것은 객체가 동일한지를 판단하는 것, 우선 Integer 객체들을 HashSet에 저장해보자

[예제]

package hashsett;
import java.util.HashSet;

public class Test1 {

    public static void main(String[] args){

        Integer[] a ={2,3,1,4,4,1,1,2,2,2,3,1,4,4,1};
        HashSet<Integer> set = new HashSet<Integer>();

        for(int i = 0 ; i<a.length ; i++){
            set.add(a[i]); //HashSet set에 배열 a의 원소 저장
        }
        System.out.println(set); //HashSet에 저장된 요소 출력
    }
    
}
//오.. 중복걸러주고 오름차순 정렬 되엇다.

이 예제에서 보면 배열 A에 저장된 정수들이 정확히 한 번씩 HashSet에 저장되었다. HashSet은 중복된 데이터를 저장하지 않는다. 하지만 프로그래머가 만드는 서로 다른 두 객체가 그 안에 같은 데이터 값을 갖고 있는지 판단하려면 두 객체가 객체 내부에 동일한 데이터를 갖고 있는지 알 수 있도록 해야 한다. 다음 예제에서 이름과 나이를 필드로 갖는 Person 객체를 HashSet에 넣어보자

HashSet은 같은 내용의 데이터를 중복해서 저장하지 않도록한다. 그런데 사용자가 만든 객체들은 데이터 값들이 같더라도 중복 처리를 못하고 모두 HashSet에 저장한다
이를 해결하려면 반드시 다음의 메소드를 오버라이딩해야한다

  • equals 메소드
  • hashCode 메소드

위 예제에서 equals()메소드와 hashCode() 메소드는 반드시 모두 있어야하고, 만약에 하나라도 없다면 로버트가 중복이 된다 hashCode()를 어떻게 구현했는지 잘 보아야 한다. hashCode() 메소드에는 다음 그림과 같이 두 객체가 같은 실제로 같은 내용으로 구성되었다면 같은 두 객체의 참조값도 같은 값으로 만들어 주는 코드를 구현해야 한다.

package hashsett;
import java.util.HashSet;
class Person{
    String name;
    int age;
    Person(String name, int age){
        this.name = name;
        this.age = age;
    }

    public String toString(){
        return name +":" + age;
    }

    public boolean equals(Object obj){
        if(obj instanceof Person){
            Person temp = (Person)obj;
            return name.equals(temp.name) && age == temp.age;
        }
        return false;
    }
    public int hashCode(){
        return name.hashCode()+age;
    }

}


public class Test2 {
    public static void main(String[] args){
        HashSet<Object> set = new HashSet<Object>();
        set.add(new String("Alice"));
        set.add(new String("Alice"));
        set.add(new Person("Robert", 10));
        set.add(new Person("Robert",10));
        System.out.println(set);
    }
    
}
//String 객체 중복제거와 Person은 동일 데이터로 판단x로 두번 저장됨
//해시코드는 다양하게 중복검사를 할수 잇는 코드들을 넣어서 짜면 시간이 단축될수있다.
// 네임과 나이를 체크한 값을 넣은거같다

사용자가 만든 클래스의 객체를 HashSet에 저장하려면 다음ㅇ과 같이 equals, hashCode 메소드를 작성해야 합니다.

public boolean equals(Object obj){
 두 객체의 속성이 같은지 판단하는 코드를 넣음
}
public int hashCode(){
	객체의 속성으로 같은 int 값을 반환하는 코드를 넣습니다.
}

Map 인터페이스

Map 인터페이스는 <키,값>의 쌍으로 한개의 데이터를 구성하는 자료 구조이다. 키는 유일해야 하고, 값은 중복이 있을 수 있습니다. 예를 들어서, 다음과 같은 데이터를 Map으로 구성할 수 있다.

아이디암호
kimcoding12312#
swkimhelf1

[아이디: 암호]가 한쌍으로 데이터를 구성한다.
[번호 : 이름] 등...

대표적인 Map인터페이스로는 HashMap과 TreeMap 두가지가 있다.

##HashMap

HashMap 구조에서는 해쉬 함수를 이용한다. 해쉬 함수는 키 값을 입력하면 해쉬값을 출력해 주는 함수로 속도가 빠르다는 장점이 있다.
HashMap의 생성자와 메소드를 살펴 보자

[HashMap 생성자]

생성자 : 설명

  • HashMap() : 빈 HashMap 생성
  • void clear() : HashMap의 모든 데이터를 삭제함
  • boolean containsKey(Object key) : key에 해당하는 키가 존재하는지 판단함.
  • boolean containsValue(Object value):value에 해당하는 값이 존재하는지 판단함
  • Set<Map.Entry<K,V>> entrySet(): 모든 엔트리를 반환함
  • V get(Object key) : key에 해당하는 값을 반환함
  • V put(Key key, V value) :<key,value> 쌍을 HashMap에 추가함.
  • V remove(Object key) : key에 해당하는 데이터를 삭제함
  • int size() : HashMap의 크기를 반환함

위 메소드 몇개를 사용해 아이디와 암호를 저장해 두고 있는 예제를 만들어보자

package hashmapp;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Test1 {
    public static void main(String[] args){
        HashMap<String,String> map = new HashMap<>();
        map.put("david", "qwer123");
        map.put("cindy","9abcd9");
        map.put("alice","abc999");
        map.put("paul","asdf5757");
        map.put("mary","good");

        Set<String> keySet = map.keySet();//key만 뽑기
        System.out.println(keySet);
        System.out.println("-----------------");

        for(Map.Entry<String,String> e : map.entrySet()){//모든 엔트리 반환
            String key = e.getKey(); // 키
            String value = e.getValue();// 벨류 가져옴 
            System.out.println(key + " : " + value);
        }
            System.out.println("---------------------");
            String val = (String)map.get("alice");//map.get(key);
            System.out.println("Value for key alice is :"+ val);
        
       
    }
 }
[mary, cindy, alice, david, paul]
-----------------
mary : good
cindy : 9abcd9
alice : abc999
david : qwer123
paul : asdf5757
---------------------
Value for key alice is :abc999

##TreeMap

TreeMap 역시 <키,값>쌍으로 데이터가 구성되고 키를 기준으로 정렬이 이루어지기 때문에 키에 해당하는 클래스를 Comparable 또는 Comparator 인터페이스로 구현해야 한다.

[TreeMap 메소드]

메소드 : 설명

  • void clear() : TreeMap의 모든 원소삭제
  • boolean containsKey(Object key) : key에 해당하는 키가 존재하는지 판단함.
  • boolean containsValue(Object value): value에 해당하는 키가 존재 하는지 판단함.
  • set<Map.Entry<K,V>> entrySet(): 모든 엔트리 반환
  • V.get(Object key) :Key에 해당하는 값을 반환
  • Set keySet() : key들의 집합을 반환함.
  • V put(K key, V value) : 키벨류 쌍을 HashMap에 추가
  • V remove(Object key) : key에 해당하는 데이터를 삭제함
  • int size() : HashMap의 크기를 반환
  • Collection values() : value들의 집합을 반환
package hashmapp;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

class Person{
    private String name;
    private int score;

    Person(String name, int score){
        this.name = name;
        this.score = score;
    }
    public String toString(){
       return "(" + name +"," + score +")";
    }


}

public class Test2 {
    public static void main(String[] args){
        TreeMap<Integer, Person> map = new TreeMap<>();
        map.put(3,new Person("David",80));
        map.put(1,new Person("Bob",90));
        map.put(2,new Person("Alice",88));
        map.put(5,new Person("Cindy",77));
        map.put(4,new Person("Jenny",93));

        Set<Integer> KeySet = map.keySet();
        System.out.println(KeySet);
        System.out.println("-------------------");

        for(Map.Entry<Integer,Person> e:map.entrySet()){
            Integer key = e.getKey();
            Person value = e.getValue();
            System.out.println(key + ":" + value);
        }
        System.out.println("------------------");
        Person val = (Person)map.get(3);
        System.out.print("Key 3 ->");
        System.out.println(val);

    }

}
[1, 2, 3, 4, 5]
-------------------
1:(Bob,90)
2:(Alice,88)
3:(David,80)
4:(Jenny,93)
5:(Cindy,77)
------------------
Key 3 ->(David,80)

자바에서 제공하는 기본적인 자료 구조를 알아보았다. 자료 구조는 아주 중요하고, 자바의 경우 스택, 큐, Set,MAP등을 제공하고 있어 효율적으로 코딩할수 있다.

profile
《REACT》《JAVASCRIPT 》 만지고있어욤

0개의 댓글