18일차(1)/java(20) : 추상클래스, 인터페이스
<Weapon> 이라는 추상클래스를 생성
2022.10.28 - [국비교육] - 17일차(2)/java(17) : Abstract Class
[ 추상 클래스 ]
: 메소드를 정의할 때는 해당 메소드가 호출되었을 때 어떤 코드를 수행할지를 { } 안에 명시하는데,
추상클래스는 이를 명시해 놓지 않은 완성되지 않은 메소드를 갖고있는 클래스를 말한다.
(완성되지 않은 메소드를 하나라도 가지고있으면 추상클래스이다)
- 미완성 메소드를 하나라도 가지고 있으면 클래스에 abstract 를 붙여 추상 클래스임을 표시한다.
- 반제품. 일부는 완성, 일부는 미완성인 상태의 클래스
일부 만들어졌으나 완벽하게 만들어지지는 않은 것. ex) 밀키트?
- data type 역할을 할 수 있다.
- 생성자는 존재하지만 추상클래스 단독으로는 객체 생성 불가
public abstract void attack();
- 메소드의 모양은 완벽하다. 접근지정자, 리턴타입, 메소드명, 매개변수 등은 전부 정해져 있고 {} 만 비어 있는 상태.
Weapon w=null;
- 추상클래스 Weapon 타입의 변수든 필드든 만들 수 있다.
w.prepare(); 형태로 클래스의 참조값을 담은 지역변수에 점을 찍어서 사용 가능.
- 단, 호출할 때 어떤 메소드를 실행할 수 없을 뿐이다.
- 데이터 타입으로 쓸 수 있다는 말은
미완성이긴 하지만 그 변수나 필드에 대한 사용설명서 역할은 충분히 할 수 있다는 뜻!
- 실제로 run 해보면 오류 발생 → NullPointerException 오류.(참조값이 없는데 메소드를 호출하려고 하면 발생하는 오류)
- 그러면 참조값을 얻어내려면?
Weapon w=new weapon 으로는 쓸 수 없다.
미완성인 메소드가 존재하는 상태에서 저 추상클래스가 단독으로 객체생성을 할 수는 없기 때문에!
Weapon w=new MyWeapon();
- Weapon을 상속받는 별도의 MyWeapon 클래스를 생성한다.
(add an implemented method : 자식클래스에서 override하는 것)
- 해당 추상클래스의 하위클래스 안에서 미완성 메소드에서 어떤 작업을 할지 정하면 된다.
- 자식 객체는 타입을 부모타입으로 받을 수 있기 때문에!
- 상속관계는 위와같기 때문에 new MyWeapon(); 은 3가지 타입으로 받을 수 있다.
→ object, Weapon, MyWeapon
Q) 클래스를 이왕 만들거면 완성시키지 왜 미완성의 메소드를 만드나?
A) java에서 기본으로 제공해주는 클래스 외에 여러 클래스의 묶음을 다운받아서 사용할 일이 많다.
규모가 작으면 유틸리티, 규모가 크면 프레임워크 등등...
해당 유틸리티나 프레임워크 개발자가 여러분을 위해 대신 복잡한 기능을 특정 메소드안에 구현을 해준다.
그러면 해당 메소드를 호출하는 것만으로도 복잡한 작업을 간단히 해결할 수있다.
어떤 기능은 유틸리티 사용자(여러분)이 어떤 용도로 사용할지 알 수 없기 때문에
완성하지 않고 미완성인 상태로 남겨두는 것이다.
대신 메소드의 모양은 완벽하기때문에 완성한 메소드를 어떻게 사용할지 미리 정해놓을 수 있다.★
복잡한 기능이나 기반시설은 다 갖추어 두고 몇개의 메소드만 미완성인 상태로 배포하여
개발자들이 각자 새로운 기능을 구현할 수 있도록 한 것.
Weapon w=null;
w.prepare();
w.attack();
- 위와 같은 형태로 만들어져 있다면,
아직 null상태이고 들어가있는 참조값은 없지만 어떻게 사용할지 정해둘 수 있는 것처럼!
- 추상클래스에 만들어진 완벽한 메소드는 대부분 미완성인 메소드를 만드는데 뒷받침이 되는 메소드이다.
개발자들이 각자 다른 용도로 활용하기 위해 미리 만들어둔 것!
<MainClass04>
package test.main;
import test.mypac.Weapon;
public class MainClass04 {
//내부 클래스
static class YourWeapon extends Weapon{
@Override
public void attack() {
System.out.println("공중 공격을 해요!");
}
public static void main(String[] args) {
Weapon w1=new YourWeapon();
useWeapon(w1);
//Local Inner Class
class OurWeapon extends Weapon{
@Override
public void attack() {
System.out.println("지겹다 이제 아무나 공격하자!");
}
}
Weapon w2=new OurWeapon();
useWeapon(w2);
}
public static void useWeapon(Weapon w) {
w.prepare();
w.attack();
}
}
}
- 하지만 MyWeapon은 추상클래스 타입을 만들기 위한 매개체 역할을 할 뿐,
추가 메소드도 없고, 사실상 따로 클래스를 만들 필요가 없다.
(게다가 만약 추가 메소드를 쓰고싶다면 Weapon타입으로는 받지 못하고 MyWeapon타입으로 받아야 함.)
- MyWeapon이란 클래스를 굳이 파일로 만드는 대신 사용할 수 있는 문법
1) 클래스안에 클래스 정의 : inner class
2) 메소드안에 클래스 정의 : local inner class
Weapon w1=new YourWeapon();
useWeapon(w1);
- 위와 같은 형태로 클래스 안/메소드 안에 작성하는 것!
<MainClass05> 익명의 inner class 이용하기
- 클래스인데 이름이 없는 클래스이다. ( ) 앞의 부분을 extends/implement하는 클래스!
(클래스면 extends, 인터페이스면 implements)
- 익명 클래스는 상속만 받고 메소드를 추가로 정의하지 않을 때 사용할 수 있다.
상속받아서 메소드를 재정의(override)할 필요만 있을 때 사용!
- 해당 클래스는 데이터 타입으로 쓰지 않는다. 즉 클래스안에서 다른 메소드를 추가해봤자 사용할 수 없다.
- 필드를 선언하고, 필드에 추상클래스의 참조값을 바로 얻어내서 대입하는 것.
- 필드 선언은 순서가 의미가 없다.
메소드안에는 순서대로 실행할 코드가 들어있지만 필드는 그저 어떤 공간을 만들어서 어떤 값을 넣어두는 것.
위의 new Scanner와 마찬가지!
- 필드에 붙은 static은 static 메소드 안에서 사용할 예정이기 때문에 필요한 것이다.
static 메소드 안에서 static이 아닌 필드는 허상에 불과하다. static이 없으면 오류 발생!
- w2 라는 지역변수를 선언해서 / 참조값을 얻어내고 / 값을 대입하는 것 과 같다.
- 메소드 호출하면서 static 필드에 미리 준비된 값을 전달하기
- 메소드 호출하면서 지역변수에 미리 준비된 값을 전달하기
- 메소드 호출하면서 값을 즉석에서 만들어서 전달하기
- 메소드를 호출하면서 전달할 값이 필드에 있는 경우도 있고, 지역변수에 있을 수도 있고, 아니면 즉석에서 만들어서 전달할수도 있다. 코드 보는 눈을 길러 구분하기!
- 메소드를 호출하면서 값을 즉석에서 만들어서 전달하기
public void useNum( int num ){ }
- 호출: useNum(999);
public void useString( String str ){ }
- 호출: useString( "kim" ); == String str="kim"
(str타입의 참조값을 얻어내는식)
public void useScanner( Scanner s ){ }
- 호출: useScanner( new Scanner() ); == Scanner s=new Scanner();
public void useWeapon( Weapon w ){ }
- 호출: useWeapon ( new Weapon(){} ); == Weapon w=new Weapon(){};
- 메소드를 호출할 때 괄호 안에 배치하는 것만으로도 매개변수에 알아서 대입된다.
- 지역변수를 만들어 대입연산자를 사용해서 쓰는 식과 같은 것이다.
- 매개변수에 들어갈 값을 괄호 안에 배치하면 메소드 호출 시점에 자동으로 대입된다. 대입연산자를 떠올려보자!
[ 인터페이스 ]
- 추상 메소드만 정의 가능하다. 완성된 메소드는 아예 가질수없다.
메소드의 모양만 정의 가능!
Remocon r=null;
r.up();
r.down();
- 이렇게는 표시할 수 있다. 사용설명서(데이터타입)으로는 사용할 수 있다.
(물론 실행하면 null 이외의 값을 넣어주어야 한다)
- 클래스가 아닌 인터페이스라는 이름으로 만든다.
- 필드는 static final 만 가질 수 있다.(생략해도 무방함. 어차피 이 속성만 가질 수 있으므로..)
- 생성자 없음. 객체 만들 수 없음. 단지 구현할 메소드의 모양만 강제하는 효과를 갖는다.
- 필드가 필요한 경우라 해도 각각의 객체가 만드는 고유한 필드는 가질 수 없고(객체가 만들어지지 않으므로) static 필드만 만들어질 수 있다.
- static final 필드는 클래스와 동일하게 인터페이스명에 점 찍어서 사용 가능!
ex) Remocon.COMPANY
- 추상클래스는 extends, 인터페이스는 implements 사용
public class MyRemocnon implements Remocon, A, B
- 위 형태로 원한다면 여러개의 인터페이스를 다중구현하는 것도 가능하다.
- 인터페이스로 객체를 생성하면 인터페이스 타입. 객체생성은 안되지만 타입 역할은 가능하다.
- 따로 메소드가 있는 것도 아니고, 타입으로만 사용된다. 구현체에 불과하다.
→ 익명 클래스 사용이 가능하다.
- 추상클래스와 같은 패턴이 가능하다. 하지만 인터페이스는 오직 구현되지 않은 메소드만을 가질 수 있다는 것이 차이점!
- 동일하게 필드 값 활용 / 지역변수의 값 황용 / 값 즉석에서 얻어내기 가능
- 어떤 클래스를 만들지 아직 알 수 없지만,
객체가 어떻게 동작할지는 정해둔 상태에서 그 안에서 xxx라는 클래스를 프로그래밍하는 것.
- 인터페이스도 미완성인 채로 남겨두었다가 필요한 개발에 사용될 수 있도록 하는 도구이다.
어떻게 사용할지 정해둔 상태!
- 인터페이스를 구현해서 클래스를 만든다면, 그 클래스가 완성되기 전에도
그 클래스를 활용해 만들어낸 객체를 어떻게 사용할지 미리 코딩해둘 수 있다.
(만들어둔 메소드를 override해서 클래스를 만들기때문에 미리 코드를 작성할 수 있음)
public void up(Tool t);
- 위와 같이 인터페이스를 정의할 수도 있다. Tool이라는 객체를 넣으면서 up이라는 메소드를 사용할 수 있도록.
- 메소드의 인자로 도구를 전달하는 것이다.
- 실행하면 Tool객체가 들어온다는 가정 하에 코딩하면 된다.
ex) 자동차 부품을 납품한다고 생각하기. 납품을 해야 한다면 그 회사의 규격대로 만들어야 한다.
그 규격을 맞추는 것이 class나 interface를 상속/구현 해서 사용하는 일!
- 이미 개발의 틀은 짜여져 있다. 메인메소드와 기본 틀은 갖춰져 있는 상태에서, 몇 개의 클래스만 만들어서 끼워넣으면 되는 것. 부분 프로그래밍이라고 생각하면 된다.
일부 핵심적인 동작을 만들어서 끼워넣으면 그것이 부품의 역할을 해서 무언가를 만드는 것이다.
ex) 현대자동차에 헤드라이트를 납품한다.
→ 자유도가 줄어든다. 장착할 모양도 이미 정해져 있다. 그 안의 라이트부품을 마음대로 만드는 정도.
전기가 지금 연결되어 있지는 않지만, 자체적으로 전기를 공급하는것은 아니고,
헤드라이트를 만들고 장착될 때 전기가 들어온다는 가정 하에 부품을 만들면 된다.
- 부분 프로그래밍이란, 이 Tool이라는 객체는 적절한 때에 들어올 것이니 Tool을 사용할 메소드만 코딩해두면 된다는 의미!
[ 프레임워크 ]
- 웹 프레임워크 / Spring 프레임워크 등...
ex) 영화를 만든다고 하자.
100개의 프레임을 촬영해서 합치면 하나의 영화가 되는 것이라면,
Frame 1 / Frame 2 / Frame 3 / ... / Frame 30 / ... / Frame 40 / ... / Frame 100
프레임은 반드시 시간 순서대로 활영해야하는건 아니다. 순서가 중요하지 않다.
100개의 프레임이 다 촬영되기만 하면 된다.
만일 내가 30, 40 프레임에만 나오는 단역배우라면, 그 프레임 촬영에 참여해서 상황에 맞게 연기하면 된다.
그 이전, 이후 장면에 대해서는 하나하나 세세히 알 필요가 없다.
부분적으로 출연했지만 저 프레임이 합쳐져서 하나의 거대한 영화가 만들어지는 것이다.
- 이것이 하나의 어플리케이션이라면.
수많은 코드블럭 중에서 대부분의 기반시설들은 다 프로그래밍이 되어있다.
일부분만 부분 프로그래밍해서 합치면 하나의 앱이 완성되는 것이다.
→ 이렇게 일부 프레임만 프로그래밍할 수 있게 만들어진 것이 프레임워크이다.
- java의 main 메소드는 보통 다른곳에 있다.
어떤 장소에 핵심적인 틀은 만들어져 있기 때문에, 메인메소드는 만들 일이 거의 없다. 일부 부분만 동작을 정의하면 된다.
대부분 어떤 클래스, 어떤 인터페이스를 구현해서 만들게 될 것!
내 클래스로 생성된 객체가 적절한 시점에 특별한 기능을 할 수 있도록 작업하는 것.
- 추상메소드가 1개만 있는 인터페이스는 좀 특별하다.
- 호출시 모양을 특이하게 만들어서 쓸 수 있다! 메소드가 오직 하나인 경우에만!
- Drill class에 Hole 이라는 추상메소드가 1개 있는 상태
useDrill( ( )->{ } );
- 메소드 하나짜리는 이렇게 줄여서 사용할 수 있다.
- 객체생성, 메소드명을 생략하고 ( )->{ } 중괄호 안에서 override 해야 하는 메소드만 수정한다.
- 메소드명을 굳이 쓰지 않아도 된다. 어차피 메소드가 한 개밖에 없기 때문에.
- 메소드를 굳이 구분할 일이 없으니까 ()->{} 을 그 메소드라고 생각한다.
바깥의 자질구레한 것들을 다 생략하고 메소드 override부분만 작성하는 것.
- 메소드가 여러개 있을 때 이렇게 작성하면 오류가 발생한다!
<Joinner> 인터페이스 생성
package test.mypac;
//추상메소드를 1개만 만들도록 강제하는 역할 ( ()->{} 형태로 사용할 수 있도록 보장 )
@FunctionalInterface
public interface Joinner {
public String join(String one, String two);
}
- 인자 2개를 받아서, 2개의 문자열을 합쳐서 합친 문자열을 리턴해주는 인터페이스라고 가정한다.
<MainClass05>
package test.main;
import test.mypac.Joinner;
public class MainClass05 {
public static void main(String[] args) {
Joinner j1=new Joinner() {
@Override
public String join(String one, String two) {
return one+two;
}
};
String result1=j1.join("안녕","하세요");
Joinner j2=(String one, String two)->{
return one+two;
};
//메소드 안에서 특별히 실행할 코드가 없다면, 아래와 같이 중괄호를 생략하고 리턴할 값만 명시하면 된다.
Joinner j3=(one, two)-> one+two; //람다식
}
}
- 추상메소드 하나짜리 인터페이스의 경우, @FunctionalInterface 를 넣어준다. (@Fun 입력하고 ctrl+space)
- 추상메소드를 1개만 만들도록 강제하는 역할! (여러개 만들면 자동으로 오류 발생)
- ()->{} 형태로 사용할 수 있도록 보장(고정)하는 효과를 갖는다.
- ctrl+space로 자동완성해서 익명 inner class 만들기!
- 디버그모드에서 확인해보면, 두 문자열이 합쳐져서 대입되는 것을 볼 수 있다.
- ctrl+space 하면 해당 괄호 안에 들어갈 내용이 나온다.
인자로 전달되는 타입이 뭔지, 뭘 리턴해주는지 보여준다.
[ ()->{} 형태로 쓰는 다양한 방법 ]
1) 기본형. 리턴값 명시
Joinner j2=(String one, String two)->{
return one+two;
};
2) 타입 생략. 리턴값 명시
Joinner j2=(one, two)->{
return one+two;
};
- 화살표 함수 형태에서 타입은 생략 가능하다.
매개변수의 이름만 잘 적으면 된다.
3) 타입 생략, {}, 리턴 예약어 생략
Joinner j3=(one, two)-> one+two;
- j2 안에는 실행할 코드가 따로 없고 return예약어만 있다.
이렇게 메소드 안에서 특별히 실행할 코드가 없는 경우, 중괄호, 예약어를 생략하고 리턴할 값만 명시하면 된다.
- 1을 2로, 2를 3으로 간단하게 요약해서 쓸 수 있다.
- 2개의 값을 전달해주면 두 값을 합친 값을 j3에 리턴해주겠다. 라는 의미로, 형태를 기억하기!
- 이 한줄 코드를 '람다식' 이라고 부른다.
(자바의정석 3판 794p 참조)
'국비교육(22-23)' 카테고리의 다른 글
18일차(3)/java(22) : Util Class (ArrayList, HashMap) (0) | 2022.10.31 |
---|---|
18일차(2)/java(21) : Generic Class (0) | 2022.10.31 |
17일차(4)/java(19) : Interface (0) | 2022.10.30 |
17일차(3)/java(18) : Inner Class, Anonymous Class (1) | 2022.10.30 |
17일차(2)/java(17) : Abstract Class (0) | 2022.10.28 |