본문 바로가기
Java & Spring

자바 정리 (2)

by WhoamixZerOne 2024. 2. 6.

이 글은 자바의 정석 3판을 정리한 내용입니다.
자바의 정석 3판

 

✔ 객체지향 프로그래밍 OOP(Object Oriented Programming)

  • 객체 : 모든 인스턴스를 대표하는 일반적인 용어
  • 인스턴스 : 특정 클래스로부터 생성된 객체(new 키워드)

ex) 클래스(TV설계도), 객체(TV제품)

✔ 클래스의 정의

  • 클래스 : 데이터와 함수의 결합
  • 서로 관련 있는 속성(데이터)과 기능(메서드)을 묶어 놓은 것

1. 선언 위치에 따른 변수의 종류

class Variables {  // → 클래스 영역 시작
    int iv; // 인스턴스 변수
    static int cv;  // 클래스 변수(static 변수)
    
    void method() {  // → 메서드 영역 시작
        int lv = 0;  // 로컬 변수(지역 변수)
    }  // → 메서드 영역 끝
}  // → 클래스 영역 끝
  • 멤버 변수(필드) : 클래스 영역에 선언된 변수
  • 인스턴스 변수 : 멤버 변수 중 static이 붙지 않은 것을 인스턴스 변수
  • 클래스 변수(static 변수) : 멤버 변수 중 static이 붙은 것을 클래스 변수
  • 지역 변수(로컬 변수) : 멤버 변수를 제외한 나머지 변수들은 모두 지역 변수
변수의 종류 선언 위치 생성 시기
클래스 변수
(class variable)
클래스 영역 클래스가 메모리에 올라갈 때
인스턴스 변수
(instance variable)
인스턴스가 생성되었을 때
지역 변수
(local variable)
클래스 영역 이외의 영역
(메서드, 생성자, 초기화 블록 내부)
변수 선언문이 수행되었을 때

 

2. 클래스 변수와 인스턴스 변수

ex) 클래스 변수와 인스턴스 변수 차이(카드 게임)

  • Card 클래스
  • 속성 : 무늬, 숫자, 폭, 높이
  • 기능 : ···
class Card {
	// 클래스 변수
    static int width = 100;  // 폭
    static int height = 250; // 높이
    
    // 인스턴스 변수
    String kind;  // 무늬
    int number;   // 숫자
}

무늬, 숫자의 경우 여러 카드 중 개별적으로 값을 가진다. 즉, 객체마다 다르게 유지되어야 하므로 인스턴스 변수로 사용한다.

폭, 높이의 경우 여러 카드가 동일한 폭, 높이의 값을 가져야 한다. 즉, 객체가 공통으로 사용해야 하므로 클래스 변수로 사용한다.

※ 클래스 변수 사용
'참조변수.변수명'으로 사용은 가능하지만 권장하지 않는다.
'클래스명.변수명'으로 사용하길 권장한다.(참조변수.변수명으로 사용 시 클래스 변수와 인스턴스 변수가 혼동)

 

  • Card 클래스 메모리 구조
class Card {
	static int width = 100;
    static int height = 250;
    
    String kind;
    int number;
}

public class CardMain {
	public static void main(String[] args) {
    	System.out.println(Card.width);   // 100
        System.out.println(Card.height);  // 250
        
        Card c1 = new Card();
        c1.kind = "Heart";
        c1.number = 7;
        
        Card c2 = new Card();
        c2.kind = "Spade";
        c2.number = 4;
    }
}

 

3. 클래스(static) 메서드와 인스턴스 메서드

인스턴스 메서드

  • 인스턴스 생성 후, '참조변수.메서드이름()'으로 호출
  • 인스턴스 멤버(iv, im)와 관련된 작업을 하는 메서드
  • 메서드 내에서 인스턴스 멤버(iv, im) 사용 가능
  • 메서드 내에서 클래스 멤버(cv, cm) 사용 가능

클래스 메서드(static 메서드)

  • 객체 생성 없이, '클래스이름.메서드이름()'으로 호출
  • 인스턴스 멤버(iv, im)와 관련 없는 작업을 하는 메서드
  • 메서드 내에서 인스턴스 멤버(iv, im) 사용 불가

※ 클래스(static) 메서드에서 인스턴스 멤버 사용하지 못하는 이유?

인스턴스 멤버는 인스턴스를 생성하고 난 후 사용이 가능한데, 클래스(static) 메서드는 인스턴스를 생성하지 않고도 사용이 가능하기 때문에. 즉, 클래스(static) 메서드를 사용할 때 인스턴스를 생성하지 않았을 수도 있기 때문에 보장할 수 없어서 사용이 불가능하다.

✔ 오버로딩(Overloading)

한 클래스 안에 같은 이름의 메서드를 여러 개 정의하는 것.

 

오버로딩 사용 조건

  • 메서드 이름이 같아야 한다
  • 매개변수(파라미터)의 개수 또는 타입이 달라야 한다
  • 반환 타입은 영향이 없다

ex) 오버로딩 Error(메서드 중복 정의 에러)

int add(int a, int b) { return a + b; }
int add(int x, int y) { return x + y; }

ex) 오버로딩 OK

// 메서드 이름 같고, 매개변수(파라미터)의 타입의 순서가 다르기 때문에 성립
long add(int a, long b) { return a + b; }
long add(long a, int b) { return a + b; }

add(3, 3L);  // OK
add(3L, 3);  // OK

// int는 long으로 자동 형변환 가능하기 때문에 둘 중에 어느 것을 호출할 지 모호하다
add(3, 3);   // Error

✔ 생성자(Constructor)

인스턴스가 생성될 때마다 호출되는 "인스턴스 초기화 메서드"

  • 생성자는 클래스 이름과 동일해야 한다
  • 리턴 값이 없다(void 안 붙임)
  • 모든 클래스는 반드시 생성자를 가져야 한다

1. 기본 생성자(Default Constructor)

  • 매개변수(파라미터)가 없는 생성자를 기본 생성자라 한다
  • 생성자가 하나도 없을 때만, 컴파일러가 자동 추가 해준다
  • 매개변수(파라미터)가 있는 생성자를 정의했을 땐, 컴파일러가 추가해주지 않는다

2. 생성자 this()

생성자에서 다른 생성자를 호출할 때 사용

 

this() 사용 조건

  • 다른 생성자 호출 시 첫 줄에서만 사용 가능

3. 참조 변수 this

  • 인스턴스 자신을 가리키는 참조 변수(인스턴스의 주소가 저장되어 있다)
  • 인스턴스 메서드와 생성자에서 사용 가능
  • 지역(로컬) 변수와 인스턴스 변수를 구별할 때 사용

4. 변수의 초기화

멤버 변수는 초기화를 하지 않아도 자동적으로 변수의 자료형에 맞게 기본값으로 초기화가 되지만, 지역(로컬) 변수는 사용하기 전에 반드시 초기화해야 한다.

멤버 변수(클래스 변수, 인스턴스 변수)와 배열의 초기화는 선택적이지만, 지역(로컬) 변수의 초기화는 필수적이다.

자료형 기본값
boolean false
char '\u0000'
byte, short, int 0
long 0L
float 0.0f
double 0.0d or 0.0
참조형 변수 null

5. 멤버 변수(iv, cv)의 초기화

멤버 변수의 초기화 방법

  1. 명시적 초기화(Explicit Initialization) : 대입 연산자(=)로 초기화
  2. 생성자(Constructor)
  3. 초기화 블록(Initialization Block)
    • 인스턴스 초기화 블록 : 인스턴스 변수를 초기화하는 데 사용 "{}"
    • 클래스 초기화 블록 : 클래스 변수를 초기화하는 데 사용 "static {}"

6. 멤버 변수(iv, cv) 초기화 순서

  1. 자동 초기화(기본값)
  2. 간단 초기화(대입 연산자)
  3. 복잡 초기화(초기화 블록, 생성자)
  • 클래스 변수 초기화 시점 : 클래스가 처음 로딩될 때 단 한번 초기화
  • 인스턴스 변수 초기화 시점 : 인스턴스가 생성될 때마다 각 인스턴스별로 초기화
  • 클래스 변수 초기화 순서 : 기본값 → 명시적 초기화 → 클래스 초기화 블록
  • 인스턴스 변수 초기화 순서 : 기본값 → 명시적 초기화 → 인스턴스 초기화 블록 → 생성자
class InitTest {
	static int cv = 1;  // 명시적 초기화(간단 초기화)
    int iv = 1;         // 명시적 초기화(간단 초기화)
    
    static {
    	cv = 2;  // 클래스 초기화 블럭(복잡한 초기화)
    }
    {
    	iv = 2;  // 인스턴스 초기화 블럭(복잡한 초기화)
    }
    
    InitTest() {
    	iv = 3;  // 생성자(복잡한 초기화)
    }
}

 

클래스 초기화 인스턴스 초기화
기본값 명시적 초기화 클래스 초기화 블록 기본값 명시적 초기화 인스턴스 초기화 블록 생성자
cv : 0 cv : 1 cv : 2 cv : 2
iv : 0
cv : 2
iv : 1
cv : 2
iv : 2
cv : 2
iv : 3
  • 클래스 초기화 : 클래스가 처음 메모리에 로딩될 때 차례대로 수행
  • 인스턴스 초기화 : 인스턴스를 생성할 때 차례대로 수행

✔ 상속(Inheritance)

기존의 클래스로 새로운 클래스를 작성하는 것.(코드의 재사용)

class 자식클래스 extends 부모클래스 {}

class Parent {
	int age;
}
class Child extends Parent {
}
  • 자식은 부모의 모든 멤버를 상속받는다(생성자, 초기화 블록 제외)
  • 자식의 멤버 개수는 부모보다 적을 수 없다(같거나 많다)
  • 자식의 변경은 부모에 영향을 미치지 않는다(부모의 변경은 자식에 영향을 미친다)

1. 클래스의 관계

상속, 포함 관계가 있다.

 

포함(Composite)?
클래스의 멤버로 참조변수를 선언하는 것.
작은 단위의 클래스를 만들고, 이들을 조합해서 클래스를 만든다.(멤버 변수가 줄어든다)
class Point {
	int x;
	int y;
}
// Circle이 Point를 포함하고 있다
class Circle {
	Point p = new Point(); // 포함관계
	int r;
}

 

2. 클래스 간의 관계 결정하기

  • 상속관계 : '~은 ~이다(is-a)'
  • 포함관계 : '~은 ~을 가지고 있다(has-a)'

ex) Circle, Point

원은 점이다. Circle is a Point

원은 점을 가지고 있다. Circle has a Point

 

2번째 문장이 더 자연스럽기 때문에 포함관계로 한다.

하지만 관계 결정하는 게 위와 같이 결정하는 게 아니라 상황마다 다르기 때문에 참고 정도로만 생각해야 한다.

 

3. 모든 클래스의 조상

부모가 없는 클래스는 자동적으로 Object 클래스를 상속받게 된다.(컴파일러가 자동으로 추가)

✔ 오버라이딩(Overriding)

  • 상속받은 부모의 메서드를 자신에 맞게 변경하는 것
  • 선언부 변경 불가, 구현부만 변경 가능

1. 오버라이딩 조건

  • 선언부가 부모 클래스의 메서드와 일치해야 한다
  • 접근제어자를 부모 클래스의 메서드보다 좁은 범위로 변경할 수 없다
    • public > protected > (default) > private
  • 예외는 부모 클래스의 메서드보다 많이 선언할 수 없다(같거나 적어야 한다)
    • 예외의 개수도 해당되지만, 'Exception'처럼 모든 예외의 조상을 하면 안 된다(상속관계까지 고려해야 한다)

2. 참조 변수 super

  • 부모의 멤버를 자신의 멤버와 구별할 때 사용
  • 객체 자신을 가리키는 참조변수, 인스턴스 메서드, 생성자 내에만 존재(static 메서드 내에서 사용 불가)

3. 부모의 생성자 super()

  • 부모의 생성자를 호출할 때 사용
  • 부모의 멤버는 부모의 생성자를 호출해서 초기화

super() 사용 조건

  • 부모 생성자 호출 시 첫 줄에서만 사용 가능(생성자 첫 줄에 반드시 부모 생성자를 호출해야 한다)
  • 컴파일러가 생성자의 첫 줄에 'super();'를 추가한다

✔ 제어자(Modifier)

클래스와 클래스의 멤버(멤버 변수, 메서드)에 부가적인 의미 부여

  • 접근 제어자 : public, protected, (default), private
  • 그 외 : static, final, abstract, native, transient, synchronized, volatile, strictfp

1. 접근 제어자(Access Modifier)

  • private : 같은 클래스 내에서만 접근이 가능
  • (default) : 같은 패키지 내에서만 접근이 가능
  • protected : 같은 패키지 내에서, 다른 패캐지의 자식 클래스에서 접근이 가능
  • public : 접근 제한이 전혀 없다
제어자 같은 클래스 같은 패키지 자손 클래스 전 체
public O O O O
protected O O O X
(default) O O X X
private O X X X

 

※ 접근 제한 범위 순서(뒤로 갈수록 좁아진다)

접근 제한 없음     같은 패키지 + 자식(다른 패키지)     같은 패키지      같은 클래스

public             >                protected                        >    (default)    >     private

 

2. 캡슐화와 접근 제어자

접근 제어자를 사용하는 이유? (캡슐화)

  • 외부로부터 데이터를 보호하기 위해
  • 외부에는 불필요한 것을 굳이 노출시킬 필요가 없다(내부적으로만 사용되는 부분을 감추기 위해서)

✔ 다형성(Polymorphism)

  • 여러 가지 형태를 가질 수 있는 능력
  • 부모 타입 참조변수로 자식 타입 객체를 다루는 것
  • 자식 타입의 참조변수는 부모 타입의 객체를 다룰 수 없다
class Tv {}
class SmartTv extends Tv {}
Tv t = new Tv();              // 타입 일치로 생성 가능
SmartTv stv = new SmartTv();  // 타입 일치로 생성 가능
Tv t1 = new SmartTv();        // 타입 불일치여도 생성 가능(다형성)

 

객체와 참조변수의 타입이 일치할 때와 일치하지 않을 때의 차이?

둘 다 같은 타입의 인스턴스지만 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다.

SmartTv s = new SmartTv();  // 참조변수와 인스턴스의 타입이 일치
Tv t = new SmartTv();       // 부모 타입 참조변수로 자식 인스턴스 타입 참조

 

1. 참조변수의 형변환

  • 사용할 수 있는 멤버의 개수를 조절하는 것
  • 부모, 자식 관계의 참조변수는 서로 형변환 가능
  • 자식에서 부모로 형변환의 경우 생략 가능(업 캐스팅)
  • 부모에서 자식으로 형변환의 경우 생략 불가능(다운 캐스팅)
  • 형변환은 실제 인스턴스가 중요
    • 부모 타입으로 자식 인스턴스는 생성할 수 있지만, 자식 타입으로 부모 인스턴스는 생성 불가
  • 형변환 전에 instanceof 연산자로 가능 여부를 확인

2. instanceof 연산자

Java 16 버전부터 변수 선언 가능

public static void main(String[] args) {
    Parent parent = new Child();
    method(parent);
}

private static void method(Parent parent) {
    if (parent instanceof Child) {
        Child child = (Child) parent;
        child.method();
    }
}

Java 16 버전 이전에는 위와 같이 다운 캐스팅을 해서 사용했다면 Java 16 버전 이후에는 변수 값을 'instanceof 클래스명' 옆에 적어서 사용 가능(자동 다운 캐스팅)

public static void main(String[] args) {
	Parent parent = new Child();
	method(parent);
}

private static void method(Parent parent) {
    if (parent instanceof Child child) {        
        child.method();
    }
}

 

3. 추상 클래스(abstract class 추상의, 미완성의)

제어자 대상 의 미
abstract 클래스 클래스 내에 추상 메서드가 선언되어 있음을 의미한다.(인스턴스 생성 불가)
추상 메서드가 없어도 클래스에 적용할 수 있다.
메서드 선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알린다.
  • 추상 클래스는 인스턴스 생성이 불가하다(미완성 클래스, 미완성 설계도)
  • 추상 메서드(미완성 메서드)를 갖고 있는 클래스
  • 추상 클래스를 상속받아서 완전한 클래스를 만든 후에 인스턴스 생성 가능
  • 일반 클래스와 동일하지만, 추상 메서드를 가지고 있을 뿐이다
  • 추상 메서드를 가지고 있지 않아도 추상 클래스를 적용할 수 있다

4. 인터페이스(interface)

  • 추상 메서드의 집합
  • 구현된 것이 전혀 없는 설계도. 껍데기(모든 멤버가 public)
  • 변수는 선언 불가. 상수는 선언 가능(public, static, final 생략 가능)
  • public, abstract 생략 가능
  • 인터페이스의 부모는 인터페이스만 가능(Object가 최고 조상 아님)
  • 자바는 단일 상속만을 허용하지만, 인터페이스로 다중 상속과 같이 구현 가능(추상 메서드는 충돌해도 문제없음, 선언부만 존재하기 때문에)

인터페이스의 멤버 제약사항

  • 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다
  • 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다
    • static 메서드와 default 메서드는 예외(JDK 1.8부터 추가되었다)
interface PlayingCard {
  // public, static, final 생략 가능
  public static final int SPADE = 4;
  final int DIAMOND = 3;  // public static final int DIAMOND = 3;
  static int HEART = 2;   // public static final int HEART = 2;
  int CLOVER = 1;         // public static final int CLOVER = 1;
  
  // public, abstract 생략 가능
  public abstract String getCardNumber();
  String getCardKind();   // public abstract String getCardKind();
}

 

5. 인터페이스(interface)의 장점

  • 두 대상(객체) 간의 '연결, 대화, 소통'을 돕는 '중간 역할'을 한다
  • 선언(설계)과 구현을 분리시킬 수 있게 한다
  • 변경에 유리한 유연한 설계가 가능
  • 서로 관계없는 클래스들을 관계를 맺어줄 수 있다

6. default 메서드와 static 메서드

interface MyInterface {
  // static 메서드(public 생략 가능)
  public static void staticMethod1() { }
  static void staticMethod2() { }
  
  // default 메서드(public 생략 가능)
  public default void defaultMethod1() { }
  default void defaultMethod2() { }
}

 

 

 

🔗 Reference

'Java & Spring' 카테고리의 다른 글

자바 정리 (4)  (4) 2024.02.28
자바 정리 (3)  (0) 2024.02.19
자바 정리 (1)  (0) 2024.01.22
Java 버전 관리 도구  (0) 2023.11.18
[Spring] 인터셉터(Interceptor) 적용  (0) 2022.04.17

댓글