자주 사용했던 코드

Stream과 Distinct를 활용한 중복제거

** List<Object>일 경우에는 Object의 hash가 다를 수 있으므로 distinct 하여도 중복된 값이 걸러지지 않을 수 있음.

private void checkDuplicated(List<Integer> list) {
  list = list.stream().distinct.sorted.collect(Collectors.toList());
  list.forEach(i -> System.out.print(i));
}

Map 내 Key, Value 체크가 필요할때

private void printKeyValue(Map<String, Object> map) {
   map.forEach((key, value) -> System.out.println(key + " : " + value + "\n"));
}

JAR

jar 란?

  • 일종의 자바 프로젝트 압축 파일
  • ZIP 파일 압축 알고리즘을 기반으로 만들어짐 >> 반디집, 알집과 같은 zip 프로그램과 호환 가능
  • JAR 파일은 웹브라우저에서 빠르게 다운로드할 수 있도록, 자바 애플릿을 위한 클래스, 이미지 및 사운드 파일들을 하나의 파일에 압축하여 담고 있는 파일이다.
  • 사용자의 요청에 의해 웹페이지의 일부로 들어오는 애플릿에는 여러개의 파일들이 담겨 있을 수 있는데, 각각은 웹페이지와 함께 다운로드 되어야 한다. 이 때 그 파일들을 하나의 파일에 압축하면 다운로드에 소요되는 시간이 절약된다.
  • 자바로 개발한 여러 클래스 파일들 또는 패키지 파일이 있을 때, 이를 하나로 묶으면 그 클래스들을 참조하기도 편하고, 다운 받기도 쉽다.
  • 자바 어플리케이션 프로그램을 개발 후 하나의 파일로 묶어서 실행하게 해준다.
  • JAR 로 묶어서 배포하게 되면, 경로나 파일의 위치에 상관없이 프로그램의 실행이 가능하다.

jar 옵션

-client

자바 HotSpot Client VM을 선택한다. (디폴트 값이다)

-server

자바 HotSpot Server VM을 선택한다.

-classpath (-cp)

참조할 클래스 파일 패스를 지정하는데, jar파일, zip파일, 클래스파일의 디렉터리 위치를 기술한다.

각 클래스파일 패스는 콜론(:)을 통해서, 분리시켜 기술한다.

자바VM은  자바프로그램을 로딩시, -classpath로 지정된 클래스 패스나, java플래폼이 설치된, 운영체제에서의 환경변수로 지정된,

클래스패스를 통해서, 클래스 파일들을 참조하게 된다.

-D <property name>=<property value>

시스템의 property 값을 설정한다.

ex) java -Djava.library.path=. HelloWorld

자바의 시스템 property(속성)중 “java.library.path”값을 “.” (현재디렉터리)로 지정해서, HelloWorld 실행시켜라는 의미 이다.

위와같이 자바VM에 지정된 속성을 실행시 -D옵션을 사용해서, 변경, 지정할수 있다.

-jar 파일이름

jar파일로 압축되어져 있는 자바 프로그램을 실행시킨다.

클래스 파일이름 대신 jar파일을 사용해서, 압축되어져 있는 자바 프로그램을 실행시킬수 있는데, 위프로그램이 제대로 실행되어지기

위해서, Jar파일안의 manifest라는 텍스트 파일에 Main-Class:classname 같은 형태의 텍스트 라인이 포함되어 있어야 한다.

그리고, 여기에 기술된 classname은 main함수를 포함하고 있는 클래스 이름이 되어야 한다.

-verbose

자바프로그램 실행되어지는 정보를 화면에 출력해준다.

-verbose:class

로딩되어지는 각클래스들의 정보를 화면에 출력한다.

-verbose:gc

garbage collection 이벤트를 화면에 출력한다.

-verbose:jni

native 함수들과 다른 자바 native 인터페이스 사용에 대한 정보를 출력한다.

-version

현재 JVM의 버젼 정보만 출력한다.

-showversion

현재 JVM의 버젼정보를 출력한다.

java -showversion HelloWolrd 와 같이 자바 프로그램을 실행시키면서, 자바 버젼정보를 출력할수 있다.

-X

비표준 자바옵션 리스트를 화면에 출력해준다.

-Xms, -Xmx

자바를 구동시, JVM이 사용가능한 최대 메모리 사이즈를 변경합니다.

JVM이 자바프로그램을 구동하기 위해, 초기설정된 메모리사이즈는 64M입니다.

사용 방법은 다음과 같습니다

java -Xms <초기힙사이즈> -Xmx <최대힙사이즈>

예를들어, Hello.class 자바 프로그램을 시작시, 256M(메가)의 힙사이즈를 할당하며, 최대 512M의 힙사이즈를 할당받고 싶다면, 

다음과 같이 합니다.

java -Xms256m -Xmx512m Hello

-XX:PermSize,  -XX:MaxPermSize

클래스 메타 정보 메모리 (Xms, Xmx 메모리와 별도로 관리된다.

-XX:PermSize=64m -XX:MaxPermSize=256m

intelli j 에서 jar 파일 생성

terminal 에서 jar 파일 생성

  1. 리눅스 환경에서 java(jar)를 데몬처럼 실행
    • java -jar jar파일명.jar
    • 사용자가 로그아웃시 프로그램 종료 됨
  2. 사용자가 로그아웃해도 백그라운드로 실행되게 하는 명령어
    • $ nohup java -jar jar파일명.jar
  3. 프로세스 종료
    • 찾기 : ps -ef | grep ‘jar파일명’
    • pid
      • sudo netstat -ltnp
    • 종료 : kill -9 (pid)

STREAM은 사용시 주의!

다량의 데이터 처리 작업을 지원하고자 자바8에 스트림 API가 추가되었다.
이 API가 제공하는 추상 개념 중 핵심은 두 가지다.

  • 스트림(stream)은 데이터 원소의 유한 혹은 무한 시퀀스(sequence)를 뜻한다.
  • 스트림 파이프라인(stream pipleline)은 이 원소들로 수행하는 연산 단계를 표현하는 개념이다.

스트림 파이프라인은 소스 스트림에서 시작해 종단 연산(terminal operation)으로 끝나며, 그 사이에 하나 이상의 중간 연산(intermediate operation)이 있을 수 있다.

각 중간 연산들은 스트림을 어떠한 방식으로 변환(transform)하여 다른 스트림으로 변환한다.

또한 스트림 파이프라인은 지연 평가(lazy evaluation)이 되며, 평가는 종단 연산이 호출될 때 이뤄진다. 종단 연산에 쓰이지 않는 데이터 원소는 계산이 쓰이지 않는다.

스트림을 제대로 사용하면 프로그램이 짧고 깔끔해지지만, 잘못 사용하면 읽기 어렵고 유지보수도 힘들어 진다.

예를 살펴보자 (이펙티브 자바 참고).

아래는 아나그램관련 프로그램이다.
⇒ 사전 파일에서 단어를 읽어 사용자가 지정한 문턱값(minGroupSize)보다 원소 수가 많은 아나그램그룹을 출력한다.

아나그램 : 단어나 문장을 구성하고 있는 문자의 순서를 바꾸어 다른 단어나 문장을 만드는 놀이

public class Anagrams {
    public static void main(String[] args) throws FileNotFoundException {
        File dictionary = new File(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);

        Map<String, Set<String>> groups = new HashMap<>();
        try(Scanner s = new Scanner(dictionary)){
            while(s.hasNext()){
                String word = s.next();
                groups.computeIfAbsent(alphabetize(word),
                        (unused) -> new TreeSet<>()).add(word);
            }
        }

        for(Set<String> group : groups.values()){
            if(group.size() >= minGroupSize)
                System.out.println(group.size() + " : " + group);
        }
    }

    private static String alphabetize(String s){
        char[] a = s.toCharArray();
        Arrays.sort(a);
        return new String(a);
    }
}

computeIfAbsent (참고)

이 메소드는 map 안에 키가 있는지 찾은 다음, 있으면 단순히 그 키에 매핑된 값을 반환한다.
키가 없으면 건네진 함수 객체를 계산하여 그 키에 해당하는 값으로 매핑한 후 계산된 값을 반환한다.

그러면 이제 위의 코드를 같은 일을 하는 스트림을 사용한 코드로 변경해보자. (과한 스트림을 사용)

public class AnagramsStream {
    public static void main(String[] args)throws IOException {
        Path dictionary = Paths.get(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);
        try(Stream < String > words = Files.lines(dictionary)) {
            words
                .collect(groupingBy(word - > word
                    .chars()
                    .sorted()
                    .collect(StringBuilder::new, (sb, c) -> sb.append((char)c), StringBuilder::append)
                    .toString()))
                .values()
                .stream()
                .filter(group - > group.size() >= minGroupSize)
                .map(group - > group.size() + " : " + group)
                .forEach(System.out::println);
        }
    }
}

위의 코드는 확실히 짧지만 읽기는 어렵다. 이처럼 스트림을 과용하면 프로그램을 읽거나 유지보수하기 어려워진다.

다행히 절충 지점이 있는데 아래와 같이 스트림을 적당히 사용하면 코드가 짧아질 뿐만아니라 명확해지기까지 한다.

public class AnagramsEasyStream {
    public static void main(String[] args)throws IOException {
        Path dictionary = Paths.get(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);
        try(Stream < String > words = Files.lines(dictionary)) {
            words
                .collect(groupingBy(word - > alphabetize(word)))
                .values()
                .stream()
                .filter(group - > group.size() >= minGroupSize)
                .forEach(group - > System.out.println(group.size() + ": " + group));
        }
    }
    private static String alphabetize(String s) {
        char[] a = s.toCharArray();
        Arrays.sort(a);
        return new String(a);
    }
}

결론

스트림을 처음 사용하게 되면 모든 반복문을 스트림으로 바꾸고 싶은 유혹이 일게 된다고 한다. (나 또한 그랬다.)

하지만 스트림으로 바꾸는 게 가능할지라도 코드 가독성과 유지보수 측면에서 손해를 볼 수 있기 때문에 기존 코드는 스트림을 사용하도록 리팩토링하되, 새 코드가 더 나아 보일때만 반영 해야 한다.

꼭 for문, stream을 써야하는 경우가 정해져 있는 것은 아니지만 특정상황에서 for문 또는 stream을 써야만하는 경우가 존재한다.

for문을 써야하는 경우(stream에서 할 수 없는 것들)

  • 코드블록안에서 지역변수를 수정해야할 때
    (람다에서는 final이거나 사실상 final인 변수만 읽을 수 있다. 지역변수를 수정하는 건 불가능하다.)
  • 중간에 return 문으로 메서드를 빠져나가는 경우나, break continue 문으로 블록 바깥의 반복문을 종료하거나 건너띄는 경우.

stream을 써야하는 경우(stream과 궁합이 맞는 경우)

  • 원소들의 시퀀스를 일관되게 변환한다.
  • 원소들의 시퀀스를 필터링한다.
  • 원소들의 시퀀스를 하나의 연산을 사용해 결합한다. (더하기, 연결하기, 최솟값 구하기 등)
  • 원소들의 시퀀스를 컬렉션에 모은다.
  • 원소들의 시퀀스에서 특정 조건을 만족하는 원소를 찾는다.
람다에서 매개변수 이름 지을때 주의!!

람다에서는 타입 이름을 자주 생략하므로 매개변수 이름을 잘 지어야 스트림 파이프라인의 가독성이 유지된다.