for문을 사용할때 for(int i=0 ; i<parcels.size();i++){

이런 구문을 주로 사용했지만, 이제는

for(Parcel index : parcels){

콜론만을 활용해서 객체 : 리스트 형식으로 for문을 돌려서 리스트 안의 값을 Object안에 넣어주는 것을 목표로 한다.


자바 for each문을 사용할 떄 주의 할 점은 
오직 배열의 값을 가져다 사용할 수만 있고(read) 수정할 수는 없다는 것(write)입니다.

성능 차이는 딱히 없다.


for each(향상된 for문)에서는 배열의 항목 수만큼 실행 부분을 반복하는데 반복이 이루어질 때마다 배열의 항목을 순서대로 꺼내어 변수(number)에 자동으로 대입해 줍니다.


따로 반복 변수(int i)를 선언하거나 배열의 값을 가져오는 부분(array[i]) 없이 실행부분에서는 하고자 하는 작업에 집중할 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import java.util.*;
import java.util.stream.*;
import static java.util.stream.Collectors.*;
import static java.util.Comparator.*;
 
class Student3{
 
    public Student3(String name, boolean sex, int hak, int ban, int score) {
        this.name = name;
        this.sex = sex;
        this.hak = hak;
        this.ban = ban;
        this.score = score;
    }
 
    public String getName() {
        return name;
    }
 
    public boolean isSex() {
        return sex;
    }
 
    public int getHak() {
        return hak;
    }
 
    public int getBan() {
        return ban;
    }
 
    public int getScore() {
        return score;
    }
 
    String name;
    boolean sex;
    int hak;
    int ban;
    int score;
 
 
public String toString(){
    return String.format("[%s, %s, %d학년, %d반, %3d점]",name, sex ? "남":"여" ,hak,ban,score);
}
 
//groupingBy사용
enum Level {HIGH, MID, LOW} //성적을 상,중,하 세 단계로 분류
}
public class StreamEx7 {
    public static void main(String[] args) {
 
        Student3[] stuArr = {
                new Student3("권영미"false11350),
                new Student3("박진영"true11320),
                new Student3("김서현"false11300),
                new Student3("이지현"false12280),
                new Student3("이남의"false12200),
                new Student3("조예은"false12270),
                new Student3("김한솔"true13240),
                new Student3("우지원"true1370),
                new Student3("조현우"true13100),
                new Student3("김기태"true13330),
                new Student3("정하준"true13200),
 
 
                new Student3("권영미"false21350),
                new Student3("박진영"true21320),
                new Student3("김서현"false21300),
                new Student3("이지현"false22280),
                new Student3("이남의"false22200),
                new Student3("조예은"false22270),
                new Student3("김한솔"true23240),
                new Student3("우지원"true2370),
                new Student3("조현우"true23100),
                new Student3("김기태"true23330),
                new Student3("정하준"true23200)
        };
 
        System.out.println("1. 단순 분할(성별로 분할)");
 
        Map<Boolean,List<Student3>> stuBySex = Stream.of(stuArr).collect(partitioningBy(Student3::isSex));
 
        //리스트로 넣은 다음 출력해주기 위함
        List<Student3> maleStudent = stuBySex.get(true);
        List<Student3> femaleStudent = stuBySex.get(false);
 
        for(Student3 s : maleStudent) System.out.println(s);
        for(Student3 s : femaleStudent) System.out.println(s);
 
 
        System.out.println("%n2. 단순분할 + 통계(성별 학생수를 내놔라) %n ");
 
        Map<Boolean,Long> stuNumBySex = Stream.of(stuArr).collect(partitioningBy(Student3::isSex,counting()));
 
        System.out.println("남학생 수 :"+ stuNumBySex.get(true));
        System.out.println("여학생 수 :"+ stuNumBySex.get(false));
 
        System.out.println("%n3. 단순분할 + 통계(성별 1등) %n");
 
        Map<Boolean,Optional<Student3>> topScoreBySex = Stream.of(stuArr).collect(partitioningBy(Student3::isSex,maxBy(comparingInt(Student3::getScore))));
 
        //Optional이 같이 출력됨
        System.out.println("남학생 1등 : "+ topScoreBySex.get(true));
        System.out.println("여학생 1등 : "+ topScoreBySex.get(false));
 
        //Optional을 Map에 안쓰고 그냥 Student3바로 쓸때
        Map<Boolean,Student3> topScoreBySex2 = Stream.of(stuArr)
                        .collect(partitioningBy(Student3::isSex
                        ,collectingAndThen(maxBy(comparingInt(Student3::getScore)),Optional::get)));
 
        //?? 이게 제대로 출력되는데 왜 이러는지 모름
        System.out.println("남학생 1등 : " + topScoreBySex2.get(true));
        System.out.println("여학생 1등 : " + topScoreBySex2.get(false));
 
        System.out.println("%n4. 다중분할(성별 불합격자, 100점 이하)%n");
        Map<Boolean, Map<Boolean,List<Student3>>> failedStuBySex =
                Stream.of(stuArr).collect(partitioningBy(Student3::isSex,
                        partitioningBy(s -> s.getScore() <= 100))
                );
 
        List<Student3> failedMaleStu = failedStuBySex.get(true).get(true);
        List<Student3> failedFemaleStu = failedStuBySex.get(false).get(true);
 
        for(Student3 s : failedMaleStu) System.out.println(s);
        for(Student3 s : failedFemaleStu) System.out.println(s);
 
 
    }
}
 
cs


1. 단순 분할(성별로 분할)

[박진영, 남, 1학년, 1반, 320점]

[김한솔, 남, 1학년, 3반, 240점]

[우지원, 남, 1학년, 3반,  70점]

[조현우, 남, 1학년, 3반, 100점]

[김기태, 남, 1학년, 3반, 330점]

[정하준, 남, 1학년, 3반, 200점]

[박진영, 남, 2학년, 1반, 320점]

[김한솔, 남, 2학년, 3반, 240점]

[우지원, 남, 2학년, 3반,  70점]

[조현우, 남, 2학년, 3반, 100점]

[김기태, 남, 2학년, 3반, 330점]

[정하준, 남, 2학년, 3반, 200점]

[권영미, 여, 1학년, 1반, 350점]

[김서현, 여, 1학년, 1반, 300점]

[이지현, 여, 1학년, 2반, 280점]

[이남의, 여, 1학년, 2반, 200점]

[조예은, 여, 1학년, 2반, 270점]

[권영미, 여, 2학년, 1반, 350점]

[김서현, 여, 2학년, 1반, 300점]

[이지현, 여, 2학년, 2반, 280점]

[이남의, 여, 2학년, 2반, 200점]

[조예은, 여, 2학년, 2반, 270점]

%n2. 단순분할 + 통계(성별 학생수를 내놔라) %n 

남학생 수 :12

여학생 수 :10

%n3. 단순분할 + 통계(성별 1등) %n

남학생 1등 : Optional[[김기태, 남, 1학년, 3반, 330점]]

여학생 1등 : Optional[[권영미, 여, 1학년, 1반, 350점]]

남학생 1등 : [김기태, 남, 1학년, 3반, 330점]

여학생 1등 : [권영미, 여, 1학년, 1반, 350점]

%n4. 다중분할(성별 불합격자, 100점 이하)%n

[우지원, 남, 1학년, 3반,  70점]

[조현우, 남, 1학년, 3반, 100점]

[우지원, 남, 2학년, 3반,  70점]

[조현우, 남, 2학년, 3반, 100점]


*Optional 요약

null 레퍼런스의 문제점과 null을 멀리해야하는 이유


null 대신 optional : null로 부터 안전한 도메인 모델 재구현하기


Optional활용 : null확인 코드 제거하기


Optional에 저장된 값을 확인하는 방법


값이 없을 수도 있는 상황을 고려하는 프로그래밍



- 역사적으로 프로그래밍 언어에서는 null레퍼런스로 값이 없는 상황을 표현해왔다.

- 자바 9에서는 값이 있거나 없음을 표현할 수 있는 클래스 java.util.Optional<T>를 제공한다.

- 팩토리 메서드 Optional.empty.Optional.of. Optional.ofNullable 등을 이용해서 Optional객체를 만들 수 있다.

- Optional 클래스는 스트림과 비슷한 연산을 수행하는 map, flatMap, filter 등의 메서드를 제공한다.

- Optional로 값이 없는 상황을 적절하게 처리하도록 강제할 수 있다. 즉, Optional로 예상치 못한 null 예외를 방지할 수 있다.

- Optional을 활용하면 더 좋은 API를 설계할 수 있다. 즉, 사용자는 메서드의 시그니처(메서드 이름 + 파라미터)만 보고도 Optional값이 사용되거나 반환되는지 예측할 수 있다.


*Optional 클래스의 소개


java.util.Optional<T>라는 새로운 클래스를 제공한다. Optional은 선택형값을 캡슐화하는 클래스이다.

예를들어, 어떤 사람이 차를 소유하고 있지 않다면 Person클래스의 car 변수는 null을 가져야 할 것이다. 하지만 새로운 Optional을 이용할 수 있으므로 null을 할당하는 것이 아니라

변수형식을 Optional<Car>로 설정할 수 있다.


Optional<Car>가 Car형식의 객체를 포함 하던지

Optional<Car>가 빈 Optional이든지


값이 있으면 Optional클래스는 값을 감싼다. 없으면 Optional.empty 메서드로 Optional을 반환한다.

Optional.empty는 Optional의 특별한 싱글턴 인스턴스를 반환하는 정적 팩토리 메서드이다. null 레퍼런스와 Optional.empty()는 서로 무엇이 다른지 궁금하다.

null을 참조하려면 NullPointerException이 발생하지만, Optional.empty()는 Optional객체이므로 이를 다양한 방식으로 활용할 수 있다.


null대신 Optional을 사용하면서 Car형식이 Optional<Car>로 바뀌었다. 이는 값이 없을 수 있음을 명시적으로 보여준다. 반면 Car형식을 사용했을때는 Car에 null레퍼런스가 할당될 수 있는데 이것이 올바른 값인지 아니면 잘못된 값인지 판단할 아무 정보도 없다.



앞서 잠시 언급된 것과 같이 최종 연산의 결과 타입이 Optional인 경우가 있다.


최종연산에 대해 배우기 전에 Optional에 대해 배워보자


Optional<T>은 지네릭 클래스로 'T타입의 객체'를 감싸는 래퍼 클래스(객체아닌게 객체화 하는 래퍼클래스)이다. 

그래서 Optional타입의 객체에는 모든 타입의 참조 변수를 담을 수 있다.


참고) java.util.Optional은 JDK1.8부터 추가되었다.

public final class Optional<T>{

private final T value;    //T타입의 참조변수

}


최종 연산의 결과를 그냥 반환하는게 아니라 Optional객체에 담아서 반환하는 것이다.

이처럼 객체에 담아서 반환을 하면, 반환된 결과가 null인지 매번 if문으로 체크하는 대신 Optional에 정의된 메서드를 통해서 간단히 처리할 수 있다.

이제 널 체크를 위한 if문 없이도 NullPointException이 발생하지 않는 보다 간결하고 안전한 코드를 작성하는 것이 가능해진 것이다.


참고) Objects클래스에 isNull(), nonNull(), requireNonNull()과 같은 메서드가 있는 것도 널 체크를 위한 if문을 메서드안으로 넣어서 코드의 복잡도를 낮추기 위한 것이다.



*Optional객체 생성하기


Optional객체를 생성할 떄는 of() 또는 ofNullable()을 사용한다.


String str = "abc";

Optional<String> optVal = Optional.of(str);

Optional<String> optVal = Optional.of("abc");

Optional<String> optVal = Optional.of(new String("abc"));


만일 참조변수의 값이 null일 가능성이 있으면, of()대신 ofNullable()을 사용해야 한다.

of()는 매개변수의 값이 null이면 NullPointerException일 ㄱ발생하기 때문이다.


Optional<String> optVal = Optional.of(null); //NullPointerException이 발생

Optional<String> optVal = Optional.ofNullable(null); //OK


Optional<T>타입의 참조변수를 기본값으로 초기화 할 때는 empty()를 사용한다. null로 초기화 하는 것이 가능하지만, empty()로 초기화 하는 것이 바람직하다.


참고)empty()는 지네릭 메서드라서 앞에 <T>를 붙였다. 추정가능하므로 생략할 수 있다.


Optional<String> optVal = null; // null로 초기화

Optional<String> optVal = Optional.<String>empty(); //빈 객체로 초기화


*Optional객체의 값 가져오기


Optional객체에 저장된 값을 가져올 때는 get()을 사용한다. 값이 null일 때는 NoSuchElementException이 발생하며, 이를 대비해서 orElse()로 대체할 값을 지정할 수 있다.


Optional<String> optVal = Optional.of("abc");

String str1 = optVal.get();         //optVal에 저장된 값을 반환. null이면 예외발생

String str2 = optVal.orElse(""); //optVal에 저장된 값이 null일 때는, ""을 반환


orElse()의 변형으로는 null을 대체할 값을 반환하는 람다식을 지정할 수 있는 orElseGet()과 null일 때 지정된 예외를 발생시키는 orElseThrow()가 있다.


T orElseGet(Supplier<? extends T> other>

T orElseThrow(Supplier<? extends X> exceptionSupplier)


사용하는 방법은 아래와 같다.


String str3 = optVal2.orElseGet(String::new); //() -> new String()과 동일

String str4 = optVal2.orElseThrow(NullPointException::new); //null이면 예외 발생


Stream처럼 Optional객체에도 filter(), map(), flatMap()을 사용할 수 있다. map()의 연산결과가 Optional<Optional<T>>일 때, flatMap()을 사용하면 Optional<T>를 결과로 얻는다. 만일, Optional객체의 값이 null이면, 이 메서드들은 아무 일도 안한다.


int result = Optional.of("123")

.filter(x->x.length() > 0)

.map(Integer::parseInt).orElse(-1); //result=123

result = Optional.of("")

.filter(x->x.length() > 0)

.map(Integer::parseInt).orElse(-1); //result = -1


우리가 이미 알고 있는 것처럼 parseInt(문자열을 기본형으로)는 예외가 발생하기 쉬운 메서드이다. 만일 예외처리된 메서드를 만든다면 다음과 같을 것이다.


static int optStrToInt(Optional<String> optStr, int defaultValue){

try{

return optStr.map(Integer::parseInt).get();

}catch(Exception e){

return defaultValue;

}

}


isPresent()는 Optional객체의 값이 Null이면 false를, 아니면 true를 반환한다.

ifPresent(Consumer<T> block)은 값이 있으면 주어진 람다식을 실행하고, 없으면 아무일도 하지 않는다.


if(str!=null){

System.out.println(str);

}

이 코드를 ifPresent()를 이용해서 바꾸면 더 간단히 할 수 있다. 아래의 문장은 참조변수 str이 null이 아닐 때만 값을 출력하고, null이 아니면 아무 일도 일어나지 않는다.


Optional.ofNullable(str).ifPresent(System.out::println);


ifPresent()는 Optional<T>를 반환하는 findAny()나 findFirst()와 같은 최종 연산과 잘 어울린다.

Stream클래스에 정의된 메서드 중에서 Optional<T>를 반환하는 것들은 다음과 같다.


Optioanl<T> findAny()

Optional<T> findFirst()

Optional<T> max(Comparator<? super T> comparator)

Optional<T> min(Comparator<? super T> comparator)

Optional<T> reduce(BinaryOperator<T> accumulator)


이처럼 Optional<T>를 결과로 반환하는 최종 연산 메서드들은 몇 개 없다. 심지어 max()와 min()같은 메서드들은 reduce()를 이용해서 작성된 것이다.




*OptionalInt, OptionalLong, OptionalDouble

IntStream과 같은 기본 스트림에는 Optional도 기본형을 값으로 하는 OptionalInt, OptionalLong, OptionalDouble을 반환한다. 아래의 목록은 IntStream에 정의된

 메서드들이다.



OptionalInt    findAny()

OptioanlInt    findFirst()

OptionalInt    reduce(IntBinaryOperator op)

OptionalInt    max()

OptionalInt    min()

OptionalInt    average()


    반환 타입이 Optional<T>가 아니라는 것을 제외하고는 Stream에 정의된 것과 비슷하다.

그리고 기본형 Optional에 저장된 값을 꺼낼 때에는 사용하는 메서드의 이름이 조금씩 다르다는 것에 주의


Optional 클래스             값을 반환하는 클래스

Optional<T>                  T        get()

OptionalInt                    int     getAsInt()

OptionalLong                long    getAsLong()

OptionalDouble            double getAsDouble()


OptionalInt    는 다음과 같이 정의되어 있다. 앞서 래퍼 클래스에 대해서 배웠으므로, 이렇게 정의되어 있을 것이라고 짐작하는 것은 그리 어려운 일이 아니었을 것이다.


public final class OptionalInt{


private final boolean isPresent; //값이 저장되어 있으면 true

private final int value;     //int 타입의 변수


기본형 int의 기본값은 0이므로 아무런 값을 갖지 않는 OptionalInt에 저장되는 값은 0일 것이다. 그러면 아래의 두 OptionalInt객체는 같은 것일까?


OptionalInt opt = OptionalInt.of(0);        //OptionalInt에 0을 저장

OptionalInt opt2 = OptionalInt.empty();     //OptionalInt에 0을 저장


다행히 저장된 값이 없는 것과 0이 저장된 것은 (다른것이다) isPresent라는 인스턴스변수로 구분이 가능하다.

isPresent()는 인스턴스의 값을 반환한다.


System.out.println(opt.isPresent()); //true

System.out.println(opt2.isPresent()); //false

System.out.println(opt.getAsInt()); //0

System.out.println(opt2.getAsInt()); //NoSuchElementException예외 발생

System.out.println(opt.equals(opt2)); //false


그러나 Optional객체의 경우 null을 저장하면 비어 있는 것과 동일하게 취급한다.

Optional<String> opt = Optional.ofNullable(null);

Optional<String> opt2 = Optional.empty();


System.out.println(opt.equals(opt2)); //true


import java.util.*;
 
public class OptionalEx1 {
    public static void main(String[] args) {
        Optional<String> optStr = Optional.of("abcde");
        Optional<Integer> optInt = optStr.map(String::length);
        System.out.println("optStr="+optStr.get());
        System.out.println("optInt="+optInt.get());
 
        String str = null;
        int result1 = Optional.of("123")
                            .filter(x->x.length() > 0)
                            .map(Integer::parseInt).get(); //여기서 get은 뭐지? Optional객체에 저장된 값을 가져올때
        int result2 = Optional.of("")
                            .filter(x->x.length() > 0)
                            .map(Integer::parseInt).orElse(-1); //Integer함수에 있는 parseInt객체를 쓴다는 말일 뿐이다.
        //여기서 orElse는 객체에 저장된 값을 가져올때 null일때 NoSuchElementException이 발생하며, 이를 대비해서 orElse()로 대체할 값을 지정한다.
        //""은 값이 null이라고 인식하고 null은 왜 에러가 나는거지? -> 중간연산과정 filter는 x->x.length() > 0 을 제외하고 map은 그거를 숫자로 바꿀때 값이 null이어서 -1 출력
        //filer는 주어진 조건에 맞지 않는 요소는 다 제거 즉, length>0이아닌 길이가 없는 것들 다 제거
 
        System.out.println("result1="+result1);//123
        System.out.println("result2="+result2);//-1
 
        Optional.of("456").map(Integer::parseInt)
                            .ifPresent(x->System.out.printf("result3=%d%n",x));//456
 
        OptionalInt optInt1 = OptionalInt.of(0); //0을 저장
        OptionalInt optInt2 = OptionalInt.empty(); //빈 객체를 생성
 
        System.out.println(optInt1.isPresent()); //true
        System.out.println(optInt2.isPresent()); //false
 
        System.out.println(optInt1.getAsInt()); //0
      //System.out.println(optInt2.getAsInt()); //NoSuchElementException
        System.out.println("optInt1="+optInt1); //OptionalInt[0]
        System.out.println("optInt2="+optInt2); //OptionalInt.empty
        System.out.println("optInt1.equals(optInt2)?"+optInt1.equals(optInt2)); //false -> 0이랑 비어있는것은 다른것이다.
 
        Optional<String> opt = Optional.ofNullable(null); //null을 저장한다.
        Optional<String> opt2 = Optional.empty();   //빈 객체를 생성
        System.out.println("opt="+opt); //Optional.empty
        System.out.println("opt2="+opt2); //Optional.empty
        System.out.println("opt.equals(opt2) ?"+opt.equals(opt2)); //true -> null이랑 비어있는것은 같은것이다.
 
        int result3 = optStrToInt(Optional.of("123"),0);
        int result4 = optStrToInt(Optional.of(""),0);
 
        System.out.println("result3="+result3);  //123
        System.out.println("result4="+result4); //0
    }
 
    static int optStrToInt(Optional<String> optStr, int defaulValue){
        try {
            return optStr.map(Integer::parseInt).get();
        }catch (Exception e){
            return defaulValue;
        }
    }
}
 
cs

optInt=5

result1=123

result2=-1

result3=456

true

false

0

optInt1=OptionalInt[0]

optInt2=OptionalInt.empty

optInt1.equals(optInt2)?false

opt=Optional.empty

opt2=Optional.empty

opt.equals(opt2) ?true

result3=123

result4=0



'자바 이론 > 스트림' 카테고리의 다른 글

최종연산  (0) 2019.04.17
스트림의 중간연산 + 자바 CompareTo메서드 (Comparable)  (0) 2019.04.15
스트림 만들기  (0) 2019.04.15
스트림 중간연산,최종연산 목록  (0) 2019.04.15

1. 스트림의 중간연산


(스트림 자르기 - skip(), limit())


skip()과 limit()은 스트림의 일부를 잘라낼 때 사용하며, 사용법은 아주 간단하다. skip(3)은 처음 3개의 요소를 건너 뛰고, limit(5)는 스트림의 요소를 5개로 제한한다.

Stream<T> skip(long n)

Stream<T> limit(long maxSize)


예를 들어 10개의 요소를 가진 스트림에 skip(3)과 limit(5)를 순서대로 적용하면 4번째 요소부터 5개의 요소를 가진 스트림이 반환된다.


IntStream intStream = IntStream.rangeClosed(1,10); //1~10의 요소를 가진 스트림

intStream.skip(3).limit(5).forEach(System.out::print); //45678


기본형 스트림에도 skip()과 limit()이 정의되어 있는데, 반환 타입이 기본형 스트림이라는 점만 다르다.


IntStream skip(long n)

IntStream limit(long maxSize)


(스트림의 요소 잘라내기 - filter(), distinct())

distinct()는 스트림에서 중복된 요소들을 제거하고, filter()는 주어진 조건(Predicate)에 맞지 않는 요소를 걸러낸다.


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

Stream<T> distinct()


distinct()의 사용법은 간단하다.


IntStream intStream  = IntStream.of(1,2,2,3,3,3,4,5,5,6);

intStream.distinct().forEach(System.out.::print); //123456


filter()는 매개변수로 Predicate를 필요로 하는데, 아래와 같이 연산결과가 boolean인 람다식을 사용해도 된다.


IntStream intStream = IntStream.rangeColsed(1, 10); //1~10

intStream.filter(i -> i%2 ==0).forEach(System.out::print); //246810


필요하다면 filter()를 다른 조건으로 여러 번 사용할 수도 있다.


//아래의 두 문장은 동일한 결과를 얻는다.

intStream.filter(i -> i%2 !=0 && i%3!=0).forEach(System.out::print); //157

intStream.filter(i -> i%2 !=0).filter(i->i%3!=0).forEach(System.out::print);


(정렬 - sorted())

스트림을 정렬할 때는 sorted를 사용하면 된다.


Stream<T> sorted()

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


sorted()는 지정된 Comparator로 스트림 정렬하는데, Comparator대신 int값을 반환하는 람다식을 사용하는 것도 가능하다. Comparator를 지정하지 않으면 스트림 요소의 기본 정렬 기준(Comparable)으로 정렬한다.단, 스트림의 요소가 Comparable을 구현한 클래스가 아니면 예외가 발생한다.


Stream<String> strStream = Stream.of("dd","aaa","CC","cc","b");

strStream.sorted().forEach(System.out::print); //CCaaabccdd


위의 코드는 문자열 스트림을 String에 정의된 기본 정렬(사전순 정렬 대문자->소문자->abc순서)로 정렬해서 출력한다. 아래의 표는 위의 문자열 스트림(strStream)을 다양한 방법으로 정렬한 후에 forEach(System.out::println)로 출력한 결과를 보여준다.


참고) String.CASE_INSENSITIVE_ORDER는 String클래스에 정의된 Comparator이다.


문자열 스트림 정렬하는 다양한 방법


strStream.sorted() //기본 정렬

strStream.sorted(Comparator.naturalOrder())  //기본정렬

strStream.sorted((s1,s2) -> s1.compareTo(s2)); //람다식도 가능

strStream.sorted(String::compareTo); //위의 문장과 동일

CCaaabccdd


strStream.sorted(Comparator.reverseOrder()) //기본 정렬의 역순

strStream.sorted(Comparator.<String>naturalOrder().reversed())

ddccbaaaCC


strStream.sorted(String.CASE_INSENSITIVE_ORDER) //대소문자 구분안함 aaabCCccdd

strStream.sorted(String.CASE_INSENSITIVE_ORDER.reverese()) //  오타아님 ddCCccbaaa 이때는 또 대소문자 구분해서 대문자 먼저 써준다.


strStream.sorted(Comparator.comparing(String::length) //길이 순 정렬

strStream.sorted(Comparator.comparingInt(String::length)) //no오토박싱 

bddCCccaaa (1개 -> 2개 -> (대문자2개) -> (소문자2개) -> 3개)


strStream.sorted(Comparator.comparing(String::length).reversed()) 

aaaddCCccb(3개 -> 2개 -> (대문자2개) ->(소문자2개) -> 1개)

JDK1.8부터 Comparator 인터페이스에 static메서드와 디폴트 메서드가 많이 추가 되었는데, 이 메서드들을 이용하면 정렬이 쉬워진다. 

이 메서드들은 모두 Comparator<T>를 반환하며, 아래의 메서드 목록은 지네릭에서 와일드 카드를 제거하여 간단히 한 것이다. 보다 정확한 메서드의 선언을 보려면 자바 API를 봐야한다.


Comparator의 default메서드

reversed()

thenComparing(Comparator<T> other)

thenComparing(Function<T,U> keyExtractor)

thenComparing(Function<T,U> keyExtractor, Comparator<U> keyComp)

thenComparing(ToIntFunction<T> keyExtractor)

thenComparingLong(ToLongFunction<T> keyExtractor)

thenComparingDouble(ToDoubleFunction<T> keyExtractor)


Comparator의 static메서드

naturalOrder()

revereseOrder()

comparing(Function<T,U> keyExtractor)

comparing(Function<T,U> keyExtractor, Comparator<U> keyComparator)

comparing(ToIntFunction<T> keyExtractor)

comparing(ToLongFunction<T> keyExtractor)

comparing(ToDoubleFunction<T> keyExtractor)

nullsFirst(Comparator<T> comparator)

nullsLast(Comparator<T> comparator)


정렬에 사용되는 메서드의 개수가 많지만, 가장 기본적인 메서드는 comparing이다.


comparing(Function<T,U> keyExtractor)

comparing(Function<T,U> keyExtractor, Comparator<U> keyComparator)


스트림의 요소가 Comparable을 구현한 경우, 매개변수 하나짜리를 사용하면 되고 그렇지 않은 경우, 

추가적인 매개변수로 정렬기준(Comparator)을 따로 지정해 줘야 한다.


comparingInt(ToIntFunction<T> keyExtractor)

comparingLong(ToLongFunction<T> keyExtractor)

comparingDouble(ToDoubleFunction<T> keyExtractor)


비교대상이 기본형인 경우, comparing()대신에 위의 메서드를 사용하면 오토박싱과 언박싱 과정이 없어서 더 효율적이다. 

그리고 정렬 조건을 추가할 때는 thenComparing()을 사용한다.


thenComparing(Comparator<T> other)

thenComparing(Function<T,U> keyExtractor)

thenComparing(Function<T,U> keyExtractor, Comparator<U> keyComp)


예를 들어 학생 스트림(studentStream)을 반(ban)별, 성적(totalScore)순, 그리고 이름(name)순으로 정렬하여 출력하려면 다음과 같이 한다.


studentStream.sorted(Comparator.comparing(Student::getBan)

.thenComparing(Student::getTotalScore)

.thenComparing(Student::getName)

.forEach(System.out::println);



*compareTo()

Interface Comparable<T> 가 구현되어 있는 객체에서 사용가능한 객체 비교 메서드(Method)

Java에서 제공되는 정렬이 가능한 클래스들은 모두 Comparable 인터페이스를 구현하고 있으며, 정렬 시에 이에 맞게 정렬이 수행된다.

// Integer class

public final class Integer extends Number implements Comparable<Integer> { ... }

// String class

public final class String implements java.io.Serializable, Comparable<String>, CharSequence { ... }

Ex) Integer, Double 클래스: 오름차순 정렬

Ex) String 클래스: 사전순 정렬

기본 유형(Primitive Types)에 대해서는 적용할 수 없다.

반환 형태: integer type

현재 객체 < 인자로 넘어온 객체: return 음수

현재 객체 == 인자로 넘어온 객체: return 0

현재 객체 > 인자로 넘어온 객체: return 양수

사용자가 정의한 정렬 기준에 맞춰 정렬하기 위한 용도 로 compareTo() 메서드를 오버라이드하여 구현한다.

구현 방법

정렬할 객체에 Comparable interface를 implements 후, compareTo() 메서드를 오버라이드하여 구현한다.

compareTo() 메서드 작성법

위의 반환 형태 참고

음수 또는 0이면 객체의 자리가 그대로 유지되며, 양수인 경우에는 두 객체의 자리가 바뀐다.

정렬 사용 방법

Arrays.sort(array)

Collections.sort(list)

https://gmlwjd9405.github.io/2018/10/06/java-==-and-equals.html


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import java.util.*;
import java.util.stream.*;
 
//학생의 성적을 반별 오름차순, 총점별 내림차순으로 정렬하여 출력한다.
class StreamEx1 {
    public static void main(String[] args) {
 
/*      Student student1 = new Student("박진영", 1, 300);
        Student student2 = new Student("권영미", 3, 100);
        student1.compareTo(student2);
        */
 
        Stream<Student> studentStream = Stream.of(
                new Student("이자바"3300),
                new Student("김자바"1200),
                new Student("안자바"2100),
                new Student("박자바"2150),
                new Student("소자바"1200),
                new Student("나자바"3290),
                new Student("감자바"3180));
 
        studentStream.sorted(Comparator.comparing(Student::getBan) //반별 정렬
                .thenComparing(Comparator.naturalOrder())) // 기본 정렬 여기서 sorted끝을 막아준다. naturalOrder에서 compareTo호출해준다. 그래서 내림차순으로 중간연산 끝을 의미
                .forEach(System.out::println); //최종 연산 끝
    }
}
 
//따로 구현해 놓지 않아도 Comparable은 원래 interface없이 라이브러리로 기능 구현해놓으라고 해놓음 그래서 interface없이 implements가능
/*interface test{
    void test();
}*/
class Student implements Comparable<Student>{
    String name;
    int ban;
    int totalScore;
 
    //생성자 만들기
    Student(String name, int ban, int totalScore) {
        this.name = name;
        this.ban = ban;
        this.totalScore = totalScore;
    }
    //String타입으로반환해주는 toString함수 만들기 뒤에 .toString꼭 붙히기 포맷이 toString이라는 뜻
    public String toString() {
        return String.format("[%s, %d, %d]", name, ban, totalScore).toString();
    }
 
    public String getName() {
        return name;
    }
 
    public int getBan() {
        return ban;
    }
 
    public int getTotalScore() {
        return totalScore;
    }
 
    //총점을 내림차순으로 기본 정렬 한다.
    public int compareTo(Student s){
        return s.totalScore - this.totalScore;
        //"안자바" 2 학생 자신의 토탈스코어가 100일떄 전체 토탈 스코어 150 빼면  -50이 되서 순서를 바꿔준다.
        //음수면 순서를 바꿔줄 필요가 없다..
        //두 객체가 둘다 음수가 나와서 객체끼리 자리가 뒤바뀌게 된다.
 
    }
}
cs

학생의 성적 정보를 요소로하는 Stream<Student>을 반별로 정렬한 다음에, 총점별 내림차순으로 정렬한다. 정렬하는 코드를 짧게 하려고, Comparable을 구현해서 총점별 내림차순 정렬이 Student클래스의 기본정렬이 되도록 했다.


studentStream.sorted(Comparator.comparing(Student::getBan) //반별 정렬
.thenComparing(Comparator.naturalOrder())) // 기본 정렬 여기서 sorted끝을 막아준다 중간연산 끝을 의미
.forEach(System.out::println); //최종 연산 끝

(변환 - map())

스트림의 요소에 저장된 값 중에서 원하는 필드만 뽑아내거나 특정 형태로 변환해야 할 때가 있다. 이때 사용하는 것이 바로 map()이다. 이 메서드의 선언부는 아래와 같으며, 매개변수 T타입을 R타입으로 변환해서 반환하는 함수를 지정해야 한다.


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

예를 들어 File의 스트림에서 파일의 이름만 뽑아서 출력하고 싶을 때, 아래와 같이 map()을 이용하면 File 객체에서 파일의 이름(String)만 간단히 뽑아 낼 수 있다.


Stream<File> fileStream = Stream.of(new File("Ex1.java"), new File("Ex1"), new File("Ex1.bak"), new File("Ex2.java"), new File("Ex1.txt"));


//map()으로 Stream<File>을 Stream<String>으로 변환

Stream<String> filenameStream = fileStream.map(File::getName);

filenameStream.forEach(System.out::println); //스트림의 모든 파일이름을 출력


map()역시 중간 연산이므로, 연산 결과는 String을 요소로 하는 스트림이다. map()으로 Stream<File>을 Stream<String>으로 변환했다고 볼 수 있다.

그리고, map()도 filter()처럼 하나의 스트림에 여러 번 적용할 수 있다. 다음 문장은 File스트림에서 파일의 확장자만을 뽑은 다음 중복을 제거해서 출력한다.


fileStream.map(File::getName) //Stream<File> -> Stream<String>

.filter(s -> s.indexOf('.') != -1 //확장자가 없는 것은 제외

.map(s  -> s.substring(s.indexOf('.')+1)) //Stream<String>->Stream<String>

.map(String::toUpperCase)// 모두 대문자로 변환

.distinct()        //중복 제거

.forEach(System.out::print);    //JAVABAKTXT



(조회 - peek())

연산과 연산 사이에 올바르게 처리되었는지 확인하고 싶다면, peek()를 사용하자. forEach()와 달리 스트림의 요소를 소모하지 않으므로 연산 사이에 여러 번 끼워 넣어도 문제가 되지 않는다.


fileStream.map(File::getName) //Stream<File> -> Stream<String>

.filter(s -> s.indexOf('.') != -1)        //확장자가 없는 것은 제외

.peek(s ->System.out.printf("filename=%s%n", s)) //파일명을 출력한다.

.map(s ->s.substring(s.indexOf('.')+1))  //확장자만 추출

.peek(s->System.out.printf("extension=%s%n", s)) //확장자를 출력한다.

.forEach(System.out::println);



filter()나 map()의 결과를 확인할 때 유용하게 사용될 수 있다. 아래의 예제에는 실행 결과가 복잡해 지지 않게 peek()를 넣지 않았는데 peek()넣고 확인해보기


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.io.*//이것덕분에 File이 에러가 안남
import java.util.stream.*;
 
public class StreamEx2 {
    public static void main(String[] args) {
        File[] fileArr = { new File("Ex1.java"), new File("Ex1.bak"), new File("Ex2.java"), new File("Ex1"), new File("Ex1.txt")};
 
        Stream<File> fileStream = Stream.of(fileArr);
 
        //map()으로 Stream<File>을 Stream<String>으로 변환
        Stream<String> filenameStream = fileStream.map(File::getName);
        filenameStream.forEach(System.out::println); //모든 파일의 이름을 출력
 
        fileStream = Stream.of(fileArr); //스트림을 다시 생성
 
        fileStream.map(File::getName) //Stream<File> -> Stream<String>
                    .filter(s->s.indexOf('.')!=-1//확장자가 없는 것은 제외
                    .map(s->s.substring(s.indexOf('.')+1)) //확장자만 추출
                    .map(String::toUpperCase) //모두 대문자로 변환
                    .distinct()     //중복 제거
                    .forEach(System.out::println);  //JAVABAKTXT 출력
 
        System.out.println();
 
 
        //map이 하는 일이 많구나
        //1. Stream<File> -> Stream<String>  1. Stream타입변환
        // Stream<String> filenameStream = fileStream.map(File::getName);
        // fileStream.map(File::getName).map(s->s.substring(s.indexOf('.')+1) 2. 확장자만 호출
        //3. 대문자 변환  fileStream.map(String::toUpperCase)
        //distinct()는 중복제거
        //그리고 s->s.indexOf('.')!=-1 할때 s->s의 의미가 무엇일지 이것은 메서드 참조하는 것을 람다식으로 표현한 것?? 다시 확인하기
    }
}
 
cs


Ex1.java

Ex1.bak

Ex2.java

Ex1

Ex1.txt

JAVA

BAK

TXT


(mapToInt(), mapToLong(), mapToDouble())


map()은 연산의 결과로 Stream<T>타입의 스트림을 반환하는데, 스트림의 요소를 숫자로 변환하는 경우 IntStream과 같은 기본형 스트림으로 변환하는 것이 더 유용할 수 있다. Stream<T>타입의 스트림을 기본형 스트림으로 변환할 때 사용하는 것이 아래의 메서드들이다.

DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)

IntStream mapToInt(ToIntFunction<? super T> mapper)

LongStream mapToLong(ToLongFunction<? super T> mapper)


앞서 사용했던 studentStream에서, 스트림에 포함된 모든 학생의 성적을 합산해야 한다면, map()으로 학생의 총점을 뽑아서 새로운 스트림을 만들어 낼 수 있다.


Stream<Integer> studentScoreStream = studentStream.map(Student::getTotalScore);


그러나 이럴 때는 애초부터 mapToInt()를 사용해서 Stream<Integer>가 아닌 IntStream 타입의 스트림을 생성해서 사용하는 것이 더 효율적이다. 성적을 더할 때, Integer를 int로 변환할 필요가 없기 때문이다.


IntStream studentScoreStream = studentStream.mapToInt(Student::getTotalScore);

int allTotalScore = studentScoreStream.sum(); //int sum();


count()만 지원하는 Stream<T>와 달리 IntStream과 같은 기본형 스트림은 아래와 같이 숫자를 다루는데 편리한 메서드들을 제공한다.


참고) max()와 min()은 Stream에도 정의되어 있지만, 매개변수로 Comparator를 정의해야  한다는 차이가 있다.


int                            sum()        스트림의 모든 요소의 총합

OptionalDouble    average()         sum() / (double)count()

OptionalInt         max()                스트림의 요소 중 제일 큰 값

OptionalInt        min()                    스트림의 요소 중 제일 작은 값



스트림의 요소가 하나도 없을 떄, sum() 은 0을 반환하면 그만이지만 다른 메서드들은 단순히 0을 반환할 수 없다. 여러 요소들을 합한 평균이 0일 수도 있기 때문이다.

이를 구분하기 위해 단순히 double값을 반환하는 대신, double타입의 값을 내부적으로 가지고 있는 OptionalDouble을 반환하는 것이다. OptionalInt, OptionalDouble등은 일종의 래퍼클래스로 각각 int값과 Double값을 내부적으로 가지고 있다. 이 Optional클래스들에 대한 내용은 잠시 후에 자세히 설명할 것이다.



그리고 이 메서드들은 최종연산이기 떄문에 호출 후에 스트림이 닫힌다는 점을 주의해야 한다. 아래의 코드 처럼 하나의 스트림에 sum()과 average()를 연속해서 호출할 수 없다.


IntStream scoreStream = studentStream.mapToInt(Student::getTotalScore);


long totalScore = scoreStream.sum(); //sum()은 최종연산이라 호출 후 스트림이 닫힘

OptionalDouble average = scoreStream.average(); //에러. 스트림이 이미 닫혔음.


double d = average.getAsDouble(); //OptionalDouble에 저장된 값을 꺼내서 d에 저장


sum()과 average()를 모두 호출해야할 때, 스트림을 또 생성해야 하므로 불편하다. 그래서 summaryStatistics()라는 메서드가 따로 제공된다.


IntSummaryStatistics stat = scoreStream.summaryStatistics();

long        totalCount = stat.getCount();

long        totalScore = stat.getSum();

double    avgScore = stat.getAverage();

int          minScore = stat.getMin();

int           maxScore = stat.getMax();


IntSummaryStatistics는 위와 같이 다양한 종류의 메서드를 제공하며, 이 중에서 필요한 것만 골라서 쓰면 된다.

기본형 스트림 LongStream과 DoubleStream도 IntStream과 같은 연산(반환타입은 다름)를 지원한다. 

반대로 IntStream을 Stream<T>로 변환할 때는 mapToObj()를, Stream<Integer>로 변환할 때는 boxed()를 사용한다.


Stream<U>    mapToObj(IntFunction<?    extends    U> mapper)

Stream<Integer> boxed()


아래는 로또번호를 생성해서 출력하는 코드인데, mapToObj()를 이용해서 IntStream을 Stream<String>으로 변환하였다.


IntStream intStream = new Random().ints(1,46); //1~45사이의 정수(46은 포함 안됨)

Stream<String> lottoStream = intStream.distinct().limit(6).sorted().mapToObj(i -> i+","); //정수를 문자열로 변환 6개만 나와라

lottoStream.forEach(System.out::print); //12,14,20,23,26,29


이외에도 스트림의 변환에 대해서는 알아야할 것들이 더 있는데, 이에 대해서는 나중에 한꺼번에 정리할 것이다.


참고로 CharSequence에 정의된 chars()는 String이나 StringBuffer에 저장된 문자들을 IntStream으로 다룰 수 있게 해준다.


IntStream charStream = "12345".chars(); //default IntStream chars()

int charSum = charStream.map(ch -> ch-'0').sum(); //charSum = 15


위의 코드에서 사용된 map()은 IntStream에서 정의된 것이고, IntStream을 결과로 반환한다. 그리고 mapToInt(Integer::parseInt)나 valueOf()가 있다는 것도 알아두자.


Stream<String> -> IntStream 변환 할 때, mapToInt(Integer::parseInt)

Stream<Integer> -> IntStream  변환 할 떄, mapToInt(Integer::intValue)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import java.util.*;
import java.util.stream.*;
 
public class StreamEx3 {
    public static void main(String[] args) {
        Student1[] stuArr = {
                new Student1("이자바"3300),
                new Student1("김자바"1200),
                new Student1("안자바"2100),
                new Student1("박자바"2150),
                new Student1("소자바"1200),
                new Student1("나자바"3290),
                new Student1("감자바"3180)};
 
        Stream<Student1> stuStream = Stream.of(stuArr);
 
        stuStream.sorted(Comparator.comparing(Student1::getBan)
                .thenComparing(Comparator.naturalOrder()))
                .forEach(System.out::println);
 
        stuStream = Stream.of(stuArr); //스트림을 다시 생성한다!!!!! 위에서 사용 끝났기 떄문에
        IntStream stuScoreStream = stuStream.mapToInt(Student1::getTotalScore); //스트림의 형이 아닌 요소를 변환한다. 형이랑 요소랑 차이가 있나? Stream<String>을 Stream<Integer>로 바꾸는건데
 
        IntSummaryStatistics stat = stuScoreStream.summaryStatistics();
        System.out.println("count=" + stat.getCount());
        System.out.println("sum=" + stat.getSum());
        System.out.println("average=" + stat.getAverage());
        System.out.println("min=" + stat.getMin());
        System.out.println("max=" + stat.getMax());
    }
}
class Student1 implements Comparable<Student1>{
    String name;
    int ban;
    int totalScore;
 
    Student1(String name, int ban, int totalScore) {
        this.name = name;
        this.ban = ban;
        this.totalScore = totalScore;
    }
 
 
    public String getName() {
        return name;
    }
 
    public int getBan() {
        return ban;
    }
 
    public int getTotalScore() {
        return totalScore;
    }
 
    public String toString() {
        return String.format("[%s, %d, %d]", name, ban, totalScore).toString();
    }
 
    public int compareTo(Student1 s){
        return s.totalScore - this.totalScore;
 
        }
    }
 
 
cs


[김자바, 1, 200]

[소자바, 1, 200]

[박자바, 2, 150]

[안자바, 2, 100]

[이자바, 3, 300]

[나자바, 3, 290]

[감자바, 3, 180]

count=7

sum=1420

average=202.85714285714286

min=100

max=300


(flatMap) - Stream<T[]>를 Stream<T>로 변환)

스트림의 요소가 배열이거나 map()의 연산결과가 배열인 경우, 즉 스트림의 타입이 Stream<T[]>인 경우, Stream<T>로 다루는 것이 더 편리할 때가 있다. 그럴 때는 map()대신 flatMap()을 사용하면 된다.


예를들어서 아래와 같이 요소가 문자열 배열(String[])인 스트림이 있을 떄,


Stream<String[]> strArrStrm = Stream.of( 

new String[]{"abc","def","ghi"},

new String[]{"ABC","GHI","JKLMN"}

);


각 요소의 문자열들을 합쳐서 문자열이 요소인 스트림, 즉 Stream<String>으로 만들려면 어떻게 해야 할까?

먼저 스트림의 요소를 변환해야하니까 일단 map()을 써야 할 것이고 여기에 배열을 스트림으로 만들어주는 Arrays.stream(T[])을 함께 사용해보자


Stream<Stream<String>> strStrStrm = strArrStrm.map(Arrays::stream);


예상한 것과 달리, Stream<String[]>을 'map(Arrays::stream)'으로 변환한 결과는 Stream<String>이 아닌, Stream<String>>이다. 즉, 스트림의 스트림인 것이다 


각 요소의 문자열들이 합쳐지지 않고, 스트림의 스트림 형태로 되어 버렸다. 이 때, 간단히 map()을 아래와 같이 flatMap()으로 바꾸기만 하면 우리가 원하는 결과를 얻을 수 있다.


Stream<Stream<String>> strStrStrm = strArrStrm.map(Arrays::stream);

Stream<String> strStrm = strArrStrm.flatMap(Arrays::stream);


flatMap()은 map()과 달리 스트림의 스트림이 아닌 스트림으로 만들어준다.


Stream<String[]> ====================map(Arrays::stream)==================> Stream<Stream<String>>

Stream<String[]> ===================flatMap)Arrays::stream)================> Stream<String>


또 다른 경우를 예를 들자.

아래와 같이 여러 문장을 요소로 하는 스트림이 있을 때, 이 문장들을 split()으로 나눠서 요소가 단어인 스트림을 만들고 싶다면 어떻게 해야 할까요?


String[] lineArr = {

"Believe or not It is true"

"Do or do not There is no try",

};

Stream<String> lineStream = Arrays.stream(lineArr);

Stream<String[]> strArrStream = lineStream.map(line->Stream.of(line.split(" +")));


위 문장에서 알 수 있듯이,map()은 Stream<String>이 아니라 Stream<String[]>을 결과로 돌려준다. 이럴 때도 map()대신 flatMap()으로 원하는 결과를 얻을 수 있다.

Stream<String> strStream = lineStream.flatMap(line->Stream.of(line.split(" +")));


map()과  flatMap()의 차이를 간단히 정리하면 다음과 같다.


Stream<String> ======map(s->Stream.of(s.split(" +")))=========> Stream<Stream<String>>

Stream<String> ------flatMap(s->Stream.of(s.split(" +")))----------> Stream<String>


strStream의 단어들을 모두 소문자로 변환하고, 중복된 단어들을 제거 한 다음에 정렬해서 출력하는 문장은


strStream.map(String::toLowerCase)    //모든 단어를 소문자로 변환

.distinct()                            //중복된 단어를 제거

.sorted()                            //사전 순으로 정렬

.forEach(System.out::println); //화면에 출력


드물지만, 스트림을 요소로 하는 스트림, 즉 스트림의 스트림을 하나의 스트림으로 합칠 때도 flatMap()을 사용한다.


Stream<String> strStrm = Stream.of("abc","def","jklmn");

Stream<String> strStrm2 = Stream.of("ABC","GHI","JKLMN");


Stream<Stream<String>> strmStrm = Stream.of(strStrm, strStrm2);


위와 같이 요소의 타입이 Stream<String>인 스트림(Stream<Stream<String>>)이 있을 떄 , 이 스트림을 Stream<String>으로 변환하려면 다음과 같이 map()과 flatMap()을 함께 사용해야 한다.

Stream<String> strStream = strmStrm.map(s -> s.toArray(String[]::new) //Stream<Stream<String>> -> Stream<String[]>

.flatMap(Arrays::stream);            //Stream<String[]> -> Stream<String>


toArray()는 스트림을 배열로 변환해서 반환한다. 매개변수를 지정하지 않으면 Object[]을 반환하므로 위와 같이 특정 타입의 생성자를 지정해 줘야 한다.

여기서는 String배열의 생성자(String[]::new)를 지정하였다. 그 다음엔 flatMap()으로 Stream<String[]>을 Stream<String>으로 변환한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import java.util.*;
import java.util.stream.*;
 
public class StreamEx4 {
    public static void main(String[] args) {
        Stream<String[]> strArrStrm = Stream.of(
                new String[] {"abc","def","jkl"},
                new String[] {"ABC","GHI","JKL"}
        );
 
        //Stream<Stream<String>> strStrmStrm = strArrStrm.map(Arrays::stream);
        Stream<String> strStrm = strArrStrm.flatMap(Arrays::stream);
 
        strStrm.map(String::toLowerCase)
                .distinct()
                .sorted()
                .forEach(System.out::println);
        System.out.println();
 
        String[] lineArr = {
 
                "Believe or not It is true",
 
                "Do or do not There is no try",
 
        };
 
        Stream<String> lineStream = Arrays.stream(lineArr);
        lineStream.flatMap(line -> Stream.of(line.split(" +"))) // )꼭 하나 더 넣기여기서 막아버려야 한다.
                .map(String::toLowerCase)
                .distinct()
                .sorted()
                .forEach(System.out::println);
        System.out.println();
 
        //이게 스트림의 스트림 Stream<Stream<String>> 이다 Stream.of가 들어 갔기 떄문에
        Stream<String> strStrm1 = Stream.of("AAA","ABC","bBb","Dd");
        Stream<String> strStrm2 = Stream.of("bbb","aaa","ccc","dd");
 
        Stream<Stream<String>> strmStrmStrm = Stream.of(strStrm1,strStrm2);
 
 
        //위와 같이 요소의 타입이 Stream<String>인 스트림(Stream<Stream<String>>)이 있을 떄 ,
        //이 스트림을 Stream<String>으로 변환하려면 다음과 같이 map()과 flatMap()을 함께 사용해야 한다.
 
        //toArray()는 스트림을 배열로 변환해서 반환한다. 매개변수를 지정하지 않으면 Object[]을 반환하므로 위와 같이 특정 타입의 생성자를 지정해 줘야 한다.
        //여기서는 String배열의 생성자(String[]::new)를 지정하였다. 그 다음엔 flatMap()으로 Stream<String[]>을 Stream<String>으로 변환한다.
        Stream<String> strStream = strmStrmStrm.map(s -> s.toArray(String[]::new))
                                                .flatMap(Arrays::stream);
 
        strStream.map(String::toLowerCase)
                .distinct()
                .forEach(System.out::println);
 
    }
}
 
cs


'자바 이론 > 스트림' 카테고리의 다른 글

최종연산  (0) 2019.04.17
Optional<T>와 OptionalInt / NULL대신에 Optional  (0) 2019.04.16
스트림 만들기  (0) 2019.04.15
스트림 중간연산,최종연산 목록  (0) 2019.04.15

스트림으로 작업 하려면, 스트림이 필요하니까 일단 스트림을 생성하는 방법부터 먼저 시작하자. 

스트림의 소스가 될 수 있는 대상은 배열, 컬렉션, 임의의 수 등 다양하며, 

이 다양한 소스들로 부터 스트림을 생성하는 방법에 대해 배우게 될 것이다.



*컬렉션

컬렉션의 최고 조상인 Collection에 stream()이 정의되어 있다. 그래서 Collection의 자손인 List와 Set을 구현한 컬렉션 클래스들은 모두 이 메서드로 스트림을 생성할 수 있다.

stream()은 해당 컬렉션을 소스(source)로 하는 스트림을 반환한다.


Stream<T> Collection.stream()


예를 들어 List로 부터 스트림을 생성하는 코드는 다음과 같다.

List<Integer> list = Arrays.asList(1,2,3,4,5); //가변인자

Stream<Integer> intStream = list.stream(); //list를 소스로 하는 컬렉션 생성


forEach()는 지정된 작업을 스트림의 모든 요소에 대해 수행한다. 아래의 문장은 스트림의 모든 요소를 화면에 출력한다.

intStream.forEach(System.out::println); //스트림의 모든 요소를 출력한다.
intStream.forEach(System.out::println); //에러. 스트림이 이미 닫혔다.


한 가지 주의할 점은 forEach(0가 스트림의 요소를 소모하면서 작업을 수행하므로 같은 스트림 forEach()를 두 번 호출할 수 없다는 것이다. 그래서 스트림의 요소를 한번 더 출력하려면 스트림을 새로 생성해야 한다. forEach()에 의해 스트림의 요소가 소모되는 것이지, 소스의 요소가 소모되는 것은 아니기 때문에 같은 소스로 부터 다시 스트림을 생성할 수 있다.


forEach()에 대한 것은 나중에 더 자세히 배우기로 하고, 지금은 forEach()로 스트림의 모든 요소를 화면에 출력하는 방법만 알아두자.



*배열

배열을 소스로 하는 스트림을 생성하는 메서드는 다음과 같이 Stream과 Arrays에 static 메서드로 정의되어 있다.


Stream<T> Stream.of(T...values) //가변인자

Stream<T> Stream.of(T[]) 

Stream<T> Arrays.stream(T[])

Stream<T> Arrays.stream(T[] array, int startInclusive, int endExclusive)

예를 들어 문자열 스트림은 다음과 같이 생성한다.


Stream<String> strStream = Stream.of("a","b","c"); //가변인자

Stream<String> strStream = Stream.of(new String[] {"a","b","c"});

Stream<String> strStream = Arrays.stream(new String[]{"a","b","c"});

Stream<String> strStream = Arrays.stream(new String[]{"a","b","c"},0,3);


이 외에도 long과 double타입의 배열로 부터 LongStream과 DoubleStream을 반환하는 메서드들이 있지만 일일이 나열하지 않아도 쉽게 유추해낼 수 있을 것으로 생략한다.


*특정 범위의 정수

IntStream과 LongStream은 다음과 같이 지정된 범위의 연속된 정수를 스트림으로 생성해서 반환하는 range()와 rangeClosed()를 가지고 있다.


IntStream IntStream.range(int begin, int end)

IntStream IntStream.rangeClosed(int begin, int end)


range()의 경우 경계의 끝인 end가 범위에 포함되지 않고, rangeClosed()의 경우는 범위가 포함된다.

IntStream intStream = IntStream.range(1,5); //1,2,3,4

IntStream intStream = IntStream.rangeClosed(1,5); //1,2,3,4,5


int보다 큰 범위의 스트림을 생성하려면 LongStream에 있는 동일한 이름의 메서드를 사용하면 된다.



*임의의 수

난수를 생성하는데 사용하는 Random클래스에는 아래와 같이 인스턴스 메서드들이 포함되어 있다. 이 메서드들은 해당 타입의 난수들로 이루어진 스트림을 반환한다.


IntStream ints()

LongStream longs()

DoubleStream doubles()


이 메서드들이 반환하는 스트림은 크기가 정해지지 않은 '무한 스트림(infinited stream)'이므로 limit()도 같이 사용해서 스트림의 크기를 제한해 주어야 한다. limit()은 스트림의 개수를 지정하는데 사용되며, 무한 스트림을 유한 스트림으로 만들어 준다.

IntStream     intStream = new Random().ints();     //무한 스트림

intStream.limit(5).forEach(System.out::println); //5개의 요소만 출력한다.

아래의 메서드들은 매개변수로 스트림의 크기를 지정해서 '유한 스트림'을 생성해서 반환하므로 limit()을 사용하지 않아도 된다.


IntStream ints(long streamSize)

LongStream longs(long streamSize)

DoubleStream doubles(long streamSize)


IntStream intStream = new Random().ints(5); //크기가 5인 난수 스트림을 반환

위 메서드들에 의해 생성된 스트림 난수는 아래의 범위를 갖는다.


Integer.MIN_VALUE <= ints()         <= Integer.MAX_VALUE

Long.MIN_VALUE <= longs()     <=Long.MAX_VALUE

0.0                <= doubles()      < 1.0


지정된 범위(begin~end)의 난수를 발생시키는 스트림을 얻는 메서드는 아래와 같다. 단, end는 범위에 포함되지 않는다.


IntStream ints(int begin, int end)

LongStream longs(long begin, long end)

DoubleStream doubles(double begin, double end)

IntStream ints(long streamSize, int begin, int end)

LongStream longs(long streamSize, long begin, long end)

DoubleStream doubles(long streamSize, double begin, double end)

*람다식-  iterator(), generate()

Stream클래스의 iterate()와 generate()는 람다식을 매개변수로 받아서, 이 람다식에 의해 계산된 결과를 다시 seed값으로 해서 계산을 반복한다. 아래의 evenStream은 0부터 시작해서 값이 2씩 계속 증가한다.


Stream<Integer> evenStream = Stream.iterate(0,n->n+2); //0,2,4,6, ...


n -> n+2

0 -> 0+2

2 -> 2+2

4 -> 4+2


generate()도 iterate()처럼, 람다식에 의해 계산되는 값을 요소로 하는 무한 스트림을 생성해서 반환하지만, iterator()와 달리, 이전 결과를 이용해서 다음 요소를 계산하지 않는다.

Stream<Double> randomStream = Stream.generate(Math::random);

Stream<Integer> oneStream = Stream.generate(()->1);


그리고 generate()에 정의된 매개변수의 타입은 Supplier<T>이므로 매개변수가 없는 람다식만 허용된다. 한 가지 주의할 점은 iterate()와 generate()에 의해 생성된 스트림을 아래와 같이 기본형 스트림 타입의 참조 변수로 다룰수 없다는 것이다.


IntStream evenStream = Stream.iterate(0,n->n+2); //에러

DoubleStream randomStream = Stream.generate(Math::random); //에러


굳이 필요하다면, 아래와 같이 mapToInt()와 같은 메서드로 변환을 해야 한다.

IntStream evenStream = Stream.iterate(0, n->n+2).mapToInt(Integer::valueOf);

Stream<Integer> stream = evenStream.boxed(); //IntSteam -> Stream<Integer>


반대로 IntStream타입의 스트림을 Stream<Integer>타입으로 변환하려면, boxed()를 사용하면 된다. 스트림간의 변환에 대해서는 나중에 같이 다룰 것이므로, 지금은 참고만 하자.


*스트림의 변환에 관한 표


스트림 타입 변환 표


스트림 타입 변환 정리표

From

To

변환 메소드

스트림 -> 기본형 스트림

Stream<T>

IntStream

LongStream

DoubleStream

MapToInt (Function<T> mapper)

mapToLong (ToLongFunction<T> mapper)

mapToDouble(ToDoubleFunction<T>mapper)

기본형 스트림 -> 스트림

IntStream

LongStream

DoubleStream

Stream<Integer>

Stream<Long>

Stream<Double>

boxed()

Stream<U>

mapToObj (DoubleFunction<U> mapper)

기본형 스트림 -> 기본형 스트림

Stream<T>

IntStream

LongStream

DoubleStream

asLongStream()

asDoubleStream()

스트림 -> 부분 스트림

Stream<T>

IntStream

Stream<T>

IntStream

skip (long n)

limit (long maxSize)

두 개의 스트림 -> 스트림

Stream<T>, Stream<T>

Stream<T>

concat (Stream<T> a, Stream<T> b)

IntStream, IntStream

IntStream

concat (IntStream a, IntStream b)

LongStream, LongStream

LongStream

concat (LongStream a, LongStream b)

DoubleStream, DoubleStream

DoubleStream

concat (DoubleStream a, DoubleStream b)

스트림의 스트림 -> 스트림

Stream<Stream<T>>

Stream<T>

flatMap (Function mapper)

Stream<IntStream>

IntStream

flatMapToInt (Function mapper)

Stream<LongStream>

LongStream

flatMapToLong (Function mapper)

Stream<DoubleStream>

DoubleStream

flatMapToDouble (Function mapper)

스트림 <-> 병렬 스트림

Stream<T>

IntStream

LongStream

DoubleStream

Stream<T>

IntStream

LongStream

DoubleStream

parallel() // 스트림->병렬 스트림

sequential() // 병렬 스트림 -> 스트림

스트림 -> 컬렉션

Stream<T>

IntStream

LongStream

DoubleStream

Collection<T>

collect (Collectors.toCollection (Supplier factory))

List<T>

collect (Collectors.toList())

Set<T>

collect (Collectors.toSet())

컬렉션 -> 스트림

Collection<T>, List<T>, Set<T>

Stream<T>

stream()

스트림 -> Map

Stream<T>

IntStream

LongStream

DoubleStream

Map<K,V>

collect(Collectors.toMap(Function key, Function value))

collect(Collectors.toMap(Function key, Function value, BinaryOperator merge))

collect(Collectors.toMap(Function key, Function value, BinaryOperator merge, Supplier mapSupplier))

스트림 -> 배열

Stream<T>

Object[]

toArray()

T[]

toArray(IntFunction<A[]> generator)

IntStream

LongStream

DoubleStream

int[]

long[]

double[]

toArray()


*파일

 java.nio.file.Files는 파일을 다루는데 필요한 유용한 메서드들을 제공하는데, list()는 지정된 디렉토리(dir)에 있는 파일의 목록을 소스로 하는 스트림을 생성해서 반환한다.

참고)Path는 하나의 파일 또는 경로를 의미한다.

Stream<Path> Files.list(Path dir)

이 외에도 Files클래스에는 Path를 요소로 하는 스트림을 생성하는 메서드가 더 있지만, 이 장의 주제를 벗어나므로 생략,

그리고, 파일의 한 행(line)을 요소로 하는 스트림을 생성하는 메서드도 있다. 아래의 세번째 메서드는 BufferedReader클래스에 속한 것인데, 파일 뿐만 아니라 다른 입력대상으로부터도 데이터를 행단위로 읽어올 수 있다.


Stream<String>    Files.lines(Path path)

Stream<String>    Files.lines(Path path, Charset cs)

Stream<String>    lines()         //BufferedReader클래스의 메서드


*빈 스트림

요소가 하나도 없는 비어있는 스트림을 생성할 수도 있다. 스트림에 연산을 수행한 결과가 하나도 없을 떄, null보다 빈 스트림을 반환하는 것이 낫다.


Stream emptyStream = Stream.empty(); //empty()는 빈 스트림을 생성해서 반환한다.

long count = emptyStream.count(); //count의 값은 0


count()는 스트림 요소의 개수를 반환하며, 위의 문장에서 변수 count의 값이 0이 된다.


*두 스트림의 연결

Stream의 static메서드인 concat()을 사용하면, 두 스트림을 하나로 연결할 수 있다. 물론 연결하려는  두 스트림의 요소는 같은 타입이어야 하낟.


String[] str1 = {"123","456","789"};

String[] str = {"ABC","abc","DEF"};


Stream<String> strs1 = Stream.of(str1);

Stream<String> strs2 = Stream.of(str2);

Stream<String> strs3 = Stream.concat(strs1,strs2); //두 스트림을 하나로 연결



1. 스트림  연산과정에 필요한 3가지

1) 질의 대상이 되는 소스(컬렉션)

2) 파이프 라인에 해당하는 중간 연산자

3) 파이프 라인을 종료하고 결과를 출력하는 최종 연산자




중간 연산자


 연산

 반환값

연산 인수 

함수 디스크립터 

 filter

Stream<T>

 Predicate<T>

T -> boolean 

 map

Stream<T>

Function<T, R> 

T -> R 

 limit

Stream<T> 

 

 

 sorted

 Stream<T>

Comparator<T> 

(T, T) -> int 

distinct 

 Stream<T>

  

skip                            Stream<T>



<스트림의 중간 연산자>

Stream<T> distinct()        중복을 제거

Stream<T> filter(Predicate<T> predicate)     조건에 안 맞는 요소 제외

Stream<T> limit (long maxSize)         스트림의 일부를 잘라낸다

Stream<T> skip(long n)        스트림의 일부를 건너뛴다.

Stream<T> peek(Consumer<T> action)     스트림의 요소에 작업을 수행

Stream<T> sorted()

Stream<T> sorted(Comparator<T> comparator)     스트림의 요소를 정렬한다.


스트림의 요소를 변환한다.

Stream<R> map(Function<T,R> mapper)

DoubleStream mapToDouble(ToDoubleFunction<T> mapper)

IntStream mapToInt(ToIntFunction<T> mapper)

LongStream mapToLong(ToLongFunction<T> mapper)

Stream<R> flatMap(Function<T,Stream<R>> mapper)

DoubleStream flatMapToDouble(Function<T.DoubleStream> m)

IntStream flatMapToInt(Function<T, IntStream> m)

LongStream flatMapToLong(Function<T,LongStream> m)




<스트림의 최종 연산자>

void forEach(Consumer<? super T> action>

void forEachOrdered(Consumer<? super T>action)     각 요소에 지정된 작업 수행


long count()         스트림의 요소의 개수 반환


Optional<T> max (Comparator<? super T> comparator)        

Optional<T> min (Comparator<? super T> comparator) 스트림의 최대값/최소값을 반환

스트림의 요소 하나를 반환

Optional<T> findAny() //아무거나 하나

Optional<T> findFirst() //첫번째 요소


주어진 조건을 모든 요소가 만족시키는지, 만족시키지 않는지 확인

boolean allMatch(Predicate<T> p) //모두 만족 하는지

boolean anyMatch(PredicaAte<T> p ) //하나라도 만족하는지

boolean nonMatch(Predicate<> p) //모두 만족하지 않는지


스트림의 모든 요소를 배열로 반환

Object[] toArray()    

A[] toArray(IntFunction<A[]> generator)


스트림의 요소를 하나씩 줄여가면서(리듀싱) 계산한다.

Optional<T> reduce(BinaryOperator<T> accumulator)

T reduce (T identity, BinaryOperator<T> accumulator)

U reduce (U identity, BiFunction<U,T,U> accumulator, BinaryOperator<U> combiner)


스트림의 요소를 수집한다. 주로 요소를 그룹화하거나 분할한 결과를 컬렉션에 담아 반환하는데 사용된다.

R collect(Collector<T,A,R> collector)

R collect(Supplier<R> supplier, BiConsumer<R,T> accumulator, BiConsumer<R,R> combiner)


연산 

비고 

 forEach

스트림에 각 요소를 람다를 통해 특정 작업을 실행한다. 

 count

스트림의 요소 개수를 반환한다. (long) 

collect 

스트림을 컬렉션 형태로 반환한다. 



**중간 연산은 map()과 flatMap(), 최종연산은 reduce()와 collect()가 핵심이다. 나머지는 이해하기 쉽고 사용법도 간단하다.

참고) Optional은 일종의 래퍼 클래스(wrapper class)로 내부에 하나의 객체를 저장할 수 있다.
출처: https://demoversion.tistory.com/27 [Demoversion]


*지연된 연산

스트림 연산에서 한 가지 중요한 점은 최종 연산이 수행되기 전까지는 중간 연산이 수행되지 않는다는 것이다. 스트림에 대해 distinct()나 sort()같은 중간 연산을 호출해도 즉각적인 연산이 수행되는 것은 아니라는 것이다. 중간 연산을 호출하는 것은 단지 어떤 작업이 수행되어야 하는지를 지정해주는 것일 뿐이다. 최종 연산이 수행되어야 비로소 스트림의 요소들이 중간 연산을 거쳐 최종 연산에서 소모된다.


Stream<Integer>와 IntStream

요소의 타입이 T인 스트림은 기본적으로 Stream<T>이지만, 오토박싱&언박싱으로 인한 비효율을 줄이기 위해 데이터 소스의 요소를 기본형으로 다루는 스트림, IntStream, LongStream, DoubleStream이 제공된다. 일반적으로 Stream<Integer>대신 IntStream을 사용하는 것이 더 효율적이고, IntStream에는 int타입의 값으로 작업하는데 유용한 메서드들이 포함되어 있다. 보다 자세한 것은 곧 설명한다.


*병렬 스트림

스트림으로 데이터를 다룰 때의 장점 중 하나가 바로 병렬 처리가 쉽다는 점이다. fork&join프레임워크로 작업을 병렬처리하는 것을 배웠는데, 병렬 스트림은 내부적으로 이 프레임워크를 이용해서 자동적으로 연산을 병렬로 수행한다. 우리가 할일이라고는 그저 스트림에 parallel()이라는 메서드를 호출해서 병렬로 연산을 수행하도록 지시하면 될 뿐이다.

반대로 병렬로 처리되지 않게 하려면 sequential()을 호출하면 된다. 모든 스트림은 기본적으로 병렬 스트림이 아니므로 sequential()을 호출할 필요가 없다. 이 메서드는 parallel()을 호출한 것을 취소할 때만 사용한다.


참고) parallel()과 sequential()은 새로운 스트림을 생성하는 것이 아니라, 그저 스트림의 속성을 변경할 뿐이다.


int sum = strStream.parallelStream() //strStream을 병렬 스트림으로 전환

.mapToInt(s -> s.length())

.sum();


앞서, 병렬처리가 항상 더 빠른 결과를 얻게 해주는것이 아니라는 것을 명심하자.


비트 연산자는 피연산자를 비트단위로 논리 연산한다. 피연산자를 이빈수로 표현했을 때의 각 자리를 아래의 규칙에 따라 수행 하면 피연산자 실수는 허용하지 않는다. 정수(문자포함)만 허용


| (OR연산자) : 피연산자 중 한 쪽의 값이 1이면, 1을 결과로 얻는다, 그 외에는 0을 얻는다.

& (AND연산자) : 피연산자 양쪽이 모두 1 이어야만 1을 결과로 얻는다. 그외에는 0을 얻는다.

^ (XOR연산자) : 피연산자의 값이 서로 다를 때만 1을 결과로 얻는다. 같을 때는 0을 얻는다. ( 1 ^ 1 = 0)


|(OR연산자)는 주로 특정 비트의 값을 변경할 때 사용한다. (0xAB | 0xF = 0xAF)

&(AND연산자)는 주로 특정 비트의 값을 뽑아 낼 때 사용한다.(0xAB & 0xF = 0xB)

^(XOR연산자)는 두 피연산자의 비트가 다를 때만 1이 된다. 그리고 같은 값으로 두고 XOR연산을 수행하면 원래의 값으로 돌아오는 특징이 있어서 간단한 암호화에 사용된다.



논리 연산자랑 다름 

||(OR결합) : 피연산자 중 어느 한 쪽만 true이면 true를 결과로 얻는다.

&&(AND결합) : 피연산자 양쪽 모두 true이어야 true를 결과로 얻는다.




조건 연산자 ? :

조건 연산자는 조건식? 식1 : 식2

true면 식1, false면 식2가 된다. 

조건연산자 쓰는 이유 if문보다 간략하다 조건 연산자를 중첩해서 사용하면 셋 이상 중의 하나를 결과로 얻을 수 있다. 

조건연산자를 여러 번 중첩하면 코드가 간략해지긴 하지만, 가독성이 떨어지므로 꼭 필요한 경우에 한 번 정도만 중첩하는 것이 좋다.

타입이 다른 경우 연산자처럼 산술 변환이 발생한다. double타입으로 통일

x = x + (mod <0.5? 0.0 : 0.5) 0이 0.0으로 변환되었다.


public class OperatorEx32 {
public static void main(String[] args) {
int x,y,z;
int absX,absY,absZ;
char signX, signY, signZ;

x=10;
y=-5;
z=0;

absX = x >= 0 ? x : -x; //x의 값이 음수이면, 양수로 만든다.
absY = y >= 0 ? y : -y;
absZ = z >= 0 ? z : -z;

signX = x > 0 ? '+' : (x==0 ? ' ' : '-');
signY = y > 0 ? '+' : (y==0 ? ' ' : '-');
signZ = z > 0 ? '+' : (z==0 ? ' ' : '-');

System.out.printf("x=%c%d%n", signX , absX);
System.out.printf("y=%c%d%n", signY , absY);
System.out.printf("z=%c%d%n", signZ , absZ);

}

x=+10

y=-5

z= 0



public class OperatorEx28 {
public static void main(String[] args) {
int x = 0xAB, y = 0xF;

System.out.printf("x = %#X \t\t%s%n", x, toBinaryString(x));
System.out.printf("y = %#X \t\t%s%n", y, toBinaryString(y));
System.out.printf("%#X ^ %#X = %#X %s%n", x, y, x ^ y, toBinaryString(x ^ y));
}


static String toBinaryString(int x) { //10진수 정수를 2진수로 변환하는 메서드
String zero = "0000000000000000000000000000";
String tmp = zero + Integer.toBinaryString(x);
return tmp.substring(tmp.length() - 32);
}
}

x = 0XAB 00000000000000000000000010101011

y = 0XF 00000000000000000000000000001111

0XAB ^ 0XF = 0XA4 00000000000000000000000010100100

+ Recent posts