본문 바로가기

JAVA

자바 기초부터 차근차근...(10)

※ Chapter10 클래스 변수와 클래스 메소드. 
※ 본문은 윤성우의 열혈JAVA를 정리한 글입니다. 
※ 문제가 될시 비공개 처리하겠습니다.

 

어느덧 벌써 10번째 정리글이 되었다.

이제 슬슬 코딩테스트 공부를 하면서 정리해야하나 생각이 들기도 하지만

다다음주면 중간고사 기간이기도 하고 진행하는 프로젝트도 있기 때문에 천천히 진행하려고 한다.

(사실 너무 많은걸 한번에 해봤는데 모두 포기하게 된다.)

 

하루에 최소 1개 or 2개정도는 정리글을 올리면서 빠르게 JAVA책을 훑어보고

일주일에 3~4개씩 코딩테스트를 정리한 글을 만들 목표를 가지고 있다.

 

혹시라도 처음부터 같이 공부하고 있는 취준생들은 같이 파이팅하자!

(영어공부도 얼른해야하고... 프로젝트 진행도 해야하고... 할게 많은데 게임도 하고싶다...)


잡담은 여기까지하고 이제 공부를 시작하자!

 

§ static 선언을 붙여서 선언하는 클래스 변수

'인스턴스 변수'는 인스턴스가 생성되었을 때, 생성된 인스턴스 안에 존재하는 변수이다.

그러나 '클래스 변수'는 인스턴스 생성과 상관없이 존재하는 변수이다.

 

static선언을 붙이면 '클래스 변수'가 된다. 예제를 보면서 기본 특성을 파악해보자.

결과값을 통하여 다음과 같은 결과에 다다를수 있다.

"static으로 선언된 변수는 변수가 선언된 클래스의 모든 인스턴스가 공유하는 변수이다."

 

클래스 변수는 인스턴스 내에 존재하는 변수가 아니다.

'어떠한 인스턴스에도 속하지 않는 상태로 메모리 공간에 딱 하나만 존재하는 변수'이다.

 

■ 클래스 변수의 접근 방법

 

- 클래스 내부 접근  -> 변수의 이름을 통해 직접 접근

- 클래스 외부 접근  -> 클래스 또는 인스턴스의 이름을 통해 접근

 

클래스 접근 방법을 보이는 예제를 보자.

way.num++; // 인스턴스의 이름을 통한 접근

AccessWay.num++; // 클래스의 이름을 통한 접근

 

■ 클래스 변수의 초기화 시점과 초기화 방법

 

클래스 변수는 인스턴스의 생성과 상관이 없다.

메모리 공간에 할당되고 초기회되는 시점은 언제일까?

다음 예제를 보면 알 수 있다.

위의 예제를 통해서 언급되는 내용은 다음과 같다.

"클래스 변수는 인스턴스 생성 이전에 메모리 공간에 존재한다."

클래스 변수는 해당 클래스 정보가 가상머신에 읽히는 순간 메모리 공간에 할당되고 초기화된다.

 

■ 클래스 변수를 언제 유용하게 활용할 것인가?

"인스턴스 간에 데이터 공유가 필요한 상황에서 클래스 변수를 선언한다."

 

클래스 변수가 유용하게 사용되는 사례를 보고 마무리하자.

 

class Circle {

     static final double PI = 3.1415;

     private double radius;

 

     Circle(double red) {

          radius = rad;

     }

     void showPerimeter() {

          double peri = (radius * 2) * PI;

     }

     void showArea() {

          double area = (radius * radius) * PI;

          System.out.println("넓이: " + area);

     }

}

 

class CircleConstPI {

     public static void main(String[] args) {

          Circle c = new Circle(1,2);

          c.showPerimeter();

          c.showArea();

     }

}

---------------------

 

둘레: 7.5396

넓이: 4.52376

 

----------------------

 

위 예제에서 PI가 상수로 선언이 되었다.

'클래스 변수'로 선언되었는데, 이는 모든 Circle 인스턴스가 참조해야 하는 값이지만,

인스턴스가 각각 지녀야 하는 값은 아니기 때문이다.

 

"참조를 목적으로만 존재하는 값은 final 선언이 된 클래스 변수에 담는다."

 

§ static 선언을 붙여서 선언하는 클래스 메소드

 

클래스 내에 정의된 메소드에 static 선언을 하면 '클래스 메소드'가 된다.

클래스 메소드는 인스턴스 생성 이전부터 호출이 가능한, 그리고 어느 인스턴스에도 속하지 않는

메소드라는 점이 클래스 변수와 동일하다.

 

■ 클래스 메소드의 정의와 호출

클래스 변수의 두가지 특성부터 보자

- 인스턴스 생성 이전부터 접근이 가능하다.

- 어느 인스턴스에도 속하지 않는다.

 

이 두 가지는 클래스 메소드도 동일하게 갖는 특성이다.

 

예제를 보며 확인해보자.

main 메소드 안에서 다음과 같이 클래스 메소드를 호출하였다.

 

NumberPrinter.showInt(20); // 클래스 이름을 통한 클래스 메소드 호출

이 값이 정상적으로 나오는 것으로 보아, 클래스 메소드가 어느 인스턴스에도 속하지 않는다는 사실을 알 수 있다.

(인스턴스 생성 이전에 호출이 되었기 때문)

 

■ 클래스 메소드에서 인스턴스 변수에 접근이 가능할까?

 

기본적으로 생각한다면 불가능하다.

이유는 간단하다.

인스턴스 변수는 인스턴스가 생성되어야지 메모리 공간에 존재하여 호출 가능하기 때문이다.

따라서 다음 코드는 유효한 코드가 아니다.

 

class AAA {

     int num = 0;

     static void addNum(int n) {

          num+= n;

     }

}

 

이것을 다음 방식으로 작성하면 유효한 코드가 된다.

 

class AAA {

     static int num = 0;

     static void showNum() {

          System.out.println(num);       //클래스 변수 접근 가능

     }

     static void addNum(int n) {

          num += n;      //클래스 변수 접근 가능

          showNum();    // 클래스 메소드 호출 가능

     }

}

 

 

 

§ System.out.println() 그리고 public static void main()

 

System.out.println()에서 out과 println의 정체는?

 

일단 System은 자바에서 제공하는 클래스이다. (java.lang) 패키지에 묶여 있다.

따라서 원칙적으로는 다음과 같이 호출해야 한다.

java.lang.System.out.println(...);

 

그러나, 컴파일러가 다음 문장을 삽입해주기 때문에 패키지의 이름 부분을 생략할 수 있다.

import java.lang.*; // 컴파일러가 삽입하는 import 선언

 

이제 out을 보자

 

System.out으로 접근을 하니, 분명 static으로 선언된 클래스 변수이다.

그럼 어떠한 형식으로 되어있는지 확인하자

 

public final class System extends Object {

     public static final PrintStream out; // 참조변수 out

     ,,,

}

 

마지막으로 println은 PrintStream 클래스의 인스턴스 메소드이다.

따라서 System.out.println( ... );은 다음과 같이 이해할 수 있다.

"System에 위치한 클래스변수 out이 참조하는 인스턴스의 println 메소드를 호출하는 문장"

 

■ main 메소드가 public이고 static인 이유는?

public static void main(String[] args) {

     ...

}

 

이는 일종의 약속이며, 이러한 약속에 근거하여 다음과 같이 실행을 하면

C:\JavaStudy>java MyMainClass

MyMainClass에 public 으로 그리고 static으로 선언된 main 메소드를 찾아 실행을 하게 된다.

main 메소드의 호출이 이루어지는 영역은 클래스 외부이다.

따라서 public으로 선언하는 것이 타당함을 알 수 있다.

그리고 main메소드는 인스턴스가 생성되기 이전에 호출되므로 static이 옮음을 알 수 있다.

 

■ main 메소드를 어디에 위치시킬 것인가?

 

만약 Car 클래스와 Boat 클래스가 정의되어 있다면 어디에 위치시킬 것인가?

 

정답은 : 둘다 상관없다.

 

다만 그 클래스가 별도의 공간을 제공했을 뿐이지, 그 클래스와 main 메소드는 별개이다.

 

 

§ 또 다른 용도의 static 선언

 

사용 빈도가 높진 않지만 다른 용도로도 사용이 가능한 static에 대하여 알아두자.

 

■ static 초기화 블록 (Static Initialization Block)

 

다음 클래스 정의를 보자

class DateOfExecution {

     static String date;

     

     public static void main(String[] args) {

          System.out.println(date);

     }

}

프로그램 실행 날짜를 저장할 목적인 date는 참조만을 목적으로 하므로 '클래스 변수'로 선언하였다.

 

오늘 날짜를 얻어오는 코드는 다음과 같다.

LocalDate nDate = LocalDate.now();

date = nDate.toString();

변수 date가 인스턴스 변수라면 위의 두 문장을 생성자에 넣으면 된다.

그러나 클래스 변수이므로 적절하지 않다.

 

이러한 상황을 고려하여 'static 초기화 블록' 이라는 것을 제공한다.

static {

    LocalDate nDate = LocalDate.now();

    date = nDate.toString();

}

이렇게 사용한다면 클래스 변수를 선언과 동시에 초기화 할 수 있다.

 

■ static import 선언

앞서 클래스 변수를 설명하면서 다음과 같이 원주율을 클래스 변수로 선언한 바 있다.

static final double PI = 3.1415;

 

그런데 이 값은 클래스 java.lang.Math에 실제로 클래스 변수로 선언되어 있다.

따라서 다음과 같이 출력할 수 있다.

 

System.out.println(Math.PI);

 

앞서 언급하였듯이 다음 문장이 자동으로 삽입되므로 접근 가능한 것이다.

import java.lang.*;

 

만일 클래스 변수의 이름만으로 접근하려고 한다면, 다음과 같은 방법을 사용하면 된다.

import static java.lang.Math.PI;

 

System.out.println(PI);

 

이렇듯 클래스 변수와 클래스 메소드에 대한 import 선언을 할 수 있다.

import static java.lang.Math.*; // 모든 클래스 변수와 메소드에 대한 import 선언

 

 

이것으로 10장을 마치도록 하겠다.

 

다음 글에서는 지금까지 공부한 것을 한번 정리한 후 다시 이어서 나가자.

(코딩테스트 문제도 중요하지만 면접도 중요하기에 한번에 두마리 토끼를 잡으면서 가자!)