일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- 주린이
- javascript
- 무디스
- 프로그래머스
- XLF
- 배당성장
- 기업분석
- 주식
- 금리인하
- 접근제어자
- 현금흐름표
- StringBuffer
- etf
- 제태크
- 객체지향
- FCF
- 알고리즘
- Java
- 오버라이딩
- 백준
- 자바
- S&P500
- 인플레이션
- object
- 다형성
- 미국주식
- 그리디 알고리즘
- 잉여현금흐름
- mco
- 금리인상
- Today
- Total
오늘의하루
Java 객체지향 공부 다형성, interface, 상속, 포함 등 본문
Java에서 가장 중요한 개념인 객체지향에 대해 공부한 자료입니다.
객체지향 개념
코드의 재사용성을 높이고 코드의 관리를 수월하게 하며 신뢰성이 높은 프로그램의 개발을 가능하게 만들어준다.
클래스와 객체의 정의
- 클래스란 객체를 정의해 놓은 것을 말한다.
- 객체란 실제로 존재하는 개념이다.
- 객체는 인스턴스를 포함하는 일반적인 의미이다.
- 인스턴스화란 클래스로부터 인스턴스를 생성하는 것을 말한다.
객체의 구성 요소
객체에는 속성(변수)과 기능(메서드)으로 이루어져 있다.
- 객체에 있는 속성과 기능을 객체의 멤버라고 부른다.
인스턴스 생성과 사용
인스턴스를 생성하는 방법은 일반적으로 "클래스명 참조 변수명 = new 클래스명()"이다.
Tv t; // 객체를 다루기 위한 참조변수를 선언
Tv t = new Tv(); // 객체 생성 후, 생성된 객체의 주고를 참조변수에 저장
인스턴스를 사용하는 방법은 "참조 변수명. 객체 멤버"이다.
class Tv{
int channel; // 변수
void channelUp(){ // 메서드
channel++; // 변수 channel을 1 증가시킨다.
}
}
Tv t = new Tv(); // 인스턴스 생성 후 생성된 인스턴스의 주소를 참조변수 t에 저장한다.
t.channel = 7; // 생성된 인스턴스의 channel 변수에 7을 대입
t.channelUp(); // 생성된 인스턴스의 channelUp() 메서드를 사용
참조 변수와 인스턴스의 관계
여러 개의 참조 변수가 하나의 인스턴스 주소를 가지고 있는 건 가능하지만 하나의 참조 변수가 여러 개의 인스턴스 주소를 가지고 있는 건 불가능하다.
클래스의 정의
클래스란 데이터와 함수의 결합(구조체 + 함수)을 의미하며, 서로 관련된 값을 묶어서 하나의 타입으로 정의합니다.
선언 위치에 따른 변수의 종류
변수는 선언되는 위치에 따라 종류와 범위(scope)가 결정된다.
class Test{
// 클래스 영역 시작
int iv; // 인스턴스 변수
static int cv; // 클래스 변수
// 메서드 영역 시작
void method(){
int lv = 0; // 지역 변수
}
// 메서드 영역 끝
// 클래스 영역 끝
}
- 클래스 변수
- 클래스 영역에서 선언되며 클래스가 메모리에 올라갈 때 생성된다.
- 인스턴스 생성 없이 "클래스명. 클래스 변수명"으로 접근 및 사용이 가능하다.
- 클래스가 로딩될 때 생성되며 프로그램이 종료될 때 소멸한다.
- 인스턴스 변수
- 클래스 영역에서 선언되며 인스턴스가 생성될 때 같이 생성된다.
- 인스턴스 생성 후 "참조 변수명. 인스턴스 변수명"으로 접근 및 사용이 가능하다.
- 생성되는 인스턴스마다 각기 다른 값을 유지할 수 있다.
- 지역변수
- 메서드 영역에서 선언되며 메서드가 호출될 때 생성된다.
- 메서드 종료 시 함께 소멸된다.
메서드(Method)
메서드란 작업을 수행하기 위한 명령문의 집합이며 어떤 값을 입력받아서 처리하고 그 결과를 돌려준다.
- 하나의 메서드는 한 가지의 기능만 수행하도록 작성하는 것이 좋다.
- 메서드는 선언부와 구현부로 나뉘며 클래스 영역에만 정의할 수 있다.
- 호출 방법은 "참조 변수. 메서드명(매개변수)"이다.
리턴타입 메서드명(매개변수...){ // 선언부
메서드 호출 시 수행될 코드 // 구현부
}
int add(int a, int b){
return a+b;
}
return문
현재 실행 중인 메서드를 종료하고 호출한 메서드로 돌아간다.
- 반환 값이 없는 경우 "return;"만 작성해주면 된다.
- 메서드의 리턴 타입이 void인 경우는 return문을 생략할 수 있다.
- 반환 값이 있는 경우 "return 반환 값;"으로 작성해주면 된다. ( 필수 )
- 메서드의 리턴 타입과 반환 값의 타입이 일치해야 한다.
JVM의 메모리 구조
- 메서드 영역(Method Area)
- 클래스 멤버의 정보가 저장되는 곳이다.
- 호출 스택(Call Stack)
- 메서드의 작업공간이다.
- 메서드가 호출되면 필요한 메모리 공간을 할당받는다.
- 메서드가 종료되면 사용하던 메모리를 반환한다.
- 힙(Heap)
- 인스턴스가 생성되는 공간이다.
- new 연산자에 의해 생성되는 배열과 객체는 모두 여기에 생성된다.
호출 스택에 대해 좀 더 자세한 내용은 아래 글에 작성되어 있다.
기본형 매개변수와 참조형 매개변수
기본형 매개변수의 경우 변수의 값을 읽기만 가능하고, 참조형 매개변수의 경우 변수의 값을 읽기 및 변경까지 가능하다.
class Study{
public static void method(String[] args){
Test t = new Test();
t.x = 10;
System.out.println("Test의 t값은 = " + t.x); // 10
change(t.x); // 1000
System.out.println("Test의 t값은 = " + t.x); // 10
change2(t); // 1000
System.out.println("Test의 t값은 = " + t.x); // 1000
}
static void change(int x){ // 기본형 매개변수
x = 1000;
System.out.println("change() t.x = " + t.x);
}
static void change2(Test t){
t.x = 1000;
System.out.println("change2() t.x = " + t.x);
}
}
class Test{
int x;
}
클래스 메서드와 인스턴스 메서드
인스턴스 메서드
- 인스턴스 생성 후 "참조 변수명. 메서드명(매개변수)"로 호출이 가능하다.
- 인스턴스 변수 또는 인스턴스 메서드와 관련된 작업을 하는 메서드이다.
- 메서드 내부에서 인스턴스 멤버 및 클래스 멤버를 사용할 수 있다.
클래스 메서드
- 인스턴스 생성 없이 "클래스명. 메서드명(매개변수)"로 호출이 가능하다.
- 인스턴스 변수 또는 인스턴스 메서드와 관련 없는 작업을 하는 메서드이다.
- 클래스 메서드 내부에서는 인스턴스 멤버 사용이 불가능하지만 클래스 멤버는 사용 가능하다.
class Test{
long a, b; // 인스턴스 변수
long add1(){ // 인스턴스 메서드
long sum = add2(100L, 200L); // 클래스 메서드 호출
return sum+a+b;
}
static long add2(long x, long y){ // 클래스 메서드
return x+y;
}
}
// 클래스 메서드 호출
System.out.println(Test.add2(300L,400L)); // 700L
Test t = new Test();
t.a = 100L;
t.b = 200L;
// 인스턴스 메서드 호출
System.out.println(t.add1()); // 600L
메서드 오버 로딩(Method overloading)
메서드 오버 로딩은 아래에 있는 조건을 만족하는 같은 이름의 메서드를 여러 개 정의한 것을 말한다.
- 메서드명이 같아야 한다.
- 매개변수의 개수 또는 타입이 달라야 한다.
- 리턴 타입은 오버 로딩에 아무런 영향을 주지 못한다.
int add(int a, int b){return a+b;}
int add(int x, int y){return a+b;} // X 중복 정의
long add(int a, int b){return (long)(a+b);} // X 리턴타입은 오버로딩에 영향을 주지 못한다.
long add(long a, int b){return a+b;} // O 오버로딩
생성자(Constructor)
생성자란 인스턴스가 생성될 때마다 호출되는 인스턴스 초기화 메서드를 말한다.
- 모든 클래스에는 반드시 하나 이상의 생성자가 있어야 한다.
- new 연산자에 의해 메모리(heap)에 해당 클래스의 인스턴스가 생성된다.
- 생성자 "클래스명()"가 호출되어 수행한다.
- new 연산자의 결과로 생성된 해당 클래스의 인스턴스 주소가 반환되어 참조 변수에 저장된다.
- 생성자의 이름은 클래스명과 같아야 한다.
- 기본 생성자란 매개변수가 없는 생성자를 말한다.
- 클래스에 생성자가 하나도 없을 때 컴파일러가 기본 생성자를 자동으로 추가해준다.
- 기본 생성자의 형태는 "클래스명(){}"이다.
class Test1{
int x;
// Test1(){}; >> 컴파일 시 자동으로 기본생성자가 생성된다.
}
class Test2{
int x;
Test2(int num){ // 매개변수 있는 생성자
x = num;
}
}
Test1 t1 = new Test1(); // ok
Test2 t2 = new Test2(); // Error!
// 이걸 쓰고 싶으면 해당 클래스안에 기본생성자를 하나 만들어줘야 한다.
Test2 t3 = new Test2(1); // ok
다른 생성자를 호출하는 "this()"
"this()"는 같은 클래스의 다른 생성자를 호출할 때 사용되며 생성자의 첫 문장에서만 사용 가능합니다.
class Car{
String color;
int door;
Car(){
this("white", 4); // "this()" 사용
// Car("white", 4)를 의미합니다.
}
Car(String c, int d){
color = c;
door = d;
}
}
참조 변수 this
this는 인스턴스 자신을 가리키는 참조 변수이며, 인스턴스의 주소가 저장되어 있다.
- 모든 인스턴스 메서드에 지역변수로 숨겨진 채로 존재한다.
class Car{
String color;
int door;
Car(){
this("white", 4);
}
Car(String color, int door){
this.color = color;
this.door = door;
}
}
변수의 초기화
변수를 선언하고 처음으로 값을 저장하는 것을 말하며 멤버 변수와 배열은 각 타입의 기본값으로 자동 초기화되므로 초기화를 생략할 수 있다.
- 지역변수의 경우 사용 전 필수로 초기화를 해줘야 한다.
class Test{
int x;
void method(){
int lv1; // 지역변수
int lv2 = lv1; // Error! 지역변수를 초기화 하지 않고 사용했다.
}
}
자료형과 기본값 알아보기
- boolean = false
- char = '\u0000' (null)
- byte = 0
- short = 0
- int = 0
- long = 0L
- float = 0.0f
- double = 0.0
- 참조형 변수 = null
멤버 변수의 초기화 방법
- 명시적 초기화
class Test{
int door = 4;// 기본형 변수의 초기화
Engine e = new Engine(); // 참조형 변수의 초기화
}
- 생성자
class Test{
int door;
Stirng color;
Test(int door, String color){ // 생성자
this.door = door;
this.color = color;
}
}
- 초기화 블록
- 인스턴스 초기화 블록 : {}
- 생성자에서 공통적으로 수행되는 작업에 사용된다.
- 인스턴스 생성될 때마다 생성자보다 먼저 실행된다.
- 클래스 초기화 블록 : static {}
- 클래스 변수의 복잡한 초기화에 사용되며 클래스가 로딩될 때 실행된다.
- 인스턴스 초기화 블록 : {}
class Test{
static int[] arr = new int[10]; // 명시적 초기화
static{ // 클래스 초기화 블록
for(int i = 0; i < arr.length; i++){
arr[i] = (int)(Math.random()*10)+1;
}
}
}
멤버 변수의 초기화 시기와 순서
class Test{
static int cv = 1; // 명시적 초기화
int iv = 2; // 명시적 초기화
static {cv = 3;} // 클래스 초기화 블록
{iv = 4} // 인스턴스 초기화 블록
Test(){ // 생성자
iv = 5;
}
}
- 클래스 변수 cv는 int형의 기본값 0
- 클래스 변수 cv는 명시적 초기화로 1
- 클래스 변수 cv는 초기화 블록에 의해 3
- 인스턴스 변수 iv는 int형의 기본값 0
- 인스턴스 변수 iv는 명시적 초기화로 2
- 인스턴스 변수 iv는 초기화 블록에 의해 4
- 인스턴스 변수 iv는 생성자에 의해 5
상속(inheritance)
상속이란 기존의 클래스를 재사용해서 새로운 클래스를 작성하는 것이다.
- 두 클래스를 부모와 자식으로 관계를 맺어주는 것이다.
- 자식 클래스는 부모 클래스의 모든 멤버를 상속받는다.(단, 생성자와 초기화 블록은 제외)
- 부모 클래스의 변경은 자식 클래스에게 영향을 주지만 자식 클래스의 변경이 부모 클래스에게 영향을 주지 못한다.
- 사용 방법은 "자식 클래스 extends 부모 클래스"이다.
- 상속은 단일 상속만 가능하다.
class Point{
int x;
int y;
}
class Point3D extends Point{
int z;
}
포함(compsite)
포함이란 한 클래스의 멤버 변수로 다른 클래스를 선언하는 것을 말한다.
class Point{
int x;
int y;
}
class Circle{
Point p = new Point(); // 포함
int r;
}
상속과 포함 구분하기
상속과 포함 둘 중 어느 것을 사용하는 게 좋을지를 구분해보자
- 상속관계 : ~은 ~이다. (~is ~a)
- 예: Circle은 Shape이다.
- 포함관계 : ~은 ~을 가지고 있다. (~has ~ a)
- 예: Circle은 Point을 가지고 있다.
모든 클래스의 최고 조상 Object 클래스
조상이 없는 클래스들은 자동적으로 Object 클래스를 상속받는다.
- Object 클래스에 있는 11개의 메서드를 상속받는다.
- 예 : toString(), equals(Object obj), hashCode() 등등
오버 라이딩 (Overriaing)
부모 클래스로부터 상속받은 메서드의 구현부를 상속받은 자식 클래스에 맞게 변경하는 것을 말합니다.
- 오버 로딩과 아무런 관계가 없는 것이다.
- 선언부가 같아야 한다. (메서드명, 매개변수, 리턴 타입)
- 접근 제어자가 좁은 범위로 변경할 수 없다.
- 접근 제어자 범위 순서: public > protected > default > private
- 부모 클래스의 메서드보다 많은 수의 예의를 선언할 수 없다.
class Point{
int x;
int y;
String getLocation(){
return "x : " + x + " y : " + y;
}
}
class Point3D extends Point{
int z;
String getLocation(){ // 오버라이딩
return "x : " + x + " y : " + y + " z : " + z;
}
}
참조 변수 super
super는 this와 비슷하지만 부모의 멤버와 자신의 멤버를 구별하기 위해 사용됩니다.
class Parent{
int x = 10;
String getLocation(){
return "x = " + x;
}
}
class Child extends Parent{
int x = 20;
int y = 30;
void method(){
System.out.println("x = " + x); // 가까운 x 20
System.out.println("this.x = " + this.x); // 자기 자신 x 20
System.out.println("super.x = " + super.x); // 부모의 x 10
}
String getLocation(){ // 오버라이딩
return super.getLocation() + " y = " + y; // 부모 메서드 호출
// x = 10, y = 30
}
}
부모 클래스의 생성자 호출 "super()"
상속을 받은 자식 클래스의 인스턴스를 생성할 때 부모의 멤버들도 같이 초기화해줘야 한다.
class Point{
int x;
int y;
Point(){
this(0,0);
}
Point(int x, int y){
this.x = x;
this.y = y;
}
}
class Point3D extends Point{
int z;
Point3D(int z){
super();
this.z = z;
} // 결과 x = 0 y = 0 z = z
// 또는
Point3D(int x, int y, int z){
super(x,y);
this.z = z;
} // 결과 x = x y = y z = z
}
패키지 (Package)
서로 관련된 클래스와 인터페이스의 묶음을 의미합니다.
- 클래스가 물리적으로 클래스 파일("*. class")인 것처럼, 패키지는 물리적으로 폴더이다.
- 패키지는 서브 패키지를 가질 수 있으며 "."으로 구분한다.
- 클래스의 실제 이름(full Name)은 패키 지명이 포함되어 있다.
- String 클래스의 실제 이름은 java.lang.String이다.
- rt.jar는 Java API의 기본 클래스들을 압축한 파일이다.
- 패키지 선언은 소스파일의 가장 첫 번째 문장으로 단 한번 선언한다.
- 하나의 소스파일에 두 개 이상의 클래스가 포함된 경우 모두 같은 패키지에 속하게 된다.
- 모든 패키지는 하나의 패키지에 속하며, 패키지가 선언되지 않으면 자동적으로 이름 없는 패키지에 속한다.
import문
사용할 클래스가 속한 패키지를 지정하는 데 사용된다.
- import문을 사용하면 클래스를 사용할 때 패키 지명을 생략할 수 있다.
class Test{
java.util.Date today = new java.util.Date();
}
import java.util.Date;
class Test{
Date today = new Date();
}
- java.lang 패키지는 import하지 않아도 패키 지명 생략이 가능하다.
- import문은 패키지 문과 클래스 선언의 사이에서 선언된다.
- import문을 선언하는 방법
- import 패키 지명. 클래스명;
- 패키지에 있는 클래스는 패키 지명을 생략할 수 있다.
- import 패키 지명.*;
- 패키지 안에 있는 모든 패키 지명을 생략할 수 있다.
- import 패키 지명. 클래스명;
- import문은 컴파일 시에 처리되므로 프로그램의 성능에는 아무런 영향을 주지 않는다.
- 다른 패키지에 같은 이름의 클래스가 있다면 꼭 패키지 명을 작성해주는 게 좋다.
import java.sql.*;
import java.util.*;
java.util.Date today = new java.util.Date();
제어자(modifier)
제어자란 클래스, 변수, 메서드의 선언부에 사용되어 부가적인 의미를 부여합니다.
- 제어자는 크게 접근제어자와 그 외 제어자로 나뉩니다.
- 접근제어자 : public, protected, default(생략 가능), private
- 그 외 제어자 : static, final, abstract, native, transient, synchronized, volatile, strictfp
Static - 공통적인
static이 사용될 수 있는 곳은 멤버 변수, 메서드, 초기화 블록이다.
- static 멤버 변수
- 모든 인스턴스에 공통적으로 사용되는 클래스 변수가 된다.
- 클래스 변수는 인스턴스 생성 없이 "클래스명. 변수명"으로 사용이 가능하다.
- 클래스가 메모리에 로딩될 때 생성된다.
- static 메서드
- 인스턴스 생성 없이 "클래스명. 메서드명(매개변수)"로 사용 가능하다.
- static 메서드 내부에서는 인스턴스 멤버를 사용할 수 없다.
final - 마지막의
final이 사용될 수 있는 곳은 클래스, 메서드, 멤버 변수, 지역변수이다.
- final 클래스
- 부모 클래스가 될 수 없다.
- final 메서드
- 오버 라이딩을 할 수 없다.
- final 멤버 변수, final 지역변수
- 값을 변경할 수 없는 상수가 된다.
- 상수는 선언과 동시에 초기화해주는 게 좋다.
- 인스턴스 변수의 경우는 생성자를 통해 상수를 초기화할 수 있다.
abstract - 추상의
abstract가 사용될 수 있는 곳은 클래스, 메서드이다.
- abstract 클래스
- 추상 클래스라고 부르며 추상 메서드가 선언된 클래스를 의미합니다.
- abstract 메서드
- 추사 메서드라고 부르며 선언 부만 작성하고 구현부가 없는 메서드를 의미합니다.
접근제어자
멤버 또는 클래스에 사용되어 외부로부터의 접근을 제한하는 역할을 합니다.
- public = 클래스, 멤버 변수
- 접근 제한이 없다.
- protected = 멤버 변수
- 같은 패키지 내부와 다른 패키지의 자식 클래스에서 접근이 가능하다.
- default(생략 가능) = 클래스, 멤버 변수
- 같은 패키지 내부에서만 접근이 가능하다.
- private = 멤버 변수
- 같은 클래스 내에서만 접근이 가능하다.
class Time{
private int hour; // 같은 클래스에서만 접근가능
Time(int hour){
setHour(hour);
}
public int getHour(){return hour;}
public void setHour(int hour){
if(hour < 0 && hour > 23){
return;
}
this.hour = hour;
}
}
Time t = new Time(); // Error! 기본생성자가 없다.
// 기본생성자가 있더라도 t.hour은 불가능하며 setHour 메서드를 통해서만 가능하다.
Time t = new Time(4); // Ok
생성자와 접근 제어자
일반적으로 생성자의 접근 제어자는 클래스의 접근 제어자와 일치한다.
- 생성자에 접근 제어자를 사용함으로써 인스턴스의 생성을 제한할 수 있다.
class Test{
private static Test t;
private String name;
private Test(){
name = "Java";
}
public static Test getInstance(int a){
if (a > 0){
t =new Test();
}
return t;
}
public void getname(){
System.out.println("Access Successful! name = " + name);
}
}
Test t1 = new Test();
// Error! 생성자는 private 접근제어자이기 때문에 다른 클래스에서는 사용할 수 없다.
Test t2 = Test.getInstance(3); // 메서드를 통해서 인스턴스를 생성한다.
t2.getname();
Test t3 = Test.getInstance(-1);
t3.getname();
System.out.println(t2); // Access Successful! name = Java
System.out.println(t3); // Access Successful! name = Java
제어자의 조합
대상 | 사용가능한 제어자 |
클래스 | public, (default), final, abstract |
메서드 | 모든 접근 제어자, final, abstract, static |
멤버 변수 | 모든 접근 제어자, final, static |
지역 변수 | final |
- 메서드에 static과 abstract를 함께 사용할 수 없다.
- static 메서드는 구현부가 있는 메서드만 사용할 수 있기 때문이다.
- 클래스에 abstract와 final을 동시에 사용할 수 없다.
- 클래스에 사용되는 final은 부모 클래스가 될 수 없다는 의미이며, abstract는 상속을 통해서 완성해야 하는 의미이기 때문에 서로 모순된다.
- abstract 메서드의 접근제어자가 private일 수 없다.
- abstract 메서드는 자식 클래스에서 구현해줘야 하는데 private이면 접근할 수 없기 때문이다.
- 메서드에 private과 final을 같이 사용할 필요는 없다.
- private인 메서드는 오버 라이딩할 수 없는데 final도 오버 라이딩할 수 없기 때문에 중복된다.
다형성(polymorphism)
부모 타입의 참조 변수로 자식 타입의 객체를 다룰 수 있는 것이 다형성이다.
- 반대로 자식 타입은 부모 타입의 인스턴스를 참조할 수 없다.
class Tv{
int channel;
void channelUp(){ channel++; }
}
class SmartTv extends Tv{
String text;
void caption(){ System.out.println(text); }
}
Tv t1 = new Tv();
Tv t2 = new SmartTv();
t2.text = "Java"; // Error!
// 실제 인스턴스는 SmartTv타입이지만 참조변수의 타입은 Tv이기 때문에 text를 사용할 수 없다.
참조 변수의 형 변환
서로 상속관계에 있는 타입 간의 형 변환만 가능하다.
class Car{
String color;
int door;
void drive(){ System.out.println("drive, Brr~"); }
void stop(){ System.out.println("stop!!!"); }
}
class FireEngine extends Car{
void water(){ System.out.println("water!!!"); }
}
Car car = null;
FireEngine fe1 = new FireEngine();
FireEngine fe2 = null;
fe1.water(); // water!!!
car = fe1; // Car car = (Car)fe1;
// car.water(); >> Error!!!
fe2 = (FireEngine)car; // FireEngine fe2 = (FireEngine)car;
fe2.water(); // water!!!
instanceof 연산자
참조 변수가 참조하는 인스턴스의 실제 타입을 체크하는 데 사용한다.
- instanceof의 연산 결과가 true이면 해당 타입으로 형 변환이 가능하다.
- A instanceof B = B타입이 A의 실제 인스턴스의 타입의 부모라면 true를 반환한다.
class Car{}
class FireEngine extends Car{}
Car car = new Car();
FireEngine fe = new FireEngine();
Object obj = new Object();
System.out.println(fe instanceof Car); // true
System.out.println(fe instanceof FireEngine); // true
System.out.println(car instanceof FireEngine); // false
System.out.println(car instanceof Object); // true
System.out.println(obj instanceof Car); // false
참조 변수와 인스턴스 변수의 연결
- 멤버 변수가 중복 정의된 경우, 참조 변수의 타입에 따라 연결되는 멤버 변수가 달라진다.
- 참조 변수 타입에 영향을 받는다.
- 메서드가 중복 정의된 경우, 참조 변수의 타입에 관계없이 항상 실제 인스턴스의 타입에 정의된 메서드가 호출된다.
- 참조 변수 타입에 영향을 받지 않는다.
class Parent{
int x = 100;
void method(){
System.out.println("Parent method");
}
}
class Child extends Parent{
int x = 200;
void method(){
System.out.println("Child method");
}
}
Parent p = new Child();
Child c = new Child();
System.out.println("p.x = " + p.x); // 100 변수는 참조변수의 타입에 따라 연결된다.
p.method(); // Child method 메서드는 실제 생성된 인스턴스의 타입에 정의된 메서드가 호출된다.
System.out.println("c.x = " + c.x); // 200 변수는 참조변수의 타입에 따라 연결된다.
c.method(); // Child method 메서드는 실제 생성된 인스턴스의 타입에 정의된 메서드가 호출된다.
매개변수의 다형성
참조형 매개변수는 메서드 호출 시 자신과 같은 타입 또는 자식 타입의 인스턴스를 넘겨줄 수 있다.
class Product{
int price;
}
class Tv extends Product{}
class Computer extends Product{}
class Buyer{
int money = 100;
void buy(Product p){
money = money - p.price;
}
}
Product tv = new Tv();
tv.price = 10;
Product computer = new Computer();
computer.price = 20;
Buyer b = new Buyer();
b.buy(tv); // buy 메서드의 매개변수 Product가 tv의 부모이기 때문에 가능하다.
b.buy(computer); // buy 메서드의 매개변수 Product가 computer의 부모이기 때문에 가능하다.
System.out.println(b.money); // 70
여러 종류의 객체를 하나의 배열로 다룰 수 있다.
부모 타입의 배열에 자식들의 인스턴스를 담을 수 있다.
class Product{
int price;
}
class Tv extends Product{}
class Computer extends Product{}
class Buyer{
int money = 100;
int i = 0;
Product[] cart = new Product[10]; // 여러 객체들을 담을 하나의 배열 cart를 만들었다.
void buy(Product p){
if (money < p.price){
System.out.println("잔액부족");
return;
}
money = money - p.price;
cart[i++] = p; // cart 배열에 하나씩 객체를 p타입의 객체를 넣는다.
}
}
Product tv = new Tv();
tv.price = 10;
Product computer = new Computer();
computer.price = 20;
Buyer b = new Buyer();
b.buy(tv);
b.buy(computer);
for (int i = 0; i < 10; i++){
System.out.println(b.cart[i]);
}
추상 클래스 (abstract class)
클래스가 설계도라면 추상 클래스는 미완성된 설계도입니다.
- 일반 메서드가 추상 메서드를 호출할 수 있다. (필요한 건 선언 부이기 때문이다.)
- 추상 메서드는 선언 부만 있고 구현부가 없는 메서드이다.
- 자식 클래스마다 다르게 구현될 것으로 예상되는 경우 사용한다.
- 상속받는 자식 클래스에서는 추상 메서드의 구현부를 완성해줘야 한다.
- 완성된 클래스가 아니므로 인스턴스를 생성할 수 없다.
abstract class Player{
int x;
Player(){ // 추상 클래스도 생성자가 있어야한다.
x = 10;
}
abstract void play(int pos); // 추상 메서드
abstract void stop(); // 추상 메서드
void play2(){
play(x); // 추상 메서드를 사용할 수 있다.
}
}
- 일반 클래스에서 공통적으로 사용될 수 있는 부분을 뽑아서 추상 클래스로 만든다.
class Marine{
int x, y; // 현재 위치
void move(int x, int y){ /* 지정된 위치로 이동 */ }
void stop(){ /* 현재 위치에 정지 */ }
void stimPack(){ /* 스팀팩을 사용한다. */ }
}
class Tank{
int x, y;
void move(int x, int y){ /* 지정된 위치로 이동 */ }
void stop(){ /* 현재 위치에 정지 */ }
void changeMode(){ /* 공격모드를 변환한다. */ }
}
class Dropship{
int x, y;
void move(int x, int y){ /* 지정된 위치로 이동 */ }
void stop(){ /* 현재 위치에 정지 */ }
void load(){ /* 선택된 대상을 태운다. */ }
void unload(){ /* 선택된 대상을 내린다. */ }
}
//==========================추상 클래스를 이용해서 만들기=================================
abstract class Unit{ // 공통된 부분을 추상 클래스로 만든다.
int x, y;
abstract void move(int x, int y); // 추상메서드
void stop() { /* 현재 위치에 정지 */ }
}
class Marine extends Unit{
void move(int x, int y){ /* 지정된 위치로 이동 */ }
void stimPack(){ /* 스팀팩을 사용한다. */ }
}
class Tank extends Unit{
void move(int x, int y){ /* 지정된 위치로 이동 */ }
void changeMode(){ /* 공격모드를 변환한다. */ }
}
class Dropship{
void move(int x, int y){ /* 지정된 위치로 이동 */ }
void load(){ /* 선택된 대상을 태운다. */ }
void unload(){ /* 선택된 대상을 내린다. */ }
}
인터페이스 (interface)
추상 클래스보다 추상화 정도가 높은 일종의 추상 클래스이다.
- 실제 구현된 것이 아무것도 없다.
- 추상 메서드와 디폴트 메서드, 상수만을 멤버로 가질 수 있다.
- 추상 메서드는 모두 public abstract이기 때문에 생략이 가능하다.
- 상수는 모두 public static final이기 때문에 생략이 가능하다.
- 추상 클래스와 마찬가지로 인스턴스를 생성할 수 없다.
interface InterfaceName{
public static final int x = 1; // 상수
public abstract method1(String s); // 추상 메서드
int y = 2;
method2(String s);
}
- 인터페이스는 다중 상속이 가능하다.
- 인터페이스는 Object 클래스 같은 최고 조상이 없다.
interface A{ void move(int x, int y); }
interface B{ void attack(Unit u); }
interface C extends A, B{}
- 인터페이스를 구현하는 것은 클래스를 상속받는 것과 같다.
- 클래스 상속과 인터페이스 구현 동시에 가능하다.
class 클래스명 extends 클래스명 implements 인터페이스명{
인터페이스에 정의된 추상 메서드 모두 구현
}
인터페이스를 이용한 다형성
인터페이스 타입의 변수로 인터페이스를 구현한 클래스의 인스턴스를 참조할 수 있다.
class Fighter extends Unit implement Fightable{
public void move(int x, int y){ /* 내용 생략 */}
}
Fighter f = new Fighter();
Fightable fa = new Fighter();
- 인터페이스를 메서드의 매개변수 타입으로 지정할 수 있다.
void attack(Fightable){ /* 내용 생략 */}
// Fightable 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 받는 메서드
- 인터페이스를 메서드의 리턴 타입으로 지정할 수 있다.
Fightable method(){ // Fightable 인터페이스를 구현한 클래스의 인스턴스를 반환한다.
return new Fighter();
}
인터페이스의 장점
- 개발 시간을 단축시킬 수 있다.
- 표준화가 가능하다.
- 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다.
- 독립적인 프로그래밍이 가능하다.
interface Repairable{}
class Unit{
int hitPoint;
final int MAX_HP;
Unit(int hp){
MAX_HP = hp;
}
}
class GroundUnit extends Unit{
GroundUnit(int hp){
super(hp);
}
}
class Tank extends GroundUnit implements Repairable{
Tank(){
super(150);
hitPoint = MAX_HP;
}
public String toString(){ // 오버라이딩
return "Tank";
}
}
class SCV extends GroundUnit implements Repairable{
SCV(){
super(60);
hitPoint = MAX_HP;
}
void repair(Repairable r){
if (r instanceof Unit){
Unit u = (Unit)r;
while(u.hitPoint != u.MAX_HP){
u.hitPoint++;
}
}
}
}
Tank tank = new Tank();
Marine marine = new Marine();
SCV scv = new SCV();
scv.repair(tank);
scv.repair(marine); // Error!! Marine클래스는 Repairable인터페이스를 구현하지 않았다.
디폴트 메서드
JDK 1.8 이후 인터페이스에 디폴트 메서드 및 static 메서드를 추가 가능하게 바뀌었다.
- 디폴트 메서드는 인터페이스에 추가된 일반 메서드이다.
- 디폴트 메서드가 기존의 메서드와 충돌하는 경우 아래와 같이 해결한다.
- 여러 인터페이스의 디폴트 메서드 간의 충돌
- 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버 라이딩해야 한다.
- 디폴트 메서드와 부모 클래스의 메서드 간의 충돌
- 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.
- 여러 인터페이스의 디폴트 메서드 간의 충돌
내부 클래스(inner class)
내부 클래스란 클래스 안에 선언된 클래스를 말합니다.
- 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
class A{ // 외부 클래스
...
class B{ // 내부 클래스
....
}
....
}
내부 클래스의 종류과 특징
내부 클래스의 종류는 변수의 선언 위치에 따른 종류와 유효 범위(scope)가 유사하다.
내부클래스 | 특징 |
인스턴스 클래스 | 외부 클래스의 멤버변수 선언위치에 선언한다. 주로 외부 클래스의 인스턴스 멤버들과 관련된 작업에 사용될 목적으로 선언된다. |
스태틱 클래스 | 외부 클래스의 멤버변수 선언위치에 선언한다. 주로 외부 클래스의 static 멤버, static 메서드에서 사용될 목적으로 선언된다. |
지역 클래스 | 외부 클래스의 메서드나 초기화 블록안에 선언되며, 선언된 내부에서만 사용될 수 있다. |
익명 클래스 | 클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스(일회용) |
class Outer{
class InstanceInner{} // 인스턴스 클래스
static class StaticInner{} // 스태틱 클래스
void Method(){
class LocalInner{} // 지역 클래스
}
}
'JAVA' 카테고리의 다른 글
Collections & Collection class 정리 (0) | 2022.08.23 |
---|---|
[JAVA] Generics 정의 및 타입변수, 다형성 (0) | 2022.08.23 |
[JAVA] Map 관련 내용 (0) | 2022.08.19 |
[JAVA] TreeSet (0) | 2022.08.19 |
[JAVA] HashSet (0) | 2022.08.18 |