Don't Panic
in Java ☕ 28분 읽기

[Java]Java 기본 복습 part1

목차

환경 설치

JDK/JVM 환경 설치 1

JDK/JVM 환경 설치 2

자바의 경우 클래스 파일을 바로 실행하지 못해서 별도로 실행해주는 엔진이 필요한데, 그게 JVM이다. 클래스 파일을 운영체제 플랫폼에 맞게 컴파일해서 실행한다(내부적으로 투 페이스 컴파일이 일어난다). 실제 실행도 exe가 아닌 내부의 JVM이 담당한다.

운영체제에 맞는 JDK를 다운받아 설치하면, 그 위에 JVM이라는 가상 머신이 우리 눈에 보이진 않지만 탑재된다.

JVM 동작 과정:

  • JVM에게 시스템 콜을 한다.
  • JVM이 호출되면, 실행하라고 클래스 파일을 찾아서 1차 컴파일된 코드를 읽는다.
  • 프로그램이 윈도우인지 리눅스인지 맥인지 어디서 실행할지 모른다. 실행 위치와 상관없이 OS에 독립적으로 구성하려면, 특정 운영체제에 컴파일하면 안 된다.
  • JVM이 있으면 어떤 운영체제에서도 상관없이 구동된다.

자바는 어떤 운영체제에서도 운영 가능하다. 다만 가상 머신으로 실행되므로 좀 느릴 수 있다. 또한 누군가 자바로 된 프로그램을 주면, JVM으로 실행하기 위해 JRE라는 실행 환경을 설치해야 하는 불편함도 있다.


객체지향의 기초

객체지향 프로그래밍 개념

객체를 만들어서 프로그래밍하는 것을 객체지향 프로그래밍이라 한다.

  • 변수를 선언하면, 이를 관리하기 위해 내부에서 테이블이 만들어지고 컴파일러가 변수를 보고 찾아간다(포인터 개념과 동일).
  • 변수에 데이터를 집어넣는 것을 할당, 대입이라고 한다.

객체를 저장하는 변수

b에다가는 책 객체를 담는다. 객체가 메모리에 만들어지고, 객체의 본질을 가리키는 것이 객체 생성이다.

VO와 DTO:

  • 용어상 class로 Book을 설계하면 하나의 덩어리인데, 이 덩어리 구조를 객체지향에서 VO(value object)라 한다. 멤버 5개로 이뤄져 있지만 객체적으로는 1개이기 때문에 VO라 부른다.
  • 또 하나는 DTO가 있는데, 데이터가 이동하는 객체다. Book이 자료형을 가지고 있고, 이 데이터를 저장하고 출력하기 위해 바구니가 필요한데, 데이터를 담아서 다른 객체로 이동시켜 받아 저장하는 것을 DTO(transfer object)라 한다.
  • VO나 DTO는 모두 데이터 이동을 하기 위해 만들어진 것이다.

이렇게 기본 자료형과 사용자 자료형이 나뉜다.

프로그램의 3대 요소 V, D, A

프로그램의 3대 요소는 V, D, A이다.

  • variable (변수)
  • datatype (자료형)
  • assign (할당)

멤버 변수와 인스턴스

상태 정보(속성, 멤버 변수)로 만들면 클래스 안에 포함되어 멤버 변수라는 말을 쓴다. 이 설계도가 메모리에 만들어져서 객체를 생성하게 된다.

멤버 변수와 객체 생성

이 실체를 Instance라고 한다. 앞으로 자바로 ERP를 만들려면 이런 객체 데이터가 필요할 것이다.

배열

배열의 인덱스

각 배열은 인덱스(색인)로 구분한다.

배열을 쓰는 이유는? 맨 앞 배열의 번지만 이동하면 나머지 전부 이동하는 것과 동일하기 때문이다. 즉, 여러 개의 데이터를 모아서 하나로 쓰기 위해 사용한다.

변수를 개별적으로 만들면:

  1. 데이터 처리가 어렵고
  2. 데이터 이동이 어렵고
  3. 데이터를 한 개만 저장 가능하다.

반면 배열은 다음 장점이 있다:

  • 많은 수의 변수를 만들기 용이하다.
  • 기억 공간 접근이 쉽다(반복문 사용 가능).
  • 데이터 이동이 쉽다.
  • 데이터를 하나의 형태로 담아서 이동할 수 있다.

2차원 배열 구조

2차원 배열도 마찬가지다. 2차원 배열은 보기에는 정사각형 모양이지만, 실제 컴퓨터 내부에서는 일차원의 선으로 쭉 나열된 것이며 우측 하단과 같이 구성된다. (1차원이 여러 개 모인 것이 2차원 배열이다.)

변수와 메서드

  • 변수는 데이터를 한 개만 저장 가능하다(저장만 가능).
  • 메서드는 메서드 이름이 변수 역할을 한다.

변수와 메서드의 역할

메서드의 매개변수 전달

  • 호출이 되려면 실 인수와 매개변수(가인수)가 필요하다.
  • 실 인수와 가인수의 개수가 같아야 한다.

JVM 메모리 모델

JVM 메모리 모델 - 클래스 로딩

클래스가 동작하면 해당 클래스를 찾아야 한다.

  1. 현재 디렉토리에서 실행할 클래스를 찾는다. (성공하는 경우와 실패하는 경우로 나뉜다.)
    • 환경 변수 classpath를 설정하고 그 위치를 잡아주면 연결된 곳에서 classpath를 찾을 수 있지만, 대다수의 경우는 디렉토리에서 찾는다.
    • 찾으면 클래스 내부의 static 키워드가 있는 메서드를 메모리로 로딩한다.
  2. 메서드는 메모리에 로딩되어 있어야 호출이 가능하다.
    • static 키워드가 있는 메서드를 메모리로 로딩한다.
    • 어떤 메모리에 로딩되냐면 메서드 에어리어(Method Area)로 로딩된다.
    • Method Area는 static zone과 non-static zone으로 나뉜다.

그렇게 메인 메서드(나 다른 메서드들)가 실행되면 Stack Area로 들어가게 된다.

  • 메서드가 호출되면, main의 호출 정보가 stack으로 할당받아서 맨 밑바닥을 가리킨다.
  • 앞서 a, b가 메모리에 만들어졌지만, 이제 main의 스택 부분에서 만들어진다(메모리 부분이 스택 부분).
  • 그 부분을 call stack frame area라고 부른다.
  • 이 변수들이 스택에서, 그중에서도 자기 메서드 영역에서 만들어진다. a, b를 지역 변수라 부르는 이유도 이 때문이다.
  • static이 붙어있지 않으면 메모리에 올라가지 못한다.

JVM 메모리 모델 - 힙 영역

별도로 메모리에 로딩하는 방법도 있다. 여기선 heap 메모리 부분이 나오는데, 자바 객체지향에서 클래스 부분이 heap 메모리 부분에 들어간다.

  • heap 메모리는 객체가 생성되는 영역이라 new 연산자로 만든다.
  • 클래스는 객체 설계 시 사용하며, 객체를 메모리로 올려야 사용 가능하다.
  • main은 프로그램 시작 메서드라 맨 처음에 호출되어야 하므로, 객체 생성 없이도 static 키워드가 붙어 있어서 자동으로 메모리에 할당된다.
  • 반면 add가 static이 아니면 객체를 생성해야만 add를 메모리에 넣을 수 있다.

new는 메모리에 객체를 생성하라는 뜻이다.

  • main은 static이라 메모리에 있어서 생성하지 않고, add는 static이 아니라 new로 생성해야 한다.
  • new를 이용해서 객체를 생성하면 heap area에 객체가 만들어진다.
  • 메서드는 Method Area에 기계어 코드가 올라가는데, static zone과 non-static zone이 있다. 번지는 heap area에 잡힌다.
  • new를 하면 메모리 영역을 할당하고 기계어 코드가 올라온다.

메서드는 Method Area에 기계어 코드가 올라가서 사실은 non-static zone에 올라오고, 힙 영역은 이 Method Area 영역을 포인터(주소 참조)하고 있는 형태다.

이제 main에서 add를 어떻게 호출할까?

  • tpc라는 변수가 가리키는 곳의 add를 호출한다(tpc.add → tpc가 가리키는 곳의 add 호출).
  • 호출이 되면 non-static zone에 있는 add를 호출한다.
  • 객체를 생성하는 부분만 추가된다.
  • static이 붙어 있지 않은 메서드는 강제로 메모리에 올린다. 이때 non-static으로 올리기 위해 new로 할당하고 호출한다.
  • main은 static이라 static zone으로 바로 올라가지만, add는 non-static zone으로 올라간다. 이 add만 기계어 코드가 non-static zone에 오른다.
  • heap area에서 포인터로 non-static zone의 add를 가리키고, 이 번지를 stack area의 번지로 가리키며, add가 메서드 에어리어 쪽에 있는 add를 호출하므로 스택 메모리에 오른다.
  • heap area가 하나 더 사용된다(객체가 생성되는 메모리 영역).
public class TPC09 {
	public static void main(String[] args) {
       int a=56;
       int b=40;
       // a+b=?
       //int v=sum(a,b);
       TPC09 tpc=new TPC09(); // heap Area(힙)
       int v=tpc.sum(a, b);
       System.out.println(v); // 96
	}
	public int sum(int a, int b) {
		int v=a+b;
		return v;
	}

}

기본 자료형(PDT) vs 사용자 정의 자료형(UDDT)

기본 자료형과 사용자 정의 자료형

새로운 자료형을 만들어 쓰려면 일단 클래스로 설계를 한다(새로운 자료형). 설계되면 그 구조가 메모리(heap Area)에 만들어지는데, 이는 곧 객체가 생성된다는 의미다. 이 설계도대로 기억 공간에 실제로 만들어진다.

생성자 메서드

객체를 생성하는 메서드를 생성자 메서드라고 하며, 이로 인해 객체가 만들어진다.

  • new는 생성하라는 명령어이고, 실제 생성하는 것은 생성자 메서드다.
  • 클래스 안에 생성자 메서드가 있어야 하는데 명시하지 않은 경우, 멤버들을 생성하는 함수(기본 생성자)가 생략된 것이다. 무조건 들어가는 것이므로, 이를 기본 생성자라고 한다.
  • 생성자는 클래스 이름과 같고 리턴 타입이 없다(다른 메서드와 형식이 다르다).
  • 기본 생성자가 메모리에 있는 내용을 객체로 만드는 역할을 한다.

객체가 메모리에 만들어지면 this도 생기는데, 이건 자기 자신을 가리키는 객체다(b도 객체를 가리키는 것).

정리하면, 새로운 자료형을 쓰려면 클래스로 설계하고, 쓰려면 객체를 생성해야 하며, 생성하려면 new 연산자와 생성자 메서드를 이용해 객체를 생성한다.


import kr.tpc.BookDTO;

public class TPC10 {
	public static void main(String[] args) {
       // int, float, char, boolean -> PDT
	   int a;
	   a=10;
	   // 책(BookDTO)이라는 자료형을 만들자.--> class
	   // 객체생성
	   BookDTO b=new BookDTO();
	   b.title="자바";
	   b.price=17000;
	   b.company="영진";
	   b.page=670;

	   System.out.print(b.title+"\t");
	   System.out.print(b.price+"\t");
	   System.out.print(b.company+"\t");
	   System.out.println(b.page+"\t");

	}

}

BookDTO 객체 생성 결과

객체는 메모리에 만드는 작업이며, 그것이 생성자 메서드가 하는 일이다. 그리고 자기 자신을 가리키는 this도 만들어진다.

필요한 속성만 뽑아다 쓰는 것을 모델링이라 한다. 객체는 두 가지 관점으로 볼 수 있는데(1. 상태 정보, 2. 행위 정보), 지금은 상태 정보 측면에서 객체를 본다.

생성자의 역할과 중복 정의

객체를 만드는 데 관련된 메서드가 생성자 메서드(constructor)이며, 생성자가 하는 가장 큰 역할은 초기화다.

  • 초기화를 하려면 객체가 만들어져야 값을 넣을 수 있다. 그래서 객체를 만드는 과정을 먼저 하고, 그래야 초기화가 가능하다.
  • 디폴트 생성자만으로는 초기화를 못하고, 생성자를 하나 더 만들어서 써야 (값을 가진) 객체 생성이 가능하다. 이게 생성자의 중복 정의다.
  • 객체가 만들어진 후에 값을 집어넣을 수 있는데 그것이 초기화다.
  • 메서드 이름이 같아도 개수나 타입이 다르면 중복해서 쓸 수 있다(오버로딩). 메서드 이름이 같다는 건 하는 역할이 비슷하다는 뜻이며, 굳이 다르게 안 만들고 하는 일이 비슷하니 한 이름으로 묶자는 개념이다.

생성자 중복 정의

주의: 디폴트 생성자는 자동으로 만들어지지만, 생성자를 따로 만들면 디폴트 생성자는 만들어지지 않는다. 그래서 다른 생성자와 함께 쓸 때는 디폴트 생성자도 명시적으로 만들어 줘야 한다.

private 생성자와 static 메서드

생성자 메서드가 private이면 new로 객체를 생성할 수 없게 된다.

  • 그래서 생성자가 private이면 객체 생성 없이도 사용 가능해야 한다(모든 클래스의 멤버가 static 멤버가 되어야 한다).
  • static 메서드가 붙어 있으면 클래스 메서드라 부르고, 없으면 인스턴스 메서드라 부른다.
  • 인스턴스 메서드는 객체 생성 후에 접근이 가능하다.

객체가 만들어지는 것은 메모리에 올라오는 것이고, 인스턴스 메서드는 객체를 생성한 후에 호출된다. 그런데 private이면 객체 생성이 안 되므로, 그 메서드는 절대 호출할 수 없다(접근 불가).

반면 static이 붙은 메서드는 new로 객체 생성 없이도 접근이 가능하다(클래스 이름으로 접근). static은 클래스 생성 없이 사용 가능하다.

static 메서드 - 클래스 이름으로 접근 1

static 메서드 - 클래스 이름으로 접근 2

클래스 이름으로 바로 입력해서 사용 가능하다. (참고: 이클립스에서 static 메서드는 15도 정도 기울여서(이탤릭으로) 보인다.)

static 메서드 - 객체 생성 불필요

static인 경우 자동으로 메모리에 올라가기 때문에, 생성해서 호출할 필요 없이 바로 사용 가능하다. 즉, 객체를 생성하는 부분이 불필요하다.

정리하면, 생성자를 private으로 만들면 생성자 메서드를 호출할 수 없게 되고, 그러면 private 클래스 안의 모든 메서드가 static 메서드여야 한다.


class, object, instance의 상호관계

class, object, instance 관계

  • object일 때는 구분이 안 되지만, 객체가 b1, b2, b3로 가리키게 되면 이때를 인스턴스 변수라 부른다.
  • 객체가 구체적인 메모리 대상체(실체)를 가리킨다고 해서 인스턴스 변수라고 한다.
  • object가 인스턴스가 되는 과정은, 객체가 생성된 후 구체적인 메모리를 가리키는 것이다(실체 메모리가 생기며 그걸 인스턴스라 한다).
  • 객체나 인스턴스 변수나 비슷하지만 부르는 시점이 약간씩 다르다.

DTO를 만드는 이유는 클래스를 담는 새로운 자료형을 만들어 데이터를 이동시키기 위함이다. 만약 4개의 데이터를 이동시켜야 한다고 가정하면, 개별로 되어 있어 묶어서 이동하려면 하나의 공간이 필요하다.

  1. 배열로 할 수 있고,
  2. 배열로 못하면 직접 설계한다(int, string 등 여러 데이터가 들어가면).

DTO로 데이터 묶기 1

DTO로 데이터 묶기 2

DTO에 데이터가 묶인다.

DTO 활용


정보 은닉

정보 은닉 개념

기억 공간에 데이터를 빼고 넣을 때, 잘못된 데이터를 넣어도 문제가 안 되게 한다.

  • 어떤 클래스를 설계하면 객체가 가지고 있는 것을 상태 정보라 한다.
  • 객체의 상태 정보는 보호해야 하고, 외부에서 이 객체에 마음대로 접근해서 값을 넣고 빼게 하면 안 된다. 객체 설계 시 외부에서 마음대로 못하도록 하는 것이 정보 은닉이다.

정보 은닉: 다른 객체(class)로부터 접근을 막는 것(private). 즉, 모든 객체(멤버)를 private으로 막는다.

getter/setter로 접근

그럼 접근을 못하는데 어떻게 객체에 접근할까? private으로 되어 있는 기억 공간에 접근하려면, getter와 setter로 이뤄진 한 쌍의 메서드들이 필요하다.


잘 설계된 DTO, VO 클래스

잘 설계된 DTO/VO 클래스 1

  1. 정보 은닉된 클래스다.
  2. private으로 만든 멤버 변수에 접근하기 위해 setter, getter 메서드를 만든다.
    • setter는 외부로부터 변수를 받아서 멤버로 넣는다(종속 객체 주입의 역할과 개념).
    • DI(종속 객체 주입): setter의 역할
  3. default 생성자를 명시적으로 만든다.
  4. 객체가 가지고 있는 값 전체를 출력하기 위한 toString() 메서드를 재정의한다.

잘 설계된 DTO/VO 클래스 2


메서드 오버로딩

메서드 오버로딩

  • 같은 이름의 메서드를 여러 개 가지면서, 매개변수의 유형과 개수를 다르게 하는 기술이다.
  • 메서드의 signature가 다르면 된다(signature: 매개변수의 타입, 개수).
  • 이렇게 하는 이유는 편리하기 때문이다.
  • 오버로딩은 속도 저하의 원인이 되지 않는다(보통 메서드가 여러 개면 속도가 떨어지는 원인이 되지만 오버로딩은 그렇지 않다).
  • 컴파일러가 이런 메서드들을 내부적으로 이름을 바꿔버린다.
  • 이 오버로딩을 정적 바인딩이라 한다(컴파일 시점에 호출될 메서드가 이미 결정되는 바인딩). 정적 바인딩으로 인한 프로그램의 속도 저하는 없다.

정적 바인딩


동일한 구조 vs 이질적인 구조 (배열 vs 클래스)

배열과 클래스의 관계

  • Array = 동일한 데이터 구조
  • class = 서로 다른 이질적인 데이터 구조

차이점:

  • 배열은 바로 생성되지만, 클래스는 설계 후 객체 생성을 거쳐야 메모리에 만들어진다.
  • 모양은 비슷하지만 만드는 방식이 다르다.

객체 배열

객체 배열로도 만들 수 있다.

  • 어떤 데이터가 동일한 구조면 배열을 쓰고,
  • 데이터가 객체를 저장할 경우(이질적인 경우) 새로운 클래스를 정의해서 사용한다.

정리: 우리가 만드는 Model 종류 (3가지)

Model 종류 - DTO, DAO, Utility

  • DTO: 데이터 구조, 담는 역할
  • DAO: 가장 핵심 클래스(데이터를 처리하는 비즈니스 로직, 데이터와 CRUD하는 역할). 회사가 비즈니스를 하지 않으면 영업이 안 되듯 핵심이다.
  • Utility(Helper Object): 도움을 주는 기능을 제공하는 역할

JSON 변환 - gson

이렇게 JSON으로 만들어주는 gson도 사용 가능하다(문자열 형태인 JSON으로 바꿔준다).


상속 (Inheritance): 수평적 구조 vs 수직적 구조

상속 - 수평적 구조 vs 수직적 구조

  • 수평적 설계: 쉽지만 코드의 중복이 발생하고, 새로운 요구사항에 코드 수정이 불가피하며 관리가 어렵다.
  • 수직적 설계: 수평적 설계보다 확장이 쉽지만 코드가 복잡해진다.

여러 개의 클래스가 상속 관계에 있을 때, 객체 생성을 상속 관계에서 어떻게 하느냐가 핵심이다. 그리고 모든 상속은 메모리적으로 이해해야 한다.

상속과 객체 생성 순서:

  • 모든 클래스는 Object 클래스를 상속받는다. super를 생략해도 자동으로 넣어준다.
  • 기본적으로 생성자는 객체를 생성하는 역할이다.
  • 자신부터 생성될까, 부모부터 생성될까? 당연히 부모부터 생성되어야 한다. 부모 객체를 만들려면 부모 생성자를 호출해줘야 한다.
  • 예를 들어 Animal 클래스가 먼저 메모리에 만들어지고, dog/cat이 만들어진다. Animal도 먼저 Object가 만들어진 다음에 만들어진다.

재정의 (Override)

재정의(Override) - 부모와 자식의 eat

객체 생성 시 상속에서는 Animal이 먼저 (메모리에) 생성되고, 그다음 Dog가 생성된다.

  • Dog는 부모를 상속했기 때문에 부모 영역까지 접근이 가능하며, 전체를 Dog 타입으로 볼 수 있다. 변수 d가 메모리 전체를 가리킨다(타입이 Dog지만 dog 부분만이 아니라, 자식은 부모까지 다 접근 가능).
  • 그럼 eat이 2개 공존하게 된다(부모 eat과 자식 eat).
  • 이 eat은 어디 것일까? 당연히 타입이 Dog이기 때문에 자식의 eat을 가리킨다(부모는 무시). 그래서 부모/자식이 있으면 자식의 메서드를 호출하게 된다.

또 하나는 다음처럼 객체를 만들 수도 있다.

  • Dog 클래스의 모든 내용을 알고 있으면 Dog d = new Dog()처럼 쓰지만,
  • 아닐 경우 Animal d = new Dog()처럼 쓴다.

바로 위 챕터에서 설명한 Tv를 만드는데 설계도를 다 주지 않는 경우가 있는 것처럼 생각하면 된다.

업캐스팅 (upcasting)

Dog의 동작 방식을 모르는 경우, 부모 타입을 이용해서 Dog의 eat를 호출한다. (이 방법이 아주 중요하다.)

부모가 자식의 타입을 받을 수 있는데, 이를 업캐스팅(upcasting)이라고 한다.

업캐스팅과 메모리 영역

  • 부모-자식은 업캐스팅이 자동으로 된다.
  • 받는 타입은 Animal 타입이 된다. 그럼 이 타입은 부모 메모리 영역밖에 접근이 안 된다. 메모리 구조는 왼쪽과 똑같은데 타입과 접근 영역이 달라진다.
  • 부모에 있는 eat에 접근해서 출력하게 되며, 아래 Dog 타입 영역으로는 갈 수 없다.
  • 그런데도 부모가 가진 eat을 자식이 재정의하면(메모리가 연결되면), d가 갈 수 있는 범위가 Animal이었더라도 재정의된 자식 메서드를 찾아간다. 그래서 자식의 eat을 실행해 “개처럼 먹는다”가 나온다.
  • 즉, Dog의 기능을 몰라도 자식의 기능을 동작시키는 것이 Override의 개념이다.

정리: 재정의를 하고 업캐스팅하면, 부모 타입으로 갈 수 있는 범위는 Animal이 끝이었는데, 자식 메서드에서 재정의를 하면 이 d 객체가 실행할 때 재정의된 메서드를 찾아간다(중요!).

그래서 재정의(Override)는 동적 바인딩이라 한다(호출될 메서드가 실행 시점에 결정되는 바인딩). 프로그램의 속도가 떨어지는 원인이 되지만, 이점이 더 많기 때문에 사용한다.

업캐스팅과 다운캐스팅

업캐스팅과 다운캐스팅

  • 부모 타입은 크고, 자식은 작은 타입이다.
  • 작은 게 큰 것으로 가면 업캐스팅
  • 반대로 큰 게 작은 것으로 가면 다운캐스팅

다운캐스팅 예시 1

이 부분들이

다운캐스팅 예시 2

이 사진을 말한다.

Dog의 동작 방식을 전혀 몰라도 실행이 된다. Animal 타입만 있으면 Dog 타입을 실행할 수 있다는 게 아주 강력하다.

public static void main(String[] args) {
	 // Animal 부모클래스를 사용하지 않음
 Dog d=new Dog();
 d.eat(); // ? -> 개처럼먹다.

 Cat c=new Cat();
 c.eat();
 c.night();

 // Dog.class, Cat.class
 Animal ani=new Dog(); // upcasting(자동형변환)
 ani.eat(); // ? -> 개처럼먹다.

 ani=new Cat();
 ani.eat(); // ? -> 고양이 처럼먹다.
 //ani.night();
 ((Cat)ani).night(); // 밤에 눈에서 빛이난다. -->downcasting(강제형변환)
}

Animal은 ani.night()를 할 수 없다(Animal에는 night이 없다). 그래서 자식으로 바꿔서 night를 해야 하므로, 자식 타입으로 강제 형변환(다운캐스팅)을 하고 실행해야 한다.

상속과 다형성의 활용

올라가다 보면 최초의 객체가 있고, Object가 최상위 객체가 된다.

  • 나를 기준으로 상속의 범위가 부모와는 밀접하지만, 고조·증조 등 위쪽 관계와는 멀게 느껴진다. 상속도 바로 부모와의 관계를 잘 이해하면 위쪽 관계와 쉽게 연관 지을 수 있다.
  • 상속 관계 클래스를 쓰기 위해서 객체를 사용한다. 어떻게 써야 가장 효율적일까?
  • 사람이 TV 동작을 몰라도 리모컨이 가진 기능으로 다 동작시킬 수 있다.
  • 사람 클래스와 TV 클래스가 다이렉트로 연결되면 바로 연결할 수 있지만, 모를 경우가 더 많다. 리모컨과 TV 같은 관계의 클래스를 다른 사람에게 주면, 소스 코드를 노출하지 않고 보호할 수 있으며 리모컨으로 언제든 동작시킬 수 있다. 이런 관계가 상속 관계에 포함된다. 이것이 인터페이스 역할을 한다.
  • 상속은 인터페이스 기반의 프로그래밍을 한다.

상속과 메모리 영역 확장

  • Animal이 먼저 만들어지고 아래에 Dog가 만들어진다.
  • Dog 입장에서는 Animal을 상속해서 범위가 확장된다. 이 메모리 전체의 영역을 Dog의 영역으로 할 수 있다.
  • 왼쪽은 타입이 무엇인지 다 알고 동작도 다 알 때 쓰고, 오른쪽(업캐스팅) 방식을 훨씬 많이 쓰게 된다.
  • new Dog을 하면 객체는 같게 만들어지는데, 담는 타입이 Animal이라 Dog 타입으로 갈 수 없다. 아래쪽으로 못 가면 Dog를 못 쓰는데, 그래서 재정의(override)한 뒤 접근하게 한다(부모와 자식의 연결 고리가 만들어진다). 재정의한 메서드는 자식의 경우도 접근 가능하다(이 기법이 아주 중요하다).
  • 부모의 메서드를 찾고 자식의 메서드가 있으면 자식의 메서드를 실행한다.
  • 이런 방법(업캐스팅)이 상속 관계에 숨어 있으며, 이것이 상속 관계에서 객체 생성할 때의 장점이다.

상속 관계 객체 생성 1

현재 상속 관계다.

상속 관계 객체 생성 2

사전에 Dog와 Animal이 상속 관계여야 이런 객체 생성을 할 수 있다(업캐스팅 = 부모 쪽으로 객체를 만든다). 계속 말하지만, 부모 클래스만 있어도 자식을 구동할 수 있다.

다운캐스팅: 부모는 1명이지만 자식은 여러 명(1, 2, 3, 4…)일 수 있다. 부모가 특정 하위 클래스(자식 타입)로 좁혀지는 것이 다운캐스팅이다.

다형성 - 하나의 메시지, 다른 반응

이렇게 업캐스팅 하나만 가지고 있어도 Dog와 Cat을 구동시킬 수 있다.

  • 한 명의 부모가 먹으라고 했을 때, 개는 “개처럼 먹다”, 고양이는 “고양이처럼 먹다”가 된다.
  • eat 메서드는 하나인데 다르게 작동한다. = 다형성
  • 다형성은 상위 클래스가 하위 클래스에게 동일한 메서드를 주고, 동일한 메시지를 보내면 하위 메서드가 서로 다르게 동작하는 원리다. 즉, 하나의 메시지에 대해 반응이 다르다.
  • 상속 관계가 되고 그 관계에서 casting을 하게 되면 다형성 이론이 적용된다.

이 이론은 한 번 더 나올 것이다.

다형성 활용 예시

사실 전에 했던 내용과 중복되는 점이 많다.


public static void main(String[] args) {

       // upcasting
	   // Cat ani=new Cat();
	   // Object ani=new Cat();
		Animal ani=new Cat(); //upcasting
		ani.eat(); // 컴파일시점->Animal, 실행시점->Dog

		//ani.night();
		//Cat c=(Cat)ani; // downcasting
		//c.night();
		((Cat)ani).night();

		ani=new Dog();
		ani.eat();
		// 다형성
		// 상위클래스가 하위클래스에게 동일한 메세지로 서로다르게 동작시키는 원리

		Object o=new Dog();
		//o.eat();
		((Dog)o).eat();
	}

앞으로 부모 클래스를 가지고 오브젝트를 쓰므로 다형성이 중요해진다. 활용 부분을 알면 소스 코드를 이해하기 편하다. 부모 클래스는 리모컨이라 생각하면 좋다.


다형성 전제조건

다형성과 리모컨 비유

부모 클래스와 자식 클래스가 있을 때 부모 클래스는 리모컨 역할을 한다. 그래서 상속을 쓰는 이유가 되기도 한다.

객체지향에서 꽃이라 부를 수 있는 것이 message polymorphism(메시지 다형성)이다. 이걸 응용하면 객체지향 프로그래밍이 훨씬 쉬워진다.

다형성 전제조건

다형성 전제조건

  • 상속 관계일 것
  • 객체 생성을 업캐스팅으로 할 것 (상위 클래스가 하위 클래스에게 메시지를 보내야 하므로. 업캐스팅이 되면 다운캐스팅을 할 수 있다.)
  • 하위 클래스가 반드시 재정의해야 한다 (다형성 보장을 위해).
  • 동적 바인딩을 통해 실현된다. (동적 바인딩: 실행 시점에 사용될 메서드가 결정되는 바인딩. 프로그램의 속도를 떨어뜨리는 원인이 된다.)

프로그램 속도를 떨어뜨려도 이점이 있어서 사용한다. 사람이 TV를 작동시킬 때 리모컨이 있으면 더 편하듯, TV에 가서 직접 동작시키는 것과 리모컨으로 하는 것 사이에 엄청난 차이가 나지 않기 때문이다.

다형성과 재정의 정리

다형성은 상위 클래스가 하위 클래스에 명령을 내리면 서로 다르게 동작하는 객체지향의 원리다.

  • 상속 관계에서 부모가 가진 기능을 하위 클래스가 물려받아, 그 기능이 맞지 않으면 일부를 수정하거나 추가하는 것이 재정의(Override) 개념이었다.
  • 부모와 자식의 연결은, 자식이 재정의해야 부모가 명령을 내렸을 때 반드시 동작한다.
  • 이 다형성이 구현되려면 추상 클래스와 인터페이스를 구현해 놓아야 한다.

추상 클래스

다형성을 보장하기 위해 등장한 개념이 추상 클래스다.

  • 부모의 Body(구현부)가 필요 없는 경우가 있다. Dog와 Cat이 상속받고 재정의하면, 부모의 ? 같은 미완성 출력은 나오면 안 된다.
  • 만약 Animal이 지닌 eat를 하위 클래스가 재정의하지 않으면 부모의 미완성 동작이 나올 수 있는데, 이를 막기 위해 Dog와 Cat이 재정의해야 한다.
  • 원형(구현부)이 없는 것은 출력될 일이 없으므로 Body를 없애다 보니, 구현부가 없는 메서드가 되고 이것을 추상 메서드라 한다.
  • 불완전한(구현부 없는) 메서드를 가진 것이 추상 클래스다.

부모가 추상 클래스면, 자식이 상속하게 될 때 반드시 구현해야 한다(Body를 만들어야 한다 = 재정의를 해야 한다).

  • 만약 Animal이 가진 추상 메서드를 하위 클래스 Dog도 구현하지 않으면, Dog도 추상 메서드를 가지게 되어 자기 자신도 추상 클래스가 되어버린다. 그러면 Dog도 Dog d = new Dog() 같은 객체 생성을 할 수 없게 된다.
  • 추상 클래스는 부모 역할은 할 수 있다(Animal a = new Animal()처럼 자기 스스로 객체 생성은 못한다).
  • 자식과 부모가 만나면 된다. 불완전한 객체라도 부모 역할을 하고, 업캐스팅으로 객체를 생성할 수 있다.

추상 클래스 - 비슷한 기능 묶기

기능이 비슷한 것을 묶을 때는 추상 클래스로 묶을 수 있다. 중요한 점은 추상 클래스에는 구현 메서드가 들어갈 수 있다는 것이다.

  • 추상 메서드는 반드시 재정의해야 하지만, 추상 클래스를 받아서 쓰면 Dog, Cat 다 사용 가능하다.
  • 예를 들어 개와 고양이 모두 move가 들어가도 이상이 없다(기능이 비슷하기 때문).

추상 클래스 vs 인터페이스

서로 기능이 비슷한 게 아니라 다른 클래스를 묶을 때 쓰는 것이 인터페이스다.

  • 인터페이스는 서로 기능이 다를 때 쓴다(추상 클래스는 기능이 비슷할 때).
  • 추상 클래스는 일부만 다형성을 보장하고, 인터페이스는 100% 다형성을 보장한다.

인터페이스

인터페이스 개념

  • 하위 클래스가 100% 재정의를 해야 한다.
  • 하위 클래스가 재정의를 할 때, 인터페이스가 메시지를 호출하면 동작한다.
  • 인터페이스도 부모-자식으로 만들 수 있다.

인터페이스는 100% 추상 메서드로 만들 수 있다. TV와 RADIO는 가전제품으로는 비슷하지만 서로 기능이 다른 클래스인데, 이런 것도 공통으로 묶을 수 있을까? 자기 집의 TV와 선풍기를 리모컨 하나로 묶을 수 있는 것처럼 가능하다.

  • 부모를 추상 클래스로 묶으면 비슷한 것끼리 묶이지만, 다른 것을 묶을 때는 인터페이스로 묶는다. 왜냐하면 하위 클래스가 서로 기능이 다르기 때문이다.
  • 서로 다른 기능은 추상 클래스로 묶으면 안 되고, 인터페이스로 묶어야 한다.

인터페이스로 TV와 라디오 묶기

이렇게 리모컨 하나로 객체를 구현할 수 있다(TV와 라디오를 동작). 우리는 내부를 알 필요가 없다. 부모가 하나지만 TV와 라디오가 동일한 메시지로 다르게 동작하는 것이 다형성이다. 다형성 인자와 배열은 앞과 똑같다.

  • 인터페이스는 객체를 생성하지 못하기 때문에, 객체 생성 없이 쓰는 것은 static과 함께 .min이든 .max든 이런 식으로 썼었다.
  • 인터페이스는 객체 생성을 못해서 new를 못한다. 그래서 static을 써야 자동으로 메모리에 올라간다.
  • final은 변수에 값이 올라가면 수정하면 안 된다.

부모 클래스의 중요성

추상 메서드와 인터페이스 비교

추상 메서드(추상 클래스)와 인터페이스의 공통점:

  • 다형성을 보장하기 위해 사용되고, 객체를 생성하지 못한다.
  • 하위 클래스에 의해 구현되어야 한다(Override: 재정의 필수).
  • 부모(상위 클래스)의 역할로 사용한다(upcasting으로 생성).
  • 추상 메서드를 가진다.

차이점:

  • abstract class(추상 클래스): 서로 기능이 비슷한 클래스의 공통 부분을 묶는다.
  • 인터페이스: 설계 규약이다. 다중 상속의 형태를 지원하기 위해 쓴다.
    • 자바는 단일 상속만 지원하며, 클래스는 하나만 상속할 수 있다.
    • 인터페이스는 여러 개 상속할 수 있어서, 복잡하지만 우회적으로 다중 상속을 지원하는 형태로 쓸 수 있다.

인터페이스와 JDBC의 관계

인터페이스와 JDBC

  • 인터페이스는 무조건 자식 클래스가 있어야(추상 클래스이기 때문에) 동작시킬 수 있다.
  • 리모컨 기능으로 TV를 조절하는 기능을 재정의한다.
  • 하위 클래스의 방식을 모르더라도 동작시킬 수 있다.

이것이 JDBC와 어떤 관계가 있을까?

자바에서 DB와 연동해서 프로그래밍하는 것을 JDBC 프로그래밍이라 한다. JDBC 프로그래밍을 하려면 벤더사가 제공하는 DB를 써야 한다. 각 벤더사들이 각자의 DB를 만들어 뒀고, 자바는 JDBC를 쓰기 위해 벤더에서 제공하는 클래스들을 사용해야 한다.

  • 벤더가 바뀔 때마다 안의 메서드들이 달라서 유지보수가 어렵다.
  • A를 인터페이스로 만들고 x를 추상 메서드로 만들면, x라는 메서드를 b, c, d에서 다 구현해야 한다.
  • 이걸 java.sql에 다 만들어 두면, 이 인터페이스를 벤더들이 가져가서 구현 클래스를 만들 때 인터페이스 이름대로 구현해야 한다.
  • 그래서 자바 개발자는 인터페이스를 통해서 하위 클래스를 핸들링할 수 있고, 동작 방식을 몰라도 구현이 가능하다.

예를 들어 아래 그림에서 getConnection 안에 url, user, passwd 이 3가지 정보만 있으면, 벤더들은 이 정보로 접속할 수 있다(세부 내용을 몰라도 연결 가능). 자바는 오라클, MySQL, MSSQL 등을 다 구현하더라도 마음대로 할 순 없고, getConnection으로 통일시키고 url, user, passwd를 주면 벤더가 알아서 구현한다.

그리고 이 구현 클래스들을 드라이버 클래스라 한다. 벤더들이 자기네에 맞게 구현한 것이 드라이버 클래스다. 각 회사 이름으로 검색해보면 JDBC 드라이버가 벤더마다 있으며, 벤더마다 제공하는 클래스가 필요하다.


인터페이스의 상속 관계

인터페이스의 상속 관계

  • A, B 둘 다 인터페이스인데 B가 A를 상속받는다. 인터페이스 또한 상속받을 수 있다.
  • 왼쪽 객체 생성처럼 B r = new X, A r = new X면 둘 다 부모가 되어 객체를 A나 B로 생성해도 된다.
  • 다중 상속을 인터페이스를 통해 만들 수 있어서, 인터페이스는 여러 개 상속할 수 있다.
    • 클래스는 여러 개 받을 수 없지만, 인터페이스는 여러 개 상속 가능하다.
    • 자바의 클래스는 하나만 상속 가능하고, 인터페이스는 여러 개 상속 가능하다.

예시로 살펴보면:

  • 동물(Animal) 클래스를 물려받았다. 애완동물이 많이 있는데, play라는 메서드(함께 생활하는 동작)를 넣고 싶다.
  • 개·고양이 같은 특성은 Animal에 넣어줄 수 있다. 하지만 play 같은 특성은 집에서 같이 생활하지 않는 동물도 있으니 Animal에 넣으면 안 된다.
  • 그래서 별도의 클래스(인터페이스)를 만들어서, 필요한 클래스에만 기능을 선택적으로 넣어주면 된다. Pet이라는 인터페이스(추상 메서드)를 만들고 안에 넣어주면 된다.
  • 로봇이 분해·조립되는 기능도 마찬가지로 모든 동물에 넣을 수는 없고, 별도의 인터페이스를 만들어 선택적으로 넣어주면 된다.

그러다 보면 하위 클래스가 여러 개의 형식을 받을 수 있어서 다중 상속처럼 보일 수 있다. 자바에서 상속받는 객체는 Object를 상속받는다.


Object 클래스는 갓(최상위)

Object 클래스 - 최상위 루트

  • 맨 처음 만들어진 클래스가 Object 클래스이며, 자바에서 만든 API 중 최상위 클래스다.
  • 객체가 메모리에 만들어지면 최초에 만들어진 번지를 출력하는 기능이 있다.
  • toString을 가지고 있는 최상위 루트 클래스를 Object 클래스라 부른다.

상속 관계에서 최상위 클래스(부모)를 사용하면 유연하게 사용할 수 있다. A의 부모가 Object인 걸 알면 유용하게 사용 가능하다.

  • 왼쪽 아래처럼 Object로 만들 수 있다.
  • toString은 부모가 가지고 있고, 이를 재정의하지 않으면 Object의 메서드가 실행되고, 재정의하면 자식 것이 실행된다.
A a=new A();
		a.display();
		//System.out.println(a.toString());
		System.out.println(a); // Object-->toString() : kr.poly.A@279f2327

		Object o=new A(); // upcasting
		((A)o).display();

		System.out.println(o.toString());	//다형성에 의해 부모가 명령 내리면 자식 있는 메서드 실행하게 된다.

이렇게 Object 클래스를 이용하면 특정 객체를 핸들링할 수 있다. Object 클래스가 여러 개 있을 경우 Object로 묶어서 다른 메서드로 보낼 수 있고, 이 Object를 메서드의 매개변수로 다양하게 받을 수 있다.


Object 클래스의 활용 (1) - 다형성 인수

Object 클래스 활용 - 다형성 인수

A, B가 Object를 상속받았다고 가정하면:

  • 업캐스팅·다운캐스팅을 하고, go라는 메서드를 Object를 이용해 접근하려면 다운캐스팅해서 받을 수 있다.
  • Object로 업캐스팅하게 되면 반드시 다운캐스팅해서 메서드를 핸들링해야 하는 게 핵심이다.
public static void main(String[] args) {

	 A a=new A();
     display(a);

	 B b=new B();
	 display(b);

	}
	private static void display(Object o) { // 다형성 인수
	  if( o instanceof A) {
		((A)o).go();
	  }else {
	    ((B)o).go();
	  }
	}

원래는 중간에 다음과 같이 두 개의 오버로딩된 메서드가 있었다.

private static void display(B b) { // 다형성 인수


	}
	private static void display(A a) { // 다형성 인수

	}

이것을 하나로 줄인 것이다(Object로 줄임). Object에는 display할 go가 없어서 다운캐스팅을 한다. 받을 때는 다형성 인수(Object 객체)로 받고, 쓸 때는 다운캐스팅을 해줘야 한다.


Object 클래스의 활용 (2) - 다형성 배열

다형성 배열이 있다.


public static void main(String[] args) {
      // A, B 클래스를 저장할 배열
	  Object[] o=new Object[2]; // 다형성배열
	  o[0]=new A();
	  o[1]=new B();

	  for(int i=0;i<o.length;i++) {
		  if(o[i] instanceof A) {
			  ((A)o[i]).go();	//A로 다운캐스팅 해서 go
		  }else {
			  ((B)o[i]).go();	//B로 다운캐스팅 해서 go
		  }		  
	  }
	  printGo(o);
	}
	private static void printGo(Object[] o) {	//메서드로도 가능
		 for(int i=0;i<o.length;i++) {
			  if(o[i] instanceof A) {
				  ((A)o[i]).go();
			  }else {
				  ((B)o[i]).go();
			  }		  
		  }

이런 식으로 Object 객체 배열 및 메서드를 설계한 후, 다운캐스팅해서 얼마든지 사용할 수 있다.


학습 정리

학습 정리 - 객체지향 3대 특징

객체지향 3대 특징(객체 설계 시):

정보 은닉: 정보를 보호하는 것. private이라는 접근 제어자로 마음대로 접근하는 것을 막는다. VO나 DTO 설계 시 막아줘야 한다.

상속: 수직적 구조로 설계한다(수평적 구조로 설계할지 수직적 구조로 설계할지 알아야 한다).

  • 상속의 좋은 점은 부모 클래스 이용 시 하위 클래스를 언제든지 동작 가능하다는 것이다.
  • 결론적으로 상속은 다형성 기법을 쓰기 위해 필연적으로 사용한다.
  • 업캐스팅을 만들면 상위 클래스가 하위 클래스를 몰라도 동작시킬 수 있고, 재정의시켜서 동작시킬 수 있다.

다형성: 아주 중요하다. 상속 관계에 있는 클래스에서, 상위 클래스가 하위 클래스에 메시지를 보내면 서로 다르게 동작시킨다.

다형성 정리

  • 자바 클래스의 모든 객체지향을 알아야 다형성을 논할 수 있다. 자바 전체를 다형성으로 표현해야 하므로, 개념적인 원리로 아는 것보다 실습을 통해 이해해 두는 게 좋다.
  • 다형성 전제조건: 상속 조건이 되어야 하고, 부모가 메시지를 보내야 하므로 업캐스팅으로 만들어야 한다. 그리고 하위 클래스가 반드시 재정의해야 한다.
  • 동적 바인딩은 실행 시점에 동작한다.
  • 마지막으로 다형성을 보장하기 위해 추상 클래스와 인터페이스를 쓴다. 부모 클래스가 동작할 때 하위 클래스가 무조건 동작하게 하려면, 추상 메서드를 만들어서 하위 클래스가 그걸 동작하도록 유도하는 게 중요하다.
  • 추상 클래스와 인터페이스는 자체적으로 객체를 생성하지 못하고, new로 객체를 만들 수 없다.
  • 다형성이 보장되기 위해선 하위 클래스가 반드시 오버라이드해야 한다.

패키지란

클래스 단위로 묶어둔 것을 말한다(기능이 비슷한 클래스를 묶음).

패키지 개념

또 다른 개념으로 패키지는 폴더 개념이다.

  • A라는 패키지 안에 클래스들이 있고, 이 바깥에서 A 안에 있는 클래스를 쓰려면 접근할 수 있어야 한다.
  • 기본적으로 패키지 바깥에 있는 것은 패키지 안에 접근을 못하게 할 수 있다.

그런데 클래스를 만드는 목적은 다른 객체가 사용하기 위해서다. 패키지 안에 있는 것을 밖에서 접근 못하도록 만드는 것은 바람직하지 않다.

패키지를 쓰는 이유:

  • 기능이 비슷한 클래스를 모아 쓰기 위해 패키지를 쓴다.
  • 또는 한 프로젝트를 여러 구조로 나눌 때, A 구조에서 클래스를 만들고 B 구조에서도 만드는데, 최종적으로 합칠 때 A와 B의 이름이 같으면 문제가 발생한다. 그럼 이름을 다 수정해야 한다. (패키지로 구분하면 이를 방지할 수 있다.)

다른 패키지에 접근하려면 풀 네임을 알아야 한다. 정확하게 접근하려면 kr.tpc를 앞에 붙여 써야 하지만, 매번 이렇게 하기 힘드므로 import를 써서 패키지를 import한 뒤 사용한다.


자바에서 제공하는 API

자바 API - jar 파일

  • 자바에서 제공하는 것은 기본적으로 jar 파일로 제공된다.
  • 가장 기본적인 것이 jrt-fs.jar이고, 이걸 풀어보면 lang, util, io, sql, net 등의 폴더들이 있다.

문자열도 객체

문자열도 객체

문자열을 처리하려면 String을 쓴다. 즉 문자열을 객체로 취급한다(새로운 자료형이 클래스로 만들어진 것).

  • 직접 만들 수도 있고 제공된 클래스를 쓸 수도 있다.
  • 문자열은 직접 만드는 게 아니라 자바가 제공해준다. 자바의 java.lang 패키지에 String을 만들어 놓아서, 이것이 자료형이며, 문자열을 변수에 저장하려면 타입을 String으로 해야 한다.

문자열 생성 시:

  1. new로 생성하면 다른 번지를 가리키고 힙 영역에 저장된다.
  2. 상수로 생성하면 같은 번지에 저장되고, 문자열 상수(Literal Pool)라는 곳에 생성되어 이를 재활용한다.

비교:

  • ==는 주소가 같은지 확인한다.
  • equals는 안의 문자열 내용이 같은지 확인한다.

Object와 다형성 - 컬렉션

항상 Object가 등장하면 다형성 이론이 등장한다고 보면 된다.

  • Object는 부모이기 때문에 다 넣을 수 있다. 그래서 사이즈만큼 반복하면 Object가 나온다.
  • 그런데 A 타입, B 타입 서로 다른 타입이 들어 있으니, A 인스턴스인지 B 인스턴스인지 다운캐스팅하고 확인해서 넣어야 한다.
  • 업캐스팅은 API로 들어갈 때 일어난다.

import java.util.*;

import kr.tpc.BookDTO;

public class TPC38 {
	public static void main(String[] args) {

		List list=new ArrayList(1); // Object[] 이 경우 어떤 타입이 오든(movie든 book이든 뭐든) 넣을 수 있다.
		List<BookDTO> list=new ArrayList<BookDTO>(1); // Object[]-->BookDTO[] //이경우 오브젝트 클래스(List)가 아니라 BookDto만 올 수 있게 된다.
		list.add(new BookDTO("자바", 12000, "이지스", 600));
		list.add(new BookDTO("C언어", 17000, "에이콘", 700));
		list.add(new BookDTO("Python", 15000, "제이펍", 690));

		for(int i=0;i<list.size();i++) {
			//Object o=list.get(i);
			BookDTO vo=list.get(i);
			System.out.println(vo.title+"\t"+vo.price+"\t"+vo.company+"\t"+vo.page);
		}

	}
}
  • List list = new ArrayList(1); // Object[] — 어떤 타입이 오든(movie든 book이든) 넣을 수 있다.
  • List<BookDTO> list = new ArrayList<BookDTO>(1); // Object[] → BookDTO[] — 이 경우 Object 클래스(List)가 아니라 BookDTO만 올 수 있다.

후자처럼도 쓸 수 있으며, 이 경우를 제네릭이라 한다.

제네릭 예시


Wrapper 클래스란

Wrapper 클래스

기본 자료형을 객체 자료형으로 사용할 수 있도록 만들어 놓은 자료형(포장 클래스)이다.

왜 객체로 만들어야 할까? 기본 자료형을 Object 자료형으로(부모와 연동해서) 쓰려면 Wrapper 클래스가 필요하기 때문이다.

  • 예를 들어 int형 데이터에 1이 들어가는데, 이것은 정수형인 int 자료다. 이를 new Integer라는 클래스로 만들 수도 있다.
  • 즉, Wrapper 클래스는 Object 클래스에 저장하기 위해 필요하다.