-
[자바] 객체지향(2) - 2자바 2022. 7. 13. 14:29728x90
1. 다형성
다형성 - ( 자바에서 ) 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 한다.
구체적으로, 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 한다.
Tv t = new Tv(); CaptionTv c = new CaptionTv();
지금까지는 생성된 인스턴스를 다루기 위해서는 인스턴스의 타입과 일치하는 타입의 참조변수만을 사용했다.
즉, Tv 인스턴스를 다루기 위해서는 Tv타입의 참조변수를 사용하고, CaptionTv 인스턴스를 다루기 위해서는 CaptionTv타입의 참조변수를 사용했다.
Tv t = new CaptionTv();
하지만, 상속의 경우에는 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조가 가능하다.
주의!
CaptionTv c = new CaptionTv(); Tv t = new CaptionTv();
실제 인스턴스가 CaptionTv타입이라 할지라도, 참조변수 t로는 CaptionTv인스턴스의 모든 멤버를 사용할 수 없다.
( CaptionTv 인스턴스 중에서 Tv 클래스의 멤버들 ( 상속받은 멤버포함 ) 만 사용이 가능하다. CaptionTv의 것은 사용 불가능 )
-> Tv : power(), power, channel, channelUp(), channelDown()
-> CaptionTv : power(), power, channel, channelUp(), channelDown(), text, cpation()
여기서는 text랑 caption() 사용이 불가능
둘 다 같은 타입의 인스터스지만 참조변수의 타입에 따라 사용할 수 있는 멤버 개수가 다르다.
조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있다.
반대로 조상타입의 참조변수로 조상타입의 인스턴스를 참조할 수 없다.참조변수의 형변환
상속 관계에 있을떄 참조변수도 형변환이 가능하다.
형변환은 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 개수를 조절하는 것 뿐이다.
형변환을 수행하기 전에 instanceof 연산자를 사용해서 참조변수가 참조하는 인스턴스의 타입 확인하기
자손 -> 조상 ( 업캐스팅 ) : 형변환 생략가능 , 가능한 이유는 자손이 조상보다 멤버개수가 더 많아서
조상 -> 자손 ( 다운캐스팅 ) : 형변환 생략불가Car car = null; FireEngine fe = new FireEngine(); FireEngine fe2 = null; car = fe; // 조상 <- 자손 ( 업캐스팅 : car = (Car)fe ) fe2 = (FireEngine)car; // 자손 <- 조상 ( 다운캐스팅 : 형변환 생략 불가능 )
다만, 참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않는다
class casting { public static void main(String[] args) { Car car = new Car(); // 인스턴스 생성 -> 조상타입의 인스턴스를 자손타입의 참조변수로 참조한는것은 허용되지 않는다 Car car2 = null; // 얘는 참조 가능 FireEngine fe = null; fe = (FrieEngine)car; // error // Car car = new FireEngine(); 했으면 에러 안남 fe.drive(); } }
Instanceof 연산자
참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 사용한다.
주로 조건문에 사용되며, true면 검사한 타입으로 참조변수가 형변환이 된다는 것이다.
참조변수와 인스턴스의 연결
조상 타입의 참조변수와 자손 타입의 참조변수의 차이점이 사용할 수 있는 멤버의 개수에 있다.
메서드의 경우 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드( 오버라이딩된 메서드 ) 가 호출되지만, 멤버변수의 경우의 경우 참조 타입에 따라 달라진다 ( 멤버변수 결과 != 메소드 결과 )
멤버 변수가 조상 - 자손 클래스에 중복 정의가 된 경우,
조상타입의 참조변수를 사용 -> 조상 클래스에 선언된 멤버 변수 사용
Parent p = new Child();
자손타입의 참조변수를 사용 -> 자손 클래스에 선언된 멤버 변수 사용예시 코드
class BindTest { public static void main(String[] args) { Parent p = new Child(); Child c = new Child(); System.out.println("p.x =" + p.x); p.method(); System.out.println("c.x =" + c.x); c.method(); } } class Parent { int x = 100; void method() { System.out.println("parent"); } } class Child extends Parent { int x = 200; void method() { System.out.println("child"); } } // p.x = 100 // Child // p.x = 200 // Child
child 클래스에 아무런 멤버 정의 되지 않으면 단순히 조상으로 부터 상속받아 조상 멤버를 사용 ( 중복정의 x )
-> 참조변수의 타입에 따른 변화가 없다
class BindTest { public static void main(String[] args) { Parent p = new Child(); Child c = new Child(); System.out.println("p.x =" + p.x); p.method(); System.out.println("c.x =" + c.x); c.method(); } } class Parent { int x = 100; void method() { System.out.println("parent"); } } class Child extends Parent { } // p.x = 100 // parent // p.x = 100 // parent
관련 정리 핵심 비교 코드
class BindTest { public static void main(String[] args) { Parent p = new Child(); Child c = new Child(); System.out.println("p.x =" + p.x); p.method(); System.out.println("c.x =" + c.x); c.method(); } } class Parent { int x = 100; void method() { System.out.println("parent"); } } class Child extends Parent { int x = 200; void method() { System.out.println("x ="+ x); // this.x 와 같다 System.out.println("super.x ="+ super.x); System.out.println("this.x ="+ this.x); } } // p.x = 100 // x = 200 // super.x = 100 // this.x = 200 // c.x = 200 // x = 200 // super.x = 100 // this.x = 200
Vector 클래스
- 배열의 크기를 가변적으로 둘 수 있다 ( 배열의 크기가 동적으로 관리 된다. )
매서드 / 생성자 설명 Vector() 10개의 객체 저장할 수 있는 Vector 인스턴스 생성,
10개 이상의 인스턴스 저장시, 자동적으로 크기 증가boolean add( Object o ) vector에 객체 추가시 성공 true 실패 false boolean remove( Object o ) vector에 저장되어 있는 객체 제거, 성공 true, 실패 false boolean isEmpty( ) vector가 비워있는지 , 비워있음 true, 비어있지 않음 false Object get( int index ) 저장된 위치의 객체 반환 ( 형변환 필요 ) int size( ) vector에 저장된 객체수 vector을 사용한 코드 예시
import java.util.*; class Product { int price; int bonusPoint; Product(int price) { this.price = price; bonusPoint = (int)(price/10.0); } Product() { price = 0; bonusPoint = 0; } } class Tv extends Product { Tv() { super(100); } public String toString() { return "Tv"; } // Object 클래스 오버라이딩 } class Computer extends Product { Computer() { super(100); } public String toString() { return "Computer"; } } class Audio extends Product { Audio() { super(100); } public String toString() { return "Audio"; } } class Buyer { int money = 1000; int bonusPoint = 0; Vector item = new Vector(); // Vector 객체 생성 void buy(Prodecu p) { if(money < p.price) { return } money -= p.price; bonusPoint += p.bonusPoint; item.add(p); // vector에 저장 } void refund(Prodecu p) { if(item.remove(p)) { money += p.price; bonusPoint -= p.bonusPoint; } else { // 제거 실패 System.out.println('실패'); } } void summary(Prodecu p) { int sum = 0; String itemList = ""; if(item.isEmpty()) { return } for(int i = 0; i < item.size; i++) { Product p = (Product)item.get(i); sum += p.price; itemList += (i==0) ? "" + p : ", " + p; } } } class test { public static void main(String[] args) { Buyer b = new Buyer(); Tv tv = new Tv(); Computer com = new Computer(); Audio audio = new Audio(); b.buy(tv); b.summary(); b.refun(); b.summary(); } }
2. 추상 클래스
추상 클래스는 미완성 설계도에 비유되며, 인스턴스를 생성할 수 없고, 상속되어 자손클래스에 의해서만 완성될 수 있다.
- 추상클래스는 추상메소드를 포함하고 있다는 사실을 빼면, 일반 클래스와 다르지 않다. ( 생성자, 멤버변수, 메서드가 있다. )
- 추상 메소드 : 선언부만 작성하고, 구현부는 작성하지 않는다.
- 이 자체로 인스턴스 생성 못함 ( 상속을 통해 해결 )
abstract class이면, 추상메서드 있는지 확인하기
추상클래스 작성방식
abstract 리턴타입 메서드이름();
// 주석으로 어떤 기능을 수행할 목적으로 만들었는지 작성하기abstract class Player { // 추상클래스 abstract void play(int pos); // 추상 메소드 abstract void stop(); // 추상 메소드 } class Player extends Player { void play(int pos) { /* 내용은 생략하지만 꼭 있어야 함 */ } // 추상 메소드 구현 void stop() { /* 내용은 생략하지만 꼭 있어야 함 */ } // 추상 메소드 구현 } abstract class AbstractPlayer extends Player { void play(int pos) { /* 내용은 생략하지만 꼭 있어야 함 */ } // 추상 메소드 구현 }
abstract을 굳이 쓰는 이유는 자손 클래스에서 추상메서드를 반드시 구현하도록 강요하기 위함
추상 클래스 - 추상 메서드만 있으면 됨 ( 메서드 생성자 멤버변수 모두 가능 )
인터페이스 - 더 추상화 -> 추상메서드, 상수만 존재해야함3. 인터페이스
- 일종의 추상클래스이다. ( 추상 클래스 보다 추상화가 더 되어 있다. )
- 오직 추상 메서드와 상수만 멤버로 갖는다
인터페이스 작성방식
interface 인터페이스이름 { public static final 타입 상수이름 = 값; public abstract 메서드이름(매개변수 목록); }
- 모든 멤버변수는 public static final ( 생략가능 ) - 접근제어자 public 명심
- 모든 메서드는 public abstract ( 생략가능 ) - 접근제어자 public 명심- 인터페이스는 다중 상속이 가능하다.
인터페이스 구현
- 인터페이스는 자체로 인스턴스를 생성할 수 없다.
추상 클래스가 상속을 통해 추상 메서드를 완성하듯 implements를 통해 클래스 작성
class 클래스 이름 implements 인터페이스이름 { } class Fighter implements Fightable { public void move(int x, int y) { /* */ } public void attack(Unit u) { /* */ } }
만일, 인터페이스의 메서드 중 일부만 구현하면 abstract 붙여줘야 한다.
abstract class Fighter implements Fightable { public void move(int x, int y) { /* */ } }
상속과 구현을 동시에 할 수 있다.
class Fighter extends Unit implements Fightable { public void move(int x, int y) { /* */ } public void attack(Unit u) { /* */ } }
인터페이스를 이용한 다형성
인터페이스도 이를 구현한 클래스의 조상이라 할 수 있으므로 해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조가능하고, 인터페이스의 타입으로 형변환도 가능하다.
인터페이스 Fightable을 클래스 Fighter가 구현했을때
Fightable f = new Fighter(); // Fightable 이라는 인터페이스 // Fighter 이라는 클래스
메서드의 매개변수의 타입도 가능
void attack(Fightable f) { }
인터페이스 타입의 매개변수가 갖는 의미는 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야한다는것이다
Fiahtable method() { Fighter f = new Fighter(); return f; // 위의 두줄을 윗말대로 줄여보면 // return new Fighter(); // 인스턴스를 반환하고 있다 }
class Fighter extends Unit implements Fightable { void attack(Fightable f) { } }
정말 중요한것!
Fightable method() { Fighter f = new Fighter(); return f; }
리턴 타입이 인터 페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.
interface Parseable { public static void parse(String fileName); } class ParserManager { // 리턴타입이 Parseable 인터페이스이다. public static void main(String[] args) { if( type.equals("XML")) { return new XMLParser(); } else { Parseable p = new HTMLParseable(); return p; } } } class XMLParser implements Parseable { public void parse(String fileName) { System.out.println(fileName); } } class HTMLParser implements Parseable { public void parse(String fileName) { System.out.println(fileName); } } class ParserTest { public static void main(String[] args) { Parseable parser = ParserManager.getParser("XML"); parser.parse("xml"); parser = ParserManager.getParser("HTML"); parser.parse("html"); } ]
getParser 매서드는 매개변수로 넘겨받는 type에 따라 값이 XMLParser 인스턴스 또는 HTMLParser 인스턴스 반환한다
이렇게 하면, 프로그램 자체를 변경하지 않고, 서버측의 변경만으로 사용자가 새로 개전된 프로그램을 사용할 수 있다.
인터페이스 이해
직접적인 관계
A( User ) -> B ( Provider ) : 직접적인 관계 , B가 바뀌면 A도 바뀌어야함
class A { public void method(B b) { b.methodB(); } } class B { public void method() { System.out.println("method b"); } } class InterfaceTest { public static void main(String[] args) { A a = new A(); a.methodA(new B()); } }
간접적인 관계로 바꾸기 위해서는 인터페이스를 통해 클래스 B의 선언과 구현 분리 필요
간접적으로 'A-I-B' 관계 : A는 인터페이스의 영향만 직접적으로 받는다 ( B의 영향을 받지 않는다.)
class A { public void method(I i) { // 이 부분이 중요 i.methodB(); // 이 부분이 중요 } } interface I { public abstract void methodB(); } class B implements I { public void method() { System.out.println("interface method b"); } } class InterfaceTest { public static void main(String[] args) { A a = new A(); a.methodA(new B()); } }
인스턴스를 직접 생성하지 않고, getInstance() 라는 메서드를 통해 제공 받는다
나중에 다른 클래스의 인스턴스로 변경되어도 A클래스의 변경없이 getInstance()만 변경하면 된다는 장점이 있다
class InterfaceTest { public static void main(String[] args) { A a = new A(); a.methodA(); } } class A { void method() { I i = InstanceManager.getInstance(); i.methodB(); System.out.println(i.toString()); } } interface I { public abstract void methodB(); } class B implements I { public void method() { System.out.println("interface method b"); } public String toString() { return "class B" } } class InstanceManager { public static I getInstace() { return new B(); // 다른 인스턴스로 바꾸려면 여기만 바꾸면 된다 } }
인터페이스 I 타입의 참조변수 i로도 Object 클래스에 정의된 메서들을 호출 가능하다 ( toString() 이런 메서드 )
디폴트, static 메서드
static 메서드
- 가장 대표적인 것은 java.util.Collection 인터페이스가 있다.
- 접근제어자가 public, 생략가능
디폴트 메서드
- 추상 메서드의 기본적인 구현하는 메소드
- 인터페이스에 메소드 추가시에는 인터페이스를 구현하는 모든 클래스에 클래스에 추가를 해줘야 하는 번거로움이 있다. 이럴때는 디폴트 메서드 앞에 default 를 붙이고 { } 을 붙여주면, 기존의 클래스를 변경하지 않아도 된다
-> 조상 클래스에 추가한 것과 동일해 지는 것이다.
1. 여러 인터페이스의 디폴트 메서드 간의 호출
- 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야한다
2. 디폴트 메서드와 조상 클래스의 메서드 간의 출돌
- 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.4. 내부 클래스
내부 클래스는 선언위치가 중요한데, 위치에 따라 선언 위치의 변수와 동일한 유효범위와 접근성을 갖는다.
abstract, final, 접근제어자 사용이 가능하다
class Inner { class InstanceInner { int iv = 100; // static int cv = 100; // 에러 static 변수 선언 불가 final static int CONST = 100; // final static은 상수여서 가능 } static class StaticInner { int iv = 200; static int cv = 200; // static 클래스만 static 멤버 정의 가능 } void method() { int lv = 0; final int lv = 0; // JDK 1.8 부터 final todfir rksmd // 지역 클래스에서는 final이 붙은 지역변수만 접근이 가능하다. class LocalInner { int iv = 300; // static int cv = 100; // 에러 static 변수 선언 불가 final static int CONST = 100; // final static은 상수여서 가능 } } public static void main(String[] args) { System.out.println(InstanceInner.CONST); System.out.println(StaticInner.cv); } }
익명 클래스
- 이름이 없고, 일회용 클래스이다.
외부클래스명$숫자.class 의 형식으로 클래스 파일명이 결정된다.
익명 클래스 구조
new 조상클래시이름( ) { // 멤버 선언 } new 구현인터페이스이름( ) { // 멤버 선언 }
- 생성자 가질 수 없다
- 하나의 클래스로 상속받는 동시에 인터페이스 구현하거나 둘 이상의 인터페이스 구현 못한다.
- 오로지 단 하나의 클래스를 상속 받거나 하나의 인터페이스만을 구현할 수 있다
'자바' 카테고리의 다른 글
[자바] JAVA.LANG패키지와 유용한 클래스 (0) 2022.07.15 [자바] 예외처리 (0) 2022.07.13 [자바] 객체지향(2) - 1 ( 패키지 ) (0) 2022.07.09 [자바] 객체지향(1) (0) 2022.07.07 [자바] 배열의 활용 (0) 2022.07.01