레이블이 Java study인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Java study인 게시물을 표시합니다. 모든 게시물 표시

2021. 3. 5.

15주차 과제: 람다식

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

목표

자바의 람다식에 대해 학습하세요.

학습할 것 (필수)

  • 람다식 사용법
  • 함수형 인터페이스
  • Variable Capture
  • 메소드, 생성자 레퍼런스


람다식 사용법

람다 표현식은 자바 8에서 추가됐습니다.

람다식은 기본적으로 함수형 인터페이스(functional interface: 단일 추상 메소드를 가진 인터페이스. 예-java.lang.Runnable)의 인스턴스 표현입니다. 추상 메소드가 하나만 포함되어 있어서 구현 시 메소드의 이름을 생략 가능합니다.

파라미터(매개변수)를 받아 값을 반환하는 짧은 코드 블록입니다.

// 하나의 파라미터
parameter -> expression

// 둘 이상의 파라미터
(parameter1, parameter2) -> expression

// 코드 블록 사용 시
(parameter1, parameter2) -> { code block }

표현식에는 제한이 있습니다. 즉시 값을 반환해야 하며 변수, 값 할당 if나 for 같은 구문을 포함할 수 없습니다. 더 복잡한 작업을 수행하려면 중괄호({ })를 사용해야 합니다. 람다식이 값을 반환해야 하는 경우에는 코드 블록에 return문이 있어야 합니다.

GUI 애플리케이션의 Lambda 표현식

키보드 동작, 마우스 동작, 스크롤 동작과 같은 graphical user interface(GUI) 응용 프로그램에서 이벤트를 처리하려면 일반적으로 특정 인터페이스 구현과 관련된 이벤트 처리기를 만듭니다. 종종 이벤트 핸들러 인터페이스는 함수형 인터페이스(functional interface)입니다.

btn.setOnAction(new EventHandler<ActionEvent>() {

    @Override
    public void handle(ActionEvent event) {
        System.out.println("Hello World!");
    }
});

'btn.setOnAction' 메소드 호출은 btn 객체가 나타내는 단추를 선택할 때 발생하는 작업을 지정합니다. 이 메소드에는 EventHandler<ActionEvent> 타입의 객체가 필요합니다. EventHandler<ActionEvent> 인터페이스에는 void handle(T event) 메소드가 하나만 포함되어 있습니다. 이 인터페이스는 함수형 인터페이스(functional interface)이므로 다음과 같이 람다식을 사용하여 대체할 수 있습니다.

btn.setOnAction(
    event -> System.out.println("Hello World!")
);

변수 할당

Callable c = () -> process();

메소드 파라미터

new Thread(() -> process()).start();

출처:
https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
https://www.w3schools.com/java/java_lambda.asp


함수형 인터페이스

함수형 인터페이스를 나타내는 '@FunctionalInterface' 문서 설명입니다.

Java 언어 사양에 정의된 함수형 인터페이스임을 나타내는데 사용하는 정보 주석 타입입니다. 개념적으로 함수형 인터페이스에는 정확히 하나의 추상 메소드가 있습니다. 기본 메소드(default method)는 구현이 있으므로 추상이 아닙니다. 인터페이스가 java.lang.Object의 공용 메소드 중 하나를 재정의하는 추상 메소드를 선언하면 java.lang.Object 또는 다른 곳에서 구현되므로 추상 메소드 수에 포함되지 않습니다.

함수형 인터페이스의 인스턴스는 람다식, 메소드 참조(reference), 생성자 참조를 사용해 만들 수 있습니다.

이 애노테이션 타입이 적혀있는 경우 컴파일러는 다음 조건이 아닌 경우 오류 메시지를 생성합니다.

  • 인터페이스 타입이며 애노테이션 타입, enum 타입, 클래스가 아닙니다.
  • 애노테이션 타입은 함수형 인터페이스의 요구 사항을 충족합니다.

하지만 컴파일러는 @FunctionalInterface 선언 여부와 관계없이 함수형 인터페이스의 정의를 충족하는 모든 인터페이스를 함수형 인터페이스로 취급합니다.

// 예시
interface FileFilter { boolean accept(File x); }
interface ActionListener { void actionPerformed(…); }
interface Callable<T> { T call(); }

@FunctionalInterface
public interface Runnable {
    // 추상 메소드가 1개
    public abstract void run();
}

@FunctionalInterface
public interface Predicate<T> {
    default Predicate<T> and(Predicate<? super T> p) {…};
    default Predicate<T> negate() {…};
    default Predicate<T> or(Predicate<? super T> p) {…};
    static <T> Predicate<T> isEqual(Object target) {…};
    // 추상 메소드가 1개이기 때문에 함수형 인터페이스
    boolean test(T t);
}

@FunctionalInterface
public interface Comparator {
    // Static, default 메소드 생략
    
    // 오직 1개인 추상 메소드
    int compare(T o1, T o2);
    // equals(객체) 메소드는 Object class에서 온 메소드로 추상 메소드 수에 포함하지 않습니다.
    boolean equals(Object obj);
}

출처:
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/FunctionalInterface.html
https://www.oracle.com/webfolder/technetwork/tutorials/moocjdk8/documents/week1/lesson-1-3.pdf


Variable Capture

로컬, 익명 클래스처럼 람다식도 변수를 캡처할 수 있습니다. 둘러싼 범위(enclosing scope) 내에서 로컬 변수에 대해 동일한 액세스 권한을 가집니다. 그러나 로컬 및 익명 클래스와 달리 람다식에는 shadowing 문제가 없습니다. 람다 표현식은 문법적으로 범위가 지정됩니다. 상위타입(supertype)에서 이름을 상속하지 않고 새로운 범위 지정을 하지 않습니다. 람다식의 선언은 둘러싼 환경에 따라 해석됩니다. 다음 예제 LambdaScopeTest는 이를 보여줍니다.

package com.test.mytest;

import java.util.function.Consumer;

public class LambdaScopeTest {

    public int x = 0;

    class FirstLevel {
        public int x = 1;

        void methodInFirstLevel (int x) {
            Consumer<Integer> myConsumer = (y) ->
            {
                System.out.println("x = " + x);
                System.out.println("y = " + y);
                System.out.println("this.x = " + this.x);
                System.out.println("LambdaScopeTest.this.x = " + LambdaScopeTest.this.x);
            };
            myConsumer.accept(x);
        }
    }

    public static void main(String[] args) {
        LambdaScopeTest st = new LambdaScopeTest();
        LambdaScopeTest.FirstLevel f1 = st.new FirstLevel();
        f1.methodInFirstLevel(23);
    }
}
x = 23
y = 23
this.x = 1
LambdaScopeTest.this.x = 0

Process finished with exit code 0

Interface Consumer<T>: 입력 인수(argument)를 받아서 결과값을 반환하지 않는 작업을 합니다. 대부분 다른 함수형 인터페이스와 달리 Consumer는 부작용을 예상하고 작동합니다.

만약 람다식 myConsumer 매개변수(parameter) y를 x로 교체하면 컴파일 오류를 생성합니다.

람다식이 새로운 범위를 지정하지 않았기 때문에 컴파일러에서 "변수 x가 methodInFirstLevel(int)에 이미 정의되어 있습니다."라는 오류를 생성합니다. 결과적으로 둘러싼 범위(enclosing scope)의 필드, 메소드, 로컬 변수에 직접 액세스 할 수 있습니다. 예를 들어 람다식은 methodInFirstLevel 메소드의 매개변수(parameter)에 직접 접근합니다. 근접 클래스(enclosing class) 변수에 접근하려면 this 키워드를 사용합니다. 예시에서 this.x는 맴버 변수 FirstLevel.x를 참조합니다.

하지만 로컬 및 익명 클래스처럼 람다식은 근접 블록 내의 final 또는 사실상 final인 로컬 변수와 매개변수(parameter)에만 접근할 수 있습니다. 예를 들어 methodInFirst 바로 뒤에 'x = 99;' 할당문을 추가한다고 가정해 봅시다.

이 할당문 때문에 FirstLevel.x 변수는 사실상 final이 아닙니다. 결과적으로 "람다식에서 참조된 로컬 변수가 final 혹은 사실상 final이어야 합니다."와 유사한 오류 메세지를 생성합니다.

출처: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#accessing-local-variables


메소드, 생성자 레퍼런스

메소드 레퍼런스

람다식을 사용해 익명 메소드를 만듭니다. 하지만 람다식은 종종 기존 메소드를 호출하는 것 외에는 아무것도 하지 않습니다. 이런 경우 기존의 메소드 이름을 참조하는 것이 더 명확합니다. 메소드 참조(reference)를 사용해 이미 이름이 있는 메소드를 간결하고 읽기 쉬운 람다식으로 사용합니다.

public class Person {

    public enum Sex {
        MALE, FEMALE
    }

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    public int getAge() {
        // ...
    }
    
    public Calendar getBirthday() {
        return birthday;
    }    

    public static int compareByAge(Person a, Person b) {
        return a.birthday.compareTo(b.birthday);
    }}

    // ...

애플리케이션의 멤버가 배열에 포함되어 있고 배열을 연령별로 정렬한다고 가정합니다.

Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);

class PersonAgeComparator implements Comparator<Person> {
    public int compare(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
        
Arrays.sort(rosterAsArray, new PersonAgeComparator());

인터페이스 Comparator는 함수형 인터페이스입니다. 따라서 Comparator를 구현(implement)하는 클래스의 새 인스턴스를 정의하고 만드는 대신 람다식을 사용할 수 있습니다.

Arrays.sort(rosterAsArray,
    (Person a, Person b) -> {
        return a.getBirthday().compareTo(b.getBirthday());
    }
);

하지만 두 Person 인스턴스의 생년월일을 비교하는 메소드는 이미 Person.compareByAge로 존재합니다. 람다식의 본문에서 이 메소드를 호출할 수 있습니다.

Arrays.sort(rosterAsArray,
    (a, b) -> Person.compareByAge(a, b)
);

람다식은 기존의 메소드를 호출하므로 람다식 대신에 메소드 레퍼런스를 사용할 수 있습니다.

Arrays.sort(rosterAsArray, Person::compareByAge);

메소드 레퍼런스 'Person::compareByAge'는 람다식 '(a, b) -> Person.compareByAge(a, b)'와 의미상 동일합니다. 다음과 같은 특징이 있습니다.

  • 형식 인자(formal parameter) 리스트 (Person, Person)는 Comparator<Person>.compare에서 복사됩니다.
  • 본문은 Person.compareByAge 메소드를 호출합니다.


생성자 레퍼런스

new 이름을 사용해 static 메소드와 동일한 방법으로 생성자를 참조할 수 있습니다. 다음 메소드는 컬렉션에서 다른 컬렉션으로 요소(elements)를 복사합니다.

public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
    DEST transferElements(
        SOURCE sourceCollection,
        Supplier<DEST> collectionFactory) {
        
        DEST result = collectionFactory.get();
        for (T t : sourceCollection) {
            result.add(t);
        }
        return result;
}

함수형 인터페이스 Supplier에는 인수(argument)를 받지 않고 객체를 반환하는 메소드가 있습니다. 따라서 다음과 같이 람다식을 사용해 'transferElements' 메소드를 호출할 수 있습니다.

Set<Person> rosterSetLambda =
    transferElements(roster, () -> { return new HashSet<>(); });

다음과 같이 람다식 대신 생성자 레퍼런스를 사용할 수 있습니다.

Set<Person> rosterSet = transferElements(roster, HashSet::new);

자바 컴파일러는 당신이 Person 타입 요소를 포함하는 HashSet collection을 만들고 싶어한다고 추론합니다. 대안으로 다음과 같이 작성할 수 있습니다.

Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);

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

2021. 2. 26.

14주차 과제: 제네릭

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

목표

자바의 제네릭에 대해 학습하세요.

학습할 것 (필수)

  • 제네릭 사용법
  • 제네릭 주요 개념 (바운디드 타입, 와일드 카드)
  • 제네릭 메소드 만들기
  • Erasure


제네릭 사용법

2004년 J2SE(Java 2 Platform, Standard Edition. 6.0 버전 이후 'Java SE'로 변경) 5.0 버전에 추가된 기능입니다. 자바의 타입 시스템을 확장해 "컴파일 시 타입 안정성을 제공하면서 다양한 타입의 객체에서 작동하는 타입 또는 메소드"를 제공하도록 설계되었습니다.

Java 컬렉션 프레임 워크는 컬렉션 인스턴스에 저장된 오브젝트 타입을 지정하는 제네릭을 지원합니다.

다음 Java 코드는 제네릭을 사용하지 않을 때 존재하는 문제를 보여줍니다.

import java.util.List;
import java.util.ArrayList;

public class Main {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("hello"); // 문자열은 정수 타입으로 형 변환이 불가능합니다.
        Integer i = (Integer) list.get(0); // 런타임 오류
    }
}
Exception in thread "main" java.lang.ClassCastException: 
    class java.lang.String cannot be cast to class java.lang.Integer 
    (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
	at com.testcompany.Main.main(Main.java:11)

코드는 오류 없이 컴파일되지만, 메인 코드의 세 번째 줄을 실행할 때 런타임 예외(java.lang.ClassCastException)가 발생합니다. 이러한 유형의 논리 오류를 제네릭을 사용하여 컴파일 시간에 감지할 수 있으며 이를 사용하는 주요 동기입니다.

import java.util.List;
import java.util.ArrayList;

public class Main {

    public static void main(String[] args) {
        List<String> v = new ArrayList<String>();
        v.add("test");
        Integer i = (Integer) v.get(0); // (타입 오류)  컴파일 오류
    }
}

cast(형 변환) 제거

다음 코드는 제네릭을 사용함으로써 캐스팅이 불필요해짐을 보여줍니다.

import java.util.List;
import java.util.ArrayList;

public class Main {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("hello");
        Strign s = (String) list.get(0);
    }
}
import java.util.List;
import java.util.ArrayList;

public class Main {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("hello");
        String s = list.get(0); // 형 변환 불필요
    }
}


- 간단한 상자 클래스

public class Box {
    private Object object;

    public void set(Object object) { this.object = object; }
    public Object get() { return object; }
}

컴파일 시간에는 클래스가 어떻게 사용됐는지 확인할 방법이 없습니다. Integer 타입이 통과하고 다른 파트에서 실수로 String 값을 통과 시켜 런타임 에러를 발생할 수 있습니다.

- 제네릭 버전 상자 클래스

제네릭 클래스는 다음과 같은 형식으로 정의합니다.

class name<T1, T2, ..., Tn> { /* ... */ }

클래스 이름 다음으로 홑화살괄호(부등호 기호)로 구분된 타입 매개변수(parameter)가 있습니다. 타입 매개변수(타입 변수라고도 합니다.)는 'T1, T2, ..., Tn'처럼 열거합니다.

public class Box<T> {
    private T t;

    public void set(T t) { this.t = t; }
    public T get() { return t; }
}

Object는 모두 T로 대체됩니다. 똑같은 테크닉으로 제네릭 인터페이스도 만들 수 있습니다.

- 타입 파라미터 명명 규칙

규칙에 따라 타입 파라미터(유형 매개변수) 이름은 단일 대문자입니다. 이미 알고 있는 변수 명명 규칙과 다르지만 그럴만한 이유가 있습니다. 이 규칙이 없으면 타입 변수와 일반 클래스 또는 인터페이스 이름의 차이를 구분하기 어려울 겁니다.

가장 일반적으로 사용되는 타입 파라미터 이름은 다음과 같습니다.

  • E - Element (자바 컬렉션 프레임워크에서 광범위하게 사용)
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S, U, V etc. - 2nd, 3rd, 4th types

- 제네릭 유형 호출 및 인스턴스화

코드 내에서 제네릭 박스 클래스를 참조하려면 T를 Integer와 같은 구체적인 값으로 대체해 호출해야 합니다.

Box<Integer> integerBox;

클래스를 인스턴스화 하려면 평소처럼 new 키워드를 사용하되 클래스 이름과 괄호 사이에 <Integer>를 넣습니다.

Box<Integer> integerBox = new Box<Integer>

- 다이아몬드

Java SE 8 이상부터 컴파일러가 컨텍스트에서 타입 인수(arguments)를 결정하거나 추론할 수 있으면 제네릭 클래스에서 생성자를 호출하는데 필요한 타입 인수를 (<>) 세트로 바꿀 수 있습니다. 비공식적으로 다이아몬드라고 합니다.

Box<Integer> integerBox = new Box<>();

출처:
https://en.wikipedia.org/wiki/Generics_in_Java
https://docs.oracle.com/javase/tutorial/java/generics/why.html


제네릭 주요 개념 (바운디드 타입, 와일드 카드)

- 바운디드 타입 파라미터

타입 인수(type arguments)를 제한하려는 경우에 사용합니다. 예를 들어 숫자에 작동하는 메소드는 Number 또는 subclass 인스턴스만 허용하려고 할 수 있습니다. 이것이 바운디드 타입 파라미터(경계 유형 매개변수)의 용도입니다.

바운디드 타입 파라미터를 선언하려면 타입 파라미터의 이름, extends 키워드, 상위 경계(upper bound)(예시에서는 Number)를 나열합니다.

이 컨텍스트에서 extends는 확장(extends;클래스에서 사용되는) 또는 구현(implements;인터페이스에서 사용되는)의 일반적인 의미로 사용됩니다.

public <U extends Number> void inspect(U u){
    System.out.println("T: " + t.getClass().getName());
    System.out.println("U: " + u.getClass().getName());
}

바운드에 정의된 메소드 실행도 가능합니다.

public class NaturalNumber<T extends Integer> {

    private T n;

    public NaturalNumber(T n)  { this.n = n; }

    public boolean isEven() {
        return n.intValue() % 2 == 0;
    }

    // ...
}

isEven 메소드는 n을 사용해 Integer 클래스에 정의된 intValue 메소드를 호출합니다.

- 와일드 카드

제네릭 코드에서 와일드카드라고 하는 물음표(?)는 알 수 없는 타입을 나타냅니다. 와일드카드는 파라미터, 필드, 지역 변수의 타입 등 다양한 상황에서 사용할 수 있습니다.

public static void process (List <? extends Number> list) {/ * ... * /}

상한 제한 와일드카드(Upper Bounded Wildcards)는 예시처럼 Number 타입 또는 상속받는 subclass를 사용합니다.

public static void printList (List <?> list) {/ * ... * /}

무제한 와일드카드(Unbounded Wildcards)는 <?>를 사용합니다.

public static void addNumbers (List <? super Integer> list) {/ * ... * /}

하위 제한 와일드카드(Lower Bounded Wildcards)는 해당 타입과 superclass로 제한합니다.


제네릭 메소드 만들기

제네릭 메소드는 자체 타입 파라미터(형식 매개변수)를 사용합니다. 제네릭 타입을 선언하는 것과 비슷하지만 타입 파라미터 제한은 메소드 범위까지만 제한됩니다. 제네릭 클래스 생성자뿐만 아니라 static, non-static 제네릭 메소드도 사용 가능합니다.

public class Util {
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
    // ...
}

메소드를 호출하는 구문은 다음과 같습니다.

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);

// 타입을 생략 가능하며 컴파일러가 필요한 타입을 추론합니다.
boolean same = Util.compare(p1, p2);


Erasure

제네릭을 구현하기 위해 Java 컴파일러는 다음과 같은 상황에서 타입 제거(type erasure)를 적용합니다.

  • 제네릭 타입의 모든 타입 파라미터를 해당 바운드나 Object(unbounded 경우)로 교체합니다. 그러므로 생성된 바이트 코드에는 일반 클래스, 인터페이스 및 메소드만 포함됩니다.
  • 타입 안전을 유지하기 위해 필요한 경우 타입 캐스트를 삽입합니다.
  • 확장된 제네릭 타입의 다형성(polymorphism)을 보존하는 브릿지(bridge) 메소드를 생성합니다.

타입 제거(type erasure)는 parameterized(매개변수화) 타입에 새 클래스가 생성되지 않도록 합니다. 결과적으로 런타임 오버헤드를 발생시키지 않습니다.

public class Node<T> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
    // ...

타입 파라미터 T가 unbounded이기 때문에 컴파일러는 Object로 교체합니다.

public class Node {

    private Object data;
    private Node next;

    public Node(Object data, Node next) {
    // ...

다음 예제는 제한된(bounded) 타입 파라미터를 사용합니다.

public class Node<T extends Comparable<T>> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
    // ...

컴파일러는 bounded 타입 파라미터 T를 첫번째 바운드 클래스 Comparable로 교체합니다.

public class Node {

    private Comparable data;
    private Node next;

    public Node(Comparable data, Node next) {
    // ...

타입 제거 프로세스 일부로 브리지 메소드를 만들어야 할 수 있습니다. 일반적으로 브리지 메소드에 대해 걱정할 필요는 없지만, 스택 추적에 나타나는 경우 당황할 수 있습니다.

public class Node {

    public Object data;

    public Node(Object data) { this.data = data; }

    public void setData(Object data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node {

    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

타입 삭제 후 메소드의 시그니쳐가 일치하지 않습니다. Node 메소드는 setData(Object)가 되고 MyNode 메소드는 setData(Integer)가 됩니다. 따라서 MyNode setData 메소드는 Node setData 메소드를 재정의하지 않습니다.

이 문제를 해결하고 다형성을 보존하기 위해 컴파일러는 subtype이 예상대로 작동하는지 확인하는 브릿지 메소드를 생성합니다.

class MyNode extends Node {

    // Bridge method generated by the compiler
    //
    public void setData(Object data) {
        setData((Integer) data);
    }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }

    // ...
}

출처: https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

2021. 2. 19.

13주차 과제: I/O

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

목표

자바의 Input과 Ontput에 대해 학습하세요.

학습할 것 (필수)

  • 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
  • InputStream과 OutputStream
  • Byte와 Character 스트림
  • 표준 스트림 (System.in, System.out, System.err)
  • 파일 읽고 쓰기


스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O

스트림

  • 입력에서 출력으로 흐르는 흐름 (단방향)
  • FIFO(First In First Out)
  • 블록킹 방식만 지원(동기)
  • 입력 스트림, 출력 스트림 필요

버퍼

  • 기본 데이터 타입을 저장할 수 있는 저장소
  • 일정한 양이 채워지면 데이터를 전송

채널

  • 쌍방향 통로
  • 데이터를 주고받을 때 버퍼를 사용
  • 입력과 출력을 위한 별도의 채널 불필요

NIO (New I/O, Non-blocking I/O)

  • 자바 1.4 버전부터 추가된 API
  • Buffer(버퍼)를 사용
  • 비동기 방식 지원
  • 스트림이 아닌 채널(Channel)을 사용


InputStream과 OutputStream

InputStream

  • 바이트 단위 입출력을 위한 최상위 입력 스트림 클래스
  • FileInputStream, BufferedInputStream, DataInputStream

표 출처: https://coding-factory.tistory.com/281


OutputStream

  • 바이트 단위 입출력을 위한 최상위 입력 스트림 클래스
  • FileOutputStream, PrintStream, BufferedOutputStream, DataOutputStream

표 출처: https://coding-factory.tistory.com/281


Byte와 Character 스트림

Byte Stream

  • 바이트 기반 입출력 스트림
  • 입출력 단위가 1byte
  • FileInputStream/FileOutputStream: 파일
  • ByteArrayInputStream/ByteArrayOutputStream: 메모리(byte배열)
  • PipedInputStream/PipedOutputStream: 프로세스(프로세스간 통신)
  • AudioInputStream/AudioOutputStream: 오디오 장치

Character Stream

  • 문자(2 byte)를 위한 스트림
  • 모든 문자 스트림 클래스는 Reader / Writer의 자손
  • 스트림 클래스로 수행 된 입력 및 출력은 로컬 문자 집합과 자동으로 변환

https://docs.oracle.com/javase/tutorial/essential/io/charstreams.html


표준 스트림 (System.in, System.out, System.err)

콘솔을 통한 데이터 입력과 출력을 위한 스트림

  • System.in - 콘솔로부터 데이터 입력받는데 사용
  • System.out - 콘솔 화면에 문자열 출력을 위한 용도
  • System.err - System.out과 같이 콘솔 문자열 출력이지만 버퍼링을 지원하지 않고 빨간색으로 출력된다.


파일 읽고 쓰기

채널 I/O를 사용한 파일 읽고 쓰는 방법

스트림 I/O가 한 번에 문자를 읽는 동안 채널 I/O는 한 번에 버퍼를 읽습니다. ByteChannel 인터페이스는 기본으로 read write 기능을 제공합니다. SeekableByteChannel은 ByteChannel의 위치를 유지하거나 변경할 수 있는 기능이 있습니다. 또한 채널과 관련된 파일 자르기 및 파일 크기 쿼리를 지원합니다.

채널 I/O를 읽고 쓰는 방법에는 두 가지가 있습니다.

  • newByteChannel(Path, OpenOption...)
  • newByteChannel(Path, <Set? extends OpenOption>, FileAttribute<?>...)

다음 코드 조각은 파일을 읽고 표준 출력으로 인쇄합니다.

// Defaults to READ
try (SeekableByteChannel sbc = Files.newByteChannel(file)) {
    ByteBuffer buf = ByteBuffer.allocate(10);

    // Read the bytes with the proper encoding for this platform.  If
    // you skip this step, you might see something that looks like
    // Chinese characters when you expect Latin-style characters.
    String encoding = System.getProperty("file.encoding");
    while (sbc.read(buf) > 0) {
        buf.rewind();
        System.out.print(Charset.forName(encoding).decode(buf));
        buf.flip();
    }
} catch (IOException x) {
    System.out.println("caught exception: " + x);

UNIX 및 기타 POSIX 파일 시스템 용으로 작성된 다음 예제는 특정 파일 권한 집합을 사용하여 로그 파일을 만듭니다. 이 코드는 로그 파일을 만들거나 이미있는 경우 로그 파일에 추가합니다. 로그 파일은 소유자에 대한 읽기 / 쓰기 권한과 그룹에 대한 읽기 전용 권한으로 생성됩니다.

import static java.nio.file.StandardOpenOption. *; 
import java.nio. *; 
import java.nio.channels. *; 
import java.nio.file. *; 
import java.nio.file.attribute. *; 
import java.io. *; 
import java.util. *; 

public class LogFilePermissionsTest { 

  public static void main (String [] args) { 
  
    // 파일에 추가하기위한 옵션 세트를 만듭니다. 
    Set <OpenOption> options = new HashSet<OpenOption>(); 
    options.add(APPEND); 
    options.add(CREATE); 

    // 사용자 지정 권한 속성을 만듭니다. 
    Set <PosixFilePermission> perms = 
      PosixFilePermissions.fromString ("rw-r-----"); 
    FileAttribute <Set<PosixFilePermission>>
      PosixFilePermissions.asFileAttribute(perms); 

    // 문자열을 ByteBuffer로 변환합니다. 
    String s = "Hello World!"; 
    byte data[] = s.getBytes (); 
    ByteBuffer bb = ByteBuffer.wrap(data); 
    
    경로 파일 = Paths.get ("./permissions.log"); 

    try (SeekableByteChannel sbc = 
      Files.newByteChannel (file, options, attr)) { 
      sbc.write (bb); 
    } catch (IOException x) { 
      System.out.println ("예외 발생: "+ x); 
    } 
  } 
}

출처: https://docs.oracle.com/javase/tutorial/essential/io/file.html

2021. 2. 5.

12주차 과제: 애노테이션

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

목표

자바의 애노테이션에 대해 학습하세요.

학습할 것 (필수)

  • 애노테이션 정의하는 방법
  • @retention
  • @target
  • @documented
  • 애노테이션 프로세서


애노테이션 정의하는 방법

// 골뱅이(@, at sign) 기호는 컴파일러에게 주석임을 알려줍니다.
// 예) @재정의
@Override
void mySuperMethod() { ... }

자바 애노테이션(Annotations)이란?

메타 데이터(metadata: 다른 데이터를 설명해 주는 데이터)의 한 형태인 주석(Annotations)은 프로그램의 일부는 아니지만, 프로그램에 대한 데이터를 제공합니다.

주석은 코드 작동에 직접적인 영향을 주지 않습니다.

주석은 다음과 같은 여러 용도가 있습니다.

  • 컴파일러에 대한 정보 - 컴파일러에서 주석을 사용하여 오류를 감지하거나 경고를 억제할 수 있습니다.
  • 컴파일 시간, 배포 시간 처리 - 주석 정보를 처리해 코드, XML 파일 등을 생성할 수 있습니다.
  • 런타임 처리 - 일부 주석은 런타임에 검사 가능
@SuppressWarnings (value = "unchecked") 
void myMethod () {...}

값이 하나만 있으면 다음과 같이 생략 가능합니다.

@SuppressWarnings ("unchecked")

주석 유형이 동일한 경우 반복 주석이라고 합니다.

@Author (name = "Kim") 
@Author (name = "Park") 
class MyClass {...}

반복 주석은 Java SE 8 릴리스부터 지원됩니다.

클래스, 필드, 메소드 및 기타 프로그램 요소 선언에도 주석을 사용할 수 있습니다.

자바 SE 8 릴리스부터 주석을 타입으로 적용할 수도 있습니다.

// 클래스 인스턴스 생성
new @Interned MyObject();

// 타입 캐스트
myString = (@NonNull String) str;

// 구현 절(implements clause)
class UnmodifiableList<T> implements
        @Readonly List<@Readonly T> { ... }

// 예외 처리
void monitorTemperature() throws
        @Critical TemperatureException { ... }

많은 애노테이션이 코드의 주석(코멘트)을 대체합니다.

소프트웨어 개발 그룹이 전통적으로 클래스 시작을 다음과 같이 중요한 정보를 제공하는 주석으로 시작한다고 가정해 봅시다.

public class MyClass extends MySuperClass {

   // Author: Tester1
   // Date: 2/01/2021
   // Current revision: 3
   // Last modified: 1/31/2021
   // By: Kim
   // Reviewers: Alice, Bill, Cindy

}

애노테이션을 사용해 똑같은 메타데이터를 추가합니다. 먼저 애노테이션 타입을 정의합니다.

@interface ClassPreamble {
   String author();
   String date();
   int currentRevision() default 1;
   String lastModified() default "N/A";
   String lastModifiedBy() default "N/A";
   // Note use of array
   String[] reviewers();
}

애노테이션 타입 정의는 인터페이스 정의와 비슷하게 interface 키워드 앞에 @를 붙입니다. 애노테이션 타입은 인터페이스의 한 형태입니다.

애노테이션 타입을 정의한 후 다음과 같이 값을 채워 타입으로 사용할 수 있습니다.

@ClassPreamble (
   author = "Tester1",
   date = "2/01/2021",
   currentRevision = 3,
   lastModified = "1/31/2021",
   lastModifiedBy = "Kim",
   reviewers = {"Alice", "Bob", "Cindy"}
)
public class MyClass extends MySuperClass { ... }

참고: @ClassPreamble의 정보가 Javadoc 문서에 나타나도록 하려면 @ClassPreamble 정의에 @Documented 주석을 추가해야 합니다.

// @Documented 사용하기 위해 import
import java.lang.annotation.*;

@Documented
@interface ClassPreamble {
   // 애노테이션 요소 정의
}

출처:
https://docs.oracle.com/javase/tutorial/java/annotations/


@Retention

애노테이션 타입이 있는 애노테이션이 얼마나 보존될지를 나타냅니다. 타입 선언에 Retention 애노테이션이 없는 경우 retention 정책 기본값은 RetentionPolicy.CLASS 입니다.

  • RetentionPolicy.SOURCE - 소스 수준에서만 유지되며 컴파일러에서 무시됩니다.
  • RetentionPolicy.CLASS - 컴파일 타임에 컴파일러에 의해 유지되지만 JVM에서는 무시됩니다.
  • RetentionPolicy.RUNTIME - JVM에 의해 유지되므로 런타임 환경에서 사용할 수 있습니다.

Retention.java

package java.lang.annotation;

/**
 * Indicates how long annotations with the annotated type are to
 * be retained.  If no Retention annotation is present on
 * an annotation type declaration, the retention policy defaults to
 * {@code RetentionPolicy.CLASS}.
 *
 * A Retention meta-annotation has effect only if the
 * meta-annotated type is used directly for annotation.  It has no
 * effect if the meta-annotated type is used as a member type in
 * another annotation type.
 *
 * @author  Joshua Bloch
 * @since 1.5
 * @jls 9.6.4.2 @Retention
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class MyTest {
    public static void main(String[] args) {
    
    @Retention(RetentionPolicy.RUNTIME)
//    @Retention(RetentionPolicy.CLASS)
//    @Retention(RetentionPolicy.SOURCE)
    @Target(ElementType.METHOD)
    /* @Target({
            ElementType.PACKAGE, // 패키지 선언시
            ElementType.TYPE, // 타입 선언시
            ElementType.CONSTRUCTOR, // 생성자 선언시
            ElementType.FIELD, // 멤버 변수 선언시
            ElementType.METHOD, // 메소드 선언시
            ElementType.ANNOTATION_TYPE, // 어노테이션 타입 선언시
            ElementType.LOCAL_VARIABLE, // 지역 변수 선언시
            ElementType.PARAMETER, // 매개 변수 선언시
            ElementType.TYPE_PARAMETER, // 매개 변수 타입 선언시
            ElementType.TYPE_USE // 타입 사용시
    }) */
    public @interface MyAnnotation {
        /* enum 타입을 선언할 수 있습니다. */
        public enum Quality {BAD, GOOD, VERYGOOD}
        /* String은 기본 자료형은 아니지만 사용 가능합니다. */
        String value();
        /* 배열 형태로도 사용할 수 있습니다. */
        int[] values();
        /* enum 형태를 사용하는 방법입니다. */
        Quality quality() default Quality.GOOD;
    }
}

코드 출처: https://jdm.kr/blog/216

출처:
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/Retention.html

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/Target.html

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/ElementType.html


@Target

애노테이션을 어디에 적용할 수 있는지 나타내는 컨텍스트입니다.

@Target 메타-애노테이션은 선언된 유형 자체가 메타 주석 유형임을 나타냅니다. 주석 유형 선언에서만 사용할 수 있습니다.

    @Target(ElementType.ANNOTATION_TYPE)
    public @interface MetaAnnotationType {
        ...
    }

이 @Target 메타 주석은 복잡한 주석 유형 선언에서 맴버 유형으로 사용하기위한 것입니다. 직접 주석을 다는데는 사용할 수 없습니다.

    @Target({})
    public @interface MemberType {
        ...
    }

ElementType 상수가 @Target 애노테이션에 두 번 이상 나타나는 것은 컴파일 타임 오류입니다.

    @Target({ElementType.FIELD, ElementType.METHOD, ElementType.FIELD})
    public @interface Bogus {
        ...
    }

출처: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/Target.html


@Documented

'Documented 주석 처리된 경우 javadoc과 같은 기본 도구는 출력시 해당 유형의 주석을 표시하지만 Documented가 없는 주석은 표시되지 않습니다.'

javadoc(소스 코드의 문서 주석을 HTML 형식으로 API 문서를 생성하는 도구)으로 api 문서를 만들 때 어노테이션에 대한 설명도 포함하도록 지정하는 메타테그

주석 포맷 설명: https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html#format

출처: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/Documented.html


애노테이션 프로세서

애노테이션 프로세서는 컴파일 타임에 주석을 스캔하고 처리하기 위해 javac로 빌드된 도구입니다.

주석을 만들고 처리하는 단계

  • 주석 선언
  • 주석 프로세서 구현
  • 코드에서 주석 사용

AbstractProcessor

package com.example;

public class MyProcessor extends AbstractProcessor {

	@Override
	public synchronized void init(ProcessingEnvironment env){ }

	@Override
	public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }

	@Override
	public Set<String> getSupportedAnnotationTypes() { }

	@Override
	public SourceVersion getSupportedSourceVersion() { }

}
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;

@SupportedAnnotationTypes("Todo")   // (1)
@SupportedSourceVersion(SourceVersion.RELEASE_8)   // (2)
public class TodoProcessor extends AbstractProcessor{   // (3)
    // (4)
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        
    }
}

(1) SupportedAnnotationTypes는 클래스가 처리할 주석의 유형을 알려주는 메타 주석입니다. 이 경우 클래스는 Todo 주석을 처리합니다.

(2) SupportedSourceVersion 이 주석 프로세서가 지원하는 최신 Java 버전입니다.

(3) 주석 프로세서는 Processor 인터페이스를 구현 하거나 AbstractProcessor 클래스를 확장해야 합니다.

(4) AbstractProcessor 클래스의 프로세스 메서드를 재정의 합니다 . 컴파일러는 응용 프로그램의 모든 클래스에서이 메서드를 호출합니다.

출처:
https://docs.oracle.com/en/java/javase/11/docs/api/java.compiler/javax/annotation/processing/AbstractProcessor.html

https://thetechstack.net/how-to-process-annotations-in-java/

https://www.baeldung.com/java-annotation-processing-builder

https://iammert.medium.com/annotation-processing-dont-repeat-yourself-generate-your-code-8425e60c6657

http://hannesdorfmann.com/annotation-processing/annotationprocessing101/

https://medium.com/@jason_kim/annotation-processing-101-%EB%B2%88%EC%97%AD-be333c7b913

2021. 1. 29.

11주차 과제: Enum

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

목표

자바의 열거형에 대해 학습하세요.

학습할 것 (필수)

  • enum 정의하는 방법
  • enum이 제공하는 메소드 ( values()와 valueOf() )
  • java.lang.Enum
  • EnumSet


enum 정의하는 방법

public enum Season {
    SPRING(3, 4, 5),
    SUMMER(6, 7, 8),
    AUTUMN(9, 10, 11),
    WINTER(12, 1, 2);

    Season(int i, int i1, int i2) {
        this.start = i;
        this.middle = i1;
        this.end = i2;
    }
    private final int start;
    private final int middle;
    private final int end;

    public int getStart() {
        return start;
    }

    public int getMiddle() {
        return middle;
    }

    public int getEnd() {
        return end;
    }
}
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class MySeasonTest {
    Season season;

    public MySeasonTest(Season season) {
        this.season = season;
    }

    public void showMeTheSeason() {
        System.out.println(season.getStart());
        System.out.println(season.getMiddle());
        System.out.println(season.getEnd());
    }

    @Test
    @DisplayName("main")
    public static void main(String[] args) {
        MySeasonTest mySeasonTest
                = new MySeasonTest(Season.WINTER);
        mySeasonTest.showMeTheSeason();
    }
}
> Task :MySeasonTest.main()
12
1
2

BUILD SUCCESSFUL in 415ms
3 actionable tasks: 2 executed, 1 up-to-date
  • Java의 enum 타입은 클래스
  • 상수이기에 변수명은 대문자
  • 암시적으로 java.lang.Enum을 상속(extends)
  • enum 생성자는 반드시 package-private, private 접근
  • enum 시작 부분에 정의된 상수를 자동으로 생성
  • enum 생성자를 직접 호출 불가능
  • 암시적으로 public static final

출처, 참고:
https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html

https://docs.oracle.com/javase/specs/jls/se13/html/jls-8.html#jls-8.9.2

https://woowabros.github.io/tools/2017/07/10/java-enum-uses.html


enum이 제공하는 메소드 ( values()와 valueOf() )

    @Test
    @DisplayName("main")
    public static void main(String[] args) {
        MySeasonTest mySeasonTest
                = new MySeasonTest(Season.WINTER);

        // valueOf()
        Season tempSeason 
                = mySeasonTest.season.valueOf("SUMMER");
        System.out.println("tempSeason.getStart(): " 
                + tempSeason.getStart());

        // values()
        for (Season s :
                Season.values()) {
            System.out.println(s + ", " 
                    + s.getStart() + ", " 
                    + s.getMiddle() + ", " 
                    + s.getEnd());
        }
        
        // ordinal()
        for (Season s :
                Season.values()) {
            System.out.println(
                    s + " at index " + s.ordinal());
        }
    }
> Task :MySeasonTest.main()

// valueOf()
tempSeason.getStart(): 6

// values()
SPRING, 3, 4, 5
SUMMER, 6, 7, 8
AUTUMN, 9, 10, 11
WINTER, 12, 1, 2

// ordinal()
SPRING at index 0
SUMMER at index 1
AUTUMN at index 2
WINTER at index 3

BUILD SUCCESSFUL in 411ms
3 actionable tasks: 2 executed, 1 up-to-date
  • valueOf(): 지정한 이름의 enum 상수를 반환
  • values(): 해당 enum의 모든 상수를 저장한 배열을 생성하여 반환
  • ordinal(): enum 상수의 순서값 반환
  • toString(): enum 상수의 이름을 반환
  • name(): enum 상수의 이름을 반환

toString() 메소드가 사용자에게 더 친숙한 이름을 반환하므로 name() 메소드보다 우선적으로 사용해야 합니다. (Most programmers should use the toString() method in preference to this one, as the toString method may return a more user-friendly name.)

출처: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Enum.html#name()


Enum 바이트코드(ByteCode)

public enum Day {
    SUNDAY, MONDAY, TUESDAY
}
javap -c Day.class

Compiled from "Day.java"
public final class com.testcompany.Day extends java.lang.Enum<com.testcompany.Day> {
  public static final com.testcompany.Day SUNDAY;

  public static final com.testcompany.Day MONDAY;

  public static final com.testcompany.Day TUESDAY;

  public static com.testcompany.Day[] values();
    Code:
       0: getstatic       #13  // Field $VALUES:[Lcom/testcompany/Day;
       3: invokevirtual   #17  // Method "[Lcom/testcompany/Day;".clone:()Ljava/lang/Object;
       6: checkcast       #18  // class "[Lcom/testcompany/Day;"
       9: areturn

  public static com.testcompany.Day valueOf(java.lang.String);
    Code:
       0: ldc             #1  // class com/testcompany/Day
       2: aload_0
       3: invokestatic    #22 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
       6: checkcast       #1  // class com/testcompany/Day
       9: areturn

  static {};
    Code:
       0: new             #1  // class com/testcompany/Day
       3: dup
       4: ldc             #32 // String SUNDAY
       6: iconst_0
       7: invokespecial   #33 // Method "<init>":(Ljava/lang/String;I)V
      10: putstatic       #3  // Field SUNDAY:Lcom/testcompany/Day;
      13: new             #1  // class com/testcompany/Day
      16: dup
      17: ldc             #34 // String MONDAY
      19: iconst_1
      20: invokespecial   #33 // Method "<init>":(Ljava/lang/String;I)V
      23: putstatic       #7  // Field MONDAY:Lcom/testcompany/Day;
      26: new             #1  // class com/testcompany/Day
      29: dup
      30: ldc             #35 // String TUESDAY
      32: iconst_2
      33: invokespecial   #33 // Method "<init>":(Ljava/lang/String;I)V
      36: putstatic       #10 // Field TUESDAY:Lcom/testcompany/Day;
      39: invokestatic    #36 // Method $values:()[Lcom/testcompany/Day;
      42: putstatic       #13 // Field $VALUES:[Lcom/testcompany/Day;
      45: return
}
  • 놀랍게도 바이트코드 내부에 'public static final' 선언
  • values(), valueOf()도 미리 준비
  • 메소드의 형태로 static 필드에 선언

바이트 코드 설명

  • getstatic: 클래스의 static 필드값 가져오기
  • invokevirtual: 인스턴스 메서드 호출
  • checkcast: 객체의 타입 확인
  • areturn: 메소드 참조 반환 (Return reference from method)
  • ldc: 런타임 상수 풀에 아이템 푸시
  • aload_<n>: 지역변수 레퍼런스 로드
  • invokestatic: static 메서드 호출
  • new: 새로운 객체 생성
  • dup: 연산자 스택의 맨 위의 값 복사
  • iconst_<i>: int 상수 푸시
  • invokespecial: 생성자, private 메소드, 슈퍼 클래스의 메소드 호출
  • putstatic: static 필드 선언
  • return: Return void from method
여기서 관심을 가질 부분은 "Ljava/lang/String;"과 마지막의 "V"이다. 자바 바이트코드의 표현에서 "L;"은 클래스 인스턴스이다. 즉, addUser() 메서드는 java/lang/String 객체 하나를 파라미터로 받는 메서드이다. 이 사례의 라이브러리에서는 파라미터가 변경되지 않았으므로 이 파라미터는 정상이다. 위 메시지 마지막의 "V"는 메서드의 반환값을 나타낸다. 자바 바이트코드 표현에서 "V"는 반환값이 없음을 의미한다.

참고, 출처:
https://d2.naver.com/helloworld/1230

https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings

https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-6.html


java.lang.Enum

public abstract class Enum<E extends Enum<E>>
        implements Constable, Comparable<E>, Serializable {
    private final String name;
    
    @NotNull
    public final String name() {
        return name;
    }
    
    private final int ordinal;
    
    @Range(from = 0, to = java.lang.Integer.MAX_VALUE)
    public final int ordinal() {
        return ordinal;
    }
    
    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }
    
    public String toString() {
        return name;
    }
    
    ...
}
  • 모든 자바 열거형의 기본 클래스
  • Enum은 추상 클래스. Enum 클래스를 생성할 수 없다.
  • final 메소드로 선언되므로 수정이 불가능


EnumSet

// Creates an enum set containing all of the elements 
// in the specified element type.
// 명시한 요소 타입이 모두 들어간 enum 세트 생성
EnumSet<Season> myEnumSet = EnumSet.allOf(Season.class);
System.out.println("allOf(Season.class): " + myEnumSet);

// Creates an empty enum set with the specified element type.
// 명시한 요소 타입을 사용해 빈 enum 세트 생성
myEnumSet = EnumSet.noneOf(Season.class);
System.out.println("noneOf(Season.class): " + myEnumSet);

// Creates an enum set initially containing the specified element.
// 생성시 명시한 요소가 들어간 enum 세트 생성
myEnumSet = EnumSet.of(Season.SPRING, Season.AUTUMN);
System.out.println("of(Season.SPRING, Season.AUTUMN): " + myEnumSet);

// Creates an enum set with the same element type 
// as the specified enum set, initially containing 
// all the elements of this type that are not contained 
// in the specified set.
// 명시한 세트가 포함되지 않은 enum 세트 생성
myEnumSet = EnumSet.complementOf(myEnumSet);
System.out.println("complementOf(myEnumSet): " + myEnumSet);

// Creates an enum set initially containing all of the elements 
// in the range defined by the two specified endpoints.
// 지정된 두 요소 범위안의 enum 세트를 생성
myEnumSet = EnumSet.range(Season.SUMMER, Season.AUTUMN);
System.out.println("range(Season.SPRING, Season.AUTUMN): " + myEnumSet);
allOf(Season.class): [SPRING, SUMMER, AUTUMN, WINTER]
noneOf(Season.class): []
of(Season.SPRING, Season.AUTUMN): [SPRING, AUTUMN]
complementOf(myEnumSet): [SUMMER, WINTER]
range(Season.SPRING, Season.AUTUMN): [SUMMER, AUTUMN]

Process finished with exit code 0
  • enum 타입과 함께 사용하기 위한 특수 Set의 구현
  • 모든 요소는 enum 셋을 만들 때 명시적으로 또는 암시적으로 지정된 단일 enum 타입을 가져와야 함
  • 내부적으로 bit vector로 표시됨 (매우 간결하고 효율적)
  • 이 클래스의 공간 및 시간 성능은 기존의 int 기반 "비트 플래그"의 대안으로 고품질에 타입 안전으로 사용할 수 있을 만큼 충분히 우수해야 함

출처: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/EnumSet.html


2021. 1. 22.

10주차 과제: 멀티쓰레드 프로그래밍

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

목표

자바의 멀티쓰레드 프로그래밍에 대해 학습하세요.

학습할 것 (필수)

  • Thread 클래스와 Runnable 인터페이스
  • 쓰레드의 상태
  • 쓰레드의 우선순위
  • Main 쓰레드
  • 동기화
  • 데드락


Thread 클래스와 Runnable 인터페이스

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println("Runnable thread");
    }

    public static void main(String args[]) {
        (new Thread(new MyRunnable())).start();
    }

}
public class MyThread extends Thread {

    public void run() {
        System.out.println("extends thread");
    }

    public static void main(String args[]) {
        (new MyThread()).start();
    }

}

출처: https://docs.oracle.com/javase/tutorial/essential/concurrency/runthread.html


쓰레드의 상태

NEW - 아직 시작되지 않은 스레드 상태

RUNNABLE - Java 가상 머신에서 실행중인 스레드 상태

BLOCKED - 모니터 잠금을 기다리면서 차단 된 스레드 상태

WAITING - 다른 스레드가 특정 작업을 수행 할 때까지 무기한 대기중인 스레드 상태

TIMED_WAITING - 다른 스레드가 지정된 대기 시간까지 작업을 수행하기를 기다리는 상태

TERMINATED - 종료 된 스레드 상태

public class MyThread implements Runnable {
    @Override
    public void run() {
        // moving thread2 to timed waiting state
        try {
            Thread.sleep(1500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("State of thread1 while it 
        called join() method on thread2 -"
        + MyTest.thread1.getState());
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class MyTest implements Runnable {
    public static Thread thread1;
    public static MyTest obj;

    public static void main(String[] args) {
        obj = new MyTest();
        thread1 = new Thread(obj);

        // thread1 created and is currently 
        // in the NEW state.
        System.out.println("State of thread1 after 
        creating it - " + thread1.getState());
        thread1.start();

        // thread1 moved to Runnable state
        System.out.println("State of thread1 after calling 
        .start() - " 
        + thread1.getState());
    }

    @Override
    public void run() {
        MyThread myThread = new MyThread();
        Thread thread2 = new Thread(myThread);

        // thread1 created and is currently in the NEW
        // state.
        System.out.println("State of thread2 after
        creating it - " + thread2.getState());
        
        thread2.start();

        // thread2 moved to Runnable state
        System.out.println("State of thread2 after calling
        .start() - " + thread2.getState());

        // moving thread1 to timed waiting state
        try {
            //moving thread1 to timed waiting state
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("State of thread2 after calling
        .sleep() - " + thread2.getState());

        try {
            // waiting for thread2 to die
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("State of thread2 when it has
        finished - " + thread2.getState());
    }
}
State of thread1 after creating it - NEW
State of thread1 after calling .start() - RUNNABLE
State of thread2 after creating it - NEW
State of thread2 after calling .start() - RUNNABLE
State of thread2 after calling .sleep() - TIMED_WAITING
State of thread1 while it called join() - WAITING
State of thread2 when it has finished - TERMINATED

Process finished with exit code 0

코드 출처: https://www.geeksforgeeks.org/lifecycle-and-states-of-a-thread-in-java/


쓰레드의 우선순위

public static int MIN_PRIORITY: 최소 우선 순위. 값: 1

public static int NORM_PRIORITY: 기본 우선 순위. 값: 5. 명시하지 않을 경우 기본값.

public static int MAX_PRIORITY: 스레드의 최대 우선 순위. 값: 10


public final int getPriority(): java.lang.Thread.getPriority() 주어진 스레드의 우선 순위를 리턴

public final void setPriority(int newPriority): java.lang.Thread.setPriority() 스레드의 우선 순위를 newPriority 값으로 변경. newPriority의 값이 minimum(1) maximum(10) 한계를 초과하면이 IllegalArgumentException 발생

public class ThreadDemo extends Thread {

    public void run() {
        System.out.println("Inside run method");
    }

    public static void main(String[] args) {
        ThreadDemo t1 = new ThreadDemo();
        ThreadDemo t2 = new ThreadDemo();
        ThreadDemo t3 = new ThreadDemo();

        // Default 5
        System.out.println("t1 thread priority : "
                + t1.getPriority());

        // Default 5
        System.out.println("t2 thread priority : "
                + t2.getPriority());

        // Default 5
        System.out.println("t3 thread priority : "
                + t3.getPriority());

        t1.setPriority(2);
        t2.setPriority(5);
        t3.setPriority(8);

        // t3.setPriority(21); will throw
        // IllegalArgumentException

        // 2
        System.out.println("t1 thread priority : "
                + t1.getPriority());

        // 5
        System.out.println("t2 thread priority : "
                + t2.getPriority());

        // 8
        System.out.println("t3 thread priority : "
                + t3.getPriority());

        // Main thread

        // Displays the name of
        // currently executing Thread
        System.out.println(
                "Currently Executing Thread : "
                + Thread.currentThread().getName());

        System.out.println(
                "Main thread priority : "
                + Thread.currentThread().getPriority());

        // Main thread priority is set to 10
        Thread.currentThread().setPriority(10);
        System.out.println(
                "Main thread priority : "
                + Thread.currentThread().getPriority());
    }
}
t1 thread priority : 5
t2 thread priority : 5
t3 thread priority : 5
t1 thread priority : 2
t2 thread priority : 5
t3 thread priority : 8
Currently Executing Thread : main
Main thread priority : 5
Main thread priority : 10

Process finished with exit code 0

코드 출처: https://www.geeksforgeeks.org/java-thread-priority-multithreading/


Main 쓰레드

Java 프로그램이 시작되면 하나의 스레드가 즉시 실행. 프로그램이 시작될 때 실행되는 스레드이기 때문에 일반적으로 프로그램의 메인 스레드라고 한다.

"자식"스레드가 생성되는 스레드

public class MyTestThread extends Thread {

    public static void main(String[] args) {
        // getting reference to Main thread
        Thread t = Thread.currentThread();
        // getting name of Main thread
        System.out.println(
                "Current thread: "
                        + t.getName());
        // changing the name of Main thread
        t.setName("Geeks");
        System.out.println(
                "After name change: "
                        + t.getName());
        // getting priority of Main thread
        System.out.println(
                "Main thread priority: "
                        + t.getPriority());
        // setting priority of Main thread to MAX(10)
        t.setPriority(MAX_PRIORITY);
        System.out.println(
                "Main thread new priority: "
                        + t.getPriority());
    }
}
Current thread: main
After name change: Geeks
Main thread priority: 5
Main thread new priority: 10

Process finished with exit code 0

코드 출처: https://www.geeksforgeeks.org/main-thread-java/


동기화

public class SynchronizedCounter {
    private int c = 0;

    public synchronized void increment() {
        c++;
    }

    public synchronized void decrement() {
        c--;
    }

    public synchronized int value() {
        return c;
    }
}

1. 동기화(synchronized) 메소드를 동일한 객체에서 두 번 호출하는 인터리빙(interleave - 스레드가 수행되는 순서)이 불가능해진다. 첫 스레드가 동기화 메소드를 실행 완료할 때까지 같은 블록의 동기화 메소드를 실행한 모든 스레드는 실행을 일시 중단하고 기다린다.

2. 동기화 메소드가 종료되면 같은 객체의 동기화된 메소드의 다음 호출 관계를 자동으로 설정. 모든 스레드에 객체의 상태 변경 사항을 보여준다.

생성자는 동기화가 불가능. 생성자를 동기화하는 것은 의미가 없습니다. 객체를 생성하는 스레드만이 접근할 수 있어야 하기 때문.

출처: https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html


데드락

public class Deadlock {
    static class Friend {
        private final String name;
        public Friend(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
        public synchronized void bow(Friend bower) {
            System.out.format("%s: %s"
                + "  has bowed to me!%n", 
                this.name, bower.getName());
            
            // 교착상태를 만드는 부분
            bower.bowBack(this);
        }
        public synchronized void bowBack(Friend bower) {
            System.out.format("%s: %s"
                + " has bowed back to me!%n",
                this.name, bower.getName());
        }
    }

    public static void main(String[] args) {
        final Friend alphonse = new Friend("Alphonse");
        final Friend gaston = new Friend("Gaston");
        
        new Thread(new Runnable() {
            public void run() { alphonse.bow(gaston); }
        }).start();
        
        new Thread(new Runnable() {
            public void run() { gaston.bow(alphonse); }
        }).start();
    }
}

코드 출처: https://docs.oracle.com/javase/tutorial/essential/concurrency/deadlock.html

동기화된 bowBack 메소드를 호출하지만 동기화된 bow 메소드 종료를 기다리기에 교착상태(Deadlock) 발생


2021. 1. 15.

9주차 과제: 예외 처리

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

목표

자바의 예외 처리에 대해 학습하세요.

학습할 것 (필수)

  • 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
  • 자바가 제공하는 예외 계층 구조
  • Exception과 Error의 차이는?
  • RuntimeException과 RE가 아닌 것의 차이는?
  • 커스텀한 예외 만드는 방법


자바에서 예외 처리 방법 (try, catch, throw, throws, finally)

public class MyClass {
    public static void main(String[] args) {
        try {
        
            // 예외를 발생시킬 수 있는 코드
            int[] myArray = {1, 2, 3};
            System.out.println(myArray[3]);
            
        } catch (Exception e) {
        
            // 오류 발생 시 실행할 코드
            System.err.println(e.getMessage());
            
        } finally {
        
            // try 블록 종료 시 항상 실행되는 코드
            System.out.println("try catch block end");
        }
    }
}
Index 3 out of bounds for length 3
try catch block end

Process finished with exit code 0
public class MyClass {

    private static void checkAge(int age) 
            throws ArithmeticException {

        if (age < 18) {
            // throw를 사용해 특정 Exception으로 예외를 던진다.
            throw new ArithmeticException("Access denied");
        } else {
            System.out.println("Access granted");
        }
    }
    public static void main(String[] args) {
        checkAge(20);
    }
}
Exception in thread "main"
java.lang.ArithmeticException: Access denied
	at com.company.MyClass.checkAge(MyClass.java:7)
	at com.company.MyClass.main(MyClass.java:13)

출처: https://www.w3schools.com/java/java_try_catch.asp

throw / throws 비교

  • 메소드 안에서 예외를 사용 / 메소드의 블럭에서 예외를 받아 처리
  • 1개의 예외 선언 가능 / 여러개의 예외 선언 가능

https://www.w3schools.com/java/ref_keyword_throws.asp

https://docs.oracle.com/javase/tutorial/essential/exceptions/try.html

https://docs.oracle.com/javase/tutorial/essential/exceptions/catch.html

https://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html


자바가 제공하는 예외 계층 구조

출처: https://www.manishsanger.com/java-exception-hierarchy/

Throwable

  • 예외처리의 최상위 클래스
  • Serializable 인터페이스의 구현
  • throwable에는 생성 당시 스레드의 실행 스택 스냅샷이 포함

https://www.geeksforgeeks.org/throwable-class-in-java-with-examples/

Error

  • 예상치 못한 심각한 문제
  • 처리 기술로 복구할 수 없는 조건 (비정상적인 프로그램 종료의 원인)
  • 런타임에 발생 (ex 메모리 부족, 시스템 충돌 오류)

Exception

  • 포착하려는 조건의 문제
  • 런타임시 발생하는 조건으로 프로그램이 종료 될 수 있지만 try catch, throw 키워드로 복구 가능
  • checked / unchecked 예외 두 가지 범주

https://docs.oracle.com/javase/tutorial/essential/exceptions/throwing.html


Exception과 Error의 차이는?

출처: https://www.geeksforgeeks.org/errors-v-s-exceptions-in-java/


RuntimeException과 RE가 아닌 것의 차이는?

Checked Exception

  • 반드시 예외를 처리해야 함
  • 컴파일 단계에서 확인

Unchecked Exception

  • 예외 처리를 강제하지 않음
  • 실행 단계에서 확인
  • RuntimeException 하위 예외


커스텀한 예외 만드는 방법

class MyException extends Exception {
    public MyException(String errorMessage) {
    	// 부모의 생성자 호출
        super(errorMessage); 
    } 
}

public class Main { 
    public static void main(String args[]) { 
        try
            throw new MyException("메세지"); 
        } 
        catch (MyException ex) { 
            System.out.println("Caught");
            System.out.println(ex.getMessage()); 
        }
    }
}


2021. 1. 8.

8주자 과제: 인터페이스

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

목표

자바의 인터페이스에 대해 학습하세요.

학습할 것 (필수)

  • 인터페이스 정의하는 방법
  • 인터페이스 구현하는 방법
  • 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
  • 인터페이스 상속
  • 인터페이스의 기본 메소드 (Default Method), 자바 8
  • 인터페이스의 static 메소드, 자바 8
  • 인터페이스의 private 메소드, 자바 9


인터페이스 정의하는 방법

public interface MyInterface extends MyInterface1, 
MyInterface2, MyInterface3 {
    void doExample(int i, double x);
    int doExampleSomething(String s);
}

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

인터페이스 선언은 접근 지시자, interface 키워드, 인터페이스 이름, 쉼표로 구분된 상위 인터페이스 목록(있는 경우) 및 인터페이스 본문으로 구성

서브 클래스처럼 다른 인터페이스를 확장하거나 다른 클래스를 확장 가능

클래스는 하나의 다른 클래스만 확장할 수 있지만 인터페이스는 여러 인터페이스를 확장 가능

Interface Body

인터페이스 본문에는 추상 메소드, 기본 메소드, static 메소드가 포함될 수 있다.

인터페이스의 모든 추상, 기본, static 메소드는 암시적으로 공용이므로 public을 생략 가능

인터페이스에 정의된 모든 상숫값은 암시적으로 public, static 및 final


인터페이스 구현하는 방법

샘플 인터페이스, MyInterface

public interface MyInterface {
    public int isCompareTo(MyInterface other);
}

MyInterface 인터페이스 구현

public class MyRectangle implements MyInterface {

    // 가로 * 세로
    public int getArea() {
        return width * height;
    }

    // MyInterface 인터페이스 구현을 위한 메소드
    @Override
    public int isCompareTo(MyInterface other) {
        MyRectangle otherRect 
            = (MyRectangle)other;
        if (this.getArea() < otherRect.getArea())
            return -1;
        else if (this.getArea() > otherRect.getArea())
            return 1;
        else
            return 0;               
    }
}

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

MyInterface 인터페이스를 구현(implements)해 MyRectangle 클래스 안에서 사각형의 크기를 계산하는 메소드 isCompareTo()를 오버라이딩(overriding;재정의)

MyRectangle는 MyInterface을 구현(implements)하기 때문에 두 MyRectangle 객체의 크기를 비교 가능

Note: isCompareTo 메소드는 MyInterface 타입의 객체를 사용합니다. MyRectangle 타입 캐스팅은 컴파일러에 실제 객체가 무엇인지 알려줍니다. 다른 인스턴스(other.getArea())에서 직접 getArea를 호출하면 다른 인스턴스가 실제로 MyRectangle 인스턴스임을 이해하지 못하기 때문에 컴파일에 실패합니다.


인터페이스 레퍼런스를 통해 구현체를 사용하는 방법

새로운 인터페이스를 정의할 때 새로운 레퍼런스 데이터 타입으로 정의할 수 있다. 데이터 타입 이름을 사용할 수 있는 모든 곳에서 인터페이스 이름을 사용할 수 있다.

public Object findLargest(Object object1, Object object2) {
    MyInterface obj1 = (MyInterface) object1;
    MyInterface obj2 = (MyInterface) object2;
    if ((obj1).isLargerThan(obj2) > 0)
        return object1;
    else
        return object2;
}

object1을 MyInterface 타입으로 캐스팅하여 isLargerThan 메소드를 호출할 수 있다.

다양한 클래스에서 MyInterface을 구현(implementing)하려는 경우 두 객체가 동일한 클래스인 경우 클래스 중 하나에서 인스턴스화 된 객체를 findLargest()로 비교가 가능하다. 비슷하게 다음과 같이 비교할 수 있다.

public Object findSmallest(Object object1, Object object2) {
    MyInterface obj1 = (MyInterface) object1;
    MyInterface obj2 = (MyInterface) object2;
    if ((obj1).isLargerThan(obj2) < 0)
        return object1;
    else
        return object2;
}

public boolean isEqual(Object object1, Object object2) {
    MyInterface obj1 = (MyInterface) object1;
    MyInterface obj2 = (MyInterface) object2;
    if ((obj1).isLargerThan(obj2) == 0)
        return true;
    else
        return false;
}


인터페이스 상속

클래스와 인터페이스의 중요한 차이점은 클래스는 필드를 가질 수 있지만 인터페이스는 가질 수 없다. 추가로 클래스의 객체 생성이 가능하지만 인터페이스는 불가능 하다.

객체는 클래스에 정의된 필드의 상태를 저장한다. 자바 언어가 둘 이상의 클래스를 확장할 수 없는 이유는 여러 클래스에서 필드를 상속하는 기능인 다중 상속 문제를 방지하기 위한 것.

인터페이스에는 필드가 포함되지 않기 때문에 다중 상속으로 인해 발생하는 문제에 대해 걱정할 필요가 없다.

구현(implementation)의 다중상속은 여러 클래스에서 메소드의 정의를 상속하는 기능

다중상속에서 이름 충돌 및 모호성과 같은 문제가 발생하는데 컴파일러가 슈퍼 클래스의 같은 이름의 메소드를 만나면 어떤 멤버나 메소드를 실행하거나 접근해야 하는지를 결정할 수 없는 경우가 있다. 또한 슈퍼 클래스에 새 메소드를 추가하여 무의식적으로 이름 충돌을 일으킬 수 있다. 기본 메소드(Default methods)는 다중 상속의 한 형태를 소개한다. 클래스는 동일한 이름을 가진 기본 메소드를 포함한 인터페이스를 2개 이상 구현할 수 있다. 컴파일러는 특정 클래스가 사용하는 기본 메소드를 결정하는 규칙을 제공한다.

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


인터페이스의 기본 메소드 (Default Method), 자바 8

산업 표준 인터페이스를 발표하는 컴퓨터 제어 자동차 제조업체의 예를 들어 설명해 봅시다. 컴퓨터로 제어되는 자동차 제조업체가 비행과 같은 새로운 기능을 자동차에 추가하면 어떻게 될까요? 제조업체는 다른 회사(예: 전자 유도 기기 제조업체)가 소프트웨어를 적용하도록 특별히 새로운 메소드가 필요할 겁니다. 이러한 새로운 비행 관련 메소드를 어디에 선언해야 할까요? 만약 오리지널 인터페이스에 추가하면 해당 인터페이스를 구현한 프로그래머는 구현을 다시 작성해야 합니다. 만약 static 메소드로 추가하면 프로그래머는 필수 핵심 메소드가 아닌 유틸리티 메소드로 간주할 것입니다.

기본 메소드(default methods)를 사용하면 라이브러리의 인터페이스에 새 기능을 추가하고 이전 버전용으로 작성된 코드와 바이너리 호환성을 보장할 수 있습니다.

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

public interface MyInterface {
    default int getData(int i) {
        return i;
    }
}


인터페이스의 static 메소드, 자바 8

public interface MyInterface {
    static public int getData(String myString) {
        try {
            return 1;
        } catch (Exception e) {
            System.err.println("error");
            return -1;
        }
    }
}

클래스의 모든 인스턴스는 static 메소드를 공유

라이브러리에서 helper 메소드를 쉽게 구성할 수 있다.


인터페이스의 private 메소드, 자바 9

Java 9부터 인터페이스에 private 메소드와 private static 메소드 추가 가능

인터페이스 내부의 코드 재사용성을 향상

private 메소드는 인터페이스 내에서만 사용 가능

private static 메소드는 다른 static or non-static 인터페이스 메소드 안에서 사용 가능

private non-static 메소드는 private static 메소드 내부에서 사용 불가능

public interface MyInterface {
    private int add(int nums) {
        return nums + 1;
    }
}

출처: https://howtodoinjava.com/java9/java9-private-interface-methods/

링크1: 인터페이스의 장점에 대해 모르겠습니다. 납득좀 시켜주세요

링크2: 객체지향 개발 5대 원리: SOLID

2021. 1. 1.

7주차 과제: 패키지

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

목표

자바의 패키지에 대해 학습하세요.

학습할 것 (필수)

  • package 키워드
  • import 키워드
  • 클래스패스
  • CLASSPATH 환경변수
  • -classpath 옵션
  • 접근지시자


package 키워드

//in the Draggable.java file
package graphics;
public interface Draggable {
    . . .
}

//in the Graphic.java file
package graphics;
public abstract class Graphic {
    . . .
}

//in the Circle.java file
package graphics;
public class Circle extends Graphic
    implements Draggable {
    . . .
}

//in the Rectangle.java file
package graphics;
public class Rectangle extends Graphic
    implements Draggable {
    . . .
}

//in the Point.java file
package graphics;
public class Point extends Graphic
    implements Draggable {
    . . .
}

//in the Line.java file
package graphics;
public class Line extends Graphic
    implements Draggable {
    . . .
}
  • package 선언은 소스 파일 최상단에
  • 1개의 소스 파일은 1개의 패키지만 선언 가능
  • 1개의 소스 파일에 여러 타입을 넣는 경우 한 개의 타입은 'public'이며 소스 파일 이름과 동일해야 함
    ex) 'public class Circle'의 소스 파일 이름 'Circle.java', 'public interface Draggable'의 소스 파일 이름 'Draggable.java', 'public enum Day'의 소스 파일 이름 'Day.java'
  • 'public' 타입과 동일한 파일에 'private' 타입을 포함할 수 있지만('private' 타입이 작고 'public' 타입과 밀접한 관련이 있는 경우를 제외하고는 강력히 권장하지 않음) 'public' 타입만 외부에서 접근 가능

전 세계 프로그래머가 Java 언어를 사용하여 클래스와 인터페이스에 같은 이름을 사용할 가능성이 높다. 예) java.awt 패키지에 이미 Rectangle 클래스가 있을 때 Rectangle 클래스를 정의. 그래도 컴파일러는 두 클래스가 다른 패키지에 있을 경우 같은 이름을 허락한다. Rectangle 클래스의 정규화 이름(fully qualified name)에 패키지 이름이 포함된다. graphics 패키지의 Retangle 클래스의 정규화 이름은 'graphics.Rectangle', java.awt 패키지의 Retangle 클래스의 정규화 이름은 'java.awt.Rectangle'.

두명의 프로그래머가 동일한 패키지 이름을 사용하지 않는한 잘 작동합니다. 이 문제를 방지하는 방법은? 규칙 (Convention)

패키지 명명 규칙

  • 패키지 이름은 클래스 or 인터페이스 이름과 충돌하지 않도록 모두 소문자로 작성
  • 역방향 인터넷 도메인 이름을 사용 ex) example.com에서 만든 mypackage 패키지 → com.example.mypackage
  • 회사 내에서 발생하는 이름 충돌은 회사 이름 뒤에 지역 or 프로젝트 이름 (ex: com.example.region.mypackage)을 포함하여 사내 규칙에 따라 처리)
  • Java 언어 안의 패키지는 java or javax로 시작
  • 도메인 이름이 패키지 이름으로 적절하지 않거나 하이픈, 특수문자, Java 키워드, 이름규칙에 안맞는 숫자 문자가 포함될 경우 다음 예시와 같이 처리

출처: https://docs.oracle.com/javase/tutorial/java/package/createpkgs.html


import 키워드

// 정규화 이름 사용 예
graphics.Rectangle myRect = new graphics.Rectangle();
import graphics.Rectangle;
. . .
// import 사용 예
Rectangle myRectangle = new Rectangle();
. . .
import graphics.*;
. . .
// 패키지 내 모든 내용 import 사용 예
Circle myCircle = new Circle();
Rectangle myRectangle = new Rectangle();
. . .
// 패키지가 계층적으로 보이지만 사실 그렇지 않음
// 예) java.awt.* 이 java.awt 패키지의 모든 내용을 가져올 것 같지만
// java.awt.color, java.awt.font 같은 패키지 내용을 가져오지 않음
import java.awt.*;
import java.awt.color.*;

가져온 두 패키지의 맴버 이름이 동일하면 정규화 이름을 사용

java.awt.Rectangle rect1;
graphics.Rectangle rect2;

Static Import

하나 or 두 개의 클래스에서 static final fields (constants 상수)와 static 메소드에 자주 접근해야 하는 상황이 있습니다. 이러한 클래스 이름을 반복해서 붙이면 코드가 복잡해질 수 있습니다. static import 선언을 사용하면 상수와 static 메소드를 가져오는 방법을 제공하므로 해당 클래스의 접두사를 지정할 필요가 없습니다.

java.lang.Math 클래스는 PI 상수와 많은 static 메소드, 사인, 코사인, 탄젠트 등등을 포함합니다. 예를 들어

public static final double PI 
    = 3.141592653589793;
public static double cos(double a)
{
    ...
}

일반적으로 다른 클래스의 객체를 사용하려면 다음과 같이 클래스 이름을 접두사로 지정

double r = Math.cos(Math.PI * theta);

static import를 사용하면 'java.lang.Math'의 static members를 가져올 수 있으므로 Math 접두사를 붙일 필요가 없습니다. Math의 static members는 개별적으로 불러올 수 있습니다.

import static java.lang.Math.PI;
import static java.lang.Math.*;

// 불러온 static members는 제한없이 사용할 수 있다.
double r = cos(PI * theta);

// 자주 사용하는 상수와 static 메소드를 포함하는 클래스를 작성한 다음
// import 문을 사용할 수 있습니다.
import static mypackage.MyConstants.*;

'static import'는 매우 절약해서 사용하세요. 과도하게 사용하면 코드를 읽는 사람이 특정 static 객체가 어느 클래스에서 정의됐는지 알 수 없기 때문에 코드를 읽고 유지 관리하기 어려울 수 있습니다. 올바르게 사용하면 static import 사용해 클래스 이름의 반복을 제거해 코드를 더 쉽게 읽을 수 있습니다.

출처: https://docs.oracle.com/javase/tutorial/java/package/usepkgs.html


클래스패스

컴파일러와 JVM이 .class 파일을 찾을 수 있도록 설정하는 경로

<출력 파일의 상위 디렉터리 경로>\com\example\graphics\Rectangle.class
<출력 파일의 상위 디렉터리 경로>\com\example\graphics\Helper.class
<path_one>\sources\com\example\graphics\Rectangle.java
<path_two>\classes\com\example\graphics\Rectangle.class

이렇게 하면 소스를 공개하지 않고 다른 프로그래머에게 클래스 디렉토리를 제공할 수 있다. 또한 컴파일러와 JVM이 프로그램에서 사용하는 모든 타입을 찾을 수 있도록 이러한 방식으로 소스 및 클래스 파일을 관리해야 한다.

현재 CLASSPATH 값 확인
In Windows:   C:\> set CLASSPATH
In UNIX:      % echo $CLASSPATH

현재 CLASSPATH 값 삭제
In Windows:   C:\> set CLASSPATH=
In UNIX:      % unset CLASSPATH; export CLASSPATH

CLASSPATH 설정 예시
In Windows:
C:\> set CLASSPATH=C:\users\george\java\classes
In UNIX:
% CLASSPATH=/home/george/java/classes; export CLASSPATH

출처: https://docs.oracle.com/javase/tutorial/java/package/managingfiles.html


CLASSPATH 환경변수

읽어보기: https://hyoje420.tistory.com/7

https://effectivesquid.tistory.com/entry/자바-클래스패스classpath란


-classpath 옵션

클래스 경로를 지정하는 기본 방법은 -cp 명령어를 사용. 이를 통해 다른 애플리케이션에 영향을 주지 않고 각 애플리케이션에 개별적으로 CLASSPATH를 설정할 수 있다.

javac -cp C:\Java\jdk1.7.0\bin;C:\Windows\System32\
java -cp C:\Java\jdk1.7.0\bin;C:\Windows\System32\

출처: https://docs.oracle.com/javase/tutorial/essential/environment/paths.html


접근지시자

패키지 레벨과 관련있는 접근지시자

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

public: 제한없이 접근 가능

protected: 자신의 패키지 내에서만 접근 가능

default(접근 지시자가 없는 경우): 자신의 패키지 내에서만 접근 가능

private: 클래스 내에서만 접근 가능

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;
    }

}