클래스에 대한 추가 정보

이 섹션에서는 객체에 대한 이전 섹션에서 배운 객체 참조와 점 연산자 사용에 의존하는 클래스의 더 많은 측면을 다룹니다:

  • 메서드에서 값을 반환합니다.
  • this 키워드.
  • 클래스 대 인스턴스 멤버.
  • 접근 제어.

 

메서드에서 값 return하기

메서드는 메서드를 호출한 코드로 return됩니다.

  • 메서드의 모든 명령문을 완료하면 호출한 코드로 return됩니다,
  • return 문에 도달하거나, 또는
  • 예외를 던지거나(나중에 다루겠습니다),
  • 먼저 발생하는 것으로 return됩니다.

메서드의 반환 유형은 메서드 선언에서 선언합니다. 메서드 본문에서 return 문을 사용하여 값을 반환합니다.

void로 선언된 메서드는 값을 반환하지 않습니다. return 문을 포함할 필요는 없지만 포함할 수도 있습니다. 이러한 경우 return 문을 사용하여 제어 흐름 블록에서 분기하고 메서드를 종료할 수 있으며 다음과 같이 간단히 사용할 수 있습니다:

return;

void로 선언된 메서드에서 값을 return하려고 하면 컴파일러 오류가 발생합니다.

void로 선언되지 않은 모든 메서드에는 다음과 같이 해당 return값이 있는 return 문이 포함되어야 합니다:

return returnValue;

return 값의 데이터 타입은 메서드의 선언된 반환 타입과 일치해야 하며, boolean을 반환하도록 선언된 메서드에서는 정수 값을 반환할 수 없습니다.

객체에 대한 섹션에서 설명한 Rectangle 클래스의 getArea() 메서드는 정수를 반환합니다:

// a method for computing the area of the rectangle
public int getArea() {
    return width * height;
}

이 메서드는 width*height 표현식이 평가하는 정수를 반환합니다.

getArea() 메서드는 원시 타입을 반환합니다. 메서드는 참조 타입을 반환할 수도 있습니다. 예를 들어 Bicycle 객체를 조작하는 프로그램에는 다음과 같은 메서드가 있을 수 있습니다:

public Bicycle seeWhosFastest(Bicycle myBike, Bicycle yourBike, Environment env) {
    Bicycle fastest;
    // code to calculate which bike is 
    // faster, given each bike's gear 
    // and cadence and given the 
    // environment (terrain and wind)
    return fastest;
}

 

클래스 또는 인터페이스 return하기

이 섹션이 혼란스럽다면 건너뛰고 인터페이스와 상속 섹션을 마친 후에 다시 돌아와서 살펴보세요.

메서드가 seeWhosFastest()처럼 클래스 이름을 반환 타입으로 사용하는 경우, 반환되는 객체 타입의 클래스는 반환 타입의 서브클래스이거나 정확한 클래스여야 합니다. 다음 그림과 같이 ImaginaryNumberjava.lang.Number의 서브클래스이고, 이는 다시 Object의 서브클래스인 클래스 계층 구조가 있다고 가정해 보겠습니다.

ImaginaryNumber의 클래스 계층구조

이제 Number를 반환하도록 선언된 메서드가 있다고 가정해 봅시다:

public Number returnANumber() {
    ...
}

returnANumber() 메서드는 ImaginaryNumber를 반환할 수 있지만 Object는 반환하지 못합니다. ImaginaryNumber의 인스턴스도 Number의 인스턴스이기 때문에 ImaginaryNumberNumber의 서브클래스입니다. 위 설명의 Object가 반드시 Number일 필요는 없습니다. String이나 다른 타입일 수도 있습니다.

다음과 같이 메서드를 재정의하고 원래 메서드의 서브클래스를 반환하도록 정의할 수 있습니다:

public ImaginaryNumber returnANumber() {
    ...
}

covariant return type 이라고 하는 이 기술은 return 타입이 서브클래스와 같은 방향으로 변할 수 있다는 것을 의미합니다.

참고: 인터페이스를 반환 타입으로 사용할 수도 있습니다. 이 경우 반환되는 객체는 지정된 인터페이스를 구현해야 합니다.

 

this 키워드 사용

인스턴스 메서드 또는 생성자 내에서, this는 현재 객체 에 대한 참조입니다. — 메서드 또는 생성자가 호출되는 객체입니다. 인스턴스 메서드나 생성자 내에서 this를 사용하여 현재 객체의 모든 멤버를 참조할 수 있습니다.

필드와 함께 this 사용하기

this 키워드를 사용하는 가장 일반적인 이유는 필드가 메서드 또는 생성자 매개변수에 의해 섀도잉 처리되기 때문입니다.

예를 들어 Point 클래스는 다음과 같이 작성되었습니다:

public class Point {
    public int x = 0;
    public int y = 0;
        
    //constructor
    public Point(int a, int b) {
        x = a;
        y = b;
    }
}

이렇게 작성할 수도 있습니다:

public class Point {
    public int x = 0;
    public int y = 0;
        
    //constructor
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

생성자에 대한 각 인수는 객체의 필드 중 하나를 섀도잉합니다. — 생성자 내부의 x는 생성자의 첫 번째 인수의 로컬 복사본입니다. 생성자가 Point 필드 x를 참조하려면 생성자는 this.x를 사용해야 합니다.

생성자와 함께 사용하기

생성자 내에서 this 키워드를 사용하여 같은 클래스에 있는 다른 생성자를 호출할 수도 있습니다. 이를 명시적 생성자 호출이라고 합니다. 다음은 객체 섹션에 있는 것과는 다른 구현을 가진 또 다른 Rectangle 클래스입니다.

public class Rectangle {
    private int x, y;
    private int width, height;
        
    public Rectangle() {
        this(0, 0, 1, 1);
    }
    public Rectangle(int width, int height) {
        this(0, 0, width, height);
    }
    public Rectangle(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }
    ...
}

이 클래스에는 생성자 집합이 포함되어 있습니다. 각 생성자는 사각형의 멤버 변수 중 일부 또는 전부를 초기화합니다. 생성자는 인자로 초기값이 제공되지 않는 멤버 변수에 대해 기본값을 제공합니다. 예를 들어, 인수가 없는 생성자는 좌표 0,0에 1x1 직사각형을 만듭니다. 인자가 두 개인 생성자는 인자가 네 개인 생성자를 호출하여 widthheight를 전달하지만 항상 0,0 좌표를 사용합니다. 이전과 마찬가지로 컴파일러는 인자의 수와 타입에 따라 어떤 생성자를 호출할지 결정합니다.

다른 생성자가 있는 경우, 다른 생성자의 호출은 생성자의 첫 번째 줄에 있어야 합니다.

 

클래스 멤버에 대한 접근 권한 제어하기

액세스 수준 수정자는 다른 클래스가 특정 필드를 사용하거나 특정 메서드를 호출할 수 있는지 여부를 결정합니다. 액세스 제어에는 두 가지 수준이 있습니다:

  • 최상위 수준 —public 또는 패키지-비공개(명시적 수정자 없음).
  • 멤버 수준 — public, private, protected 또는 패키지-비공개(명시적 수정자 없음).

클래스는 public 수정자를 사용하여 선언할 수 있으며, 이 경우 해당 클래스는 모든 클래스에서 볼 수 있습니다. 클래스에 수정자가 없는 경우(기본값, 패키지 비공개라고도 함), 해당 클래스는 자체 패키지 내에서만 표시됩니다(패키지는 관련 클래스의 그룹으로 명명되며, 이에 대해서는 이후 섹션에서 배우게 됩니다).

멤버 수준에서도 최상위 클래스와 마찬가지로 같은 의미로 public 수정자를 사용하거나 수정자를 사용하지 않을 수 있습니다(패키지-비공개). 멤버의 경우 privateprotected이라는 두 가지 추가 액세스 수정자가 있습니다. private 수정자는 해당 멤버가 자체 클래스에서만 액세스할 수 있음을 지정합니다. protected 수정자는 멤버가 자체 패키지 내에서만 액세스 할 수 있도록 지정합니다(패키지 비공개와 마찬가지로), 또한 다른 패키지에 있는 해당 클래스의 하위 클래스에서도 액세스 할 수 있도록 지정합니다.

The following table shows the access to members permitted by each modifier.

ModifierClassPackageSubclassWorld
publicYYYY
protectedYYYN
no modifierYYNN
privateYNNN

첫 번째 열은 클래스 자체에 액세스 수준에 정의된 멤버에 대한 액세스 권한이 있는지 여부를 나타냅니다. 보시다시피 클래스는 항상 자체 멤버에 대한 액세스 권한이 있습니다.

두 번째 열은 클래스와 같은 패키지에 있는 클래스(부모 클래스에 관계없이)가 멤버에 액세스할 수 있는지 여부를 나타냅니다.

세 번째 열은 이 패키지 외부에서 선언된 클래스의 서브클래스가 멤버에 액세스할 수 있는지 여부를 나타냅니다.

네 번째 열은 모든 클래스가 멤버에 대한 액세스 권한이 있는지 여부를 나타냅니다.

액세스 수준은 두 가지 방식으로 영향을 줍니다. 첫째, Java 플랫폼의 클래스 등 다른 소스에서 가져온 클래스를 사용하는 경우 액세스 수준에 따라 내 클래스에서 사용할 수 있는 해당 클래스의 멤버가 결정됩니다. 둘째, 클래스를 작성할 때 클래스의 모든 멤버 변수와 모든 메서드가 어떤 액세스 수준을 가져야 하는지 결정해야 합니다.

접근 수준 선택에 관한 팁:

다른 프로그래머가 내 클래스를 사용하는 경우 오용으로 인한 오류가 발생하지 않도록 하고 싶을 것입니다. 접근 수준은 이를 위해 도움이 될 수 있습니다.

특정 회원에게 적합한 가장 제한적인 액세스 수준을 사용하세요. 특별한 이유가 없는 한 private를 사용하세요.

상수를 제외한 public 필드는 피하세요. 튜토리얼의 많은 예제에서 public 필드를 사용합니다. 이는 몇 가지 요점을 간결하게 설명하는 데 도움이 될 수 있지만 프로덕션 코드에는 권장되지 않습니다. public 필드는 특정 구현에 연결되는 경향이 있고 코드를 변경할 때 유연성을 제한하기 때문에 좋은 방법이 아닙니다.

 

클래스 멤버 이해하기

이 섹션에서는 클래스의 인스턴스가 아닌 클래스에 속하는 필드와 메서드를 생성하기 위해 static 키워드를 사용하는 방법에 대해 설명합니다.

클래스 변수

동일한 클래스 블루프린트에서 여러 개의 객체가 생성되면 각각 고유한 인스턴스 변수의 복사본을 갖게 됩니다. Bicycle 클래스의 경우 인스턴스 변수는 cadence, gear, speed입니다. 각 Bicycle 객체에는 이러한 변수에 대한 고유한 값이 서로 다른 메모리 위치에 저장됩니다.

때로는 모든 객체에 공통된 변수를 갖고 싶을 때가 있습니다. 이 경우 static 수정자를 사용하면 됩니다. 선언에 정적 수정자가 있는 필드를 정적 필드 또는 _클래스 변수_라고 합니다. 이러한 필드는 객체가 아닌 클래스와 연관됩니다.

클래스의 모든 인스턴스는 메모리의 고정된 위치 하나에 있는 클래스 변수를 공유합니다. 모든 객체는 클래스 변수의 값을 변경할 수 있지만 클래스 변수는 클래스의 인스턴스를 생성하지 않고도 조작할 수 있습니다.

예를 들어 여러 개의 Bicycle 객체를 만들고 첫 번째 객체에 1로 시작하는 일련 번호를 각각 할당한다고 가정해 보겠습니다. 이 ID 번호는 각 객체마다 고유하므로 인스턴스 변수입니다. 동시에 다음 객체에 할당할 ID를 알 수 있도록 생성된 Bicycle 객체 수를 추적하는 필드가 필요합니다. 이러한 필드는 개별 객체가 아니라 클래스 전체와 관련이 있습니다. 이를 위해서는 다음과 같이 클래스 변수인 numberOfBicycles가 필요합니다:

public class Bicycle {
        
    private int cadence;
    private int gear;
    private int speed;
        
    // add an instance variable for the object ID
    private int id;
    
    // add a class variable for the
    // number of Bicycle objects instantiated
    private static int numberOfBicycles = 0;
        ...
}

클래스 변수는 다음과 같이 클래스 이름 자체로 참조됩니다.

Bicycle.numberOfBicycles

이렇게 하면 클래스 변수임을 명확히 알 수 있습니다.

참고: myBike.numberOfBicycles와 같은 객체 참조를 사용하여 정적 필드를 참조할 수도 있지만 클래스 변수임을 명확히 하지 않으므로 권장하지 않습니다.

Bicycle 생성자를 사용하여 ID 인스턴스 변수를 설정하고 numberOfBicycles 클래스 변수를 증가시킬 수 있습니다:

public class Bicycle {
        
    private int cadence;
    private int gear;
    private int speed;
    private int id;
    private static int numberOfBicycles = 0;
        
    public Bicycle(int startCadence, int startSpeed, int startGear){
        gear = startGear;
        cadence = startCadence;
        speed = startSpeed;
 
        // increment number of Bicycles
        // and assign ID number
        id = ++numberOfBicycles;
    }
 
    // new method to return the ID instance variable
    public int getID() {
        return id;
    }
        ...
}

클래스 메서드

Java 프로그래밍 언어는 정적 변수뿐만 아니라 정적 메서드도 지원합니다. 선언에 static 수정자가 있는 정적 메서드는 다음과 같이 클래스의 인스턴스를 생성할 필요 없이 클래스 이름과 함께 호출해야 합니다.

ClassName.methodName(args)

참고: instanceName.methodName(args)와 같은 객체 참조를 사용하여 정적 메서드를 참조할 수도 있지만 클래스 메서드라는 것을 명확하게 알 수 없으므로 권장하지 않습니다.

정적 메서드의 일반적인 용도는 정적 필드에 액세스하는 것입니다. 예를 들어, Bicycle 클래스에 정적 메서드를 추가하여 numberOfBicycles 정적 필드에 액세스할 수 있습니다:

public static int getNumberOfBicycles() {
    return numberOfBicycles;
}

인스턴스 및 클래스 변수와 메서드의 모든 조합이 허용되는 것은 아닙니다:

  • 인스턴스 메서드는 인스턴스 변수와 인스턴스 메서드에 직접 접근할 수 있습니다.
  • 인스턴스 메서드는 클래스 변수와 클래스 메서드에 직접 접근할 수 있습니다.
  • 클래스 메서드는 클래스 변수와 클래스 메서드에 직접 접근할 수 있습니다.
  • 클래스 메서드는 인스턴스 변수나 인스턴스 메서드에 직접 접근할 수 없으며, 객체 참조를 사용해야 합니다. 또한 클래스 메서드는 참조할 인스턴스가 없으므로 this 키워드를 사용할 수 없습니다.

상수

정적수정자는final 수정자와 함께 상수를 정의하는 데에도 사용됩니다. 최종 수정자는 이 필드의 값이 변경될 수 없음을 나타냅니다.

예를 들어, 다음 변수 선언은 값이 파이(지름에 대한 원의 둘레의 비율)의 근사치인 PI라는 상수를 정의합니다:

static final double PI = 3.141592653589793;

이러한 방식으로 정의된 상수는 재할당할 수 없으며, 프로그램에서 재할당을 시도하면 컴파일 타임 오류가 발생합니다. 관례에 따라 상수 값의 이름은 대문자로 표기합니다. 이름이 두 개 이상의 단어로 구성된 경우 단어는 밑줄(_)로 구분됩니다.

참고: 기본 타입이나 문자열이 상수로 정의되고 컴파일 시점에 값을 알 수 있는 경우 컴파일러는 코드의 모든 곳에서 상수 이름을 해당 값으로 바꿉니다. 이를 컴파일 타임 상수라고 합니다. 외부 세계의 상수 값이 변경되는 경우(예: 파이가 실제로는 3.975여야 한다고 규정된 경우) 이 상수를 사용하는 모든 클래스를 다시 컴파일하여 현재 값을 가져와야 합니다.

자전거 클래스

이 섹션에서 모든 수정이 완료된 ‘자전거’ 클래스는 이제 다음과 같습니다:

public class Bicycle {
        
    private int cadence;
    private int gear;
    private int speed;
        
    private int id;
    
    private static int numberOfBicycles = 0;
 
        
    public Bicycle(int startCadence,
                   int startSpeed,
                   int startGear) {
        gear = startGear;
        cadence = startCadence;
        speed = startSpeed;
 
        id = ++numberOfBicycles;
    }
 
    public int getID() {
        return id;
    }
 
    public static int getNumberOfBicycles() {
        return numberOfBicycles;
    }
 
    public int getCadence() {
        return cadence;
    }
        
    public void setCadence(int newValue) {
        cadence = newValue;
    }
        
    public int getGear(){
        return gear;
    }
        
    public void setGear(int newValue) {
        gear = newValue;
    }
        
    public int getSpeed() {
        return speed;
    }
        
    public void applyBrake(int decrement) {
        speed -= decrement;
    }
        
    public void speedUp(int increment) {
        speed += increment;
    }
}

 

필드 초기화하기

지금까지 살펴본 것처럼 선언에서 필드의 초기 값을 제공할 수 있는 경우가 많습니다:

public class BedAndBreakfast {
 
    // initialize to 10
    public static int capacity = 10;
 
    // initialize to false
    private boolean full = false;
}

이 방법은 초기화 값을 사용할 수 있고 초기화를 한 줄에 넣을 수 있을 때 잘 작동합니다. 그러나 이러한 형태의 초기화에는 단순성 때문에 한계가 있습니다. 초기화에 일부 로직(예: 오류 처리 또는 복잡한 배열을 채우기 위한 for 루프)이 필요한 경우 단순 할당은 부적절합니다. 인스턴스 변수는 생성자에서 초기화할 수 있으며, 여기서 오류 처리 또는 기타 논리를 사용할 수 있습니다. 클래스 변수에 대해 동일한 기능을 제공하기 위해 Java 프로그래밍 언어에는 정적 초기화 블록 이 포함되어 있습니다.

참고: 클래스 정의의 시작 부분에 필드를 선언할 필요는 없지만, 이것이 가장 일반적인 관행입니다. 필드를 사용하기 전에 선언하고 초기화하기만 하면 됩니다.

정적 초기화 블록

정적 초기화 블록은 중괄호 { }로 묶인 일반 코드 블록으로, 그 앞에 static 키워드가 붙습니다. 다음은 예시입니다:

static {
    // whatever code is needed for initialization goes here
}

클래스에는 정적 초기화 블록이 얼마든지 포함될 수 있으며, 클래스 본문의 어느 곳에나 나타날 수 있습니다. 런타임 시스템은 정적 초기화 블록이 소스 코드에 나타나는 순서대로 호출되도록 보장합니다.

정적 블록에 대한 대안으로 비공개 정적 메서드를 작성할 수 있습니다:

class Whatever {
    public static varType myVar = initializeClassVariable();
        
    private static varType initializeClassVariable() {
 
        // initialization code goes here
    }
}

비공개 정적 메서드의 장점은 나중에 클래스 변수를 다시 초기화해야 할 때 재사용할 수 있다는 점입니다.

단, 정적 블록의 내용을 재정의할 수 없다는 점에 유의해야 합니다. 일단 작성된 후에는 이 블록이 실행되는 것을 막을 수 없습니다. 어떤 이유로든 정적 블록의 콘텐츠를 실행할 수 없는 경우 이 클래스에 대한 객체를 인스턴스화할 수 없으므로 애플리케이션이 제대로 작동하지 않습니다. 정적 블록에 파일 시스템이나 네트워크와 같은 외부 리소스에 액세스하는 코드가 포함된 경우 이러한 문제가 발생할 수 있습니다.

인스턴스 멤버 초기화하기

일반적으로 인스턴스 변수를 초기화하는 코드는 생성자 안에 넣습니다. 인스턴스 변수를 초기화하기 위해 생성자를 사용하는 대신 초기화 블록과 파이널 메서드 두 가지가 있습니다.

인스턴스 변수를 위한 초기화 블록은 정적 초기화 블록과 비슷하지만 정적 키워드가 없습니다:

{
    // whatever code is needed for initialization goes here
}

Java 컴파일러는 모든 생성자에 초기화 블록을 복사합니다. 따라서 이 접근 방식은 여러 생성자 간에 코드 블록을 공유하는 데 사용할 수 있습니다.

final method 는 서브클래스에서 재정의할 수 없습니다. 이에 대해서는 상속 섹션에서 설명합니다. 다음은 인스턴스 변수를 초기화하기 위해 final method를 사용하는 예시입니다:

class Whatever {
    private varType myVar = initializeInstanceVariable();
        
    protected final varType initializeInstanceVariable() {
 
        // initialization code goes here
    }
}

이는 서브클래스가 초기화 메서드를 재사용하고자 할 때 특히 유용합니다. 인스턴스 초기화 중에 non-final 메서드를 호출하면 문제가 발생할 수 있으므로 이 메서드는 final 메서드입니다.

 

클래스 및 객체 생성 및 사용 요약

클래스 선언은 클래스의 이름을 지정하고 클래스 본문을 중괄호로 묶습니다. 클래스 이름 앞에는 수정자가 올 수 있습니다. 클래스 본문에는 클래스의 필드, 메서드 및 생성자가 포함됩니다. 클래스는 필드를 사용하여 상태 정보를 담고 메서드를 사용하여 동작을 구현합니다. 클래스의 새 인스턴스를 초기화하는 생성자는 클래스 이름을 사용하며 반환 타입이 없는 메서드처럼 보입니다.

클래스 및 멤버에 대한 액세스는 선언에 public과 같은 액세스 수정자를 사용하여 동일한 방식으로 제어합니다.

멤버 선언에 static 키워드를 사용하여 클래스 변수 또는 클래스 메서드를 지정합니다. static으로 선언되지 않은 멤버는 암시적으로 인스턴스 멤버입니다. 클래스 변수는 클래스의 모든 인스턴스가 공유하며 클래스 이름과 인스턴스 참조를 통해 액세스할 수 있습니다. 클래스의 인스턴스는 각 인스턴스 변수의 자체 복사본을 가지며, 인스턴스 참조를 통해 액세스해야 합니다.

new 연산자와 생성자를 사용하여 클래스에서 객체를 생성합니다. new 연산자는 생성된 객체에 대한 참조를 반환합니다. 이 참조를 변수에 할당하거나 직접 사용할 수 있습니다.

선언된 클래스 외부의 코드에서 액세스할 수 있는 인스턴스 변수와 메서드는 한정된 이름을 사용하여 참조할 수 있습니다. 인스턴스 변수의 정규화된 이름은 다음과 같습니다:

objectReference.variableName

메서드의 정규화된 이름은 다음과 같습니다:

objectReference.methodName(argumentList)

또는:

objectReference.methodName()

가비지 컬렉터는 사용하지 않는 객체를 자동으로 정리합니다. 프로그램이 더 이상 참조를 보유하지 않는 객체는 사용되지 않는 객체입니다. 참조를 보유하고 있는 변수를 null로 설정하여 명시적으로 참조를 삭제할 수 있습니다.