classCar{// 필드가 가지는 것
// 처음에 참조형으로 선언하고 초기화 안했으니 당연히 null이겠네
privateStringname;publicCar(){System.out.println("자동차가 1대 생성됩니다.");}publicCar(Stringname){this.name=name;}publicvoidprintName(){System.out.println("자동차 이름 : "+name);}}publicclassCarExam02{publicstaticvoidmain(String[]args){Carc1=newCar();c1.printName();System.out.println("----------------------");Carc2=newCar("람보르기니");c2.printName();}}// 자동차가 1대 생성됩니다.
// 자동차 이름 : null
// ----------------------
// 자동차 이름 : 람보르기니
publicclassUser{privateStringemail;privateStringpassword;privateStringname;// 생성자를 하나라도 만들게 되면 기본 생성자가 자동으로 안만들어짐
publicUser(Stringname,Stringemail){this.name=name;this.email=email;}// 생성자 오버로딩(Overloading)
publicUser(Stringname,Stringemail,Stringpassword){this.name=name;this.email=email;this.password=password;}publicStringgetEmail(){returnemail;}publicStringgetName(){returnname;}}
publicclassUser{privateStringemail;privateStringpassword;privateStringname;// 생성자를 하나라도 만들게 되면 기본 생성자가 자동으로 안만들어짐
publicUser(Stringname,Stringemail){this.name=name;this.email=email;}// 생성자 오버로딩(Overloading)
publicUser(Stringname,Stringemail,Stringpassword){this.name=name;this.email=email;this.password=password;}publicStringgetEmail(){returnemail;}publicStringgetName(){returnname;}// password는 일부로 뺐음
@OverridepublicStringtoString(){return"User{"+"email='"+email+'\''+", name='"+name+'\''+'}';}}publicclassUserExam{publicstaticvoidmain(String[]args){Useruser2=newUser("신재윤","wlwhsvkdlxh@gmail.com","1234");System.out.println(user2);}}// User{email='wlwhsvkdlxh@gmail.com', name='신재윤'}
publicclassCar2{publicCar2(){super();// 자동으로 들어간다.
System.out.println("Car2() 생성자 호출");}}publicclassBus2extendsCar2{publicBus2(){// 자동으로 들어간 디폴트 생성자
super();// 자동으로 들어간다.
}}publicclassCar2Exam{publicstaticvoidmain(String[]args){Car2c1=newCar2();Bus2b1=newBus2();}}// Car2() 생성자 호출
// Car2() 생성자 호출
publicclassBus2extendsCar2{publicBus2(){super("Bus!!");System.out.println("Bus2 기본 생성자");}@Overridepublicvoidrun(){System.out.println("후륜구동으로 동작한다.");}}publicclassSportsCarextendsCar2{publicSportsCar(Stringname){super(name);}@Overridepublicvoidrun(){System.out.println("사륜구동으로 동작한다.");}}
Car2를 상속받은 두 클래스 모두 run() 메서드를 @Override를 이용하여 오버라이딩해서 구현했다.
부모가 기본 생성자가 없기 때문에 반드시 super()를 호출하는 모습 또한 확인가능하다.
추상 클래스는 템플릿 메서드 패턴(Template Method Pattern)에서 가장 많이 사용된다고 생각함.
프로그래밍을 작성하는데, 어떤 기능들이 있다고 하자. 이 기능들은 항상 초기화, 실행, 마무리의 순서로 작성되어야한다. 그런데 이때, 초기화와 마무리는 동일하고 실행만 다르다고 해보자. Controller 클래스를 상속받도록 하여 만들자.
Controller.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
publicabstractclassController{publicvoidinit(){System.out.println("초기화 하는 코드");}publicvoidclose(){System.out.println("마무리 하는 코드");}publicabstractvoidrun();// 매번 달라지는 코드
publicvoidexecute(){this.init();this.run();this.close();}}
init()과 close()는 미리 구현해놓고 run()은 추상 메서드로 선언하여 선언만하고 구현은 Controller를 상속받는 클래스에게 하도록 한다.
추가로 execute() 메서드를 만들어서 정해진 순서대로 실행하도록 만든 메서드를 추가했다. 이런 메서드를 템플릿 메서드라고 한다.
FirstController.java
1
2
3
4
5
6
publicclassFirstControllerextendsController{@Overridepublicvoidrun(){System.out.println("별도로 동작하는 코드 111111");}}
부모 타입(Controller)으로 자식(FirstController)을 참조하도록 했다.
하지만 이렇게하면 부모 클래스인 Controller의 init과 close 메서드의 접근제한자가 public이라서 ControllerMain 클래스에서 c1.init()과 같은 형태로 사용할 수 있게 되버린다.
Controller.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
publicabstractclassController{protectedvoidinit(){System.out.println("초기화 하는 코드");}protectedvoidclose(){System.out.println("마무리 하는 코드");}publicabstractvoidrun();// 매번 달라지는 코드
publicvoidexecute(){this.init();this.run();this.close();}}
접근제한자 public을 protected으로 바꿔서 상속받는 클래스만 사용할 수 있게 코드를 수정했다.
접근제한자 protected는 동일한 패키지 내에 존재하거나, 상속받는 클래스만 사용 가능하다.
하지만, 이렇게하면 문제점이 init()과 close()를 상속받는 클래스에서 수정할 수 있다.
Controller.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
publicabstractclassController{protectedfinalvoidinit(){System.out.println("초기화 하는 코드");}protectedfinalvoidclose(){System.out.println("마무리 하는 코드");}publicabstractvoidrun();// 매번 달라지는 코드
publicvoidexecute(){this.init();this.run();this.close();}}
init()과 close()의 수정을 막기 위해 final 키워드를 붙여 재선언이 불가능하게 만들었다.
정리한 내용은 아래와 같다.
반복되는 메서드는 미리 선언과 구현을 해줬음
매번 달라지는 메서드는 추상 메서드로 선언만 하고 구현은 상속받는 클래스에 넘김
제작자가 원하는 순서대로 메서드가 실행되도록 템플릿 메서드를 구현했음
사용하는 곳에서 반복되는 메서드를 직접 사용하지는 못하게 접근제한자를 protected으로 수정했음
publicclassStringExam2{publicstaticvoidmain(String[]args){Stringstr1="hello";Stringstr2=newString("hello");if(str1.equals(str2)){// 값이 같나요?
System.out.println("str1과 str2의 값이 같다.");}Strings=str1.toUpperCase();System.out.println(s);System.out.println(str1);Stringsubstring=str1.substring(3);System.out.println(substring);System.out.println(str1);}}// str1과 str2의 값이 같다.
// HELLO
// hello
// lo
// hello
publicinterfaceLottoMachine{intMAX_BALL_COUNT=45;intRETURN_BALL_COUNT=7;publicvoidsetBalls(Ball[]balls);// Ball[]은 Ball 여러 개를 받겠다. 45개를 받는다.
publicvoidmix();// 자기가 가지고 있는 Ball들을 섞는다.
publicBall[]getBalls();// 6개의 Ball을 반환한다.
}
모든 필드에는 public static, 모든 메서드에는 abstract를 붙여야하지만, Java 8부터는 생략해도 자동으로 붙는다.
필드가 public static 하다는거니까 LottoMachine.MAX_BALL_COUNT처럼 쓸 수 있다는 의미
생각의 과정
코드를 보면 Ball[]을 쓰니까 Ball 객체를 위해 Ball 클래스를 만들어야겠네.
인터페이스를 구현할 LottoMachineImpl 클래스를 만들어야겠네.
메인 메서드를 포함할 LottoMachineMain 클래스를 만들어야겠네.
Ball.java
1
2
3
4
5
6
7
8
9
10
11
12
// 생성자를 통해서만 값을 받고 setter가 없으니 불변객체
publicclassBall{privateintnumber;publicBall(intnumber){this.number=number;}publicintgetNumber(){returnnumber;}}
// 인터페이스를 구현하게 되면 반드시 인터페이스가 가지고 있는 메서드를 오버라이딩 할 필요가 있다.
publicclassLottoMachineImplimplementsLottoMachine{privateBall[]balls;@OverridepublicvoidsetBalls(Ball[]balls){this.balls=balls;}@Overridepublicvoidmix(){for(inti=0;i<10000;i++){intx1=(int)(Math.random()*LottoMachine.MAX_BALL_COUNT);intx2=(int)(Math.random()*LottoMachine.MAX_BALL_COUNT);if(x1!=x2){Balltmp=balls[x1];// 값을 치환할 때는 같은 Type의 임시변수가 필요하다.
balls[x1]=balls[x2];balls[x2]=tmp;}}}@OverridepublicBall[]getBalls(){// Ball 6개를 참조할 수 있는 배열
Ball[]result=newBall[LottoMachine.RETURN_BALL_COUNT];for(inti=0;i<LottoMachine.RETURN_BALL_COUNT;i++){result[i]=balls[i];}returnresult;}}
Math.random() : 숫자를 랜덤하게 정하고 싶을 때
이 메서드는 0.0 <= x < 1.0의 실수값이 나온다. 예를 들어, 0.5432342 같은거
여기에 45를 곱했다고 생각해보자. 0.0 <= x < 45.0이겠네
이걸 int로 형변환 해주면 정밀한 범위 -> 덜 정밀한 범위니까 정보의 손실 발생
따라서, 0 <= x < 45니까 0~44 정수겠네!
이렇게 랜덤한 값을 구하고 나면 둘을 swap 한다.
0~44 인덱스를 가지는 배열이 있다.
랜덤하게 뽑은 인덱스가 3번과 9번이라고 하자.
3번 인덱스는 숫자가 4인 공을 참조, 9번 인덱스는 숫자가 10인 공을 참조
이때, 참조를 서로 바꾼다. 3번 인덱스는 숫자가 10인 공을 참조하게 바꾸고 9번 인덱스는 숫자가 4인 공을 참조하도록 바꾼다.
자동차를 구입하여 사용하고 싶은데, 고객 입장에서 자동차가 만들어지는 과정은 궁금하지 않음
객체가 생성되는 과정을 숨겨주는 패턴이 팩토리 메서드 패턴이다.
공장에서 하는 복잡한 생산 과정을 숨기고, 완성된 인스턴스만 반환한다.
BeanFactory.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
publicclassBeanFactory{// 1. private 생성자를 만들어서 외부에서 인스턴스를 생성하지 못하게 한다.
privateBeanFactory(){}// 2. 자기 자신 인스턴스를 참조하는 static한 필드를 선언한다.
privatestaticBeanFactoryinstance=newBeanFactory();// 3. 2번에서 생성한 인스턴스를 반환하는 static한 메서드를 만든다.
publicstaticBeanFactorygetInstance(){returninstance;}// 객체 생성 메서드
publicBusgetBus(){returnnewBus();}}
싱글턴 패턴으로 작성된 BeanFactory 클래스가 있다고 하자.
여기에 getBus() 메서드를 통해 new Bus()라는 새로 생성한 인스턴스를 반환한다.
예를 들어, Car 추상 클래스가 있고 이를 상속받는 Bus, SuperCar 클래스가 있다고 하자.
chap05/Car.java
1
2
3
publicabstractclassCar{publicabstractvoida();}
chap05/Bus.java
1
2
3
4
5
publicclassBusextendsCar{publicvoida(){System.out.println("Bus 클래스 a()");}}
chap05/SuperCar.java
1
2
3
4
5
publicclassSuperCarextendsCar{publicvoida(){System.out.println("SuperCar 클래스 a()");}}
ClassLoaderMain.java
1
2
3
4
5
6
7
8
9
10
11
publicclassClassLoaderMain{publicstaticvoidmain(String[]args)throwsException{StringclassName="chap05.Bus";Classclazz=Class.forName(className);Objectobj=clazz.newInstance();Carcar=(Car)obj;}}// Bus 클래스 a()
className에 해당하는 클래스 정보를 CLASSPATH에서 읽어들이고 그 정보를 참조변수 clazz가 참조하도록 하였다.
그러나, 만약 아예 Car와 관련없는 MyHome이라는 클래스를 받아온다면? 위 코드는 동작하지 않을 것이다. Car 타입으로 형변환 할 수 없기 때문이다.
ClassLoaderMain.java
1
2
3
4
5
6
7
8
9
10
11
12
publicclassClassLoaderMain{publicstaticvoidmain(String[]args)throwsException{StringclassName="chap05.MyHome";Classclazz=Class.forName(className);Objectobj=clazz.newInstance();Methodm=clazz.getDeclaredMethod("a",null);m.invoke(obj,null);}}// MyHome 클래스 a()
Method 타입으로 참조하는 참조변수 m은 className에서 a() 메서드 정보를 가지고 있는 메서드를 반환받은 것이다. m은 메서드 정보라는 말이다.
m.invoke()는 Object obj가 참조하는 객체의 m 메서드를 실행하라는 의미이다.
정리하자면, 클래스 정보를 얻고 그 정보를 통해서 인스턴스를 만든다. 그리고 메서드 이름을 통해서 실행한다. 즉, 문자열로 된 클래스 이름과 문자열로 된 메서드 이름만 있어도 인스턴스를 만들도록 표현할 수 있는 방법이 있다는 말이다. 이것이 자바의 리플렉션(Reflection)이다.