(103)

Stream(1)

스트림 Streams 자바 8에서 추가한 스트림(Streams)은 람다를 활용할 수 있는 기술 중 하나입니다. 자바 8 이전에는 배열 또는 컬렉션 인스턴스를 다루는 방법은 for 또는 foreach문을 돌면서 요소 하나씩을 꺼내서 다루는 방법이었습니다. 간단한 경우라면 상관없지만 로직이 복잡해질수록 코드의 양이 많아져 여러 로직이 섞이게 되고, 메소드를 나눌 경우 루프를 여러 번 도는 경우가 발생합니다. 스트림은 '데이터의 흐름’입니다. 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있습니다. 또한 람다를 이용해서 코드의 양을 줄이고 간결하게 표현할 수 있습니다. 즉, 배열과 컬렉션을 함수형으로 처리할 수 있습니다. 또 하나의 장점은 간단하게 병렬처..

명품 자바 프로그래밍 - 6장 Open Challange 영문자 히스토그램

히스토그램 = 문장들중에서 해당 알파벳이 얼마나 나왔는지를 보여주는 지표 텍스트를 키보드로 입력받아 알파벳이 아닌 문자는 제외하고 영문자 히스토그램을 만들어보자. 대문자와 소문자는 모두 같은 것으로 간주하고, 세미콜론(;)만 있는 라인을 만나면 입력의 끝으로 해석한다. --출력-- 영문 텍스트를 입력하고 세미콜론을 입력하세요. It's now or never, come hold me tight Kiss me my darling, be mine tonight Tomorrow will be too late It's now or never, my love won't wait When I first saw you, with your smile so tender My heart was captured, my so..

Map

Map Key와 Value값의 쌍(K, V)으로 저장 (Key값은 중복저장이 불가능하다) → 특정 Key로 value를 얻어낸다 동일한 Key로 다른 데이터를 저장 시 기존 데이터를 덮는다 요소의 저장 순서를 유지하지 않음 HashMap Key와 Value를 묶어 하나의 Entry로 저장한다 많은 양의 데이터를 검색하는 데 속도가 빠르다 null이 가능하다 데이터의 크기가 예상 범위내에 있는 경우 ( 크기지정 ), 삽입 삭제가 빈번한 경우 사용 class ExamMain { public static void main(String[] args) { Map map = new HashMap(); // key-value 삽입 map.put("a", 11); map.put("b", 22); map.put("c",..

Set

Set 중복 저장 불가능 순서가 없다 (인덱스로 관리하지 않음) → 데이터 검색을 위해 iterator메서드로 iterator(반복자)를 생성하고 데이터를 가져와야 한다 HashSet 데이터 크기가 어느정도 예상되면서 삽입, 삭제가 빈번할 경우 사용하는 것이 좋다 객체를 넣으면 해당 객체의 hashcode 값을 사용하여 버킷을 찾고 해당 위치에 요소가 있는지 확인한다. 같은 객체라 판단되면 저장하지 않는다 서로 다른 객체여도 같은 hashcode값을 가질 수 있다. → 해당 버킷에 이미 값이 있다면 equals메서드를 통해 비교작업을 거친다. equals=false이면 LinkedList의 형태로 버킷에 값을 추가한다 class ExamMain { public static void main(String[..

List

대표적인 클래스 종류 ArrayList LinkedList Vector Stack ArrayList 가장 많이 사용되는 컬렉션 클래스 중 하나 가변배열이며 JDK 1.2부터 제공됨 배열 공간이 가득차면 새로운 배열을 만들어 기존의 내용을 복사 (복사하는 과정에서 지연이 발생하고, 데이터가 많아질수록 카피에 대한 지연시간 증가) 중간 데이터가 삭제되면 그 앞 인덱스에 있는 데이터들이 한 칸씩 당겨져온다 특정 데이터에 빠르게 접근할 수 있다 데이터의 양이 일관적이고 삽입 및 삭제가 거의 없는경우, 접근속도가 중요할 때 사용한다 (가변배열이지만 거의 변하게 하지 않을 때 사용하는 것이 좋다) class ExamMain { public static void main(String[] args) { // 생성은 가..

컬렉션 프레임워크 collection framework

collection framework 다수의 데이터를 쉽고 효과적으로 처리할 수 있는 표준화, 정형화된 방법을 제공하는 클래스의 집합 객체들을 효율적으로 추가, 삭제, 검색할 수 있도록 제공되는 라이브러리 데이터를 저장하는 자료구조와 데이터를 처리하는 알고리즘을 구조화하여 클래스로 구현 해 놓은것 인터페이스를 사용하여 구현된다 컬렉션 프레임워크를 구성하는 모든 클래스가 제네릭으로 표현되어 있다 ※ 배열의 문제점 : 크기가 정적이면서 불변이다 (객체 삭제 시 해당 인덱스가 공백) 주요 인터페이스 인터페이스 설명 클래스 List 순서를 유지하고 저장 중복 저장 가능 (1번 인덱스에 새 값을 넣으면 기존 값은 2번으로 밀려난다) ArrayList, LinkedList, Stack Queue, Vector Se..

제네릭(Generic)

제네릭 Generic 타입을 파라미터화 하여 실행시에 구체적으로 해당하는 타입으로 결정 → 특정(Specific)타입을 미리 지정해주는 것이 아닌 필요에 의해 지정할 수 있도록 하는 일반(Generic)타입 장점 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지 가능 따로 타입을 체크하고 변환할 필요가 없다 (캐스팅 최소화로 인한 관리수월) 타입체크를 강력하게 하여 잘못된 데이터를 일반적인 원시, 참조 타입보다 안정성 확보에 좋음 코드의 재사용성이 높아짐 사용방법 타입 설명 Type Element Key Value Number 반드시 한 글자일 필요도 없고 위의 표와 일치할 필요도 없다. Just 암묵적 규칙 선언 파라미터로 명시할 수 있는 것은 참조타입이다 사용자가 정의한 클래스도 타입으로 올 ..

지역변수 전역변수 Variable

지역변수 특정 구역 내에서 생성되어 그 구역에서만 사용 public class ExamMain { static String str = "전역변수"; public static void main(String[] args) { System.out.println(str); String local = "지역변수"; System.out.println(local); method1(); } public static void method1() { System.out.println(str); //System.out.println(local); //지역변수이기에 출력 불가. 에러발생 } } // Console 전역변수 지역변수 전역변수 전역변수 멤버(인스턴스) 변수 : 클래스 영역에 선언되어, 객체가 생성될 때 마다 만들어..

split() - JAVA 문자열 자르기

Tech/Java,Kotlin,Spring 2022. 12. 17. 15:53
728x90
728x90

Split

특정 문자를 기준으로 문자열을 나누어 배열에 저장한다

String phoneNum = "010-0101-0101";
String[] strArr = phoneNum.split("-");


for(int i=0; i<strArr.length; i++) {
    System.out.println(strArr[i]);			
}

//Console
//010
//0101
//0101
// split(String regex, int limit)
// limit 만큼의 배열크기 지정

String phoneNum = "010-0101-0101-0101-010";
String[] strArr = phoneNum.split("-", 3);


for(int i=0; i<strArr.length; i++) {
    System.out.println(strArr[i]);			
}

//Console
//010
//0101
//0101-0101-010
728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

Stream(1)

Tech/Java,Kotlin,Spring 2022. 12. 17. 13:39
728x90
728x90

스트림 Streams


자바 8에서 추가한 스트림(Streams)은 람다를 활용할 수 있는 기술 중 하나입니다. 자바 8 이전에는 배열 또는 컬렉션 인스턴스를 다루는 방법은 for 또는 foreach문을 돌면서 요소 하나씩을 꺼내서 다루는 방법이었습니다. 간단한 경우라면 상관없지만 로직이 복잡해질수록 코드의 양이 많아져 여러 로직이 섞이게 되고, 메소드를 나눌 경우 루프를 여러 번 도는 경우가 발생합니다.

 

스트림은 '데이터의 흐름’입니다. 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있습니다. 또한 람다를 이용해서 코드의 양을 줄이고 간결하게 표현할 수 있습니다. 즉, 배열과 컬렉션을 함수형으로 처리할 수 있습니다.

 

또 하나의 장점은 간단하게 병렬처리(multi-threading)가 가능하다는 점입니다. 하나의 작업을 둘 이상의 작업으로 잘게 나눠서 동시에 진행하는 것을 병렬 처리(parallel processing)라고 합니다. 즉 쓰레드를 이용해 많은 요소들을 빠르게 처리할 수 있습니다.

스트림에 대한 내용은 크게 세 가지로 나눌 수 있습니다.

 

  1. 생성하기 : 스트림 인스턴스 생성.
  2. 가공하기 : 필터링(filtering) 및 맵핑(mapping) 등 원하는 결과를 만들어가는 중간 작업(intermediate operations).
  3. 결과 만들기 : 최종적으로 결과를 만들어내는 작업(terminal operations).
전체 -> 맵핑 -> 필터링 1 -> 필터링 2 -> 결과 만들기 -> 결과물

 

 

 

생성하기

보통 배열과 컬렉션을 이용해서 스트림을 만들지만 이 외에도 다양한 방법으로 스트림을 만들 수 있습니다. 하나씩 살펴보겠습니다.

배열 스트림

스트림을 이용하기 위해서는 먼저 생성을 해야 합니다. 스트림은 배열 또는 컬렉션 인스턴스를 이용해서 생성할 수 있습니다. 배열은 다음과 같이 Arrays.stream 메소드를 사용합니다.

String[] arr = new String[]{"a", "b", "c"};
Stream<String> stream = Arrays.stream(arr);
Stream<String> streamOfArrayPart = 
  Arrays.stream(arr, 1, 3); // 1~2 요소 [b, c]

 

컬렉션 스트림

 

컬렉션 타입(Collection, List, Set)의 경우 인터페이스에 추가된 디폴트 메소드 stream 을 이용해서 스트림을 만들 수 있습니다.

public interface Collection<E> extends Iterable<E> {
  default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
  } 
  // ...
}


그러면 다음과 같이 생성할 수 있습니다.

List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
Stream<String> parallelStream = list.parallelStream(); // 병렬 처리 스트림

비어 있는 스트림

비어 있는 스트림(empty streams)도 생성할 수 있습니다. 언제 빈 스트림이 필요할까요? 빈 스트림은 요소가 없을 때 null대신 사용할 수 있습니다.

public Stream<String> streamOf(List<String> list) {
  return list == null || list.isEmpty() 
    ? Stream.empty() 
    : list.stream();
}

Stream.builder()

빌더(Builder)를 사용하면 스트림에 직접적으로 원하는 값을 넣을 수 있습니다. 마지막에 build 메소드로 스트림을 리턴합니다.

Stream<String> builderStream = 
  Stream.<String>builder()
    .add("Eric").add("Elena").add("Java")
    .build(); // [Eric, Elena, Java]

 

Stream.generate()

generate메소드를 이용하면 Supplier<T> 에 해당하는 람다로 값을 넣을 수 있습니다. Supplier<T>  는 인자는 없고 리턴값만 있는 함수형 인터페이스죠. 람다에서 리턴하는 값이 들어갑니다.

public static<T> Stream<T> generate(Supplier<T> s) { ... }

이 때 생성되는 스트림은 크기가 정해져있지 않고 무한(infinite)하기 때문에 특정 사이즈로 최대 크기를 제한해야 합니다.

Stream<String> generatedStream = 
  Stream.generate(() -> "gen").limit(5); // [el, el, el, el, el]

5개의 “gen” 이 들어간 스트림이 생성됩니다.

Stream.iterate()

iterate 메소드를 이용하면 초기값과 해당 값을 다루는 람다를 이용해서 스트림에 들어갈 요소를 만듭니다. 다음 예제에서는 30이 초기값이고 값이 2씩 증가하는 값들이 들어가게 됩니다. 즉 요소가 다음 요소의 인풋으로 들어갑니다. 이 방법도 스트림의 사이즈가 무한하기 때문에 특정 사이즈로 제한해야 합니다.

Stream<Integer> iteratedStream = 
  Stream.iterate(30, n -> n + 2).limit(5); // [30, 32, 34, 36, 38]

기본 타입형 스트림

물론 제네릭을 사용하면 리스트나 배열을 이용해서 기본 타입(int, long, double) 스트림을 생성할 수 있습니다. 하지만 제네릭을 사용하지 않고 직접적으로 해당 타입의 스트림을 다룰 수도 있습니다. range  rangeClosed 는 범위의 차이입니다. 두 번째 인자인 종료지점이 포함되느냐 안되느냐의 차이입니다.

IntStream intStream = IntStream.range(1, 5); // [1, 2, 3, 4]
LongStream longStream = LongStream.rangeClosed(1, 5); // [1, 2, 3, 4, 5]

제네릭을 사용하지 않기 때문에 불필요한 오토박싱(auto-boxing)이 일어나지 않습니다. 필요한 경우 boxed메소드를 이용해서 박싱(boxing)할 수 있습니다.

Stream<Integer> boxedIntStream = IntStream.range(1, 5).boxed();

Java 8 의 Random 클래스는 난수를 가지고 세 가지 타입의 스트림(IntStream, LongStream, DoubleStream)을 만들어낼 수 있습니다. 쉽게 난수 스트림을 생성해서 여러가지 후속 작업을 취할 수 있어 유용합니다.

DoubleStream doubles = new Random().doubles(3); // 난수 3개 생성

문자열 스트링

스트링을 이용해서 스트림을 생성할수도 있습니다. 다음은 스트링의 각 문자(char)를 IntStream 으로 변환한 예제입니다. char 는 문자이지만 본질적으로는 숫자이기 때문에 가능합니다.

IntStream charsStream = 
  "Stream".chars(); // [83, 116, 114, 101, 97, 109]

다음은 정규표현식(RegEx)을 이용해서 문자열을 자르고, 각 요소들로 스트림을 만든 예제입니다.

Stream<String> stringStream = 
  Pattern.compile(", ").splitAsStream("Eric, Elena, Java");
  // [Eric, Elena, Java]

파일 스트림

자바 NIO 의 Files 클래스의 lines 메소드는 해당 파일의 각 라인을 스트링 타입의 스트림으로 만들어줍니다.

Stream<String> lineStream = 
  Files.lines(Paths.get("file.txt"), 
              Charset.forName("UTF-8"));

병렬 스트림 Parallel Stream

스트림 생성 시 사용하는 stream 대신 parallelStream 메소드를 사용해서 병렬 스트림을 쉽게 생성할 수 있습니다. 내부적으로는 쓰레드를 처리하기 위해 자바 7부터 도입된 Fork/Join framework 를 사용합니다.

// 병렬 스트림 생성
Stream<Product> parallelStream = productList.parallelStream();

// 병렬 여부 확인
boolean isParallel = parallelStream.isParallel()

따라서 다음 코드는 각 작업을 쓰레드를 이용해 병렬 처리됩니다.

boolean isMany = parallelStream
  .map(product -> product.getAmount() * 10)
  .anyMatch(amount -> amount > 200);

다음은 배열을 이용해서 병렬 스트림을 생성하는 경우입니다.

Arrays.stream(arr).parallel();

컬렉션과 배열이 아닌 경우는 다음과 같이 parallel 메소드를 이용해서 처리합니다.

IntStream intStream = IntStream.range(1, 150).parallel();
boolean isParallel = intStream.isParallel();

다시 시퀀셜(sequential) 모드로 돌리고 싶다면 다음처럼 sequential 메소드를 사용합니다. 뒤에서 한번 더 다루겠지만 반드시 병렬 스트림이 좋은 것은 아닙니다.

IntStream intStream = intStream.sequential();
boolean isParallel = intStream.isParallel();

스트림 연결하기

Stream.concat 메소드를 이용해 두 개의 스트림을 연결해서 새로운 스트림을 만들어낼 수 있습니다.

Stream<String> stream1 = Stream.of("Java", "Scala", "Groovy");
Stream<String> stream2 = Stream.of("Python", "Go", "Swift");
Stream<String> concat = Stream.concat(stream1, stream2);
// [Java, Scala, Groovy, Python, Go, Swift]

가공하기

전체 요소 중에서 다음과 같은 API 를 이용해서 내가 원하는 것만 뽑아낼 수 있습니다. 이러한 가공 단계를 중간 작업(intermediate operations)이라고 하는데, 이러한 작업은 스트림을 리턴하기 때문에 여러 작업을 이어 붙여서(chaining) 작성할 수 있습니다.

List<String> names = Arrays.asList("Eric", "Elena", "Java");

아래 나오는 예제 코드는 위와 같은 리스트를 대상으로 합니다.

Filtering

필터(filter)은 스트림 내 요소들을 하나씩 평가해서 걸러내는 작업입니다. 인자로 받는 Predicate 는 boolean 을 리턴하는 함수형 인터페이스로 평가식이 들어가게 됩니다.

Stream<T> filter(Predicate<? super T> predicate);

간단한 예제입니다.

Stream<String> stream = 
  names.stream()
  .filter(name -> name.contains("a"));
// [Elena, Java]

스트림의 각 요소에 대해서 평가식을 실행하게 되고 ‘a’ 가 들어간 이름만 들어간 스트림이 리턴됩니다.

Mapping

맵(map)은 스트림 내 요소들을 하나씩 특정 값으로 변환해줍니다. 이 때 값을 변환하기 위한 람다를 인자로 받습니다.

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

스트림에 들어가 있는 값이 input 이 되어서 특정 로직을 거친 후 output 이 되어 (리턴되는) 새로운 스트림에 담기게 됩니다. 이러한 작업을 맵핑(mapping)이라고 합니다.

간단한 예제입니다. 스트림 내 String 의 toUpperCase 메소드를 실행해서 대문자로 변환한 값들이 담긴 스트림을 리턴합니다.

Stream<String> stream = 
  names.stream()
  .map(String::toUpperCase);
// [ERIC, ELENA, JAVA]

다음처럼 요소 내 들어있는 Product 개체의 수량을 꺼내올 수도 있습니다. 각 ‘상품’을 ‘상품의 수량’으로 맵핑하는거죠.

Stream<Integer> stream = 
  productList.stream()
  .map(Product::getAmount);
// [23, 14, 13, 23, 13]

map 이외에도 조금 더 복잡한 flatMap 메소드도 있습니다.

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

인자로 mapper를 받고 있는데, 리턴 타입이 Stream 입니다. 즉, 새로운 스트림을 생성해서 리턴하는 람다를 넘겨야합니다. flatMap 은 중첩 구조를 한 단계 제거하고 단일 컬렉션으로 만들어주는 역할을 합니다. 이러한 작업을 플래트닝(flattening)이라고 합니다.

 

다음과 같은 중첩된 리스트가 있습니다.

List<List<String>> list = 
  Arrays.asList(Arrays.asList("a"), 
                Arrays.asList("b"));
// [[a], [b]]

이를 flatMap을 사용해서 중첩 구조를 제거한 후 작업할 수 있습니다.

List<String> flatList = 
  list.stream()
  .flatMap(Collection::stream)
  .collect(Collectors.toList());
// [a, b]

이번엔 객체에 적용해보겠습니다.

students.stream()
  .flatMapToInt(student -> 
                IntStream.of(student.getKor(), 
                             student.getEng(), 
                             student.getMath()))
  .average().ifPresent(avg -> 
                       System.out.println(Math.round(avg * 10)/10.0));

위 예제에서는 학생 객체를 가진 스트림에서 학생의 국영수 점수를 뽑아 새로운 스트림을 만들어 평균을 구하는 코드입니다. 이는 map 메소드 자체만으로는 한번에 할 수 없는 기능입니다.

Sorting

정렬의 방법은 다른 정렬과 마찬가지로 Comparator 를 이용합니다.

Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);

인자 없이 그냥 호출할 경우 오름차순으로 정렬합니다.

IntStream.of(14, 11, 20, 39, 23)
  .sorted()
  .boxed()
  .collect(Collectors.toList());
// [11, 14, 20, 23, 39]

인자를 넘기는 경우와 비교해보겠습니다. 스트링 리스트에서 알파벳 순으로 정렬한 코드와 Comparator 를 넘겨서 역순으로 정렬한 코드입니다.

List<String> lang = 
  Arrays.asList("Java", "Scala", "Groovy", "Python", "Go", "Swift");

lang.stream()
  .sorted()
  .collect(Collectors.toList());
// [Go, Groovy, Java, Python, Scala, Swift]

lang.stream()
  .sorted(Comparator.reverseOrder())
  .collect(Collectors.toList());
// [Swift, Scala, Python, Java, Groovy, Go]

Comparator 의 compare 메소드는 두 인자를 비교해서 값을 리턴합니다.

int compare(T o1, T o2)

기본적으로 Comparator 사용법과 동일합니다. 이를 이용해서 문자열 길이를 기준으로 정렬해보겠습니다.

lang.stream()
  .sorted(Comparator.comparingInt(String::length))
  .collect(Collectors.toList());
// [Go, Java, Scala, Swift, Groovy, Python]

lang.stream()
  .sorted((s1, s2) -> s2.length() - s1.length())
  .collect(Collectors.toList());
// [Groovy, Python, Scala, Swift, Java, Go]

Iterating

스트림 내 요소들 각각을 대상으로 특정 연산을 수행하는 메소드로는 peek 이 있습니다. ‘peek’ 은 그냥 확인해본다는 단어 뜻처럼 특정 결과를 반환하지 않는 함수형 인터페이스 Consumer 를 인자로 받습니다.

Stream<T> peek(Consumer<? super T> action);

따라서 스트림 내 요소들 각각에 특정 작업을 수행할 뿐 결과에 영향을 미치지 않습니다. 다음처럼 작업을 처리하는 중간에 결과를 확인해볼 때 사용할 수 있습니다.

int sum = IntStream.of(1, 3, 5, 7, 9)
  .peek(System.out::println)
  .sum();

결과 만들기

가공한 스트림을 가지고 내가 사용할 결과값으로 만들어내는 단계입니다. 따라서 스트림을 끝내는 최종 작업(terminal operations)입니다.

Calculating

스트림 API 는 다양한 종료 작업을 제공합니다. 최소, 최대, 합, 평균 등 기본형 타입으로 결과를 만들어낼 수 있습니다.

long count = IntStream.of(1, 3, 5, 7, 9).count();
long sum = LongStream.of(1, 3, 5, 7, 9).sum();

만약 스트림이 비어 있는 경우 count  sum 은 0을 출력하면 됩니다. 하지만 평균, 최소, 최대의 경우에는 표현할 수가 없기 때문에 Optional 을 이용해 리턴합니다.

OptionalInt min = IntStream.of(1, 3, 5, 7, 9).min();
OptionalInt max = IntStream.of(1, 3, 5, 7, 9).max();

스트림에서 바로 ifPresent 메소드를 이용해서 Optional 을 처리할 수 있습니다.

DoubleStream.of(1.1, 2.2, 3.3, 4.4, 5.5)
  .average()
  .ifPresent(System.out::println);

 

이 외에도 사용자가 원하는대로 결과를 만들어내기 위해 reduce  collect 메소드를 제공합니다. 이 두 가지 메소드를 좀 더 알아보겠습니다.

Reduction

스트림은 reduce라는 메소드를 이용해서 결과를 만들어냅니다. 람다 예제에서 살펴봤듯이 스트림에 있는 여러 요소의 총합을 낼 수도 있습니다.

다음은 reduce메소드는 총 세 가지의 파라미터를 받을 수 있습니다.

  • accumulator : 각 요소를 처리하는 계산 로직. 각 요소가 올 때마다 중간 결과를 생성하는 로직.
  • identity : 계산을 위한 초기값으로 스트림이 비어서 계산할 내용이 없더라도 이 값은 리턴.
  • combiner : 병렬(parallel) 스트림에서 나눠 계산한 결과를 하나로 합치는 동작하는 로직.
// 1개 (accumulator)
Optional<T> reduce(BinaryOperator<T> accumulator);

// 2개 (identity)
T reduce(T identity, BinaryOperator<T> accumulator);

// 3개 (combiner)
<U> U reduce(U identity,
  BiFunction<U, ? super T, U> accumulator,
  BinaryOperator<U> combiner);

먼저 인자가 하나만 있는 경우입니다. 여기서 BinaryOperator<T> 는 같은 타입의 인자 두 개를 받아 같은 타입의 결과를 반환하는 함수형 인터페이스입니다. 다음 예제에서는 두 값을 더하는 람다를 넘겨주고 있습니다. 따라서 결과는 6(1 + 2 + 3)이 됩니다.

OptionalInt reduced = 
  IntStream.range(1, 4) // [1, 2, 3]
  .reduce((a, b) -> {
    return Integer.sum(a, b);
  });

이번엔 두 개의 인자를 받는 경우입니다. 여기서 10은 초기값이고, 스트림 내 값을 더해서 결과는 16(10 + 1 + 2 + 3)이 됩니다. 여기서 람다는 메소드 참조(method reference)를 이용해서 넘길 수 있습니다.

int reducedTwoParams = 
  IntStream.range(1, 4) // [1, 2, 3]
  .reduce(10, Integer::sum); // method reference

마지막으로 세 개의 인자를 받는 경우입니다. Combiner 가 하는 역할을 설명만 봤을 때는 잘 이해가 안갈 수 있는데요, 코드를 한번 살펴봅시다. 그런데 다음 코드를 실행해보면 이상하게 마지막 인자인 combiner 는 실행되지 않습니다.

Integer reducedParams = Stream.of(1, 2, 3)
  .reduce(10, // identity
          Integer::sum, // accumulator
          (a, b) -> {
            System.out.println("combiner was called");
            return a + b;
          });

Combiner 는 병렬 처리 시 각자 다른 쓰레드에서 실행한 결과를 마지막에 합치는 단계입니다. 따라서 병렬 스트림에서만 동작합니다.

Integer reducedParallel = Arrays.asList(1, 2, 3)
  .parallelStream()
  .reduce(10,
          Integer::sum,
          (a, b) -> {
            System.out.println("combiner was called");
            return a + b;
          });

결과는 다음과 같이 36이 나옵니다. 먼저 accumulator 는 총 세 번 동작합니다. 초기값 10에 각 스트림 값을 더한 세 개의 값(10 + 1 = 11, 10 + 2 = 12, 10 + 3 = 13)을 계산합니다. Combiner 는 identity 와 accumulator 를 가지고 여러 쓰레드에서 나눠 계산한 결과를 합치는 역할입니다. 12 + 13 = 25, 25 + 11 = 36 이렇게 두 번 호출됩니다.

combiner was called
combiner was called
36

병렬 스트림이 무조건 시퀀셜보다 좋은 것은 아닙니다. 오히려 간단한 경우에는 이렇게 부가적인 처리가 필요하기 때문에 오히려 느릴 수도 있습니다.

Collecting

collect 메소드는 또 다른 종료 작업입니다. Collector 타입의 인자를 받아서 처리를 하는데요, 자주 사용하는 작업은 Collectors 객체에서 제공하고 있습니다.

이번 예제에서는 다음과 같은 간단한 리스트를 사용합니다. Product 객체는 수량(amout)과 이름(name)을 가지고 있습니다.

List<Product> productList = 
  Arrays.asList(new Product(23, "potatoes"),
                new Product(14, "orange"),
                new Product(13, "lemon"),
                new Product(23, "bread"),
                new Product(13, "sugar"));

Collectors.toList()

스트림에서 작업한 결과를 담은 리스트로 반환합니다. 다음 예제에서는 map 으로 각 요소의 이름을 가져온 후 Collectors.toList 를 이용해서 리스트로 결과를 가져옵니다.

List<String> collectorCollection =
  productList.stream()
    .map(Product::getName)
    .collect(Collectors.toList());
// [potatoes, orange, lemon, bread, sugar]

Collectors.joining()

스트림에서 작업한 결과를 하나의 스트링으로 이어 붙일 수 있습니다.

String listToString = 
 productList.stream()
  .map(Product::getName)
  .collect(Collectors.joining());
// potatoesorangelemonbreadsugar

Collectors.joining 은 세 개의 인자를 받을 수 있습니다. 이를 이용하면 간단하게 스트링을 조합할 수 있습니다.

  • delimiter : 각 요소 중간에 들어가 요소를 구분시켜주는 구분자
  • prefix : 결과 맨 앞에 붙는 문자
  • suffix : 결과 맨 뒤에 붙는 문자
String listToString = 
 productList.stream()
  .map(Product::getName)
  .collect(Collectors.joining(", ", "<", ">"));
// <potatoes, orange, lemon, bread, sugar>

Collectors.averageingInt()

숫자 값(Integer value )의 평균(arithmetic mean)을 냅니다.

Double averageAmount = 
 productList.stream()
  .collect(Collectors.averagingInt(Product::getAmount));
// 17.2

Collectors.summingInt()

숫자값의 합(sum)을 냅니다.

Integer summingAmount = 
 productList.stream()
  .collect(Collectors.summingInt(Product::getAmount));
// 86

IntStream 으로 바꿔주는 mapToInt 메소드를 사용해서 좀 더 간단하게 표현할 수 있습니다.

Integer summingAmount = 
  productList.stream()
  .mapToInt(Product::getAmount)
  .sum(); // 86

Collectors.summarizingInt()

만약 합계와 평균 모두 필요하다면 스트림을 두 번 생성해야 할까요? 이런 정보를 한번에 얻을 수 있는 방법으로는 summarizingInt 메소드가 있습니다.

IntSummaryStatistics statistics = 
 productList.stream()
  .collect(Collectors.summarizingInt(Product::getAmount));

이렇게 받아온 IntSummaryStatistics 객체에는 다음과 같은 정보가 담겨 있습니다.

IntSummaryStatistics {count=5, sum=86, min=13, average=17.200000, max=23}
  • 개수 getCount()
  • 합계 getSum()
  • 평균 getAverage()
  • 최소 getMin()
  • 최대 getMax()

이를 이용하면 collect 전에 이런 통계 작업을 위한 map 을 호출할 필요가 없게 됩니다. 위에서 살펴본 averaging, summing, summarizing 메소드는 각 기본 타입(int, long, double)별로 제공됩니다.

Collectors.groupingBy()

특정 조건으로 요소들을 그룹지을 수 있습니다. 수량을 기준으로 그룹핑해보겠습니다. 여기서 받는 인자는 함수형 인터페이스 Function 입니다.

Map<Integer, List<Product>> collectorMapOfLists =
 productList.stream()
  .collect(Collectors.groupingBy(Product::getAmount));

결과는 Map 타입으로 나오는데요, 같은 수량이면 리스트로 묶어서 보여줍니다.

{23=[Product{amount=23, name='potatoes'}, 
     Product{amount=23, name='bread'}], 
 13=[Product{amount=13, name='lemon'}, 
     Product{amount=13, name='sugar'}], 
 14=[Product{amount=14, name='orange'}]}

Collectors.partitioningBy()

위의 groupingBy 함수형 인터페이스 Function 을 이용해서 특정 값을 기준으로 스트림 내 요소들을 묶었다면, partitioningBy 은 함수형 인터페이스 Predicate 를 받습니다. Predicate 는 인자를 받아서 boolean 값을 리턴합니다.

Map<Boolean, List<Product>> mapPartitioned = 
  productList.stream()
  .collect(Collectors.partitioningBy(el -> el.getAmount() > 15));

따라서 평가를 하는 함수를 통해서 스트림 내 요소들을 true 와 false 두 가지로 나눌 수 있습니다.

{false=[Product{amount=14, name='orange'}, 
        Product{amount=13, name='lemon'}, 
        Product{amount=13, name='sugar'}], 
 true=[Product{amount=23, name='potatoes'}, 
       Product{amount=23, name='bread'}]}

Collectors.collectingAndThen()

특정 타입으로 결과를 collect 한 이후에 추가 작업이 필요한 경우에 사용할 수 있습니다. 이 메소드의 시그니쳐는 다음과 같습니다. finisher 가 추가된 모양인데, 이 피니셔는 collect 를 한 후에 실행할 작업을 의미합니다.

public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(
  Collector<T,A,R> downstream,
  Function<R,RR> finisher) { ... }

다음 예제는 Collectors.toSet 을 이용해서 결과를 Set 으로 collect 한 후 수정불가한 Set 으로 변환하는 작업을 추가로 실행하는 코드입니다.

Set<Product> unmodifiableSet = 
 productList.stream()
  .collect(Collectors.collectingAndThen(Collectors.toSet(),
                                        Collections::unmodifiableSet));

Collector.of()

여러가지 상황에서 사용할 수 있는 메소드들을 살펴봤습니다. 이 외에 필요한 로직이 있다면 직접 collector 를 만들 수도 있습니다. accumulator 와 combiner 는 reduce 에서 살펴본 내용과 동일합니다.

public static<T, R> Collector<T, R, R> of(
  Supplier<R> supplier, // new collector 생성
  BiConsumer<R, T> accumulator, // 두 값을 가지고 계산
  BinaryOperator<R> combiner, // 계산한 결과를 수집하는 함수.
  Characteristics... characteristics) { ... }

코드를 보시면 더 이해가 쉬우실 겁니다. 다음 코드에서는 collector 를 하나 생성합니다. 컬렉터를 생성하는 supplier 에 LinkedList 의 생성자를 넘겨줍니다. 그리고 accumulator 에는 리스트에 추가하는 add 메소드를 넘겨주고 있습니다. 따라서 이 컬렉터는 스트림의 각 요소에 대해서 LinkedList 를 만들고 요소를 추가하게 됩니다. 마지막으로 combiner 를 이용해 결과를 조합하는데, 생성된 리스트들을 하나의 리스트로 합치고 있습니다.

Collector<Product, ?, LinkedList<Product>> toLinkedList = 
  Collector.of(LinkedList::new, 
               LinkedList::add, 
               (first, second) -> {
                 first.addAll(second);
                 return first;
               });

따라서 다음과 같이 collect 메소드에 우리가 만든 커스텀 컬렉터를 넘겨줄 수 있고, 결과가 담긴 LinkedList 가 반환됩니다.

LinkedList<Product> linkedListOfPersons = 
  productList.stream()
  .collect(toLinkedList);

Matching

매칭은 조건식 람다 Predicate 를 받아서 해당 조건을 만족하는 요소가 있는지 체크한 결과를 리턴합니다. 다음과 같은 세 가지 메소드가 있습니다.

  • 하나라도 조건을 만족하는 요소가 있는지(anyMatch)
  • 모두 조건을 만족하는지(allMatch)
  • 모두 조건을 만족하지 않는지(noneMatch)
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);

간단한 예제입니다. 다음 매칭 결과는 모두 true 입니다.

List<String> names = Arrays.asList("Eric", "Elena", "Java");

boolean anyMatch = names.stream()
  .anyMatch(name -> name.contains("a"));
boolean allMatch = names.stream()
  .allMatch(name -> name.length() > 3);
boolean noneMatch = names.stream()
  .noneMatch(name -> name.endsWith("s"));

Iterating

foreach 는 요소를 돌면서 실행되는 최종 작업입니다. 보통 System.out.println 메소드를 넘겨서 결과를 출력할 때 사용하곤 합니다. 앞서 살펴본 peek 과는 중간 작업과 최종 작업의 차이가 있습니다.

 

names.stream().forEach(System.out::println);

출처

https://futurecreator.github.io/2018/08/26/java-8-streams/

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

명품 자바 프로그래밍 - 6장 Open Challange 영문자 히스토그램

Tech/Java,Kotlin,Spring 2022. 12. 11. 11:59
728x90
728x90
히스토그램 = 문장들중에서 해당 알파벳이 얼마나 나왔는지를 보여주는 지표

텍스트를 키보드로 입력받아 알파벳이 아닌 문자는 제외하고 영문자 히스토그램을 만들어보자. 
대문자와 소문자는 모두 같은 것으로 간주하고,
세미콜론(;)만 있는 라인을 만나면 입력의 끝으로 해석한다.

--출력--
영문 텍스트를 입력하고 세미콜론을 입력하세요.
It's now or never, come hold me tight
Kiss me my darling, be mine tonight
Tomorrow will be too late
It's now or never, my love won't wait
When I first saw you, with your smile so tender
My heart was captured, my soul surrendered
I spent a lifetime, waiting for the right time
Now that your near, the time is here, at last
It's now or never, come hold me tight
Kiss me my darling, be mine tonight
Tomorrow will be too late
It's now or never, my love won't wait
;

히스토그램을 그립니다.
A----------------
B----
C---
D--------
E-------------------------------------------
F---
G--------
H--------------
I-------------------------------
J
K--
L----------------
M--------------------
N-----------------------
O-----------------------------------
P--
Q
R---------------------------
S------------------
T---------------------------------------
U------
V------
W------------------
X
Y---------
Z

package challenge.Histogram;

import java.util.Scanner;

public class HistoGram {
		
	String inputEng() {
		
		StringBuffer stringBuffer=new StringBuffer();
		Scanner sc = new Scanner(System.in);
		
        // while문 안에 넣어서 종료조건=;로 지정한 후 만족할 때 까지 무한입력 가능하게 하기
		while(true) {
			String sentence = sc.nextLine();      			
			if(sentence.length()==1 && sentence.contains(";")) {
				System.out.println("종료");
				break;
				
			}stringBuffer.append(sentence);     // 버퍼에 문자열 저장
			
		}
		// System.out.println(stringBuffer.toString());  문자열 들어온지 확인해봤음 오 잘들어옴 
		return stringBuffer.toString();  // 리턴값을 가져나와 다른메서드에 활용
	
	}
	
	void checkEng(String engsentence){
		int a=65; int b=97; // 아스키코드 A,a 번호
		char start=65;
		String line = "-";
		boolean bl=true;
		
		while(bl) {
			System.out.print(start);
			for(int i=0; i<engsentence.length(); i++) {
				char c = engsentence.charAt(i);
				if(c==a || c==b) {
					System.out.print(line);
				}				
			}
			
			System.out.println();
			a++; b++; start++;
			if(start>90) {       // Z가 넘어가도 계속 출력되는거 막기
				break;
			}	
			
		}	
		
	}
	
}
package challenge.Histogram;

import java.util.Scanner;

public class HistogramMain {

	public static void main(String[] args) {
		
		System.out.println("영문 텍스트를 입력하세요");
		System.out.println("종료를 원할경우 세미콜론(;)을 입력해 주세요");
		
		HistoGram histoGram1 = new HistoGram();
		String input = histoGram1.inputEng();
		histoGram1.checkEng(input);
		
	}
}
아직 메서드 세분화에 엄청 미숙한 것 같다
728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

Map

Tech/Java,Kotlin,Spring 2022. 12. 11. 08:31
728x90
728x90

Map


Key와 Value값의 쌍(K, V)으로 저장 (Key값은 중복저장이 불가능하다)

  → 특정 Key로 value를 얻어낸다

동일한 Key로 다른 데이터를 저장 시 기존 데이터를 덮는다

요소의 저장 순서를 유지하지 않음

 

HashMap


Key와 Value를 묶어 하나의 Entry로 저장한다

많은 양의 데이터를 검색하는 데 속도가 빠르다

null이 가능하다

데이터의 크기가 예상 범위내에 있는 경우 ( 크기지정 ), 삽입 삭제가 빈번한 경우 사용

class ExamMain {

	public static void main(String[] args) {

		Map<String, Integer> map = new HashMap<String, Integer>();		
		
        // key-value 삽입
		map.put("a", 11);
		map.put("b", 22);
		map.put("c", 33);
		map.put("d", 44);
		map.put("e", 55);		
		// key값이 중복되면 마지막에 저장한 값으로 대체한다
		map.put("d", 77);
		map.put("e", 777);
		System.out.println(map.size());
		System.out.println(map);
// Console
// 5
// {a=11, b=22, c=33, d=77, e=777}        
		
		// value값 가져오기
   		System.out.println(map.get("d"));
		System.out.println(map.values());
// Console
// 77
// [11, 22, 33, 77, 777]
		
        // key값 가져오기
		Set<String> list1= map.keySet();
		System.out.println(list1);
// Console
// [a, b, c, d, e]
		
        // Iterator을 이용한 출력
		Iterator<String> iter = list1.iterator();
		while(iter.hasNext()) {
			String key= iter.next();
			int val= map.get(key);
			System.out.print(key+":"+val+" ");
		}
// Console
// a:11 b:22 c:33 d:77 e:777
        
}

public class Apartment {

	private int houseNum;
	private String name;
		
	public Apartment(int houseNum, String name) {
		super();
		this.houseNum = houseNum;
		this.name = name;
	}

	public int getHouseNum() {
		return houseNum;
	}

	public void setHouseNum(int houseNum) {
		this.houseNum = houseNum;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
		
	@Override
	public int hashCode() {
		return Objects.hash(this.houseNum, this.name);		
	}
	
	public static void main(String[] args) {
		
		Map<Apartment, Integer> goottMap = new HashMap<Apartment, Integer>();
		
		goottMap.put(new Apartment(001, "신짱구"), 30);
		
		System.out.println(goottMap);
		
		Set<Apartment> keySet2 = goottMap.keySet();
		Iterator<Apartment> iter2 = keySet2.iterator();
		while(iter2.hasNext()) {
			Apartment key= iter2.next();
			System.out.println(key.getHouseNum());
			System.out.println(key.getName());
			System.out.println(goottMap.get(key));
		}
		
	}		
	
}

// Console
// {CollectionList.Apartment@2f4b05b=30}
// 1
// 신짱구
// 30

 

TreeMap


노드로 연결되어 있는 구조

부모, 자식 노드로 구성되어 있으며 최상위 노드(root)가 항상 존재한다

정렬에 유리함(Key값 기준 오름차순, 내림차순)

null값 X

노드(node)
자료 구조의 일부분 하나 하나를 의미
노드는 데이터를 포함하며 다른 노드와 연결될 수 있다
대형 네트워크에서는 장치나 데이터 지점을 의미

※ 자식노드가 없는 경우 잎 노드(leaf node), 말단 노드(terminal)노드라고도 한다
class ExamMain {

	public static void main(String[] args) {
		
        // TreeMap 생성
		TreeMap<Integer,String> map1 = new TreeMap<Integer,String>();
        // new에서 타입 파라미터 생략가능
		TreeMap<Integer,String> map2 = new TreeMap<>();
		// map1의 모든 값을 가진 TreeMap생성
		TreeMap<Integer,String> map3 = new TreeMap<>(map1);
        // 초기값 설정
		TreeMap<Integer,String> map4 = new TreeMap<Integer,String>(){{
		    put(1,"a");
        }};
        
		
		// key-value값 추가
		map1.put(1, "리그오브레전드");
		map1.put(2, "발로란트");
		map1.put(3, "스타크래프트");

		// 출력
		System.out.println(map1);
		System.out.println(map1.get(1));
		System.out.println(map1.firstEntry());
		System.out.println(map1.firstKey());
		System.out.println(map1.lastEntry());
		System.out.println(map1.lastKey());
// Console
// {1=리그오브레전드, 2=발로란트, 3=스타크래프트}
// 리그오브레전드
// 1=리그오브레전드
// 1
// 3=스타크래프트
// 3
		
		// entrySet을 사용한 전체 값 출력
		for (Entry<Integer, String> entry : map1.entrySet()) {
		    System.out.println("Key:" + entry.getKey() + " Value:" + entry.getValue());
		}
// Console
// Key:1 Value:리그오브레전드
// Key:2 Value:발로란트
// Key:3 Value:스타크래프트
		
		// keySet활용
		for(Integer i : map1.keySet()){ //저장된 key값 확인
		    System.out.println("Key:" + i + " Value:" + map1.get(i));
		}
// Console
// Key:1 Value:리그오브레전드
// Key:2 Value:발로란트
// Key:3 Value:스타크래프트
		
		// 값 제거
		map1.remove(1);
		map1.clear();
        
	}
    
}

 

HashTable


HashMap의 구버전

병렬 처리를 하면서 자원의 동기화를 고려해야 하는 상황에 사용(멀티스레드)

 

Properties


Key, Value를 String타입으로 제한한 Map 컬렉션

변경이 잦은 문자열을 저장해서 유지보수를 편리하게 만들어 준다

만약 프로퍼티 파일이 없다면 소스코드를 변경 시 마다 수정해서 릴리즈(요청)을 해야함 ㅡ> 불편

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

Set

Tech/Java,Kotlin,Spring 2022. 12. 11. 07:51
728x90
728x90

Set


중복 저장 불가능

순서가 없다 (인덱스로 관리하지 않음)

  → 데이터 검색을 위해 iterator메서드로 iterator(반복자)를 생성하고 데이터를 가져와야 한다

 

HashSet


데이터 크기가 어느정도 예상되면서 삽입, 삭제가 빈번할 경우 사용하는 것이 좋다

객체를 넣으면 해당 객체의 hashcode 값을 사용하여 버킷을 찾고 해당 위치에 요소가 있는지 확인한다. 같은 객체라 판단되면 저장하지 않는다

서로 다른 객체여도 같은 hashcode값을 가질 수 있다. → 해당 버킷에 이미 값이 있다면 equals메서드를 통해 비교작업을 거친다. equals=false이면 LinkedList의 형태로 버킷에 값을 추가한다
class ExamMain {
	
	public static void main(String[] args) {
		
       Set<String> fruit = new HashSet<String>();
       
       // 데이터 삽입
       fruit.add("사과");
       fruit.add("오렌지");
       fruit.add("수박");
       fruit.add("포도");
       
       // 데이터 삭제
       fruit.remove("수박");
       
       // 데이터 출력
       Iterator<String> iter1 = fruit.iterator();
       while(iter1.hasNext()) {
    	   System.out.print(iter1.next()+" ");
       }
       
       System.out.println();       
       System.out.println(fruit.toString());
       
       // 데이터 포함 유무
       System.out.println(fruit.contains("수박"));
       System.out.println(fruit.contains("포도"));
       
       // 전체 데이터 삭제
       fruit.clear();
       System.out.println(fruit.toString());
       
       // 데이터의 존재 유무
       System.out.println(fruit.isEmpty());
       
       // 해당 Set의 크기
       System.out.println(fruit.size());
    		           
	}
    
}

// Console
포도 오렌지 사과 
[포도, 오렌지, 사과]
false
true
[]
true
0

public class Member {

	private String name;
	private int age;

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

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}	
	
	public static void main(String[] args) {		
    
		Set<Member> set = new HashSet<Member>();        
        //클래스의 인스턴스주소값이 다르기 때문에 Key값이 같아도 각각 저장 가능하다
		set.add(new Member("홍길동", 30));
		set.add(new Member("홍길동", 100));
		
		Iterator<Member> iterator = set.iterator();		
		while(iterator.hasNext()) {
			Member elem = iterator.next();
			System.out.println(elem.getName() + elem.getAge());
		}
		
	}
    
}

// Console
홍길동30
홍길동100

 

TreeSet


트리구조로 저장하는 Set
오름차순으로 데이터를 저장한다

class ExamMain {	

	public static void main(String[] args) {
		Set<Integer> set = new TreeSet<Integer>();

		set.add(4); // 데이터 추가
		set.add(2);
		set.add(1);
		set.add(3);
		set.add(6);
		set.add(5);

		Iterator<Integer> iter2 = set.iterator();
		while(iter2.hasNext()) {
			System.out.print(iter2.next()+" ");
		}

		System.out.println();

		Set<String> set2 = new TreeSet<String>();
		set2.add("d");
		set2.add("c");
		set2.add("a");
		set2.add("b");

		Iterator<String> iter3 = set2.iterator();
		while (iter3.hasNext()) {
			System.out.print(iter3.next()+" ");
		}
		
	}
	
}

// Console
1 2 3 4 5 6 
a b c d

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

List

Tech/Java,Kotlin,Spring 2022. 12. 10. 17:15
728x90
728x90

대표적인 클래스 종류


  • ArrayList<E>
  • LinkedList<E>
  • Vector<E>
  • Stack<E>

 

ArrayList


가장 많이 사용되는 컬렉션 클래스 중 하나

가변배열이며 JDK 1.2부터 제공됨

 

배열 공간이 가득차면 새로운 배열을 만들어 기존의 내용을 복사

(복사하는 과정에서 지연이 발생하고, 데이터가 많아질수록 카피에 대한 지연시간 증가)

중간 데이터가 삭제되면 그 앞 인덱스에 있는 데이터들이 한 칸씩 당겨져온다

특정 데이터에 빠르게 접근할 수 있다

데이터의 양이 일관적이고 삽입 및 삭제가 거의 없는경우, 접근속도가 중요할 때 사용한다

(가변배열이지만 거의 변하게 하지 않을 때 사용하는 것이 좋다)

class ExamMain {
	
	public static void main(String[] args) {
		
        // 생성은 가능하나 제네릭타입이기 때문에 경고 발생
   		// ArrayList list1 = new ArrayList();
        
		ArrayList<Integer> list1 = new ArrayList<Integer>();
		list1.add(3);
		list1.add(33);
		list1.add(333);
		list1.add(77);
		list1.add(777);	
		
        // for문과 get메서드를 이용한 출력
		for(int i=0; i<list1.size(); i++) {
			System.out.print(list1.get(i)+" ");
		}
		System.out.println();
        // size메서드를 이용한 배열의 크기
		System.out.println(list1.size()); 
        
        // remove메서드를 이용한 요소의 삭제
        list1.remove(3);
        
        // Collections.sort메서드를 이용한 정렬
		Collections.sort(list1);
                
        // set메서드를 이용한 요소의 변경
   		list1.set(2, 33333);
   		
        // iterator메서드와 get메서드를 이용한 출력
        Iterator<Integer> iter = list1.iterator();
		while (iter.hasNext()) {
		    System.out.print(iter.next() + " ");
		}
        
	}
    
}

// Console
3 33 333 77 777 
5
3 33 33333 777

 

LinkedList


ArrayList 클래스가 배열을 이용하여 요소를 저장함으로써 발생하는 단점을 극복하기 위해 고안됨

노드에 의해 데이터들이 연결되어있는 리스트

 

데이터의 삭제시 연결만 해지된다
데이터 삽입, 삭제가 빠르다
노드를 차례대로 검색하기 때문에 접근속도가 ArrayList에 비해 느리다
데이터의 삽입, 삭제가 자주 일어날 때 / 맨 앞, 맨 뒤 데이터의 삭제가 빈번할 때 사용

ArrayList클래스와 사용할 수 있는 메서드가 거의 같다
728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

컬렉션 프레임워크 collection framework

Tech/Java,Kotlin,Spring 2022. 12. 10. 16:07
728x90
728x90

collection framework 


다수의 데이터를 쉽고 효과적으로 처리할 수 있는 표준화, 정형화된 방법을 제공하는 클래스의 집합

  • 객체들을 효율적으로 추가, 삭제, 검색할 수 있도록 제공되는 라이브러리
  • 데이터를 저장하는 자료구조와 데이터를 처리하는 알고리즘을 구조화하여 클래스로 구현 해 놓은것
  • 인터페이스를 사용하여 구현된다
  • 컬렉션 프레임워크를 구성하는 모든 클래스가 제네릭으로 표현되어 있다
※ 배열의 문제점 : 크기가 정적이면서 불변이다 (객체 삭제 시 해당 인덱스가 공백)

 

주요 인터페이스


인터페이스 설명 클래스
List<E> 순서를 유지하고 저장
중복 저장 가능
(1번 인덱스에 새 값을 넣으면 기존 값은 2번으로 밀려난다)
ArrayList, LinkedList, Stack
Queue, Vector
Set<E> 저장 순서가 유지되지 않는다
중복 저장 불가
HashSet, TreeSet
Map<K, V> Key와 Value값의 한 쌍으로 저장된다
순서가 유지되지 않음
Key는 중복저장이 불가
(동일한 Key로 다른 Value저장 시 기존 데이터에 덮어씌움)
HashMap, TreeMap
HashTable, Properties

 

컬렉션 클래스 collection class


컬렉션 프레임워크에 속하는 인터페이스를 구현한 클래스

List, Set, Map중 하나의 인터페이스를 구현

클래스의 이름에도 구현한 인터페이스의 이름이 포함되므로 바로 구분이 가능

class Collection1 {
	
	public static void main(String[] args) {
		
		ArrayList<Integer> arrInt = new ArrayList<Integer>();
		arrInt.add(1);
		arrInt.add(3);
		arrInt.add(5);
		arrInt.add(7);
		
		for(int i=0; i<arrInt.size(); i++) {
			System.out.print(arrInt.get(i));
		}
        
		System.out.println();
		System.out.println(arrInt.get(3));
        
	}
    
}

// Console
1357
7

 

기본 메서드


메서드 설명
boolean add(Object o)
boolean addAll(Collection c)
지정된 객체 또는 콜렉션 객체를 Collection에 추가
void clear() Collection의 모든 객체를 삭제
boolean contains(Object o)
boolean containsAll(Collection c)
지정된 객체 또는 Collection의 객체들이 Collection에 포함되어 있는지 확인
boolean equals(Object o) Collection이 동일한지 비교
int hashCode() Collection의 hash code를 반환
boolean   isEmpty() Collection이 비어있는지 확인
Iterator iterator() Collection의 Iterator를 얻어 반환
boolean remove(Object o) 지정된 객체를 삭제한다.
boolean removeAll(Collection c) Collection에 포함된 객체들을 삭제
boolean retainAll(Collection c) Collection에 포함된 객체만을 남기고 다른 객체들은 삭제
기존 Collection에 변화가 생기면 true 아니면 false를 반환
int size() Collection에 저장된 객체의 수를 반환
Object[] toArray() Collection에 저장된 객체를 객체배열(Object[])로 반환
Object[] toArray(Object[] a) 지정된 배열에 Collection의 객체를 저장해 반환.

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

제네릭(Generic)

Tech/Java,Kotlin,Spring 2022. 12. 10. 14:11
728x90
728x90

제네릭 Generic


타입을 파라미터화 하여 실행시에 구체적으로 해당하는 타입으로 결정

  → 특정(Specific)타입을 미리 지정해주는 것이 아닌 필요에 의해 지정할 수 있도록 하는 일반(Generic)타입

 

장점


  • 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지 가능
  • 따로 타입을 체크하고 변환할 필요가 없다 (캐스팅 최소화로 인한 관리수월)
  • 타입체크를 강력하게 하여 잘못된 데이터를 일반적인 원시, 참조 타입보다 안정성 확보에 좋음
  • 코드의 재사용성이 높아짐

 

사용방법


타입 설명
<T> Type
<E> Element
<K> Key
<V> Value
<N> Number
  • 반드시 한 글자일 필요도 없고 위의 표와 일치할 필요도 없다.
  • Just 암묵적 규칙

 

선언


파라미터로 명시할 수 있는 것은 참조타입이다

  • 사용자가 정의한 클래스도 타입으로 올 수 있다는 뜻과 같다
  • int, double등 기본타입은 Integer, Double과 같은 Wrapper Type으로 사용한다
  • static에는 제네릭 타입 선언 불가능하지만 정적 메서드에는 선언 가능 (밑에서 설명)
public class ExamMain<T> {
}

public Interface ExamMain2<T>{	
}

// 멀티 제네릭
// 두 개 이상의 타입을 파라미터로 사용 가능
public class ExamMain<T, E> {
}

public Interface ExamMain2<K, V>{	
}

 

제네릭 클래스


class ExamMain<E> {
	
	private E element;	     
	
	void set(E element) {	   
		this.element = element;
	}
	
	E get() {	             
		return element;
	}
    
}

 

class genericExamMain {
	
	public static void main(String[] args) {
		
		ExamMain<String> a = new ExamMain<String>();
		ExamMain<Integer> b = new ExamMain<Integer>();
		
		a.set("안녕");
		b.set(10);
	
		System.out.println("a data : " + a.get());
		System.out.println("a E Type : " + a.get().getClass().getName());      // 타입 출력
		
		System.out.println("b data : " + b.get());
		System.out.println("b E Type : " + b.get().getClass().getName());      // 타입 출력
		
	}
}

// Console
a data : 안녕
a E Type : java.lang.String
b data : 10
b E Type : java.lang.Integer

// 멀티 제네릭

public class Tv<E, T> {
	
	private E e;
	private T t;	
    
	public Tv() {
		
	}
	
	public T getT() {
		return t;
	}

	public void setT(T t) {
		this.t = t;
	}

	public E getE() {
		return e;
	}
	
	public void setE(E e) {
		this.e=e;
	}
    
}
public class TvMain {

	public static void main(String[] args) {
		
		Tv<Integer, String> tv1 = new Tv<Integer, String>();
		Tv<String, Float> tv3= new Tv<String, Float>();
		tv1.setE(123456);
		tv1.setT("가나다라마바");
		tv3.setE("어쩔티비");
		tv3.setT(12.345f);
		int tv2=tv1.getE();
		String tv4=tv3.getE();
		System.out.println(tv2);
		System.out.println(tv4);		
	}
	
}

// Console
123456
어쩔티비

 

제네릭 메서드


클래스에서 지정한 제네릭 유형과 별개로 독립적으로 제네릭 유형을 선언하여 사용 가능

public <T> T genericMethod(T t) {
	return t;
		
}

// 접근제어자 <제네릭타입> 반환타입 메소드명(제네릭타입 파라미터)

// 위의 TV예제에 제네릭 메서드인 genericMethod메서드를 추가
public class Tv<E, T> {
	
	private E e;
	private T t;
	
	public Tv() {
		
	}
	
	public T getT() {
		return t;
	}

	public void setT(T t) {
		this.t = t;
	}

	public E getE() {
		return e;
	}
	
	public void setE(E e) {
		this.e=e;
	}
	
	<N> N genericMethod(N n) {	// 제네릭 메소드
		return n;
	}
    
}
public class TvMain {
	
	public <T> T genericMethod(T t) {
		return t;
		
	}

	public static void main(String[] args) {
		
		Tv<Integer, String> tv1 = new Tv<Integer, String>();
		Tv<String, Float> tv3= new Tv<String, Float>();
		tv1.setE(123456);
		tv1.setT("가나다라마바");
		tv3.setE("어쩔티비");
		tv3.setT(12.345f);
		int tv2=tv1.getE();
		String tv4=tv3.getE();
		System.out.println(tv2);
		System.out.println(tv4);
		
		System.out.println("<N> returnType : " + tv1.genericMethod(3).getClass().getName());		
		System.out.println("<N> returnType : " + tv1.genericMethod("ABCD").getClass().getName());		
		System.out.println("<N> returnType : " + tv1.genericMethod(tv3).getClass().getName());		
	}
	
}

// Console
123456
어쩔티비
<N> returnType : java.lang.Integer
<N> returnType : java.lang.String
<N> returnType : generic1201.Tv
메서드는 해당 클래스의 객체가 인스턴스화 됐을 때 <> 괄호 사이에 파라미터로 넘겨준 타입으로 지정이 된다
※ Static은 객체가 생성되기 이전, 즉 클래스가 생성될 때 같이 선언이 되기 때문에 제네릭 타입을 사용 불가능
    (제네릭 타입을 얻어올 방법이 없다)
정적메서드를 두고 싶은 경우 제네릭 클래스와 별도로 독립적인 제네릭이 사용되어야 한다

 

제한된 Generic과 와일드카드


<E>를 외부클래스에서 String파라미터로 보내면 E는 String이되고, Integer을 보내면 E는 Integer이 된다

Tv<E>클래스를 만들고 파라미터를 TV로 보내면 E는 TV 클래스가 된다. → 참조타입이 될 수 있다

 

extends, super, ? 을 통해특정 범위로 좁혀서 제한할 수 있다 (여기서 ?는 와일드카드. 즉 알 수 없는 타입이라는 의미)

  • extends T : 상한경계
  • ? super T : 하한경계
  • <?> : 와일드카드
<E extends T>	       // T와 T의 자손 타입만 가능 (E는 들어오는 타입으로 지정 됨)
<E super T>	         // T와 T의 부모(조상) 타입만 가능 (E는 들어오는 타입으로 지정 됨)
<? extends T>	       // T와 T의 자손 타입만 가능
<? super T>      	// T와 T의 부모(조상) 타입만 가능
<?>		        // 모든 타입 가능. <? extends Object>

 

위 그림과 같은 경우

<T extends B>	// T = B~C
<T extends E>	// T = E
<T extends A>	// T = A~E

<? extends B>	// ? = B~C
<? extends E>	// ? = E
<? extends A>	// ? = A~E
<T super B>	// T = A~B
<T super E>	// T = A,D,E
<T super A>	// T = A
 
<? super B>	// ? = A~B
<? super E>	// ? = A,D,E
<? super A>	// ? = A

<?>		        // 모든 타입 가능. <? extends Object>

public class Exam extends Object{
	System.out.println("<?>(wild Card)와 같은 의미");
}

// 어떤 클래스를 만들던 extends Object를 암시적으로 상속받는다





 

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

지역변수 전역변수 Variable

Tech/Java,Kotlin,Spring 2022. 12. 10. 14:05
728x90
728x90

지역변수

특정 구역 내에서 생성되어 그 구역에서만 사용

public class ExamMain {
	
	static String str = "전역변수";
	

    public static void main(String[] args) {
		System.out.println(str);		
		String local = "지역변수";				
		System.out.println(local);		
		method1();
	}
    
	public static void method1() {
		System.out.println(str);
        //System.out.println(local);
		//지역변수이기에 출력 불가. 에러발생
	}
	
}

// Console
전역변수
지역변수
전역변수

전역변수

멤버(인스턴스) 변수 : 클래스 영역에 선언되어, 객체가 생성될 때 마다 만들어지고 생성되었을 때만 호출하여 사용가능

정적(클래스) 변수 : 객체를 따로 생성하지 않아도 사용가능. 남발 시 프로그램 속도에 악영향을 끼침

public class ExamMain {
	
	static String str = "전역변수";
	String str2 = "멤버변수";

	
	public static void main(String[] args) {
		System.out.println(str);
		
		ExamMain examMain=new ExamMain();
		System.out.println(examMain.str2);        // 인스턴스 생성해야 호출 가능
	}

}
728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

방명록