개발/Java

Java Class

Debin 2022. 5. 20.
반응형

백기선님이 과거에 진행했던 Java 스터디 5주차 스터디 입니다.

클래스 정의하는 방법

클래스를 정의하는 방법을 알기 위해서는 클래스에 대해 알아야한다.

아래는 클래스, 객체, 인스턴스에 대한 내용을 정리한 포스팅이다.

https://devdebin.tistory.com/93?category=1004578 

 

클래스, 객체 , 인스턴스

클래스란? 객체를 정의해 놓은 것. 객체의 설계도 또는 틀이다. 객체를 생성하는 데 사용하지만, 객체 그 자체는 아니다. 기능(메서드)과 속성(필드)을 가진다. 객체란? 우리가 주변에서 볼 수 있

devdebin.tistory.com

필자가 정리한 클래스의 의미는 '객체(인스턴스)를 생성하기 위한 설계도'이다.

클래스를 정의하기 위해서는 다양한 고려 사항이 있다.

  1. 클래스명
  2. 멤버 변수(필드, 속성, 상태, 특성이라고도 한다)
  3. 메서드 (함수, 행위라고도 한다)
  4. 접근 제어자
  5. 다형성과 상속
  6. 생성자

자세하게 들어가면 더 많이 존재하겠지만 크게 이 정도가 있다고 생각한다.

이제 예시 코드를 통해 클래스 정의 방법을 알아보자. 일단 상속과 다형성은 제외했다.

public class Member{ //접근 제어자를 public으로 설정한 클래스명이 Member인 클래스

    private String name; //접근 제어자가 private인 멤버 변수
    private int age; //접근 제어자가 private인 멤버 변수
    
    public Member (){ //기본 생성자
    
    }
    
    public Member (String name, int age) { //접근 제어자를 public으로 설정한 생성자
    	this.name = name;
        this.age = age;
    }
    
    public int getOld(int riceCakeSoupCount){ //public 메서드 떡국의 개수 만큼 나이를 먹는다.
    	this.age += riceCakeSoupCount;
    	return this.age;
    }
    
    public void announce(){ //public 메서드 멤버의 이름을 발표한다.
    	System.out.println("My name is " + this.name + "!!!!!");
    }
}
  • 쉽게 이해하게 몇 가지 포인트를 집어보자.
  • 객체들은 메서드를 통해 상호 작용을 이룬다. 메시지를 주고 받는다고 이해할 수 있겠다.
  • 그래서 객체가 할 행동(맡은 책임)은 메서드로 만들고 public하게 지정했다.
  • 객체가 유지하는 상태는 멤버 변수로 지정한다. (age와 name)
  • 캡슐화가 잘 된 설계는 객체가 서로 메시지를 보낼 때 (상호작용)객체의 내부에서 무슨 일이 일어나는지는 모르지만,
    원하는 결과만 받는 것이다. 따라서 메서드를 사용하는 것이지 서로의 상태(멤버변수),로직(메서드 동작)에는 전혀 관심이 없다.
  • 더불어 캡슐화를 진행했다고 해도 내부 외부는 구분되지만, 내부의 정보가 감춰지는 것이 아니다.
  • 따라서 은닉화를 위해 멤버 변수 접근 제어자를 private으로 지정했다.
  • 한 번 과정을 설명해봤다. 이렇게 객체지향적으로 생각하면서 클래스를 설계하면 된다.

객체 만드는 방법

먼저 크게 2가지 키워드가 있다. 바로 생성자와 new 연산자다.

 

생성자란?

생성자는 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드다.

생성자 역시 메서드처럼 클래스 내에 선언되며, 생성자도 오버로딩이 가능해 하나의 클래스에 여러 개의 생성자가 존재할 수 있다.

생성자의 조건은 2가지가 있다.

  1. 생성자의 이름은 클래스의 이름과 같아야 한다.
  2. 생성자는 리턴 값이 없다.

주의 할점은 new연산자가 인스턴스를 생성하는 것이지 생성자가 인스턴스를 생성하는 것이 아니다.

생성자는 단지 인스턴스 변수(static 키워드가 붙지 않은 멤버 변수)들의 초기화에 사용되는 특별한 메서드다.

결론은 멤버 변수 초기화에 사용되는 것이 생성자다!!!!

 

new 연산자란?

먼저 아래와 같은 코드를 살펴보자. 객체(인스턴스)를 생성하는 new + 생성자다.

Member member = new Member();
  1. 연산자 new에 의해서 메모리 (heap)에 Member 클래스의 인스턴스가 생성된다. 즉 new 연산자가 메모리 공간을 할당한다.
  2. 생성자 Member()가 호출되어 수행된다. (멤버변수 초기화)
  3. 연산자 new의 결과로, 생성된 인스턴스의 주소가 반환되어 참조변수 member에 저장된다.
  4. 이제 member 참조 변수를 통해 만들어진 Member 인스턴스에 접근이 가능하다.

결론은 인스턴스를 힙 메모리에 생성하는 것이 new 연산자다!!!!

메소드 정의하는 방법

메소드를 정의할 때는 크게 몇 가지 정할 것이 있다.

  • 접근제어자
  • return 타입 
  • 메소드 이름
  • 매개 변수 
  • 메서드 바디 내부 코드

이제 예시 코드로 확인해보자. getCarInfo에서 위 체크 리스트를 모두 확인할 수 있다.

public class Car {
    private String name;
    private String color;
    
    Car(String name, String color){
        this.name = name;
        this.color = color;
    }
    
    public String getCarInfo(boolean CarOwner){
        if(CarOwner == true){
            String result = name + " 이름을 가진 " + color " 차의 주인이다";
            return result;
        }
        return "차 주인이 아니다";
    }
}

생성자 정의하는 방법

우선 위에서 언급한 것처럼 생성자는 조건이 있다.

생성자의 이름은 클래스 이름과 같고, 리턴 값이 없다는 것이다. 생성자도 메서드이므로 오버로딩이 가능하다.

오버로딩에 대한 포스팅은 아래 링크에서 확인할 수 있다.

https://devdebin.tistory.com/99

 

오버로딩과 오버라이딩

오늘은 오버로딩과 오버라이딩에 대해 공부해보겠습니다. 단어가 유사하다 보니 처음에 공부하시는 분들이 많이 헷갈려합니다. 오버로딩 한 클래스 내에 같은 이름의 메서드를 여러 개 정의하

devdebin.tistory.com

이제 예시 코드를 살펴보자.

public class Member {
    private String name;
    private int age;
    private String job;
    
    Member(){
    }
    
    Member(String n){
        name = n;
    }
    
    Member(String name_, int age_){
        name = name_;
        age = age_;
    }
    
    Member(String name, int age, String job){
        this.name = name;
        this.age = age;
        this.job = job;
    }
}

위와 같이 오버로딩해 생성자를 사용할 수 있다. (this에 대해서는 아래에서 설명하겠다.)

아래는 위 생성자를 사용한 코드 예시다.

Member member1 = new Member();
Member member2 = new Member("bin");
Member member3 = new Member("bin", 24);
Member member4 = new Member("bin", 24, "Student");

this란

이제 this키워드에 대해 알아보겠다. 크게 두 가지에 대해 알아볼 것이다.

  1. this
  2. this()

??이게 무슨 말 장난인가?!?! 하지만 딱 보면 감이 올 것이다. 첫 번째는 어떠한 변수고, 두 번째는 어떤 메서드다.

먼저 정리하고 살펴보자.

  • this : 인스턴스 자신을 가리키는 참조변수, 인스턴스의 주소가 저장된다. 모든 인스턴스 메서드에 지역변수로 숨겨진 채 존재한다.
  • this(), this(매개변수) : 생성자, 같은 클래스의 다른 생성자를 호출할 때 사용한다.

위에 있는 생성자 코드를 조금 수정해서 살펴보자. 

    Member(String n, int a){
        name = n;
        age = a;
    }
    
    Member(String name, int age, String job){
        this.name = name;
        this.age = age;
        this.job = job;
    }

둘다 동일하게 멤버 변수를 초기화한다. 우리의 매개변수가 name인데, 멤버 변수도 name이다. 만약 아래와 같은 코드라면?

Member(String name, int age, String job){
        name = name;
        age = age;
        job = job;
    }
// 서로 구분이 안된다.
    
    
Member(String name, int age, String job){
        this.name = name;
        this.age = age;
        this.job = job;
    }
//읽는 사람이 코드를 명확하게 이해할 수 있다.

두 변수를 전혀 구분할 수 없다. 이럴 때 this를 사용하면 코드를 읽는 사람이 멤버 변수(인스턴스 변수)로 이해할 수 있다.

이처럼 생성자의 매개변수로 인스턴스 변수들의 초기값을 제공받는 경우가 많기 때문에,

매개변수와 인스터스 변수의 이름이 동일한 경우가 많다.

이때 this 키워드를 사용하면 변수에 대한 의미를 더 명확하게 이해할 수 있다.

참고로 this 키워드는 인스턴스 메서드에서 가능하지, 클래스 메서드에서는 사용할 수 없다.

클래스 메서드와 인스턴스 메서드에 관한 내용은 아래 포스팅에서 확인할 수 있다.

https://devdebin.tistory.com/96?category=1004578 

 

클래스 메서드(static 메서드)와 인스턴스 메서드

변수와 마찬가지로, 메서드 앞에 static이 붙어 있으면 클래스 메서드, 붙어 있지 않으면 인스턴스 메서드다. 클래스 메서드도 클래스 변수처럼, 객체를 생성하지 않고도 '클래스이름.메서드이름'

devdebin.tistory.com

그럼 이제 this()다. this()는 결과적으로 같은 클래스의 다른 생성자를 호출하는 것이다. 여기에도 몇 가지 규칙이 있다.

  • 생성자의 이름으로 클래스이름 대신 this를 사용한다.
  • 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다.

이제 코드로 살펴보자. 아래 코드처럼 this를 이용해 다른 생성자를 사용할 수 있다.

public class Car {
    private String color;
    private String name;
    private int door;

    Car(){
    	super("while", "superCar", 4);
    }
    
    Car(){
        super("black", "bus", 2);
    }

    Car(String color, String name, int door){
        this.color = color;
        this.name = name;
        this.door = door;
    }
}

초기화 블럭 (+추가)

  • 초기화 블럭에는 2가지 종류가 있다.
  • 클래스 초기화 블럭 : 클래스 변수의 복잡한 초기화에 사용된다.
  • 인스턴스 초기화 블럭 : 인스턴스 변수의 복잡한 초기화에 사용된다.

우선 바로 초기화 블럭을 코드로 살펴보자. 추가적으로 명시적 초기화도 살펴보자.

class InitBlock {

     int num = 1; //프리미티브 타입의 명시적 초기화
     String name = new Name(); //참조형 변수의 명시적 초기화
     static {
         // 클래스 초기화 블럭
     }
     
     {
         // 인스턴스 초기화 블럭
     }
}

클래스 초기화 블럭과 인스턴스 초기화 블럭 작성의 차이는 static 키워드라는 것을 위에서 확인할 수 있다.

초기화 블럭 내에는 메서드 내에서와 같이 조건문, 반복문, 예외처리구문 등을 자유롭게 사용할 수 있으므로,

초기화 작업이 복잡하여 명시적 초기화만으로는 부족한 경우 초기화 블럭을 사용한다.

클래스 초기화 블럭은 클래스가 메모리에 처음 로딩될 때 한 번만 수행되며, (프로그램 작동 시 1번!!1)

인스턴스 초기화 블럭은 생성자와 같이 인스턴스를 생성할 때 마다 수행된다.

 

만약 모든 생성자에 공통으로 쑤행되어야 하는 문장들이있으면, 인스턴스 블럭에 넣어주면 코드가 간결해진다. 아래 예시를 살펴보자.

Car(){
    count++;
    serialNo = count;
    color = "White";
    gearType = "Auto";
}

Car(String color, String gearType){
    count++;
    serialNo = count;
    this.color = color;
    this.gearType = gearType;
}

//아래와 같이 수정하자

{
    //인스턴스 초기화 블럭. 생성자마다 반복된다.
    count++;
    serialNo = count;
}

Car(){
    color = "White";
    gearType = "Auto";
}

Car(String color, String gearType){
    this.color = color;
    this.gearType = gearType;
}

코드의 중복을 제거해 코드가 더욱 깔끔해진 것을 확인할 수 있다.

 

이제 마지막으로 초기화 시기와 초기화 순서에 대해 알아보겠다.

  • 클래스 변수의 초기화 시점 : 클래스가 처음 로딩될 때 단 한 번 초기화. 프로그램 작동 시 단 한번!!!
  • 인스턴스 변수의 초기화 시점 : 인스턴스가 생성될 때 마다 각 인스턴스 별로 초기화가 이루어진다.
  • 클래스 변수의 초기화 순서 : 기본값 -> 명시적 초기화 -> 클래스 초기화 블럭
  • 인스턴스 변수의 초기화 순서 : 기본값 -> 명시적 초기화 -> 인스턴스 초기화 블럭 -> 생성자

중요하니 잘 기억하자.

과제(Optional)

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

4주차 과제 내용이 들어있는 커밋은 아래와 같습니다.

https://github.com/happysubin/JAVA-practice/commit/e346989d38e9b0633798d63a27aaecc7f7ce3162

 

4주차 과제 Binary Tree로 bfs, dfs 구현 · happysubin/JAVA-practice@e346989

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

github.com

 

이상으로 포스팅을 마칩니다. 감사합니다!!

 

참고자료 

자바의 정석 (저자 남궁성, 객체지향 프로그래밍 파트 1) 

https://tecoble.techcourse.co.kr/post/2021-05-17-constructor/

반응형

댓글