class A {} class B {} // 대를 거쳐가며 상속받는 것은 다중상속이 아니다. class D extends A {} class E extends D {} // interface IA {} interface IB {} // 인터페이스 상속해서 인터페이스 선언시 extends , 다중상속 가능 interface IC extends IA, IB {} // 인터페이스 상속해서 class 선언시 implements , 다중상속 가능 class F implements IA, IB {} class G extends B implements IA, IB {} // public class Test107 { public static void main( String[] args ) { //... } }
인터페이스는 다중상속
을 지원한다
인터페이스의 메서드들은 선언되었지만 정의되지 않기 때문에 함수 포인터가 동시에 두개의 함수 선언을 가리키는 일은 발생하지 않는다.
interface ICalc { public int execute( int i ); } // class Plus implements ICalc { private int data = 0; public Plus( int j ) { this.data = j; } public int execute( int i ) { return i + data; } } // class Minus implements ICalc { private int data = 0; public Minus( int j ) { this.data = j; } public int execute( int i ) { return i - data; } } // class Multi implements ICalc { private int data = 0; public Multi( int j ) { this.data = j; } public int execute( int i ) { return i * data; } } // public class Test109 { public static void main( String[] args ) { ICalc ic = new Plus( 5 ); System.out.println( ic.execute( 3 ) ); // 5 + 3 ICalc ic2 = new Minus( 2 ); System.out.println( ic2.execute( 3 ) ); // ICalc[] l = new ICalc[4]; l[0] = new Plus( 3 ); l[1] = new Minus( 1 ); l[2] = new Plus( 4 ); l[3] = new Minus( 3 ); // int start = 10; for( int i = 0 ; i < l.length ; i++ ) { start = l[i].execute( start ); } System.out.println( ": " + start ); } }
Command Pattern
동작하나를 인스턴스로 만들어서 활용하는 기법
동작 하나를 인스턴스로 만들어서 미리 일련작업을 만들어 저장해 놓으면 필요할때 반복문 돌려서 한꺼번에 실행할 수 있다.
interface IGreet { public String greet(); } // Game Player class HelloGreet implements IGreet { public String greet() { return "Hello"; } } // class MerciGreet implements IGreet { public String greet() { return "Merci"; } } // 과금 Item class SharpDeco implements IGreet { private IGreet ig = null; public SharpDeco( IGreet i ) { this.ig = i; } public String greet() { return "#" + ig.greet() + "#"; } } // class StarDeco implements IGreet { private IGreet ig = null; public StarDeco( IGreet i ) { this.ig = i; } public String greet() { return "*" + ig.greet() + "*"; } } // class DoubleDeco implements IGreet { private IGreet ig = null; public DoubleDeco( IGreet i ) { this.ig = i; } public String greet() { return ig.greet() + ":" + ig.greet(); } } // public class Test110 { public static void main( String[] args ) { IGreet ig = new SharpDeco( new DoubleDeco( new MerciGreet() ) ); System.out.println( ig.greet() ); // #:Merci:# } }
HelloGreet 과 MerciGreet 를 플레이어의 게임 캐릭터라고 생각하면 SharpDeco, StarDeco, DoubleDeco 를 과금 아이템이라고 생각하면 이해하기 편하다.
예를 들어 닉네임을 꾸미고 싶으면 아이템을 구매해 닉네임에 적용하는 것이다. ( 원하는 만큼!! )
public class Test111 { public static void main( String[] args ) { Object t = 100; System.out.println( t.getClass().getName() ); // java.lang.Integer // Integer t2 = new Integer( 200 ); Integer t3 = t2; // 이런 경우 ( Integer 형 변수를 int 변수에 대입하는 ) // 컴파일러는 자동으로 t3.intValue() 를 호출하게 된다. 이런 것을 Unboxing 이라고 한다. int j = t3; System.out.println( j ); // int k = t; // Integer 형 참조형 변수는 Unboxing 되지만 Object 형 변수는 Unboxing 안된다 } }
java 는 int*
, double*
같은 자료형 변수의 기억공간에 대한 포인터 개념이 아예 없다. 그 대안으로 제시한 것이 Wrapper Class
int - Integer
double - Double
float - Float
char - Character
boolean - Boolean
Object t = 100;
: t 는 인스턴스를 가리키기 위한 용도의 참조형변수. 100 은 정수값
t.getClass().getName()
: t 가 가리키는 인스턴스를 생성한 클래스명
Object t = 100;
이런 코드가 보여지면 컴파일러는
Object t = new Integer(100);
이렇게 바꿔(씌워)준다.
"참조형 변수에 값을 대입해야 하는 코드가 보이면 그때는 값을 Wrapping
해 준다"
int j = new Integer( 200 );
Integer 형 변수를 int 형 변수에 대입하는 경우 .intValue() 를 호출한다.
Integer 형 변수는 언박싱이 가능하고, Object 형 변수는 불가능하다.
class Temp { public void print( String... p ) { System.out.println( p.length ); for( int i = 0 ; i < p.length ; i++ ) { System.out.println( p[i] ); } System.out.println(); } public void print2( Object... p ) { System.out.println("print2"); } } // public class Test113 { public static void main( String[] args ) { Temp t = new Temp(); t.print(); t.print("apple"); t.print("apple","banana"); t.print("apple","banana","orange"); // 매개변수를 어떻게 넣더라도 AutoBoxing 때문에 다 가능한 함수가 되어버린다. t.print2( 100, 3.14 ); t.print2( 100, 3.14, "HelloWorld" , null ); } }
String... p
가변길이 파라미터 라고 한다. 정체는 배열이다.
매개변수에 전달되는 것이 String 이기만 하면 갯수는 상관없는 형태가 된다.
class Bank { Object t = null; } class Bank2 { String t = null; } public class Test114 { public static void main( String[] args ) { Bank b = new Bank(); b.t = "HelloWorld"; // 에러 : String b2 = b.t; String b2 = (String)b.t; // Bank2 c = new Bank2(); c.t = "HelloWorld"; String c2 = c.t; } }
Bank는 어떤 인스턴스이든 멤버변수로 가리킬 수 있지만, 원래대로 꺼낼때 반드시 원래 타입으로 캐스팅 해 주어야 한다.
Bank2는 String 만 멤버변수를 이용하여 가리킬 수 있지만 , 원래대로 꺼낼때 캐스팅 필요 없다.
양쪽의 장점이 확연히 갈린다. "양쪽의 장점을 결합할 수는 없을까??"
class Bank <X extends Object> { X t = null; } // public class Test115 { public static void main( String[] args ) { Bank<Object> bank = new Bank<Object>(); bank.t = "HelloWorld"; String t2 = (String)bank.t; // Bank<String> bank2 = new Bank<String>(); bank2.t = "HelloWorld"; String t3 = bank2.t; // 이런식으로 오토박싱 언박싱을 이용하니 코드가 깔끔해진다. Bank<Integer> bank3 = new Bank<Integer>(); bank3.t = 100; int j = bank3.t; } }
제네릭
: 클래스 안에서 언급되는 변수의 타입을 <> 안에 지정되는 타입으로 동적으로 결정할 수 있다.
interface ITemp <X> { public X getData(); } // class Temp implements ITemp<String> { public String getData() { return "HelloWorld"; } } // class Temp2 implements ITemp<Integer> { public Integer getData() { return 100; } } // public class Test117 { public static void main( String[] args ) { ITemp<String> it = new Temp(); String l = it.getData(); System.out.println( l ); // ITemp<Integer> it2 = new Temp2(); int j = it2.getData(); System.out.println( j ); } }
class A {} // interface ITemp<X> { public X getData(); } // public class Test118 { public static void main( String[] args ) { A t = new A(){}; System.out.println( t.toString() ); // ITemp<Integer> it = new ITemp<Integer>(){ public Integer getData(){ return 100; } }; int j = it.getData(); System.out.println( j ); } }
new ITemp<Integer>(){ ... }
: ITemp 를 상속받으며 만든 무명의 클래스의 인스턴스 생성. ITemp 를 상속받았으니 부모에서 선언된 메소드를 오버라이딩 해 줘야 한다.
anonymous class
: 부모 클래스로부터 상속받으며, 클래스를 선언하는데, 이름이 없다! 재사용이 불가능한 1회용 클래스라는 뜻
interface ITemp<X> { public X getData(); } // public class Test119 { public static void main( String[] args ) { int t = 200; // ITemp<Integer> it = new ITemp<Integer>() { public Integer getData(){ // 에러 : t = t + 1; return t; } }; int j = it.getData(); System.out.println( j ); } }
로컬변수
를 만일 anonymous class 가 사용하고 있다면
해당 인스턴스가 가베지 컬렉션 되지 않는 한에서는 로컬변수는 함수 호출이 끝나도 없어지면 안된다.
interface ICallback { public void onEvent( int i ); } // class Button { public void onClick( ICallback cb ) { System.out.println("onClick ..."); if( cb != null ) { cb.onEvent( 100 ); } } } // public class Test120 { public static void main( String[] args ) { Button btn = new Button(); btn.onClick( new ICallback(){ public void onEvent( int i ) { System.out.println( "onEvent XX " + i ); } } ); } }
출력결과 onClick ... onEvent XX100
호출당한 쪽( onClick )에서 호출한 쪽이 오버라이딩한 함수( onEvent )를 호출한다고 해서 이런 기법을 Callback
이라고 한다.