앞단원이 객체지향을 이해하기 위한 밑바탕이었다면, 7단원이야 말로 객체지향 프로그래밍의 특징을 깊이 있게 이해하는 단원인 거 같다(두근)
= 기존의 클래스를 재사용하여 새로운 클래스를 작성
적은 양의 코드로 새로운 클래스 작성 가능
코드를 공통으로 관리할 수 있어 추가 및 변경이 용이
=> 재사용성 높이고, 코드의 중복 제거 => 유지보수
Good
(앞선 단원에서의 가졌던 궁금증의 이유가 바로 풀려버리는,,)
extends
키워드와 함께 새로 만들 클래스와 기존의 클래스를 작성해주면 됨.
ex. class Child extends Parent {}
여기서는 Child가 새로운 클래스, Parent가 상속받고자 하는 클래스!
조상 클래스: 부모 클래스, 상위 클래스
자손 클래스: 자식 클래스, 하위 클래스
상속 이외에 클래스를 재사용 하는 방법이 있다. 이것이 바로 포함
관계를 맺어주는 것!
포함
= 한 클래스의 멤버 변수로 다른 클래스를 선언하는 것
class Car {
Engine e = new Engine();
Door[] d = new Door[4];
}
우리는 앞서 설명한 상속
과 포함
중 어떤 관계를 맺어줄 것인지 결정해야 한다.
상속관계 = ~는 ~이다 (is-a)
포함관계 = ~는 ~을 가지고 있다 (has-a)
우리가 관계를 고민하는 두 클래스에 대해 윗 문장에 대입하여 생각해보면 된다.
class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
Point() {this(0, 0);}
}
class Circle {
Point center; //Point 클래스를 `포함`하여 Circle 클래스 생성
int r;
Circle() {
this(new Point(0, 0), 100);
}
Circle(Point center, int r) {
this.center = center;
this.r = r;
}
}
다중 상속
시 서로 다른 클래스로부터 상속받은 멤버 간의 이름이 같을 때 구별할 수 없기 때문에, 자바에서는 단일 상속
을 원칙으로 한다.
물론 다중 상속의 장점을 가져갈 수 없으므로 불편한 점도 있겠지만, 클래스 간의 관계가 보다 명확해지고 코드를 더욱 신뢰할 수 있게 만들어준다는 큰 장점을 가지게 된다!
//상속과 포함 관계가 모두 적용된 클래스 예시
class Tv {
boolean power;
int channel;
void power() {power != power;}
void channelUp() {++channel;}
void channelDown() {--channel;}
}
class VCR {
boolean power;
int counter = 0;
void power() {power != power;}
void play() {}
void stop() {}
void rew() {}
void ff() {}
}
class TVCR extends Tv { //extends = 상속
VCR vcr = new VCR(); //포함
int counter = vcr.counter;
void play() {vcr.play();}
void stop() {vcr.stop();}
void rew() {vcr.rew();}
void ff() {vcr.ff();}
}
Object
클래스는 모든 클래스 상속계층도의 제일 위에 위치하는 조상클래스
이다. 다른 클래스로부터 상속 받지 않는 클래스가 있다면 자동적으로 Object 클래스를 상속받는다.
(ex. 우리가 String 문자열을 비교할 때 equals를 사용했던 것이나, 어떤 자료형을 String으로 바꿀 때 사용했던 toString 모두 Object 클래스 안에 있었기 때문에 바로 사용이 가능했던 것..!)
= 조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것
= 덮어씌우기
class Point {
int x, y;
String getLoc() {
return "x :"+x+", y :"+y;
}
}
class Point3D extends Point {
int z;
String getLoc() {
return "x :"+x+", y :"+y+", z:"+z;
}
}
Exception
을 예외로 던지면 모든 예외의 최고 조상이기 때문에 가장 많은 개수의 예외를 던지도록 선언한 것이므로 주의!!!오버라이딩 = 덮어쓰기 (기존에 있던 메서드 내용 변경)
오버로딩 = 생성 (기존에 없던 새로운 메서드 정의)
super
는 자손 클래스에서 조상 클래스로부터 상속받은 멤버 참조
에 사용되는 참조 변수
이다.
this
처럼 super도 상속받은 멤버와 본인 클래스의 멤버 이름이 같을 때 구별하기 위해 super를 써주면 된다.super
도 this
와 마찬가지로 인스턴스 메서드에서만 사용할 수 있다.class Ex2 {
public static void main(String[] args) {
Child c = new Child();
c.method();
}
}
class Parent {int x = 10;}
class Child extends Parent {
int x = 20;
void method() {
System.out.println("x="+x);
System.out.println("this.x="+this.x);
System.out.println("super.x="+super.x);
}
}
// 출력결과
//20 줄바꿈 20 줄바꿈 10
//super는 상속받은 부모 클래스의 멤버 참조에 사용되니까!!
= 조상 클래스의 생성자
super()
를 꼭 넣어줘야함class Point {
int x = 10;
int y = 20;
Point(int x, int y) {
super(); // 이렇게 되면 최상위 조상 클래스 Object를 불러옴
this.x = x;
this.y = y;
}
}
class Point3D extends Point {
int z = 30;
Point3D() {
this(100, 200, 300);
}
Point3D(int x, int y, int z) {
super(x, y);
this.z = z;
}
}
- 클래스 - 어떤 클래스의 인스턴스를 생성할 것?
- 생성자 - 선택한 클래스의 어떤 생성자를 이용해서 인스턴스를 생성할 것?
= 클래스의 묶음 (클래스 or 인터페이스)
ex. String 클래스 -> java.lang.String
= java.lang 패키지에 속한 String 클래스
.
으로 구분되는 -> java . lang (java 패키지의 하위 패키지)# 궁금해서 ChatGPT에게 물어본 반드시 패키지에 속해야 하는 이유
1. 이름 충돌 방지: 패키지는 클래스의 이름 충돌을 방지하는 데 사용됩니다. 서로 다른 패키지에 있는 두 개의 클래스가 동일한 이름을 가질 수 있습니다. 이렇게 하면 두 클래스를 구분할 수 있습니다.
2. 클래스의 가시성 제어: 패키지는 클래스의 가시성을 제어하는 데 사용됩니다. 클래스를 패키지 내부에 두면 다른 패키지에서 접근할 수 없습니다. 이는 객체 지향 프로그래밍의 캡슐화 개념과 일치합니다.
3. 코드 관리 및 유지 보수: 클래스를 패키지에 그룹화하면 코드 관리 및 유지 보수가 쉬워집니다. 특정 기능 또는 관련된 클래스를 포함하는 패키지를 만들고 그 패키지에 대한 문서를 작성하면 코드를 사용하는 다른 개발자들이 코드를 더 쉽게 이해할 수 있습니다.
= package 패키지명
이름없는 패키지
때문!++ 실행시 java -d . 파일이름.java
로 실행한다.
-d
옵션은 소스파일에 지정된 경로를 통해 패키지의 위치를 찾아 클래스 파일을 생성한다. 만약 일치하는 디렉토리가 존재하지 않는다면 자동적으로 생성!
= 컴파일러, JVM, .. 이 클래스의 위치를 찾는데 사용하는 경로
=> 내 컴퓨터 어디에서도 Java 라이브러리? 패키지?를 사용하기 위해
= 사용하고자 하는 클래스의 패키지를 미리 명시하기 위해 사용
= 컴파일러에게 소스파일에 사용된 클래스의 패키지에 대한 정보를 제공
** 컴파일 시간을 조금 늘릴 뿐, 성능과는 아무런 관련 X !
import 패키지명.클래스명
or import 패키지명.*
선언
된다.= 클래스, 변수, or 메서드의 선언부에 사용되어 부가적인 의미 부여
접근 제어자: public, protected, default, private
그 외: static, final, abstract, native, transient, synchronized, volatile, strictfp
야무지게 설명해놓은 static과 final ㅎㅎ
-> 옛날에 차이점이 궁금해서 정리해놓은 글이다! 뉘앙스를 이해할 수 있어서 위의 글을 미리 읽고 와도 좋을 것 같당.
= 클래스의
, 공통적인
인스턴스 변수는 하나의 클래스로부터 생성되어도 다른 값을 유지하지만, 클래스 변수는 인스턴스에 관계없이 같은 값을 가짐!
(클래스 변수는 모든 인스턴스가 공유하고 있기 때문)
static이 붙었다면 인스턴스를 생성하지 않고도 사용 가능!!
-> 멤버변수, 메서드, 초기화 블럭 에서 사용
인스턴스 멤버를 사용하지 않는 메서드가 있다면 static 메서드로 바꾸면 좋음
(static 블럭은 클래스가 메모리에 로드될 때 한번만 수행되고, 주로 클래스 변수를 초기화하는데 사용됨)
변수
에 사용되면 값을 변경할 수 없는 상수, 메서드
에 사용되면 오버라이딩을 할 수 없고, 클래스
에 사용되면 자신을 확장하는 자손 클래스를 정의하지 못한다.= 각 인스턴스마다 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);
}
}
= 메서드의 선언부만 작성하고 실제 수행 내용은 구현하지 않은 추상메서드
-> 클래스, 메서드에서 사용!
abstract class AbstractTest {
abstract void move();
}
= 해당 멤버 or 클래스를 외부에서 접근하지 못하도록 제한
private:
같은 클래스 내
에서만 사용
default:같은 패키지 내
에서만 사용
protected:같은 패키지 내, 다른 패키지의 자손클래스
에서 접근
public: 접근 제한없다
<아래로 갈수록 접근 범위가 넓다>
= 데이터 감추기 = (객체지향) 캡슐화
public class Time {
//아래의 멤버 변수들의 접근 제어자를 private로 하여 외부에서
//접근하지 못하도록!
private int hour;
private int minute;
private int second;
public int getHour() {return hour;}
public void setHour(int hour) {
if(hour < 0 || hour > 23) return;
this.hour = hour;
}
public int getMinute() {return minute;}
public void setMinute(int minute) {
if(minute < 0 || minute > 59) return;
this.minute = minute;
}
public int getSecond() {return second;}
public void setSecond(int second) {
if(second < 0 || second > 59) return;
this.second = second;
}
}
++ 위의 예시에서 set메서드는 원하는 조건에 맞는 값을 때만 멤버변수에 값을 대입하도록 하였다.
++ 만약 상속을 고려한다면 private가 아닌 protected로 바꿔주는 것이 적절하다. private라면 자손 클래스에서도 접근이 불가능하기 때문이다!!
= 생성자에 접근 제어자를 사용하여 인스턴스의 생성을 제한
한다.
class Singleton {
private Singleton() {//..}
//클래스 내부에서'만' 인스턴스 생성이 가능
//(외부에서 생성자에 접근할 수가 없음
}
class Singleton {
private static Singleton s = new Singleton();
private Singleton() {}
//여기서 사용하기 위해 위에서 미리 static으로 인스턴스 생성
public static Singleton getInstance() {return s;}
}
그래서 실제로 사용할 때는
class SingletonTest {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
}
}
//생성자가 private라면 다른 클래스의 조상도 될 수 없기 때문에
//클래스 앞에 final을 추가하여 상속할 수 없음을 알리면 좋음
ex.
public final class Math {
private Math() {//...}
}
자바의 정석, 2nd Edition.