클래스란 무엇일까?

클래스는 어떠한 객체들에 대한 분류이다.

  • 분류, 집합, 같은 속성과 기능을 가진 객체들을 총칭하는 개념이다.
  • 클래스는 해당 객체가 무엇인지 판단할 수 있는 일종의 식별 타입이 된다.

 

클래스의 구성요소

  • 변수 : 클래스의 Scope = { } 안에서 존재하는 변수를 말한다.

    • 객체 변수 : 접근 제어자와 변수 타입, 변수 명을 가지는 일반적인 상태 변수를 말한다.

      • 해당 변수는 클래스 정보에서 초기화되지 않고, 해당 타입을 가지는, 상속받은 객체가 생성되었을 때 Scope에 맞게 가시 된다. (공유된다)
    • 클래스 변수 : 위의 요소를 가지면서, static 이 작성된 상태 변수를 말한다.

      • 클래스가 로드되는 시점Class 정보가 저장되는 영역에서 해당 정보가 등록, 초기화 되기 때문에 이러한 명칭을 가진다. (Perm Gen, Meta space)
      • 모든 영역에서 가시되는 성질을 가진다.
  • 메서드 : 이것도 클래스의 Scope = { } 안에서 존재한다.

    • 객체 메서드 : 접근 제어자와 반환 타입, 메서드 명, 매개변수를 가지는 메서드를 말한다.

      • 인스턴스 변수와 마찬가지로 객체가 생성되었을 때 Scope에 맞게 가시 된다.
    • 클래스 메서드 : 위의 요소를 가지면서, static이 작성된 상태 변수를 말한다.

      • 클래스 변수와 마찬가지로 클래스 로드 시에 등록, 초기화된다.
      • 클래스를 생성하는 메서드인 팩토리 메서드를 정의할 때 static을 사용한다.
      • 어떠한 기능을 사용하기 위해 해당 타입의 객체를 생성하는 것이 무리가 있을 때, (유틸 성 기능의 필요함으로) static을 적용한다.
        • 예 : String.valueOf(), String.join(), String.format() 등
  • 생성자 : 당연히.. 클래스의 { } 안에 있다.

    • 객체를 생성할 때 사용되는 특별한 메서드를 말한다.
      • 별도의 생성자를 작성하지 않을 경우 인자가 없는 생성자가 자동으로 생성된다.
      • 생성 시점부터 필요한 상태 변수가 있을 경우 매개 변수를 가지는 생성자를 이용한다.

 

클래스의 생성 방법

Public final class FullName {

        // 어떠한 분류(클래스 타입)에 속하는 객체들이 가지는 상태 변수 : 객체의 상태 
        private final String firstName;
        private final String lastName;

        // 객체가 가지는 상태 값을 조작, 제공하는 메서드 : 객체의 행위이자 책임이다.

        // 메서드 선언부 public String getFirstName() : 객체가 수행해야할 책임
        // -> 인터페이스 구현, 추상 클래스의 상속을 통한 추상적인 책임은 무조건 구현하여햐 한다.

        // 메서드 구현부 { } : 객체가 해당 책임을 수행하는 방법. (객체의 자율적인 행위)
        public String getFirstName() {
                return this.firstName;
        }

        public String getLastName() {
                return this.lastName;
        }

        // 객체를 생성할 때 사용되는 생성자 : 인자 값을 통한 초기화를 진행하거나, 별도 로직을 수행한다.
        public FullName(String first, String last) {
                this.firstName = first;
                this.lastName = last;
        }
        
        public FullName() {
        	this("dev","lob");
        }
}

 

클래스에서 사용될 수 있는 수식자

  • 접근 제어자 : 적용된 키워드에 따라 해당 객체, 메서드가 가시 될 범위를 결정한다.

    • public : 모든 클래스에서 해당 클래스를 참조 가능하다.
    • package-private : 해당 클래스가 포함된 패키지 내에서만 참조 가능하다. (지정이 없는 경우)
    • protect : 서브, 하위 클래스와 동일 패키지 내부에 존재하는 클래스만 참조 가능하다.
    • private : 자기 자신인 객체 내부에서만 참조 가능하다. (인터페이스도 JDK9부터 사용 가능)
  • abstract : 해당 클래스, 메서드에서 구현부를 가지지 않고 상속 시 구현을 강제하는 지정 방식이다.

    • 추상 클래스, 인터페이스에서 사용된다.
  • static : 해당 클래스가 인스턴스화 되어있지 않아도 사용 가능하다는 지정 방식이다.

    • 위에서도 언급하였지만 생성 방식과 시점이 다르다.
  • final : 해당 지정자가 작성된 것에 대해 상속이나 변경을 금지한다는 의미이다.

    → 일종의 관례로써 전역적으로 사용되는 static 요소들은 final을 붙인다고 한다.

    → 하지만 private가 지정된 메서드나 final class에 대해선 명시하지 않아도 된다.

    • 객체 변수 : 그 필드의 값이 변경되는 것을 금지한다.

      → 값을 해당 필드에서 초기화하지 않는다면, 생성자를 통한 초기화가 강제된다.

    • 객체 메서드 : 해당 클래스 타입을 상속받는 서브, 하위 클래스에서 오버라이드를 금지한다.

    • 클래스 자신 : 해당 클래스의 상속을 금지한다. (String, Integer.... 등)

  • transient : 객체 직렬화 시에 직렬화 대상에서 제외한다. (이 경우 null로 처리된다.)

  • volatile : 해당 지정자가 붙은 변수에 대해서는 스레드가 값을 캐시하지 않고, 메인 메모리에서 처리한다.

    • 시간에 따라서 각 쓰레드가 참조하는 값이 달라지는 것을 방지한다.

    • 하나의 쓰레드가 값을 증가시키고, 여러 쓰레드가 값을 읽는 경우 항상 최신의 값을 제공하기 위해서는 해당 지정자를 사용하는 것이 좋다.

      → 여러 쓰레드가 값을 증가시키고, 읽는다면 Atomic Variable을 사용하자.

  • sysnchronized : 해당 지정자가 붙은 메서드와 스코프에 스레드 간 동기화를 진행한다는 의미이다.

    • (이후에 멀티 쓰레딩이라는 주제가 있으니 그때 알아보자.)
  • native : 해당 지정자가 붙은 메서드는 Java 가 아닌 네이티브 코드를 사용하는 것을 의미한다.

    • C/C++ 등으로 작성된 DDL이나 JNI에서 제공하는 코드를 말한다.
  • strictfp : 해당 지정자가 붙은 Double, Float에 대하여서 IEEE 754 규격을 적용한다는 의미이다.

    • 인터페이스나 메서드에 지정할 수 있다.

 

객체를 생성하는 방법?

객체를 생성하는 방법은 new 키워드를 사용하는 것이다.

// public static void main 함수나 그외 메서드 영역, 클래스에서 사용한다.

// 클래스 타입, 인스턴스(레퍼런스) 변수명 = new 키워드 생성자(인자...); 의 형태를 가진다.
// new 를 이용한 객체의 생성을 인스턴스화라고 한다.
FullName fullName = new FullName("babo", "Lob");

객체를 생성하면, 해당 객체는 바로 Heap 영역에서 생성된다. 이때 final을 제외한 모든 변수는 초기화되지 않은 상황이 되는데, 이 경우 초깃값을 가지게 된다.

  • char = "\u0000"
  • byte, short, int = 0
  • long = 0L
  • float = 0.0F
  • double = 0.0D
  • boolean = false
  • 배열, 레퍼런스 변수 = null

 

 

올바른 객체지향 시점 (내가 매번 복기하는 3가지 문장)

  • 클래스는 객체지향의 핵심이 아니다. 클래스는 객체를 구현하는 메커니즘이자 설계도일 뿐이다.
  • 클래스는 결국 비슷한 특징을 가지는 객체들의 분류를 의미한다.
  • 클래스는 어떠한 값을 담는 것이 중요한 게 아니다. 객체가 되었을 때, 제공할 행위가 중요하다.

 

메서드를 정의하는 방법?

 

메서드 시그니처?

메서드 정의 시에 메서드의 이름과 매개 변수들을 의미한다.

 

다음 주제에서 정리해야 될 것으로 예상되는 오버 로딩을 제공하기 위함이다.

// {public, private} {static, strictfp, sysnchronized} {Types, void} 메서드명 (인자들) {} 

public String getFirstName() {
        return this.firstName;
}

 

메서드의 기본적인 동작

반환될 값의 타입이 존재한다면 (void 가 아니라면) return을 통한 값 전달이 강제된다.

  • return을 통한 값 전달은 쉽게 생각하면, 메서드가 호출된 곳에 값을 주는 것이다.

 

생성자를 정의하는 방법?

생성자에 대해서도 메서드 시그니처가 적용된다. 이는 점층적 생성자 패턴을 통해 알 수 있다.

 

전달하는 인자에 의해서 호출되는 생성자가 달라진다.

  • 점층적으로 생성되는 생성자가 5개 이상으로 늘어난다면 코드가 복잡해진다.. 이때에는 빌더 패턴을 고려해보자. (이펙티브 자바)
public SomeClass() {
}

SomeClass some = new SomeClass();

public SomeClass(String a) {
        this.aa = a;
}

SomeClass some = new SomeClass(a, b);

public SomeClass(String a, String b, String c) {
        this.aa = a;
        this.bb = b;
        this.cc = c;
}

SomeClass some = new SomeClass(a, b, c);

...... 

 

this 키워드 이해하기

this 참조 변수는 객체가 자기 자신을 참조하는 데 사용하는 키워드이다.

즉 this는 해당 객체의 참조 값 (hashcode 값)을 가지고 있는 것이다.

@Test
public void helloThis(){
    Animal animal = new Animal();
    animal.showThis();
    System.out.println(animal);
}

public class Animal {

    public Animal() {
    }

    public void showThis() {
        System.out.println(this);
    }
}
notefive.oop.Animal@4eb7f003
notefive.oop.Animal@4eb7f003

 

결과를 보면 System.out.println(this), System.out.println(animal)가 동일한 결과를 출력함을 알 수 있다.

객체를 println으로 출력할 때는 인스턴스를 출력할 경우 toString()을 호출한다는 것은 알 것이라고 생각한다.

재정의가 되지 않았을 경우, Object의 toString()을 호출한다.

문자를 Bold 처리한 부분을 보면 출력되는 정보에 뒷부분에 hashCode()를 사용함을 볼 수 있다.

// Object의 toString
public String toString() {
    return getClass().getName() + "@" + **Integer.toHexString(hashCode());**
}

 

this는 언제 사용하는가?

  • 객체 변수와 같은 이름을 가진 다른 것이 존재할 때 객체 변수를 가리키고 있음을 명확히 한다.
String name;
public void someMethod(String name) {
    this.name = name;
}
  • 생성자에서 다른 클래스의 생성자를 호출하는 데 사용할 수 있다.
// 해당 코드는 밑의 생성자를 호출한다.
public SomeClass() {
        this("someString");
}

public SomeClass(String a) {
        this.aa = a;
}
  • 현재 Java 객체를 매개 변수로 전달하는 데 사용한다.
public void SomeClassA{
    public void run() {
        SomeClassB someClassB = new SomeClassB();
        someClassB.getClass(this);
    }
  }

public void SomeClassB{
public void getClass (SomeClassA some){
        // Do SomeThing
    }
}
  • 현재 객체를 반환하는 데 사용할 수 있다. 해당 코드는 빌더 패턴에서 볼 수 있다.
public Builder name(int val) {
    name = val;   // 사용되는 변수명이 다르므로 this를 생략 가능하다.
    return this;  // 객체 자기 자신을 반환함으로써 참조 연산자 . 을 이용한 체이닝이 가능하다.
}

 

5주 차 과제 BinaryTree 구현하기

트리 (Tree)

  • 나무의 형태를 뒤집은 것 같은 형태의 자료구조이며, 각 노드는 M개만큼의 자식 노드를 가지게 된다. 루트 노드부터 시작하여 각 노드는 단반향 간선을 통해서 연결되어 있으며, 하위 레밸에서도 트리가 반복되는 모습을 보이게 되어있다.
  • 트리의 길이는 출발한 노드부터 목적지 노드까지의 거쳐야 하는 가짓수를 의미한다.
  • 트리의 깊이는 루트 노드부터 특정 노드까지의 길이를 의미한다.

 

이진트리 (Binary Tree)

  • 모든 노드의 자식 노드가 최대 2개까지인 트리를 말한다.

 

Binary Tree의 종류

  • 이진 탐색 트리 (Binary Search Tree)

    • Root 노드의 값을 기준하여 좌, 우측에 값을 나누어 저장하는 반 정렬 상태의 이진트리이다.
    • 기본적으로 좌측의 서브 트리, 노드들은 Root 노드보다 작으며, 우측은 크다.
    • 중복된 값을 허용하지 않는다.
  • 정 이진트리 (Full Binary Tree)

    • 마지막 래벨의 노드(리프 노드)를 제외한 모든 노드가 두 자식을 가지고 있는 트리를 말한다.
  • 완전 이진트리

    • 마지막 레밸을 제외하고는 모든 레밸이 완전히 채워져 있는 상태의 트리를 말한다.
    • 마지막 레밸의 노드를 채울 때에는 가장 왼쪽부터 채우게 된다.

 

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

  • int value, Node left, right를 가지고 있어야 합니다.
public class Node {

    private int element;
    private Node left;
    private Node right;

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

    public Node(int element, Node left, Node right) {
        this.element = element;
        this.left = left;
        this.right = right;
    }

    public int getElement() {
        return element;
    }

    public Node getLeft() {
        return left;
    }

    public Node getRight() {
        return right;
    }
}

Lob-dev/DashBoardDemo

 

 

BinrayTree라는 클래스를 정의하고 주어진 노드를 기준으로 출력하는 bfs(Node node)와 dfs(Node node) 메서드를 구현하세요.

  • DFS는 왼쪽, 루트, 오른쪽 순으로 순회하세요.
public class BinaryTree {

    public void dfs(Node node) {
        if (node == null) {
            return;
        }
        dfs(node.getLeft());
        System.out.print(node.getElement() + " ");
        dfs(node.getRight());
    }

    public void bfs(Node node) {
        if (node == null) {
            return;
        }
        Queue<Node> queue = new ArrayDeque<>();
        queue.offer(node);
        while (!queue.isEmpty()) {
            traverse(queue);
        }
    }

    private void traverse(Queue<Node> queue) {
        Node poll = queue.poll();
        if (poll != null){
            System.out.print(poll.getElement() + " ");
            getChild(queue, poll.getLeft());
            getChild(queue, poll.getRight());
        }
    }

    private void getChild(Queue<Node> queue, Node child) {
        if (child != null) {
            queue.add(child);
        }
    }
}

Lob-dev/DashBoardDemo

 

테스트 코드

Lob-dev/DashBoardDemo

 

참고자료

+ Recent posts