이 노트는 "윤성우의 열혈 java 프로그래밍" 책을 공부하면서
내가 이해한대로 다시 정리하면서 작성되었다.
클래스를 구분하고 파악하는데 도움이 된다.
클래스 이름이 겹치는 문제도 해결한다.
서로 다른 패키지의 두 클래스는 인스턴스 생성 시 사용하는 이름이 다르다.
서로 다른 패키지의 두 클래스 파일은 저장되는 위치가 다르다.
하나의 소스 파일에는 public
으로 선언된 클래스의 정의를 하나만 둘 수 있다.
소스파일의 이름은 public
으로 선언된 클래스의 이름과 동일해야 한다.
'정보'는 클래스의 '인스턴스 변수'를 의미한다.
정보를 은닉한다는 것은 인스턴스 변수를 숨긴다는 뜻이다.
인스턴스 변수의 직접적 접근을 허용하면 중대한 문제가 발생할 수 있다.
인스턴스 변수에 private
선언을 추가하자.
getter
setter
접근의 허용 수준을 결정할 때 선언하는 키워드
지시자 | 클래스 내부 | 동일 패키지 | 상속 받은 클래스 | 이외의 영역 |
---|---|---|---|---|
private | ⭕️ | ❌ | ❌ | ❌ |
default | ⭕️ | ⭕️ | ❌ | ❌ |
protected | ⭕️ | ⭕️ | ⭕️ | ❌ |
public | ⭕️ | ⭕️ | ⭕️ | ⭕️ |
키워드가 아닌 아무런 선언도 하지 않은 상황
클래스가 public
으로 선언되면 위치에 상관없이
어디서든 해당 클래스의 인스턴스를 생성할 수 있다.
default
로 선언되면 동일 패키지로 묶인 클래스 내에서만 인스턴스 생성이 가능하다.
하나의 목적을 이루기 위해 관련 있는 모든 것을 하나의 클래스에 담아 두는 것.
클래스를 적절히 캡슐화 시키면 프로그램이 간결해진다.
한 클래스가 다른 클래스의 인스턴스를 멤버로 가질 수 있는데, 이런 관계를 포함관계라고 한다.
'인스턴스 변수'는 인스턴스가 생성되었을 때 생성된 인스턴스 안에 존재하는 변수이다.
하지만 '클래스 변수'는 인스턴스의 생성과 상관없이 존재하는 변수이다.
클래스 내에 선언된 변수 앞에 static
선언을 붙이면 인스턴스 변수가 아닌 '클래스 변수'가 된다.
package com.newwisdom;
class InstCnt {
static int instNum = 0; // 클래스 변수 (static 변수)
InstCnt() {
instNum++; // static 변수 값 증가
System.out.println("인스턴스 생성 : " + instNum);
}
}
class ClassVar {
public static void main(String[] args) {
InstCnt cnt1 = new InstCnt();
InstCnt cnt2 = new InstCnt();
InstCnt cnt3 = new InstCnt();
}
}
인스턴스 생성 : 1
인스턴스 생성 : 2
인스턴스 생성 : 3
static
으로 선언된 변수는 변수가 선언된 클래스의 모든 인스턴스가 공유하는 변수이다.
클래스 변수는 인스턴스 내에 존재하는 변수가 아니라 '어떠한 인스턴스에도 속하지 않는 상태로 메모리 공간에 딱 하나만 존재하는 변수'이다.
클래스 내부 접근
변수의 이름을 통해 직접 접근
way.num++;
클래스 외부 접근
클래스 또는 인스턴스의 이름을 통해 접근
AccessWay.num++;
클래스 변수는 인스턴스 생성 이전에 메모리 공간에 존재한다.
즉 클래스 변수는 해당 클래스 정보가 가상머신에 의해 읽히는 순간 메모리에 할당된다.
이는 인스턴스 생성과는 무관하게 이뤄진다.
class InstCnt {
static int instNum = 0; // 클래스 변수의 정상적 초기화 방법
InstCnt() {
instNum = 100; // 클래스 변수의 초기화가 아니다.
}
}
인스턴스 간 데이터 공유가 필요한 상황에서 클래스 변수를 선언한다!
참조를 목적으로만 존재하는 값은 final 선언이 된 클래스 변수에 담는다!
(이는 참조를 위한 값이니 public으로 선언해도 괜찮은 값이다.)
클래스 내 정의된 메소드에 static 선언을 하면 '클래스 메소드'가 된다.
인스턴스 생성을 목적으로 설계되지 않는다!
불가능!
클래스 메소드는 인스턴스에 속하지 않으니 인스턴스 변수에 접근이 불가능 하고,
클래스 메소드는 인스턴스 메소드의 호출도 불가능하다.
클래스 변수와 마찬가지로 가상머신이 클래스의 정보를 읽어 들일 때 실행된다.
static 초기화 블록을 사용하면 클래스 변수 선언과 동시에 초기화 할 수 있다.
class DateOfExecution {
static String date;
static {
LocalDate nDate = LocalDate.now();
date = nDate.toString();
}
public static void main(String[] args) {
System.put.println(date);
}
}
호출할 메소드를 찾을 때 다음 두 가지 정보를 참조하여 메소드를 찾게 된다.
매개변수의 선언이 다르면 동일한 이름의 메소드 정의를 허용한다.
생성자 또한 오버로딩이 가능하다.
매개변수와 인스턴스 변수명이 같을 때 this
키워드를 이용하면 인스턴스 변수를 가리킬 수 있다.
String str1 = new String("My String")';
String str2 = "Your String";
str1은 같은 문자를 ==
연산을 할 경우 false(다른 인스턴스 참조)가 나오고,
str2는 true(동일 인스턴스 참조)가 나온다.
valueOff
메소드를 사용하자!
double e = 2.63412;
String se = String.valueOf(e);
자바에서는 배열도 인스턴스이다.
int[] ref = new int[5]; // 길이가 5인 int형 1차원 배열의 생성문
int[] ref
는 참조변수의 선언이고, new int[5]
는 배열 인스턴스 생성이다.
1차원 배열의 참조변수는 배열의 길이에 상관없이 참조가 가능하다.
int[] arr = new int[] {1, 2, 3};
배열의 길이를 생략한다.
for(요소 : 배열) {
반복할 문장들
}
int[][] arr = new int[3][4];
세로 길이가 3, 가로 길이가 4인 int형 2차원 배열
생성과 동시에 초기화가 가능하다.
int[][] arr = new int[3][3] {
{11, 22, 33},
{44, 55, 66},
{77, 88, 99}
};
상속은 코드의 재활용을 위한 문법이다? ❌
상속은 연관된 일련의 클래스들에 대해 공통적인 규약을 정의할 수 있다.
기존에 정의된 클래스에 메소드와 변수를 추가하여 새로운 클래스를 정의하는 것이 상속이다.
extends
는 상속을 의미하는 키워드이다.
class Man {
String name;
public Man(String name) {
this.name = name;
}
public void tesllYourName() {
System.out.println("My name is " + name);
}
}
class BusinessMan extends Man {
String company;
String position;
public BusinessMan(String company, String position) {
this.company = company;
this.position = position;
}
public void tellYourInfo() {
System.out.println("My company is " + company);
System.out.println("My position is " + position);
tellYourName();
}
}
String name;
은 상속으로 인해 BusinessMan
클래스에 존재하지만 초기화 되지 않는 문제 발생.
따라서 이 변수도 초기화해주어야 한다.
class BusinessMan extends Man {
String company;
String position;
public BusinessMan(String name, String company, String position) {
this.name = name; // 상위 클래스 멤버 초기화
this.company = company;
this.position = position;
}
public void tellYourInfo() {
System.out.println("My company is " + company);
System.out.println("My position is " + position);
tellYourName();
}
}
근데 적절한 생성자 정의의 형태는 아니다.
하위 클래스의 인스턴스 생성 시 상위 클래스, 하위 클래스의 생성자 모두 호출된다.
하위 클래스의 인스턴스 생성 시 상위 클래스의 생성자가 먼저 호출된다.
하위 클래스의 생성자에서 상위 클래스의 생성자를 명시적으로 호출하지 않으면 인자를 받지 않는 생성자가 자동으로 호출된다.
상위 클래스의 생성자를 명시적으로 호출하려면 super
키워드를 사용해라!
상위 클래스의 생성자는 하위 클래스 생성자의 맨 첫 문장으로 등장해야 한다.
자바는 상속 관계에 있을지라도, 상위 클래스의 멤버는 상위 클래스의 생성자를 통해서 초기화하도록 유도되고 있다.
자바는 프로그램이 과도하게 복잡해지는 것을 막기 위해 단일 상속만을 지원한다.
즉 하나의 클래스가 상속할 수 있는 클래스의 수가 최대 하나라는 것이다.
class A {...}
class Z extends A {...}
하지만 상속의 깊이를 더하는 것은 얼마든지 가능하다.
class A {...}
class M extends A {...}
class Z extends M {...}
하지만 다중 상속으로 설계하는 일은 극히 드물다.
🤔 상위 클래스에 위치한 클래스 변수와 메소드에 하위 클래스에서 어떻게 접근하는가?
선언된 접근 수준 지시자가 접근을 허용한다면 변수의 이름만으로도 접근이 가능하다!
하위 클래스는 상위 클래스의 모든 특성을 지니며 자신만의 추가적인 특성을 더하게 된다.
'IS-A'는 '~은 ~이다.'로 표현되는 관계이다.
ex) 노트북은 컴퓨터이다.
메소드 오버라이딩은 상위 클래스에 정의된 메소드를 하위 클래스에서 다시 정의하는 것을 뜻한다.
다음과 같은 상속 관계를 가질 때
class SmartPhone extends MobilePhone {...}
다음과 같은 문장들을 구성할 수 있다.
SmartPhone phone = new SmartPhone("010-555-777", "Nougat");
// or
MobilePhone phone = new SmartPhone("010-555-777", "Nougat");
모바일폰을 상속하는 스마트폰도 일종의 모바일 폰이다.
MobilePhone을 상속하는 SmartPhone 인스턴스는 MobilePhone 인스턴스 이기도 하다.
따라서 MobilePhone형 참조변수는 SmartPhone 인스턴스를 참조할 수 있다.
이렇듯 상위 클래스의 참조변수는 하위 클래스의 인스턴스를 참조할 수 있다.
즉 MobilePhone phone
은 MobilePhone
의 인스턴스인 동시에 SmartPhone
의 인스턴스가 된다.
그러나 이럴 경우 phone
는 SmartPhone
의 메소드 호출은 불가능하다.
Cake
가 상위 클래스이고 CheeseCake
가 하위 클래스일 때,
CheeseCake ca1 = new CheeseCake();
Cake ca2 = ca1;
위의 코드는 가능하지만
Cake ca3 = new CheeseCake();
CheeseCake ca4 = ca3;
위의 코드는 불가능하다.
🤔 왜?
자바는 참조변수의 타입 정보를 기준으로 대입의 가능성을 판단하기 때문이다!
형 변환을 하면 가능하다!
Cake ca3 = new CheeseCake();
CheeseCake ca4 = (CheeseCake)ca3;
메소드의 이름, 메소드의 반환형, 메소드의 매개변수 선언이 같아야
오버라이딩이 가능하다.
여기서 말하는 오버라이딩은 무효화 됨을 의미한다.
class Cake {
public void yammy() {}
}
class CheeseCake extends Cake {
public void yammy() {} // Cake 메소드 오버라이딩
}
class ChocoCheeseCake extends CheeseCake {
public void yammy() {} // CheeseCake 메소드 오버라이딩
}
Cake c1 = new ChocoCheeseCake();
CheeseCake c2 = new ChocoCheeseCake();
ChocoCheeseCake c3 = new ChocoCheeseCake();
c1.yammy(); // ChocoCheeseCake의 메소드 호출
c2.yammy(); // ChocoCheeseCake의 메소드 호출
c3.yammy(); // ChocoCheeseCake의 메소드 호출
ChocoCheeseCake 인스턴스를 대상으로 오버라이딩 된 메소드를 호출하면 가장 마지막으로 오버라이딩한 메소드가 호출된다.
상위 클래스에 정의된 오버라이딩 된 메소드를 호출하려면 super
키워드를 사용한다.
변수는 오버라이딩 되지 않는다.
참조 변수가 참조하는 인스턴스의 클래스나 참조하는 인스턴스가 상속하는 클래스를 묻는 연산자이다.
해당 클래스를 다른 클래스가 상속하는 것을 원치 않는다면 final
선언을 추가한다.