클래스

클래스는 ‘객체를 정의해 놓은 것’ 또는 ‘객체의 설계도 또는 틀’ 이라 할 수 있다.

그렇다면 객체란 무엇인가. 객체란, ‘ 누구누구의 입장에서 누구를 구성하는 요소’로, 예를 들면 사람의 입장에서 눈, 코, 입 또는 노트북의 입장에서 CPU, RAM 등을 지칭한다. 단, 사람의 입장에서 CPU, RAM은 객체가 아니다.

이러한 것을 객체라 하는데 프로그래밍 상에서 객체는 클래스에 정의된 내용대로 메모리에 생성된 것을 뜻한다.

설계도를 보면 제품을 만드는 일이 쉬워 지듯이 마찬가지로 클래스를 한번 잘 만들어 놓기만 하면 클래스에서 객체를 생성해서 사용하기만 하면 된다.

JDK(Java Development Kit)에서는 프로그래밍을 위해 많은 수의 유용한 클래스 (Java API)를 기본적으로 제공해준다.

클래스를 정의하는 방법

class 키워드를 사용하여 클래스 선언 후 내용을 작성한다.

  1. 접근제한자 (또는 접근제어자)

    public : 같은 프로젝트면 어디에서든 접근 가능

    default : 같은 패키지 안에서는 어디에서든 접근 가능

    private : 같은 클래스 안에서는 어디에서든 접근 가능

    protected : 상속 관계이면 어디에서나 접근 가능

  2. class : 자바에서 미리 예약되어 있는 명령 (키워드)로 이 키워드를 사용하여 클래스를 선언할 수 있다.
  3. ③, ④ 는 생성자로 인스턴스가 생성될 때 호출되는 ‘인스턴스 초기화 메서드’ 이다.

    인스턴스 변수의 초기화 작업에 주로 사용되며 인스턴스 생성 시 실행되어야 할 작업을 위해서도 사용된다.

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

    모든 클래스 안에는 반드시 하나 이상의 생성자가 정의되어야 하는데 컴파일러가 기본 생성자 (default constructor)를 제공한다 (고로, 생성자를 따로 코딩하지 않아도 인스턴스를 생성할 수 있었음)

객체를 만드는 방법

💡 클래스명 변수명;
💡 변수명 = new 클래스명();

class Study {

    String subject;

    boolean hasReview;

    int reviewCount;

    void reviewUp() {
        ++reviewCount;
    }

    void reviewDown() {
        --reviewCount;
    } 
}

class JavaStudy {

    public static void main(String args[]) {
        // 1.
        Study javaStudy;          // 참조변수 선언
        // 2.
        javaStudy = new Study();  // 객체 생성 후 객체의 주소를 참조변수에 저장
        // 3.
        javaStudy.subject = "java"; // Study 인스턴스의 멤버변수의 값 변경
        javaStudy.reviewCount = 2;
    }
}

메모리 변화

  • Study javaStudy;

    Study 타입의 참조변수 javaStudy를 선언했을 때 메모리에 참조변수 javaStudy를 위한 공간이 생긴다.

  • javaStudy = new Study();

    new 연산자에 의해 Study 클래스의 인스턴스가 메모리의 빈 공간에 생성된다. (= 인스턴스화)

    이 때, 멤버변수들은 각 자료형에 해당하는 기본값으로 초기화 된다.

    • 참조형 : null, boolean : false, int: 0

    그 다음에는 대입연산자( = )에 의해 생성된 객체의 주소값이 참조변수 javaStudy 에 저장된다.

  • javaStudy.subject = "java"; javaStudy.reviewCount = 2;

    참조변수 javaStudy에 저장된 주소에 있는 인스턴스의 멤버변수 값들을 변경하려면

    참조변수.멤버변수로 변경할 수 있다.

💡 인스턴스는 참조변수를 통해서만 다룰 수 있으며, 참조변수의 타입은 인스턴스의 타입과 일치한다.

하나의 클래스에 여러개의 인스턴스

// 1. 
Study javaStudy = new Study();
Study cStudy = new Study();
// 2.
cStudy = javaStudy
// 3. 
javaStudy.reviewCount = 3;
System.out.println("javaStudy 의 reviewCount 값 : " + javaStudy.reviewCount);
System.out.println("cStudy 의 reviewCount 값 : " + cStudy.reviewCount);
  1. 하나의 클래스로 두개의 인스턴스를 생성하면 다음과 같은 상황이 메모리에 펼쳐진다.

  2. javaStudy가 저장하고 있는 값(주소)을 cStudy에 저장하게 되면 다음과 같다.

javaStudy는 참조변수이므로 인스턴스의 주소값을 가지고 있는데 이 것을 cStudy에 저장하게 되면 cStudy가 가지고 있던 값은 잃어버리게 되고 javaStudy에 저장된 값이 cStudy에 저장이되어 cStudyjavaStudy가 참조하는 인스턴스를 같이 참조하게 된다. (cStudy가 원래 참조하고 있던 인스턴스는 더 이상 사용할 수 없게됨)

고로 cStudy가 원래 참조하고 있던 인스턴스는 더 이상 아무도 사용하지 않으므로 가비지 컬렉터 (Garbage Collector)에 의해서 자동적으로 메모리에서 제거된다.

  1. javaStudycStudy 모두 같은 Study 클래스의 인스턴스를 가르키고 있기 때문에 결과 값은 같이 3이 출력된다.

💡 참조변수에는 하나의 값 (주소) 만이 저장될 수 있으므로 둘 이상의 참조변수가 하나의 인스턴스를 참조하는 것은 가능하지만 하나의 참조변수로 여러 개의 인스턴스를 참조하는 것은 불가능하다.

  • 변수
변수 종류 선언 위치 생성 시기
클래스 변수
(class variable)
클래스 영역 클래스가 메모리에 올라갈 때
인스턴스변수
(instance variable)
클래스 영역 인스턴스가 생성되었을 때
지역변수
(local variable)
클래스 영역 이외의 영역
( 메서드, 생성자, 초기화 블럭 내부)
변수 선언문이 수행되었을 때
  • 인스턴스변수

    클래스 영역에 선언된다. (class className { } 이 블록 안이 클래스 영역임)

    클래스의 인스턴스를 생성할 때 만들어지며 인스턴스는 독립적인 저장공간을 가지므로 서로 다른 값을 가질 수 있다.

    인스턴스마다 고유한 상태를 유지해야 하는 경우 인스턴스 변수로 선언하여 사용한다.

  • 클래스변수

    클래스 영역에 선언된다.

    인스턴스변수 앞에 static 을 붙여 만들 수 있다.

    클래스가 메모리에 올라갈 때 생성되며, 모든 인스턴스가 공통된 저장공간(변수)을 공유하게 된다.

    (그래서 공유 변수 shared variable 이라고도 함.)

    클래스변수는 인스턴스를 생성하지 않고도 바로 사용가능하고 ‘클래스이름.클래스변수’ 형식으로 사용한다.

    클래스가 로딩될 때 생성되어 프로그램이 종료될 때까지 유지되며 접근제한자를 public으로 붙이면 같은 프로그램 내에서 어디서나 접근할 수 있는 전역변수(global variable) 성격을 갖는다.

  • 지역변수

    메서드, 생성자, 초기화 블럭 내에 선언되어 그안에서만 사용가능하며, 만약, 메서드가 종료되면 소멸되어 사용할 수 없다.

    for 문 또는 while문 블럭 내에 선언된 지역변수는 선언된 블럭 {} 내에서만 사용 가능하며 블럭을 벗어나면 소멸되어 사용할 수 없다.

메서드

어떤 작업을 수행하기 위한 명령문의 집합

  • 하나의 메서드는 한 가지 기능을 수행하도록 작성해야 함.
  • 반복적으로 수행되어야 하는 여러 문장을 하나의 메서드로 정의하면 좋음.
  • 관련된 여러 문장을 하나의 메서드로 만들어 놓는 것이 좋음.

메서드 정의하는 방법

리턴타입 메서드이름 (타입 변수명, 타입 변수명 ....) { // 선언부 (매개변수가 없을 수도 있음)
    ... // 구현부
}

메서드는 크게 선언부와 구현부(body) 두 부분으로 나누어져 있는데 선언부에는 리턴타입과 메서드 이름과 괄호() 안에 매개변수를 선언하고 (없으면 생략) 구현부에는 메서드에서 구현할 기능을 넣어주면 된다.

메서드 괄호() 안에 선언된 매개변수는 지역변수로 간주되어 메서드 내에서만 사용 가능하며 종료되는 순간 메모리에서 제거되어 더 이상 사용할 수 없다

리턴 타입 (return type) 은 메서드의 수행결과를 어떤 타입(자료형) 으로 반환할 것인지를 나타내는 것으로 리턴 타입이 없는 메서드는 리턴 타입 대신 void 로 선언해야 한다.

return

현재 실행 중인 메서드를 종료하고 호출한 메서드로 되돌아가게 한다.

  1. 반환값이 없는 경우 : return;
  2. 반환값이 있는 경우 : return 반환값;
    • 반환값은 메서드 선언부에 정의된 리턴 타입과 일치하거나 자동 형변환이 가능한것이어야 함. - 호출스택 (call stack)

    호출스택은 메서드의 작업에 필요한 메모리 공간을 제공한다. 메서드가 호출되면 호출스택에 호출된 메서드를 위한 메모리가 할당되며 이 메모리는 메서드가 작업을 수행하는 동안 지역변수들과 연산의 중간결과등을 저장하는데 사용되고 작업을 마치면 할당되었던 메모리 공간은 반환되어 비워진다.

     public static void main(String[] args) {
         firstMethod();
     }
        
     private static void firstMethod() {
         secondMethod();
     }
        
     private static void secondMethod() {
         System.out.println("secondMethod()");
     }
    

    1. 첫번째로 호출된 메서드를 위한 작업 공간이 호출스택의 맨 밑에 마련된다 (main)
    2. main 수행 중에 다른 메서드를 호출하게 되면 (firstMethod) main 스택 바로 위에 firstMethod 스택이 추가된다. 이때, main은 수행을 멈추고 firstMethod가 수행된다.
    3. firstMethod에서 secondMethod를 호출하니까 firstMethod는 수행을 멈추고 secondMethod가 수행되고 secondMethod 안의 println 메서드가 수행되고
    4. 수행을 마치게 되면 println을 위해 제공되었던 메모리 공간은 반환되며 secondMethod가 다시 수행을 시작한다. 이 과정을 반복하여 모든 메서드를 실행한다.
    5. 결론적으로 호출스택의 제일 상위에 위치하는 메서드가 현재 실행 중인 메서드이며, 나머지는 대기상태에 있게된다.
      • 반환 타입이 있는 메서드는 종료되면서 결과값을 자신을 호출한 메서드 (caller)에게 반환한다. 대기상태에 있던 caller는 넘겨받은 반환값으로 수행을 계속 진행한다.

    💡- 메서드가 호출되면 수행에 필요한 만큼의 메모리를 스택에 할당 받는다.
    💡- 메서드가 수행을 마치고나면 사용했던 메모리를 반환하고 스택에서 제거된다.
    💡- 호출스택의 제일 위에 있는 메서드가 현재 실행 중인 메서드이다.
    💡- 아래에 있는 메서드가 바로 위의 메서드를 호출한 메서드이다.

  • 기본형 매개변수 vs 참조형 매개변수

    자바에서는 메서드를 호출할 때 매개변수로 지정한 값을 메서드의 매개변수에 복사해서 넘겨준다.

      public static void main(String[] args) {
          String msg = "메롱";     
          printMsg(msg);
      }
        
      private static void printMsg(String msg) {
          System.out.println(msg);
      }
    

    메인 스택에 있는 msg 와 printMsg 메소드 매개변수 msg 는 같지 않다. (복사해서 받은것)

    매개변수의 타입이 기본형일때는 기본형 값이 복사되겠지만 참조형일때는 인스턴스의 주소가 복사된다.

    메서드의 매개변수를 기본형으로 선언하면 저장된 값을 얻지만 참조형으로 선언하면 값이 저장된 곳의 주소를 알 수 있기 때문에 값을 읽을수도, 변경도 가능하다.

    💡 기본형 매개변수 - 변수의 값을 읽기만 할 수 있음 (read only)
    💡 참조형 매개변수 - 변수의 값을 읽고 변경할 수 있음 (read & write)

    • 기본형 매개변수
      public static void main(String[] args) {
          JavaStudy javaStudy = new JavaStudy();
          javaStudy.studentAge = 10;
          System.out.println("main () : studentAge = " + javaStudy.studentAge);
    
          changeAge(javaStudy.studentAge);
          System.out.println("After change(javaStudy.studentAge)");
          System.out.println("main() : studentAge = " + javaStudy.studentAge);
        
      }
    
      private static void changeAge(int studentAge) {
          studentAge = 20;
          System.out.println("change() : studentAge = " + studentAge);
      }
    
      결과
      main () : studentAge = 10
      change() : studentAge = 20
      After change(javaStudy.studentAge)
      main() : studentAge = 10
    
    • 참조형 매개변수
      public static void main(String[] args) {
          JavaStudy javaStudy = new JavaStudy();
          javaStudy.studentAge = 10;
          System.out.println("main () : studentAge = " + javaStudy.studentAge);
    
          changeAge(javaStudy);
          System.out.println("After change(javaStudy.studentAge)");
          System.out.println("main() : studentAge = " + javaStudy.studentAge);
    
      }
        
      private static void changeAge(JavaStudy javaStudy) {
          javaStudy.studentAge = 30;
          System.out.println("change() : studentAge = " + javaStudy.studentAge);
      }
    
      결과
      main () : studentAge = 10
      change() : studentAge = 30
      After change(javaStudy.studentAge)
      main() : studentAge = 30
    
  • 클래스메서드 (static 메서드) vs 인스턴스메서드

    메서드 앞에 static이 붙어있으면 클래스메서드 아니면 인스턴스메서드이다.

    1. 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통적으로 사용해야 하는 것static을 붙인다.
    2. 클래스변수는 인스턴스를 생성하지 않아도 사용할 수 있다.
    3. 클래스메서드에서는 인스턴스변수를 사용할 수 없다.
      • 클래스메서드를 호출할 때 인스턴스가 생성되어 있을 수 있어서 인스턴스변수의 사용을 허용하지 않는다.
      • 반대는 가능하다. 인스턴스변수가 존재한다는것은 static이 붙은 변수가 이미 메모리에 존재한다는 의미이기 때문이다.
    4. 메서드내에서 인스턴스변수를 사용하지 않는다면 static을 붙이는 것을 고려한다.
      • 메서드 호출시간이 짧아지기 때문에 효율이 높아진다.
  • 클래스멤버 vs 인스턴스멤버

    같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다. 단, 클래스멤버가 인스턴스멤버(인스턴스변수, 인스턴스메서드)를 참조 또는 호출하고자 하는경우에는 인스턴스를 생성해야 한다.

    인스턴스멤버가 존재하는 시점에 클래스멤버는 항상 존재하지만, 클래스멤버가 존재하는 시점에 인스턴스멤버가 존재할 수도 있고 존재하지 않을 수도 있기 때문

메서드 오버로딩 (Overloading)

메서드도 변수와 마찬가지로 같은 클래스 내에서 서로 구별되어야 하니까 서로 다른 이름을 가져야 한다. 그러나 자바에서는 한 클래스 내에 이름이 같은 메서드가 있어도 매개변수의 개수 또는 타입(시그니처)이 다르면 같은 이름을 사용해서 메서드를 정의할 수 있다.

한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것을 메서드 오버로딩(overloading) 이라 한다.

  • 조건
    • 메서드 이름이 같아야 한다.
    • 매개변수 개수 또는 타입이 달라야한다.
    • 매개변수는 같고 리턴타입이 다른 경우는 오버로딩이 성립되지 않는다.
  •   void println()
      void println(boolean x)
      void println(char x)
      ...
    

메서드 오버라이딩 (Overriding)

상위클래스가 가지고 있는 메서드를 하위 클래스가 재정의해서 사용하는 것을 오버라이딩이라 한다.

class Point {

    int x;
    int y;
    
    String getLocation() {
        return "x : " + x + ", y : " + y;
    }
}

class Point3D extends Point {

    int z;

    String getLocation() {             // 오버라이딩
        return "x :" + x + ", y :" + y + ", z :" + z;
    }
}
  • 오버라이딩 조건
    • 하위클래스에서 오버라이딩 하는 메서드는 상위클래스의 메서드와
      • 이름이 같아야한다.
      • 매개변수가 같아야한다.
      • 리턴타입이 같아야한다.
    • 즉, 선언부가 서로 일치해야 한다.
    • 접근제어자와 예외는 제한된 조건 하에서 다르게 변경할 수 있다.
      • 접근제어자는 상위클래스의 메서드보다 좁은 범위로 변경 할 수 없다.
      • 조상클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.

Constructor

초기화 블록 (Initialization Block)

클래스가 생성될 때 무조건 수행되는 블록으로

  • 클래스초기화블럭
    • 클래스가 처음 로딩될 때 한 번만 수행되는 블럭
      static {
          // ...
      }
    
  • 인스턴스 초기화 블럭
    • 인스턴스가 생성될 때마다 수행 되는 블럭

생성자

인스턴스가 생성될 때 호출되는 ‘인스턴스 초기화 메서드’

인스턴스 변수의 초기화 작업에 주로 사용되며 인스턴스 생성 시에 실행해야 할 작업을 위해서도 사용된다.

연산자 new 가 인스턴스를 생성하는 것이고 생성자가 인스턴스를 생성하는 것이 아니다.

Car car = new Car();

// 1. 연산자 new에 의해서 메모리(heap)에 Card클래스의 인스턴스가 생성된다.
// 2. 생성자 Card()가 호출되어 수행된다.
// 3. 연산자 new의 결과로, 생성된 Card인스턴스의 주소가 반환되어 참조변수 c에 저장된다.
  • 생성자 조건
    • 생성자의 이름은 클래스의 이름과 같아야한다.
    • 생성자는 리턴값이 없다.
  • 기본 생성자 (default constructor)
    • 모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 하는데 개발자가 작성하지 않고도 인스턴스를 생성할 수 있었던 이유는 컴파일러가 제공하는 기본생성자 덕분이다
  • 매개변수가 있는 생성자
    • 인스턴스마다 각기 다른 값으로 초기화되어야 하는 경우 유용하다
      class Car {
        
          String color;
          String gearType;
          int door;
            
          Car() {}        
          Car(String c, String g, int d) {
                  color = c;
                  gearType = g;
                  door = d;
          }
      }
    
      Car blueCar = new Car ("BLUE", "auto", 4);
      Car whiteCar = new Car("WHITE", "auto", 2);
    

생성자 정의하는 방법

// 매개변수가 없는 생성자
클래스이름 () {
    // 인스턴스 생성 시 수행될 코드
}
// 매개변수가 있는 생성자
클래스이름 (타입 변수명, 타입 변수명, ...) {
    // 인스턴스 생성 시 수행될 코드
}

this

this참조변수로 인스턴스 자신을 가리킨다. 생성자를 포함한 모든 인스턴스메서드에는 자신이 관련된 인스턴스를 가리키는 참조변수 this 가 지역변수로 숨겨진채 존재한다

this() , this(매개변수)

생성자, 같은 클래스의 다른 생성자를 호출할 때 사용한다.

class Car {
    String color;
    String gearType;
    int door;

    Car() {
            this("white", "auto", 4);
    }

    Car(String color) {
            this(color, "auto", 4);
    }

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