'자바의 정석 3rd Edition'을 공부하며 정리한 내용입니다.
- 접근 제어자 public, protected, default, private
- 그 외 static, ifinal, abstract, native, transient, synchronized, volatile, strictfp
static이 사용될 수 있는 곳 - 멤버변수, 메서드, 초기화 블럭
- 대상이 멤버변수인 경우
- 모든 인스턴스에 공통적으로 사용되는 클래스 변수가 됨
- 클래스 변수는 인스턴스를 생성하지 않고도 사용 가능
- 클래스가 메모리에 로드될 때 생성됨
- 대상이 메서드인 경우
- 인스턴스를 생성하지 않고도 호출이 가능한 static 메서드가 됨
- static 메서드 내에서는 인스턴스 멤버들을 직접 사용할 수 없음
final이 사용될 수 있는 곳 - 클래스, 메서드, 멤버변수, 지역변수
- 대상이 클래스인 경우
- 변경될 수 없는 클래스, 확장될 수 없는 클래스가 됨
- final로 지정된 클래스는 다른 클래스의 조상이 될 수 없음
- 대표적인 final 클래스로 String, Math가 있음
- 대상이 메서드인 경우
- 변경될 수 없는 메서드
- final로 지정된 메서드는 오버라이딩을 통해 재정의 될 수 없음
- 대상이 멤버변수, 지역변수인 경우
- 변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가 됨
생성자를 이용한 final 멤버 변수의 초기화
class Card {
final int NUMBER; // 상수지만 선언과 함께 초기화 하지 않고
final String KIND; // 생성자에서 단 한번만 초기화할 수 있다.
static int width = 100;
static int height = 250;
Card(String kind, int num) {
KIND = kind;
NUMBER = num;
}
Card() {
this("HEART", 1);
}
public String toString() {
return KIND +" "+ NUMBER;
}
}
class FinalCardTest {
public static void main(String args[]) {
Card c = new Card("HEART", 10);
// c.NUMBER = 5; // error 발생
System.out.println(c.KIND);
System.out.println(c.NUMBER);
System.out.println(c); // System.out.println(c.toString());
}
}
abstract가 사용될 수 있는 곳 - 클래스, 메서드
- 대상이 클래스인 경우
- 클래스 내에 추상 메서드가 선언되어 있음을 의미
- 대상이 메서드인 경우
- 선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알림
abstract class AbstractTesk { // 추상 클래스 (추상 메서드를 포함하는 클래스)
abstract void move(); // 추상 메서드 (구현부가 없는 메서드)
}
접근 제어자가 사용될 수 있는 곳 - 클래스, 멤버변수, 메서드, 생성자
- private 같은 클래스 내에서만 접근 가능
- default 같은 패키지 내에서만 접근 가능
- protected 같은 패키지 내에서, 그리고 다른 패키지의 자손클래스에서 접근 가능
- public 접근 제한이 없음
접근 범위가 넓은 쪽에서 좁은 쪽의 순으로 왼쪽부터 나열하면
public > protected > (default) > private
대상에 따라 사용할 수 있는 접근 제어자
대상 | 사용가능한 접근 제어자 |
---|---|
클래스 | public, (default) |
메서드 | public protected, (default), private |
멤버변수 | public protected, (default), private |
지역변수 | 없음 |
접근 제어자를 이용한 캡슐화
접근 제어자 사용 이유
- 외부로부터 데이터를 보호하기 위해
- 외부에는 불필요한, 내부적으로만 사용되는, 부분을 감추기 위해
생성자의 접근 제어자
대상에 따라 사용할 수 있는 제어자
대상 | 사용가능한 제어자 |
---|---|
클래스 | public, (default), final, abstract |
메서드 | 모든 접근 제어자, final, abstract, statit |
멤버변수 | 모든 접근 제어자, final, static |
지역변수 | final |
제어자를 조합해서 사용할 때 주의해야할 사항
1. 메서드에 static과 abstract를 함께 사용할 수 없음
static 메서드는 몸통이 있는 메서드에만 사용할 수 있기 때문
2. 클래스에 abstract와 final을 동시에 사용할 수 없음
클래스에 사용되는 final은 클래스를 확장할 수 없다는 의미이고 abstract는 상속을 통해서 완성되어야 한다는 의미이므로 서로 모순되기 때문
3. abstract 메서드의 접근 제어자가 private일 수 없음
abstract 메서드는 자손 클래스에서 구현해주어야 하는데 접근 제어자가 private이면, 자손 클래스에서 접근할 수 없기 때문
4. 메서드에 private과 final을 같이 사용할 필요는 없음
접근 제어자가 private인 메서드는 오버라이딩될 수 없기 때문. 둘 중 하나만 사용해도 의미가 충분
정리
- 조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있음
- 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수 없음
자손타입 -> 조상타입 (Up-casting): 형변환 생략 가능
자손타입 <- 조상타입(Down-casting): 형변환 생략 불가
class CastingTest2 {
public static void main(String args[]) {
Car car = new Car();
Car car2 = null;
FireEngine fe = null;
car.drive();
fe = (FireEngine)car; // 8번째 줄. 컴파일은 OK. 실행 시 에러가 발생
fe.drive();
car2 = fe;
car2.drive();
}
}
서로 상속관계에 있는 타입간의 형변환은 양방향으로 자유롭게 수행될 수 있으나, 참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않음
참조변수가 가리키는 인스턴스 타입이 무엇인지 확인하는 것이 중요!!
조상 클래스에 선언된 멤버변수와 같은 이름의 인스턴스 변수를 자손 클래스에 중복으로 정의했을 때, 조상타입의 참조변수로 자손 인스턴스를 참조하는 경우와 자손타입의 참조변수로 자손 인스턴스를 참조하는 경우 서로 다른 결과를 얻음
클래스이름.메서드()
로 호출멤버변수가 조상 클래스와 자손 클래스에 중복으로 정의된 경우, 조상타입의 참조변수를 사용했을 때는 조상 클래스에 선언된 멤버변수가 사용되고, 자손타입의 참조변수를 사용했을 때는 자손 클래스에 선언된 멤버변수가 사용됨
멤버변수들은 주로 private으로 접근을 제한하고, 외부에서는 메서드를 통해서만 멤버변수에 접근할 수 있도록 함
class Product {
int price; // 제품의 가격
int bonusPoint; // 제품구매 시 제공하는 보너스점수
Product(int price) {
this.price = price;
bonusPoint =(int)(price/10.0); // 보너스점수는 제품가격의 10%
}
}
class Tv extends Product {
Tv() {
// 조상클래스의 생성자 Product(int price)를 호출한다.
super(100); // Tv의 가격을 100만원으로 한다.
}
public String toString() { // Object클래스의 toString()을 오버라이딩한다.
return "Tv";
}
}
class Computer extends Product {
Computer() {
super(200);
}
public String toString() {
return "Computer";
}
}
class Buyer { // 고객, 물건을 사는 사람
int money = 1000; // 소유금액
int bonusPoint = 0; // 보너스점수
void buy(Product p) {
if(money < p.price) {
System.out.println("잔액이 부족하여 물건을 살수 없습니다.");
return;
}
money -= p.price; // 가진 돈에서 구입한 제품의 가격을 뺀다.
bonusPoint += p.bonusPoint; // 제품의 보너스 점수를 추가한다.
System.out.println(p + "을/를 구입하셨습니다.");
}
}
class PolyArgumentTest {
public static void main(String args[]) {
Buyer b = new Buyer();
b.buy(new Tv());
b.buy(new Computer());
System.out.println("현재 남은 돈은 " + b.money + "만원입니다.");
System.out.println("현재 보너스점수는 " + b.bonusPoint + "점입니다.");
}
}
/* 실행결과
Tv을/를 구입하셨습니다.
Computer을/를 구입하셨습니다.
현재 남은 돈은 700만원입니다.
현재 보너스점수는 30점입니다.
*/
// 1. 참조변수 배열로 처리하지 않을 경우
Product p1 = new Tv();
Product p2 = new Computer();
Product p3 = new Audio();
// 2. 참조변수 배열로 처리한 경우
Product p[] = new Product[3];
p[0] = new Tv();
p[1] = new Computer();
p[2] = new Audio();
Vector 클래스
Vector 클래스는 내부적으로 Object 타입의 배열을 가지고 있어 이 배열에 객체를 추가하거나 제거할 수 있게 작성됨
배열의 크기를 알아서 관리해주기 때문에 저장할 인스턴스의 개수에 신경쓰지 않아도 됨
단지 동적으로 크기가 관리되는 객체 배열
Vector 클래스의 주요 메서드
메서드/생성자 | 설명 |
---|---|
Vector() | 10개의 객체를 저장할 수 있는 Vector 인스턴스 생성. 10개 이상의 인스턴스가 저장되면, 자동적으로 크기가 증가됨 |
boolean add(Object o) | Vector에 객체를 추가. 추가에 성공하면 결과값으로 true, 실패하면 false 반환 |
boolean remove(Object o) | Vector에 저장되어 있는 객체를 제거. 제거에 성공하면 true, 실패하면 false 반환 |
boolean isEmpty() | Vector가 비어있는지 검사. 비어있으면 true, 비어있지 않으면 false 반환 |
Object get(int index) | 지정된 위치(index)의 객체를 반환. 반환타입이 Object 타입이므로 적절한 타입으로의 형변환 필요 |
int size() | Vector에 저장된 객체의 개수를 반환 |