자바 Collection 이야기

106019 단어 JavaJava
  1. Collection
  • 다양한 데이터들의 묶음을 컬렉션이라 한다 (추상체, 인터페이스)

    • 일종의 자료구조라고 보면 된다

    • Collention -> List -> LinkedList, Array, Vector 등등

  • Collection에 데이터를 넣으면 순서가 존재하지 않는다

  • List, Set은 Collection의 상속관계, Map은 Set의 의존관계, 점선화살표는 구현이다

  • 기본적인 코드

    • package com.programmers.java.collection;
      import java.util.ArrayList;
      import java.util.List;
      
      public class Main {
          public static void main(String[] args) { // 하하하
              List<Integer> list = new ArrayList<>();
              list.add(1);
              list.add(2);
              list.add(3);
      
              for(int i=0; i<list.size(); i++){
                  System.out.println(list.get(i));
              }
          }
      }

  • method chaining을 위한 MyCollection 코드

    • package com.programmers.java.collection;
      import java.util.Arrays;
      import java.util.function.Consumer;
      
      public class MyCollection<T> {
          private List<T> list;
      
          public MyCollection(List<T> list){
              this.list = list;
          }
      
          public void foreach(Consumer<T> consumer){
              for(int i=0; i<list.size(); i++){
                  T data = list.get(i);
                  consumer.accept(data);
              }
          }
      }
    • package com.programmers.java.collection;
      
      import java.util.ArrayList;
      import java.util.List;
      import java.util.function.Consumer;
      import java.util.function.Function;
      
      public class MyCollection<T> {
          private List<T> list;
      
          public MyCollection(List<T> list){
              this.list = list;
          }
      
          public MyCollection<T> filter(Predicate<T> predicate){
              List<T> newList = new ArrayList<>();
      
              foreach(data -> {
                  if(predicate.test(data)) newList.add(data);
              });
      
              return new MyCollection<>(newList);
          }
      
          // '이 함수에서 <U>를 제네릭으로 사용해라'라는 뜻으로 `<U> MyCollection<U>`을 붙임
          // <U>는 이 메소드에서만 유효한 상태가 됨 -> 딴 곳에서 못 써!
          public <U> MyCollection<U> map(Function<T, U> function){
              //Function<T, U> : T를 input, U를 output으로 받는 함수
              List<U> uTypeList = new ArrayList<>();
      
              // function에서 나온 U타입 반환물을 list에 넣기 (list는 아까 List<U> list라고 선언해서 U타입을 받을 수 있다)
              foreach(data -> uTypeList.add(function.apply(data)));
      
              return new MyCollection<>(uTypeList);
          }
      
          public void foreach(Consumer<T> consumer){
              for(int i=0; i<list.size(); i++){
                  T data = list.get(i);
                  consumer.accept(data);
              }
          }
      }

  • method chaining을 위한 Main 코드

    • public class Main { // 람다 표현식으로 바뀌기 전
          public static void main(String[] args) {
      
              new MyCollection<Integer>(Arrays.asList(1,2,3,4,5,6,7))
                      .foreach(new Consumer<Integer>() {
                          @Override
                          public void accept(Integer integer) {
                              System.out.println(integer);
                          }
                      });
      
          }
      }
    • public class Main { // 람다 표현식으로 바뀐 후
          public static void main(String[] args) {
           /*
              new MyCollection<Integer>(Arrays.asList(1,2,3,4,5,6,7))
                      .foreach(integer -> System.out.println(integer));
               */
      
              new MyCollection<Integer>(Arrays.asList(1,2,3,4,5,6,7)) // Integer
                      .foreach(System.out::println);
      
              new MyCollection<String>(Arrays.asList("A","B","C","D","E","F","G")) // String
                      .foreach(System.out::println);
          }
      }
    • public class Main {
          public static void main(String[] args) {
      
              MyCollection <String> c1 = new MyCollection<>(Arrays.asList("Ar", "Brr", "Crrr", "Da"));
      
              //MyCollection <Integer> c2 = c1.map(s -> s.length());
      
              MyCollection <Integer> c2 = c1.map(String::length);
      
              c2.foreach(System.out::println);
      
              //위의 코드 3줄이랑 같은 의미임 == 미친듯한 생략이 람다 표현식의 가장 큰 장점이자 나에게 단점...
              // 이런 코드를 method chaining 라고 한다
              new MyCollection<>(Arrays.asList("Ar", "Brr", "Crrr", "Da", "Ee", "F12", "G"))
                      .map(String::length) // 문자열의 길이를 int로 변환하고
                      .filter(i -> i % 2 == 0) // 필터링을 통해 문자열 길이가 짝수인 것만
                      .foreach(System.out::println); // 출력하겠다
          }
      }

  • 19세 이상인 학생이름을 출력하기 (위의 코드에서 추가)

    • 이런 코드는 직관적이나 getter 이용시 맘대로 객체에 대한 정보를 조회할 수 있기 때문에 완벽한 코드는 아니다

    • package com.programmers.java.collection;
      import java.util.Arrays;
      
      public class User {
          private String name;
          private int age;
      
          public User(String name, int age) {
              this.name = name;
              this.age = age;
          }
      
          public String getName() { // 원치않는 사용자가 객체의 이름 정보에 대해 임의로 조회가능
              return name;
          }
      
          public int getAge() { // 원치않는 사용자가 객체의 나이 정보에 대해 임의로 조회가능
              return age;
          }
      }
      
      public class Main2 {
          public static void main(String[] args) {
              new MyCollection<User>(
                      Arrays.asList(
                              new User("학생A", 15),
                              new User("학생B", 16),
                              new User("학생C", 17),
                              new User("학생D", 18),
                              new User("학생E", 19),
                              new User("학생F", 20),
                              new User("학생G", 21),
                              new User("학생H", 22),
                              new User("학생I", 23)
                      )
              )
                      .filter(u -> u.getAge() >= 19)
                      .map(u -> u.getName())
                      .foreach(System.out::println);
          }
      }

  • 19세 이상인 학생이름을 출력하기(효율적인 개선)

    • package com.programmers.java.collection;
      import java.util.Arrays;
      
      public class User {
          private String name;
          private int age;
      
          public User(String name, int age) {
              this.name = name;
              this.age = age;
          }
      
          public boolean isAdult() { // 객체의 나이가 19세 이상인지 아닌지 boolean값으로 반환하는 함수
              return age >= 19;
          }
      
          @Override
          public String toString() { // toString()을 오버라이드하기
              return "name = " + name +
                      ", age =" + age;
          }
      }
      
      public class Main2 {
          public static void main(String[] args) {
              new MyCollection<User>(
                      Arrays.asList(
                              new User("학생A", 15),
                              new User("학생B", 16),
                              new User("학생C", 17),
                              new User("학생D", 18),
                              new User("학생E", 19),
                              new User("학생F", 20),
                              new User("학생G", 21),
                              new User("학생H", 22),
                              new User("학생I", 23)
                      )
              )
                      .filter(user -> user.isAdult())
                      .foreach(System.out::println); //toString()은 그냥 객체만 넣고 출력해도 나온다
          }
      }

  • TMI

    • 인터페이스명추상 메소드설명
      Consumervoid accept(T t)객체를 T를 받아 소비
      BiConsumer<T,U>void accept(T t, U u)객체 T, U를 받아 소비
      DoubleConsumervoid accept(double value)double 값을 받아 소비
      intConsumervoid accept(int value)int 값을 받아 소비
      LongConsumervoid accept(long value)long 값을 받아 소비
      ObjDoubleConsumervoid accept(T t, double value)객체 T와 double 값을 받아 소비
      ObjIntConsumervoid accept(T t, int value)객체 T와 int 값을 받아 소비
      ObjLongConsumervoid accept(T t, long value)객체 T와 long 값을 받아 소비
      Predicateboolean test(T. t)매개 변수 T를 받아서 boolean값을 반환



  1. Iterator
  • 다양한 데이터의 묶음을 풀어서 하니씩 처리하는 방법을 제공하는 인터페이스를 Iterator라고 합니다

  • Iterator.next()를 통해서 다음 데이터를 조회할 수 있다

    • 그러나 이전 데이터를 조회할 수 없다

  • 기본 코드

    • package com.programmers.java.iterator;
      
      import java.util.Arrays;
      import java.util.Iterator;
      import java.util.List;
      
      public class Main {
          public static void main(String[] args) {
              List<String> list = Arrays.asList("A", "B", "C", "D", "F");
              Iterator<String> iterator = list.iterator();
              // iterator.next()를 할 때마다 list에서 하나씩 띄어서 데이터를 처리하는 것임
      
              while (iterator.hasNext()){
                  System.out.println(iterator.next());
              }
          }
      }

  • 개선코드(MyCollection.javaMyIterator<T> iterator(), MyIterator<T> 인터페이스관련 코드를 삽입)

    • package com.programmers.java.iterator;
      //MyCollection.java에 이 코드들만 추가
      
      
      public interface MyIterator<T> { // T타입을 반환할 것이다는 의미
          boolean hasNext(); // 다음 원소가 있는지 체크해서 boolean으로 반환하는 추상 메소드
          T next(); // T타입을 반환하는 추상 메소드
      }
      
      public MyIterator<T> iterator(){ // Iterator<T>의 객체에 대한 생성자함수
              return new MyIterator<T>() {
                  private int index = 0;
      
                  @Override
                  public boolean hasNext() {
                      return index < list.size();
                  }
      
                  @Override
                  public T next() {
                      return list.get(index++); // 하고나서 index값을 증가시켜야 하므로 후위계산식 num++을 넣기
                  }
              };
          }
      
      
      public class Main {
          public static void main(String[] args) {
              MyIterator<String> iterator =
                      new MyCollection<String>(Arrays.asList("A123","B2","C4","D555","E6666"))
                              .iterator();
      
              while (iterator.hasNext()){
                  System.out.println(iterator.next());
              }
          }
      }



  1. Stream
  • 데이터 처리연산을 지원하도록 데이터에서 추출된 연속된 요소

  • Stream은 데이터의 흐름(연속)이다.

    • 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있다.

    • 또한 람다를 이용해서 코드의 양을 줄이고 간결하게 표현할 수 있습니다. 즉, 배열과 컬렉션을 함수형으로 처리할 수 있습니다.

  • /*모두 Stream의 한 종류이다*/
    System.out //0utputStream
    System.in //InputStream

  • Collection.stream()은 Java 8에서부터 사용가능

    • Java 8에서부터 지원하는 것은 데이터에 관련한 Stream이다

  • filter, map, forEach 같은 고차함수(함수를 인자로 받는 함수)가 제공된다

    • package com.programmers.java.stream;
      
      import java.util.Arrays;
      
      public class Main {
          public static void main(String[] args) {
      
              Arrays.asList("Ar", "Brr", "Crrr", "Da", "Ee", "F12", "G")
                      .stream()
                      .map(s -> s.length())
                      .filter(i -> i % 2 == 0)
                      .forEach(System.out::println);
          }
      }

  • 다른 코드

    • package com.programmers.java.stream;
      import java.util.Arrays;
      import java.util.stream.Collectors;
      import java.util.stream.IntStream;
      import java.util.stream.Stream;
      
      public class Main2 {
          public static void main(String[] args) {
              Stream<Integer> s1 = Arrays.asList(1, 2, 3, 4).stream();
      
              // IntStream s2 = Arrays.asList(new int[]{1,2,3,4}); 가 불가능하기 때문에
              // Integer가 아닌 int배열을 stream으로 변환하고 그걸 받아 줄 수 있는 특별한 클래스인 IntStream을 생성한다
              IntStream s2 = Arrays.stream(new int[]{1,2,3,4});
              // 즉 primitive타입(int, boolean, double 등)을 위한 클래스 IntStream라고 보면 된다
      
              Arrays.stream(new int[]{1,2,3,4}).map(i -> Integer.valueOf(i));
              Arrays.stream(new int[]{1,2,3,4}).boxed(); //boxed()라는 메소드로 wrapper 클래스로 변환가능
              Arrays.stream(new int[]{1,2,3,4}).boxed().collect(Collectors.toList()); // 컬렉터의 리스트로 변환하겠다
              Arrays.stream(new int[]{1,2,3,4}).boxed().toArray(Integer[]::new); // int형 Array로 변환하겠다
              Arrays.stream(new int[]{1,2,3,4}).boxed().toArray(); // Object를 담는 Array로 변환하겠다
              // 이를 위해 Integer를 이용
      
          }
      }

  • Stream을 만드는 방법1 : generate()

    • public class Main2 {
          public static void main(String[] args) {
             
              Stream.generate(() -> 1) // 1 값을 무한 생성
                      .forEach(System.out::println);
              
              Random random = new Random();
              Stream.generate(random::nextInt) // random 값을 10개만 생성
                      .limit(10)
                      .forEach(System.out::println);
          }
      }

  • Stream을 만드는 방법2 : iterate()

    •     public class Main2 {
              public static void main(String[] args) {
                         
                  Stream.iterate(0, i -> i + 1) // 0부터 시작해서 i+=1을 하며 10개 출력
                          .limit(10)
                          .forEach(System.out::println);
              }
          }

  • 주사위를 1000번 던져서 3이 나올 확률

    • package com.programmers.java.stream;
      import java.lang.reflect.Array;
      import java.util.Arrays;
      import java.util.Comparator;
      import java.util.Random;
      import java.util.stream.Stream;
      
      public class Main3 {
          public static void main(String[] args) {
      
              //주사위를 1000번 던져서 3이 나올 확률
              Random random = new Random();
              var cnt = Stream.generate(()->random.nextInt(6)+1)
                      .limit(1000)
                      .filter(i -> i == 3)
                      .count(); // count()는 long값을 반환한다
              System.out.println((float)cnt/1000);
      
              
              //1~10까지 중복되지 않는 것만 3개 뽑아서 배열에 넣기
              Random r = new Random(); // 1 ~ 10까지 숫자 중에서
              var arr = Stream.generate(()->r.nextInt(10)+1)
                      .distinct() // 중복되지 않는 것만
                      .limit(3) // 숫자 3개
                      .mapToInt(i -> i)
                      .toArray(); // int[]을 반환함
              System.out.println(Arrays.toString(arr));
      
              
              // 1 ~ 200까지 값중에서 랜덤값 5개를 뽑아 큰 순서대로 표시하시오
              Random ran = new Random();
              int[] array = Stream.generate(()->ran.nextInt(200)+1)
                      .limit(5)
                      .sorted(Comparator.reverseOrder())
                      .mapToInt(i -> i)
                      .toArray();
      
              System.out.println(Arrays.toString(array));
          }
      }

  • 스트림을 사용하면 연속된 데이트에 대해 풍부한 고차함수를 이용해서 강력한 기능들을 간결하게 표현할 수 있다

  • 자주 사용하다보면 익숙해지고, 익숙해지면 굉장히 편리해진다




  1. Optional
  • 배경

    • Null Pointer Exception(NPE) : 가장 많이 발생하는 널포인터 참조 오류

    • 자바에서는 (거의) 모든 것이 레퍼런스값이다. 이말은 (거의) 모든 것이 null이 될 수 있다는 것을 의미한다

    • 그래서 개발자들은 항상 null인지 아닌지 확인해야 합니다

    • 이런 불편함을 줄이기 위해 개발자들끼리 null을 쓰지않기로 약속을 합니다. 이를 계약한다라고 말하고, 계약을 하고 프로그래밍한다

      • null을 없앨 수는 없으니깐 쓰지말자고 서로 약속하는 것

  • EMPTY 객체를 사용하는 방법

    • package com.programmers.java.collection;
      
      public class User {
          //null값으로 초기화하지 않기 위해서 없을 것 같은 존재를 임의의로 만든다
          public static final User EMPTY = new User("", 0);
      
          private String name;
          private int age;
      
          public User(String name, int age) {
              this.name = name;
              this.age = age;
          }
      
          public boolean isAdult() {
              if (this == EMPTY) return false;
              return age >= 19;
          }
      
          @Override
          public String toString() {
              return "name = " + name +
                      ", age =" + age;
          }
      }
      
      
      package com.programmers.java.optional;
      
      import com.programmers.java.collection.User;
      
      public class Main {
          public static void main(String[] args) {
              User user1 = User.EMPTY;
      
              User user2 = getUser();
              if(user2 == User.EMPTY){
      
              }
              System.out.println(user1);
          }
      
          private static User getUser() {
              return User.EMPTY;
          }
      }

  • Optional을 사용하는 방법

    • null 데이터 : Optional.empty();

    • 데이타 : Optional.of({DATA});

    • package com.programmers.java.optional;
      import com.programmers.java.collection.User;
      import java.util.Optional;
      
      public class Main2 {
          public static void main(String[] args) {
      
              // User 객체가 실제로 정보가 있을 수도, 없을 수도 있으니 긴가민가한 이들을 위해 만든 것이 Optional
              Optional<User> optionalUser = Optional.empty(); // 얘는 현재 null
              
              optionalUser = Optional.of(new User("학생", 18));
              
              optionalUser.isEmpty(); // null이면 true 반환
              optionalUser.isPresent(); // null이 아닌 데이터가 존재하면 true 반환
          }
      }

  • Optional에 담긴 데이터를 확인하는 방법

    • optionalUser.isEmpty();, optionalUser.isPresent();

    • public class Main2 {
          public static void main(String[] args) {
      
              // User 객체가 실제로 정보가 있을 수도, 없을 수도 있으니 긴가민가한 이들을 위해 만든 것이 Optional
              Optional<User> optionalUser = Optional.empty(); // 얘는 현재 null
              optionalUser = Optional.of(new User("학생", 18));
      
              if(optionalUser.isPresent()){
                  //do1
              }else{
                  ///do2
              }
      
              if(optionalUser.isEmpty()){
                  //do1
              }else {
                  //do2
              }
      
              optionalUser.ifPresent(user -> {
                  //do1
              });
      
              optionalUser.ifPresentOrElse(user ->{ //{}가 2개 들어감
                  //do1, 데이터가 있을 경우
              }, ()-> {
                  //do2, 데이터가 없을 경우
              });
      
          }
      }

  • Optional 사용시 장점

    • EMPTY 객체는 약속되지 않은 개발자 사이에서 코드를 짜다보면 NPE가 나올 수 있지만 Optional을 넣음으로써 서로가 그 코드에 대해 NPE가 안 발생하게 유의할 수 있다 (일종의 서로를 위한 방어코드)
    • null인지 아닌지 모르는 객체에 대해 간결하게 처리할 수 있다

좋은 웹페이지 즐겨찾기