객체 지향 프로그래밍(Object Oriented Programming)
객체 지향 프로그래밍이란 프로그램을 단순히 데이터와 처리 방법으로 나누는 것이 아니라, 프로그램을 수많은 '객체(object)'라는 기본 단위로 나누고 그것간의 유기적인 상호 작용통해 프로그램을 발전 시키는 프로그래밍 방법론이다.
- 객체(Object)란 책상, 의자, 시계, 전등, 책 등 물리적으로 존재하거나 추상적으로 생각할 수 있는 것 중에서 자신의 속성을 가지고 있고 다른 것과 식별 가능한 것을 말한다.
객체지향 프로그래밍은 실세계에 존재하고 인지하고 있는 객체(Object)를 소프트웨어의 세계 흉내 내어 가장 모든 데이터를 객체(부품)로 취급한다.
- 객체 지향 프로그래밍의 가장 기본적인 단위인 객체란 속성과 동작으로 구성되어 있다. 자바는 이 속성과 동작들을 각각 필드(field)와 메소드(method)라 부른다.
- 객체 지향에는 클래스, 객체, 인스턴스, 상속, 인터페이스, 다형성, 추상화 등의 많은 개념들이 존재한다.
- 프로그램을 단위(객체)로 쪼개서, 레고를 조립하듯 가져다 쓰기 편하게 만들어 놓은 방식이라고 보면 된다.
객체와 클래스
현실에서 객체를 만들 때 설계도를 바탕으로 만들어 지듯 객체지향 프로그래밍에서도 객체를 생성하기 위한 설계도를 만드는 작업이 필요하다. 자바에서 클래스란 객체 생성하기위한 객체의 설계도 또는 틀이라고 정의할 수 있다.
- 자바에서는 이런 클래스를 토대로 객체를 생성하여 사용한다.
- 클래스는 객체를 생성하기 위한 필드(field), 생성자(Constructor)와 메소드(method)로 구성된다.
필드(field) : 객체의 데이터를 저장하기 위해 클래스에 포함된 변수(variable)를 의미한다.
생성자(Constructor) : 객체 생성 시 초기화를 담당한다. 객체를 사용할 준비
메소드(method) : 객체의 동작을 수행하기 위한 명령문의 집합을 의미한다.
public class ClassName {
static int field; // 필드
ClassName(){...} // 생성자
void method() { // 메서드
}
}
- 클래스 설계 => 클래스를 통해 객체 생성 => 생성된 객체를 사용
클래스는 데이터와 함수의 결합이라고도 할 수 있다.
- 데이터를 처리하기 위한 데이터의 저장형태의 발전과정으로 서로 관련된 변수들을 정의하고 이들에 대한 작업을 수행하는 함수들을 함께 저장한 것이 클래스이다.
변수 : 하나의 데이터를 저장할 수 있는 공간
배열 : 같은 종류의 여러 데이터를 하나로 묶어 저장할 수 있는 공간
구조체 : 서로 관련된 여러 데이터를 하나로 저장할 수 있는 공간(서로 다른 자료형도 가능)
클래스 : 데이터와 함수의 결합( 구조체 + 함수), 서로 관련있는 데이터와 함수(메서드) 를 묶는다.
- 예를 들어 자바에서는 문자열을 String 클래스로 정의하여 문자열을 문자의 배열로만 정의하지 않고 문자열을 다루는데 필요한 함수들과 함꼐 묶어 사용한다.
- 사용자 정의 타입을 활용하여 원하는 타입(클래스)을 만들 수 있다.
하나의 소스파일에 하나의 클래스를 작성하는 것이 바람직하다.
- 하나의 소스파일에는 둘 이상의 public class는 존재하면 안된다.
객체의 생성
객체는 설계도인 클래스를 사용해 클래스에 정의된 내용대로 메모리 힙(heap) 영역에 생성되기 때문에 필드(field)와 메소드(method)로 이루어져 있다.
- 속성 보다는 멤버변수(member variable), 기능보다는 메서드(method)라는 용어로 부르는게 일반적이다.
- 객체는 new 연산자를 통해 생성한다.
new 클래스명();
// 생성자 : 객체를 생성하고 객체의 주소를 리턴한다.
// new연산자 : 객체를 생성하고 객체의 주소를 반환
- new 연산자가 반환한 객체의 주소를 참조변수에 저장하여 사용한다.
클래스명 참조변수명 = new 클래스명();
// 참조변수(리모콘), 클래스의 객체를 참조하기 위한 참조변수를 선언
객체는 위와 같은 방법으로 생성할 수 있는데 클래스로부터 객체를 선언하는 과정을 인스턴스화라고 하며, 이떤 클래스로부터 만들어진 객체를 그 클래스의 인스턴스라고 한다.
인스턴스와 객체는 같은 의미이지만 객체는 모든 인스턴스를 대표하는 포괄적인 의미를 갖고 있으며, 인스턴스는 어떤 클래스로부터 만들어진 것을 강조하는 구체적인 의미를 갖고 있다.
인스턴스의 사용
인스턴스를 사용 한다는 것은 인스턴스의 멤버변수와 메서드를 사용하는 것을 의미한다.
- 인스턴스는 참조변수를 통해서만 다룰 수 있으며, 참조변수의 타입과 인스턴스의 타입은 일치해야 한다.
참조변수.멤버변수; // 변수 호출
참조변수.메서드(); // 메서드 호출, 메서드가 가진 문장들이 실행된다.
- 하나의 클래스로부터 여러 개의 인스턴스를 생성할 수 있으며 같은 클래스로부터 생성된 인스턴스는 서로 독립된 속성을 값을 가지며, 메서드의 내용은 모든 인스턴스가 공유한다.
class Ex {
static int field;
void method() {
}
}
public class Main {
public static void main(String[] args) {
Ex e = new Ex();
Ex e1 = new Ex();
// e != e1
}
}
- 자신을 참조하고 있는 참조변수가 하나도 없는 인스턴스는 JVM의 가비지 컬렉터에 의해 자동적으로 제거된다.
Ex@75a1cd57
Ex@515f550a
Ex@75a1cd57
Ex@75a1cd57 //Ex@515f550a은 더이상 참조하고 있는참조변수가 존재하지 않아 삭제된다.
- 변수는 하나의 값만을 저장할 수 있기 때문에 참조변수에는 하나의 주소 만을 저장할 수 있다.
- 반대로 둘 이상의 참조변수가 하나의 인스턴스를 참조하는 것은 가능하다.
- 참조변수의 출력이나 덧셈 연산자를 이용한 참조변수와 문자열간의 결합에는 toString()이 자동적으로 호출되어 참조변수를 문자열로 대치한 후 처리한다.
객체 배열
객체도 배열로 다루는 것이 다능하며 객체 배열 안에 객체의 주소가 저장된다. 참조변수들을 묶어 하나의 배열로 다루는 참조변수 배열이다.
Ex[] eArr = new Ex[3]; // 객체 배열 생성, 배열은 요소는 초기값 null으로 초기화된다.
eArr[0] = new Ex(); // 각 요소(참조변수)에 객체를 생성
eArr[1] = new Ex();
eArr[2] = new Ex();
- 객체 배열도 일반적일 배열과 마찬가지로 같은 타입의 객체만을 저장할 수 있다.
- 많은 수의 객체를 다뤄야할 때, 배열을 사용된다.
변수의 종류
- 변수는 클래스변수, 인스턴스변수, 지역변수 3가지가 있고 변수의 종류는 변수의 선언된 위치에 따라 결정된다.
- 클래스 필드에 선언된 멤버변수중 static이 붙은 변수를 클래스 변수, 반대로 붙지 않은 변수를 인스턴스 변수라고 하며 지역변수는 메소드나 생성자 블록 내에 위치한 변수를 모두 지역변수라 한다.
class Ex {
static int classV; // 클래스 변수 선언
int instanceV; // 인스턴스 변수 선언
int method() {
int localV = 0; // 지역변수 선언
return localV;
}
}
변수 | 생성 시기 | 소멸 시기 | 저장 메모리 | |
클래스 변수 | 클래스가 메모리에 올라갈 때 | 프로그램이 종료될 때 | 메서드 영역 | |
인스턴스 변수 | 인스턴스가 생성되었을 떄 | 인스턴스가 소멸할 때 | 힙 영역 | |
지역 변수 | 변수의 선언문이 수행되었을 때 | 블록을 벗어날 때 | 스택 영역 |
인스턴스 변수(instance variable)
클래스 영역에 선언되며, 클래스의 인스턴스를 생성할 때 만들어진다.
- 사용하기 위해서는 인스턴스 생성이 선행되어야 한다.
- 인스턴스마다 고유한 값을 유지해야 하는 속성의 경우 사용한다.
클래스 변수(static variable)
인스턴스마다 독립적인 저장공간을 갖는 인스턴스 변수와 달리 클래스 변수는 해당 클래스로부터 인스턴스화 되어 생성된 모든 인스턴스가 공통된 저장공간을 공유하게 된다.
- 클래스 변수는 한 클래스로부터 생성된 인스턴스가 공유해야 하는 값의 유지를 위해 사용된다.
- 인스턴스 변수와 달리 인스턴스를 생성하지 않고 언제라도 사용이 가능하다.
- 참조변수의 선언이나 객체의 생성과 같이 클래스의 정보가 필요할 때, 클래스는 메모리에 로딩된다.
class Ex {
static int classV = 2; // 클래스 변수 선언
int instanceV; // 인스턴스 변수 선언
Ex(int iv) {
instanceV = iv; // 인스턴스 생성시 생성자에 의해 인스턴스 변수는 초기화 된다.
}
}
//문제 푸는 공간
public class Main {
public static void main(String[] args) {
// 인스턴스를 생성, 생성자에 의해 초기화된다.
Ex e1 = new Ex(1);
Ex e2 = new Ex(2);
Ex e3= new Ex(3);
// 인스턴스 변수는 각 인스턴스마다 다른 값이 출력된다.
System.out.println("instanceV : " + e1.instanceV); // 1
System.out.println("instanceV : " + e2.instanceV); // 2
System.out.println("instanceV : " + e3.instanceV); // 3
// 클래스 변수의 값을 변경
e1.classV = 1;
e2.classV = 2;
e3.classV = 3;
// 클래스 변수는 인스턴스끼리 공유하는 값이므 같은 값인 3이 출력된다.
System.out.println(e1.classV); // 3
System.out.println(e2.classV); // 3
System.out.println(e3.classV); // 3
}
}
- 인스턴스 변수(iv)로 오해하기 쉽기 때문에 클래스 변수(cv)임을 나타내기 위해 참조변수.변수명이 아닌 클래스명.변수명으로 사용한다.
// 인스턴스 변수로 오해하기 쉽다.
e1.classV = 1;
e2.classV = 2;
e3.classV = 3;
// 클래스 변수임을 나타내기 위해
// 클래스명.변수명 을 사용한다.
Ex.classV = 1;
Ex.classV = 2;
Ex.classV = 3;
지역 변수(local variable)
메서드, 생성자, 초기화 블럭 내부 에 선언되어 블럭 내에서만 사용이 가능하며 블럭을 벗어나면 소멸되어 사용할 수 없다.
메서드(method)
작업단위로 문장들을 묶어서 이름을 붙인 것을 뜻한다.
- 반복적으로 수행되는 여러 문장을 메서드로 작성한다.
- 하나의 메서드는 한 가지 기능만 수행하도록 작성한다.
- 메서드는 클래스안에 선언되어야하지만 함수는 클래스에 독립적이라는 차이점이 있다.
- 메서드는 선언부와 구현부로 이루어져 있다.
반환타입 메서드명 (매개변수1, 매개변수2,....)// 선언부
{// 구현부
실행될 문장
}
선언부 : 메서드가 작업을 수행하기 위해 어떤 값들을 필요로 하고 작업의 결과로 어떤 타입의 값을 변환하는지에 대한 정보를 제공한다.(반환타입, 메서드 이름, 매개변수 등)
몸통부 : 메서드 호출시 수행될 문장
매개변수(parameter)
메서드 호출시에 입력되는 인수의 값을 저장할 변수들을 명시한 것을 말한다.
- 인수(arguments) : 메소드를 호출할 때 전달하는 입력값을 의미한다.
class Ex {
static int classV = 2; // 클래스 변수 선언
int instanceV; // 인스턴스 변수 선언
int method(int iv) { // iv는 매개변수(parameter)
instanceV = iv;
return classV * instanceV; //return 반환값;
}
}
public class Main {
public static void main(String[] args) {
Ex e = new Ex();
System.out.println(e.method(2)); // 압력값 2는 인수(arguments)
}
}
- 반환 값은 0 ~ 1개로 값을 받아서 처리하고, 결과를 반환한다.
- 만약 반환 값이 없는 경우는 반환 타입을 void로 한다.
- 여러개의 값을 받고 싶다면 배열이나 객체를 사용한다.
- 매개변수는 지역변수다.
- 메서드를 호출하는 쪽에서 주는 값을 메서드에 전달하는 중간 매개체 역할을 한다는 의미로 매개변수라 한다.
return 반환값
- return : 실행 중인 메서드를 종료하고 호출한 곳으로 되돌아간다. 반환타입이 void일 때만 생력이 가능하다.(컴파일러가 자동으로 추가)
- 반환값과 메서드의 반환 타입은 일치해야한다.(자동형변환 가능할 경우 가능),
- 변환값을 저장할 변수와의 타입도 일치해야한다.
기본형 매개변수
매소드의 매개변수의 타입이 기본형인경우
기본형 매개변수 : 변수의 값을 읽기만 할 수 있다.
참조형 매개변수
참조형 매개변수 : 변수의 값을 읽고 변경할 수 있다.
참조형 반환타입
객체의 주소를 반환, 반환하는 변수의 타입과 결과를 받는 변수의 타입은 일치해야한다.
같은 클래스에 있는 메서드는 참조변수.메서드명 에서 참조변수를 생략할 수 있다.
static메서드는 객체의 생성없이 호출이 가능하다.
메서드 호출
// 인수가 필요한 메서드
e.method(2) //메서드 이름(값1, 값2, ....);
class Ex {
static int classV = 2; // 클래스 변수 선언
int instanceV = 2; // 인스턴스 변수 선언
void method() { // iv는 매개변수(parameter)
System.out.println(classV * instanceV);
}
}
public class Main {
public static void main(String[] args) {
Ex e = new Ex();
//입력값을 필요로 하지 않는 메서드 예시
e.method();
}
}
호출 스택
- 메서드 수행에 필요한 메모리가 제공되는 공간이다.
- 메서드가 호출되면 호출스택에 메모리 할당, 종료되면 해제된다.
public class Main {
public static void main(String[] args) {
System.out.println();
}
}
- main() 메서드에서 println() 메서드를 호출
main() 메서드 : 대기 상태
println() 메서드 : 실행 상태
- println() 메서드 실행 종료
println() 메서드 : 제거
main() 메서드 : 실행 상태
- main() 메서드가 실행이 완료되면 호출 스택은 비게 되고 프로그램이 종료된다.
- 아래 있는 메서드가 위의 메서드를 호출한 것, 메서드 간의 호출 관계를 알 수 있다.
- 맨 위의 메서드 하나만 실행 상태, 나머지는 대기 상태이다.
- 스레드당 하나의 스택이 존재한다.
클래스 메서드와 인스턴스 메서드
- 클래스 변수와 인스턴스 변수와 같이 메서드는 클래스 메서드와 인스턴스 메서드 두가지로 나뉜다.
클래스 메서드(static method) : 메서드 앞에 static을 붙인다.
- 인스턴스 생성 없이 '클래스이름.메서드이름()'으로 호출할 수 있다.
- 인스턴스 멤버와 관련없는 작업을 하는 메서드이다.
- 메서드 내에서 인스턴스 변수(iv)와 인스턴스 메서드를 사용/호출할 수 없다.
- 클래스 메서드는 클래스메서드를 호출이 가능하다.
- 지역 변수(매개변수)를 사용할 수 있다.(객체가 필요 없다는 뜻)
인스턴스 변수는 객체를 생성해야 사용이 가능하기 한데 객체가 생성되었는지 알 수 없기 때문에 인스턴스 변수를 사용할 수 없고. 인스턴스 메서드는 인스턴스 변수를 사용하기 때문에 마찬가지로 사용할 수 없다.
인스턴스 메서드(instance method) : 메서드 앞에 static을 붙이지 않는다.
- 인스턴스 생성 후, '참조변수.메서드이름()'으로 호출할 수 있다.
- 인스턴스 멤버와 관련된 작업을 하는 메서드이다.
- 메서드 내에서 인스턴스 변수 사용가능(지역변수를 사용하기 위해 객체의 생성이 필요하다.)
그렇다면 static은 언제 붙여야 할까?
클래스 변수 : 속성(멤버 변수) 중에서 공통된 값을 유지해야 하는 경우 static을 붙인다.
클래스 매서드 : 인스턴스 멤버(인스턴스 변수, 인스턴스 메서드)를 사용하지 않는 메서드에 static을 붙인다.
메서드 오버로딩(overloading)
메서드 오버로딩은 한 클래스 안에 같은 이름의 메서드 여러 개를 중복하여 정의하는 것을 말한다.
원래의 경우 같은 이름의 한 클래스에 메서드는 둘 이상 존재할 수없지만 오버로딩의 성립조건을 만족한다면 같은 이름의 메서들 작성할 수있다.
오버로딩 성립 조건
1. 메서드 이름이 같아야 한다
2. 매개변수의 개수 또는 매개변수의 타입이 달라야 한다.
- 반환타입은 영향이 없다. 매개변수의 개수 와 매개변수의 타입이 같은데 반환타입만 다르다면 오버로딩 X
int add(int x, int y) {return x + y;}
int add(int a, int b) {return a + b;} //오버로딩 X, 매개변수의 이름은 영향 X, 중복정의
long add(int x, int y) {return x + y;} //오버로딩 X, 반환 타입은 영향 X, 중복 정의
int add(int x, int y, int z) {return x + y;} //오버로딩 O, 매개변수의 개수가 다르다.
int add(long x, long y) {return (int)(x + y);} //오버로딩O, 매개변수의 타입이 다르다.
- 매개변수에 전달될 인수를 명확히 해야한다.
class Ex {
//매개변수의 타입이 다르다
long add(int x, long y) {return x + y;}
long add(long x, int y) {return x + y;}
}
public class Main {
public static void main(String[] args) {
Ex e = new Ex();
System.out.println(e.add(3, 3));
// 어떤 메소드를 호출할지 명확하지 않아(ambiguous) 에러가 난다.
//e.add(3L, 3)나 e.add(3, 3L)로 어떤 메서드를 호출하지 명확하게 해야한다.
}
}
- 오버로딩은 매개변수는 다르지만 같은 의미의 기능(작업)을 수행해야 한다.
class Ex {
int add(int x, int y) {return x + y;}
long add(long x, long y) {return x + y;}
int add(int[]a) {
int sum = 0;
for(int i = 0; i < a.length; i++) {
sum +=a[i];
}
return sum;
}
}
생성자(constructor)
자바에서는 객체가 생성과 동시에 인스턴스 변수를 원하는 값으로 초기화할 수 있는 생성자를 제공한다.
- 생성자는 인스턴스가 생성될 때마다 호출되는 인스턴스 변수 초기화 메서드를 의미한다.
- 인스턴스 생성시 수행할 작업에 사용한다.
생성자 규칙
- 생성자의 이름은 클래스 이름과 같아야 한다.
- 객체 생성시 한 번 호출된다.
- 객체가 생성될 때 반드시 호출된다.
- 메서드와 비슷하지만 리턴값이 없다.(void를 붙이지 않는다.)
- 모든 클래스는 반드시 생성자를 가져야 한다.
class Ex {
int x;
//클래스이름(매개변스1,.. , 매개변수n){ ..... }
Ex(){ //기본 생성자, 매개변수가 없고 생성자가 없을 때 컴파일러가 자동으로 추가한다.
//인스턴스 초기화 작업
}
//생성자 오버로딩
Ex(int a){ //매개변수가 있는 생성자
x = a;
}
}
public class Main {
public static void main(String[] args) {
Ex e1 = new Ex(); // 기본 생성자 호출
Ex e2 = new Ex(1); // 매개변수가 있는 생성자 호출
}
}
기본 생성자
모든 클래스에는 반드시 하나 이상의 생성자가 정의되 있어야 하지만 생성자가 하나도 없을 경우 컴파일러가 자동으로 추가한다.
- 매개변수가 없는 생성자
- 클래스명( ) { }
- 대부분의 경우 기본 생성자는 기본적으로 항상 만들어줘야한다.
매개변수가 있는 생성자를 사용하면 객체를 생성과 동시에 초기화 할 수 있다.
class Ex {
int x;
Ex(int a){ // 매개변수가 있는 생성자
x = a; // 객체(iv의 묶음)를 초기화한다.
}
// Ex() {} : 기본 생성자, 클래스이름() {}
}
public class Main {
public static void main(String[] args) {
Ex e = new Ex(); // 기본 생성자 호출, 생성자가 없을 때 컴파일러가 자동으로 추가한다.
// 생성자가 정의되어 있기 때문에 컴파일러가 기본 생성자를 자동으로 추가하지 않아 오류 발생
// 직접 기본 생성자를 추가 해줘야 한다.
}
}
생성자 this()
- 생성자 내부에서 같은 클래스에 있는 다른 생성자를 호출할 때 사용한다.
- 다른 생성자 호출시 첫 줄에서만 사용이 가능하다.
class Ex {
int x;
Ex(){
this(1); // Ex(1)와 같은 의미이다.
}
Ex(int a){
x = a;
}
}
코드의 중복을 제거하기 위해 생성자들 끼리 호출하는 경우가 많다.
class Name {
String firstname;
String lastname;
// 기본 생성자
Name(){
this("GilDong", "Hong");
// Name("GilDong", "Hong")와 같은 의미이다.
// 매개변수가 없음으로 디폴트 값으로 초기화한다는 의미이다.
// firstname = "GilDong";
// lastname = "Hong";
// 위와 같은 방식은 아래의 매개변수가 있는 생성자와 비슷한 문장이다.
// this()를 사용해 매개변수가 있는 생상자를 호출함하여 코드의 중복을 제거한다.(생성자 안의 생성자)
}
// 매개변수가 있는 생성자
Name(String f, String n){
firstname = f;
lastname = n;
}
}
참조변수 this
- 인스턴스(객체) 자신을 가리키는 참조변수, 주소가 저장되어 있다.
- 모든 인스턴스 메서드에는 지역변수로 존재하기 때문에 선언이 필요 없다.
- 인스턴스 메서드(생성자 포함)에서 사용이 가능하다.
- 지역변수와 인스턴스 변수를 구별할 때 사용한다.
같은 클래스에서는 생략이 가능하지만 매개변수(지역변수)와 인스턴스 변수의 이름이 같을 경우 구별해줘야 한다.
class Name {
//인스턴스 변수
String firstname; // this.firstname, this는 생략이 가능하다.
String lastname; // this.lastname
// 매개변수가 있는 생성자, this는 생략이 가능하다.
Name(String firstname, String lastname){
//this가 없다면 모두 지역변수로 취급한다.
this.firstname = firstname; // firstname = firstname;
this.lastname = lastname; // lastname = lastname;
// 이름이 같아 this생략 불가
}
// 인스턴스 메서드, this는 생략이 가능하다.
String FullName(){
return firstname + lastname; // this.firstname + this.lastname
// this는 생략
}
// 클래스 메서드에서는 사용이 불가능하다.
}
변수의 초기화
지역변수는 수동초기화 해야하지만 멤버변수(클래스 변수, 인스턴스 변수)는 자동 초기화 된다.
class Ex {
int x; // 인스턴스 변수로 자동 초기화된다.
static int y; // 클래스 변수로 자동 초기화된다.
void method(){
int z; // 지역변수로 수동 초기화가 필요하다.
int x = z; // 지역변수를 초기화하지 않고 사용하여 에러가 발생한다.
}
}
멤버 변수는 생명주기가 길어 객체 생성시 자동으로 초기화 되지만 지역변수는 메서드가 호출되어 작업하는 동안만 존재하기 때문에(생명주기가 짧음) 자동으로 초기화 하면 성능이 떨어진다 따라서 사용시 새로운 값을 저장하는 방식으로 초기화한다.
멤버변수의 초기화
자동 초기화 이외 3가지 방법이 있다.
1. 명시적 초기화(explicit initialization)(=, 간단 초기화)
- 변수의 선언과 동시에 대입연산자를 사용하여 초기화하는 것.
int x = 4; // 기본형 초기화
Ex e = new Ex(); // 참조형 초기화
2. 초기화 블럭(initialization block)(복잡 초기화)
여러 문장을 사용하여 복합한 방법으로 초기화 할때 사용한다.
인스턴스 초기화 블럭 : { } ( 거의 사용되지 않는다.)
- 인스턴스 초기화 블럭은 생성자와 같이 인스턴스를 생성할 때 마다 수행된다.
클래스초기화 블럭 : static { }
- 클래스 초기화 블럭은 클래스가 메모리에 처음 로딩될 때 한번만 수행,
class Ex {
{/* 초기화 문장*/ } // 인스턴스 초기화 블럭
static {/* 초기화 문장*/} // 클래스 초기화 블럭
}
클럭 내에 실행될 문장을 작성하여 초기화한다.
3. 생성자(복잡 초기화)
인스턴스 변수는 생성자를 사용하여 초기화 한다.
클래스 변수의 초기화 시점 : 클래스가 처음 메모리에 올라갈 때 한번 초기화 된다.
인스턴스 변수 초기화 시점 : 인스턴스가 생성될 때마다 각 인스턴스별로 초기화된다.
class Ex {
static int x = 1;
// 1. 클래스 변수가 메모리에 생성되고 int형의 기본값 0으로 자동 초기화
// 2. 대입 연산자에 의한 명시적 초기화, 1로 초기화.
int y = 1;
// 4. 클래스의 인스턴스가 생성되면서 인스턴스 변수가 int형의 기본값 0으로 자동 초기화
// 5. 대입 연산자에 의한 명시적 초기화, 1로 초기화.
static{x = 2;}
// 3. 클래스 초기화 블럭 static{}을 사용한 클래스 변수 초기화, 2로 초기화
{ y = 2;}
// 6. 초기화 블럭 {}을 사용한 인스턴스 변수 초기화, 2로 초기화
// 7. 생성자를 사용한 인스턴스 변수 초기화, 3으로 초기화
Ex(){
y = 3;
}
}
클래스 초기화 | 인스턴스 초기화 | |||||
기본값 | 명시적 초기화 |
클래스 초기화블럭 |
기본값 | 명시적 초기화 |
인스턴스 초기화블럭 |
생성자 |
x : 0 | x : 1 | x : 2 | x : 2 | x : 2 | x : 2 | x : 2 |
y : 0 | y : 1 | y : 2 | y :3 | |||
1 | 2 | 3 | 4 | 5 | 6 | 7 |
클래스 변수의 초기화가 선행된뒤 클래스 연산자가 초기화 된다.
클래스변수의 초기화 순서 : 기본값 => 명시적초기화 => 클래스 초기화 블럭
인스턴스변수의 초기화순서 : 기본값 => 명시적초기화 => 인스턴스 초기화 블럭 => 생성자
상속(inheritance)
상속이란 기존의 클래스에 기능을 추가하거나 재정의하여 새로운 클래스를 정의하는 것을 의미한다.연관있는 기존클래스에 대해 공통적인 구성요소를 정의하고, 이를 대표하는 클래스를 정의하는 것을 말한다.
상속을 사용하면 기존클래스를 재사용하여 적은양의 코드로 새로운 클래스를 작성이 가능하고 코드를 공통적으로 관리할 수 있어 코드의 추가 및 변경이 매우 용이하다.
이는 코드 재사용성을 높이고, 코드의 중복을 제거하여 프로그램 생산성과 유지보수에 증대에 크게 기여한다.
상속 관계(extends)
기존 클래스를 '조상클래스' 새로운 클래스는 '자손 클래스'라고한다.
기존 클래스 | 상속 받는 클래스 |
부모 클래스(parent class), 상위 클래스(super class), 기초 클래스(base class) |
자식 클래스(child class) , 하위 클래스(sub class), 파생 클래스(derived class) |
서로 같은 클래스를 상속 받은 클래스끼리는 아무런 관계가 성립되지 않고 부모와 자식 관계만이 존재한다.
자바에서는 extends를 사용해 상속관계를 설정할 수 있는데 생성자와 초기화블럭은 상속되지 않고. 멤버(변수, 메서드)만 상속된다.
class 새로운클래스 extends 기존클래스{}
공통된 내용을 하나 이상의 클래스에 중복적으로 추가해야하는 경우, 공통 조상 클래스에 작성하여 코드의 중복을 최소화 해야한다.
class Car {
int door = 4;
private int wheel = 4;
String color;
}
class Pony extends Car{
System.out.println(wheel);
}
class Sonata extends Car{
System.out.println(door);
}
자손 클래스의 인스턴스를 생성한다면 조상 클래스의 멤버와 자손 클래스의 멤버가 합쳐진 하나의 인스턴스로 생성된다.
하지만 위의 예시의 wheel은 private으로 선언되어 접근할 수 없다.
C++와 같은 다른 객체지향 언어에서는 여러 조상 클래스로부터 상속받는 다중 상속을 허용하지만, 자바는 단일 상속만을 허용한다. 같은 이름을 가지만 여러개의 클래스를 상속 받는 경우 어떤 메소드를 사용할지 결정하는데 문제가 생기기 때문이다.
상속 관계에서 자식 클래스를 인스턴스화 하면 부모 클래스가 먼저 인스턴스가 생성되고 자식 클래스의 인스턴스가 생성된다.
다른 클래스로부터 상속받지 않는 모든 클래스는 자동적으로 object클래스를 상속받는다.(컴파일러가 자동으로 추가한다.) 따라서 모든 클래스는 자동으로 Object클래스를 상속받게 된다.
포함관계(composite)
한 클래스의 멤버변수로 다른 클래스 타입의 참조변수를 선언하는 것을 뜻한다.
클래스 간의 관계 결정 | |
상속관계 | ~은 ~이다.(is-a) |
포함관계 | ~은 ~을 가지고 있다.(has-a) |
class Circle {
Point p = new Point();
int r;
}
class Point {
int x;
int y;
}
포함 관계는 위의 예시와 같이 원은 점(중심 좌표 x,y)를 가지고 있기 때문에 포함 관계에 해당한다. 이와 같인 이들을 조합해서 클래스를 만들 때 사용한다고 보면 된다.
오버라이딩(overloading)
오버라이딩은 상속관계에 있는 부모 클래스에서 이미 정의된 메서드를 그대로 사용해도 되지만 필요한 경우 자식 클래스에서 내용만을 변경하여 재정의하는 것을 말한다.
오버라이딩의 조건
1. 메서드 선언부는 기존의 부모 클래스의 선언부와 완전히 일치해야한다.(이름, 매개변수, 반환 타입등) 하지만 메소드의 반환 타입은 부모 클래스의 반환 타입으로 타입 변환할 수 있는 타입이라면 변경할 수 있다.
2. 접근 제어자를 조상 클래스의메서드보다 좁은 범위로 변경할 수 없다.
3. 예외는 조상 클래스의 메서드보다 많이 선언할 수 없다.
4. 인스턴스 메서드를 static 메서드 또는 그 반대로 변경할수 없다.
class Ex {
void print() {
System.out.println("부모 클래스 입니다.");
}
}
class ChildEx {
// 오버라이딩
void print() {
System.out.println("자식 클래스 입니다.");
}
// 오버로딩
void print(String s) {
System.out.println(s + " 예제입니다.");
}
}
public class Main {
public static void main(String[] args) {
ChildEx ce = new ChildEx();
ce.print(); // "자식 클래스 입니다"
ce.print("오버로딩"); // "오버로딩 예제 입니다."
}
}
super
this 키워드가 인스턴스 변수의 이름과 지역 변수의 이름이 같을 경우 구분하는데 사용되었다면 super 키워드는 부모 클래스의 멤버와 자식 클래스의 멤버 이름이 같을 경우 구분하기 위해 사용된다.
이렇게 super 키워드는 부모 클래스로부터 상속받은 멤버(필드나 메소드)를 자식 클래스에서 참조하는 데 사용하는 참조 변수로 super 참조 변수를 사용하여 부모 클래스의 멤버에 접근할 수 있다.
class Ex {
void print() {
System.out.println("부모 클래스 입니다.");
}
}
class ChildEx extends Ex {
void print() {
System.out.println("자식 클래스 입니다.");
super.print();
}
}
public class Main {
public static void main(String[] args) {
ChildEx ce = new ChildEx();
ce.print(); // "자식 클래스 입니다"
}
}
this와 마찬가지로 super 참조 변수를 사용할 수 있는 대상도 인스턴스 메소드뿐이며, 클래스 메소드에서는 사용할 수 없다.
super()
this() 메소드가 같은 클래스의 다른 생성자를 호출할 때 사용되었다면, super() 메소드는 부모 클래스의 생성자를 호출할 때 사용한다.
자손 클래스의 인스턴스 생성시 자손의 멤버와 조상의 멤버가 합쳐진 하나의 인스턴스가 생성되어도 멤버만을 상속 받고 생성자는 상속 받지 못한다. 멤버를 사용하기 위해서는 초기화가 필요한데 부모 클래스의 멤버를 초기화하기 위해서는 자식 클래스의 생성자에서 부모 클래스의 생성자를 호출 해야한다. 이때 조상 클래스의 멤버의 초기화 작업을 수행하기 위해 자손 클래스의 생성자에서 조상 클래스의 생성자를 호출하는데 사용된다.
자손 클래스의 멤버가 조상 클래스의 멤버를 사용할 수 있기 때문에 조상의 멤버들이 먼저 초기화 되어야 하기 때문에 자손 클래스의 생성자의 첫줄에서 조상 클래스의 생성자를 호출해야 한다.
class Ex {
int x, y;
Ex(){
this.x = 10;
this.y = 20;
}
}
class ChildEx extends Ex {
int z;
ChildEx(){
super();
this.z = 30;
}
void method() {
System.out.println("x : "+ x + ", y : " + y+", z : " + z);
// x : 10, y : 20, z : 30
}
}
다른 클래스로부터 상속받지 않는 모든 클래스는 자동적으로 object클래스를 상속받기 때문에 조상 클래스의 생성자의 호출을 반복하다. 최상위 클래스인 Object()까지 가서야 끝이난다.
따라서 object클래스를 제외한 모든 클래스의 생성자는 첫줄에 반드지 자신의 다른 생성자 또는 조상의 생성자를 호출해야한다.(this(), super())
그렇지 않으면 컴파일러는 부모 클래스의 생성자를 명시적으로 호출하지 않는 모든 자식 클래스의 생성자의 첫 줄에 자동으로 super()을 첫줄에 추가하여, 부모 클래스의 멤버를 초기화한다.
class Ex {
int x, y;
Ex(){
this.x = 10;
this.y = 20;
}
}
class ChildEx extends Ex {
int z;
ChildEx(){
//super()을 생략하여도 컴파일러가 추가한다.
this.z = 30;
}
void method() {
System.out.println("x : "+ x + ", y : " + y+", z : " + z);
// x : 10, y : 20, z : 30
}
}
컴파일러는 컴파일 시 클래스에 생성자가 하나도 정의되어 있지 않아야만, 자동으로 기본 생성자를 추가해 주기 때문에 부모 클래스에 매개변수를 가지는 생성자를 하나라도 선언했다면, 부모 클래스에는 기본 생성자가 자동으로 추가되지 않아 부모 클래스의 매개변수에 맞는 super(매개변수1, 매개변수2, ...)을 직접 호출해야 한다.
class Ex {
int x, y;
Ex(int x, int y){
this.x = x;
this.y = y;
}
void method() {
System.out.println("x : "+ this.x + ", y : " + this.y);
}
}
class ChildEx extends Ex {
int z;
ChildEx(int x, int y, int z){
super(x, y);
this.z = z;
}
void method() {
System.out.println("x : "+ x + ", y : " + y+", z : " + z);
}
}
public class Main {
public static void main(String[] args) {
ChildEx ce = new ChildEx(10, 20, 30);
ce.method();
}
}
패키지(package)
패키지란 비슷한 성격의 클래스 파일 의 묶음이다. 클래스 또는 인터페이스를 포함시킬 수 있으며 서로 관련된 클래스들을 묶어놓은 하나의 디렉토리, 클래스는 패키지에 존재하는 클래스파일(.class)을 의미한다. 비슷한 성격의 클래스를 묶어 놓아 클래스를 효율적으로 관리할 수 있으며 .
다른 패키지라는 것은 디렉터리의 경로가 다르다는 것을 의미하며 같은 이름의 클래스라도 다른 패키지라면 유일성을 보장받을 수 있어 클래스명이 겹치는 경우가 발생하더라도 수정해야하는 번거로움을 방지할 수 있다.
클래스의 이름과 쉽게 구분하기 위해 패키지명을 소문자로 하는 것이 원칙이나 대문자 사용이 금지된 것은 아니다.
자바에서는 [패키지명].[하위패키지명].[클래스명]와 같이 패키지는 다른 패키지를 포함할 수 있으며 점 '.'을 구분자로 하여 계층구조로 구성할 수 있다.
하나의 소스파일에는 첫 문장으로 단 한번의 패키지 선언만을 허용한다. 모든 클래스는 반드지 하나의 패키지에 속해야 한다. 패키지의 선언은 package [패키지명]; 통해 하며 소스파일 상 주석과 공백을 제외한 첫번째 문장이어야 한다.
package example;
// package [패키지명];
public class Main {
public static void main(String[] args) {
}
}
소스파일에 포함된 클래스나 인터페이스는 선언된 패키지에 속하게 되고 패키지를 지정하지 않은 클래스는 자동적으로 '이름없는 패키지'(default package)에 속하게 된다.
public class Main {
public static void ain(String[] args) {
}
}
import
클래스의 실제 이름은 [패키지명].[클래스명]로 클래스를사용하기 위해선 패키지 명을 명시해야한다. 같은 패키지 안의 클래스 파일(.class)은 package [패키지명]를 명시한다면 패키지 명을 생략하고 사용할 수 있지만 다른 패키지의 클래스를 사용하려면 import문으로 사용하고자 하는 클래스의 패키지를 미리 명시해야만 소스코드 작성시 클래스 이름에서 패키지 명을 생략할 수 있다.
- import을 통해 컴파일러는 명시된 패키지의 정보를 토대로 소스파일에 사용된 클래스의 패키지를 알아낸 다음 모든 클래스 이름 앞에 패키지 명을 붙여준다.
// import 패키지명.클래스명;
import java.util.Scanner;
// 클래스명 대신 와일드카드(*)을 사용하며 지정된 패키지에 속하는 모든 클래스를 패키지명 없이 사용이 가능하다.
import java.util.*;
// 와일드카드를 사용하더라도 해당 패키지의 하위패키지의 클래스까지 사용할 수 있는 것은 아니다.
java.lang패키지를 사용하려면 import로 패키지를 명시해줘야 하지만 모든 소스파일에는 묵시적으로 improt java.lang이 선언되어 있기 때문에 따로 명시해주지 않아도 된다.(자주사용되는 패키지이기 때문이다.System, String, StringBuffer등)
static import
import 문을 사용하면 클래스의 패키지명을 생략할수 있다면 static import을 사용한다면 static 멤버(클래스 변수, 클래스 메서드)를 호출할 때 클래스 이름을 생략할 수 있다.
import static java.lang.Math.random;
import static java.lang.System.out;
public class main {
public static void main(String[] args) {
// System.out.println()에서 System이 생략되었다.
out.println(random()); // Math.random()에서 Math 클래스가 생략되었다.
}
}
제어자(modifier)
제어자(modifier)란 클래스와 클래스 멤버에 형용사와 같으 부가적인 의미를 부여하는 키워드를 의미한다.
크게 접근 제어자 와 기타 제어자로 나뉜다.
접근제어자 : public protected, (default), private
기타 제어자 : static, final, abstract, native, transient, synchronized, volatile, strictfp .....
기타 제어자는 하나의 대상에 여러 제어자를 같이 사용이 가능하지만 접근 제어자는 하나만 사용이 가능하다.
기타 제어자
static 제어자
클래스의. 공통적인 라는 의미를 가진다.
static 멤버는 메모리의 메서드 영역에 생성되고 인스턴스 멤버는 힙 영역에 생성된다.
제어자 | 대상 | 의미 | |
static | 멤버변수 | - 모든 클래스에 공통적으로 사용되는 클래스 변수가 된다. - 클래스 변수는 인스턴스를 생성하지 않고도 사용이 가능하다. - 클래스가 메모리에 로드될 때 생성된다. |
|
메서드 | - 인스턴스를 생성하지 않고도 호출이 가능한 static 메서드가 된다. - static메서드 내에서는 인스턴스 멤버들을 직접 사용할 수 없다. |
class Ex{
static int cv= 10; // static 변수, 한 클래스에서 생서된 인스턴스들이 공통으로 사용
int iv = 20; // 인스턴스 변수
static int getTestInt() { // static 메서드, 인스턴스 멤버들을 직접 사용할 수 없다
return cv;
}
}
final 제어자
마지막의, 변경될 수 없는 이라는 의미를 가진다.
제어자 | 대상 | 의미 | |
final | 클래스 | - 변경될 수 없는 클래스, 확장될 수 없는 클래스가 된다. - 그래서 final로 지정될 클래스는 다른 클래스의 조상이 될 수 없다. |
|
메서드 | - 변경될 수 없는 메서드, final로 지정된 메서드는 오버라딩을 통해 재정의될 수 없다. | ||
멤버변수 | - 변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가 된다. | ||
지역변수 |
확장될 수 없다는 것은 조상 클래스가 될 수 없다는 것을 의미한다. 상속 계층의 마지막 클래스를 의미(String클래스, Math클래스)
final class Final{ //클래스, 조상이 될 수 없는(상속X)클래스
final int TEST_INT = 10; // 멤버변수, 값을 변경할 수 없는 상수
final int getTestInt() { // 메서드, 오버라이딩할 수 없는 클래스
final int TEST_NUM = TEST_INT ; // 지역변수, 값을 변경할 수 없는 상수
return TEST_NUM;
}
}
접근 제어자(access modifier)
객체 지향에서 정보 은닉(data hiding)이란 사용자가 굳이 알 필요가 없는 정보는 사용자로부터 숨겨야 한다는 개념이다.
자바에서는 접근 제어자(access modifier) 사용하여 외부로부터의 데이터를 보호할수 있다.
class Time{
public int hour; // 0 ~ 23 사이의 값을 가져야 한다.
public int minute; // 0 ~ 59 사이의 값을 가져야 한다.
public int second; // 0 ~ 59 사이의 값을 가져야 한다.
}
public class Main {
public static void main(String[] args) {
Time t = new Time();
t.hour = 26;
}
}
위의 예시와 같이 외부 외부 사용자가 변수로 직접 접근하여 데이터를 바꾸는 경우 잘못 된 데이터로 변경될 수 있다.
class Time{
private int hour; // 0 ~ 23 사이의 값을 가져야 한다.
private int minute; // 0 ~ 59 사이의 값을 가져야 한다.
private int second; // 0 ~ 59 사이의 값을 가져야 한다.
public int gethour() {
return hour;
}
public void sethour(int hour) {
if(hour < 0 || hour > 23) return;
// 메서드를 호출 하여도 범위를 넘어가는 값을 넣는다면
// 데이터에 대한 접근을 허용X(데이터를 보호한다.)
this.hour = hour;
}
}
public class Main {
public static void main(String[] args) {
Time t = new Time();
t.sethour(25);
}
}
접근 제어자(access modifier)를 활용하여 외부에는 불필요한, 내부적으로만 사용되는 부분을 감추고 데이터를 보호할수 있다.(캡슐화/정보 은닉)
- 멤버를 private를 사용하여 멤버에 대한 직접 접근을 방지하고 public 메서드을 사용하여 멤버에 대해 간접 접근을 허용한다.
자바는 정보 은닉을 위해 접근 제어자(access modifier)라는 기능을 제공한다.
- private : 같은 클래스 내에서만 접근이 가능하다.
- (default) : 같은 패키지 내에서만 접근이 가능하다.
- protected : 같은 패키지 내에서, 그리고 다른 패키지의 자손 클래스에서 접근이 가능하다.
- public : 접근 제한이 전혀 없다.
제어자 | 같은 클래스 | 같은 패키지 | 자손 패키지 | 전체 |
public | O | O | O | O |
protected | O | O | O | |
(default) | O | O | ||
private | O |
클래스에는 public과 (default)만 사용이 가능하다. 이처럼 모든 경우 제어자가 사용이 가능한 것은 아니다.
대상에 사용 가능한 제어자여도 조합하여 사용할 수 없는 경우가 있다.
클래스
final과 abstract
메서드
private와 abstract
static과 abstract
메서드의 경우 private와 final는 같이 사용할 필요가 없다. 메서드에 private를 사용하면 같은 클래스의 경우에만 접근이 가능해진다. final을 사용한다면 오버라이딩이 불가능해진다. 같은 클래스에서만 접근이 가능한 클래스를 굳이 final을 명시해줄 필요가 없다.
public
- 접근 제한이 전혀 없다. 어디에서난 접근이 가능하다.
public class Main {
public String text = "퍼블릭 변수입니다.";
public String methode() {
return text;
}
}
private
- 같은 클래스 내에서만 접근이 가능하다.
- 자바에서는 클래스의 멤버 변수들을 private를 사용하여 접근을 제한하고, 메서드를 public으로 하여 메서드를 통해서만 해당 객체의 멤버를 수정하도록 하는 제어 기법을 권장한다(캡슐화).
class Time{
// Time 클래스 내부에서만 접근이 가능하다.
private int hour; // 0 ~ 23 사이의 값을 가져야 한다.
private int minute; // 0 ~ 59 사이의 값을 가져야 한다.
private int second; // 0 ~ 59 사이의 값을 가져야 한다.
public int gethour() {return hour;}
public int getminute() {return minute;}
public int getsecond() {return second;}
public void sethour(int hour) {
// 올바르지 않는 입력값이라면 변수의 값이 변경되지 않는다.
if(hour < 0 || hour > 23) return;
this.hour = hour;
}
}
public class Main {
public static void main(String[] args) {
Time t = new Time();
t.sethour(25); // 올바른 값이이 아니다.
System.out.println(t.gethour()); //값이 변경되지 않는다.
}
}
(default)
- 같은 패키지 내에서만 접근이 가능하다.
- 접근 제어자를 명시하지 않을경우에 해당한다.
package example;
public class Ex{
int dft;
public int pub;
}
package test;
import example.Ex;
public class Test {
public static void main(String[] args) {
Ex ex = new Ex();
System.out.println(ex.pub); // public으로 선언되어 있어 접근이 가능하다.
System.out.println(ex.dft); // 다른 패키지에 있기 때문에 접근이 불가능하다.
}
}
protected
- 같은 패키지 내에서, 그리고 다른 패키지의 자손 클래스에서 접근이 가능하다.
- 다른 패키지에서는 default와 마찬가지로 접근이 불가능하지만, 상속을 할 경우 접근이 가능해진다.
package example;
public class Ex{
int dft;
protected int prt;
public int pub;
}
package test;
import example.Ex;
public class Test extends Ex {
public static void main(String[] args) {
Ex ex = new Ex();
System.out.println(ex.pub); // public으로 선언되어 있어 접근이 가능하다.
System.out.println(ex.dft); // 다른 패키지에 있기 때문에 접근이 불가능하다.
System.out.println(ex.prt); // 상속을 사용하여 접근이 가능해졌다.
}
}
다형성(polymorphism)
여러 가지 형태를 가질 수 있는 능력으로 조상 타입 참조 변수로 자손 타입 객체를 다루는 것을 말한다.
조상타입 참조변수 = new 자손생성자; // 조상타입 참조변수 = 자손타입 객체, 허용된다.
자손타입 참조변수 = new 조상생성자; // 에러, 허용되지 않는다.
참조변수의 타입과 인스턴스의 타입은 일치하는 것이 보통이지만 일치 하지 않을 수도 있다. 일치하지 않을 경우 조상 타입의 참조변수가 자손 타입의 객체를 참조할 수 는 있지만 자손 타입의 참조변수로 조상 타입의 객체를 가리킬 수 없다. 자손 타입의 멤버가 조상 타입의 객체에 존재하지 않아 에러가 발생할 수 있기 때문이다.
객체와 참조변수의 타입이 일치할 때는 객체의 멤버 전부를 다룰 수 있지만 객체와 참조변수의 타입이 일치하지 않을 경우,조상 타입의 멤버만을 다룰 수 있다.
참조변수의 형변환
기본형의 형변환은 실제값이 달라지지만 참조변수의 형변환은 사용할 수 있는 멤버의 갯수를 조절하는 것이다.
- 참조변수의 주소값이나 객체에 대한 변화는 없다.
- 조상-자손 관계의 참조변수는 서로 형변환이 가능하다.
- 형변환 연산자는 실제로 하는 일은 없지만 단순히 타입을 일치시키는 역할을 한다.
class Time{
int hour;
int minute;
int second;
}
class Date extends Time{
int year;
int day;
int month;
}
public class Main {
public static void main(String[] args) {
Date d = new Date();
Time t = d; // (Date)d, 자손 to 조상으로의 형변환 연산자는 생략이 가능하다.
Date d2 = (Date)t; // 조상 to 자손으로의 형변환 연산자는 생략이 불가능하다.
// d, t, d2모두 같은 객체의 주소를 가지고 있지만 사용할 수 있는 멤버의 갯수가 다르다.
// System.out.println(t.year); 오류 발생
System.out.println(d.year);
}
}
실제 객체보다 자손 클래스로의 형변환은 불가능하다. (java.lang.ClassCastException오류 발생) 컴파일러는 형변환을 명시만 해준다면 오류가 발생하지 않지만 형변환을 실행하게 된다면 오류가 발생한다.
public class Main {
public static void main(String[] args) {
Time t = new Time(); // 조상 클래스 t의 인스턴스를 생성한다.
Date d = (Date)t; // t의 인슨턴스를 자손 클래스로 형변환시 오류가 발생한다.
// 실제 객체의 범위를 넘어간다.
}
}
instanceof 연산자를 사용하면 참조변수의 형변환 가능 여부를 확인하는데 사용한다, 형변환이 가능하면 true를 반환한다.
public class Main {
public static void main(String[] args) {
Date d = new Date();
// 잠조변수 instanceof 클래스
System.out.println(d instanceof Time);
System.out.println(d instanceof Date);
// 자기 자신과 상속 계층상 조상일 경우 true를 반환한다.
// 이는 형변환이 가능함을 의미하며 형변환 전 필수로 확인해야 한다.
}
}
매개변수의 다형성
참조형 매개변수는 메서드 호출시, 자신과 같은 타입 또는 자손타입의 인스턴스를 넘겨줄 수 있다. 다형적 매개변수는 메서드의 매개변수로 조상 타입의 참조변수를 사용해 하나의 메서드로 여러 타입의 참조변수를 처리할 수 있다.
class Product{
int price;
int category;
Product(int price){
this.price = price; //price의 값을 매개변수로 받은 값으로 초기화 한다.
}
}
class Pc extends Product{
Pc(){ //Pc의 객체개 생성되면 생성자가 Product 클래스의 int형 매개변수가 있는 생성자를 호출한다
super(100);
}
}
class Tv extends Product{
Tv(){
super(200);
}
}
class Buyer{
int money = 1000;
void buy(Product p) {
money -= p.price;
}
}
public class Main {
public static void main(String[] args) {
Buyer by = new Buyer();
Pc pc = new Pc();
Tv tv = new Tv();
// buy 메서드의 참조형 참조변수 p는 자신과 같은 타입 또는 자손타입의 인스턴스를 받을 수 있다.
by.buy(pc); // Product p = new Pc();
by.buy(tv); // Product p = new tv();
}
}
하나의 배열로 여러 종류의 객체 다루기
조상타입의 배열에 자손들의 객체를 저장할 수 있다.
class Product {}
class Pc extends Product {}
class Tv extends Product {}
public class Main {
public static void main(String[] args) {
Product p[] = new Product[2];
p[0] = new Pc();
p[1] = new Tv();
}
}
'Java' 카테고리의 다른 글
[JAVA] 인터페이스(Interface) (1) | 2023.09.19 |
---|---|
[JAVA] 추상 클래스(Abstract class) (0) | 2023.09.02 |
[JAVA] 배열(Array) (0) | 2023.08.16 |
[JAVA] 조건문과 반복문 (0) | 2023.08.13 |
[JAVA] 연산자(operator) (0) | 2023.08.05 |