https://sundaland.tistory.com/265
객체 (Objects)
일반적인 자바 프로그램은 많은 객체를 생성하며, 이 객체들은 메서드를 호출하여 상호 작용한다.
이러한 객체 상호 작용을 통해 프로그램은 다양한 작업을 수행할 수 있다.
객체가 생성된 목적을 다하면, 해당 객체의 자원을 다른 객체에서 재사용할 수 있도록 회수된다.
public class Point {
public int x = 0;
public int y = 0;
// a constructor!
public Point(int a, int b) {
x = a;
y = b;
}
}
public class Rectangle {
public int width = 0;
public int height = 0;
public Point origin;
// four constructors
public Rectangle() {
origin = new Point(0, 0);
}
public Rectangle(Point p) {
origin = p;
}
public Rectangle(int w, int h) {
origin = new Point(0, 0);
width = w;
height = h;
}
public Rectangle(Point p, int w, int h) {
origin = p;
width = w;
height = h;
}
// a method for moving the rectangle
public void move(int x, int y) {
origin.x = x;
origin.y = y;
}
// a method for computing the area of the rectangle
public int getArea() {
return width * height;
}
}
public class CreateObjectDemo {
public static void main(String[] args) {
// 하나의 Point 객체와 두 개의 Rectangle 객체를 선언하고 생성합니다.
Point originOne = new Point(23, 94);
Rectangle rectOne = new Rectangle(originOne, 100, 200);
Rectangle rectTwo = new Rectangle(50, 100);
// rectOne의 너비, 높이 및 면적을 출력합니다.
System.out.println("Width of rectOne: " + rectOne.width);
System.out.println("Height of rectOne: " + rectOne.height);
System.out.println("Area of rectOne: " + rectOne.getArea());
// rectTwo의 위치를 설정합니다.
rectTwo.origin = originOne;
// rectTwo의 위치를 출력합니다.
System.out.println("X Position of rectTwo: " + rectTwo.origin.x);
System.out.println("Y Position of rectTwo: " + rectTwo.origin.y);
// rectTwo를 이동시키고 새로운 위치를 출력합니다.
rectTwo.move(40, 72);
System.out.println("X Position of rectTwo: " + rectTwo.origin.x);
System.out.println("Y Position of rectTwo: " + rectTwo.origin.y);
}
}
Width of rectOne: 100
Height of rectOne: 200
Area of rectOne: 20000
X Position of rectTwo: 23
Y Position of rectTwo: 94
X Position of rectTwo: 40
Y Position of rectTwo: 72
객체 생성
클래스는 객체의 설계도를 제공하며, 클래스에서 객체를 생성한다.
각 명령문은 객체를 생성하고 변수를 할당한다.
Point originOne = new Point(23, 94);
Rectangle rectOne = new Rectangle(originOne, 100, 200);
Rectangle rectTwo = new Rectangle(50, 100);
- 선언 (Declararion) : 코드들은 변수 선언으로, 변수 이름을 객체 타입과 연관시킨다.
- 인스턴스화 (Instantiaion) : new 키워드는 객체를 생성하는 자바 연산자다.
- 초기화 (Initialization) : new 연산자 뒤에는 생성자를 호출하여 새 객체를 초기화한다.
참조 변수 객체 선언
type name;
이는 컴파일러에게 name을 사용하여 type 타입의 데이터를 참조할 것임을 알린다.
기본 데이터 타입 변수의 경우, 이 선언은 변수에 필요한 적절한 메모리 양을 예약하기도 한다.
참조 변수를 별도의 코드라인에 선언할 수도 있다.
Point orginOne;
originOne을 선언하면, 객체가 실제로 생성되어 할당될 때까지 그 값은 결정되지 않는다.
참조 변수는 선언한 것 만으로는 객체가 생성되지 않는다.
코드 사용전에 originOne에 객체를 할당하지 않으면 컴파일 오류가 발생한다.
클래스 인스턴스화 (Instantiating a Class)
new 연산자는 새로운 객체를 위한 메모리를 할당하고 해당 메모리에 대한 참조를 반환함으로써 클래스를 인스턴스화 한다. new 연산자는 객체 생성자도 호출한다.
인스턴스화는 객체를 생성한다는 것과 동일한 의미이다. 객체를 생성하면 클래스의 인스턴스를 생성하는 것이므로, 클래스를 인스턴스화라고 한다.
new 연산자는 하나의 후위 매게변수, 즉 생성자 호출이 필요하다. 생성자 이름은 인스턴스화할 클래스의 이름을 제공한다.
new 연산자는 생성한 객체에 대한 참조를 반환한다. 이 참조는 일반적으로 적절한 타입의 변수에 할당된다.
Point originOne = new Point(23, 94);
new 연산자가 반환한 참조는 변수에 할당되지 않아도 된다.
객체 초기화 (Initializing an Object)
public class Point {
public int x = 0;
public int y = 0;
// 생성자
public Point(int a, int b) {
x = a;
y = b;
}
}
이 클래스에는 단일 생성자가 포함되어 있다. 생성자는 클래스 이름과 동일한 이름을 사용하며 리턴 타입이 없다.
Point 클래스 생성자는 두 개의 정수 매게변수인 int a, int b를 취한다.
아래의 문장은 이 매게변수에 23과 94라는 값을 제공한다.
Point originOne = new Point(23, 94);
아래의 Rectangle 클래스의 코드는 네 개의 생성자를 포함하고 있다.
public class Rectangle {
public int width = 0;
public int height = 0;
public Point origin;
// 네 개의 생성자
public Rectangle() {
origin = new Point(0, 0);
}
public Rectangle(Point p) {
origin = p;
}
public Rectangle(int w, int h) {
origin = new Point(0, 0);
width = w;
height = h;
}
public Rectangle(Point p, int w, int h) {
origin = p;
width = w;
height = h;
}
// 사각형을 이동시키는 메서드
public void move(int x, int y) {
origin.x = x;
origin.y = y;
}
// 사각형의 면적을 계산하는 메서드
public int getArea() {
return width * height;
}
}
각 생성자는 기본 타입과 참조 타입을 모두 사용하여 사각형의 원점, 너비, 넓이 및 높이에 대한 초기값을 제공할 수 있도록 한다. 클래스에 여려 생성자가 있는 경우, 이들은 서로 다른 시그니처를 가져야 한다.
자바 컴파일러는 매게변수의 개수와 타입을 기반으로 생성자를 구분하기 떄문이다.
Rectangle rectOne = new Rectangle(originOne, 100, 200);
위의 코드를 만난 컴파일러는 Point 매게변수 하나와 두 개의 정수 매게변수를 필요로 하는 Rectangle 클래스의 생성자를
호출하게 된다.
또한 이 코드는 origin을 originOne으로 초기화하는 Rectangle의 생성자 중 하나를 호출하기 때문에 동일한 Point 객체에 대한 두 개의 참조가 존재하게 된다. 즉, 객체는 여러개의 참조를 가질 수 있다.
Rectangle rectTwo = new Rectangle(50, 100);
위의 코드는 디버깅 모드로 생성자가 x와 y값이 0으로 초기화된 새로운 Point 객체를 생성하는 것을 알 수 있다.
모든 클래스에는 적어도 하나의 생성자가 있다. 클래스가 명시적으로 생성자를 선언하지 않더라도 자바 컴퍼일러는 자동으로 파라미터가 없는 디폴트 생성자를 제공한다.
이 디폴트 생성자는 클래스 부모의 파라미터가 없는 생성자를 호출한다. 또한 클래스에 부모 클래스가 없으면 Object 클래스를 호출한다.
Object 클래스에는 생성자가 있지만, 만약 부모 생성자가 없는 경우에는 컴파일러가 프로그램 실행을 거부한다.
객체 필드 참조 (Referencing an Objects Fields)
객체 필드는 이름을 통해 접근한다.
필드를 해당 클래스 내에서 사용할 때는 단순히 이름을 가지고 사용할 수 있다.
System.out.println("Width and height are: " + width + ", " + height);
만약 객체의 클래스 외부에 있는 코드는 객체 참조나 expression을 사용하고, 그 뒤에 점(.) 연산자와 간단한 필드 이름을 사용해야 한다.
objectReference.fieldName
CreateObjectDemo 클래스의 코드는 Rectangle 클래스의 코드 외부에 있다. 따라서 rectOne이라는 이름의 Rectangle 객체 내의 origin, width, height 필드를 참조하려면 각각 rectOne.origin, rectOne.width, rectOne.height라는 이름을 사용해야한다.
System.out.println("Width of rectOne: " + rectOne.width);
System.out.println("Height of rectOne: " + rectOne.height);
필드에 접근하려면, 객체에 명명된 참조를 사용하거나 객체 참조를 반환하는 표현식을 사용할 수 있다.
바로 new 연산자가 객체에 대한 참조를 반환한다. 따라서 new로 부터 반환된 값을 사용하여 새 객체의 필드에 접근할 수 있다.
int height = new Rectangle().height;
위의 코드는 Rectangle 객체를 생성하고 즉시 그 높이를 가져온다. 이 문장이 실행된 후, 프로그램은 생성된 Rectangle에 대한 참조를 더 이상 가지지 않는다. 참조를 어디에도 저장하지 않았기 때문이다.
오브젝트 메서드 호출
객체의 메서드를 호출하기 위해서 객체 참조를 사용한다. 객체 참조에 메서드의 간단한 이름을 dot(.) 연산자로 연결하고, 괄호 안에 메서드의 인수를 제공한다.
메서드에 매게변수가 필요하지 않은 경우에는 빈 괄호를 사용한다.
objectReference.methodName(argumentList);
objectReference.methodName();
Rectangle 클래스에는 두 개의 메서드가 있다. 사각형 면적을 계산하는 getArea() 메서드와 사각형은 원점을 변경하는 move() 메서드이다.
다음은 이 두 메서드를 호출하는 CreateObjectDemo의 코드이다.
System.out.println("Area of rectOne: " + rectOne.getArea());
...
rectTwo.move(40, 72);
첫 번째 명령문은 rectOne의 getArea() 메서드를 호출하여 결과를 출력한다. 두 번째 라인은 rectTwo를 이동시키는데, 이는 move() 메서드가 객체의 origin.x와 origin.y의 새로운 값을 할당하기 떄문이다.
인스턴스 필드와 마찬가지로, objectReference는 객체에 대한 참조여야 한다. 변수 이름을 사용할 수 있지만, 객체 참조를 반환하는 표현식을 사용할 수도 있습니다.
new 연산자는 객체 참조를 반환하므로, new로부터 리턴된 값을 사용하여 새 객체의 메서드를 호출할 수 있다.
new Rectangle(100, 50).getArea()
new Rectangle(100,50) 계산식은 Rectangle 객체를 참조하는 객체 참조를 반환한다. 이 처럼 dot 표기법을 사용하며여 새로운 Rectangle getArea() 메서드를 호출하여 새 사각형의 면적을 계산할 수 있습니다.
getArea()는 값을 리턴한다. 값을 리턴하는 메서드의 경우, 메서드 호출을 계산식에 사용할 수 있다. 리턴 값을 변수에 할당하거나, 결정을 내리거나, 루프를 제어하는 데 사용할 수 있다.
이 코드는 getArea()가 리턴한 값을 areaOfRectangle 변수에 할당한다.
int areaOfRectangle = new Rectangle(100, 50).getArea();
특정 객체의 메서드를 호출하는 것은 그 객체에 메시지를 보내는 것과 동일하다. 이 경우 getArea()가 호출되는 객체는 생성자에 의해 반환딘 사각형 객체이다.
가비지 컬렉션 (Garbage Collection)
일부 객체 지향 언어에서는 생성한 모든 객체를 추적하고 더 이상 필요하지 않을 때 명시적으로 파괴해야한다.
메모리를 명시적으로 관리하는 것은 번거롭고 오류가 발생하기 쉽다.
하지만 자바 런타임 환경은 더 이상 사용되지 않는 객체를 자동으로 삭제한다. 이 과정을 가비지 컬렉션이라 한다,
변수에 보관된 참조는 변수가 스코프를 벗어날 때 일반적으로 제거된다. 또 변수를 특별한 값인 null로 설정하여 객체 참조를 명시적으로 제거할 수 있다.
하나의 프로그램은 동일한 객체에 대한 여러 참조를 가질 수 있으므로, 객체가 가비지 컬렉션의 대상이 되려면 해당 객체에 대한 모든 참조가 제거되어야 한다.
자바 런타임 환경에서는 가비지 컬렉터가 더 이상 참조되지 않는 객체가 사용하는 메모리를 주기적으로 해제한다.
가비지 컬렉션이 동작하면 주체(Thread)들이 CPU 사용하는 것이 불가능 해지기 때문에 작업이 끝날때까지 프로그램이 중단된다.
클래스 더 알아보기
- 메서드에서 값 리턴하기
- this 키워드
- 클래스 필드(멤버)와 인스턴스 멤버의 차이
- 접근 제어
메서드의 반환 값
메서드는 다음 상황 중 하나가 발생하면 이를 호출한 코드로 반환된다.
- 메서드 내의 모든 명령문을 완료했을 때
- return 명령문에 도달했을때
- 예외를 던졌을 때
메서드 선언에서 메서드의 리턴 타입을 선언한다. 메서드 본문 내에서 return 명령문을 사용하여 값을 리턴한다.
void로 선언된 메서드는 값을 리턴하지 않는다. 반드시 retrun 문을 포함할 필요는 없지만, 포함할 수도 있다.
그런경우 return 명령문은 제어 흐름 블록에서 벗어나 메서드를 종료하는데 사용된다.
void로 선언된 메서드에서 값을 반환하려고 하면 컴파일 오류가 발생한다.
void로 선언되지 않은 메서드는 해당하는 반환 값을 가진 return 문을 포함해야 한다.
return;
return returnValue;
리턴 값의 데이터 타입은 메서드의 선언된 리턴 타입과 일치해야 한다.
public int getArea() {
return width * height;
}
위의 코드는 Rectangle 클래스의 getArea() 메서드는 정수 값을 리턴 한다.
메서드는 참조 타입도 반환할 수 있다.
아래의 코드의 Bicycle 메서드는 가장 빠른 Bicycle 객체의 대한 참조(값)를 반환한다.
public Bicycle seeWhosFastest(Bicycle myBike, Bicycle yourBike, Environment env) {
Bicycle fastest;
fastest = new Bicycle();
return fastest;
}
클래스 반환 혹은 인터페이스 (Returning a Class or Interface)
메서드가 whoFastest처럼 클래스 이름을 리턴 타입으로 사용하는 경우, 리턴되는 객체의 클래스 타입은 서브 클래스이거나 정확히 같은 클래스여야 한다.
아래의 코드는 ImaginartyNumber가 java.lang.Number의 서브 클래스이고, Number는 Object 서브 클래스인 클래스일때 Number을 반환하도록 선언된 메서드가 있다고 가정한다.
public Number returnANumber() {
...
}
returnANumber 메서드는 ImaginaryNumber를 리턴할 수 있지만 Object를 반환할 수는 없다. ImaginaryNumber는 Number의 서브 클래스이므로 Number이다. 하지만 Object는 반드시 Number 필요가 없으며 다른 타입일 수도 있다.
메서드를 재정의하여 원래 메서드의 서브 클래스를 반환하도록 정의할 수도 있다.
public ImaginaryNumber returnANumber() {
...
}
이 기술을 공변 반환 타입 (Covariant return type)이라고 하며, 리턴 타입이 서브 클래스 방향으로 변하는 것을 허용한다는 것을 의미한다.
# 인터페이스 이름도 리턴타입으로 사용이 가능하다. 물론 리턴되는 객체는 지정된 인터페이스를 구현해야한다.
class Animal {
public Animal getInstance() {
return new Animal();
}
public void makeSound() {
System.out.println("Some generic animal sound");
}
}
class Dog extends Animal {
@Override
public Dog getInstance() {
return new Dog(); // 코바리언트 반환 타입 사용
}
@Override
public void makeSound() {
System.out.println("Woof");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Animal();
myAnimal.makeSound(); // 출력: Some generic animal sound
Dog myDog = new Dog();
myDog.makeSound(); // 출력: Woof
Animal animalInstance = myAnimal.getInstance();
animalInstance.makeSound(); // 출력: Some generic animal sound
Dog dogInstance = myDog.getInstance();
dogInstance.makeSound(); // 출력: Woof
}
}
Some generic animal sound
Woof
Some generic animal sound
Woof
해당 코드는 공변(코바리언트) 반환 타입이 자바에서 어떻게 사용되는 보여주는 예제이다.
Dog 클래스의 getInstance 메서드는 Animal 클래스의 getInstance 메서드를 오버라이드하지만, 반환 타입이 Dog로 변경되었다.
이는 반환 타입이 부모 클래스의 반환 타입의 서브 타입을 경우에만 가능하다.
키워드 사용
인스턴스 메서드나 생성자 내에서 this는 현재 객체, 즉 메서드나 생서자가 호출되고 있는 객체에 대한 참조이다.
인스턴스 메서드나 생성자 내에서 this를 사용하여 현재 객체의 모든 멤버에 접근할 수 있다.
필드와 사용
this 키워드를 사용하는 가장 일반적인 이유는 필드가 메서드나 생성자의 파라미터에 의해 가려지기 때문이다.
public class Point {
public int x = 0;
public int y = 0;
public Point(int a, int b) { // 생성자
x = a;
y = b;
}
}
혹은 이렇게 작성할 수도 있다.
public class Point {
public int x = 0;
public int y = 0;
public Point(int a, int b) { // 생성자
x = a;
y = b;
}
}
생성자의 각 매게변수는 객채의 필드 중 하나를 가린다. 생성자 내부에서 x는 생성자의 첫 번째 매게변수의 로컬 복사본이다. Point 필드 x를 참조하기 위해서는 생성자가 this.x를 사용해야 한다.
생성자와 사용 (Constructor)
생성자 내에서 this 키워드를 사용하여 같은 클래스 내의 다른 생성자를 호출할 수도 있다.
이를 명시적 생성자 호출 (Explicit Constructor Invocation)이라고 한다.
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;
}
...
}
위 코드는 일련의 생성자를 포함하고 있다.
각 생성자는 사각형의 멤버 변수 중 일부 또는 전부를 초기화한다. 생성자는 매게변수 중 일부 또는 전부를 초기화한다.
생성자는 매게변수로 초기값이 제공되지 않은 멤버 변수에 대해 기본값을 제공한다.
컴파일러는 매게변수의 수와 유형애 따라서 어떤 생성자를 호출할지 결정한다.
Controlling Access to Members of a Class
액세스 수정자 (Access Modifier)는 다른 클래스가 특정 필드를 사용할 수 있는지 또는 특정 메서드를 호출할 수 있는지를 결정한다,
Top Level [class 지정] 수준 : public 또는 pakcage-private 패키지 전용 (명시적 수정자가 없음)
# 클래스 생성 위저드에서 Class Access Modifier로 선택할 수 있는 것은 public와 package-private뿐이다.
package com.intheeast.hello;
class TopLevelPackage { // 디폴트로 package-private로 패키지 전용이다.
int a;
int b;
public static void helloWorld() { } // 디폴트 컨스트럭터는 컴파일러에 의해 생성됨.
}
package com.intheeast.world;
public class Main {
public static void mani(String[] args) {
TopLevelPackage tpk = new TopLevelPackage(); // 컴파일 오류 발생
}
}
TopLevelPackage 클래스는 hello 패키지에 있기 때문에 world 패키지의 Main 클래스에서 액세스 할 수 없다.
클래스는 public 수정자로 선언될 수 있으며, 이 경우 해당 클래스는 모든 곳에서 모든 클래스에서 사용이 가능하다.
클래스에 수정자가 없으면 디폴트 (package-private)이 되며 해방 클래스는 자신의 패키지 내에서만 보인다.
멤버 수준 : public, private, protected 또는 패키지 전용 (명시적 수정자가 없음)
package myPackage;
public class Parent {
protected String protectedField; // protected 맴버 변수
public Parent() { // protected 생성자
this.protectedField = "Protected Field";
}
protected void protectedMethod() { // protected 메서드
System.out.println("This is Protected Method");
}
}
위 코드는 myPackage 패키지에서 구현된 Parent 클래스이다.
그리고 아래의 코드는 myPackage.mySubPackage에서 구현된 myPackage 패키지의 Parent 클래스를 상속하는 Chiled 클래스이다.
package myPackage.mySubPackage;
import myPackage.Parent;
public class Child extends Parent {
public void accessProtectedMember() {
System.out.println(protectedField);
this.protectedMethod();
}
}
또 다른 패키지 anotherPackage에서 구현된 AnotherPackageClass
package anotherPackage;
import myPackage.Parent;
import myPackage.mySubPackage.Child;
public class AnotherPackageClass {
public void accessProtectedMember() {
Child child = new Child();
child.accessProtectedMember();
Parent parent = new Parent();
System.out.println(parent.protectedField); // 컴파일 에러
parent.protectedMethod(); // 컴파일 에러
}
}
위 코드는 main 클래스가 없기 때문에 콘솔로 출력되지 않는다.
package anotherPackage;
import myPackage.mySubPackage.Child;
public class AnotherPackageClass {
public static void main(String[] args) {
Child child = new Child();
child.accessProtectedMember();
}
}
Protected Field
This is Protected Method
서브 클래스에서의 접근
package hello;
public class X {
protected String protectedField = "Protected Field in X";
protected void protectedMethod() {
System.out.println("Protected Method in X");
}
}
package world;
import hello.X;
public class Y extends X {
public void accessProtectedMember() { // 서브클래스 내에서는 protected 멤버에 접근 가능
System.out.println(protectedField);
protectedMethod();
}
public static void main(String[] args) {
Y y = new Y();
y.accessProtectedMember();
// 다른 패키지에서 상속받지 않은 클래스는 protected 멤버에 접근 불가
X x = new X();
System.out.println(x.protectedField); // 컴파일 오류
x.protectedMethod(); // 컴파일 오류
}
}
- Y 클래스는 X 클래스의 procted 멤버를 상속받으며, 자신의 메서드 내에서 procted 멤버에 엑세스할 수 있다.
- Y 클래스의 인스턴스 y로 accessProtectedMember 메서드를 호출하며, X 클래스의 protected 멤버에 접근할 수 있다.
멤버 수준에서는 public 수정자나 명시적 수정자가 없는 경우 (패키지 전용) 최상위 클래스와 동일한 의미로 사용이 가능하다.
멤버에 대해서는 두 개의 추가적인 접근 수정자인 private와 protected 있다. private과 protected, private 수정자는 멤버가 클래스 내에서만 접근할 수 있음을 지정한다.
protected 수정자는 멤버가 자신의 패키지 내에서만 접근할 수 있음을 지정하며 (package-private와 동일), 추가적으로 다른 패키지에 있는 해당 클래스의 하위 클래스에서도 접근할 수 있다.
다음 의 표는 각 수정자가 허용하는 멤버 접근을 보여준다.
수정자 | 클래스 | 페키지 | 서브 클래스 | 월드 |
public | O | O | O | O |
protected | O | O | O | X |
no modifier | O | O | X | X |
private | O | X | X | X |
첫 번째 열의 클래스는 항상 자신의 멤버에 접근할 수 있다.
두 번째 열은 클래스와 같은 패키지에 있는 다른 클래스들이 해당 멤버에 접근할 수 있는 지를 나타낸다. (상속 관계 없이)
세 번째 열은 해당 패키지 외부에 선언된 하위 클래스들이 멤버에 접근할 수 있는지를 나타낸다.
네 번째 열은 모든 클래스가 멤버에 접근할 수 있는 지를 나타낸다.
액세스 레벨은 두 가지 방식으로 영향을 미친다. 첫 번째로 자바 플랫폼의 클래스와 같이 다른 소스에서 온 클래스를 사용할 때 액세스 레벨이 해당 클래스 멤버를 자신의 클래스에서 사용할 수 있는지를 결정한다.
두 번째로 클래스를 작성할 때 클래스의 각 멤버 변수와 메서드에 어떤 액세스 레벨을 부여할지를 결정해야 한다.
액세스 레벨 선택에 대한 팁
특정 멤버에 대해 의미가 있는 가장 제한적인 접근 수준을 사용한다. 특별한 이유가 없다면 private를 사용한다.
상수를 제외하고는 public 필드를 피한다. public 필드는 특정 구현에 묶이기 쉬워 코드 변경 시 유연성이 제한된다.
Understanding Class Members
클래스의 인스터스가 아니라 클래스 자체에 속하는 필드와 메서드를 생성하기 위해 static 키워드를 사용하는 것에 대해 논의한다.
클래스 변수
동일한 클랴스에서 여러 객체를 생성할 때, 각 객체는 인스턴스 변수의 고유한 복사본을 갖는다.
Bicycle 클래스의 경우 인스턴스 변수는 cadence, gear, speed이다. 각 Bicycle 객체는 이 변수들에 대한 고유한 값을 가지며, 이는 다른 메모리 위치에 저장된다.
때로는 모든 객체에 공통된 변수를 가지고 싶을 때가 있습니다. 이는 static 수정자를 사용하여 달성할 수 있다.
선언에 static 수정자가 있는 필드는 정적 필드 또는 클래스 변수라고 불린다. 이들은 어떤 객체와도 관련되지 않고 클래스와 연관됩니다. 클래스의 모든 인스턴스는 하나의 고정된 메모리 위치에 있는 클래스 변수를 공유한다.
어떤 객체든 클래스 변수의 값을 변경할 수 있지만, 클래스 변수를 조작하기 위해 객체를 생성할 필요가 없다.
public class Bicycle {
private int cadence;
private int gear;
private int speed;
private int id; // 객체 ID를 위한 인스턴스 변수 추가
private static int numberOfBicycles = 0; // 생성된 Bicycle 객체의 수를 추적하는 클래스 변수 추가
...
}
Bicycle.numberOfBicycles
정적 필드는 아래 코드와 같이 객체 참조로 사용할 수 있지만, 클래스 변수임이 명확하지 않기에 권장되지 않는다.
myBike.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;
id = ++numberOfBicycles; // Bicycle 수를 증가시키고 ID 번호를 할당
}
public int getID() { // ID 인스턴스 변수를 반환하는 새로운 메서드 추가
return id;
}
...
}
클래스 메소드
자바 프로그래밍 언어는 정적 변수뿐만이 아니라 정적 메서드도 지원한다. 선언에 static 수정자가 있는 정적 메서드는 클래스의 인스턴스를 생성할 필요 없이 클래스 이름으로 호출해야 한다
ClassName.methodName(args);
정젝 메서드도 다음과 같이 갹체 참조로도 호출할 수 있지만, 클래스 메서드임을 명확하지 않음으로 권장되지 않는다.
instanceName.methodName(args);
정적 메서드의 일반적인 사용 사례는 정적 필드에 접근하는 것이다.
아래의 코드는 Bicycle 클래스에 numberOfBicycle 정적 필드에 접근하기 위한 정적 메서드를 추가할 수 있다.
public static int getNumberOfBicycles() {
return numberOfBicycles;
}
다음과 같은 인스턴스 변수와 클래스 변수 및 메서드의 조합은 허용되지 않는다.
- 인스턴스 메서드는 인스턴스 변수와 인스턴스 메서드에 직접 접근할 수 있습니다.
- 인스턴스 메서드는 클래스 변수와 클래스 메서드에 직접 접근할 수 있습니다.
- 클래스 메서드는 클래스 변수와 클래스 메서드에 직접 접근할 수 있습니다.
- 클래스 메서드는 인스턴스 변수나 인스턴스 메서드에 직접 접근할 수 없습니다.
- 객체 참조를 사용해야 합니다. 또한 클래스 메서드는 this 키워드를 사용할 수 없습니다. this는 인스턴스를 참조하기 때문이다.
상수 (Constants)
static 수정자는 final 수정자와 결합하여 상수를 정의하는데에도 사용된다. final 수정자는 이 필드의 값이 변경될 수 없음을 나타낸다.
아래의 변수 선언은 원주율의 근사값인 PI라는 상수를 정의한다.
static final double PI = 3.141592653589793;
이렇게 정의된 상수는 재할당할 수 없으며, 프로그램에서 이를 시도하면 컴파일 타임 오류가 발생한다.
기본 타입 또는 문자열이 상수로 정의되고 그 값이 컴파일 타임에 알려진 경우, 컴파일러는 코드 내에서 상수 이름을 값으로 대체하는데 이를 컴파일 타임 상수라고 한다.
상수의 값이 외부 세계에서 변경되는 경우, 이 상수를 사용하는 모든 클래스를 컴파일하여 현재 값을 가져와야 한다.
Bicycle Class 완성판
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;
}
}
필드 초기화 (Initializing)
필드 선언시 초기 값을 제공할 수 있다.
public class BedAndBreakfast {
public static int capacity = 10; // 10으로 초기화
private boolean full = false; // false로 초기화
}
이 방법은 초기화 값이 존재하고 하나의 라인으로 초기화할 수 있을 떄 잘 작동한다. 하지만 이러한 초기화 방식은 그 단순성 때문에 한계가 있다.
초기화에 논리가 필요할 경우 간단한 할당이 불충분하다. (오류 처리 및 복잡한 배열 채우기 위한 for 루프 등등)
인스턴스 변수는 생서자에서 초기화할 수 있으며, 이 경우엔 오류 처리나 다른 논리를 사용할 수 있습니다.
클래스 변수에 대해서도 동일한 기능을 제공하기 위해 자바 프로그래밍 언어는 정적 초기화 블록을 포합한다.
필드는 클래스 정의의 시작 부분에 선언할 필요는 없지만, 이는 가장 일반적인 관행이다. 필드는 사용되기 전에 선언되고 초기화되기만 하면 된다.
정적 초기화 블록 (Static Initializing Block)
중괄호로 둘러싸인 일반적인 코드 블록이며 static 키워드가 앞에 붙는다.
static {
// 초기화에 필요한 코드는 여기에 작성합니다.
}
클래스는 정적 초기화 블록을 여러 개 가질 수 있으며, 이들은 클래스 본문 내 어디에든 나타날 수 있습니다. 런타인 시스템은 정적 초기화 블록이 소스 코드에 나타나눈 순서대로 호출됨을 보장한다.
정적 블록에 대핸 대안으로 private 정적 메서드를 작성할 수 있다.
class Whatever {
public static varType myVar = initializeClassVariable();
private static varType initializeClassVariable() {
// 초기화 코드가 여기에 작성됩니다.
}
}
private 정적 메서드의 장점은 클래스 변수를 다시 초기화해야 할 경우 나중에 재사용할 수 있다.
인스턴스 변수 초기화
일반적으로 초기화하는 코드는 생성자에 넣는다. 인스턴스 변수를 초기화하기 위해 생성자를 사용하는 것에 대한 두 가지 대안으로 초기화 블록과 finale 메서드가 있다.
초기화 블록은 정적 초기화 블록과 비슷하지만 static 키워드가 없다.
{
// 초기화에 필요한 코드는 여기에 작성합니다.
}
자바 컴파일러는 초기화 블록을 각 생성자에 복사한다. 따라서 이 방법은 여러 생성자 간에 코드를 공유하는데 사용할 수 있다.
finale 메서드는 서브 클래스에서 오바라이드될 수 없다.
class Whatever {
private varType myVar = initializeInstanceVariable();
protected final varType initializeInstanceVariable() {
// 초기화 코드는 여기에 작성됩니다.
}
}
위 코드는 서브 클래스가 초기화 메서드를 재사용하려고 할 떄 유용하다. 메서드는 finale로 선언되는데 이는 인스턴스 초기화 중에 final이 아닌 메서드를 호출하면 문제가 발생할 수 있기 때문이다.
'자바 튜토리얼' 카테고리의 다른 글
클래스와 객체 (Classes and Objects) [4] (0) | 2024.07.05 |
---|---|
클래스와 객체 (Classes and Objects) [3] (0) | 2024.07.04 |
클래스와 객체 (Classes and Objects) [1] (0) | 2024.07.02 |
객체 지향 프로그래밍 (Object-Oriented Programming) (0) | 2024.07.02 |
제어 흐름 명령문 (Control Flow Statements) (0) | 2024.07.01 |