2020. 12. 25.

6주차 과제: 상속

https://github.com/whiteship/live-study/issues/6

목표

자바의 상속에 대해 학습하세요.

학습할 것 (필수)

  • 자바 상속의 특징
  • super 키워드
  • 메소드 오버라이딩
  • 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
  • 추상 클래스
  • final 키워드
  • Object 클래스


자바 상속의 특징

public class Vehicle {
    protected String brand = "Ford";

    public void honk() {
        System.out.println("Tuut, tuut!");
    }
}
// Vehicle 클래스를 상속받은 Car 클래스
class Car extends Vehicle {
    private String modelName = "Mustang";

    public static void main(String[] args) {
        Car myCar = new Car();

        // 부모 클래스의 메소드 사용
        myCar.honk();

        // 부모 클래스의 brand 변수 사용
        System.out.println(myCar.brand + " " + myCar.modelName);
    }
}
Tuut, tuut!
Ford Mustang

Process finished with exit code 0

코드 출처: https://www.w3schools.com/java/java_inheritance.asp

다른 클래스에서 파생된 클래스 → 하위 클래스(파생 클래스, 확장 클래스, 자식 클래스)

하위 클래스가 파생된 클래스 → 수퍼 클래스(기본 클래스, 부모 클래스)

수퍼 클래스가 없는 Object를 제외하고 모든 클래스에는 하나의 수퍼 클래스만 존재(단일상속)

수퍼 클래스가 명시돼 있지 않은 경우 모든 클래스는 암묵적으로 Object의 하위 클래스

사진, 내용 출처: https://docs.oracle.com/javase/tutorial/java/IandI/subclasses.html


public class Vehicle {
    public void honk() {}
}
class Car extends Vehicle {
    public static void main(String[] args) {
        Car myCar = new Car();
        myCar.honk();
    }
}
javap -c Vihicle.class

public com.testcompany.Vehicle();
  Code:
     0: aload_0
     1: invokespecial #1    // Method java/lang/Object."<init>":()V
     4: return

invokespecial - 인스턴스 메소드를 호출;
superclass, private, 인스턴스 초기화 메서드 호출을 특별히 다루기 위한.

내용 출처: https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokespecial

'invokespecial'은 'invokevirtual'과 다르게 객체의 클래스가 아닌 참조형 타입에 따라 메소드를 선택

다시 말해서 동적 바인딩 대신 정적 바인딩을 수행

'invokespecial'이 사용되는 세 가지 상황에서 동적 바인딩은 원하는 결과를 얻지 못합니다.

  1. 인스턴스 초기화(<init>) 메소드 호출
  2. private 메소드 호출
  3. super 카워드를 사용한 메소드 호출

<init> 메소드는 오직 새로운 인스턴스가 생성될 때 호출

새로 생성된 객체의 상속경로를 따라 각 클래스마다 하나 이상의 <init> 메소드가 호출됨

왜 'invokespecial'이 <init> 메소드 호출을 사용할까? subclass <init> 메소드는 superclass <init> 메소드를 호출할 수 있어야 하기 때문. 이것이 인스턴스화 된 객체가 많은 <init> 메소드를 호출하는 방법. 가상 머신은 object's 클래스에 선언된 <init> 메소드를 호출. <init> 메소드는 먼저 같은 클래스의 다른 <init> 메소드를 호출 or superclass의 <init> 메소드를 호출. 이 프로세스는 Object까지 계속 올라감.

class Superclass {
    private void interestingMethod() {
        System.out.println("Superclass's interesting method.");
    }

    void exampleMethod() {
        interestingMethod();
    }
}

class Subclass extends Superclass {
    void interestingMethod() {
        System.out.println("Subclass's interesting method.");
    }

    public static void main(String[] args) {
        Subclass me = new Subclass();
        me.exampleMethod();
        // 출력: Superclass's interesting method.
    }
}

Subclass에서 main()을 호출하면 "Superclass의 흥미로운 메소드"를 출력해야 함. 'invokevirtual'을 사용하면 "Subclass의 흥미로운 메소드"를 출력. WHY? 가상 머신은 객체의 실제 클래스인 Subclass 기반으로 interestingMethod()를 선택하기 때문.

반면 'invokespecial'을 사용하면 가상 머신이 참조 유형에 따라 메소드를 선택하므로 Superclass interestingMethod()를 호출.

'invokeinterface' opcode는 'invokevirtual'과 똑같이 작동. 단지 다른점이라면 'invokeinterface'는 인터페이스 타입일 때 사용.

호출 명령어와 속도

상상할 수 있듯이 인터페이스 참조가 메소드를 호출하는 것이 클래스 참조가 메소드를 호출하는 것보다 느릴 수 있다. 자바 가상 머신이 'invokevirtual' 명령어를 발견하고 인스턴스 메소드에 대한 직접 참조(direct reference)에 대한 기호 참조를 분석할 때 'direct reference'는 메소드 테이블에 대한 오프셋(offset;상대 주소)이 될 수 있다. 그 시점부터 동일한 오프셋을 사용할 수 있다. 그러나 'invokeinterface' 명령어의 경우 가상 머신은 오프셋이 이전 호출과 동일하다고 가정할 수 없기 때문에 명령어가 발생할 때마다 메소드 테이블을 검색해야 한다.

가장 빠른 명령어는 'invokespecial' 및 'invokestatic'일 가능성이 크다. 이러한 명령어에 의해 호출된 메소드는 정적으로 바인딩 되기 때문이다. JVM이 기호 참조를 해석하고 'direct reference'로 교체하면 실제 바이트 코드에 대한 포인터를 포함할 것이다.

구현 의존성

이러한 모든 속도 예측은 추측에 불과합니다. 자바 가상 머신 디자이너들이 기술을 사용해 속도를 높일 수 있기 때문입니다. 기호 참조 및 호출 메소드를 해결하기 위한 데이터 구조 및 알고리즘은 JVM 사양이 아닙니다. 이러한 결정은 JVM 설계자에게 맡겨집니다.

결론

  1. 인스턴스 메소드는 <init>메소드, private 메소드 및 super 키워드로 호출된 메소드를 제외하고 동적으로 바인딩 됩니다. 이 세 가지 특수한 경우 인스턴스 메소드는 정적으로 바인딩 됩니다.
  2. 클래스 메소드는 항상 정적으로 바인딩됩니다.
  3. 인터페이스 참조로 호출된 인스턴스 메소드는 객체 참조로 호출된 동일한 메소드보다 느릴 수 있습니다.

1997년 6월 JavaWorld에 실린 기사 입니다.

코드, 내용 출처: https://www.artima.com/underthehood/invocation.html


super 키워드

public class Superclass {

    public void printMethod() {
        System.out.println("Printed in Superclass.");
    }
}
public class Subclass extends Superclass {

    // overrides printMethod in Superclass
    public void printMethod() {
        super.printMethod();
        System.out.println("Printed in Subclass");
    }
    public static void main(String[] args) {
        Subclass s = new Subclass();
        s.printMethod();
        // 출력
        // Printed in Superclass.
        // Printed in Subclass
    }
}

메소드가 수퍼 클래스의 메소드 중 하나를 재정의하는 경우 super 키워드를 사용하여 재정의된 메소드를 호출할 수 있음

출처: https://docs.oracle.com/javase/tutorial/java/IandI/super.html


메소드 오버라이딩

인스턴스 메소드

동일한 시그니쳐(이름, 매개 변수의 개수 및 타입)와 리턴 타입을 가진 서브 클래스의 인스턴스 메소드는 슈퍼 클래스의 메소드를 재정의

메소드를 재정의할 때 컴파일러에 superclass의 메소드를 재정의할 것을 지시하는 @Override 주석(annotation)을 사용. 어떤 이유로 컴파일러가 superclass 메소드 중 하나가 존재하지 않음을 감지하면 오류를 생성.

Static 메소드

만약 subclass가 static 메소드이며 superclass의 static 메소드와 동일한 시그니처를 사용하면 subclass의 메소드는 superclass의 메소드를 숨김.

정적 메소드를 숨기는 것과 인스턴스 메소드를 오버라이딩(재정의)하는 것의 차이점은 다음과 같은 중요한 의미가 있다.

  • 호출하는 오버라이딩 인스턴스 메소드는 subclass 버전이다.
  • 호출하는 숨겨진 static 메소드의 버전은 superclass에서 호출되는지 subclass에서 호출되는지에 따라 다르다.
public class Animal {
    public static void testClassMethod() {
        System.out.println("The static method in Animal");
    }
    public void testInstanceMethod() {
        System.out.println("The instance method in Animal");
    }
}
public class Cat extends Animal {
    public static void testClassMethod() {
        System.out.println("The static method in Cat");
    }
    public void testInstanceMethod() {
        System.out.println("The instance method in Cat");
    }

    public static void main(String[] args) {
        Cat myCat = new Cat();
        Animal myAnimal = myCat;
        Animal.testClassMethod();
        myAnimal.testInstanceMethod();
        // 출력
        // The static method in Animal
        // The instance method in Cat
    }
}

Cat 클래스는 Animal 인스턴스 메소드를 재정의하고 Animal의 static 메소드를 숨긴다.

호출된 static 메소드는 superclass에 있는 버전이고 오버라이드된 인스턴스 메소드는 subclass 버전이다.

출처: https://docs.oracle.com/javase/tutorial/java/IandI/override.html


다이나믹 메소드 디스패치 (Dynamic Method Dispatch)

동적 메소드 디스패치는 컴파일 타임이 아닌 런타임에 오버라이드된 메소드에 대한 호출이 해결되는 메커니즘

출처: https://www.geeksforgeeks.org/dynamic-method-dispatch-runtime-polymorphism-java/

오버라이드된 메소드가 superclass 참조를 통해 호출되면 Java는 호출이 발생할 때 참조되는 객체의 유형에 따라 해당 메소드의 버전(수퍼 클래스 / 서브 클래스)이 실행될 것인지 결정

런타임시 실행될 재정의 된 메소드 버전을 결정하는 것은 참조되는 개체의 유형 (참조 변수 유형이 아님)에 따라 다름

superclass 참조 변수는 subclass 객체를 참조 할 수 있다. 이를 업 캐스팅이라고도 한다. Java는이 사실을 사용하여 런타임에 재정의 된 메서드에 대한 호출을 해결한다.


추상 클래스

추상(abstract) 클래스는 추상적으로 선언된 클래스로 추상 메소드를 포함하거나 포함하지 않을 수 있다.

추상 클래스는 인스턴스화할 수 없지만 subclass화할 수 있다.

abstract void moveTo(double deltaX, double deltaY);
// 클래스에 추상 메소드가 포함된 경우 클래스는 다음과 같이 선언
public abstract class GraphicObject {
   // declare fields
   // declare nonabstract methods
   abstract void draw();
}

추상 클래스가 subclass화될 때 일반적으로 부모 클래스의 추상 메소드에 대한 구현을 제공. 하지만 그렇지 않은 경우에는 subclass도 반드시 추상화 선언을 해야 함.

default or static으로 선언되지 않은 인터페이스의 메소드는 암시적으로 추상이므로 추상 수정자는 인터페이스 메소드와 함께 사용되지 않음. (사용할 수 있지만 불필요)

출처: https://docs.oracle.com/javase/tutorial/java/IandI/abstract.html


final 키워드

class ChessAlgorithm {
    enum ChessPlayer {WHITE, BLACK}
    ...
    final ChessPlayer getFirstPlayer () {
        return ChessPlayer.WHITE;
    }
    ...
}

메소드 선언에서 final 키워드를 사용해 subclass로 재정의할 수 없음을 나타냄. 'Object' 클래스가 이 작업을 수행하며 많은 메소드가 final이다.

변경되면 안되고 객체의 일관성이 매우 중요한 경우에 사용. ex) ChessAlgorithm 클래스의 getFirstPlayer 메소드를 final로 만들 수 있다.

생성자에서 호출된 메소드는 일반적으로 final로 선언되어야 함. 생성자가 non-final 메소드를 호출하면 subclass가 해당 매소드를 바람직하지 않은 결과로 재정의할 수 있다.

전체 클래스를 final로 선언할 수도 있다. final로 선언된 클래스는 subclass화할 수 없다. 예를 들어 'String' 클래스와 같은 불변의 클래스를 만들 때 특히 유용함.

출처: https://docs.oracle.com/javase/tutorial/java/IandI/final.html


Object 클래스

Object 클래스를 제외한 클래스에는 정확히 하나의 direct superclass가 있다. 클래스는 직접이든 간접적이든 상관없이 모든 superclass의 필드와 메소드를 상속. subclass는 상속하는 메소드를 재정의하거나 상속하는 필드 or 메소드를 숨길 수 있다. (필드를 숨기는 것은 일반적으로 나쁜 프로그래밍 관행입니다.)

Object 클래스는 클래스 계층 구조의 맨 위에 있다. 모든 클래스는 이 클래스의 자손이며 메소드를 상속한다. Object에서 상속받은 유용한 메소드에는 toString(), equals(), clone() 및 getClass()가 있다.

출처: https://docs.oracle.com/javase/tutorial/java/IandI/summaryinherit.html

더 읽어보기: https://opentutorials.org/course/1223/6241

2020. 12. 18.

5주차 과제: 클래스

https://github.com/whiteship/live-study/issues/5

목표

자바의 Class에 대해 학습하세요.

학습할 것 (필수)

  • 클래스 정의하는 방법
  • 객체 만드는 방법 (new 키워드 이해하기)
  • 메소드 정의하는 방법
  • 생성자 정의하는 방법
  • this 키워드 이해하기

과제 (Optional)

  • int 값을 가지고 있는 이진 트리를 나타내는 Node 라는 클래스를 정의하세요.
  • int value, Node left, right를 가지고 있어야 합니다.
  • BinrayTree라는 클래스를 정의하고 주어진 노드를 기준으로 출력하는 bfs(Node node)와 dfs(Node node) 메소드를 구현하세요.
  • DFS는 왼쪽, 루트, 오른쪽 순으로 순회하세요.


클래스 정의하는 방법

class MyClass {
    // UpperCarmelCase를 사용
    // 클래스 이름은 일반적으로 명사 또는 명사구
    // 때로는 형용사 형용사구 예) Readable
    // 테스트 클래스의 경우 마지막에 Test로 끝나도록
    // 예) HashTest
}
class MyClass extends MySuperClass implements YourInterface {
    // MySuperclass의 하위 클래스이고 
    // YourInterface 인터페이스의 구현을 표시
}
private class MyClass {
    // 접근 제어자
    // private -> default -> protected -> public
    // 순으로 보다 많은 접근을 허용
}

참고: https://google.github.io/styleguide/javaguide.html#s5.2.2-class-names


객체 만드는 방법 (new 키워드 이해하기)

// MyClass.java file
public class MyClass {
}
// Main.java file
public class Main {
    public static void main(String[] args) {
        MyClass mc = new MyClass();
    }
}
javap -c Main.class

public static void main(java.lang.String[]);
Code:
 0: new           #7  // class com/company/MyClass
 3: dup
 4: invokespecial #9  // Method com/company/MyClass.<init>:()V
 7: astore_1
 8: return

#0 new - 상수 풀(constant pool) 인덱스에서 클래스 참조(reference)로 식별되는 새로운 객체를 생성

#3 dup - 스택의 가장 위의 값을 복제

인스턴스화

new 키워드는 객체를 생성하는 Java 연산자입니다.

초기화

new 연산자 다음에 생성자 호출이 이어지며 이는 새 객체를 초기화합니다.

출처: https://docs.oracle.com/javase/tutorial/java/javaOO/objectcreation.html


메소드 정의하는 방법

public double calculateAnswer (double wingSpan,
    int numberOfEngines, double length, double grossTons) {
    // 여기서 계산
}

메서드 선언에는 순서대로 6개의 구성 요소가 있습니다.

1. 수정자
예) public, private

2. 반환 유형
메소드에서 반환한 값의 데이터 유형 또는 void(메소드가 값을 반환하지 않는 경우).

3. 메소드 이름
lowerCamelCase로 작성
일반적으로 동사, 동사구 예) sendMessage, stop
JUnit 테스트 메소드 이름에 밑줄이 표시되어 논리적 구성 요소를 구분할 수 있으며 각 구성 요소는 lowerCamelCase로 작성
전형적인 패턴 예시 <methodUnderTest>_<state>, pop_emptyStack
테스트 이름을 정하는 올바른 방법은 없습니다.
출처: https://google.github.io/styleguide/javaguide.html#s5.2.3-method-names

4. 괄호 안의 매개 변수 목록
쉼표로 구분된 입력 매개 변수 목록으로 데이터 유형이 앞에 있고 괄호()로 묶여 있습니다.
매개 변수가 없으면 빈 괄호를 사용.

5. 예외 리스트

6. 중괄호로 묶인 메서드 본문
지역 변수 선언을 포함하여 메소드의 코드가 여기에 표시됩니다.

출처: https://docs.oracle.com/javase/tutorial/java/javaOO/methods.html


생성자 정의하는 방법

public class Main {
    int x;

    // 클래스 생성자 생성
    public Main() {
        x = 5;
    }

    public static void main(String[] args) {
        Main myObj = new Main();
        // 메인 클래스 객체를 선언하면서 생성자를 호출
    }
}

생성자 생성 규칙

1. 클래스명과 메소드명이 동일하다.

2. 리턴타입을 정의하지 않는다.


this 키워드 이해하기

public class Point {
    public int x = 0;
    public int y = 0;
        
    // 생성자
    public Point(int a, int b) {
        x = a;
        y = b;
    }
}
public class Point {
    public int x = 0;
    public int y = 0;

    // 생성자
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

인스턴스 메소드 or 생성자 내에서 현재 사용하고 있는 객체에 대한 참조

this 키워드를 사용하는 가장 일반적인 이유는 필드(Field: 클래스 영역 안의 변수)가 메소드 or 생성자 매개 변수로 음영 처리되기 때문

this 키워드를 사용하여 동일한 클래스의 다른 생성자를 호출할 수 있습니다. 이를 명시적 생성자 호출(explicit constructor invocation)이라고 합니다.

public class Rectangle {
    private int x, y;
    private int width, height;

    public Rectangle() {
        this(0, 0, 1, 1);
    }
    public Rectangle(int width, int height) {
        this(0, 0, width, height);
    }
    public Rectangle(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }
    ...
}

출처: https://docs.oracle.com/javase/tutorial/java/javaOO/thiskey.html


과제

아직 작성 중입니다.

public class Node {

    // int 값을 가지고 있는 이진 트리를 나타내는
    // Node 라는 클래스를 정의하세요.

    // int value, Node left, right 를 가지고 있어야 합니다.
    private int value;
    private Node left;
    private Node right;

    public Node(int value) {
        this.value = value;
        this.left = null;
        this.right = null;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public Node getRight() {
        return right;
    }

    public void setRight(Node right) {
        this.right = right;
    }

    public Node getLeft() {
        return left;
    }

    public void setLeft(Node left) {
        this.left = left;
    }
}
public class BinaryTree {
    // BinrayTree라는 클래스를 정의하고 주어진 노드를
    // 기준으로 출력하는 bfs(Node node)와 dfs(Node node)
    // 메소드를 구현하세요.
    // DFS는 왼쪽, 루트, 오른쪽 순으로 순회하세요.

    // Breadth First Search (또는 Level Order Traversal)
    // : 너비우선탐색
    // Depth First Search : 깊이우선탐색

    // 트리의 루트
    Node root;

    // 기본 생성자에서 루트 값 초기화
    BinaryTree() {
        root = null;
    }

    void insertNode(int value) {
        root = insertRec(root, value);
    }

    Node insertRec(Node root, int value) {

        if (root == null) {
            root = new Node(value);
            return root;
        }

        if (value < root.getValue()) {
            root.setLeft(insertRec(root.getLeft(), value));
        } else if (value > root.getValue()) {
            root.setRight(insertRec(root.getRight(), value));
        }

        return root;
    }

}




2020. 12. 4.

4주차 과제: 제어문

https://github.com/whiteship/live-study/issues/4

목표

자바가 제공하는 제어문을 학습하세요.

학습할 것

선택문

반복문

과제

과제 0. JUnit 5 학습하세요.

  • 인텔리J, 이클립스, VS Code에서 JUnit 5로 테스트 코드 작성하는 방법에 익숙해 질 것.

과제 1. live-study 대시 보드를 만드는 코드를 작성하세요.

  • 깃헙 이슈 1번부터 18번까지 댓글을 순회하며 댓글을 남긴 사용자를 체크 할 것.
  • 참여율을 계산하세요. 총 18회에 중에 몇 %를 참여했는지 소숫점 두자리가지 보여줄 것.
  • Github 자바 라이브러리를 사용하면 편리합니다.
  • 깃헙 API를 익명으로 호출하는데 제한이 있기 때문에 본인의 깃헙 프로젝트에 이슈를 만들고 테스트를 하시면 더 자주 테스트할 수 있습니다.

과제 2. LinkedList를 구현하세요.

  • LinkedList에 대해 공부하세요.
  • 정수를 저장하는 ListNode 클래스를 구현하세요.
  • ListNode add(ListNode head, ListNode nodeToAdd, int position)를 구현하세요.
  • ListNode remove(ListNode head, int positionToRemove)를 구현하세요.
  • boolean contains(ListNode head, ListNode nodeTocheck)를 구현하세요.

과제 3. Stack을 구현하세요.

  • int 배열을 사용해서 정수를 저장하는 Stack을 구현하세요.
  • void push(int data)를 구현하세요.
  • int pop()을 구현하세요.

과제 4. 앞서 만든 ListNode를 사용해서 Stack을 구현하세요.

  • ListNode head를 가지고 있는 ListNodeStack 클래스를 구현하세요.
  • void push(int data)를 구현하세요.
  • int pop()을 구현하세요.

(optional) 과제 5. Queue를 구현하세요.

  • 배열을 사용해서 한번
  • ListNode를 사용해서 한번.


선택문

if (조건){
  // 조건이 참(true)이면 블록 내 코드를 실행
}
if (조건){
  // 조건이 참이면 이 블록 내 코드를 실행
} else {
  // 조건이 거짓(false)이면 이 블록 내 코드를 실행
}
if (조건1){
  // 조건1이 참이면 블록내 코드를 실행
} else if (조건2) {
  // 조건1이 거짓이고 조건2가 참이면 이 블록 내 코드를 실행
} else {
  // 조건1 조건2가 모두 거짓일 경우 이 블록 내 코드를 실행
}
switch (조건 값) {
  case 값1:
    // 조건 값 = 값1일 때 실행하고자 하는 명령문
    break;
  case 값2:
    // 조건 값 = 값2일 때 실행하고자 하는 명령문
    break;
  default:
    // 조건이 어떠한 case 값에도 해당하지 않을 때
    // 실행하고자 하는 명령문
    break;
}


반복문

while (조건문) {
  // 조건이 참일 경우 반복하고자 하는 명령문
}
do {
  // do / while 문은 먼저 루프를 한 번 실행한 후 조건식을 확인
} while (조건문)
for (초기식; 조건식; 증감식) {
  // 조건식의 결과가 참인 동안 반복적으로 실행하고자 하는 명령문
}


과제 0. JUnit 5 학습하세요.

참고: JUnit5 사용자 가이드

https://junit.org/junit5/docs/current/user-guide/

JUnit 5란?

JUnit 5 = JUnit 플랫폼 + JUnit Jupiter + JUnit 빈티지

JUnit 플랫폼: JVM 위에서 프레임워크 테스트를 위한 기초역할. TestEngine Api를 제공.

JUnit 쥬피터: 테스트 작성 및 확장을 위한 새로운 프로그래밍 모델과 확장 모델의 조합. 쥬피터 베이스 테스트를 구동하기 위한 테스트엔진.

JUnit 빈티지: JUnit 3와 JUnit 4 기반 테스트 플랫폼 실행을 위한 테스트엔진.


JUnit 5은 Java 8 이상이 필요

IntelliJ 실행 → 'New Project' or 상단 메뉴에서 'File' → 'New Project'

JUnit 5 종속성(dependency) 추가를 위해 Maven이나 Gradle 선택

Gradle:
https://docs.gradle.org/current/userguide/java_testing.html#using_junit5

Maven:
https://maven.apache.org/surefire/maven-surefire-plugin/examples/junit-platform.html

test {	// JUnit 플랫폼 테스트를 위해 추가
    useJUnitPlatform()
}
dependencies {	// JUnit 쥬피터 종속성 추가
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
}
종속성을 추가하면 인텔리j에서 친절하게 Gradle 변화를 감지하고 로드할 것인지 물어본다.


처음 시작을 위한 gradle 예시 코드
https://github.com/junit-team/junit5-samples/tree/r5.7.0/junit5-jupiter-starter-gradle

main/java → class 'Calculator' 작성

public class Calculator {
  public int add(int a, int b) {
    return a + b;
  }
}

test/java → 테스트 코드 작성. 단축키 클래스 내에서 'Ctrl+Shift+T' 참고 영상

import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;

class MyFirstJUnitJupiterTests {
  private final Calculator calculator = new Calculator();
  @Test
  void addition() {
    assertEquals(2, calculator.add(1, 1));
  }
}

주석 (Annotations)

@Test
// 테스트 메소드임을 나타냄

@ParameterizedTest
// 파라미터화 테스트 메소드(각각 다른 arguments를 테스트)

@RepeatedTest
// 반복 테스트 템플릿

@TestFactory
// 동적 테스트를 위한 테스트 팩토리 메소드

@TestTemplate
// 등록된 providers가 반환한 

@TestMethodOrder
// 주석이 달린 테스트 클래스의 테스트 메소드 실행 순서를
// 구성하기 위해 사용

@TestInstance
// 주석이 달린 테스트 클래스에서 테스트 인스턴스 수명주기를
// 구성하는데 사용

@DisplayName
// 테스트 클래스 or 테스트 메소드에 사용자 지정 이름을 선언

@DisplayNameGeneration
// 테스트 클래스에 대한 사용자 지정 이름 생성기를 선언

@BeforeEach
// @Text, @RepeatedTest, @ParameterizedTest, or @TestFactory
// 각각 메소드보다 먼저 실행되어야 함을 표시

@AfterEach
// @Text, @RepeatedTest, @ParameterizedTest, or @TestFactory
// 각각 메소드 이후에 실행되어야 함을 표시

@BeforeAll
// @Text, @RepeatedTest, @ParameterizedTest, or @TestFactory
// 모든 메소드보다 먼저 실행 되어야 함을 표시

@AfterAll
// @Text, @RepeatedTest, @ParameterizedTest, or @TestFactory
// 모든 메소드 이후에 실행 되어야 함을 표시

@Nested
// 주석이 달린 클래스가 정적이 아닌 중첩 클래스임을 나타냄

@Tag
// 메소드 or 클래스 레벨에서 테스트 필터링을 위한
// 태그 선언을 위해 사용

@Disabled
// 테스트 클래스 or 메소드를 비활성화하는 데 사용

@Timeout
// 테스트, 테스트 팩토리, 테스트 템플릿 or 
// 라이프싸이클 메소드의 실행 시간 초과 실패에 사용

@ExtendWith
// 확장 선언(클래스, 매소드)등을 등록하는데 사용

@RegisterExtension
// 프로그래밍 적으로 확장성을 등록하는데  사용

@TempDir
// 라이프사이클 메소드, 테스트 메소드의 매개 변수, 필드 주입을 위한
// 임시 폴더 사용을 위해 선언


과제 1. live-study 대시 보드를 만드는 코드를 작성하세요.

https://github.com/Sungjun-HQ/DashboardReader/tree/master/src

class DashboardReaderTest {

    @Test
    void getGHRepositoryTest() throws IOException {

        DashboardReader dashboardReader = new DashboardReader();

        // 접속한 github에서 태스트용 저장소로 이동
        GHRepository repo = dashboardReader.connectGH()
        	.getRepository("whiteship/live-study/");

        // 저장소에서 이슈 상태가 OPEN (ALL, CLOSED)
        List list = repo.getIssues(GHIssueState.ALL);
//        List list = repo.getIssues(GHIssueState.OPEN);

        assertTrue(list.size() > 0);

        Map map1 = new HashMap<>();

        for (GHIssue ghIssue : list) {
            GHUser user;
            List commentsList = ghIssue.getComments();

            if (commentsList.size() > 0) {
                for (GHIssueComment comment : commentsList) {
                    user = comment.getUser();

                    // 유저의 로그인 id 가져오기
                    if (map1.get(user.getLogin()) == null) {
                        map1.put(user.getLogin(), 1);
                    } else {
                        map1.put(user.getLogin(), map1.get(user.getLogin()) + 1);
                    }
                }
            }
        }
        // map1 소수점 출력
        for (String i : map1.keySet()) {
            float progressFloat = map1.get(i);
            progressFloat = progressFloat / 18 * 100;
            System.out.print("name: " + i);
            System.out.println(", progress: " + String.format("%.2f", progressFloat));
        }
    }
}

출력

Starting Gradle Daemon...
Gradle Daemon started in 1 s 264 ms

> Task :compileJava UP-TO-DATE
> Task :processResources NO-SOURCE
> Task :classes UP-TO-DATE
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses
> Task :test
name: a, progress: 16.67
name: b, progress: 11.11
name: c, progress: 16.67
name: d, progress: 16.67


과제 2. LinkedList를 구현하세요.






2020. 11. 27.

3주차 과제: 연산자

https://github.com/whiteship/live-study/issues/3

목표

자바가 제공하는 다양한 연산자를 학습하세요.

학습할 것

  • 산술 연산자
  • 비트 연산자
  • 관계 연산자
  • 논리 연산자
  • instanceof
  • assignment(=) operator
  • 화살표(->) 연산자
  • 3항 연산자
  • 연산자 운선 순위
  • (optional) Java 13. switch 연산자


1. 산술 연산자

public static void main(String[] args) {
    int a = 1;
    int b = 2;
    int c = a + b;
}
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: istore_3
8: return

출처: https://dzone.com/articles/introduction-to-java-bytecode

iconst_1: 정수 상수(integer constant) 1을 피연산자 스택(operand stack)으로 푸시

istore_1: 최상위 피연산자(int 값)를 pop하고 인덱스 1 지역 변수 a에 저장

iconst_2: 정수 상수 2를 피연산자 스택으로 푸시

istore_2: 최상위 피연산자 int 값을 pop하고 인덱스 2 지역 변수 b에 저장

iload_1: 인덱스 1의 지역 변수에서 int 값을 로드하고 피연산자 스택으로 푸시

iload_2: 인덱스 1의 지역 변수에서 int 값을 로드하고 피연산자 스택으로 푸시

iadd: 피연산자 스택에서 상위 두 개의 int 값을 가져와 추가한 다음 결과를 다시 피연산자 스택으로 푸시
istore_3: 최상위 피연산자 int값을 pop하고 인덱스 3 로컬 변수 c에 저장

return: void 메서드에서 반환


public class Main {
  public static void main(String[] args) {
    int a = 1;
    int b = 2;
    int c = a - b;
  }
}

javap -c Main.class 실행 결과

0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: isub		// int 뺄셈
7: istore_3
8: return


곱셈을 해보자

public static void main(String[] args) {
  int a = 1;
  int b = 2;
  int c = a * b;
}
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: imul		// 두 정수 곱셈
7: istore_3
8: return


나눗셈

public static void main(String[] args) {
  int a = 3;
  int b = 4;
  int c = a / b;
}
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: idiv		// 두 정수 나눗셈
7: istore_3
8: return


나머지 연산자도 써보자

public static void main(String[] args) {
  int a = 3;
  int b = 4;
  int c = a % b;
}
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: irem		// 논리적 정수 나머지(logical int remainder)
7: istore_3
8: return


2. 비트 연산자

public static void main(String[] args) {
  int a = 2;
  int c = a << 3; // 출력 16
  c = c >> 4;     // 출력 1
  c = c >>> 1;    // 출력 0
}
 0: iconst_2
 1: istore_1
 2: iload_1
 3: iconst_3
 4: ishl      // 정수를 왼쪽으로 비트 이동(빈자리는 0으로)
 5: istore_2
 6: iload_2
 7: iconst_4
 8: ishr      // 정수를 오른쪽으로 비트 이동(빈자리는 정수 최상위 부호비트와 같은 값으로)
 9: istore_2
10: iload_2
11: iconst_1
12: iushr     // 정수를 오른쪽으로 비트 이동(빈자리는 0으로)
13: istore_2
14: return

예시의 2개 변수가 너무 많은 거 같아 줄여보니 컴퓨터님이 계산하실 게 1줄이라도 더 줄어들어 흐뭇하다. 갑은 컴퓨터님 을은 나.

public static void main(String[] args) {
  int a = 2;
  a = a | 4;  // 110 출력 6
  a = a & 5;  // 5(101) 출력 4(100)
  a = a ^ 3;  // 3(011) 출력 7(111)
  a = ~a;     // 출력 -8
}
 0: iconst_2
 1: istore_1
 2: iload_1
 3: iconst_4
 4: ior         // or 연산
 5: istore_1
 6: iload_1
 7: iconst_5
 8: iand        // and 연산
 9: istore_1
10: iload_1
11: iconst_3
12: ixor        // xor 연산
13: istore_1
14: iload_1
15: iconst_m1   // 정수를 스택에 -1 정수값으로 로드
16: ixor        // xor 연산
17: istore_1
18: return


3. 관계 연산자

public static void main(String[] args) {
  int a = 2;
  int b = 3;
  boolean chk = a > b;
}

컴파일 후 javap -c Main.class로 확인

 0: iconst_2
 1: istore_1
 2: iconst_3
 3: istore_2
 4: iload_1
 5: iload_2
 6: if_icmple    13
 9: iconst_1
10: goto         14
13: iconst_0
14: istore_3
15: return

6번의 조건이 실패하면 다음 9번의 라인으로, 성공하면 13번의 라인으로 이동
goto는 14번으로 이동

7번 8번은 어디갔지?
찾아보니 'if_icmple', 'goto' 두 명령어가 다음 2개의 브랜치바이트(branchbyte)를 사용해 생략하고 보여준 거 같다.

기호는 '>' 이지만 코드는 'value1 ≤ value2'를 확인하는 'if_icmple"가 입력되었다.

나머지 관계 연산자( <, >=, <=, ==, != )도 순서대로 if_icmpge (value1 ≥ value2), if_icmplt (value1 < value2), if_icmpgt (value1 > value2), if_icmpne (value1 ≠ value2), if_icmpeq (value1 = value2) 바이트코드가 생성되었다.


4. 논리 연산자

public static void main(String[] args) {
  int a = 3;
  int b = 5;
  boolean chk = (b > a) && (a > b);
}

컴파일 후 javap -c -v Main.class
이번에는 -v(verbose) 명령어도 추가해 보았다.

stack=2, locals=4, args_size=1
 0: iconst_3
 1: istore_1
 2: iconst_5
 3: istore_2
 4: iload_2
 5: iload_1
 6: if_icmple     18
 9: iload_1
10: iload_2
11: if_icmple     18
14: iconst_1
15: goto          19
18: iconst_0
19: istore_3
20: return

위 코드에서 &&(and)를 ||(or)로 변경

int a = 3;
int b = 5;
boolean chk = (b > a) || (a > b);
stack=2, locals=4, args_size=1
 0: iconst_3
 1: istore_1
 2: iconst_5
 3: istore_2
 4: iload_2
 5: iload_1
 6: if_icmpgt     14
 9: iload_1
10: iload_2
11: if_icmple     18
14: iconst_1
15: goto          19
18: iconst_0
19: istore_3
20: return

6번 라인의 'if_icmple'(≤)가 'if_icmpgt'(>)로 변하고 성공 시 옮겨갈 라인 번호가 바뀌었다.

OR 연산은 두 개 중 하나의 조건만 맞아도 성공이기에 빠르게 14번 라인으로 가고자 하고 AND 연산은 반대 조건을 확인 후 실패(사실은 진실) 시 뒤에 오는 조건을 또 확인


int a = 5;
int b = 6;
boolean chk = !(a > b);
stack=2, locals=4, args_size=1
 0: iconst_5
 1: istore_1
 2: bipush        6
 4: istore_2
 5: iload_1
 6: iload_2
 7: if_icmpgt     14
10: iconst_1
11: goto          15
14: iconst_0
15: istore_3
16: return

!(부정) 연산자는 청개구리 마냥 if_icmpgt (value1 > value2)


5. instanceof

public static void main(String[] args) {
  String s = "hello";
  boolean chk = s instanceof java.lang.String;
}
stack=1, locals=3, args_size=1
0: ldc           #2                  // String hello
2: astore_1
3: aload_1
4: instanceof    #3                  // class java/lang/String
7: istore_2
8: return

ldc - 상수 풀(constant pool)에서 #index 상수를 스택으로 push
astore_1 - 지역 변수 1에 레퍼런스 저장
aload_1 - 스택위의 지역 변수 1번의 레퍼런스 로드

instanceof - 상수 풀의 참조(reference) 인덱스를 확인해 같은 타입인지 확인.
unsigned indexbyte1, indexbyte2를 사용 (indexbyte1 << 8 | indexbyte2)

자바 바이트코드 위키를 참고하면 ldc 항목에 constant pool (String, int, float, Class, java.lang.invoke.MethodType, java.lang.invoke.MethodHandle, or a dynamically-computed constant)에 대한 설명이 적혀있다. 이를 참조하고 비교해 같은 타입인지를 확인하는 거 같다.

풀이라고 하니까 수영장에서 헤엄치고 있는 오브젝트들이 상상된다.


6. assignment(=) operator

public static void main(String[] args) {
  int a = 2;
  a += a;    // 4 출력
}
stack=2, locals=2, args_size=1
0: iconst_2
1: istore_1
2: iload_1
3: iload_1
4: iadd
5: istore_1
6: return

4번라인에서 2번 3번에서 로드한 a 변수를 더하고 저장

int a = 2;
a =+ a; // 2 출력
stack=1, locals=2, args_size=1
0: iconst_2
1: istore_1
2: iload_1
3: istore_1
4: return

=+ 연산자로 바꾼 후 확인해 보니 a에 a값을 할당만 한다.
- 기호로 바꾸면 어떻게 되나 궁금하다.

public static void main(String[] args) {
  int a = 2;
  a =- a;    // -2 출력
}
stack=1, locals=2, args_size=1
0: iconst_2
1: istore_1
2: iload_1
3: ineg		// negate int
4: istore_1
5: return

이번에는 -2 값으로 출력된다. (코드에서 출력은 바이트 코드가 길어져서 출력을 확인만 하고 생략 후 컴파일했다.)

public static void main(String[] args) {
  int a = 8;
  a /= 2;	// 출력 4
}
stack=2, locals=2, args_size=1
0: bipush        8
2: istore_1
3: iload_1
4: iconst_2
5: idiv
6: istore_1
7: return

int a 값을 높여보니 bipush 새로운 바이트 코드가 생성됐다.
표에서 찾아보니 바이트를 정수 값으로 스택에 푸시하는 명령어

이후 모든 대입 연산자 (%=, &=, |=, ^=, <<=, >>=, >>>=)를 넣고 바이트 코드를 확인해 보니 위에서 확인한 산술, 비트, 논리연산자에서 나온 바이트 코드와 일치해 생략


7. 화살표(->) 연산자

public static void main(String[] args) {
  Runnable r2 = () -> System.out.println("Howdy, world!");
  r2.run();
}

출처: https://www.oracle.com/technical-resources/articles/java/architect-lambdas-part1.html

stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1                  // Method java/lang/Object."":()V
4: return

invokespecial: 객체 objectref에서 인스턴스 매소드를 호출하고 결과를 스택에 넣습니다.

오라클 람다 설명 문서에서 예시로 바이트 코드로 보기 편한 코드를 가져와 확인해 보았습니다. 설명에는 '그러나 내부적으로 Java 8 버전은 Runnable 인터페이스 상속과 익명 클래스 생성하는 것 이상의 작업을 수행하고 있습니다. 이 중 일부는 Java 7에 도입된 동적 바이트 코드 호출과 관련이 있습니다. 여기서 세부 디테일을 설명하지 않겠지만 단순한 익명 클래스 인스턴스 그 이상이란 걸 알아야 합니다'

Java8 버전부터 지원.
(@FunctionalInterface) 함수가 하나만 존재하는 Interface를 선언하고 간결하게 사용하고 구현

자바스크립트에서는 종종 써봤지만, 자바에서는 인터페이스 사용 같은 제약? 사항을 두고 사용해 정확한 적재요소에서 사용이 가능하도록 도움을 주는 느낌을 받았다. 조금이라도 사용을 해보던가 예시 작성을 통해 익숙해지는 작업이 많이 필요할 것으로 예상


8. 3항 연산자

public static void main(String[] args) {
  int x = 7;
  int y = 2;
  int a = (x < y) ? 30 : 50;	// 출력: 50;
}
stack=2, locals=4, args_size=1
 0: bipush        7
 2: istore_1
 3: iconst_2
 4: istore_2
 5: iload_1
 6: iload_2
 7: if_icmpge     15	// value1 ≥ value2
10: bipush        30
12: goto          17
15: bipush        50
17: istore_3
18: return

7번 비교를 통해 나오는 값을 스택에 정수 형태로 저장
그러면 if 구문으로 이걸 똑같이 구현하면 바이트코드가 많이 달라질까?

public static void main(String[] args) {
  int x = 7;
  int y = 2;
  // int a = 0;
        
  if (x < y) {
    a = 30;
  } else {
    a = 50;
  }
}
stack=2, locals=4, args_size=1
 0: bipush        7
 2: istore_1
 3: iconst_2
 4: istore_2
 5: iload_1
 6: iload_2
 7: if_icmpge     16
10: bipush        30
12: istore_3
13: goto          19
16: bipush        50
18: istore_3
19: return

코드에서 드라마틱한 변화가 보이지는 않는거 같다.


9. 연산자 우선 순위

  • 최우선연산자 ( ., [], () )
  • 단항연산자 ( ++,--,!,~,+/- : 부정, bit변환>부호>증감)
  • 산술연산자 ( *,/,%,+,-,shift) < 시프트연산자 ( >>,<<,>>> ) >
  • 비교연산자 ( >,<,>=,<=,==,!= )
  • 비트연산자 ( &,|,,~ )
  • 논리연산자 (&& , || , !)
  • 삼항연산자 (조건식) ? :
  • 대입연산자 =,*=,/=,%=,+=,-=

우선 순위는 어떻게 처리를 할까?

public static void main(String[] args) {
  int a = 5;
  int b = 10;
  int c = 5;
  a = (a - b * c);
}
stack=3, locals=4, args_size=1
 0: iconst_5
 1: istore_1
 2: bipush        10
 4: istore_2
 5: iconst_5
 6: istore_3
 7: iload_1
 8: iload_2
 9: iload_3
10: imul
11: isub
12: istore_1
13: return

3 정수를 차례차례 저장하고 한 번에 다 불러서 내부적으로 우선순위를 확인 후 차례대로 계산 후 값을 저장하는 것으로 보인다.

다른 연산도 해봤지만, 우선순위 계산하는 것으로 보여 나머지 내용 생략


10. (optional) Java 13. switch 연산자

스위치 연산자는 최적화에 효율적이라는 말을 들은 적이 있는데 그 호기심을 직접 볼 기회가 온 거 같다.

public static void main(String[] args) {
  int a = 5;
  switch (a) {
    case 1:
      a = 4;
      break;
    case 5:
      a = 3;
      break;
    default:
      a = 7;
  }
}
stack=1, locals=2, args_size=1
 0: iconst_5
 1: istore_1
 2: iload_1
 3: lookupswitch  { // 2
     1: 28
     5: 33
     default: 38
 }
28: iconst_4
29: istore_1
30: goto          41
33: iconst_3
34: istore_1
35: goto          41
38: bipush        7
40: istore_1
41: return

lookupswitch는 키를 사용하여 테이블에서 대상 주소를 조회하고 해당 주소의 명령어에서 계속 실행

이번에는 이 구문을 if로 변환해서 확인

public static void main(String[] args) {
  int a = 5;
  if (a == 4) {
    a = 4;
  } else if (a == 5) {
    a = 3;
  } else {
    a = 7;
  }
}
stack=2, locals=2, args_size=1
 0: iconst_5
 1: istore_1
 2: iload_1
 3: iconst_4
 4: if_icmpne     12
 7: iconst_4
 8: istore_1
 9: goto          25
12: iload_1
13: iconst_5
14: if_icmpne     22
17: iconst_3
18: istore_1
19: goto          25
22: bipush        7
24: istore_1
25: return

이번에는 정말 확실한 차이를 보여준다.
stack 공간도 1개 더 사용하고 실행해야 할 명령문도 더 많아졌다.

2020. 11. 20.

2주차 과제: 자바 데이터 타입, 변수 그리고 배열

https://github.com/whiteship/live-study/issues/2

목표

자바의 프리미티브 타입, 변수 그리고 배열을 사용하는 방법을 익힙니다.

학습할 것

  • 프리미티브 타입 종류와 값의 범위 그리고 기본 값
  • 프리미티브 타입과 레퍼런스 타입
  • 리터럴
  • 변수 선언 및 초기화하는 방법
  • 변수의 스코프와 라이프타임
  • 타입 변환, 캐스팅 그리고 타입 프로모션
  • 1차 및 2차 배열 선언하기
  • 타입 추론, var


1. 프리미티브 타입 종류와 값의 범위 그리고 기본 값

byte
8-bit signed two's complement integer (8bit 부호있는 2의 보수 정수)
-128 ~ 127(포함)

  • 바이트 데이터 유형은 메모리 절약이 중요한 대형 배열에 메모리를 절약하는데 유용할 수 있습니다.
  • 또한 제한이 코드를 명확히 하는 데 도움이 되는 곳에 int형 대신 사용할 수 있습니다. 변수의 범위를 제한한다는 사실은 문서화의 한 형태로 제공될 수 있습니다.

short
16-bit signed two's complement integer (16bit 부호있는 2의 보수 정수)
-32,768 ~ 32,767(포함)

  • 바이트와 마찬가지로 동일한 지침이 적용됩니다. 메모리 절약이 실제로 중요한 상황에서 short를 사용하여 큰 배열의 메모리를 절약할 수 있습니다.

int
32-bit signed two's complement integer (32비트 부호있는 2의 보수 정수)
-231 ~ 231-1

  • Java SE 8 이상에서는 int 데이터 타입을 사용해 최솟값 0 ~ 최댓값 232-1 unsigned 32-bit integer (부호 없는 32bit 정수)를 표현할 수 있습니다.
  • Integer 클래스를 사용해 int 데이터 유형을 부호없는 정수로 사용합니다. compareUnsigned, divideUnsigned 등과 같은 static methods가 Integer 클래스에 추가되어 unsigned integer에 대한 산술 연산을 지원합니다.

long
64-bit two's complement integer (62bit 2의 보수 정수)
-263 ~ 263-1

  • Java SE 8 이상에서는 long 데이터 타입을 사용해 최솟값 0 ~ 최대값 264-1 unsigned 64-bit long (부호 없는 64bit long)을 표현할 수 있습니다.
  • int에서 제공하는 것보다 더 넓은 값 범위가 필요한 경우 이 데이터 유형을 사용하십시오.
  • Long 클래스에는 unsigned long에 대한 산술 연산을 지원하는 compareUnsigned, divideUnsigned 등의 method도 포함되어 있습니다.

float
single-precision 32-bit IEEE 754 floating point (단정밀도 32bit IEEE 754 부동소수점)

  • 값의 범위는 이 주제의 범위를 벗어나지만, Java 언어 명세서에 부동 소수점 유형, 형식, 값이 명시돼 있습니다.
  • byte나 short의 권장 사항과 마찬가지로 부동 소수점 숫자의 큰 배열에 메모리를 절약해야 하는 경우 double 대신 float를 사용하십시오.
  • 이 데이터 타입은 통화와 같은 정확한 값에 사용해서는 안 됩니다. 이를 위해 대신 java.math.BigDecimal 클래스 사용해야 합니다.
  • 숫자나 문자열은 Java 플랫폼에서 제공하는 BigDecimal이나 다른 유용한 클래스들이 처리를 도와줍니다.

double
double-precision 64-bit IEEE 754 floating point (배정밀도 64bit IEEE 754 부동소수점)

  • 값의 범위는 이 주제의 범위를 벗어나지만, Java 언어 명세서에 부동 소수점 유형, 형식, 값이 명시돼 있습니다.
  • 기본적으로 10진수 입니다.
  • 위에서 언급했듯이 이 데이터 유형은 통화와 같은 정확한 값에 사용해서는 안 됩니다.

boolean
boolean 데이터 유형은 true와 false 두가지 값만 있습니다.

  • 참/거짓 조건을 추적하는 간단한 flag 데이터 타입으로 사용합니다.
  • 이 데이터 타입은 1bit의 정보를 표현하지만 "크기"는 정확하게 정의된 것이 아닙니다.

char
single 16-bit Unicode character (싱글 16bit 유니코드 문자)
'\u0000' (또는 0) ~ '\uffff' (또는 65,535 포함)

기본값

필드(field)가 선언될 때 항상 값을 할당할 필요는 없습니다. 선언되었지만 초기화되지 않은 필드는 컴파일러에 의해 적절한 기본값으로 설정됩니다. 일반적으로는 데이터 타입에 따라 기본값이 0 또는 null입니다. 그러나 기본값에 의존하는 것은 일반적으로 잘못된 프로그래밍 스타일로 간주합니다.

기본값은 다음과 같습니다.


2. 프리미티브 타입과 레퍼런스 타입

Primitive type (기본형 타입)

  • 메모리에 기본형 타입이 보유한 실제 데이터를 저장합니다.
  • 기본형 값이 동일한 유형의 다른 변수에 지정되면 사본이 만들어집니다.
  • 기본형이 메소드에 전달되면 기본형의 복사본만 전달됩니다. 호출된 메소드는 오리지널 기본형 값에 엑세스할 수 없음으로 변경할 수 없습니다. 호출된 메소드는 복사된 값을 변경할 수 있습니다.

Reference type (참조형 타입)

  • 사용자가 정의한 무제한 참조 유형입니다.
  • 메모리에 데이터에 대한 참조를 저장합니다.
  • 참조 유형이 다른 참조 유형에 할당되면 둘 다 동일한 객체를 가리킵니다.
  • 객체가 메소드에 전달되면 호출된 메소드는 전달된 객체의 내용을 변경할 수 있지만 객체의 주소는 변경할 수 없습니다.


3. 리터럴

기본형 타입 변수를 초기화할 때 new 키워드를 사용하지 않는다는 것을 보셨을 겁니다.
기본형 타입은 언어에 내장된 특수 데이터 유형입니다. 클래스에서 생성된 객체가 아닙니다.
리터럴은 고정된 값의 소스 코드 표현입니다. 계산 없이 코드에서 직접 표현됩니다.
아래와 같이 기본형 타입 변수에 리터럴을 할당할 수 있습니다.

boolean result = true;
char capitalC = 'C';
byte b = 100;
short s = 10000;
int i = 100000;


4. 변수 선언 및 초기화하는 방법

int num = 0;
//타입 변수이름 = 값;

//example
int myNum = 5;
float myFloatNum = 5.99f;
char myLetter = 'D';
boolean myBool = true;
String myText = "Hello";


5. 변수의 스코프와 라이프타임

public class MyStudyClass {
  int x = 0; // 인스턴스 변수
  static int result = 0; // 클래스 변수
  
  void method1() {
    int x=0; // 지역 변수
  }
}

인스턴스 변수
클래스 내부에 선언되었지만, 메소드 및 블록 외부에 선언된 변수
스코프: 정적 메소드를 제외한 클래스 전체
라이프타임: 클래스 객체가 메모리에 남아있는 동안

클래스 변수
클래스 내부, 모든 블록 외부에서 선언되고 static으로 선언된 변수
스코프: 클래스 전체
라이프타임: 프로그램이 끝날 때까지

지역 변수
인스턴스 또는 클래스 변수가 아닌 다른 모든 변수
스코프: 선언된 블록 내부 라이프타임: 컨트롤이 선언된 블록을 떠날 때까지


6. 타입 변환, 캐스팅 그리고 타입 프로모션

확대(widening) 캐스팅 (자동적으로) - 작은 타입을 큰 타입으로 변환
byte → short → char → int → long → float → double

public class Main {
  public static void main(String[] args) {
    int myInt = 9;
    double myDouble = myInt; // 자동 캐스팅

    System.out.println(myInt);      // 출력 9
    System.out.println(myDouble);   // 출력 9.0
  }
}

축소(narrowing) 캐스팅 (수동적으로) - 큰 타입을 작은 타입으로 변환
double → float → long → int → char → short → byte

public class Main {
  public static void main(String[] args) {
    double myDouble = 9.78;
    int myInt = (int) myDouble; // 수동 캐스팅

    System.out.println(myDouble);   // 출력 9.78
    System.out.println(myInt);      // 출력 9
  }
}

타입 프로모션은 자동으로 작은 범위의 타입을 큰 범위의 타입으로 승격(promotion)한 후 연산합니다.

class Test {			
  public static void main (String [] args) {
    int i = (int)12.5f;  // i 출력 12 (narrowing)
    float f = i; // f 출력 12.0 (widening)
    f = f * i; // i의 타입이 flot로 승격 후 folat*float로 연산
  }
}


7. 1차 및 2차 배열 선언하기

class ArrayDemo {
  public static void main(String[] args) {
    // integer 배열 선언
    int[] anArray;

    // 10개의 integer 배열 선언
    anArray = new int[10];
    
    // 이 양식은 관례상 권장하지 않습니다.
    float anArrayOfFloats [];
    
    // 2차원 배열 선언
    String[][] names = {
        {"Mr. ", "Mrs. ", "Ms. "},
        {"Smith", "Jones"}
    };
    // Mr. Smith
    System.out.println(names[0][0] + names[1][0]);
    // Ms. Jones
    System.out.println(names[0][2] + names[1][1]);
    }
}


8. 타입 추론, var

var a = 1;            // Legal
var b = 2, c = 3.0;   // Illegal: multiple declarators
var d[] = new int[4]; // Illegal: extra bracket pairs
var e;                // Illegal: no initializer
var f = { 6 };        // Illegal: array initializer
var g = (g = 7);      // Illegal: self reference in initializer

var a = 1;                // a has type 'int'
var b = java.util.List.of(1, 2);  // b has type List
var c = "x".getClass();   // c has type Class
var d = new Object() {};  // d has the type of the anonymous class
var e = (CharSequence & Comparable) "x";
                          // e has type CharSequence & Comparable
var f = () -> "hello";    // Illegal: lambda not in an assignment context
var g = null;             // Illegal: null type

왜 Java에 var가 있어야 하나요?

로컬 변수는 Java의 핵심 요소입니다. 로컬 변수를 사용해 메소드는 중간 값을 저렴하게 저장하여 중요한 결과를 계산합니다. 필드와 달리 로컬 변수는 동일한 블록에서 선언, 초기화하고 사용합니다. 로컬 변수의 이름과 초기화는 종종 타입보다 독자의 이해를 위해 더 중요합니다. 일반적으로 이름과 초기화는 타입만큼 많은 정보를 제공합니다.

Person person = new Person()

로컬 변수 선언에서 var의 역할은 이름과 초기화를 강조하도록 타입 자리를 대신하는 것입니다.

var person = new Person()

컴파일러는 초기화시 로컬 변수의 타입을 유추합니다.

이것은 특히 이럴때 가치가 있습니다.
타입이 와일드 카드로 매개 변수화 된 경우
또는 초기화에서 타입이 언급 된 경우.
(This is especially worthwhile if the type is parameterized with wildcards, or if the type is mentioned in the initializer.)
※모르는 부분이 많고 이해가 잘 안 돼서 다시 들여다봐야 할 부분

var를 사용하면 가독성을 떨어 뜨리지 않고 코드를 더 간결하게 만들 수 있으며 경우에 따라 중복성을 제거하여 가독성을 향상시킬 수 있습니다.

출처: http://openjdk.java.net/projects/amber/LVTIFAQ.html

2020. 11. 17.

1주차: JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가.


https://github.com/whiteship/live-study/issues/1

목표

자바 소스 파일(.java)을 JVM으로 실행하는 과정 이해하기.

학습할 것

  • JVM이란 무엇인가
  • 컴파일 하는 방법
  • 실행하는 방법
  • 바이트코드란 무엇인가
  • JIT 컴파일러란 무엇이며 어떻게 동작하는지
  • JVM 구성 요소
  • JDK와 JRE의 차이


1. JVM이란 무엇인가

JVM(Java Virtual Machine)은 Java와 자바 바이트 코드로 컴파일이 가능한 다른 언어로 쓰인 프로그램이 컴퓨터에서 실행이 가능하도록 해주는 가상 머신 입니다.

Java는 "한 번 작성하면 어디서나 실행"이라는 개념으로 다양한 플랫폼에서 실행되도록 설계되었습니다. 특정 플랫폼 용으로 컴파일되고 실행되는 C++ 같은 언어와 달리 Java 소스 코드는 먼저 .class 파일 인 바이트 코드로 컴파일 됩니다. 컴파일 된 클래스 파일은 가상 머신에서 해석됩니다.

먼저 자바 소스 코드 (.java 파일)를 바이트 코드 (.class 파일)로 컴파일합니다. 바이트 코드는 자바와 기계어 사이의 중간 언어입니다. 다른 OS나 플랫폼에서 코드를 수정하지 않고도 JVM에서 동일한 바이트 코드를 실행할 수 있습니다.


2. 컴파일 하는 방법

public class FirstJavaProgram {
  public static void main(String[] args){
    System.out.println("This is my first program in java");
  }//End of main
}//End of FirstJavaProgram Class

출처: https://beginnersbook.com/2013/05/first-java-program/

Step 1: 윈도우의 메모장이나 Mac의 'TextEdit'같은 프로그램을 열어 위의 코드를 작성합니다.

Step 2: 우리가 만든 프로그램의 'public class' 이름이 'FirstJavaProgram'이기 때문에 파일 이름을 'FirstJavaProgram.java'로 저장합니다.

Step 3: 윈도우의 'command prompt (cmd)', Mac OS의 Terminal을 열어 아래의 명령어를 작성하고 엔터를 입력합니다.

javac FirstJavaProgram.java

"javac is not recognized as an internal or external command, operable program or batch file" 와 같은 애러 메세지가 보인다면 Path(경로) 설정을 해야 합니다.

윈도우 - cmd에서 java가 설치돼 있는 경로의 bin 폴더 위치를 복사해 아래와 같이 작성합니다.

set path=C:\Program Files\Java\jdk1.8.0_121\bin

Note: jdk 버전이 다를 수 있습니다. 제가 쓰는 자바 버전은 1.8.0_121으로 똑같이 작성했습니다.

Mac OS X - 터미널에서 다음과 같이 작성합니다.

export JAVA_HOME=/Library/Java/Home

터미널에서 다음 명령을 입력해 경로를 확인합니다.

echo $JAVA_HOME

PATH 시스템 변수를 설정하거나 변경하려면 어떻게 해야 합니까?


3. 실행하는 방법

컴파일 후 .class(바이트 코드) 파일을 다음 명령어로 실행합니다.

java FirstJavaProgram


4. 바이트 코드란 무엇인가

자바 바이트 코드는 JVM(Java Virtual Machine)의 명령어 세트(instruction set)입니다.

Java bytecode instruction listings

Java 프로그래머는 Java 바이트 코드를 전혀 인식하거나 이해할 필요가 없습니다. 그러나 IBM developerWorks 저널에서 제안한 것처럼 "바이트 코드와 Java 컴파일러에 의해 생성될 수 있는 바이트 코드를 이해하면 어셈블리에 대한 지식이 C 또는 C ++ 프로그래머에게 도움이 되는 것과 동일한 방식으로 Java 프로그래머에게 도움이 됩니다."
출처: https://www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/


5. JIT 컴파일러란 무엇이며 어떻게 동작하는지

JIT (Just-In-Time) 컴파일러는 런타임시 Java 애플리케이션의 성능을 향상시키는 Java Runtime Environment의 구성 요소입니다. JIT 컴파일러는 시작시 수천개의 메서드를 호출 할 수 있으므로 호출되는 모든 메서드를 컴파일하지는 않습니다. 대신 메서드가 호출 된 횟수를 기록합니다. 개수가 사전 정의된 호출 임계값에 도달하면 JIT 컴파일이 작동합니다. JIT에 의해 메소드가 컴파일되면 VM은 해석(interpreting)하는 대신 컴파일 된 메소드를 호출합니다.

나중에 읽어볼 것
Chapter 4. Working with the JIT Compiler
The JIT compiler


6. JVM 구성 요소

JVM은 크게 세 가지 주요 영역으로 구성됩니다.

  • 클래스 로더 하위 시스템
  • 런타임 데이터 영역
  • 실행 엔진

1. 클래스 로더 서브시스템(ClassLoader Subsystem)

  1. 로딩(Loading)
    • 클래스(Class)를 사용하려고 하면 자바 클래스 로더가 해당 클래스를 메모리에 로드
    • 첫번째 클래스 실행은 일반적으로 정적 main() 메서드를 선언하고 사용
  2. 연결(Linking)
    • 검증(Verify) – 바이트 코드 검증기는 생성된 바이트 코드가 적절한지 여부를 확인하고 확인이 실패하면 확인 오류가 발생.
    • 준비(Prepare) – 모든 정적 변수에 대해 메모리가 할당(allocated)되고 기본값으로 지정(assigned)됨.
    • 해결(Resolve) – 모든 기호 메모리 참조(symbolic memory references)가 Method Area의 원래 참조(original references)로 대체.
  3. 초기화(Initialization)
    • ClassLoading의 마지막 단계입니다. 여기서 모든 정적 변수(static variables)는 원래 값(original values)으로 할당(assigned)되고 정적 블록(static block)이 실행됩니다.

2. 런타임 데이터 영역(Runtime Data Area)

런타임 데이터 영역은 5개의 주요 구성 요소로 나뉩니다.

  1. 메서드 영역(Method Area) – 정적 변수를 포함하여 모든 클래스 수준 데이터가 여기에 저장됩니다. JVM당 하나의 메소드 영역만 있으며 공유자원입니다.
  2. 힙 영역(Heap Area) – 모든 개체와 해당 인스턴스 변수 및 배열이 여기에 저장됩니다. JVM 당 하나의 힙 영역도 있습니다. 메서드 및 힙 영역은 여러 스레드에 대한 메모리를 공유하므로 저장된 데이터는 스레드로부터 안전(thread-safe)하지 않습니다.
  3. 스택 영역(Stack Area) – 모든 스레드는 각자 런타임 스택이 생성됩니다. 모든 메서드 호출에 스택 프레임이라는 불리는 스택 메모리가 만들어집니다. 모든 지역 변수는 스택 메모리에 생성됩니다. 스택 영역은 공유 리소스가 아니므로 스레드로부터 안전합니다. 스택 프레임은 세 가지 하위 항목으로 나뉩니다.
    • 로컬 변수 배열(Local Variable Array) – 얼마나 많은 로컬 변수가 메소드와 연관돼 있는지와 해당 값이 여기에 저장됩니다.
    • 피연산자 스택(Operand stack) – 중간 작업이 필요한 경우 피연산자 스택은 작업을 수행하는 런타임 작업 영역으로 작동합니다.
    • 프레임 데이터(Frame data) – 메소드와 연관된 기호가 여기에 저장됩니다. 예외가 발생하면 캐치 블록(catch block) 정보를 프레임 데이터에 유지합니다.
  4. PC 레지스터(PC Registers) – 각 스레드에는 별도의 PC 레지스터가 있으며, 명령어가 실행되면 현재 실행중인 명령어의 주소를 유지하기 위해 PC 레지스터가 다음 명령(instruction)으로 업데이트됩니다.
  5. 네이티브 메서드 스택(Native Method stacks) – 네이티브 메서드 스택은 네이티브 메서드 정보를 보유합니다. 모든 스레드에 대해 별도의 네이티브 메서드 스택이 생성됩니다.

3. 실행 엔진(Execution Engine)

런타임 데이터 영역(Runtime Data Area)에 할당 된 바이트 코드는 실행 엔진(Execution Engine)에 의해 실행됩니다. 실행 엔진은 바이트 코드를 조각 조각으로 읽고 실행합니다.

  1. 인터프리터(Interpreter) – 인터프리터는 바이트 코드를 더 빨리 해석하지만 느리게 실행됩니다. 인터프리터의 단점은 하나의 메서드가 여러 번 호출 될 때마다 새로운 해석(interpretation)이 필요하다는 것입니다.
  2. JIT 컴파일러 – JIT 컴파일러는 인터프리터의 단점을 무력화합니다. 실행 엔진은 바이트 코드를 변환하는 데 인터프리터를 사용하지만 반복된 코드를 찾으면 전체 바이트 코드를 컴파일하고 네이티브 코드로 변경하는 JIT 컴파일러를 사용합니다. 이 네이티브 코드는 반복되는 메서드 호출에 직접 사용되어 시스템 성능을 향상시킵니다.
    1. 중간 코드 생성기(Intermediate Code Generator)
    2. 코드 최적화(Code Optimizer)
    3. 타겟 코드 생성기(Target Code Generator) – 기계 코드 또는 네이티브 코드 생성 담당
    4. 프로파일러(Profiler) - 여러번 사용되는 메소드 핫스팟을 찾는 역할
  3. 가비지 수집기(Garbage Collector): 참조되지 않은 개체를 수집하고 제거합니다. 가비지 수집은 System.gc()를 호출하여 실행할 수 있지만, 실행이 보장되지는 않습니다. JVM의 가비지 수집기는 생성된 개체(objects)를 수집합니다.

JNI(Java Native Interface): JNI는 Native Method Libraries와 상호 작용하며 실행 엔진에 필요한 Native Libraries를 제공합니다.

Native Method Libraries: 실행 엔진(Execution Engine)에 필요한 네이티브 라이브러리 모음입니다.


7. JDK와 JRE의 차이

JRE(Java Runtime Environment)는 자바 프로그램 실행 환경을 위한 설치 패키지입니다. 자바 프로그램 실행에 필요한 파일들이 포함돼 있지만 개발하지 못합니다.

JDK(Java Development Kit)는 자바 개발에 모든 기능을 갖춘 SDK(Software Development Kit)입니다. JRE에 있는 모든 기능뿐만 아니라 컴파일러(javac)와 개발 도구가 포함돼 있습니다.


https://github.com/whiteship/live-study/issues/1#issuecomment-727216519
1주차 추가 과제 - javac 옵션 조사

-g
지역 변수를 포함한 모든 디버깅 정보를 생성합니다. 기본적으로 행 번호와 소스 파일 정보만 생성됩니다.

-g:none
디버깅 정보를 생성하지 않습니다.

-g:[lines, vars, source],[lines, vars, source],[lines, vars, source]
쉼표로 구분된 키워드 목록 중 지정된 종류의 디버깅 정보만 생성합니다. 유효한 키워드는 다음과 같습니다.

    lines : 줄 번호 디버깅 정보

    vars : 지역 변수 디버깅 정보

    source : 소스 파일 디버깅 정보

-nowarn
경고 메시지를 비활성화합니다. 이 옵션은 -Xlint:none 옵션과 동일하게 작동합니다.

-verbose
컴파일러가 수행하는 작업에 대한 메시지를 출력합니다. 메시지에는 로드된 클래스 및 컴파일된 소스 파일에 대한 정보가 포함됩니다.

-deprecation
더 이상 사용되지 않는(deprecated) 멤버 또는 클래스의 사용 또는 상속(override)에 대한 설명을 표시합니다. -deprecation 옵션이 없으면 javac는 소스 파일의 요약을 표시합니다. -deprecation 옵션은 -Xlint:deprecation의 줄임 표현입니다.

-classpath <path>, --class-path <path>, -cp <path>
사용자의 클래스 파일 및 주석(annotation) 프로세서를 찾을 위치를 지정합니다. 이 클래스 경로는 사용자의 CLASSPATH 환경 변수를 재정의(overrides)합니다.

    --class-path, -classpath 또는 -cp 지정되지 않은 경우, 클래스 경로는 현재 디렉토리입니다.

    -sourcepath 옵션이 지정되어 있지 않은 경우, 사용자 클래스 패스는 소스 파일을 검색합니다.

    -processorpath 옵션이 지정되어 있지 않은 경우, 클래스 패스는 주석(annotation) 프로세서를 검색합니다.

-source-path <path>, -sourcepath <path>
입력 받을 소스 파일의 위치를 지정합니다. 클래스나 인터페이스의 정의를 찾기 위해 사용할 소스 코드 경로입니다. 사용자 class path와 마찬가지로 소스 경로 항목은 Oracle Solaris에서는 콜론(:)으로, Windows에서는 세미콜론(;)으로 구분합니다. Jar archives, Zip archives, 폴더일 수 있습니다. 패키지에 사용되는 경우 디렉토리나 아카이브(archives) 내의 로컬 경로 이름이 패키지 이름을 반영해야 합니다.

-bootclasspath <path>, --boot-class-path <path>
부트스트랩(bootstrap) 클래스 파일의 위치를 재정의합니다.

-extdirs <dirs>
설치된 확장 파일의 경로를 재정의합니다. 디렉토리 변수는 콜론으로 구분된 디렉토리 목록입니다. 지정된 특정 경로의 JAR 파일을 검색해 클래스 파일을 찾습니다. 발견한 모든 JAR 파일은 클래스 경로의 일부가 됩니다.

-endorseddirs <dirs>
승인된 표준 경로의 위치를 재정의합니다.

-proc: {none,only}, {none,only}
주석 처리(annotation processing)와 컴파일이 수행되는지 여부를 제어합니다. -proc:none 주석 처리없이 컴파일이 수행됨을 의미합니다. -proc:only 후속 컴파일 없이 주석 처리만 수행됨을 의미합니다.

-processor <class1>[,<class2>,>class3>...]
실행할 주석 프로세서의 이름입니다. 기본 검색 프로세스를 무시합니다.

-processorpath <path>, -processor-path <path>
주석 프로세서를 찾을 위치를 지정합니다. 이 옵션을 사용하지 않으면 클래스 경로에서 프로세서를 검색합니다.

-parameters
메소드 매개변수의 리플렉션(reflection)을 위한 메타 데이터를 생성합니다. 생성된 클래스 파일안의 생성자와 메소드의 형식 매개변수(formal parameter) 이름을 저장해 리플렉션 API의 java.lang.reflect.Executable.getParameters 메소드가 가져올 수 있게 합니다.

-d <directory>
클래스 파일의 대상 디렉토리를 설정합니다. 클래스가 패키지의 일부인 경우 javac는 패키지 이름을 반영하는 하위 디렉토리에 클래스 파일을 넣고 필요에 따라 디렉토리를 만듭니다. -d 옵션이 지정되지 않은 경우 javac는 소스파일과 동일한 디렉토리에 저장합니다.

-s <directory>
생성된 소스 파일을 배치할 디렉토리를 지정합니다.

-h <directory>
생성된 기본 헤더 파일을 배치할 위치를 지정합니다.

-implicit:{[none, class],[none, class]}
암시적으로 참조된(implicitly referenced) 파일의 클래스 파일을 생성할지 여부를 지정합니다.

   -implicit:class 자동으로 클래스 파일을 생성합니다.

   -implicit:none 클래스 파일 생성을 금지합니다.

이 옵션을 지정하지 않으면 기본적으로 클래스 파일을 생성합니다. 이 경우 컴파일러는 주석 프로세서를 수행할 때 클래스 파일이 생성되면 경고를 발생합니다. -implicit 옵션이 명확하게 설정된 경우에는 경고가 발생하지 않습니다.

-encoding <encoding>
EUC-KR, UTF-8 같은 소스 파일에서 사용하는 문자 인코딩을 지정합니다. -endocing 옵션을 지정하지 않으면 플랫폼 기본으로 사용됩니다.

-source <release>
허용되는 소스 코드의 버전을 지정합니다.

Note: JDK 9부터 javac는 5보다 작거나 같은 값(버전)을 지원하지 않습니다. 5보다 작거나 같은 값을 사용하는 경우 javac -source 6이 지정된 것처럼 작동합니다.

-target <release>
특성 VM 버전의 클래스 파일을 생성합니다.

-profile <profile>
사용된 API가 지정된 프로필에서 사용 가능한지 확인합니다.

-version
버전 정보를 출력합니다.

-help
표준 옵션의 요약을 출력합니다.

-Akey[=value]
주석 프로세서에 전달할 옵션을 지정합니다. javac에서 직접 해석(interpreted)되지 않지만, 개별 프로세서에서 사용할 수 있습니다. 키값은 점(.)으로 구분된 하나 이상의 식별자입니다.

-X
추가 옵션에 대한 도움말을 출력합니다.

-J<flag>
옵션을 런타임 시스템에 전달합니다. 여기서 옵션은 javacommand에 설명된 옵션 중 하나입니다. 예를 들어 '-J-Xms48m'은 시작 메모리를 48MB로 설정합니다.

-Werror
경고가 발생하면 컴파일을 종료합니다.

@<filename>
파일에서 옵션 및 파일 이름을 읽습니다. javac 명령어의 단순화를 위해 javac 명령어에 대한 인수를 포함하는 하나 이상의 파일을 지정할 수 있습니다.(-J 옵션 제외한) 이를 통해 모든 운영 체제에서 다양한 길이의 javac 명령어를 만들 수 있습니다.

출처: https://docs.oracle.com/javase/9/tools/javac.htm

2020. 7. 3.

또 배틀로얄 게임? 하이퍼 스케이프(Hyper Scape)



유비소프트가 갑자기 배틀로얄 게임을 준비했습니다.

6월 29일 정보가 유출됐을 때만 해도 '이 치열한 레드 오션 시장에 또 배틀로얄 게임을 만들어?'란 생각이 먼저 들었습니다.
[루머] 유비소프트,무료 배틀로얄 하이퍼 스케이프 유출?


그러던 와중 갑자기 트위치와 연계로 7월 2일부터 7일까지 PC 기술 테스트(PC Technical Test)를 시작했습니다.
https://www.twitch.tv/directory/game/Hyper Scape

이번 유비소프트에서 만든 작품을 보니 과거 퀘이크(Quake) 시리즈의 하이퍼 FPS 시절로 회귀하는 게 아닌가란 생각이 들었습니다.

현재 FPS는 최대한 초보 유저들을 배려하고 입문도 쉽게 하는 방향으로 발전하고 있습니다. 하수가 고수를 한 번도 못 맞추고 죽기만 하는 절망감만 안겨주는 게임이 되다 보니 하이퍼 FPS의 기준? 계보라 할 수 있는 퀘이크 챔피언스의 흥행 실패언리얼 토너먼트의 개발 중단 소식을 볼 수 있습니다.
오버워치는 하이퍼 FPS라 볼 수 있지만 정말 배려심이 많은 게임에 속합니다. 넉넉한 타격 판정, 에임(Aim: 조준)이 안좋아도 할 수 있는 캐릭터들, 누르면 편하게 나가는 스킬등 많은 배려가 들어가 있어서 하이퍼 FPS의 불모지라 할 수 있는 한국에서 유일하게 성공한 게임이 아닌가 합니다.

일단 처음 배틀로얄 장르의 큰 흥행을 불러온 배틀그라운드부터 나온 순서대로 특성을 보면

[배틀그라운드]
1. 캐릭터 움직임에 제약이 많다. → 고수와 하수의 무빙 격차를 줄인다.
2. 밀리터리 총기류 사용 → 고수와 하수의 무기 숙련 격차를 줄인다.
3. 방어구가 파괴돼 없어지는 시스템 (최근에 방어구가 파괴되지 않도록 패치)
4. 낙하 시 사망
5. 사망 시 부활 불가능
6. 현대적인 분위기
7. 3인칭 / 1인칭 시점

[포트나이트]
1. 배틀그라운드 보다는 제약없는 점프 → 하이퍼 FPS 만큼은 아니지만 움직임에 약간의 실력 격차를 줌
2. 밀리터리 총기류 사용
3. 마시는 물약으로 방어막을 충전
4. 낙하 시 사망 → 낙하시 경사로에 건설로 생존 가능
5. 사망 시 부활 불가능이었지만 에이펙스 출시 후 벤치마크해 부활 자동차(Reboot Van)를 추가
6. 카툰같은 분위기의 현대? 미래?
7. 3인칭 시점
차별화 포인트
- 건설 → 실력 격차를 늘리는 큰 요소이자 게임의 아이덴티티

[에이펙스 레전드]
1. 달리면서 슬라이딩 가능, 1층 높이의 건물 벽이나 담장을 쉽게 넘어다니게 만들어 이동에 제약을 줄임, 타고 멀리 이동하거나 수직 이동을 빠르게 돕는 줄타기
2. 밀리터리 총기류 사용, 무기 특성이 변하는 부품을 추가
3. 충전식 셀(cell), 배터리로 충전
4. 낙하 데미지 없음, 맵 곳곳에 줄 타고 올라가서 멀리 이동하게 만들어 주는 풍선이 존재
5. 사망 시 팀원이 데드 박스에서 배너를 회수해야 특정 위치에서 부활 가능
6. 미래적인 분위기
7. 1인칭 시점
차별화 포인트
- 캐릭터별 스킬 1개와 궁극기 1개가 존재

[콜 오브 듀티 워존]
1. 속도감이 느껴지도록 디자인했지만 낮은 담장 정도만 빠르게 넘어다니는 것만 가능, 슬라이딩이 존재하지만 속도가 줄어들어 많이 쓰이진 않음, 고층 건물을 빠르게 올라갈 수 있는 줄타기 엘리베이터가 존재
2. 밀리터리 총기류 사용, 열화상 스코프나 다양한 모딩
3. 3칸의 방탄 플레이트를 교체, 시간이 지나면 체력은 자동 충전
4. 낙하 시 사망하지만 자유롭게 펼 수 있는 낙하산을 추가
5. 사망 시 굴라그란 1:1 전투장으로 끌려가고 대결에서 져도 팀원이 돈을 모아 특정 위치에서 부활 가능
6. 현대적인 분위기
7. 1인칭 시점
차별화 포인트
- 1:1 대결 장소 굴라그
- 돈을 모아 보급 상자를 소환하면 미리 세팅한 무기 사용 가능
- 콜 오브 듀티 시리즈 전통인 특전(perk) 시스템

[하이퍼 스케이프]
1. 2단 점프 가능, 슬라이딩 가능, 스킬 사용 시 텔레포트, 하이퍼 FPS 수준의 빠른 무빙
2. 샷건, 리볼버, 유도탄 권총, 스나이퍼, 개틀링 기관총, 돌격소총
날아가는 투사체가 보이는 에너지 대포, 유탄 발사기, 플라즈마 발사기
밀리터리 무기 비율이 높지만 하이퍼 FPS 형식의 무기 3개가 다 넓은 범위 공격형으로 난이도를 그나마 낮춰준 느낌을 받았습니다.
3. 아머가 없고 스킬로만 존재, 시간이 지나면 체력은 자동 충전
4. 낙하 데미지 없음, 점프 패드가 맵 곳곳에 존재
5. 사망 시 투명해져서 부활 장소로 이동하면 팀원이 부활시키는 방식, 부활이 불가능해도 적의 위치를 알려줄 수 있고 적이 죽으면 죽은 삼각 표시 위에서 부활 가능
6. 미래적인 분위기
7. 1인칭 시점
차별화 포인트
- 핵이라 불리는 스킬 중 원하는 2개 선택 시스템 (장벽, 지뢰, 투명화, 탐색, 치유, 장갑, 구체, 순간이동, 격돌)
- 스킬, 무기 업그레이드 시스템


출시 일정에 따라 나온 게임들이 전에 출시한 배틀로얄 게임에서 개선할 점을 찾아서 고치고 차별화 포인트를 만들어 출시한 걸 보실 수 있습니다.
아이러니 한 점은 고수와의 격차를 줄이려고 만든 제약이 자유로운 게임에 방해 요소가 되니 다시 개선해서 예전의 하이퍼 FPS로 돌아온 느낌을 준다는 점 입니다.

1. 움직임에 제약을 주고 낙하 데미지를 넣었다가 다시 자유로운 움직임과 낙하 데미지를 삭제하는 방향으로
2. 고생해 파밍한 3레벨 방어구가 전투를 할수록 내구도가 줄어들어 파괴되면 다시 방어구를 구하지 못해 손해를 보는 시스템 단점을 개선해 충전식 방어구를 채택, 심지어 방어, 체력 보충까지 개선하고자 체력을 자동 충전식으로 바꾸거나 방어구를 삭제
3. 사망 시 아무것도 못하고 다른 팀원 구경만 해야하는 문제점을 해결하고자 부활 시스템 도입. 부활하기까지 기다려야 하는 시간 조차 개선하고자 죽으면 1:1 매치 장소를 제공해 승리시 다시 전장에 투입. 이젠 맵이 줄어들어 부활 장소가 없어지면 지켜봐야 하는 것조차 바꿔 유령처럼 맵을 같이 돌아다니면서 적의 위치를 알려줄 수 있도록 하고 죽은 적의 위치 위에서 부활할 수 있도록 개선.
4. 파밍조차 루즈해지니 죽은 적 위에 아이템을 뿌려주고 그 위를 지나가면 자동으로 먹게 해 주거나 방어 아이템조차 삭제
5. 캐릭터마다 스킬의 유용함 차이가 심하니 스킬조차 선택하도록 변경

결국 돌고 돌아 하이퍼 FPS 배틀로얄까지 도착했습니다. FPS의 코어 전투에만 집중하겠다는 목표가 느껴지지만 이게 과연 성공할까란 의문이 동시에 들기도 합니다. 더 놀라운 건 작은 게임 개발사도 아닌 유비소프트에서 도전장을 내밀었다는 점입니다.
차별화와 개선점을 보았고 이 미칠듯한 레드 오션 사이에서 성공 가능성을 봤고 투자했고 심지어 결과물이 이미 나왔습니다.
조용히 정식 출시를 기다리고 반응을 지켜보겠지만 참으로 놀랍습니다.