JAVA공부 [4. JAVA의 기본 문법(4) ]
1. 🔥 다형성
다형성은 재정의(overriding)을 통해 하나의 함수 모양을 가지고 각기 다른 역할을 하는 것을 의미한다..!
- 오버로딩(overloading)소극적 의미의 다형성에 해당된다.
overriding
오버라이딩은 상속을 통해 메소드 바꿔치기, 덮어쓰기 즉, 재정의를 할 수 있게 되는데 이 부분이 다형성의 확정개념이다.
class Shape{
int x, y;
String color;
Shape(int _x, int _y, String c){
x = _x;
y = _y;
color = c;
}
void draw(){
System.out.println("x = "+x+", y = "+y+" color = "+color);
}
}
class Rect extends Shape{
int w,h;
Rect(int _x, int _y, int _w,int _h, String c){
super(_x,_y,c);
w = _w;
h = _h;
}
// void drawRect(){
// System.out.println("x = "+x+", y = "+y+" color = "+color+" | ("+w+","+h+") ");
// }
void draw(){ // 재정의(overriding) 부모의 메서드를 덮어씀
System.out.println("x = "+x+", y = "+y+" color = "+color+" | ("+w+","+h+") ");
}
}
class Circle extends Shape{
int r;
Circle(int _x, int _y, int _r, String c){
super(_x, _y, c);
r = _r;
}
void draw(){ // overriding
System.out.println("x = "+x+", y = "+y+" color = "+color+" | "+r);
}
}
public class App {
public static void main(String[] args) throws Exception {
Rect a = new Rect(10, 20, 30, 40, "White");
Circle b = new Circle(10, 10, 5, "black");
a.draw();
b.draw();
Shape sh;
sh = a; //업캐스팅
sh.draw(); //Rect draw호출 dynamic binding(동적 바인딩)
sh = b; //업캐스팅
sh.draw(); //Circle draw호출
}
}
- 왜 shape레퍼런스 변수를 만들었는데 shape의 draw가 호출되지 않고 해당 객체의 draw가 호출 될까? (여전히 필드참조 불가능)
dynamic binding(동적 바인딩)
A로부터 상속 받은 B가 존재할 때 B는 A의 메서드를 오버라이딩하였다. 이 때 업캐스팅을 통해 부모의 레퍼런스 변수가 B를 가르켜서 해당 메서드를 호출한다면 자동으로 오버라이딩된 자식의 메서드를 호출하게 된다..!
- 바인딩의 용어 처럼 함수의 이름(오버라이딩된)과 실제함수를 연결시켜주는것 가르키고 있는 객체에는 부모의 메서드 이름과 동일한 즉 오버라이딩 된 메서드가 존재하고 그것을 동적으로 판단하여 실행시키는 것..!
조금 더 다형성의 의미를 확장하여 동적바인딩을 살펴보자면
Shape [] shapes = new Shape[3]; //레퍼런스 변수 3개 배열로 생성
shapes [0] = new Shape(10, 20, "blue");
shapes [1] = new Rect(100, 100, 5, 5,"white");//업캐스팅..
shapes [2] = new Circle(1,2,3, "red"); //업캐스팅..
for(Shape elem : shapes)
elem.draw(); //동적바인딩
elem.draw()
로 하나의 메서드를 여러번 호출하지만 각각 다른 메서드를 호출하고 있다. 즉, 다형성이다.
그때 그때(실행시간 중/ 런타임 중) 해당 객체가 어떤 형태인지 보고 draw를 맞게 호출하게 때문에 동적이라고 할 수 있다.
객체지향의 꽃이며 코드가 간결해지고 읽기 편해짐!
내가 생각한 예제
- 게임을 만든다고 예를 들자면 몬스터라는 클래스를 상속 받은 오크, 고블린, 슬라임 등등이 있다.
- 이 때, 공격이라는 메서드는 몬스터라는 클래스로 부터 상속받아 각각 재정의 되어 있다.(전혀 다르지 않은 경우 재정의하지 않아도 됌 하지만 일반적으로 공격메서드의 경우 전부 각각에 맞게 재정의)
- 그렇다면 코드를 일반화 하는 과정에서 몬스터클래스들(상속받은 클래스 포함 업캐스팅을 말함)의 공격메서드를 실행한다면
- 각각 클래스에 맞게 메서드 실행코드를 만든다면 코드가 길어지고 보기 않 좋아 진다. 하지만 위와 같이 배열로 일반화가 가능하다.
확장 개념(링크드 리스트)
class Shape{
Shape next; //레퍼런스 변수(포인터 같이 동작함)
// 부모에서 next변수를 만드는 순간 자식들은 기본적으로 가지고 있게 된다.
생략
}
public class App {
public static void main(String[] args) throws Exception {
Shape start = new Shape(1, 2, "blue"); //해당 객체에는 next 즉, shape를 가리키는 레퍼런스 변수가 존재
start.next = new Rect(10,10,3,3,"red"); // 해당 레퍼런스 변수를 객체를 가리키게함
start.next.next = new Circle(3, 3, 3, "yellow"); //반복 현재는 null을 가리킴
Shape cur = start;
while(cur != null){
cur.draw();
cur = cur.next; //이동
}
}
}
- 이렇게만 봐도 c언어보다 보기 좋고 이해가 퐉! 된다..!
오버라이딩 기능추가 개념
지금까지는 오버라이딩이 재정의 덮어쓰기의 기능이였다면 super키워드를 이용하면 기능추가의 개념으로 이해할 수 있다.
class Shape{
int x, y;
String color;
Shape(int _x, int _y, String c){
x = _x;
y = _y;
color = c;
}
void draw(){
System.out.println("x = "+x+", y = "+y+" color = "+color);
}
}
class Rect extends Shape{
int w,h;
Rect(int _x, int _y, int _w,int _h, String c){
super(_x,_y,c);
w = _w;
h = _h;
}
void draw(){
super.draw(); //부모의 draw를 호출 한뒤
System.out.println("| ("+w+","+h+") "); //내가 원하는 기능 추가
}
}
- 이렇게 super키워드를 사용하면 코드 재사용을 줄이고 충돌을 예방할 수 있다.
상속받은 클래스는 super키워드를 통해 부모의 원본 메서드를 호출할 수 있다.
정리
- 수퍼클래스의 참조변수는 서브클래스 객체를 참조할 수 있다.
- 역은 특별한 경우에 성립하게 되는데 원래 가르키고 있는 객체의 참조변수로 명시적 변환이 가능하다.
- instanceof연산자는 실제 그 클래스가 맞는지 확인하는 연산자(실수 없는 코딩을 위해 사용함)
- 다형성은 코드의 간결성, 통일성, 협업을 할 때 아주 유용함
- 어떠한 타입으로도 업캐스팅하고 싶다면 Object타입으로 정의하는 것이 좋다.
1.1. 추상 메서드
추상 메서드는 두가지 목적으로 분류가 가능하다 첫 번째는 shape같이 도형이긴 하지만 어떤 도형인지 모르기 때문에 그리지 못한다.. 즉 추상적이라 구체적인 방법이 마련되지 않았다. 두번째는 이러한 메서드가 있으니 상속받은 클래스들의 구현을 강제할 수 있다.
-
정리하면 추상 메서드는 아직 구체화 되지않았지만 꼭 필요할 것 같은 것을 말한다.
-
또한 클래스에 한가지 추상메서드를 만들면 해당 클래스는 추상클래스로 인식되게 된다.
abstract class Shape{ //추상 메서드가 있어 추상 클래스로 선언되어야 함
int x, y;
String color;
Shape(int _x, int _y, String c){
x = _x;
y = _y;
color = c;
}
abstract void draw(); // 추상 메서드 선언, 메서드 바디 없음
}
class Line extends Shape{ //빨간줄 에러..! 반드시 정의해야하는 추상 메서드가 없음
String dir;
Line(int _x,int _y, String c, String d){
super(_x, _y, c);
dir = d;
}
}
그렇다면 왜 사용하는 거지?
추상 클래스(즉 추상메서드)는 객체화가 되지 않기 때문에(추상) 자기 자신을 이용해서 서브클래스를 만들어라 라는 의미를 가지고 있다..!
-
생물 -> 사람, 개구리, 장수풍뎅이 등등… 생물의 행동을 정의할 수 없고 추상적으로 표현 가능 즉, 메서드가 존재한다면 숨을 쉰다정도?
-
하지만 레퍼런스 변수로는 사용가능하다!(업캐스팅) Object개념!
정리
- 추상메서드란 선언은 되어있지만 구현이 되지 않았고 abstract키워드로 선언한다.
- 추상 메서드는 서브클래스에서 구현을 강제함
- 추상 클래스는 추상 메서드를 하나라도 가진 클래스..! (abstract표기 강제함)
- 추상 메서드가 없지만 abstract러 선언된 클래스
- 추상 클래스는 객체를 만들 수 없다..! 참조변수로는 사용가능
- 즉 동적 바인딩에 활용할 수 있다.
이러한 개념들은 계층적인 상속관계를 가지는 클래스구조를 만들 때 사용된다.
1.2. interface
인터페이스는 클래스와 클래스를 연결해주는 것이 인터페이스
- 자바는 c++과 다르게 다중상속을 제한하고 있기 때문에 무선전화기(전화기로 부터 상속받은)에 바테리를 넣고 싶다면 interface를 상속받아 부가적인 기능을 따로 해결해야한다..!(약속)
만들때 interface키워드 상속받을 때 implements키워드 사용
interface NetworkControl{
void turnOn(); //abstract가 붙어있음(생략가능) interface자체가 추상적이기 때문..!
void turnOff();//public또한 생략되어 있음
//public abstarct void turnOff(); 풀네임
}
class OldTV{
}
//클래스 상속과 인터페이스 상속도 같이 일어날 수 있음
class TV extends OldTV implements NetworkControl{
public void turnOn() { //interface에서는 생략했지만 접근지정자는 통일해야함
System.out.println("TV ON");
}
public void turnOff() { // interface를 implements하는 순간 매서드 구현을 강제함
System.out.println("TV OFF");
}
}
class Aircon implements NetworkControl{
public void turnOn() {
System.out.println("Aircon ON");
}
public void turnOff() {
System.out.println("Aircon OFF");
}
}
public class InterfaceText {
static void On(NetworkControl c){ // 인터페이스 참조 변수로 받음 즉, 업캐스팅
c.turnOn();
}
public static void main(String[] args){
TV tv = new TV();
Aircon ac = new Aircon();
On(ac);
On(tv);
}
}
-
interface는 추상메서드밖에 표현을 못함 즉, 인터페이스 참조변수를 통해 업캐스팅한 과정은 기능적인 측면이라고 볼 수 있음
-
또한 어떠한 메서드가 보장된다, 구현되어 있다를 명확하게 알려줌
interface NetworkControl{ //원격연결 기능
void turnOn();
void turnOff();
}
interface Portable{ //충전기능
public abstract void charge(); //public abstarct생략가능..!
}
class TV implements NetworkControl, Portable{
public void turnOn() {
System.out.println("TV ON");
}
public void turnOff() {
System.out.println("TV OFF");
}
public void charge() {
System.out.println("Charge..!");
}
}
- 이렇게 인터페이스는 여러개를 추가할 수 있다..!
정리하자면 본질은 TV이지만 NetworkControl, Portable인터페이스의 기능을 지닌 클래스라고 할 수 있다..!
특정기능의 구현을 보장함
추가적인 부분 && 정리
- final 상수는 추가 가능함..!
public static final int MAX = 100;
- 기능 내부에서 사용할 수 있는 상수값을 넣어줘야 하기 때문..!
- 디폴트 메서드 또한 구현 가능..!
- 구현하지 않으면 디폴트메서드가 실행된다.
- private메서드는 해당 인터페이스 내에서 호출하여 사용
- 인터페이스 객체 생성 불가능
- 인터페이스 타입의 레퍼런스 변수는 생성가능
- 인터페이스 다중 상속 가능..!
- 인터페이스 끼리의 상속도 가능(extends)
- 인터페이스의 기능을 추가,확장한다는 의미
추성클래스와 인터페이스의 유사점 객체를 생성할 수 없으며 상속을 위한 슈퍼클래스로만 사용, 다형성의 목적을 둔다.
정리
- 인터페이스의 주된 용도는 약속된 기능을 보장한다.(업캐스팅을 통한 인터페이스를 상속받은 클래스들의 기능을 일괄적으로 다룰 수 있다.)
- 하나의 클래스가 두 개의 인터페이스를 구현할 수 있다..!
2. 인터페이스의 활용
import java.util.Arrays;
//정렬을 위한 유틸패키지의 Arrays클래스 가져옴
class MyData{ //int형 데이터를 저장하는 클래스
MyData(int in){
data = in;
}
int data;
}
public class InterfaceText2 {
public static void main(String[] args){
int [] arr = {1,2,54,9,58,6,6,4,2}; //무순정렬 배열
for(int a : arr)
System.out.println(a);
System.out.println("-----------------------");
Arrays.sort(arr); //Arrays클래스의 static메서드, 정렬을 실행함
for(int a : arr)
System.out.println(a);
System.out.println("-----------------------");
MyData [] myArr = new MyData[10];
for(int i = 0;i < myArr.length; i++)
myArr[i] = new MyData((int)(Math.random()*100)); //난수발생(int)형변환
for(var a : myArr){
System.out.println(a.data);
}
System.out.println("-----------------------");
Arrays.sort(myArr); //sort에 Object도 정렬이 가능하기 때문에 내가 만든 클래스의 객체를 정렬
//error출력! java.lang.Comparable(인터페이스)를 구현하지 않았기 때문에 오류 발생
for(var a : myArr){ //var키워드 사용
System.out.println(a.data);
}
}
}
- MyData객체 myArr이가 정렬이 실행되지 않은 이유는 Comparable(인터페이스)를 구현하지 않았기 때문..!
가능하게 할려면??
class MyData implements Comparable{ //Comoarable인터페이스 상속
MyData(int in){
data = in;
}
int data;
@Override //오버라이딩키워드..! 오버라이딩메서드인지 확인할 수 있다..!
public int compareTo(Object o) { //해당 인터페이스 정렬에 사용하는 메서드 구현
if(!(o instanceof MyData)) return 0;
MyData in = (MyData)o; //다운캐스팅
return data - in.data; //같으면 0반환, 크면 양수, 작으면 음수
}
}
- 기본형이 Object타입으로 모든 클래스를 받아서 내가 명시적으로 다운캐스팅을 통해 비교메서드를 작성해주면 모든 데이터형을 정렬할 수 있다..!
활용++
interface Printable{ //인터페이스 구현
void print(); //public abstract 생략
}
class MyData_2 implements Printable{ //Printable상속
MyData_2(int in){
data = in;
}
int data;
@Override //문법적 오류 검증
public void print(){
System.out.println("data : "+data);
}
}
public class InterfaceText2 {
public static void printAll(Printable []arr){ //Printable 참조변수 배열로 인자값을 받음 즉. 업캐스팅
for(Printable e : arr)
e.print(); //동적바인딩(오버라이딩에 의한)
}
public static void main(String[] args){
MyData_2 [] myArr = new MyData_2[10];
for(int i = 0;i < myArr.length; i++)
myArr[i] = new MyData_2((int)(Math.random()*100));
printAll(myArr);
}
}
3. 내부클래스
내부클래스는 클래스안에 선언된 클래스를 의미한다..!
- 내부적으로 잠깐 쓰이는 클래스를 정의해서 사용하는 것인데 이는 c언어의 구조체와 유사하다!
class MyTriangle{
private class Point{ //private으로 가려놓았기 때문에 밖에서 보이지 않음
int x, y;
}
Point[] pts;
MyTriangle(){
pts = new Point[3];
}
}
public class InterfaceText2 {
public static void main(String[] args){
MyTriangle a = new MyTriangle();
}
}
- 사용목적은 어디선가 사용했던것 같은 클래스이거나 내부적으로만 사용되는 클래스를 만들 때 사용한다. -> 클래스 이름의 충돌을 막아줌
이러한 내부클래스를 인스턴스 클래스라고 부름
중요
내부 클래스에서는 내부클래스가 속한 클래스 즉, 내부클래스의 외부클래스의 필드에 접근이 가능하다.
- 내부클래스 관점에서는 전역변수처럼 사용 가능..!
4. 무명클래스
클래스 몸체는 정의되지만 이름이 없는 클래
인터페이스는 이름은 존재하지만 메서드의 몸체가 정의되지 않는다..부족한 부분을 채워주자,,
interface Printable{ //Printable을 상속받는 클래스들은 print기능을 강제함
void print();
}
public class InterfaceText2 {
static Printable head;
static void print(Printable p){ //Printable을 가지고 있는 모든 객체를 Printable레퍼런스 변수로 받음
p.print(); // 해당 객체의 print기능 실행 다형성 유도
}
public static void main(String[] args){
head = new Printable(){ //무명클래스
@Override
public void print() {
System.out.println("무명클래스"); //
}
};
head.print();
Printable o = new Printable(){
@Override
public void print() {
System.out.println("객체에 넣어진 무명클래스");
}
};
print(o);
print(new Printable(){
@Override
public void print() {
System.out.println("인자값으로 전달된 무명클래스");
}
});
}
}
- 무명클래스란 인터페이스나 추상클래스의 구현이 강제되는 메서드를 무명클래스로 클래스 내부에서 구현하여 따로 클래스를 만드는 번거로움 없이 해당 자리에서 구현할 수 있는 클래스이다..!
정리
- 무명클래스 작성시 new다음에 적어야하는 것은 추상클래스나 인터페이스의 이름이다.
- 특정 인터페이스의 특정 메서드의 값을 넘겨주고 싶을 때 사용하면 따로 클래스를 만들 지 않을 수 있어서 간편하다.(가독성++)
5. 람다식
최근 언어의 추세에 맞게 새로 쓰이는 문법이다. 인터페이스를 더욱 간단하게 사용가능하다.
여러가지 조건이 존재한다.
- 함수가 하나일 경우에 사용가능하다.
- 명확한 조건일 경우 해당 조건들은 생략 가능하다.
- 조건들을 생략해야지 사용자의 실수가 줄어들기 때문
print(new Printable(){ //무명클래스
@Override
public void print() {
System.out.println("인자값으로 전달된 무명클래스");
}
});
//람다식
print(()->System.out.println("람다식으로 표현한 무명클래스.."));
- 람다식은 명확한 값을 전부 지우고 정말 필요한 부분 함수의 정의부분(이름부분 다 날림)만 작성하는 것!
왜냐하면 print라는 메서드는 위에서도 그렇고 Printable이라는 인터페이스를 강제하기 때문에 생략가능.. 마찬가지로 내부 메서드가 한가지 print만 존재하기 때문에 생략가능…하다!
() -> 123.45
라는 람다식은 이름은 명확하니 적지않고 return값이 123.45라는 무명클래스를 넘겨 주는 것
double myMath() { return 123.45; }
interface MyNum{
int getNum();
}
interface MyNum_2{
int getNumR(int min,int max);
}
public class InterfaceText2 {
public static void main(String[] args){
MyNum a = new MyNum(){ //무명클래스를 사용
@Override
public int getNum(){
return (int)(Math.random()*100);
}
};
//람다식 사용
MyNum b = () -> (int)(Math.random()*100);
MyNum_2 c = (int min,int max) -> (int)(Math.random()*((max - min) + 1) + min);
System.out.println(a.getNum());
System.out.println(b.getNum());
System.out.println(c.getNumR(2, 7));
}
}
- 이 처럼 인자값을 넘겨줄 수 도 있다.
앞서 사용한 compareto인터페이스도 활용가능
댓글남기기