728x90
01, 02 상속
- 상속: 기존의 class를 재사용하여 새로운 클래스를 작성하는 것
- 상속의 특징
- 코드의 재사용성을 높인다.
- 코드의 중복을 제거한다.
- 프로그램의 생산성과 유지보수에 기여한다.
class Parent { }
class Child extends Parent {}
- 규칙
- 한 방향이다. Parent -> child. parent class의 변화는 child에 영향을 끼치지만 child의 변화가 parent에 영향을 주지 않는다.
- child 는 parent class의 모든 멤버를 상속받는다 (단, 생성자와 초기화 블럭은 상속 X)
- child 멤버 >= parent 멤버
class Point {
int x;
int y;
}
// Point class의 영향 안받음
class Point1 {
int x,y,z;
}
// Point class의 영향 받음, int x,y도 가지고 있음
class Point1 extends Point {
int z;
}
// 상속 안 받은 클래스나 받은 클래스나 동일한 결과
Point1 p = new Point()
// []는 저장공간 표현한 것. array 아님
// p[0x100] -> [x][y][z]
// p[0x200] -> [x][y][z]
03,04 포함
- 클래스 관계 = 상속 or 포함 관계
- 대체적으로 포함 관계를 많이 사용
- 포함 관계: 한 클래스의 멤버변수로 다른 클래스 타입의 참조변수를 선언하는 것
class Point {
int x;
int y;
}
class Circle {
Point p = new Point(); // 포함관계
int r;
}
// 초기화 방법 (상속이랑 다르다)
c.r = 10;
c.p.x = 10;
c.p.y = 10;
- 저장 구조
- Circle c = new Circle();
- c [0x100] -> 0x100 [ 0x200 : p ][ 0 : r ] -> 0x200 [0 : x] [0 : y]
- 포함과 상속 관계 사용하는 기준
- 상속: ~은 ~이다. ex) 포르쉐는 자동차다
- 포함: ~은 ~을 가지고 있다. ex) 원은 점을 가지고 있다.
05. 단일 상속
- 자바에서는 단일 상속만 허용
- 자바에서는 비중이 높은 class 만 상속을 하고 나머지는 그때 그때 포함관계를 이용을 한다.
- 이유: 다중 상속은 복합적인 기능을 가진 클래스로 쉽게 작성을 할 수 있지만
- 클래스간 관계가 매우 복잡해지고
- 부모 class들의 멤버간의 이름이 같은 경우 구별할 방법이 없음
class TvDVD extends Tv, DVD {} // error
//둘다 같은 method를 가지고 있을 때 누구의 power(); 인지 모름
class Tv { power(); }
class DVD{ power();}
06. Object class - 모든 class의 조상
- Object class: 모든 클래스 상속계층도의 최상위에 있는 조상클래스이다.
- 규칙
- 조상이 없는 class는 자동으로 object class를 상속 받는다.
- 모든 class는 object class에 정의된 11개의 method를 상속 받는다.
- 이미 다른 부모 class로 부터 상속받도록 작성된 클래스에 대해서는 컴파일러가 Object class를 추가하지 않는다. - 결국 마지막 최상위 조상은 Object class 이므로 11개 method 이용 가능
- 참고 - compiler가 자동 생성해주는 부분
- Method
- 기본 생성자
- void - return;
- super() : 모든 생성자는 첫줄에 다른 생성자를 넣어야 한다.
- class 생략
- extends Object
- Method
Object method toString 예제
더보기
class MyPoint {
int x;
int y;
}
class Circle extends MyPoint{
int r;
}
public class prac {
public static void main(String[] args) {
Circle c = new Circle();
System.out.println(c.toString());
//"Circle@7344699f" class @ 객체주소
System.out.println(c);
// toString() 생략가능 "Circle@7344699f", println이 참조변수를 받으면 toString()을 호출
}
}
07. 오버라이딩 - 상속
08. overriding의 조건 (암기)
- overriding: 상속받은 method의 내용을 변경하는 것 * overriding : 덮어쓰다
- 특징
- 선언문 변경 불가
- 구현부만 변경가능
- 조상의 멤버변수를 상속 받지만 오버라이딩 된 method가 호출 (다형성)
- 조건(암기 필수)
- 선언부가 조상 class method와 일치해야한다. == 선언문 변경 불가
- 접근 제어자를 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없다.
- public > protect >(default) > privite
- 예외는 조상 클래스의 메서드보다 많이 선언할 수 없다.
- super
- 오버라이딩을 통해서 조상것을 덮어졌다.
- 예제를 통해서 알아 볼 것
- 조상타입변수로 변수와 오버라이딩 method 호출
- 변수: 조상 타입 변수값 나옴
- method: 자손에서 오버라이딩 된 method 나옴
- 잘 체크해야되는 것이 변수가 변수명만 동일하고 실제로는 다른 변수인지, 아니면 생성자 와 super()를 통해서 변수가 서로 초기화가 연결되는지 잘 확인 해야한다.
- 오버라이딩 되기 전 조상 메서드 호출 방법
- super.method() 이용
- super.method를 사용하고 싶으면 class에 미리 super.method를 호출하는 method를 만들고 사용
- 변수: super를 사용하는 경우 없음
- method: super를 사용하는 경우가 존재
- 생성자를 통한 초기화인 경우 - 자동-> 명시적-> 생성자 초기화 순서로 넘어간다.
- 조상 변수가 값이 있고 조상 타입으로 다루는 자손 객체의 필드들은 결국 자손의 초기화 값을 나타내게 되어있다.
- 조상타입변수로 변수와 오버라이딩 method 호출
- 조상타입 변수, 오버라이딩 되기 전 조상 메서드 호출
package practice;
class Practice {
public static void main(String[] args) {
MyPoint p = new MyPoint3D();
System.out.println("p.x = " + p.x); // p.x = 1 조상 타입으로 자손의 x에는 접근하지 못하고 자신의 x를 꺼냈다. 자손 type에 숨어있는 x이다.
System.out.println("p.y = " + p.y); // p.y = 1
System.out.println("p.getLocation() = " + p.getLocation());
MyPoint3D c = new MyPoint3D();
System.out.println("c.x = " + c.x); // 자신의 타입으로 x를 호출한 것은 this.x를 부르는 것
System.out.println("c.y = " + c.y); // 자신의 타입으로 y를 호출한 것은 this.y를 부르는 것
System.out.println("c.z = " + c.z);
c.method(); // 숨겨져있던 조상의 변수를 출력. 자손의 변수와 이름이 동일해서 super.x super.y로 구분지음
System.out.println("c.getLocation() = " + c.getLocation());
System.out.println("c.superGetLocation() = " + c.superGetLocation()); // 오버라이딩 전 method 호출
}
}
class MyPoint {
int x = 1;
int y = 1;
String getLocation() {
return "x:" + x+", y:" + y;
}
}
class MyPoint3D extends MyPoint {
void method(){
int x1 = super.x;
int y1 = super.y;
System.out.println("y1 = " + y1);
System.out.println("x1 = " + x1);
}
int x = 5;
int y = 5;
int z = 8;
// overriding -> 조상 getLocation을 상속받지만 overriding method가 호출됨(다형성)
String getLocation() {
return "x:" + x + ", y:" + y +", z:" + z;
}
String superGetLocation(){
return super.getLocation(); // 조상 method 호출 방법
}
}
- 생성자를 통한 초기화
package practice;
class Practice {
public static void main(String[] args) {
MyPoint p = new MyPoint3D();
System.out.println("p.x = " + p.x); // p.x = 5 -> 생성자를 통해서 값을 받도록 하면 우선권이 생성자로 넘어감 "초기화 순서 자동 -> 명시적 -> 생성자"
System.out.println("p.y = " + p.y); // p.y = 6 -> 생성자를 통해서 값을 받도록 하면 우선권이 생성자로 넘어감 "초기화 순서 자동 -> 명시적 -> 생성자"
}
}
class MyPoint {
int x = 1;
int y = 1;
public MyPoint() {
this(1,1);
}
public MyPoint(int x, int y) {
this.x = x;
this.y = y;
}
}
class MyPoint3D extends MyPoint {
// 현재 int x; int y; 가 없다. 이는 조상의 x,y를 사용한다는 의미이다.
int z;
public MyPoint3D() {
this(5,6,5);
}
public MyPoint3D(int x, int y, int z) {
super();
this.x = x;
this.y = y;
this.z = z;
}
}
09. Overriding vs Overloading
- 오버로딩: 기존에 없는 새로운 method를 정의하는 것 (new) - 상속과 관련 X
- method 이름이 같다
- params 개수, type 달라야함
- return type 상관없다.
- 오버라이딩: 상속받은 method의 내용을 변경하는 것 (change, modify)
- 구현부만 건들이는 것
- 선언부는 그대로 유지하는 것
- 예외는 조상보다 적게 조상과 같은 것
ex)
class Parent {
void parentMethod(){}
}
class Child extends Parent {
void parentMethod(){ /*내용 추가*/ } // overriding
void parentMethod (int i) {} // overloading
// 리턴 타입, 메서드 이름, params
void childMethod() {} // method 정의
void childMethod(int i) {} // overloading
void childMethod() {/*내용 추가*/} // error, method 정의와 중복
}
10. 참조변수 super = this
- 공통점
- 인스턴스 method(= 생성자) 내에서만 존재
- static method에서 사용 불가
- 차이점
- super : 조상, 자식 member 구별
- this : lv(params)와 iv(memeber) 구별
class Parent {int x = 10;} // int x == int super.x
class Child extends Parent {
int x = 20; // int x == this.x
void childMethod() {
System.out.println("x=" + x); // 20
// 가까운 쪽의 값을 먼저 받음. 상속값보다 자기가 선언한 값이 가까움
System.out.println("this.x=" + this.x); // 20
System.out.println("super.x=" + super.x); // 10
}
}
- 메모리 구조
- c [0x100] -> 0x100 [ 10 - super.x ] [ 20 - this.x ] [ childMethod() ]
class Parent {int x = 10;} // int x == int super.x
class Child extends Parent {
void childMethod() {
System.out.println("x=" + x); // 상속 받은 10
// this.x는 존재하지 않지만 조상의 x를 상속받았기에 10
System.out.println("this.x=" + this.x);
System.out.println("super.x=" + super.x); // 상속 받은 10
}
}
11. super() - 조상의 생성자
- 상속은 생성자, 초기화 block은 상속이 되지 않는다.
- 생성자 혹은 초기화 block이 필요한 경우 super()를 이용하여 조상의 생성자를 호출 가능
- 조건
- 모든 생성자의 첫줄에 반드시 조상의 생성자(super()) or 자신의 생성자(this.())를 호출해야 한다.
- 생성자 내에 this() 혹은 super() 가 존재 하지 않을 때 compiler가 super()를 자동 등록한다.
public class Ex7_4 {
public static void main(String[] args) {
Point3D p = new Point3D(1, 2, 3);
System.out.println("x=" + p.x + ",y=" + p.y + ",z=" + p.z);
}
}
class Point { // class Point extends Object
int x, y;
Point(int x, int y) {
// super(); 첫줄에 조상 생성자 호출하지 않았으므로 조상 기본생성자 호출
this.x = x;
this.y = y;
}
}
class Point3D extends Point {
int z;
Point3D(int x, int y, int z) {
super(x, y); // Point(int x, int y) 호출, 조상 생성자 호출
this.z = z; // 자신의 멤버를 초기화 - 자손 class가 선언한 int z만 초기화
}
}
- error 경우 - 자동주입 패턴을 알아야 한다.
- 조상 class 에 params 생성자를 생성
- 자손에 따로 생성자를 작성하지 않은 자손 class 생성
- 자손 class에 compiler가 자동으로 기본 생성자를 생성
- 기본 생성자는 아무것도 없기때문에 자동으로 super()가 등록
- 조상 class에는 기본 생성자가 존재하지 않으므로 자손 class는 super()를 찾을 수 없다.
- error가 발생한다.
class Parents {
int x;
// params 생성자를 가지므로 기본 생성자 생성 X
Parents(int x) {
this.x = x;
}
}
class Children extends Parents {
int y;
}
- 조상 타입과 자손 타입으로 값 꺼내기
- 변수
- 현재 p.x인거지 lv의 변수명을 바꾸게 되면 (ex - abc로 변경) p.abc를 구하는 것이다.
- 그리고 p 리모컨은 자손의 x에 접근 할 수 있는 버튼이 존재하지 않는다.
- method같은 경우는 조상의 method가 Overriding이 된다.
- 조상이나 자손 type의 리모컨 모두 동일한 method를 가리킨다.
- 변수
class Practice {
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 Method");
}
}
class Child extends Parent {
int x = 200;
void method() {
System.out.println("Child Method");
}
}
12. 패키지(package)
13. 패키지 선언
- 패키지: 관련된 클래스의 묶음, 패키지 안에 패키지가 존재할 수 있다.
- 패키지는 물리적으로 하나의 디렉토리이고 그 안에 관련된 클래스 파일, 관련 패키지가 존재
- 규칙
- 주석과 공백을 제외한 첫 번째 문장이어야 함. packgae 패키지명;
- 하나의 소스파일에 단 한 번만 선언
- 구분을 위해 소문자로 작성을 원칙
- 모든 class 파일은 반드시 하나의 패키지에 포함되어야 한다. * package 작성 안할 시 default package에 들어감
15. import문
특징
- class 이름에서 package 이름을 생략 가능
- 컴파일러에게 소스파일에 사용된 클래스의 패키지에 대한 정보 제공
- Intellij 단축키: option + Enter(static import 또한 동일하다.)
- java.lang 패키지의 class는 import 없이 사용가능
- 소스파일에서 import 문은 package문 다음, class 선언문 이전에 작성을 해야한다.
- import문 선언방법
- import 패키지명.클래스명; or import 패키지명.*;
- *: 해당 패키지의 바로 아래의 class를 불러오는 것, 바로 아래의 패키지는 건들이지 않음
17. 제어자
- 클래스와 클래스의 멤버(멤버변수, 메서드)에 부가적인 의미 부여
- 접근 제어자: public, protected, default, private
- 그 외: static, final, abstract
- 하나의 대상에 대해서 여러 제어자를 조합하여 사용하는 것이 가능하지만 접근 제어자는 한번에 1개만 사용 가능
18. static
- 사용: 멤버변수, 메서드, 초기화 블럭
19. final
- 사용: 클래스, 메서드, 멤버변수, 지역변수
- 클래스: 확장될 수 없는 클래스 = 최종 자손 ex) String - 보완기능 가짐
- 메서드: 오버라이딩 할 수 없음
- 멤버변수, 지역변수: 상수
20. abstract
- 사용: 클래스, 메서드
- 구현부가 존재하지 않은 메서드를 가지고 있으며 class도 자동적으로 미완성이 됨
- 인스턴스 생성 불가
21. 접근 제어자
- 사용
- 클래스: public, default
- 멤버변수, 메서드, 생성자: private, default, protected, public
- 범위
- private: 같은 class 내에서만 접근이 가능
- default: 같은 package 내에서만 접근이 가능
- protected: 같은 패키지 + 다른 패키지의 자손클래스(=다른 패키지의 class의 조상 class로 상속 되었을 때) 접근 가능
- public: 모든게 다됨
규칙
- 멤버변수, 메서드, 생성자의 제어자 범위가 커도 결국에는 class의 제어자 범위에 맞춰진다.
- 다른 범위에서 사용할 때 Method 안에서만 사용가능
- 설계도 class 변수들을 사용하려면 instance 생성이 필요하다
- instance 생성을 안했을 때 변수의 이름이 동일하다고 문제가 생기지 않는다. 서로 다른 영역으로 확인한다.
22. 캡슐화와 접근 제어자
접근 제어자 사용 이유
- 외부로부터 데이터를 보호하기 위해서
- 외부에는 불필요한 내부적으로만 상요되는 부분을 감추기 위해서
- 수정할 때 범위를 줄여놓으면 test하기 유리
참고
더보기
class Time {
private int hour;
private int minute;
private int second;
public void setHour(int hour) {
if(checkParamsHour(hour)) return;
this.hour = hour;
}
// setHour의 true false를 정확히 하기 위함.
// 외부까지 알 필요 없으므로 private 사용, 나중에 수정 후 test시 private area만 확인하면 되는 장점 가짐
private boolean checkParamsHour(int hour) {
return hour <0 || hour >23;
}
public int getHour() { return hour;}
}
public class TimeTest {
public static void main(String[] args) {
Time t = new Time();
t.setHour(2);
System.out.println(t.getHour());
t.setHour(222);
System.out.println(t.getHour());
}
}
23. 다형성
- 조상 type 참조변수로 자손 type 객체를 다루는 것 * 반대는 안됨
- SmartTv s = new SmartTv();
- Tv t = new SmartTv(); // 다형성
- 조상 type의 참조변수로 자손 type 객체를 다룰 수 있다. 하지만 조상이 가지고 있는 멤버 만큼만 다룰 수 있다.
- 자손 type의 참조변수로 조상 객체를 다룰 수 없는 이유: 자손 멤버 중 조상에 없는 것이 있기 때문에 호출시 error 발생
class Tv {
boolean power;
int channel;
}
class SmartTv extends Tv {
void caption() {}
}
public class PackageTest {
public static void main(String[] args) {
Tv tv = new Tv();
SmartTv smartTv = new SmartTv();
Tv tv2 = new SmartTv(); // 다형성
}
}
24. 참조변수의 형변환
- 서로 상속인 관계에서 객체가 리모컨보다 범위가 넓기만 하면 된다.
- 객체 범위의 기준: 형변환을 다 떠나서 원래 객체를 의미
- SmartTv smartTv1 = (SmartTv) tv1; : tv1 참조변수의 실제 객체는 SmartTv
- SmartTv smartTv2 = (SmartTv) tv; : tv 참조변수의 실제 객체는 Tv
- 조상타입으로 형변환 생략가능
- 자손타입으로 형변환 생략불가
- SmartTv smartTv1 = (SmartTv) tv1; : tv1의 객체가 SmartTv여도 현재 type의 Tv이기 때문에 생략 불가
class Tv {}
class SmartTv extends Tv {}
public class Practice {
public static void main(String[] args) {
// 객체 생성
Tv tv = new Tv();
SmartTv smartTv = new SmartTv();
Tv tv1 = smartTv; // (Tv) 생략가능
SmartTv smartTv1 = (SmartTv) tv1;// (SmartTv) 생략불가
SmartTv smartTv2 = (SmartTv) tv; // runtime error
}
}
26. instanceof (순간적으로 헷갈림)
- instanceof 는 형변환을 하기 전 runtime error를 막기 위한 장치
- 참조변수(R) instanceof 객체(O)
- R이 가리키는 실제 객체가 O로 형변환 가능한지 boolean으로 나타낸다.
- R의 객체 범위가 O보다 크거나 같으면 true
- R의 객체가 O를 조상으로 가지면 true
- instanceOf가 필터해주는 것 2가지
- 관련 없는 type의 객체가 들어오는 것을 막음
- R이 O보다 작은 경우 막음
// Car type 중 FireEngine과 관련없는 PoliceCar 들어오면 false
// Car type 중 FireEngine보다 범위가 작은 Car가 들어오면 false
void(Car c){
if(c instanceof FireEngine){
FireEngine fe = (FireEngine)c;
fe.water;
}
}
class Car{}
class FireEngine extends Car{}
class PoliceCar extends Car{}
// R이 O보다 작은 경우 false 반환해서 막음
class Tv {
boolean power;
int channel;
}
class SmartTv extends Tv {
void caption() {}
}
public class My {
public static void main(String[] args) {
Tv tv = new Tv();
System.out.println(tv instanceof Object); // true
System.out.println(tv instanceof Tv); // true
System.out.println(tv instanceof SmartTv); // false. t참조변수의 실제 객체가 SmartTv 타입보다 작다.
}
}
27. 매개변수의 다형성 - 장점1
- 참조형 매개변수: 메서드 호출시 자신의 type 또는 자손 type 객체를 넘겨줄 수 있다.
예제
class Product{
int price;
int bonusPoint;
}
class Tv extends Product{}
class Computer extends Product{}
class Buyer {
int money = 1000;
int bonusPoint = 0;
// 문제점
void buy(Tv t){
money = money - t.price;
bonusPoint = bonusPoint + t.bonusPoint;
}
void buy(Computer c){
money = money - c.price;
bonusPoint = bonusPoint + c.bonusPoint;
}
// 해결방안
void buy(Product p){
money = money - p.price;
bonusPoint = bonusPoint + p.bonusPoint;
}
}
- 문제점: 물품이 새로 생길 때마다 void buy(){} 새로 생성해야함
- 해결방안: 매개변수에 다형성을 적용
- 모든 물품의 조상인 product를 이용
- product를 조상으로 가지는 class는 void buy의 params에 들어갈 수 있다.
- 참고
- 현재 예의 경우 사용하는 field들은 모두 Product내에서 해결이 가능한 field들이다.
- instanceOf는 형변환을 하고 싶을 때 조건이 맞는지 체크를 하기 위함인데 현재는 최고 조상인 product를 이용하므로 굳이 체크할 필요가 없다.
29. 여러 종류의 객체를 배열로 다루기 - 장점2
조상의 참조변수의 array를 이용해서 자손들의 array를 구성 가능
Product p[] = new Product[10]; // 참조변수 p의 값: 0x100주소
p[0] = new Tv(); // 0x100의 배열의 첫번째칸의 값: 0x200주소
p[1] = new Computer(); // 0x200주소의 객체: new Computer();
- p [0x100] -> 0x100 [0x200] [0x300] [null][null][null],,,[null] *10개
- p[0] [0x200] -> 0x200 [][][] 해당 주소에 new Tv(); 객체가 존재
- 정리 - 다형성의 장점 2가지
- 다형적 매개변수
- 하나의 배열로 여러종류 객체를 다룰수 있다.
31. 추상 클래스
abstract class 클래스이름{
멤버변수;
메서드;
생성자;
추상 메서드;
}
- 미완성 설계도, 인스턴스 생성불가
- 추상 클래스는 상속을 통해서 자손클래스에 의해서만 완성이되고 추싱 메서드가 완전히 완성되야 객체 생성이 가능하다.
- 추상 class안에 생성자, 멤버변수, 메서드를 가질 수 있다.
- instance method를 가져도 error가 안생기는 이유: instance method를 이용하려면 객체를 생성햐야하는데 추상 class는 객체를 만들수가 없기 때문에 뭔가 일이 생길 경우가 없음. 그래서 괜찮음.
- 추상class도 매개변수 다형성이 적용이 된다.
public class My {
public static void main(String args[]) {
Buyer buyer = new Buyer();
buyer.buy(new Tv());
}
}
abstract class Product {
int price;
abstract void try1();
}
class Tv extends Product {
void try1() {}
}
class Buyer {
int money = 1000;
void buy(Product p) {
money -= p.price;
p.try1();
}
}
32. 추상 메서드
/* 주석을 통해 어떤 기능을 수행할 목적으로 작성했는지 설명 */
abstract 리턴타입 메서드이름();
- 추상 클래스로부터 상속받는 자손클래스는 오버라이딩을 통해 조상인 추상메서드를 모두 구현해줘야한다
- 구현하지 않을 시 자손클래스도 추상 클래스가 된다.
35. 인터페이스
- 주: 구현된거 없는 추상 메서드의 집합
- 모든 메서드: public abstract, 생략가능
- 부: static method, default method, 상수
- 모든 상수: public static final, 생략가능
- abstact class, interface의 차이점
- 추상클래스: 이것 저것 다 가지고 있고 추가적으로 abstract method를 가지고 있는 것
- 인터페이스: abstract method만 가지고 있는 것
36. 인터페이스의 상속
- 인터페이스는 인터페이스로부터만 상속 가능
- 다중 상속 가능
- 선언부만 가진 미완성 method이므로 동일이름 method의 문제점이 발생하지 않는다.
- 다중 상속의 동일한 method를 받은 interface를 구현하면 양쪽에서 받은 method를 하나로 동일 취급한다.
- 다중 구현에서도 동일한 추상 method도 동일 취급 - 37. 인테페이스의 구현
- Object 클래스와 같은 최고 조상이 없다
- interface의 조상은 interface만 가능
다중 상속 예
interface Method1 {
void move();
}
interface Method2 {
void move();
}
// move()를 양쪽 interface에서 받았지만 하나만 존재
interface Method3 extends Method1, Method2 {}
class Method4 implements Method3 {
public void move() {
System.out.println("hi"); // 하나로 융합된 void move(); 하나만 구현
}
}
37. 인터페이스의 구현
- 구현 방법
- implements
- 추상메서드 모두 구현
- 추상 메서드 구현선 접근 제어자 public 작성
class Abc implements interface name {
// 인터페이스에 정의된 추상메서드를 모두 구현해야 함
// public을 무조건 붙여야함. overriding 규칙으로 조상보다 접근제어자가 좁으면 안된다.
}
- 특징
- 다중 상속이 있듯이 다중 구현도 존재
- 다중 구현할 interface의 모든 추상 method를 구현만 하면 된다.
- 다중 구현할 class의 interface들의 method가 같을 때, 하나의 method로 취급 후 하나만 구현
- abstract class와 interface의 method가 동일시, 하나의 method로 취급 후 하나만 구현 - 복합특징 예제
- 다중 상속이 있듯이 다중 구현도 존재
interface Method1 {
void move();
}
interface Method2 {
void move();
}
class Method4 implements Method1, Method2 {
// method 선언부가 동일한 추상메서드가 들어와서 하나로 취급
public void move() {
System.out.println("hi");
}
}
- 복합 특징
- class 구현시 상속과 구현을 동시에 가능 - 순서는 extends, implements 순
- 구현(implements): 다중 구현 가능
- 상속(extends): 단일 상속만 가능
- *이 때 상속은 interface의 상속이 아니라 실제 class들의 상속을 의미
- interface 구현이라해도 class를 만든것이기에 조상으로 Object를 가진다.
abstract class AbtractMethod{
abstract void method();
}
interface Method1{
void method();
}
interface Method2{
void method();
}
class Method4 extends AbtractMethod implements Method1, Method2{
public void method() {
System.out.println("하나로 취급");
}
}
38. 인터페이스 다형성 예제
- 다형성 특징
- 인터페이스 타입 변수로 해당 인터페이스 구현체를 다룬다.
- 인터페이스 타입을 매개변수로 사용가능
- return type이 인터페이스인 경우
- 해당 인터페이스를 구현한 클래스의 인스턴스를 반환값으로 넣는다는 것
- return시 자동으로 인터페이스 타입으로 형변환
abstract class AbstractClass {
abstract void abstractClass_abstractMethod();
void abstractClass_method() {}
}
interface InterfaceClass {
void interfaceMethod(InterfaceClass i);
}
class ImplementClass extends AbstractClass implements InterfaceClass {
void abstractClass_abstractMethod() {}
// 매개변수가 인터페이스인 경우
public void interfaceMethod(InterfaceClass i) {}
// 리턴타입이 interface인 경우
InterfaceClass getImplementClass() {
ImplementClass i = new ImplementClass();
return (InterfaceClass) i; // 자동 형변환 가능
}
}
public class Practice {
public static void main(String[] args) {
ImplementClass implementClass = new ImplementClass();
// 매개변수가 interface
implementClass.interfaceMethod(new ImplementClass());
// 반환타입이 interface
InterfaceClass interfaceClass1 = implementClass.getImplementClass();
ImplementClass implementClass2 = (ImplementClass) implementClass.getImplementClass(); // 형변환 생략 불가
}
}
39. 인터페이스의 장점
- 개발시간 단축
- 표준화 가능
- 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.
- 독립적인 프로그래밍이 가능 = class간의 의존성을 떨어뜨린다.
독립적 프로그래밍 예
- 독립적이지 않은 코드
class A {
public void method(B b) { // class B라는 구현체에 의존적이다.
b.method();
}
}
class B {
public void method() {
System.out.println("b");
}
}
class C {
public void method() {
System.out.println("c");
}
}
public class Practice {
public static void main(String[] args) {
A a = new A();
a.method(new B());
a.method(new C()); // class A의 params를 C로 변경해야함
}
}
- 독립적인 코드
interface I {
public abstract void method();
}
class A {
public void method(I i) { // interface에 의존적이다.
i.method();
}
}
class B implements I {
public void method() {
System.out.println("b");
}
}
class C implements I {
public void method() {
System.out.println("c");
}
}
public class My {
public static void main(String[] args) {
A a = new A();
a.method(new B());
a.method(new C()); // class A의 params를 건들일 필요 X
}
}
40. 디폴트 메서드와 static 메서드
- 문제점
- 인터페이스에 추상메서드를 추가하면 해당 인터페이스로 구현한 class 모두 새로 추가된 추상 메서드를 구현해야 된다.
- 인터페이스로 만든 객체들이 많을수록 수정할거리가 많음
- 해결
- default method 이용
interface MyInterface {
void method();
default void defaultMethod(){/* 구현체 작성 */};
}
- 새로 생긴 문제점
- 새로 추가된 디폴트 메서드명 == 기존의 메서드와 이름 -> 중복 충돌
- 해결 규칙
- 인터페이스들의 디폴트 메서드 충돌
- 인터페이스 구현한 클래스에서 디폴트 메서드를 오버라이딩
- 디폴트 메서드와 (인터페이스를 구현한 클래스)의 조상 클래스 메서드 충돌
- 조상 클래스의 메서드가 먼저 상속, 디폴트 메서드는 무시
- 규칙 암기가 싫으면 구현 클래스에 같은 내용으로 오버라이딩 하기
- 인터페이스들의 디폴트 메서드 충돌
public class Main {
public static void main(String[] args) {
Child child = new Child();
child.method1();
child.method2();
}
}
class Child extends Parent implements MyInterface, MyInterface2{
// 1. interface 구현시 defaultMethod 오버라이딩
public void method1() {
System.out.println("method1() in Child"); // overriding
}
// 3. overriding
/*
public void method2(){
System.out.println("method2() in Child");
}
*/
}
class Parent{
// 2. 조상 클래스의 메서드가 상속
public void method2(){ System.out.println("method2() in parent");}
}
interface MyInterface2{
default void method1(){ System.out.println("method1() in YouInterface2");}
}
interface MyInterface{
default void method1(){ System.out.println("method1() in MyInterface");}
default void method2(){ System.out.println("method2() in MyInterface");}
}