이 연재글은 Java 알아보기의 5번째 글입니다.

Java에서 데이터의 목록을 저장하는 자료구조인 Collections에 대하여 알아봅니다. Collection에 속해있는 다양한 자료구조를 살펴보고 특징 및 사용법에 대하여 실습해 보겠습니다.

List Collection

  • 다수의 데이터(객체)를 순서대로 저장하는 자료구조.
  • 저장 시 순서가 유지된다.
  • 동일한 데이터도 중복으로 저장 가능하다.

ArrayList

객체 추가 시 메모리상에 순차적으로 저장되며 특정 위치의 데이터를 READ 할 때 주소 계산을 통해 한번에 조회가 가능하다.(이것을 Random Access가 가능한 데이터 구조라고 한다. 시간 복잡도 O(1) )

중간 위치에 데이터를 삽입하거나 삭제할 경우 이후의 인덱스를 전부 증가시키거나 감소시켜야 하므로 추가, 삭제가 빈번한 데이터 관리에는 적합하지 않다. 데이터 추가 삭제 시 자동으로 리스트의 크기가 변경되는데 데이터를 추가하기 위해 리스트 크기 변경이 필요한 경우 메모리상에 다른 데이터가 이미 공간을 차지하고 있으면 리스트 전체를 다른 위치의 메모리로 복사해야 하는 상황이 발생할 수 있다. 따라서 데이터 집합이 매우 크거나 추가 삭제가 빈번한 집합은 ArrayList보다는 LinkedList를 사용하는 것이 좋다.

List<String> list = new ArrayList<>();
// 맨뒤에 객체 추가
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
// 특정인덱스에 객체 추가
list.add(1, "b-0");
System.out.printf("%s \n", list.toString()); // [a, b-0, b, c, d, e] 
// 리스트 사이즈
System.out.printf("size : %d\n", list.size()); // 6
// 객체 존재여부
System.out.printf("contains c : %b\n", list.contains("c")); // true
// 특정 인덱스 객체 조회
System.out.printf("get index 0 : %s\n", list.get(0)); // a
// 비어있는지 확인
System.out.printf("isEmpty : %b\n", list.isEmpty()); // false
// 객체 삭제
list.remove("b"); // by 객체
list.remove(4); // by 인덱스
System.out.printf("%s\n", list.toString()); // [a, b-0, c, d]
// 리스트 초기화
list.clear();

List<String> alist = new ArrayList<>();
alist.add("b");
alist.add("c");
alist.add("d");
List<String> blist = new ArrayList<>();
blist.add("a");
blist.add("b");
blist.add("c");
// a, b 리스트의 교집합을 a 리스트에 주입.
alist.retainAll(blist); // [b, c]
System.out.printf("retainAll %s\n", alist.toString());
// list에서 array로의 변환
String[] array = alist.toArray(new String[alist.size()]);
System.out.printf("list to array : %s\n", Arrays.toString(array)); // [b, c]
// array에서 list로의 변환
List<String> convert = new ArrayList<>(Arrays.asList(array));
System.out.printf("array to list : %s\n", convert.toString()); // [b, c]

Vector

  • ArrayList와 내부 구조 및 기능이 모두 동일하다.
  • 차이점은 Vector의 경우 동기화 메서드로 구현되어 있다.
  • 멀티스레드 환경에서 안전하게 객체를 추가, 삭제할 수 있다.(thread safe)

LinkedList

  • ArrayList처럼 데이터 저장 시 순서가 유지된다.
  • 앞, 뒤, 중간 어느 위치에나 저장 가능하며 ArrayList처럼 추가 삭제에 따른 인덱스 업데이트나 크기 변경을 위한 추가 작업이 필요 없다.
  • 데이터 추가 시 저장되는 메모리 주소가 순차적이지 않아 Random Access가 불가능한 데이터 구조이다. 즉, 특정 위치의 데이터를 조회하려면 첫 번째 데이터부터 순차적으로 읽어가면서 찾아가야 한다.
  • 데이터의 크기가 매우 커질 가능성이 있거나, 추가 삭제가 빈번하게 일어나는 데이터 집합을 다룰 때 유용한 데이터 구조이다.
LinkedList<String> list = new LinkedList<>();
// 객체 추가
list.add("a");
list.add("b");
list.add("c");
list.addFirst("d");
list.addLast("e");
list.addLast("f");
list.addLast("g");
list.addLast("h");
list.addLast("i");
// 특정인덱스에 객체 추가
list.add(1, "b-0");
System.out.printf("print : %s\n", list.toString()); // [d, b-0, a, b, c, e, f, g, h, i]
// 객체 존재여부
System.out.printf("contains : %b\n", list.contains("c")); // true
// 객체 조회
System.out.printf("get : %s\n", list.get(0)); // d
System.out.printf("getFirst : %s\n", list.getFirst()); // d
System.out.printf("getList : %s\n", list.getLast()); // i
// 인덱스 확인
System.out.printf("indexOf : %d\n", list.indexOf("a")); // 2
System.out.printf("lastIndexOf : %d\n", list.lastIndexOf("c")); // 4
// 비어있는지 확인
System.out.printf("isEmpty : %b\n", list.isEmpty()); // false
// 사이즈 확인
System.out.printf("size : %d\n", list.size()); // 10
// 객체 삭제
list.remove("b"); // by 객체
list.remove(4); // by 인덱스
System.out.printf("print : %s\n", list.toString()); // [d, b-0, a, c, f, g, h, i]
// 객체를 하나 조회한다. 조회한 객체는 리스트에서 삭제된다.
System.out.printf("poll : %s\n", list.poll()); // d
System.out.printf("pollFirst : %s\n", list.pollFirst()); // b-0
System.out.printf("pollLast : %s\n", list.pollLast()); // i
System.out.printf("print : %s\n", list.toString()); // [a, c, f, g, h]
// 맨앞의 데이터를 뺀다.
System.out.printf("pop : %s\n", list.pop()); // a
// 맨앞에 데이터를 추가한다.
list.push("first");
System.out.printf("print : %s\n", list.toString()); // [first, c, f, g, h]
// 객체를 삭제한다.
list.remove("f");
// 최초에 발견된 데이터를 삭제한다.
list.removeFirstOccurrence("i");
// 최종으로 발견된 데이터를 삭제한다. 
list.removeLastOccurrence("g");
System.out.printf("print : %s\n", list.toString()); // [first, c, h]
// 리스트를 초기화한다.
list.clear();
// 빈 list의 head 데이터 조회
System.out.printf("peek : %s\n", list.peek()); // null
System.out.printf("peekFirst : %s\n", list.peekFirst()); // null
System.out.printf("peekLast : %s\n", list.peekLast()); // null
// list.get(0); // IndexOutOfBoundsException이 발생.
// list.element(); // NoSuchElementException이 발생.

Map Collection

  • key, value 구조를 가진 Data Dictionary이다.
  • key 값이 해싱되어 저장되므로 특정 키를 가진 데이터를 한 번에 조회할 수 있다. (시간 복잡도 O(1))
  • Hash로 이루어진 Map의 경우 저장된 데이터의 순서는 유지되지 않는다.
  • key는 중복될 수 없고 value는 중복이 가능하다.
  • key에 객체를 저장할 경우에는 해당 객체의 동일 여부 파악을 위해 equals(), hashcode() 함수를 override 해서 구현해야 한다.

HashMap

Map<String, String> map = new HashMap<>();
map.put("key1", "val1");
map.put("key2", "val2");
map.put("key3", "val3");
map.put("key4", "val4");
map.put("key5", "val5");
// key로 value 조회
System.out.printf("get : %s\n", map.get("key1")); // val1
// key 존재 여부
System.out.printf("containsKey : %b\n", map.containsKey("key2")); // true
// value 존재 여부
System.out.printf("containsValue : %b\n", map.containsValue("value2")); // false
// 비어있는지 여부
System.out.printf("isEmpty : %b\n", map.isEmpty()); // false
// key 리스트 조회
System.out.printf("key lists : %s\n", map.keySet()); // [key1, key2, key5, key3, key4]
// value 리스트 조회
System.out.printf("value lists : %s\n", map.values()); // [val1, val2, val5, val3, val4]
// Map 사이즈 조회
System.out.printf("size : %d\n", map.size()); // 5
map.remove("key1"); // key로 데이터 삭제
map.remove("key4", "val4"); // key, value 두 데이터를 연관지어 삭제.
// 키가 존재하지 않으면 디폴트 값 지정
System.out.printf("default value : %s\n", map.getOrDefault("Key3", "defaultValue")); // defaultValue
// key가 없을 경우 값 쌍이 Map에 추가된다.
map.putIfAbsent("key6", "val6");
// key가 존재하면 교체. key가 없으면 아무것도 안함.
map.replace("key7", "value7");
// 순회하면서 지정된 동작 수행
map.forEach((key, value) -> System.out.printf("key : %s, value : %s\n", key, value));
System.out.println("------------------------");
// 모든 키들의 값 변경
map.replaceAll((key, value) -> key + "_" + value);
map.forEach((key, value) -> System.out.printf("key : %s, value : %s\n", key, value));
System.out.println("------------------------");

Map<String, Integer> map2 = new HashMap<>();
// key가 없을 경우 값 쌍이 Map에 추가된다.
map2.putIfAbsent("key1", 10);
map2.forEach((key, value) -> System.out.printf("key : %s, value : %s\n", key, value));
// key가 있으면 value 값을 1 증가. value가 없으면 아무것도 안함.
map2.computeIfPresent("key1", (String key, Integer value) -> ++value);
System.out.printf("computeIfPresent : %d\n", map2.get("key1"));
Map<String, User> map3 = new HashMap<>();
// map3.put("cat",new CHashSet.User("cat",3,"ccc"));
// key가 없으면 value를 계산하는 함수를 호출한다.
User user = map3.computeIfAbsent("happydaddy", key -> new User("happydaddy",20,"programmer"));
System.out.println("computeIfAbsent " + user.name);
/*
key가 존재하지 않는지, key의 값이 null의 경우, 맵에 key와 value의 페어를 추가한다. key가 존재하는 경우, value를 새롭게 계산 된 값으로 옮겨 놓습니다.
계산 된 새로운 값이 null인 경우, 맵으로부터 키가 삭제됩니다.
*/
Map<String, Integer> mapMerge = new HashMap<>();
mapMerge.merge("happydaddy", 0, (k, v) -> mapMerge.get("happydaddy") + 1);
System.out.println("merge set 0 : " + mapMerge.toString());
mapMerge.merge("happydaddy", 0, (k, v) -> mapMerge.get("happydaddy") + 1);
System.out.println("merge set 0+1 : " + mapMerge.toString());

// User.java
public static class User {
    public String name;
    public int age;
    public String job;

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

HashTable

  • HashMap과 내부 구조 및 기능이 모두 동일하다.
  • HashTable의 경우 동기화 메서드로 구현되어 있다는 차이가 있고, 따라서 멀티스레드 환경에서 안전하게 객체를 추가, 삭제할 수 있다.(thread safe)
  • 멀티스레드 환경에서 사용 시엔 HashTable보다는 최근에 작성된 ConcurrentHashMap을 사용하는 것이 좋다.

TreeMap

  • TreeMap은 이진트리를 기반으로 한 Map 컬렉션이다.
  • HashMap과 다르게 기본적으로 key로 오름차순 정렬된다. TreeMap에 객체를 저장하면 자동으로 정렬되는데 기본적으로 부모 키값과 비교해서 키값이 낮은 것은 왼쪽 자식 노드에, 키값이 높은 것은 오른쪽 자식 노드에 Map.Entry객체를 저장한다.(이진트리의 특성)
  • 정렬 및 범위 검색 기능이 강화된 자료구조로 관련 메서드를 제공한다.
  • TreeSet 과의 차이점은 키와 값이 저장된 MapEntry를 저장한다는 점이다.
TreeMap<Integer, String> map = new TreeMap<>();
map.put(90, "김구");
map.put(50, "김원봉");
map.put(10, "안창호");
map.put(80, "노무현");
map.put(40, "김대중");
// 제일 윗순위의 Map 반환
Map.Entry<Integer, String> fEntry = map.firstEntry();  
System.out.printf("firstEntry : %s\n", fEntry.toString()); // 10=안창호
// 제일 하위순위의 Map 반환
Map.Entry<Integer, String> lEntry = map.lastEntry(); 
System.out.printf("lastEntry : %s\n", lEntry.toString()); // 90=김구
Map.Entry<Integer, String> lowerEntry = map.lowerEntry(30); // 지정된 키보다 바로 아래 순위의 Map 반환
System.out.printf("lowerEntry : %s\n", lowerEntry.toString()); // 10=안창호
Map.Entry<Integer, String> higherEntry = map.higherEntry(30); // 지정된 키보다 바로 아래 순위의 Map 반환
System.out.printf("higherEntry : %s\n", higherEntry.toString()); // 40=김대중
Map.Entry<Integer, String> ceilingEntry = map.ceilingEntry(30); // 동등 or 바로 아래 순위의 Map 반환
System.out.printf("ceilingEntry : %s\n", ceilingEntry.toString()); // 40=김대중

// Map.Entry<Integer, String> pollFirstEntry = map.pollFirstEntry(); 
// 제일 위 순위의 Map 반환 후 컬렉션에서 삭제
// System.out.printf("pollFirstEntry : %s\n", pollFirstEntry.toString());
// Map.Entry<Integer, String> pollLastEntry = map.pollLastEntry(); 
// 동등 or 바로 아래 순위의 Map 반환 후 컬렉션에서 삭제
// System.out.printf("pollLastEntry : %s\n", pollLastEntry.toString());

System.out.println("---------------------------");
System.out.println("[DESC]");
NavigableMap<Integer, String> descendingMap = map.descendingMap();
descendingMap = descendingMap.subMap(50, true, 30, true); // 범위조건, 없으면 전체 순위 반환
Set<Map.Entry<Integer, String>> descendingEntrySet = descendingMap.entrySet();

/**
50-김원봉 
40-김대중 
*/
for(Map.Entry<Integer, String> entry : descendingEntrySet){
    System.out.println(entry.getKey() + "-" + entry.getValue() + " ");
}
System.out.println();

Properties

key, value로 이루어진 데이터 구조로 key, value는 String으로 제한되어있으며 key=value구조로 단순 저장된 데이터를 읽는다.(springframework의 application.properties가 이 자료 구조를 사용하는 것이다.)

// applicationl.properties
env=local
service.name=java
Properties props = new Properties();
try {
    props.load(new FileInputStream("/Users/happydaddy/application.properties"));
    /**
     env : local
     service.name : java
     */
    System.out.printf("env : %s\n", props.getProperty("env"));
    System.out.printf("service.name : %s\n", props.getProperty("service.name"));
} catch (IOException e) {
    e.printStackTrace();
}

Set Collection

  • 중복되지 않는 유일한 데이터(객체)를 저장하는 데이터 구조이다.
  • Hash타입의 Set에 데이터 저장 시엔 순서가 유지되지 않는다.

HashSet

객체를 저장할 경우 hashcode값, equals메서드를 통해 동일한 객체이면 저장하지 않는다. 따라서 Primitive타입이 아닌 Reference 타입 객체 저장 시에는 hashcode(), equals()를 재정의 해줘야 한다.

// 객체를 저장할경우 hashcode값, equals메소드를 통해 동등한 객체이면 저장하지 않는다.
Set<String> sets = new HashSet<>();
sets.add("z");
sets.add("b");
sets.add("d");
sets.add("a");
sets.add("c");
Iterator<String> itr = sets.iterator();
while (itr.hasNext()) {
    System.out.println(itr.next());
}
System.out.println("------------------------");
for (String set : sets) {
    System.out.println(set);
}

// a, b set에서 공통으로 있는 값을 a에 담는다.
Set<String> setsb = new HashSet<>();
setsb.add("c");
setsb.add("d");
sets.retainAll(setsb);
System.out.println("------------------------");
for (String set : sets) {
    System.out.println(set);
}
System.out.println("------------------------");
// set to array 변환
String[] array = sets.toArray(new String[sets.size()]);
System.out.printf("set to array : %s\n", Arrays.toString(array));

System.out.println("------------------------");
// 두개의 객체간에 동일함은 equals로만 판별되지만, set에 입력될때 유일값의 여부는 추가로 hashcode까지 확인한다.
Set<User> setUser = new HashSet<>();
User user1 = new User("김우성", 1, "actor");
User user2 = new User("김우성", 1, "dancer");
setUser.add(user1);
setUser.add(user2);
System.out.printf("user1 equals user2 : %b \n",user1.equals(user2));
System.out.printf("setUser size : %d \n",setUser.size());

/* User Class */
public static class User {
    public String name;
    public int age;
    public String job;

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

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof User) {
            User user = (User) obj;
            return this.name.equals(user.name) && this.age == user.age;
    //                return this.job.equals(user.job);
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        int result = 1;
        result = 31 * result + age;
        result = 31 * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
}

LinkedHashSet

  • Set의 구현체로 저장되는 객체는 중복되지 않는다.
  • HashSet과 다른 점은 객체 저장 시 LinkedList를 이용하므로 객체가 입력 순서대로 저장된다.
// 객체를 저장할경우 hashcode값, equals메소드를 통해 동등한 객체이면 저장하지 않는다.
Set<String> sets = new LinkedHashSet<>();
sets.add("z");
sets.add("b");
sets.add("d");
sets.add("a");
sets.add("c");
Iterator<String> itr = sets.iterator();
// 데이터는 입력순으로 저장된다.
while (itr.hasNext()) {
    System.out.printf("%s ", itr.next()); // z b d a c 
}
System.out.println();
for (String set : sets) {
    System.out.printf("%s ",set); // z b d a c 
}

// a, b set에서 공통으로 있는 값을 a에 담는다.
Set<String> setsb = new LinkedHashSet<>();
setsb.add("c");
setsb.add("d");
sets.retainAll(setsb);
System.out.println();
for (String set : sets) {
    System.out.printf("%s ", set); // d c
}
System.out.println();
// set to array 변환
String[] array = sets.toArray(new String[sets.size()]);
System.out.printf("set to array : %s\n", Arrays.toString(array)); // [d, c]

TreeSet

  • Set의 구현체이므로 저장되는 객체는 중복되지 않는다.
  • HashSet과 다른 점은 객체 저장 시 Tree를 이용하므로 객체의 정렬 및 범위 검색에 최적화된 메서드를 제공한다.
// 객체를 저장할경우 hashcode값, equals 메소드로 비교하여 동일한 객체이면 저장하지 않는다.
TreeSet<Integer> treeSet = new TreeSet<>();
treeSet.add(80);
treeSet.add(50);
treeSet.add(95);
treeSet.add(96);
treeSet.add(30);
treeSet.add(70);
treeSet.add(45);
treeSet.add(99);
treeSet.add(58);
treeSet.add(84);
treeSet.add(10);
treeSet.add(25);
Iterator<Integer> itr = treeSet.iterator();
while (itr.hasNext()) {
    System.out.printf("%d ",itr.next());
}
System.out.println("\n------------------------");
System.out.println("[ASC]");
for (Integer val : treeSet) {
    System.out.printf("%d ", val);
}
System.out.println("\n[DESC]");
// 내림 차순으로 변경
NavigableSet<Integer> descSet = treeSet.descendingSet();
for (Integer val : descSet) {
    System.out.printf("%d ", val);
}
System.out.println();
// 부분집합 데이터 검색
System.out.println("[RANGE SEARCH]");
SortedSet<Integer> subSets = treeSet.subSet(50,80);
for (Integer subSet : subSets) {
    System.out.printf("%d ", subSet);
}
System.out.println();
// 데이터 검색
System.out.println("[CONDITION SEARCH]");
System.out.printf("find first %s\n", treeSet.first()); // 가장 낮은 점수 찾기
System.out.printf("find last %s\n", treeSet.last()); // 가장 높은 점수 찾기
System.out.printf("find lower %s\n", treeSet.lower(65)); // 주어진 객체 바로 전 레벨 데이터 검색 : 65 미만이면서 가까운값
System.out.printf("find higher %s\n", treeSet.higher(65)); // 주어진 객체 바로 뒤 레벨 데이터 검색 : 65 초과이면서 가까운값
System.out.printf("find floor %s\n", treeSet.floor(65)); // 주어진 객체 동등 레벨 데이터 검색. 65와 동등값 없으면 바로 아래값
System.out.printf("find ceiling %s\n", treeSet.ceiling(65)); // 주어진 객체 동등 레벨 데이터 검색. 65와 동응값 없으면 바로 윗값
System.out.printf("find pollFirst %s\n", treeSet.pollFirst()); // 제일 낮은 레벨 데이터 가져오고 컬렉션에서 삭제
System.out.printf("find pollLast %s\n", treeSet.pollLast()); // 제일 높은 레벨 데이터 가져오고 컬렉션에서 삭제

// 미만 점수 검색
System.out.println("[LOWER SCORE]");
NavigableSet<Integer> headset = treeSet.headSet(50, false);
for (Integer head : headset) {
    System.out.printf("%d ", head);
}
System.out.println("\n[HIGH SCORE]");
// 이상 점수 검색
NavigableSet<Integer> tailset = treeSet.tailSet(80, false);
for (Integer tail : tailset) {
    System.out.printf("%d ", tail);
}
System.out.println();
TreeSet<String> strSet =new TreeSet<>();
strSet.add("apple");
strSet.add("forever");
strSet.add("description");
strSet.add("ever");
strSet.add("zoo");
strSet.add("base");
strSet.add("guess");
strSet.add("cherry");
strSet.add("fzz");
System.out.println("[RANGE SEARCH - WORD]");
/* 시작 (포함여부 선택 가능) ~ 끝 (포함여부 선택 가능) 까지의 부분 집합 반환
  subSet(50,true,100,true) : 50이상 100이하의 범위로 검색
  subSet(50,false,100,false) : 50초과 100미만의 범위로 검색
*/
final NavigableSet<String> nSet=strSet.subSet("c",true,"f",true);
for(String str:nSet){
    System.out.printf("%s ",str);
}
System.out.println();
// a, b set에서 공통으로 있는 값을 a에 담는다.
Set<Integer> treeSetb = new TreeSet<>();
treeSetb.add(70);
treeSetb.add(99);
treeSet.retainAll(treeSetb);
System.out.println("------------------------");
for (Integer set : treeSet) {
    System.out.println(set);
}
System.out.println("------------------------");
// set to array 변환
Integer[] array = treeSet.toArray(new Integer[treeSet.size()]);
System.out.printf("set to array : %s\n", Arrays.toString(array));

TreeSet에 담긴 객체를 정렬

compareTo 를 재정의(Override)하여 정렬

public class Fruit implements Comparable<Fruit> {
    private String name;
    private int price;

    public Fruit(String name, int price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public int compareTo(Fruit fruit) {
        if (this.price < fruit.price) return 1;
        else if (this.price == fruit.price) return 0;
        else return -1;
    }

    public static void main(String[] args) {
        TreeSet<Fruit> treeSet = new TreeSet<>();
        treeSet.add(new Fruit("포도", 3000));
        treeSet.add(new Fruit("수박", 10000));
        treeSet.add(new Fruit("딸기", 6000));

        Iterator<Fruit> iterator = treeSet.iterator();
        /*
        수박:10000
        딸기:6000
        포도:3000
        */
        while (iterator.hasNext()) {
            Fruit fruit = iterator.next();
            System.out.println(fruit.name + ":" + fruit.price);
        }
    }
}

Comparator를 작성하여 TreeSet에 주입하여 정렬

public class Fruit {
    private String name;
    private int price;

    public Fruit(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public static class DescComparator implements Comparator<Fruit> {
        @Override
        public int compare(Fruit o1, Fruit o2) {
            if(o1.price < o2.price) return 1;
            else if(o1.price == o2.price) return 0;
            else return -1;
        }
    }
    /*
    수박:10000
    딸기:6000
    포도:3000
    */
    public static void main(String[] args) {
        TreeSet<Fruit> treeSet = new TreeSet<>(new DescComparator());
        treeSet.add(new Fruit("포도", 3000));
        treeSet.add(new Fruit("수박", 10000));
        treeSet.add(new Fruit("딸기", 6000));

        Iterator<Fruit> iterator = treeSet.iterator();
        while (iterator.hasNext()) {
            Fruit fruit = iterator.next();
            System.out.println(fruit.name + ":" + fruit.price);
        }
    }
}

Stack

입력된 데이터는 중복될 수 있으며 나중에 들어간 것이 먼저 나오는 후입 선출(LIFO : Last In First Out) 자료구조이다.

Stack<Integer> stack = new Stack();
// 스택이 비었는지 조회
System.out.printf("empty : %b\n", stack.isEmpty()); // true
// 스택에 데이터를 추가
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
stack.push(5);
// 데이터가 스택의 몇번째에 위치하는지 조회한다.
System.out.printf("search : %d\n", stack.search(2)); // 4
// 스택의 맨 위 데이터를 꺼낸다.(스택에서 삭제됨)
System.out.printf("pop : %d\n", stack.pop()); // 5
// 스택의 맨 위 데이터를 조회한다.(스택에서 삭제되지 않음)
System.out.printf("peek : %d\n", stack.peek()); // 4

Queue

입력된 데이터는 중복될 수 있으며 먼저 들어간 것이 먼저 나오는 선입선출(FIFO : First In First Out) 자료구조이다. 데이터를 앞뒤로 추가하고 삭제할 수 있는 LinkedList를 Queue 타입으로 선언하여 사용한다.

Queue<Integer> queue = new LinkedList<>();
// 큐가 비었는지 조회
System.out.printf("empty : %b\n", queue.isEmpty()); // true
// 큐에 데이터 추가
queue.add(1);
queue.add(2);
queue.add(3);
queue.add(4);
queue.offer(5);
// 큐의 맨 앞 데이터를 조회한다.(큐에서 삭제되지 않음)
System.out.printf("peek : %d\n", queue.peek()); // 1
// 큐의 맨 앞 데이터를 꺼낸다.(큐에서 삭제됨)
System.out.printf("poll : %d\n", queue.poll()); // 1
System.out.printf("remove : %d\n", queue.remove()); // 2

Deque

Queue와 기본적으로 동일하나 데이터를 Queue 앞뒤로 추가하거나 조회, 삭제할 수 있다.

Deque<Integer> deque = new ArrayDeque();
deque.add(1);
deque.add(2);
// queue의 맨앞에 데이터 삽입
deque.addFirst(0);
// queue의 맨뒤에 데이터 삽입
deque.addLast(4);
deque.addLast(5);
for (Integer num : deque) {
    System.out.printf("%d ", num); // 0,1,2,4,5
}
System.out.println();
// queue의 맨앞 데이터 조회(큐에서 삭제되지 않음)
System.out.printf("peekFirst %d\n", deque.peekFirst()); // 0
// queue의 맨뒤 데이터 조회(큐에서 삭제되지 않음)
System.out.printf("peekLast %d\n", deque.peekLast()); // 5
// queue의 맨앞에 데이터 삽입
deque.push(100);
// queue의 맨앞 데이터를 꺼낸다(큐에서 삭제)
System.out.printf("pop %d\n", deque.pop()); // 100
// queue의 맨앞 데이터를 꺼낸다(큐에서 삭제)
System.out.printf("poll %d\n", deque.poll()); // 0
// queue의 맨앞 데이터를 꺼낸다(큐에서 삭제)
System.out.printf("pollFirst %d\n", deque.pollFirst()); // 1
// queue의 맨뒤 데이터를 꺼낸다(큐에서 삭제)
System.out.printf("pollLast %d\n", deque.pollLast()); // 5
// 앞에서부터 조회하여 최초로 발견된 데이터를 삭제한다.
System.out.printf("removeFirstOccurrence %b\n", deque.removeFirstOccurrence(4)); // true
// 뒤에서부터 조회하여 최초로 발견된 데이터를 삭제한다.
System.out.printf("removeLastOccurrence %b\n", deque.removeLastOccurrence(2)); // true

연재글 이동[이전글] Java Collectors 알아보기