이것이 자바다 의 Chapter06 을 참고하여 정리한 포스트입니다.

이전글
Class 이론 정리 - 1

클래스 정리 - 2

4. 인스턴스 멤버와 this

인스턴스(instance) 멤버란 객체(인스턴스)를 생성한 후 사용할 수 있는 필드와 메소드를 말하는데,
이들을 각각 인스턴스 필드, 인스턴스 메소드 라고 부른다.

지금까지 작성한 모든 필드와 메소드는 인스턴스 멤버들이었다.
인스턴스 필드와 메소드는 객체에 소속된 멤버이기 때문에 객체 없이는 사용할 수 없다.

  • ex) Car 클래스에 gas 필드setSpeed() 메소드가 다음과 같이 선언되었다 가정해보자
    1
    2
    3
    4
    5
    6
    7
    
    public class Car {
      //필드
      int gas;
    				
      //메소드
      void setSpeed(int speed) {...}
    }
    
  • gas 필드와 setSpeed() 메소드는 인스턴스 멤버이기 때문에 외부 클래스에서 사용하기 위해서는
    우선 Car 객체(인스턴스)를 생성하고 참조변수 myCar 또는 yourCar 로 접근해야 한다.
    1
    2
    3
    4
    5
    6
    7
    8
    
              ex) Car my Car = new Car();
           	   myCar.gas = 10;
           	   myCar.setSpeed(60);
    	
    
            	  Car yourCar = new yourCar();
            	  yourCar.gas = 20;
            	  yourCar.setSpeed(80);
    

    위 코드가 실행된 후 메모리 상태를 그림으로 표현하면 다음 그림과 같다.
    인스턴스 필드 gas 는 객체마다 따로 존재하고,
    인스턴스 메소드 setSpeed() 는 객체마다 존재하지 않고 메소드 영역에 저장되고 공유된다.

객체 외부에서 인스턴스 멤버에 접근하기 위해 참조 변수를 사용하는 것과 마찬가지로
객체 내부에서도 인스턴스 멤버에 접근하기 위해 this 를 사용할 수 있다.

  • ex) this.model 은 자신이 가지고 있는 model 필드라는 뜻이다.
    this는 주로 생성자와 메소드의 매개 변수 이름이 필드와 동일한 경우,
    인스턴스 멤버인 필드임을 명시하고자 할 때 사용된다.
    • ex) 다음은 매개 변수 model 의 값을 필드 model에 저장한다.
      1
      2
      3
      4
      5
      6
      
       Car(String model) {
           this.model = model;
       }
       void setModel(String model) {
           this.model = model;
       }
      

5. 정적 멤버와 static

정적(static)은 ‘고정된’ 이란 의미한다.
정적 멤버는 클래스에 고정된 멤버로서 객체를 생성하지 않고 사용할 수 있는 필드와 메소드를 말한다.
이들은 각각 정적 필드, 정적 메소드 라고 부른다.

정적 멤버는 객체(인스턴스)에 소속된 멤버가 아니라 클래스에 소속된 멤버이기 때문에 클래스 멤버 라고도 한다.

5.1 정적 멤버 선언

정적 필드와 정적 메소드를 선언하는 방법은 필드와 메소드 선언 시 static 키워드를 추가적으로 붙이면 된다.

  • ex) 정적 필드와 정적 메소드를 선언하는 방법
    1
    2
    3
    4
    5
    6
    7
    
    public class 클래스 {
      //정적 필드
      static 타입 필드 [=초기값];
    	
      //정적 메소드
      static 리턴 타입 메소드( 매개변수선언, ...) {...}
    }
    

    정적 필드와 정적 메소드 클래스에 고정된 멤버이므로 클래스로더가 클래스(바이트 코드)를 로딩해서 메소드 메모리 영역에 적재할 때 클래스별로 관리된다.
    따라서 클래스 로딩이 끝나면 바로 사용할 수 있다.

image

필드를 선언할 때, 인스턴스 필드로 선언할 것인가, 아니면 정적 필드로 선언할 것인가의 판단 기준

객체마다 가지고 있어야 할 데이터라면 인스턴스 필드로 선언하고,
객체마다 가지고 있을 필요성이 없는 공용적인 데이터라면 정적 필드로 선언하는 것이 좋다.

  • ex) Calculator 클래스에서 원의 넓이나 둘레를 구할 때 필요한 파이는
    Calculator 객체마다 가지고 있을 필요가 없는 변하지 않는 공용적 데이터이므로 정적 필드로 선언하는 것이 좋다.
    그러나 객체별로 색깔이 다르다면 색깔은 인스턴스 필드로 선언해야 한다.
    1
    2
    3
    4
    5
    
      public class Calculator {
    		
          String color;			// 계산기별로 색깔이 다를 수 있다. 
          static double pi = 3.14159;	// 계산기예서 사용하는 파이 값은 동일
      }
    

메소드의 경우, 인스턴스 메소드인지 정적 메소드로 선언할 것인지의 판단 기준

인스턴스 필드를 이용해서 실행해야 한다면 인스턴스 메소드로 선언하고, 인스턴스 필드를 이용하지 않는다면 정적 메소드로 선언한다.

  • ex) Calculator 클래스의 덧셈, 뺄셈 기능은 인스턴스 필드를 이용하기 보다는
    외부에서 주어진 매개값들은 가지고 덧셈과 뺄셈을 수행함므로 정적 메소드로 선언하는 것이 좋다.
    그러나. 인스턴스 필드인 색깔을 변경하는 메소드는 인스턴스 메소드로 선언해야 한다.
    1
    2
    3
    4
    5
    6
    
      public class Calculator {
          String color;						//인스턴스 필드
          void setColor(String color) { this.color = color; }	//인스턴스 메소드	
          static int plus(int x, int y) { return x + y; }		//정적 메소드
          static int minus(int x, int y) { return x + y; }	       //정적 메소드
      }
    

5.2 정적 멤버 사용

클래스가 메모리로 로딩되면 정적 멤버를 바로 사용할 수 있는데, 클래스 이름과 함께 도트(.) 연산자로 접근한다.

  • ex)
    1
    2
    
          클래스.필드;
          클래스.메소드( 메개값, ...);
    
  • ex) Calculator 클래스가 다음과 작성
    1
    2
    3
    4
    5
    
          public class Calculator {
              static double pi = 3.14159;
              static int plus(int x, int y) {...}
              static int minus(int x, int y) {...}
          }
    
  • ex) 정적 필드 pi와 정적 메소드 plus(), minus() 는 다음과 같이 사용할 수 있다.
    1
    2
    3
    
          double result1 = 10 * 10 * Calculator.pi;
          int result2 = Calculator.plus(10, 5);
          int result3 = Calculator.minus(10, 5);
    
  • ex) 정적 필드와 정적 메소드는 원칙적으로는 클래스 이름으로 접근해야 하지만
    다음과 같이 객체 참조 변수로도 접근이 가능하다.
    1
    2
    3
    4
    
          Calculator my Calcu = new Calculator();
          double result1 = 10 * 10 * myCalcu.pi;
          int result2 = myCalcu.plus(10, 5);
          int result3 = myCalcu.minus(10, 5);
    

    하지만. 정적 요소는 클래스 이름으로 접근하는 것이 좋다.
    이클립스에서는 정적 멤버를 클래스 이름으로 접근하지 않고 객체 참조 변수로 접근했을 경우, 경고 표시가 나타난다.

5.3 정적 초기화 블록

정적 필드는 다음과 같이 필드 선언과 동시에 초기값을 주는 것이 보통이다.

  • ex)
    1
    
      static double pi = 3.14159;
    

그러나. 계산이 필요한 초기화 작업이 있을 수 있다.
인스턴스 필드는 생성자에서 초기화하지만,
정적 필드는 객체 생성 없이도 사용해야 하므로 생성자에서 초기화 작업을 할 수 없다.
생성자는 객체 생성 시에만 실행되기 때문이다.

그렇다면 정적 필드를 위한 초기화 작업은 어디에서 해야 하는가?
자바는 정적 필드의 복잡한 초기화 작업을 위해서 정적 블록(static block)을 제공한다.

  • ex) 정적 블록의 형태
    1
    2
    3
    
      static { 
          ...
      }
    

    정적 블록은 클래스가 메모리로 로딩될 때 자동적으로 실행된다.
    정적 블록은 클래스 내부에 여러개가 선언되어도 상관없다.
    클래스가 메모리로 로딩될 때 선언된 순서대로 실행된다.

5.4 정적 메소드와 블록 선언 시 주의할 점

정적 메소드와 정적 블록을 선언할 때 주의할 점은 객체가 없어도 실행된다는 특징 때문에,
이들 내부에 인스턴스 필드나 인스턴스 메소드를 사용할 수 없다.
또한 객체 자신의 참조인 this 키워드도 사용이 불가능하다.

  • ex) 그래서 다음 코드는 컴파일 오류가 발생한다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    
      public class ClassName {
      //인스턴스 필드와 메소드
      int field1;
      void method1() { ... }
    	
      //정적 필드와 메소드
      static int field2;
      static void method2() { ... }
    
      //정적 블록
      static {
          field1 = 10;	// (X)	컴파일 에러
          method1();	// (X)	컴파일 에러
          field2 = 10;	// (O)
          method2();	// (O)	
    	
      }
    	
      //정적 메소드
      static {
          this.field1 = 10;	// (X)	컴파일 에러
          this.method1();		// (X)	컴파일 에러
          field2 = 10;		// (O)
          method2();		// (O)
      }
    	
      }
    
  • 정적 메소드와 정적 블록에서 인스턴스 멤버를 사용하고 싶다면 다음과 같이 객체를 먼저 생성하고 참조 변수를 접근해야 한다.
    1
    2
    3
    4
    5
    
          static void Method3() {
              ClassName obj = new ClassName();
              obj.field1 = 10;
              odj.method1();
          }
    

main() 메소드도 동일한 규칙이 적용된다.
main() 메소드도 정적(static) 메소드이므로 객체 생성 없이 인스턴스 필드와 인스턴스 메소드를 main() 메소드에서 바로 사용할 수 없다.

  • ex) 잘못 코딩된 것
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      public class Car {
          int speed;
    
          void run() { ... }
    			
          public static void main(String[] args) {
          speed = 60; 	//(X) 컴파일 에러
          run(); 		//(X) 컴파일 에러
          }
      }
    
  • ex) main() 메소드를 올바르게 수정한 것
    1
    2
    3
    4
    5
    
      public static void main(String[] args) {
          Cat myCar = new Car();
          myCar.speed = 60;
          myCar.run();
      }
    

5.5 싱글톤(Singleton)

가끔 전체 프로그램에서 단 하나의 객체만 만들도록 보장해야 하는 경우가 있다.
단 하나만 생성된다고 해서 이 객체를 싱글톤(sinfleton) 이라고 한다.

싱글톤을 만드려면 클래스 외부에서 new 연산자로 생성자를 호출할 수 없도록 막아야 한다.
생성자를 호출할 만큼 객체가 생성되기 때문이다.
생성자를 외부에서 호출할 수 없도록 하여면 생성사 앞에 private 접근 제한자를 붙여주면 된다.
외부에서 생성자 호출을 막기 위해 private 를 붙여둔다.
그리고 자신의 타입인 정적 필드를 하나 선언하고 자신의 객체를 생성해 초기화한다.
참고로 클래스 내부에서는 new 연산자로 생성자 호출이 가능하다.

정적 필드로 private 접근 제한자를 붙여 외부에서 필드값을 변경하지 못하도록 막는다.
대신 외부에서 호출할 수 있는 정적 메소드인 getInstance()를 선언하고
정적 필드에서 참조하고 있는 자신의 객체를 리턴해준다.

  • ex) 싱글톤을 만드는 코드
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
          public class 클래스 {
              //정적 필드
              private static 클래스 singleton = new 클래스();
    		
              //생성자
              private 클래스() {}
    		
              //정적 메소드
              static 클래스 getInstance() {
                  return singleton;
              }
    		
          }
    

외부에서 객체를 얻는 유일한 방법은 getInstance() 메소드를 호출하는 방법이다.
getInstance() 메소드는 단 하나의 객체만 리턴하기 때문에

  • ex) 아래 코드에서 변수 1과 변수 2는 동일한 객체를 참조한다.
    1
    2
    
      클래스 변수1 = 클래스.getInstance();
      클래스 변수2 = 클래스.getInstance();
    

6. final 필드와 상수

6.1 final 필드

final 필드는 초기값이 저장되면 이것이 최종값이 되어서 프로그램 실행 도중에 수정할 수 없다는 것이다.

  • ex) final 필드 선언
    1
    
      final 타입 필드 [=초기값];
    

6.2 상수 (Static final)

일반적으로 불변의 값을 상수라고 부른다.
불변의 값을 저장하는 필드를 자바에서는 상수(constant) 라고 한다.

final 필드는 한 번 초기화되면 수정할 수 없는 필드라고 했다.
final 필드를 상수라고 부르진 않는다.
왜냐하면 불변의 값은 객체마다 저장할 필요가 없는 공용성을 띠고 있으며, 여러 가지 값으로 초기화될 수 없기 때문이다.
final 필드는 객체마다 저장되고, 생성자의 매개값을 통해서 여러 가지 값을 가질 수 있기 때문에 상수가 될 수 없다.

[hint] 객체마다 저장할 필요가 없는 공용성을 띤다

상수는 static 이면서 final 이어야 한다.
static final 필드는 객체마가 저장되지 않고, 클래스에만 포함된다.
그리고 한 번 초기값이 저장되면 변경할 수 없다.

1
	static final 타입 상수 [= 초기값];

초기값이 단순 값이라면 선언 시에 주는 것이 일반적이지만,
복잡한 초기화일 경우 정적 블록에서도 할 수 있다.

1
2
3
4
	static final 타입 상수;
	static {
		상수 = 초기값;
	}

상수 이름은 모두 대문자로 작성하는 것이 관례이다.
ex) 상수 필드를 대문자로 올바르게 선언

1
2
	static final double PI = 3.14159;
	static final double EARTH_SURFACE_AREA;

7. 패키지

클래스를 체계적으로 관리하기 위해 package 를 사용한다.
패키지의 물리적인 형태는 파일 시스템의 폴더이다.

패키지는 단순히 파일 시스템의 촐더 기능만 하는 것이 아니라 클래스의 일부분이다.
패키지는 클래스를 유일하게 만들어주는 식별자 역할을 한다.

클래스 이름이 동일하더라도 패키지가 다르면 다른 클래스로 인식한다.
클래스의 전체 이름은 “패키지명 + 클래스명” 인데 패키지가 상.하위로 구분되어 있다면 도트(.)를 사용해서 다음과 같이 표현한다.

1
	상위패키지.하위패키지.클래스

ex) Car 클래스가 com.mycompany 패키지에 속해 있다면
Car 클래스의 전체 이름은 com.mycompany.Car 가 되고
실제 파일 시스템에서는 com\mycompany 폴더에 Car.class 가 위치한다.

패키지가 중요한 이유는 클래스만 따로 복사해서 다른 곳으로 이동하면 클래스는 사용할 수 없기 때문이다.

ex) 파일 시스템 com\yourcompany 폴더에 Car.class 를 저장하면 Car 클래스를 사용할 수 없다.
클래스를 이동할 경우에는 패키지 전체를 이동시켜야 한다.

7.1 패키지 선언

1
2
	package 상위패키지.하위패키지;
	public class ClassName { ... }

7.2 패키지 선언이 포함된 클래스 컴파일

패키지 폴더가 자동으로 생성되려면 javac 명령어 다음에 -d 옵션은 추가하고 패키지가 생성될 경로를 다음과 같이 지정해야 한다.

1
2
3
javac	-d .		ClassName.java		<- 현재 폴더 내에 생성   
javac	-d ..\bin	ClassName.java		<- 현재 폴더와 같은 위치의 bin 폴더에 생성   
javac 	-d C:\Temp\bin	ClassName.java		<- C:\Temp\bin 폴더에 생성   
  • ex) Application.java 가 C:\Temp 폴더에 작성되었다고 가정
    [Application.java] : 패키지가 선언된 클래스 컴파일
    1
    2
    3
    4
    5
    6
    7
    
      package sec12.exam01_package_compile;
    	
      public class Application {
          public static void main(String[] args) {
          system.out.println("애플리케이션을 실행합니다.");
          }
      }
    
  • cmd C:\Temp 로 이동한 후 다음과 같이 컴파일 한다.
    1
    
      C:\Temp>javac	-d	.	Application.java
    

    컴파일이 성공되면 패키지 폴더들이 생성되고 마지막에 Application.class 가 저장된다.

Application 클래스는 main() 메소드가 선언되어 있기 때문에 실행이 가능하다.
패키지에 소속된 클래스를 명령 프롬프트에서 실행하려면 바이트 코드 파일(*.class) 이 있는 폴더에서
java 명령어를 사용해서는 안 된다.

  • 패키지는 클래스의 일부분이므로 패키지가 시작하는 폴더에서 java 명령어를 사용해야 한다.
    Application 클래스의 패키지 sec12는 C:\Temp 폴더에 있기 때문에 C:\Temp 폴더에서 다음과 같이 java 명령어를 실행해야 한다.
    1
    
    C:\Temp>java sec12.exam01_package_compile.Application
    

7.3 이클립스에서 패키지 생성과 클래스 생성

이클립스에서는 패키지만 따로 생성할 수 있고, 클래스를 생성할 때 동시에 생성시킬 수도 있다.
매키지를 먼저 생성하고, 해당 패키지에서 클래스를 생성하는 방법을 가장 많이 사용한다.

WHY?
클래스를 컴파일하는 과정에서 패키지들을 생성하는 것보다는 미리 패키지를 만들어 놓고,
여기에 포함된 클래스를 결정하는 것이 쉽기 때문이다.

[메뉴]

  • File → New → Package

7.4 Import 문

같은 패키지에 속하는 클래스들은 아무런 조선 없이 다른 클래스를 사용할 수 있지만
다른 패키지에 속하는 클래스를 사용하려면 두 가지 방법 중 하나는 선택해야 한다.

첫 번째 방법은 패키지와 클래스를 모두 기술하는 것이다.

  • ex) 다음은 com.hankook 패키지에 소속된 Tire 클래스를 이용해서 필드를 선언하고 객체를 생성한 것이다.
    1
    2
    3
    4
    5
    6
    7
    
      package com.mycompany;
    	
      public class Car {
          com.hanbook.Tire tire = new com.janbook.Tire();
          ---------------- ----   -----------------------
               타입	필드명	     객체 셍성 
      }
    

    패키지 이름이 짧을 경우에는 불편함이 없겠지만, 패키지 이름이 길거나 이렇게 사용해야 할 클래스 수가 많다면
    패키지 아름을 붙인다는 것은 전체 코드를 난잡해 보이게 할 수 있다.

두 번째 방법으로 import 문을 주로 사용한다.

사용하고자 하는 패키지를 import 문으로 선언하고, 클래스를 사용할 때에는 패키지를 생략하는 것이다.

  • ex)
    1
    2
    3
    4
    5
    6
    7
    8
    
      package com.mycompany;
    	
      import com.hanbook.Tire;
      [ 또는 import com.hanbook.*; ]
    	
      public class Car {
          Tire tire = new Tire();
      }
    

import 문이 작성되는 위치는 패키지 선언클래스 선언 사이다.

패키지에 포함된 다수의 클래스를 사용해야 한다면
클래스별로 import 문을 작성할 필요가 없이 클래스 이름을 생략하고
대신 *를 사용해서 import 문을 한 번 작성하면 된다.

* 는 패키지에 속하는 모든 클래스들을 의미한다.
import 문의 개수는 제한이 없고 필요한 패키지가 있다면 얼마든지 추가할 수 있다.

주의할 점
import 문으로 지정된 패키지의 하위 패키지는 import 대상이 아니다.
만약 하위 패키지에 있는 클래스들도 사용하고 싶다면 import문을 하나 더 작성해야 한다.

  • ex)
    com.mycompany 패키지에 있는 클래스도 사용해야 하고,
    com.mycompany.projact 패키지에 있는 클래스도 사용해야 한다면
    다음과 같이 두 개의 import 문이 필요하다.
    1
    2
    
      import com.mycompany.*;
      import com.mycompany.project.*;
    

    패키지 이름 전체를 기술하는 첫 번째 방법이 꼭 필요한 경우가 있는데,
    서로 다른 패키지에 동일한 클래스 이름이 존재하고, 두 패키지가 모두 import 되어 있을 경우이다.

자바 컴파일러는 어떤 패키지에서 클래스를 로딩할지 결정할 수 없기 때문에 컴파일 에러가 발생한다.
이 경우에는 정확하게 패키지 이름 전체를 기술해야 한다.

🌞 정보 : 공부 기록용 블로그입니다. 오타나 내용 오류가 있을 경우 알려주시면 감사하겠습니다.

댓글남기기