13 분 소요

[JAVA_입문_수업] 4장 : 객체 지향 프로그래밍

해당 게시글은 JAVA_입문_수업 을 공부하며 정리한 글입니다.






객체 지향 프로그래밍

어렵게 다가가자면, 객체지향 프로그래밍(Object-Oriented Programming)은 좀 더 나은 프로그램을 만들기 위한

프로그래밍 패러다임으로 로직을 상태(state)와 행위(behave)로 이루어진 객체로 만드는 것이다.

쉽게 다가가면, 이 객체들을 마치 레고 블럭처럼 조립해서 하나의 프로그램을 만드는 것이 객체지향 프로그래밍이라고 할 수 있다.

다시 말해서 객체지향 프로그래밍은 객체를 만드는 것이다.(객체는 변수와 메소드를 그룹핑한 것)

1. 특성

1) 추상화

지하철을 타기 위해서는 서울의 실제 위성사진이 필요하지 않다. 깔끔하게 정리된 지하철 노선만 있으면 잘 찾아갈 수 있다.

여기서 잘 정리된 지하철 노선도는 실제 서울을 추상화한 것이다.

이렇게 목적에 따라, 복잡함 속에서 필요한 관점만을 추출하는 행위를 추상화라고 한다.

그렇게 우리는 해결해야하는 문제를 소프트웨어적으로 단순화할 수 있어야한다.


2) 부품화

하나의 객체를 만들면 여러 곳에서 부품처럼 가져와 사용할 수 있기에, 재활용성이 높다고 할 수 있다.

이렇게 하나의 객체는 부품처럼 사용된다.

그러나 옛날의 컴퓨터는 키보드와 모니터, 본체가 일체화(단일화)되어 있기에, 키보드가 고장나면 컴퓨터 자체를 바꿔야한다.

그러나 현대에는 당연히 키보드와 모니터, 본체가 분리되어 있기에, 따로 교체가 가능하다.

이렇게 단일화에서 벗어나 여러 구성으로 이루어지는 부품화에는 큰 장점이 있다.

앞에서 배운 메소드가 부품화의 예이다. 메소드를 사용함으로서 재활용성을 높였다.

그러나 현대에는 기술이 발전함에 따라 컴퓨터가 경량화 되어서 본체와 모니터를 단일화기도 한다.(맥북)

이렇게 부품화는 정답이 없다. 목적에 따라 적합하게 구성하면 된다.

연관되어 있는 것은 묶고! 연관이 없는 것은 분리하자!


3) 캡슐화(은닉화)

사용자가 컴퓨터를 사용할 때, 컴퓨터가 어떻게 만들어졌는지는 궁금하지 않다.

그렇게 필요한 사용방법만을 노출시키고, 필요없는 내부 동작은 숨기는 것이 캡슐화(은닉화)이다.

예를 들어, 만들어진 메소드를 사용할 때에는 기능이 무엇이고 입력값을 넣을 떄 뭐가 나오는지만 알면 바로 사용할 수 있다.

이렇게 객체가 어떻게 생긴지 모르고도 사용할 수 있는 것이 캡슐화의 취지이다.


4) 인터페이스

컴퓨터와 모니터를 만드는 업체들은 위와 같은 케이블의 규격(표준)을 공유한다.

각각의 부품은 미리 정해진 약속에 따라서 신호를 입, 출력하고, 연결점의 모양을 표준에 맞게 만들면 된다.

이러한 연결점을 인터페이스(interface)라고 한다.

즉, 인터페이스는 부품들 간의 연결을 위한 약속(표준)이다!


5) 복제와 상속

지금까지 객체를 부품으로 비유를 통해 설명하였다.

그러나 하드웨어를 통해 비유할 수 있는 특성이 있는 반면, 복제와 상속은 소트프웨어만의 특성이다.

그렇기에 다음에 설명하도록 한다.






클래스, 인스턴스, 메소드

1. 객체화를 안했을 때

아래의 코드를 보자. 두 수의 값의 합과 평균을 출력을 하는 예제이다.

두 수의 값을 변경함으로서 2번 출력하는 것을 볼 수 있다.

2번 반복이 일어나는 것으로 보아, 이는 연관된 것들로 객체화를 하면 반복하기 더욱 편리할 것이다.

image

public class ClaculatorDemo3 {
 
    public static void avg(int left, int right) {
        System.out.println((left + right) / 2);
    }
 
    public static void sum(int left, int right) {
        System.out.println(left + right);
    }
 
    public static void main(String[] args) {
        int left, right;
 
        left = 10;
        right = 20;
 
        sum(left, right);
        avg(left, right);
 
        left = 20;
        right = 40;
 
        sum(left, right);
        avg(left, right);
    }
 
}



2. 객체화를 했을 때

위에서 두수의 합과 평균을 하나의 메소드 안에서 계산하고 출력하였다.

반복을 줄이기 위해 이번에는 계산을 객체화하여 Calculator라는 클래스를 제작하였고,

메인 메소드에서 Calculator 객체를 생성하여, 하위의 더하기/평균 메소드를 사용하였다.

class Calculator{
    int left, right;
      
    public void setOprands(int left, int right){
        this.left = left;
        this.right = right;
    }
      
    public void sum(){
        System.out.println(this.left+this.right);
    }
      
    public void avg(){
        System.out.println((this.left+this.right)/2);
    }
}
public class CalculatorDemo4 {
      
    public static void main(String[] args) {
          
        Calculator c1 = new Calculator();
        c1.setOprands(10, 20);
        c1.sum();       
        c1.avg();       
          
        Calculator c2 = new Calculator();
        c2.setOprands(20, 40);
        c2.sum();       
        c2.avg();
    }
  
}

*추가적으로, Calculator의 setOprands메소드를 설명하자면.

this.left에서 this는 클래스로부터 생성된 인스턴스를 의미한다.

그래서 여기서의 left는 인스턴스의 변수이다.

이렇게 매개변수(left, right)로 인자를 받아, 각 인스턴스의 left, right변수에 저장하여

각 인스턴스마다 다른 상태를 가질 수 있도록 한다.

여기서 주의할 것은 메소드의 매개변수인 left, right와

인스턴스의 변수인 left, right는 이름만 같을 뿐 다른 것이다.



3. 개념

1) 클래스

클래스는 연관되어 있는 변수와 메소드의 집합이다.(설계도)

위의 예에서 변수 left와 right, 메소드 sum과 avg는 연관되어 있는 로직이다.

이를 그룹화 한것이 Calculator 클래스이다.


2) 인스턴스

연관된 것을 그룹화 하여 설계도(클래스)를 만들었지만, 설계도는 설계도일 뿐 이를 구체적인 상품으로 만들어야 사용할 수 있다.

이렇게 설계도(클래스)를 실제 제품으로 만든 것을 “인스턴스” 라고 부르며,

new (클래스명) 명령어를 통해 인스턴스를 만들 수 있다.(하나의 클래스로 여러개의 인스턴스를 생성할 수 있다.)


아래 코드를 보면, 만든 인스턴스를 제어하기 위해 c1이라는 변수에 담는 것을 알 수 있다.

그리고 c1 앞에는 Calculator가 적혀있다. 이는 변수를 선언할 때의 데이터타입 문법과 비슷하게 생겼다.

맞다. 변수 c1의 데이터타입은 클래스이다!

즉 클래스를 만든다는 것은 사용자 정의 데이터 타입을 만드는 것과 같은 의미이다.

따라서, 우리가 인스턴스를 만들 때 생각해야할 것은

  • 클래스를 인스턴스화 할 때는 new를 사용한다는 것.

  • 이를 담는 변수의 데이터 타입은 그 클래스가 된다는 것.

  • (한 줄의 코드에 클래스명이 2번 사용된다.)

Calculator c1 = new Calculator();

이 글의 초반에 객체지향 프로그래밍은 로직을 상태(state)와 행위(behave)로 이루어진 객체로 만드는 것이라 하였다.

또한 객체는 변수와 메소드를 그룹핑한 것이라고도 말하였다.

여기서 상태는 변수, 행위는 메소드라고 생각하면 되고

이제는 아래 예제를 보고 이것이 무슨 의미인지 이해할 수 있다.

c1, c2객체는 서로 다른 변수에 의해 다른 상태를 갖고 있고,

목적에 따라 메소드를 사용하여 각자의 행위로 이어진다.

즉, 하나의 클래스를 바탕으로 서로 다른 상태를 가진 인스턴스를 만들면 서로 다른 행동을 하게 된다는 것을 알 수 있다.

Calculator c1 = new Calculator();
c1.setOprands(10, 20);
c1.sum();       
c1.avg();       
    
Calculator c2 = new Calculator();
c2.setOprands(20, 40);
c2.sum();       
c2.avg();






클래스 맴버와 인스턴스 맴버

1. 클래스 변수

1) 클래스 맴버란

위에서 this.left / this.right를 사용해서 각 인스턴스마다 다른 변수를 가질 수 있도록하였다.

이래서 여기서의 left, right는 인스턴스의 멤버이다.

인스턴스를 만들어야 사용할 수 있었고, 인스턴스 마다 서로다른 값을 가지고 있기 떄문이다.

그렇다면 클래스도 멤버를 가질 수 있을까? 당연하다.

모든 인스턴스가 같은 값을 공유할 수 있도록 하고 싶을 때, 그 값을 클래스의 멤버로 사용하면 된다.

image

원주율 값을 사용자에게 제공하려한다. 원주율 값은 각각의 인스턴스에서 직접 계산할 필요가 없다.

그렇기에 아래와 같이 클래스 멤버로 만들어, 어느 인스턴스에서도 사용할 수 있도록 한다.

static을 맴버(변수,메소드) 앞에 붙이면 클래스의 맴버가 된다.

  • static double PI = 3.14;
class Calculator {
    static double PI = 3.14;
    int left, right;
 
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void sum() {
        System.out.println(this.left + this.right);
    }
 
    public void avg() {
        System.out.println((this.left + this.right) / 2);
    }
}
public class CalculatorDemo1 {
 
    public static void main(String[] args) {
 
        Calculator c1 = new Calculator();
        System.out.println(c1.PI);
 
        Calculator c2 = new Calculator();
        System.out.println(c2.PI);
 
        System.out.println(Calculator.PI);
 
    }
 
}


2) static이란?

인스턴스 생성이 필요없는 static

클래스의 멤버가 된다는 것은 인스턴스 생성이 필요없다는 것.

그래서 변수나 메소드를 static으로 선언하게 된다면,

클래스의 인스턴스를 생성하지 않더라도 호출할 수 있다는 특성을 갖게된다.

그래서 new (클래스명);으로 굳이 인스턴스를 만들지 않더라도, 다른 클래스에서 아래와 같이 바로 호출할 수 있다!

  • 클래스명.변수명

  • 클래스명.메소드명()


3) 클래스 변수의 용도

  • 인스턴스에 따라서 변하지 않는 값이 필요한 경우 (위의 예에서는 PI)

(이런 경우 final을 이용해서 상수로 선언하는 것이 바람직 하지만 final을 아직 배우지 않았기 때문에 언급하지 않았다)

  • 인스턴스를 생성할 필요가 없는 값을 클래스에 저장하고 싶은 경우

  • 값의 변경 사항을 모든 인스턴스가 공유해야 하는 경우



2. 클래스 메소드

1) 예시

위 예제에서 굳이 인스턴스가 left와 right의 값을 항상 유지하고 있어야 할 이유는 없다면,

메소드 역시 클래스 메소드로 선언하고, 아래와 같이 다른 클래스에서 바로바로 호출해 사용할 수 있다.

  • public static void sum()

  • public static void avg()

class Calculator3{
  
    public static void sum(int left, int right){
        System.out.println(left+right);
    }
     
    public static void avg(int left, int right){
        System.out.println((left+right)/2);
    }
}
public class CalculatorDemo3 {
     
    public static void main(String[] args) {
        Calculator3.sum(10, 20);
        Calculator3.avg(10, 20);
         
        Calculator3.sum(20, 40);
        Calculator3.avg(20, 40);
    }
 
}


2) 주의점(중요***)

앞에서 static을 통해 변수와 메소드를 클래스 멤버로 선언하여, 인스턴스 생성없이 호출할 수 있었다.

또한 인스턴스는 클래스를 바탕으로 생성된 것이기 때문에, 인스턴스 메소드는 클래스 멤버(변수, 메소드)에 접근 할 수 있다.

그러나, 클래스 메소드는 인스턴스 맴버에 접근 할 수 없다.

class C1{
    static int static_variable = 1; # 클래스 변수
    int instance_variable = 2; # 인스턴스 변수
    
    static void static_static(){
        System.out.println(static_variable);
    }
    static void static_instance(){
        //System.out.println(instance_variable);   # 클래스 메소드에서는 인스턴스 변수에 접근 할 수 없다. 
    }
    void instance_static(){
        System.out.println(static_variable);
    }
    void instance_instance(){        
        System.out.println(instance_variable);
    }
}
public class ClassMemberDemo {  
    public static void main(String[] args) {
        C1 c = new C1();

        c.static_static();

        // c.static_instance(); # 클래스 메소드가 인스턴스 변수에 접근 -> 실패
        
        c.instance_static();

        c.instance_instance();

        C1.static_static();
        
        // C1.static_instance(); # 클래스 메소드가 인스턴스 변수에 접근 -> 실패
        
        // C1.instance_static(); # 클래스를 이용해서 인스턴스 메소드에 접근 -> 실패
        
        // C1.instance_instance(); # 클래스를 이용해서 인스턴스 메소드에 접근 -> 실패
    }
 
}

일단 맨 아래 3개의 케이스는 당연히 안된다.

인스턴스 생성 없이 클래스로 인스턴스 멤버에 접근하는 것은 잘못된 접근이다.

인스턴스 생성 없이는 인스턴스 멤버가 애초에 생성되지 않테니깐 말이다.

그렇다면 c.static_instance() 를 보도록하자. c라는 인스턴스를 통해서 클래스 메소드에 접근하였다.

여기까지는 문제가 없다. 그러나 클래스 메소드에서 사용되는 변수가 인스턴스 변수이다.

클래스 메소드는 인스턴스가 생성되기 전에 정의된 것이기 때문에, (업데이트가 되지 않아)

클래스 메소드의 입장에서는 아예 존재하지 않는 것에 접근하는 것과 같다.

따라서 클래스 메소드를 선언할 때에는, 이 메소드가 클래스 변수 또는 변수 없이 수행되는 것인지를 확인하자!


[*참고]

필드(field)라는 것은 클래스 전역에서 접근 할 수 있는 변수를 의미한다.

그래서 인스턴스 변수와 클래스 변수는 아래와 같이 부르기도 한다.

  • 인스턴스 변수 -> Non-Static Field

  • 클래스 변수 -> Static Field

[정리]

클래스 변수와 인스턴스 변수 정의할 때 헷갈린다면?

  • 클래스 변수 : 다른 인스턴스 변수와 공유하고 싶은 변수. ex) 성

  • 인스턴스 변수 : 다른 인스턴스와 공유하지 않는 고유의 변수. ex) 이름


다만 클래스변수는 사용할 때 주의해야한다.

아래와 같이 클래스 변수는 소멸시기가 프로그램이 종료될 때이기 때문에, 메모리 차지가 크다.

따라서 기본적으로는 인스턴스 변수로 선언하고, 목적에 따라 클래스 변수로 바꿔 사용하라!

image






유효범위(Scope)

프로그램이 무진장 커질 수록, 다양한 변수와 메소드, 클래스 등이 존재할 것이다.

이런 경우 하나의 변수가 프토젝트 전역에서 사용된다면, 변수명을 새로만들 때에 이전에 사용했던 변수명을 고려하기 위해 많은 애를 쓸 것이다.

이런 문제로 인해 변수는 영향을 미칠 수 있는 유효범위가 지정되어있다.


1. 지역변수와 전역변수

1) 지역변수(local variables)

아래 코드를 보면, a메소드 안에 i 그리고 main메소드 안에 i. 2개의 i가 등장한다.

이는 메소드 내에서 선언되었기 때문에 지역변수라고 한다.

지역 변수는 해당 영역 내에서만 유효하다.

  • 첫번째 i는 메소드 하위에서 선언되었으니, 메소드 내에서 사용가능

  • 두번째 i는 for문 내에서 선언되었으니, for문 내에서만 사용가능(for문을 나오면 i를 못씀)

그렇다면 main메소드에서 a메소드를 5번 반복시키는데 출력되는 i의 값은 어떻게 될까?

0~4까지 출력이 된다. 이처럼 a메소드의 i와 main메소드의 i는 서로 영향을 미칠 수 없다!

static void a() {
    int i = 0;
}

public static void main(String[] args) {
    for (int i = 0; i < 5; i++) {
        a();
        System.out.println(i); # 0 1 2 3 4
    }
}


2) 전역변수(global variables)

그렇다면 전역변수는 무엇일까?

위에서 메소드 안에서만 유효하던 지역변수와 달리, 클래스 내의 어느 메소드에서 사용할 수 있는 변수이다.

아래의 경우 클래스 하위에서 직접적으로 i를 선언하였으며, 각 메소드에서 따로 할당되는 것을 볼 수 있다.

이로서 a메소드의 i가 main메소드에 영향을 미쳐, 반복문은 끝나지 않고 무한실행된다.

그래서 지역변수와 전역변수는 목적에 따라 잘 선언해야한다.

static int i;
    
static void a() {
    i = 0;
}

public static void main(String[] args) {
    for (i = 0; i < 5; i++) {
        a();
        System.out.println(i);
    }
}


3) 동시사용 예제

아래 예제는 전역변수 i와 두개의 지역변수가 있는 예제이다.

아래의 경우 어떻게 출력이 될까?

전역변수와 지역변수가 동시에 존재하면, 지역변수를 우선으로 실행이 된다.

그래서 아래의 경우 반복문 안의 프린트값은 01234,

main메소드의 마지막 i값은 전역변수 i값인 10이다.

static int i=10;

static void a() {
    int i = 0;
}

public static void main(String[] args) {
    for (int i = 0; i < 5; i++) {
        a();
        System.out.printf("%d", i);
    }
    
    System.out.println(i);
    
    
}


아래 예제에서는 무엇이 출력될까?

5가 출력이 된다. 최종적으로 b메소드가 호출되는데, b메소드는 전역변수 i를 사용하기 때문이다.

만약 b메소드 안에 새로운 지역변수 i=100을 선언하게되면, b메소드에서는 지역변수가 우선임으로 100이 출력된다!

static int i = 5;

static void a() {
    int i = 10;
    b();
}

static void b() {
    System.out.println(i);
}

public static void main(String[] args) {
    a();
}

4) 비유를 통한 이해

어느 회사가 있다고 하자. a(메소드)부서와 이에 지원하는 지원자b와 c가 존재한다.

사람을 뽑으려고 하는데, 이 a부서는 메소드 바깥에 존재하는 c의 존재(영향)을 잘 모른다.

그러나 b는 당당히 부서내 공채를 통해 들어왔으나, c는 낙하산으로 바로 들어왔다.

이렇게 부서에서는 낙하산의 존재를 모르고, 메소드 내에 어떤 영향을 줄지 모르기 때문에 전역변수는 목적 없이 함부로 선언하면 좋지 않다.

int c; # 낙하산 c

a (int b) { # a부서와 이에 공채로 지원한 지원자 b
    println(b);
    println(c); # 실제로 공채없이 근무 가능해진 낙하산 c
}



2. 인스턴스와 this()

바로 위의 글에서 b메소드에 지역변수 i가 100으로 선언됐으면, 100이 출력된다고 하였다.

그럼 b메소드에서는 전역변수를 사용할 수 없는가???!

당연히 아니다. 똑똑한 사람들이 이미 이에 대해 고민하고 해결했을 것이다.

그렇기에 this라는 것이 있다.

이미 이전에 클래스 맴버와 인스턴스 맴버에 대해 배울 때 사용했었다.

조금 돌아가 얘기를 하자면, 클래스 하위의 변수에 static을 붙혀 선언한다면 클래스 변수가 되어 인스턴스 생성없이 사용할 수 있다고 하였다.

그리고 static을 붙히지 않는다면 인스턴스 멤버가 되는데, 이것들은 메소드내에서 this.변수로 사용될 수 있다.

사용되는 해당 메소드에 같은 이름의 지역변수가 존재하지 않는다면 그냥 사용해도 될 것이다.

다만, 혹시 모르게 같은 이름의 지역변수가 있을지 모르니.

클래스 멤버가 아닌 (Non-static)전역변수를 메소드 내에서 사용할 때에는 this를 꼭 붙혀주자!

그렇기에 아래 예제에서는 m메소드에서 차례대로 지역변수 20과 전역변수(=인스턴스 멤버) 10이 출력된다.

class C2 {
    int v = 10;
 
    void m() {
        int v = 20;
        System.out.println(v);
        System.out.println(this.v);
    }
}
 
public class ScopeDemo8 {
 
    public static void main(String[] args) {
        C2 c1 = new C2();
        c1.m();
    }
 
}

[*정리]

객체개념이 없는 절차지향 프로그래밍에서는 모든 메소드에서 접근 가능한 변수의 사용을 죄악시한다고 한다.

처음에 사용한 변수명은 쌓이고쌓여 나중에 사용하지 못하기 때문이다.

이처럼 객체지향 프로그래밍에서는 객체라는 개념을 통해 연관된 변수와 메소드를 그룹핑하고,

이를 통해 좀 더 마음을 놓고 각 객체안에서 전역변수를 사용할 수 있다는 장점이 존재한다.

이러한 관점에서 바라본다면, 목적에 따라 객체를 적절히 부품화하지 않는다면 절차지향 프로그래밍의 단점이 발생한다.

따라서 적절한 단위로 객체를 쪼개는 것 또한 중요하고,

전역변수는 함부로 남용하지 말고, 목적에 따라 변수를 선언하자!



아래 예제를 통해 클래스변수, 인스턴스 변수를 헷길리지 말자.

또한 클래스 변수, 인스턴스 변수는 다른 클래스, 패키지에서 접근 가능한 public, 전역변수이다.

따라서 변수는 아래와 같이 분류된다.

  • 전역변수(클래스아래)

    • 클래스변수(static)

    • 인스턴스변수

  • 지역변수(메소드아래)

class Field {
    static int classVar = 10; // 클래스 변수 선언
    int instanceVar = 20;     // 인스턴스 변수 선언
}

public class test {
    public static void main(String[] args) {
        int var = 30;                   // 지역 변수 선언
        System.out.println(var + "\n"); // 지역 변수 참조
        
        Field myField1 = new Field();   // 인스턴스 생성
        Field myField2 = new Field();   // 인스턴스 생성
        System.out.println(Field.classVar); // 클래스 변수 참조
        System.out.println(myField1.classVar);
        System.out.println(myField2.classVar + "\n");

        myField1.classVar = 100;            // 클래스 변수의 값을 변경
        System.out.println(Field.classVar); // 클래스 변수 참조
        System.out.println(myField1.classVar);
        System.out.println(myField2.classVar + "\n");
        
        System.out.println(myField1.instanceVar); // 인스턴스 변수 참조
        System.out.println(myField2.instanceVar + "\n");

        myField1.instanceVar = 200;               // 인스턴스 변수의 값을 변경
        System.out.println(myField1.instanceVar); // 인스턴스 변수 참조
	        System.out.println(myField2.instanceVar);

	    }

}
30

10
10
10

100
100
100

20
20

200
20






필드의 초기화

필드는 클래스 내에 정의된 변수를 칭한다. 클래스, 인스턴스, 지역 변수가 있다.

여기서 int a = 10과 같이 선언과 동시에 명시적 초기화를 해도 되고, 또는 선언만 하더라도 타입에 따라 기본적으로 저장되는 값이 있다.

초기화 하는 방법으로는 다음이 존재한다.

  • 명시적 초기화

  • 생성자를 이용한 초기화

  • 초기화 블록을 이용한 초기화

    • 1) 인스턴스 초기화 블록

    • 2) 클래스 초기화 블록

초기화 블록을 통해 초기화하는 방법을 알아보자.



1. 인스턴스 변수 초기화

1) 인스턴스 초기화 블록

인스턴스 변수를 초기화하는 방법을 알아보자.

  • {} 인스턴스 초기화 블록을 생성한다. # 전역변수 선언부 아래에

  • 그 안에 this.(인스턴스변수) = 0; 으로 초기화할 수 있다.

(인스턴스 초기화 블록은 생성자와 마찬가지로 인스턴스가 생성될 때마다 실행된다.)

class Car {
    private String modelName;
    private String color;
    private int currentSpeed;

    { // 인스턴스 초기화 블록
        this.currentSpeed = 0;
    }

    Car() {} # 기본 생성자
    
    Car(String modelName, String color) {  # 생성자
        this.modelName = modelName;
        this.color = color;
    }

    public int getSpeed() {     # 메소드
        return currentSpeed;
    }
}

public class Member03 {
    public static void main(String[] args) {
        Car myCar = new Car();                // 인스턴스 생성
        System.out.println(myCar.getSpeed()); // 인스턴스 메소드의 호출
    }
}



2) 기본생성자를 이용한 인스턴스 변수 초기화

매개변수를 사용하지 않을 때(기본생성자),

인스턴스 초기화 블록을 이용하지 않고 인스턴스 변수를 초기화하는 방법도 있다.

아래와 같이 기본생성자가 생성자를 호출하면서 매개변수를 초기화해주는 방법이다!


(기본 클래스가 없는 부모 클래스에서 상속된) 자식클래스를 호출할 때, 부모의 기본생성자를 호출하여 오류를 발생하는 경우가 있다.

이 때문에 부모 클래스에 기본생성자를 만들어야하는 경우가 있는데.

이를 위해서라도 인스턴스 초기화 블록이 아닌 이 방법을 쓰는게 더욱 편할 것 같다.

class Car {
    private String modelName;
    private int modelYear;
    private String color;
    private int maxSpeed;
    private int currentSpeed;
    

    Car() {                     # 기본생성자가 생성자를 호출하여 인스턴스 변수를 초기화!
        this.modelName = "KIA";
        this.modelYear = 2020;
        this.color = "Black";
        this.maxSpeed = 150;
    }

    Car(String modelName, int modelYear, String color, int maxSpeed) {
        this.modelName = modelName;
        this.modelYear = modelYear;
        this.color = color;
        this.maxSpeed = maxSpeed;
    }

    public int getSpeed() {
        return currentSpeed;
    }

}



2. 클래스 초기화 블록

클래스 초기화 블록은 인스턴스 초기화 블록과 다른점은 단 하나다.

블록 앞에 static을 붙혀주면 된다!

(클래스 초기화 블록은 클래스 멤버와 바찬가지로, 클래스가 처음으로 메모리에 로딩될 때 단 한 번만 실행된다.)

class InitBlock {
    static int classVar; // 클래스 변수
    int instanceVar;     // 인스턴스 변수

    static { // 클래스 초기화 블록을 이용한 초기화
        classVar = 10;
    }
}

public class Member04 {
    public static void main(String[] args) {
        System.out.println(InitBlock.classVar); // 클래스 변수에 접근
    }
}



3. 필드의 초기화 순서

인스턴스가 생성될 때 인스턴스 초기화 블록은 생성자보다 우선적으로 실행된다.

이 말은 즉슨, 초기화 블록 뒤에 생성자가 실행되기에 두 곳에서 같은 변수를 초기화하면,

최종적으로는 인스턴스 변수에 생성자에서의 인자가 들어간다.

따라서 클래스의 필드는 여러번 초기화 될 수 있기 때문에 다음 순서와 같이 제일 마지막으로 초기화한 값만이 남는다.

  • 클래스 변수 : 기본값 → 명시적 초기화 → 클래스 초기화 블록

  • 인스턴스 변수 : 기본값 → 명시적 초기화 → 인스턴스 초기화 블록 → 생성자



댓글남기기