|
|
|
|
민준이 |
|
|
|
|
|
|
|
|
|
«
2010/03
»
| 일 |
월 |
화 |
수 |
목 |
금 |
토 |
| |
1 |
2 |
3 |
4 |
5 |
6 |
| 7 |
8 |
9 |
10 |
11 |
12 |
13 |
| 14 |
15 |
16 |
17 |
18 |
19 |
20 |
| 21 |
22 |
23 |
24 |
25 |
26 |
27 |
| 28 |
29 |
30 |
31 |
|
|
|
|
| 월별보기 |
|
|
|
|
|
|
| | |
|
'공부/JAVA'에 해당되는 글 8건
|
| |
|
|
|
|
| | |
| | |
|
datasource 설정 및 예제(Tomcat)
|
| Category : 공부/JAVA |
Date : 2009/11/04 20:28 |
|
|
|
1. 첨부한 zip 파일 압축을 푼 후 jar파일을 라이브러리에 추가
================================
2. web.xml
================================
<resource-ref> <description>Oraclejava DB Connection</description> <!-- 다음이 바로 리소스의 이름 --> <res-ref-name>jdbc/oraclejava</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>
=================================
3. Context XML파일의 설정예(Tomcat의 경우 Topmcat_Home/conf/catalina/localhost 아래에 컨텍스트이름.xml)
=================================
Context명이 web이라고 할때
<Context path="/web" reloadable="true" docBase="C:\java2\project\web" workDir="C:\java2\project\web\work">
<Resource name="jdbc/oraclejava" auth="Container" type="javax.sql.DataSource" username="scott" password="tiger" driverClassName="oracle.jdbc.driver.OracleDriver" url="jdbc:oracle:thin:@192.168.1.131:1521:ORCL"/>
</Context>
==================
4. datasource.jsp
==================
<%@ page language="java" contentType="text/html; charset=EUC-KR" pageEncoding="EUC-KR"%> <%@ page import="javax.naming.*" %> <%@ page import="java.sql.*" %> <%@ page import="javax.sql.*" %> <%! public String nvl(String param) { return param != null? param:""; } %> <html> <head> <title></title> <meta http-equiv="content-type" content="text/html; charset=euc-kr"> </head> <body> <% // 데이터베이스 접속 InitialContext ctx = null; DataSource ds = null; Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; String sql="SELECT * FROM TAB "; try { ctx = new InitialContext(); ds = (DataSource) ctx.lookup("java:comp/env/jdbc/oraclejava"); con = (Connection) ds.getConnection(); pstmt = con.prepareStatement(sql); rs = pstmt.executeQuery(); out.println("<table>"); while(rs.next()) { out.println("<tr>\n"); for (int i = 1; i < 3; i++) out.println("<td>" + nvl(rs.getString(i)) + "</td>"); out.println("</tr>\n"); } out.println("</table>"); } catch (NamingException e) { e.printStackTrace(); } catch (NullPointerException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { if (rs != null) try { rs.close(); } catch (Exception e) { // quit; } if (pstmt != null) try { pstmt.close(); } catch (Exception e) { // quit } if (con != null) try { con.close(); } catch (Exception e) { // quit } } %> </body> </html>
|
민준이
2009/11/04 20:28
2009/11/04 20:28
|
|
|
|
|
|
 |
http://webpro2004.com/trackback/121 |
|
|
|
|
|
|
|
| | |
| | |
|
Polygon에 대해
|
| Category : 공부/JAVA |
Date : 2009/10/21 18:08 |
|
|
|
abstract class Polygon{ abstract public double perimeter(); abstract public double area(); }
class Triangle extends Polygon{ double a,b,c; public Triangle(double a,double b,double c){ this.a = a; this.b = b; this.c = c; } public double perimeter(){ return (a + b + c); } public double area(){ double s = (a+b+c) / 2.0; return Math.sqrt(s*(s-a)*(s-b)*(s-c)); } }
class Square extends Polygon{ double w,h; public Square(double w,double h){ this.w = w; this.h = h; } public double perimeter(){ return ((w + h) * 2); } public double area(){ return (w*h); } } public class PolygonTest { public static void main(String[] args) { Polygon a1 = new Triangle(5,6,8); Polygon a2 = new Square(4,2); System.out.println("Triangle perimeter : " + a1.perimeter()); System.out.println("Triangle area : " + a1.area()); System.out.println("Square perimeter : " + a2.perimeter()); System.out.println("Square area : " + a2.area()); }
}
|
민준이
2009/10/21 18:08
2009/10/21 18:08
|
|
|
|
|
|
 |
http://webpro2004.com/trackback/119 |
|
|
|
|
|
|
|
| | |
| | |
|
추상클래스!
|
| Category : 공부/JAVA |
Date : 2009/10/19 11:16 |
|
|
|
6.1 추상클래스란? 클래스를 설계도에 비유한다면, 추상클래스는 미완성 설계도에 비유할 수 있다. 미완성 설계도란, 단어의 뜻 그대로 완성되지 못한 체로 남겨진 설계도를 말한다. 클래스가 미완성이라는 것은 멤버의 개수에 관계된 것이 아니라, 단지 미완성 메서드(추상메서드)를 포함하고 있다는 의미이다. 미완성 설계도로 완성된 제품을 만들 수 없듯이 추상클래스로는 인스턴스는 생성할 수 없다. 추상클래스는 상속을 통해서 자손클래스에 의해서 완성될 수 있다.
추상클래스 자체로는 클래스로서의 역할을 다 못하지만, 새로운 클래스를 작성하는데 있어서 바탕이 되는 조상클래스로서 중요한 의미를 갖는다.
추상클래스는 키워드 'abstract'를 붙이기만 하면 된다. 이렇게 함으로써 이 클래스를 사용할 때, 클래스 선언부의 abstract를 보고 이 클래스에는 추상메서드가 있으니 상속을 통해서 구현해주어야 한다는 것을 쉽게 알 수 있을 것이다.
abstract class 클래스이름 { // ... }
| 추상클래스는 추상메서드를 포함하고 있다는 것을 제외하고는 일반클래스와 전혀 다르지 않다. 추상클래스에도 생성자가 있으며, 멤버변수와 일반 메서드도 가질 수 있다.
[참고]추상메서드를 포함하고 있지 않은 클래스에도 키워드 'abstract'를 붙여서 추상클래스로 지정할 수도 있다. 추상메서드가 없는 완성된 클래스라 할지라도 추상클래스로 지정되면 클래스의 인스턴스를 생성할 수 없다.
6.2 추상메서드(abstract 메서드) 메서드는 선언부와 구현부(몸통)로 구성되어 있다고 했다. 선언부만 작성하고 구현부는 작성하지 않은 체로 남겨 둔 것이 추상메서드이다. 즉, 설계만 해 놓고 실제 수행될 내용은 작성하지 않기 때문에 미완성 메서드인 것이다.
메서드를 이와 같이 미완성 상태로 남겨 놓는 이유는 메서드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문에 조상클래스에서는 선언부만을 작성하고, 주석을 덧붙여 어떤 기능을 수행할 목적으로 작성되었는지 알려 주고, 실제 내용은 상속받는 클래스에서 구현하도록 비워 두는 것이다.
그래서, 추상클래스를 상속받는 자손클래스는 조상의 추상메서드를 상황에 맞게 적절히 구현해주어야 한다.
추상메서드 역시 키워드 'abstract'를 앞에 붙여 주고, 추상메서드는 구현부가 없으므로 괄호{}대신 문장의 끝을 알리는 ';'을 적어준다.
/* 주석을 통해 어떤 기능을 수행할 목적으로 작성하였는지 설명한다. */ abstract 리턴타입 메서드이름();
| 추상클래스로부터 상속받는 자손클래스는 오버라이딩을 통해 조상인 추상클래스의 추상메서드를 모두 구현해주어야 한다. 만일 조상으로부터 상속받은 추상메서드 중 하나라도 구현하지 않는다면, 자손클래스 역시 추상클래스로 지정해 주어야 한다. 실제 작업내용인 구현부가 없는 메서드가 무슨 의미가 있을까 싶기도 하겠지만, 메서드를 작성할 때 실제 작업내용인 구현부 보다 더 중요한 부분이 선언부이다. 메서드의 이름과 메서드의 작업에 필요한 매개변수, 그리고 작업의 결과로 어떤 타입의 값을 반환할 것인가를 결정하는 것은 쉽지 않은 일이다. 선언부만 작성해도 메서드의 절반 이상이 완성된 것이라 해도 과언이 아니다. 메서드를 사용하는 쪽에서는 메서드가 실제로 어떻게 구현되어있는지 보다는 메서드의 이름과 매개변수, 리턴타입, 즉 선언부만 알고 있으면 되므로 내용이 없을 지라도 추상메서드를 사용하는 코드를 작성하는 것이 가능하며, 실제로는 자손클래스에 구현된 완성된 메서드가 호출되도록 할 수 있다.
6.3 추상클래스의 작성 여러 클래스에 공통적으로 사용될 수 있는 클래스를 바로 작성하기도 하고, 기존의 클래스의 공통적인 부분을 뽑아서 추상클래스로 만들어 상속하도록 하는 경우도 있다. 참고로 추상의 사전적 정의는 다음과 같다.
추상[抽象] - 낱낱의 구체적 표상(表象)이나 개념에서 공통된 성질을 뽑아 이를 일반적인 개념으로 파악하는 정신 작용
| 상속이 자손클래스를 만드는데 조상클래스를 사용하는 것이라면, 추상화는 기존의 클래스의 공통부분을 뽑아 내서 조상클래스를 만드는 것이라고 할 수 있다.
추상화를 구체화와 반대되는 의미로 이해하면 보다 쉽게 이해할 수 있을 것이다. 상속계층도를 따라 내려갈 수록 클래스는 점점 기능이 추가되어 구체화의 정도가 심해지며, 상속계층도를 따라 올라갈 수록 클래스는 추상화의 정도가 심해진다고 할 수 있다.
즉, 상속계층도를 따라 내려 갈수록 세분화되며, 올라갈수록 공통요소만 남게 된다.
추상화 - 클래스간의 공통점을 찾아내서 공통의 조상을 만드는 작업 구체화 - 상속을 통해 클래스를 구현, 확장하는 작업
|
여러 클래스에 널리 사용될 수 있는 Player라는 추상클래스를 작성해 보았다. 이 클래스는 VCR이나 Audio와 같은 재생 가능한(Player) 기기를 클래스로 작성할 때, 이 들의 조상클래스로 사용될 수 있을 것이다.
abstract class Player { boolean pause; // 일시정지상태를 저장하기 위한 변수 int currentPos; // 현재 Play되고 있는 위치를 저장하기 위한 변수
Player() { // 추상클래스도 생성자가 있다. pause = false; currentPos = 0; } /** 지정된 위치(pos)에서 재생을 시작하는 기능이 수행하도록 작성되어야 한다. */ abstract void play(int pos); // 추상메서드 /** 재생을 즉시 멈추는 기능을 수행하도록 작성되어야 한다. */ abstract void stop(); // 추상메서드
void play() { play(currentPos); // 추상메서드를 사용할 수 있다. } void pause() { if(pause) { // pause가 true일 때(정지상태)에서 pause가 호출되면, pause = false; // pause의 상태를 false로 바꾸고, play(currentPos); // 현재의 위치에서부터 play를 한다. } else { // pause가 false일 때(play상태)에서 pause가 호출되면, pause = true; // pause의 상태를 true로 바꾸고 stop(); // play를 멈춘다. } } }
|
[참고] /** */ 주석은 javadoc.exe를 사용해서 Java API와 같은 클래스의 멤버에 대한 설명을 담은 문서를 만드는데 사용된다. 이제 Player클래스를 조상으로 하는 CDPlayer 클래스를 만들어 보자.
class CDPlayer extends Player { // 조상 클래스의 추상메서드를 구현한다. void play(int currentPos) { /* 실제구현 내용 생략 */ }
void stop() { /* 실제구현 내용 생략 */ }
// CDPlayer클래스에 추가로 정의된 멤버 int currentTrack; // 현재 재생 중인 트랙
void nextTrack() { currentTrack++; //... }
void preTrack() { if(currentTrack > 0) { currentTrack--; } //... } }
| 조상클래스의 추상메서드를 CDPlayer클래스의 기능에 맞게 완성해주고, CDPlayer만의 새로운 기능들을 추가하였다.
사실 Player클래스의 play(int pos)와 stop()을 추상메서드로 하는 대신, 아무 내용도 없는 메서드로 작성할 수도 있다. 아무런 내용도 없이 단지 괄호{}만 있을지라도, 추상메서드가 아닌 일반메서드로 간주되기 때문이다.
class Player { ... void play(int pos) {} void stop() {} ... }
| 어차피 자손클래스에서 오버라이딩하여 자신의 클래스에 맞게 구현할 테니 추상메서드로 선언하는 것과 내용없는 빈 몸통만 만들어 놓는 것이나 별 차이가 없어 보인다.
그래도 굳이 abstract를 붙여서 추상메서드로 선언하는 이유는 자손클래스에서 추상메서드를 반드시 구현하도록 강요하기 위해서이다. 만일 추상메서드로 정의되어 있지 않고 빈 몸통만 가지도록 정의되어 있다면, 상속받는 자손클래스에서는 이 메서드들이 온전히 구현된 것으로 인식하고 오버라이딩을 통해 자신의 클래스에 맞도록 구현하지 않을 수도 있기 때문이다.
하지만 abstract를 사용해서 추상메서드로 정의해놓으면, 자손클래스를 작성할 때 이들이 추상메서드이므로 내용을 새로 구현해주어야 한다는 사실을 인식하고 자신의 클래스에 알맞게 구현할 것이다.
이번엔 기존의 클래스로부터 공통된 부분을 뽑아 내어 추상클래스를 만들어 보도록 하자.
class Marine { // 보병 int x, y; // 현재 위치 void move(int x, int y) { /* 지정된 위치로 이동 */ } void stop() { /* 현재 위치에 정지 */ } void stimPack() { /* 스팀팩을 사용한다.*/} }
class Tank { // 탱크 int x, y; // 현재 위치 void move(int x, int y) { /* 지정된 위치로 이동 */ } void stop() { /* 현재 위치에 정지 */ } void changeMode() { /* 공격모드를 변환한다. */} }
class Dropship { // 수송선 int x, y; // 현재 위치 void move(int x, int y) { /* 지정된 위치로 이동 */ } void stop() { /* 현재 위치에 정지 */ } void load() { /* 선택된 대상을 태운다.*/ } void unload() { /* 선택된 대상을 태운다.*/ } }
| 유명한 컴퓨터게임인 Starcraft에 나오는 유닛들을 클래스로 간단히 정의해보았다. 이 유닛들은 각자 나름대로의 기능을 가지고 있지만 공통부분을 뽑아 내어 하나의 클래스로 만들고, 이 클래스로부터 상속받도록 변경해보자.
abstract class Unit { int x, y; abstract void move(int x, int y); void stop() { /* 현재 위치에 정지 */ } }
class Marine extends Unit { // 보병 void move(int x, int y) { /* 지정된 위치로 이동 */ } void stimPack() { /* 스팀팩을 사용한다.*/} }
class Tank extends Unit { // 탱크 void move(int x, int y) { /* 지정된 위치로 이동 */ } void changeMode() { /* 공격모드를 변환한다. */} }
class Dropship extends Unit { // 수송선 void move(int x, int y) { /* 지정된 위치로 이동 */ } void load() { /* 선택된 대상을 태운다.*/ } void unload() { /* 선택된 대상을 내린다.*/ } }
| 각 클래스의 공통부분을 뽑아 내서 Unit클래스를 정의하고 이로부터 상속받도록 하였다. 이 Unit클래스는 다른 유닛을 위한 클래스를 작성하는데 재활용될 수 있을 것이다. 이들 클래스에 대해서 stop메서드는 선언부와 구현부 모두 공통적이지만, Marine, Tank는 지상유닛이고 Dropship는 공중유닛이기 때문에 이동하는 방법이 서로 달라서 move메서드의 실제 구현 내용이 다를 것이다.
그래도 move메서드의 선언부는 같기 때문에 추상메서드로 정의할 수 있다. 최대한의 공통부분을 뽑아 내기 위한 것이기도 하지만, 모든 유닛은 이동할 수 있어야 하기 때문에 Unit클래스에는 move메서드가 반드시 필요한 것이기 때문이다. move메서드가 추상메서드로 선언된 것에는, 앞으로 Unit클래스를 상속받아서 작성되는 클래스는 move메서드를 자신의 클래스에 알맞게 반드시 구현해야한다는 의미가 담겨 있는 것이기도 하다.
Unit[] group = new Unit[5]; group[0] = new Marine(); group[1] = new Tank(); group[2] = new Marine(); group[3] = new Dropship(); group[4] = new Marine();
for(int i=0;i< group.length;i++) { // Unit배열의 모든 유닛을 좌표(100, 200)의 위치로 이동한다. group[i].move(100, 200); }
| 위의 코드는 공통조상인 Unit클래스 타입의 참조변수 배열을 통해서 서로 다른 종류의 인스턴스를 하나의 묶음으로 다룰 수 있다는 것을 보여 주기 위한 것이다. 다형성에서 배웠듯이 조상클래스타입의 참조변수로 자손클래스의 인스턴스를 참조하는 것이 가능하기 때문에 이처럼 조상클래스타입의 배열에 자손클래스의 인스턴스를 담을 수 있는 것이다.
만일 이들 클래스간의 공통조상이 없었다면 이처럼 하나의 배열로 다룰 수 없었을 것이다. Unit클래스에 move메서드가 비록 추상메서드로 정의되어 있다 하더라도 이처럼 Unit클래스 타입의 참조변수로 move메서드를 호출하는 것이 가능하다. 메서드는 참조변수의 타입에 관계없이 실제 인스턴스에 구현된 것이 호출되기 때문이다. group[i].move(100, 200)과 같이 호출하는 것이 Unit클래스의 추상메서드인 move를 호출하는 것 같이 보이지만 실제로는 이 추상메서드가 구현된 Marine, Tank, Dropship인스턴스의 메서드가 호출되는 것이다.
모든 클래스의 조상인 Object클래스 타입의 배열로도 서로 다른 종류의 인스턴스를 하나의 묶음으로 다룰 수 있지만, Object클래스에는 move메서드가 정의되어 있지 않기 때문에 move메서드를 호출하는 부분에서 에러가 발생한다.
Object[] group = new Object[5]; group[0] = new Marine(); group[1] = new Tank(); group[2] = new Marine(); group[3] = new Dropship(); group[4] = new Marine();
for(int i=0;i < group.length;i++) { // 에러!!! Object클래스에는 move메서드가 정의되어 있지 않다. group[i].move(100, 200); }
| |
민준이
2009/10/19 11:16
2009/10/19 11:16
|
|
|
|
|
|
 |
http://webpro2004.com/trackback/118 |
|
|
|
|
|
|
|
| | |
| | |
|
인터페이스!!
|
| Category : 공부/JAVA |
Date : 2009/10/19 11:14 |
|
|
|
7.1 인터페이스란? 인터페이스는 일종의 추상클래스이다. 인터페이스는 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다. 오직 추상메서드와 상수만을 멤버로 가질 수 있으며, 그 외의 다른 어떠한 요소도 허용하지 않는다. 추상클래스를 부분적으로만 완성된 '미완성 설계도'라고 한다면, 인터페이스는 구현된 것은 아무 것도 없고 밑그림만 그려져 있는 '기본 설계도'라 할 수 있다.
추상클래스처럼 인터페이스도 완성되지 않은 불완전한 것이기 때문에 그 자체만으로 사용되기 보다는 다른 클래스를 작성하는데 도움 줄 목적으로 작성된다.
7.2 인터페이스의 작성 인터페이스를 작성하는 것은 클래스를 작성하는 것과 같다. 다만 키워드로 class 대신 interface를 사용한다는 것만 다르다. 그리고 interface에도 클래스와 같이 접근제어자로 public 또는 default를 사용할 수 있다.
interface 인터페이스이름 { public static final 타입 상수이름 = 값; public abstract 메서드이름(매개변수목록); }
| 일반적인 클래스의 멤버들과 달리 인터페이스의 멤버들은 다음과 같은 제약사항을 가지고 있다.
- 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다. - 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.
|
[참고]인터페이스에 정의된 모든 멤버에 예외없이 적용되는 사항이기 때문에 제어자를 생략할 수 있는 것이며, 편의상 생략하는 경우가 많다. 생략된 제어자는 컴파일시에 컴파일러가 자동적으로 추가해준다.
interface PlayingCard { public static final int SPADE = 4; final int DIAMOND = 3; // public static final int DIAMOND=3; static int HEART = 2; // public static final int HEART=2; int CLOVER = 1; // public static final int CLOVER=1;
public abstract String getCardNumber(); String getCardKind(); // public abstract String getCardKind();로 간주된다. }
|
7.3 인터페이스의 상속 interface는 클래스가 아닌 인터페이스로부터만 상속받을 수 있으며, 클래스와는 달리 다중상속, 즉 여러 개의 인터페이스로부터 상속을 받는 것이 가능하다.
[참고]클래스와는 달리 Object클래스와 같은 모든 인터페이스의 최고조상은 없다.
interface Movable { /** 지정된 위치(x, y)로 이동하는 기능의 메서드 */ void move(int x, int y); }
interface Attackable { /** 지정된 대상(u)을 공격하는 기능의 메서드 */ void attack(Unit u); }
interface Fightable extends Movable, Attackable { }
| 클래스에서의 상속과 마찬가지로 자손인터페이스(Fightable)은 조상인터페이스(Movable, Attackable)에 정의된 멤버를 모두 상속받는다. 그래서 Fightable자체에는 정의된 멤버가 하나도 없지만 조상인터페이스로 부터 상속받은 두 개의 추상메서드, move(int x, int y)와 attack(Unit u)를 멤버로 갖게 된다.
7.4 인터페이스의 구현 추상클래스에서와 같이 인터페이스의 추상메서드를 몸통을 구현하는 자손클래스를 작성해야한다. 이때는 인터페이스를 '구현(implement)한다'고 하며 키워드 implements를 사용한다.
class 클래스이름 implements 인터페이스이름 { // 인터페이스에 정의된 추상메서드를 구현해야한다. }
class Fighter implements Fightable { public void move() { /* 내용 생략*/ } public void attack() { /* 내용 생략*/ } }
|
[참고]이 때 'Fighter클래스는 Fightable인터페이스를 구현한다.'라고 한다. 만일 구현하는 인터페이스의 메서드 중 일부만 구현한다면, 추상클래스로 선언되어야 한다.
abstract class Fighter implements Fightable { public void move() { /* 내용 생략*/ } }
| 그리고 다음과 같이 상속과 구현을 동시에 할 수도 있다.
class Fighter extends Unit implements Fightable { public void move(int x, int y) { /* 내용 생략 */} public void attack(Unit u) { /* 내용 생략 */} }
|
[참고]인터페이스의 이름에는 주로 Fightable과 같이 '~ 를 할 수 있는'의 의미인 'able'로 끝나는 것들이 많은데, 그 이유는 어떠한 기능 또는 행위를 하는데 필요한 메서드를 제공한다는 의미를 강조하기 위해서이다. 또한 그 인터페이스를 구현한 클래스는 '~를 할 수 있는' 능력을 갖추었다는 의미이기도 하다. 이름이 'able'로 끝나는 것은 인터페이스라고 쉽게 추측할 수 있지만, 모든 인터페이스의 이름이 반드시 'able'로 끝나야 하는 것은 아니다.
| [예제7-23] FighterTest.java |
class FighterTest { public static void main(String[] args) { Fighter f = new Fighter();
if (f instanceof Unit) { System.out.println("f는 Unit클래스의 자손입니다."); } if (f instanceof Fightable) { System.out.println("f는 Fightable인터페이스를 구현했습니다."); } if (f instanceof Movable) { System.out.println("f는 Movable인터페이스를 구현했습니다."); } if (f instanceof Attackable) { System.out.println("f는 Attackable인터페이스를 구현했습니다."); } if (f instanceof Object) { System.out.println("f는 Object클래스의 자손입니다."); } } }
class Fighter extends Unit implements Fightable { public void move(int x, int y) { /* 내용 생략 */} public void attack(Unit u) { /* 내용 생략 */} }
class Unit { int currentHP; // 유닛의 체력 int x; // 유닛의 위치(x좌표) int y; // 유닛의 위치(y좌표) } interface Fightable extends Movable, Attackable { } interface Movable { void move(int x, int y); } interface Attackable { void attack(Unit u); }
|
| [실행결과] |
f는 Unit클래스의 자손입니다. f는 Fightable인터페이스를 구현했습니다. f는 Movable인터페이스를 구현했습니다. f는 Attackable인터페이스를 구현했습니다. f는 Object클래스의 자손입니다.
| 예제에 사용된 클래스와 인터페이스간의 관계를 그려보면 다음과 같다.
실제로 Fighter클래스는 Unit클래스로부터 상속받고 Fightable인터페이스만을 구현했지만, Unit클래스는 Object클래스의 자손이고, Fightable인터페이스는 Movable과 Attackable인터페이스의 자손이므로 Fighter클래스는 이 모든 클래스와 인터페이스의 자손이 되는 셈이다. 인터페이스는 상속 대신 구현이라는 용어를 사용하지만, 인터페이스로부터 상속받은 추상메서드를 구현하는 것이기 때문에 인터페이스도 조금은 다른 의미의 조상이라고 할 수 있다.
여기서 주의 깊게 봐두어야 할 것은 Movable인터페이스에 정의된 void move(int x, int y)를 Fighter클래스에서 구현할 때 접근제어자를 public으로 했다는 것이다.
interface Movable { void move(int x, int y); }
class Fighter extends Unit implements Fightable { public void move(int x, int y) { /* 실제구현내용 생략 */} public void attack(Unit u) { /* 실제구현내용 생략 */} }
| 오버라이딩을 할 때는 조상의 메서드보다 넓은 범위의 접근제어자를 지정해야한다는 것을 기억할 것이다. Movable인터페이스에 void move(int x, int y)와 같이 정의되어 있지만 사실 public abstract가 생략된 것이기 때문에 실제로 public abstract void move(int x, int y)이다. 따라서 이를 구현하는 Fighter클래스에서는 void move(int x, int y)의 접근제어자를 반드시 public으로 해야 하는 것이다.
7.5 인터페이스를 이용한 다중상속 두 조상으로부터 상속받는 멤버 중에서 멤버변수의 이름이 같거나 메서드의 선언부가 일치하고 구현 내용이 다르다면 이 두 조상으로부터 상속받는 자손클래스는 어느 조상의 것을 상속받게 되는 것인지 알 수 없다. 어느 한 쪽으로부터의 상속을 포기하던가, 이름이 충돌하지 않도록 조상클래스를 변경하는 수 밖에는 없다.
자바에서는 이러한 충돌문제를 해결하기 위해서 단일 상속만을 허용하고, 인터페이스를 이용해서 단일 상속의 단점을 보완하도록 하였다.
인터페이스는 상수만을 정의할 수 있으므로 조상클래스의 멤버변수와 충돌하는 경우는 극히 드물고 추상메서드는 구현내용이 전혀 없으므로 조상클래스의 메서드와 선언부가 일치하는 경우에는 당연히 조상클래스 쪽의 메서드를 상속받으면 되므로 문제되지 않는다. 이렇게 해서 상속받는 멤버의 충돌은 피할 수 있지만, 다중상속의 장점을 잃게 된다는 것이 아쉽다.
만일 두 개의 클래스로부터 상속을 받아야 할 상황이라면, 두 조상클래스 중에서 비중이 높은 쪽을 선택하고 다른 한쪽은 클래스 내부에 멤버로 포함시키는 방식으로 처리하거나 어느 한쪽을 필요한 부분을 뽑아서 인터페이스로 만든 다음 구현하도록 한다.
다음과 같이 Tv클래스와 VCR클래스가 있을 때, TVCR클래스를 작성하기 위해 두 클래스로부터 상속을 받을 수만 있으면 좋겠지만 다중상속을 허용하지 않으므로, 한 쪽만 선택하여 상속받고 나머지 한 쪽은 클래스 내에 포함시켜서 내부적으로 인스턴스를 생성해서 사용하도록 한다.
public class Tv { protected boolean power; protected int channel; protected int volume; public void power() { power = ! power;} public void channelUp() { channel++;} public void channelDown() { channel--;} public void volumeUp() { volume++;} public void volumeDown() { volume--;} }
public class VCR { protected int counter; // VCR의 카운터 public void play() { // Tape을 재생한다. } public void stop() { // 재생을 멈춘다. } public void reset() { counter =0; } public int getCounter() { return counter; } public void setCounter(int c) { counter = c; } }
| VCR클래스에 정의된 메서드와 일치하는 추상메서드를 갖는 인터페이스를 작성한다.
public interface IVCR { public void play(); public void stop(); public void reset(); public int getCounter(); public void setCounter(int c); }
| 이제 IVCR 인터페이스를 구현하고 Tv클래스로부터 상속받는 TVCR클래스를 작성한다. 이때 VCR클래스 타입의 참조변수를 멤버변수로 선언하여 IVCR인터페이스의 추상메서드를 구현하는데 사용한다.
public class TVCR extends Tv implements IVCR { VCR vcr = new VCR(); public void play() { vcr.play(); // 코드를 작성하는 대신 VCR인스턴스의 메서드를 호출하면 된다. } public void stop() { vcr.stop(); } public void reset() { vcr.reset(); } public int getCounter() { return vcr.getCounter(); } public void setCounter(int c) { vcr.setCounter(c); } }
| IVCR인터페이스를 구현하기 위해서는 새로 메서드를 작성해야하는 부담이 있지만 이처럼 VCR클래스의 인스턴스를 사용하면 손쉽게 다중상속을 구현할 수 있다. 또한 VCR클래스의 내용이 변경되어도 변경된 내용이 TVCR클래스에도 자동적으로 반영되는 효과도 얻을 수 있다.
사실 인터페이스를 새로 작성하지 않고도 VCR클래스를 TVCR클래스에 포함시키는 것만으로도 충분하지만, 인터페이스를 이용하면 다형적 특성을 이용할 수 있다는 장점이 있다.
7.6 인터페이스를 이용한 다형성 다형성에 대해 학습할 때 자손클래스의 인스턴스를 조상타입의 참조변수로 참조하는 것이 가능하다는 것을 배웠다. 인터페이스 역시 이를 구현한 클래스의 조상이라 할 수 있으므로 해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로의 형변환도 가능하다. 인터페이스 Fightable을 클래스 Fighter가 구현했을 때, 다음과 같이 Fighter인스턴스를 Fightable타입의 참조변수로 참조하는 것이 가능하다.
Fightable f = (Fightable)new Fighter(); 또는 Fightable f = new Fighter();
|
[참고]물론 Fightable타입의 참조변수로는 인터페이스 Fightable에 정의된 멤버들만 호출이 가능하다. 따라서 인터페이스는 다음과 같이 메서드의 매개변수의 타입으로 사용될 수 있다.
void attack(Fightable f) { //... }
| 인터페이스 타입의 매개변수가 갖는 의미는 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야한다는 것이다. 그래서 attack메서드를 호출할 때는 매개변수로 Fightable인터페이스를 구현한 클래스의 인스턴스를 넘겨주어야 한다.
class Fighter extends Unit implements Fightable { public void move(int x, int y) { /* 내용 생략 */} public void attack(Fightable f) { /* 내용 생략 */} }
| 위와 같이 Fightable인터페이스를 구현한 Fighter클래스가 있을 때, attack메서드의 매개변수로 Fighter인스턴스를 넘겨 줄 수 있다. 즉, attack(new Fighter())와 같이 할 수 있다는 것이다.
그리고 다음과 같이 메서드의 리턴타입으로 인터페이스의 타입을 지정하는 것 역시 가능하다.
Fightable method() { // ... return new Fighter(); }
| 리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다. 위의 코드에서는 method()의 리턴타입이 Fightable인터페이스기 때문에 메서드의 return문에서Fightable인터페이스를 구현한 Fighter클래스의 인스턴스를 반환한다.
| [예제7-24] ParserTest.java |
class ParserTest { public static void main(String args[]) { Parseable parser = ParserManager.getParser("XML"); parser.parse("document.xml"); parser = ParserManager.getParser("HTML"); parser.parse("document2.html"); } }
interface Parseable { public abstract void parse(String fileName); // 구문 분석작업을 수행한다. }
class ParserManager { // 리턴타입이 Parseable인터페이스이다. public static Parseable getParser(String type) { if(type.equals("XML")) { return new XMLParser(); } else { Parseable p = new HTMLParser(); return p; // return new HTMLParser(); 위의 두 줄을 간단히 할 수 있다. } } }
class XMLParser implements Parseable { public void parse(String fileName) { /* 구문 분석작업을 수행하는 코드를 적는다. */ System.out.println(fileName + " - XML parsing completed."); } }
class HTMLParser implements Parseable { public void parse(String fileName) { /* 구문 분석작업을 수행하는 코드를 적는다. */ System.out.println(fileName + " - HTML parsing completed."); } }
|
| [실행결과] |
document.xml - XML parsing completed. document2.html - HTML parsing completed.
| Parseable인터페이스는 구문분석(parsing)을 수행하는 기능을 구현할 목적으로 추상메서드 parse(String fileName)를 정의했다. 그리고 XMLParser클래스와 HTMLParser클래스는 Parseable인터페이스를 구현하였다. ParserManager클래스의 getParser메서드는 매개변수로 넘겨받는 type의 값에 따라 XMLParser인스턴스 또는 HTMLParser인스턴스를 반환한다.
getParser메서드의 수행결과로 참조변수 parser는 XMLParser인스턴스의 주소값을 갖게 된다. 마치 Parseable parser = new XMLParser();이 수행된 것과 같다.
parser.parse("document.xml");
| 참조변수 parser를 통해 parse()를 호출하면, parser가 참조하고 있는 XMLParser인스턴스의 parse메서드가 호출된다.
만일 나중에 새로운 종류의 XML구문분석기 NewXMLPaser클래스가 나와도 ParserTest클래스는 변경할 필요없이 ParserManager클래스의 getParser메서드에서 return new XMLParser(); 대신 return new NewXMLParser();로 변경하기만 하면 된다.
이러한 장점은 특히 분산환경 프로그래밍에서 그 위력을 발휘한다. 사용자 컴퓨터에 설치된 프로그램을 변경하지 않고, 서버측의 변경만으로도 사용자가 새로 개정된 프로그램을 사용하는 것이 가능하다.
7.7 인터페이스의 장점 인터페이스를 사용하는 이유와 그 장점을 정리해 보면 다음과 같다.
- 개발시간을 단축시킬 수 있다. - 표준화가 가능하다. - 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다. - 독립적인 프로그래밍이 가능하다.
|
1. 개발시간을 단축시킬 수 있다. 일단 인터페이스가 작성되면, 이를 사용해서 프로그램을 작성하는 것이 가능하다. 메서드를 호출하는 쪽에서는 메서드의 내용에 관계없이 선언부만 알면 되기 때문이다. 그리고 동시에 다른 한 쪽에서는 인터페이스를 구현하는 클래스를 작성하도록 하여, 인터페이스를 구현하는 클래스가 작성될 때까지 기다리지 않고도 양쪽에서 동시에 개발을 진행할 수 있다.
2. 표준화가 가능하다. 프로젝트에 사용되는 기본 틀을 인터페이스로 작성한 다음 개발자들에게 인터페이스를 구현하여 프로그램을 작성하도록 함으로써 보다 일관되고 정형화된 프로그램의 개발이 가능하다.
3. 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다. 서로 상속관계에 있지도 않고, 같은 조상클래스를 가지고 있지 않은 서로 아무런 관계도 없는 클래스들에게 하나의 인터페이스를 공통적으로 구현하도록 함으로써 관계를 맺어 줄 수 있다.
4. 독립적인 프로그래밍이 가능하다. 인터페이스를 이용하면 클래스의 선언과 구현을 분리 시킬 수 있기 때문에 실제구현에 독립적인 프로그램을 작성하는 것이 가능하다. 클래스와 클래스간의 직접적인 관계를 인터페이스를 이용해서 간접적인 관계로 변경하면, 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능하다. 예를 들어 한 데이터베이스 회사가 제공하는 특정 데이터베이스를 사용하는데 필요한 클래스를 사용해서 프로그램을 작성했다면 이 프로그램은 다른 종류의 데이터베이스를 사용하기 위해서는 전체 프로그램 중에서 데이터베이스 관련된 부분은 모두 변경해야할 것이다.
그러나 데이터베이스 관련 인터페이스를 정의하고 이를 이용해서 프로그램을 작성하면, 데이터베이스의 종류가 변경되더라도 프로그램을 변경하지 않도록 할 수 있다.
단, 데이터베이스 회사에서 제공하는 클래스도 인터페이스를 구현하도록 요구해야한다. 데이터베이스를 이용한 응용프로그램을 작성하는 쪽에서는 인터페이스를 이용해서 프로그램을 작성하고, 데이터베이스 회사에서는 인터페이스를 구현한 클래스를 작성해서 제공해야한다.
실제로 자바에는 다수의 데이터베이스 관련 인터페이스를 제공하고 있으며, 프로그래머는 이 인터페이스를 이용해서 프로그래밍을 하면 특정 데이터베이스에 종속되지 않는 프로그램을 작성할 수 있다.
게임 스타크래프드에 나오는 유닛을 클래스로 표현하고 이들간의 관계를 상속계층도로 표현해 보았다.
게임에 나오는 모든 유닛들의 최고 조상은 Unit클래스이고 유닛의 종류는 지상유닛(GoundUnit)과 공중유닛(AirUnit)으로 나뉘어진다. 그리고 지상유닛에는 Marine, SCV, Tank가 있고, 공중유닛으로는 DropShip이 있다. SCV에게 Tank와 DropShip과 같은 기계화 유닛을 수리할 수 있는 기능을 제공하기 위해 repair메서드를 정의한다면 다음과 같을 것이다.
void repair(Tank t) { // Tank를 수리한다. }
void repair(DropShip d) { // DropShip을 수리한다. }
| 이런 식으로 수리가 가능한 유닛의 개수 만큼 다른 버젼의 오버로딩된 메서드를 정의해야할 것이다.
이것을 피하기 위해 매개변수의 타입을 이 들의 공통 조상으로 하면 좋겠지만 DropShip은 공통조상이 다르기 때문에 공통조상의 타입으로 메서드를 정의한다고 해도 최소한 2개의 메서드가 필요할 것이다.
void repair(GroundUnit gu) { // 매개변수로 넘겨진 지상유닛(GroundUnit)을 수리한다. }
void repair(AirUnit au) { // 매개변수로 넘겨진 공중유닛(AirUnit)을 수리한다. }
| 그리고 GroundUnit의 자손 중에는 Marine과 같이 기계화 유닛이 아닌 클래스도 포함될 수 있기 때문에 repair메서드의 매개변수 타입으로 GroundUnit은 부적합하다. 현재의 상속관계에서는 이들의 공통점은 없다. 이 때 인터페이스를 이용하면 기존의 상속체계를 유지하면서 이들 기계화 유닛에 공통점을 부여할 수 있다. 다음과 같이 Repairable이라는 인터페이스를 정의하고 수리가 가능한 기계화 유닛에게 이 인터페이스를 구현하도록 하면 된다.
interface Repairable {}
class SCV extends GroundUnit implements Repairable { //... }
class Tank extends GroundUnit implements Repairable { //... }
class DropShip extends AirUnit implements Repairable { //... }
| 이제 이 세 개의 클래스에는 같은 인터페이스를 구현했다는 공통점이 생겼다. 인터페이스 Repairable에 정의된 것은 아무것도 없고, 단지 인스턴스의 타입체크에 사용된다. Repairable인터페이스를 중심으로 상속계층도를 그려보면 다음과 같다.

그리고, repair메서드의 매개변수의 타입을 Repairable로 선언하면, 이 메서드의 매개변수로 Repairable인터페이스를 구현한 클래스의 인스턴스만 받아들여질 것이다.
void repair(Repairable r) { // 매개변수로 넘겨받은 유닛을 수리한다. }
| 앞으로 새로운 클래스가 추가될 때, SCV의 repair메서드에 의해서 수리가 가능하도록 하려면 Repairable인터페이스를 구현하도록 하면 될 것이다.
| [예제7-25] RepairableTest.java |
class RepairableTest { public static void main(String[] args) { Tank t = new Tank(); DropShip d = new DropShip();
Marine m = new Marine(); SCV s = new SCV();
s.repair(t); // SCV가 Tank를 수리하도록 한다. s.repair(d); // s.repair(m); 에러발생 : repair(Repairable) in SCV cannot be applied to (Marine) } }
interface Repairable {} class GroundUnit extends Unit { GroundUnit(int hp) { super(hp); } }
class AirUnit extends Unit { AirUnit(int hp) { super(hp); } }
class Unit { int hitPoint; final int MAX_HP; Unit(int hp) { MAX_HP = hp; } //... }
class Tank extends GroundUnit implements Repairable { Tank() { super(150); // Tank의 HP는 150이다. hitPoint = MAX_HP; }
public String toString() { return "Tank"; } //... }
class DropShip extends AirUnit implements Repairable { DropShip() { super(125); // Dropship의 HP는 125이다. hitPoint = MAX_HP; }
public String toString() { return "DropShip"; } //... }
class Marine extends GroundUnit { Marine() { super(40); hitPoint = MAX_HP; } //... }
class SCV extends GroundUnit implements Repairable{ SCV() { super(60); hitPoint = MAX_HP; }
void repair(Repairable r) { if (r instanceof Unit) { Unit u = (Unit)r; while(u.hitPoint!=u.MAX_HP) { /* Unit의 HP를 증가시킨다. */ u.hitPoint++; } System.out.println( u.toString() + "의 수리가 끝났습니다."); } } //... } |
| [실행결과] |
Tank의 수리가 끝났습니다. DropShip의 수리가 끝났습니다.
|
repair메서드의 매개변수 r은 Repairable타입이기 때문에 인터페이스 Repairable에 정의된 멤버만 사용할 수 있다. 그러나 Repairable에는 정의된 멤버가 없으므로 이 타입의 참조변수로는 할 수 있는 일은 아무 것도 없다. 그래서 instanceof연산자로 타입을 체크한 뒤 캐스팅하여 Unit클래스에 정의된 hitPoint와 MAX_HP를 사용할 수 있도록 하였다. 그 다음엔 유닛의 현재체력(hitPoint)이 유닛이 가질 수 있는 최고체력(MAX_HP)이 될 때까지 체력을 증가시키는 작업을 수행한다.
Marine은 Repairable인터페이스를 구현하지 않았으므로 SCV클래스의 repair메서드의 매개변수로 Marine을 사용하면 컴파일 시에 에러가 발생한다.
이와 유사한 예를 한가지 더 들어보자. 게임 스타크래프트에 나오는 건물들을 클래스로 표현하고 이들의 관계를 상속계층도로 표현하였다.

건물을 표현하는 클래스 Academy, Bunker, Barrack, Factory가 있고 이들의 조상인 Building클래스가 있다고 하자. 이 때 Barrack클래스와 Factory클래스에 다음과 같은 내용의, 건물을 이동시킬수 있는, 새로운 메서드를 추가하고자 한다면 어떻게 해야할까?
void liftOff() { /* 내용생략 */} void move(int x, int y) { /* 내용생략 */} void stop() { /* 내용생략 */} void land() { /* 내용생략 */}
| Barrack클래스와 Factory클래스 모두 위의 코드를 적어주면 되긴 하지만, 코드가 중복된다는 단점이 있다. 그렇다고 해서 조상클래스인 Building클래스에 코드를 추가해주면, Building클래스의 다른 자손인 Academy와 Bunker클래스도 추가된 코드를 상속받으므로 안된다. 이런 경우에도 인터페이스를 이용해서 해결할 수가 있다. 우선 새로 추가하고자하는 메서드를 정의하는 인터페이스를 정의하고 이를 구현하는 클래스를 작성한다.
interface Liftable { /** 건물을 들어 올린다. */ void liftOff(); /** 건물을 이동한다. */ void move(int x, int y); /** 건물을 정지시킨다. */ void stop(); /** 건물을 착륙시킨다. */ void land(); }
class LiftableImpl implements Liftable { void liftOff() { /* 내용 생략 */} void move(int x, int y) { /* 내용 생략 */ } void stop() { /* 내용 생략 */ } void land() { /* 내용 생략 */ } }
| 마지막으로 새로 작성된 인터페이스와 이를 구현한 클래스를 Barrack과 Factory클래스에 적용하면 된다.
class Barrack extends Buildings implments Liftable { LiftableImpl l = new LiftableImpl(); void liftOff() { l.liftOff();} void move(int x, int y) { l.move(x, y);} void stop() { l.stop();} void land() { l.land();} void trainMarine() { /* 내용 생략 */ } void trainMedic() { /* 내용 생략 */ } // ... }
class Factory extends Buildings implments Liftable { LiftableImpl l = new LiftableImpl(); void liftOff() { l.liftOff();} void move(int x, int y) { l.move(x, y);} void land() { l.land();} void trainMarine() { /* 내용 생략 */ } void trainMedic() { /* 내용 생략 */ } // ... }
| Barrack클래스가 Liftable인터페이스를 구현하도록 하고, 인터페이스를 구현한 LiftableImpl클래스를 Barrack클래스에 포함시켜서 내부적으로 호출해서 사용하도록 한다. 이렇게 함으로써 같은 내용의 코드를 Barrack클래스와 Factory클래스에서 각각 작성하지 않고 LiftableImpl클래스 한 곳에서 관리할 수 있다. 그리고 작성된 Liftable인터페이스와 이를 구현한 LiftableImpl클래스는 후에 다시 재사용될 수 있을 것이다.
7.8 인터페이스의 이해
지금까지 인터페이스의 특징과 구현하는 방법, 장점 등 인터페이스에 대한 일반적인 사항들에 대해서 모두 살펴보았다. 하지만 '인터페이스란 도대체 무엇인가?'라는 의문은 여전히 남아있을 것이다. 이번 절에서는 인터페이스의 규칙이나 활용이 아닌, 본질적인 측면에 대해 살펴보자.
먼저 인터페이스를 이해하기 위해서는 다음의 두가지 사항을 반드시 염두에 두고 있어야 한다.
- 클래스를 사용하는 쪽(User)과 클래스를 제공하는 쪽(Provider)이 있다. - 메서드를 사용(호출)하는 쪽(User)에서는 사용하려는 메서드(Provider)의 선언부만 알면 된다.(내용은 몰라도 된다.)
| 다음과 같이 클래스 A와 클래스 B가 있다고 하자.
class A { public static void main(String args[]) { B b = new B(); b.methodB(); } }
class B { public void methodB() { System.out.println("methodB()"); } }
| 클래스 A(User)는 클래스 B(Provider)의 인스턴스를 생성하고 메서드를 호출한다. 이것을 간단히 'A-B'라고 표현하자. 이 두 클래스는 서로 직접적인 관계에 있다.
이 경우 클래스 A를 작성하기 위해서는 클래스 B가 이미 작성되어 있어야 한다. 그리고 클래스 B의 methodB()의 선언부가 변경되면, 이를 사용하는 클래스 A도 변경되어야 한다. 이와 같이 직접적인 관계의 두 클래스는 한 쪽(Provider)가 변경되면 다른 한 쪽(User)도 변경되어야 한다는 단점이 있다.
그러나 클래스 A가 클래스 B를 직접 호출하지 않고 인터페이스를 매개체로 해서 클래스 A가 인터페이스를 통해서 클래스 B의 메서드에 접근하도록 하면, 클래스 B에 변경사항이 생기거나 클래스 B와 같은 기능의 다른 클래스로 대체 되어도 클래스 A는 전혀 영향을 받지 않도록 하는 것이 가능하다.
두 클래스간의 관계를 간접적으로 변경하기 위해서는 먼저 인터페이스를 이용해서 클래스 B(Provider)의 선언과 구현을 분리해야한다.
먼저 다음과 같이 클래스 B에 정의된 메서드를 추상메서드로 정의하는 인터페이스 I를 정의한다.
interface I { public abstract void methodB(); }
| 그 다음에는 클래스 B가 인터페이스 I를 구현하도록 한다.
class B implements I { public void methodB() { System.out.println("methodB in B class"); } }
| 이제 클래스 A는 클래스 B 대신 인터페이스 I를 사용해서 작성할 수 있다.
class A { public void methodA(I i) { i.methodB(); } }
|
[참고]methodA가 호출될 때 인터페이스 I를 구현한 클래스의 인스턴스(클래스 B의 인스턴스)를 제공받아야 한다. 클래스 A를 작성하는데 있어서 클래스 B가 사용되지 않았다는 점에 주목하자. 이제 클래스 A와 클래스 B는 'A-B'의 직접적인 관계에서 'A-I-B'의 간접적인 관계로 바뀐 것이다.

결국 클래스 A는 여전히 클래스 B의 메서드를 호출하지만, 클래스 A는 인터페이스 I하고만 직접적인 관계에 있기 때문에 클래스 B의 변경에 영향을 받지 않도록 하는 것이 가능하다. 클래스 A는 인터페이스를 통해 실제로 사용하는 클래스의 이름을 몰라도 되고 심지어는 실제로 구현된 클래스가 존재하지 않아도 문제되지 않는다. 클래스 A는 직접적인 관계에 있는 인터페이스 I의 영향만을 받는다.
인터페이스 I는 실제구현 내용(클래스 B)을 감싸고 있는 껍데기이며, 클래스 A는 껍데기 안에 어떤 알맹이(클래스)가 들어 있는지 몰라도 된다.
| [예제7-26] InterfaceTest.java |
class A { void autoPlay(I i) { i.play(); } }
interface I { public abstract void play(); }
class B implements I { public void play() { System.out.println("play in B class"); } }
class C implements I { public void play() { System.out.println("play in C class"); } }
class InterfaceTest { public static void main(String[] args) { A a = new A(); a.autoPlay(new B()); a.autoPlay(new C()); } }
|
| [실행결과] |
play in B class play in C class
|
[참고]클래스 A를 작성하는데 클래스 B가 관련되지 않았다는 사실에 주목한다. 클래스 A가 인터페이스 I를 사용해서 작성되긴 하였지만, 이처럼 매개변수를 통해서 인터페이스 I를 구현한 클래스의 인스턴스를 동적으로 제공받아야 한다. 클래스 Thread의 생성자인 Thread(Runnable target)와 AWT컴포넌트의 addActionListener(ActionListener l)가 이런 방식으로 되어 있다.
[참고]Runnable과 ActionListener는 인터페이스이다. 이처럼 매개변수를 통해 동적으로 제공받을 수 도 있지만 다음과 같이 다른 제 3의 클래스를 통해서 제공받을 수도 있다. JDBC의 DriverManager클래스가 이런 방식으로 되어 있다.
| [예제7-27] InterfaceTest2.java |
class InterfaceTest2 { public static void main(String[] args) { A a = new A(); a.methodA(); } }
class A { void methodA() { I i = InstanceManager.getInstance(); i.methodB(); } }
interface I { public abstract void methodB(); }
class B implements I { public void methodB() { System.out.println("methodB in B class"); } }
class InstanceManager { public static I getInstance() { return new B(); } }
|
| [실행결과] |
methodB in B class
| |
민준이
2009/10/19 11:14
2009/10/19 11:14
|
|
|
|
|
|
 |
http://webpro2004.com/trackback/117 |
|
|
|
|
| | |
| | |
|
오버라이딩!!
|
| Category : 공부/JAVA |
Date : 2009/10/19 11:07 |
|
|
|
2.1 오버라이딩이란?
조상클래스로부터 상속받은 메서드의 내용을 변경하는 것을 오버라이딩이라고 한다. 상속받은 메서드를 그대로 사용하기도 하지만, 자손클래스 자신에 맞게 변경해야하는 경우가 많다. 이럴 때 조상의 메서드를 오버라이딩한다. [참고]override의 사전적 의미는 '~위에 덮어쓰다(overwrite).' 또는 '~에 우선하다.'이다.
2차원 좌표계의 한 점을 표현하기 위한 Point클래스가 있을 때, 이를 조상으로 하는 Point3D클래스, 3차원 좌표계의 한 점을 표현하기 위한 클래스를 다음과 같이 새로 작성하였다고 하자.
class Point { int x; int y;
String getLocation() { return "x :" + x + ", y :"+ y; } }
class Point3D extends Point { int z; String getLocation() { // 오버라이딩 return "x :" + x + ", y :"+ y + ", z :" + z; } }
| Point클래스의 getLocation메서드는 한 점의 x, y 좌표를 문자열로 반환하도록 작성되었다. 이 두 클래스는 서로 상속관계에 있으므로 Point3D클래스는 Point클래스로부터 getLocation메서드를 상속받지만, Point3D클래스는 3차원 좌표계의 한 점을 표현하기 위한 것이므로 조상인 Point클래스로부터 상속받은 getLocation메서드는 Point3D클래스에 맞지 않는다. 그래서 이 메서드를 Point3D클래스 자신에 맞게 z축의 좌표값도 포함하여 반환하도록 오버라이딩 하였다. Point클래스를 사용하던 사람들은 새로 작성된 Point3D클래스가 Point클래스의 자손이므로 Point3D클래스의 인스턴스에 대해서 getLocation메서드를 호출하면 Point클래스의 getLocation메서드가 그랬듯이 점의 좌표를 문자열로 얻을 수 있을 것이라고 기대할 것이다. 그렇기 때문에 새로운 메서드를 제공하는 것보다 오버라이딩을 하는 것이 바른 선택이다.
2.2 오버라이딩의 조건 오버라이딩은 메서드의 내용만을 새로 작성하는 것이므로 메서드의 선언부는 조상의 것과 완전히 일치해야한다. 그래서 오버라이딩이 성립하기 위해서는 다음과 같은 조건을 만족해야한다.
자손클래스에서 오버라이딩하는 메서드는 조상클래스의 메서드와 - 이름이 같아야 한다. - 매개변수가 같아야 한다. - 리턴타입이 같아야 한다.
| 한마디로 요약하면 선언부가 서로 일치해야한다는 것이다. 다만 접근제어자(Access Modifier)와 예외(Exception)는 제한된 조건 하에서 다르게 변경할 수 있다.
1. 접근제어자는 조상클래스의 메서드보다 좁은 범위로 변경 할 수 없다. - 만일 조상클래스에 정의된 메서드의 접근제어자가 protected라면, 이를 오버라이딩하는 자손클래스의 메서드는 접근제어자가 protected나 public이어야 한다.
[참고]대부분의 경우 같은 범위의 접근제어자를 사용한다. 접근제어자의 접근범위를 넓은 것 순으로 나열하면 public, protected, default, private이다.
2.조상클래스의 메서드보다 많은 수의 예외를 선언할 수 없다. - 아래의 코드를 보면 Child클래스의 parentMethod에 선언된 예외의 개수가 조상인 Parent클래스의 parentMethod에 선언된 예외의 개수보다 적으므로 바르게 오버라이딩 되었다.
Class Parent { void parentMethod() throws IOException, SQLException { //.. } }
Class Child extends Parent { void parentMethod() throws IOException { //.. } //.. }
| 여기서 주의해야할 점은 단순히 선언된 예외의 개수의 문제가 아니라는 것이다.
Class Child extends Parent { void parentMethod() throws Exception { //.. } //.. }
| 만일 위와 같이 오버라이딩을 하였다면, 분명히 조상클래스에 정의된 메서드보다 적은 개수의 예외를 선언한 것처럼 보이지만 Exception은 모든 예외의 최고 조상이므로 가장 많은 개수의 예외를 던질 수 있도록 선언한 것이다. 그래서 예외의 개수는 적거나 같아야 한다는 조건을 만족시키지 못하는 잘못된 오버라이딩인 것이다.
2.3 오버로딩 vs. 오버라이딩 오버로딩과 오버라이딩은 서로 혼동하기 쉽지만 사실 그 차이는 명백하다. 오버로딩은 기존에 없는 새로운 메서드를 추가하는 것이고, 오버라이딩은 조상으로부터 상속받은 메서드의 내용을 변경하는 것이다.
오버로딩(Overloading) - 기존에 없는 새로운 메서드를 정의하는 것(new) 오버라이딩(Overriding) - 상속받은 메서드의 내용을 변경하는 것(change, modify)
| 아래의 코드를 보고 오버로딩과 오버라이딩을 구별할 수 있어야 한다.
class Parent { void parentMethod() {} }
class Child extends Parent { void parentMethod() {} // 오버라이딩 void parentMethod(int i) {} // 오버로딩
void childMethod() {} void childMethod(int i) {} // 오버로딩 void childMethod() {} // 에러!!! 중복정의 되었음.(already defined in Child) }
|
2.4 super super는 자손클래스에서 조상클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수이다. 멤버변수와 지역변수의 이름이 같을 때 this를 사용해서 구별했듯이 상속받은 멤버와 자신의 클래스에 정의된 멤버의 이름이 같을 때는 super를 사용해서 구별할 수 있다. 조상클래스로부터 상속받은 멤버도 자손클래스 자신의 멤버이므로 super대신 this를 사용할 수 있다. 그래도 조상클래스의 멤버와 자손클래스의 멤버가 중복 정의되어 서로 구별해야하는 경우에만 super를 사용하는 것이 좋다. 조상의 멤버와 자신의 멤버를 구별하는데 사용된다는 점을 제외하고는 super와 this는 근본적으로 같다. 모든 인스턴스메서드에는 자신이 속한 인스턴스의 주소가 지역변수로 저장되는데, 이것이 참조변수인 this와 super의 값이 된다. static메서드(클래스메서드)는 인스턴스와 관련이 없다. 그래서 this와 마찬가지로 super역시 static메서드에서는 사용할 수 없고 인스턴스메서드에서만 사용할 수 있다.
| [예제7-5] SuperTest.java |
class SuperTest { public static void main(String args[]) { Child c = new Child(); c.method(); } }
class Parent { int x=10; }
class Child extends Parent { void method() { System.out.println("x=" + x); System.out.println("this.x=" + this.x); System.out.println("super.x="+ super.x); } }
|
| [실행결과] |
x=10 this.x=10 super.x=10
| 이 경우 x, this.x, super.x 모두 같은 변수를 의미하므로 모두 같은 값이 출력되었다.
| [예제7-6] SuperTest2.java |
class SuperTest2 { public static void main(String args[]) { Child c = new Child(); c.method(); } }
class Parent { int x=10; }
class Child extends Parent { int x=20; void method() { System.out.println("x=" + x); System.out.println("this.x=" + this.x); System.out.println("super.x="+ super.x); } }
|
| [실행결과] |
x=20 this.x=20 super.x=10
| 이전 예제와는 달리 같은 이름의 멤버변수가 조상클래스인 Parent에도 있고 자손클래스인 Child클래스에도 있을 때는 super.x와 this.x는 서로 다른 값을 참조하게 된다. super.x는 조상클래스로부터 상속받은 멤버변수 x를 뜻하며, this.x는 자손클래스에 선언된 멤버변수를 뜻한다. 이처럼 조상클래스에 선언된 멤버변수와 같은 이름의 멤버변수를 자손클래스에서 중복해서 정의하는 것이 가능하며 참조변수 super를 이용해서 서로 구별할 수 있다.
변수만이 아니라 메서드 역시 super를 써서 호출할 수 있다. 특히 조상클래스의 메서드를 자손클래스에서 오버라이딩한 경우에 super를 사용한다.
class Point { int x; int y;
String getLocation() { return "x :" + x + ", y :"+ y; } }
class Point3D extends Point { int z; String getLocation() { // 오버라이딩 // return "x :" + x + ", y :"+ y + ", z :" + z; return super.getLocation() + ", z :" + z; } }
| getLocation메서드를 오버라이딩할 때 조상클래스의 getLocation메서드를 호출해서 포함시켰다. 조상클래스의 메서드의 내용에 추가적으로 작업을 덧붙이는 경우라면 이처럼 super를 사용해서 조상클래스의 메서드를 포함시키는 것이 좋다. 후에 조상클래스의 메서드가 변경되더라도 변경된 내용이 자손클래스의 메서드에 자동적으로 반영될 것이기 때문이다.
2.5 super() - 생성자 this()와 마찬가지로 super() 역시 생성자이다. this()는 같은 클래스의 다른 생성자를 호출하는 데 사용되지만, super()는 조상클래스의 생성자를 호출하는데 사용된다. 자손클래스의 인스턴스를 생성하면, 자손의 멤버와 조상의 멤버가 모두 합쳐진 하나의 인스턴스가 생성된다. 그래서 자손클래스의 인스턴스가 조상클래스의 멤버들을 사용할 수 있는 것이다. 이 때 조상클래스 멤버의 생성과 초기화 작업이 수행되어야 하기 때문에 자손클래스의 생성자에서 조상클래스의 생성자가 호출되어야 한다. 생성자의 첫 줄에서 조상클래스의 생성자를 호출해야하는 이유는 자손클래스의 멤버가 조상클래스의 멤버를 사용할 수도 있으므로 조상의 멤버들이 먼저 초기화되어 있어야 하기 때문이다.
인스턴스를 생성할 때는 다음의 2가지를 선택해야한다. 1. 클래스 - 어떤 클래스의 인스턴스를 생성할 것인가? 2. 생성자 - 선택한 클래스의 어떤 생성자를 이용해서 인스턴스를 생성할 것인가? 이처럼 인스턴스를 생성할 때는 클래스를 선택하는 것만큼 생성자를 선택하는 것도 중요한 일이다.
| 이와 같은 조상클래스 생성자의 호출은 클래스의 상속관계를 거슬러 올라가면서 계속 반복된다. 마지막으로 모든 클래스의 최고조상인 Object클래스의 생성자인 Object()까지 가서야 끝이 난다. 그래서 Object클래스를 제외한 모든 클래스의 생성자는 첫 줄에 반드시 자신의 다른 생성자 또는 조상의 생성자를 호출해야한다. 그렇지 않으면 컴파일러는 생성자의 첫 줄에 super();를 자동적으로 추가할 것이다.
| [예제7-7] PointTest.java |
class PointTest { public static void main(String args[]) { Point3D p3 = new Point3D(1,2,3); } }
class Point { int x; int y;
Point(int x, int y) { this.x = x; this.y = y; } String getLocation() { return "x :" + x + ", y :"+ y; } }
class Point3D extends Point { int z; Point3D(int x, int y, int z) { this.x = x; this.y = y; this.z = z; } String getLocation() { // 오버라이딩 return "x :" + x + ", y :"+ y + ", z :" + z; } }
|
| [컴파일결과] |
C:\j2sdk1.4.1\work>javac PointTest.java PointTest.java:22: cannot resolve symbol symbol : constructor Point () location: class Point Point3D(int x, int y, int z) { ^ 1 error
| 이 예제를 컴파일하면 위와 같은 컴파일에러가 발생할 것이다. Point3D클래스의 생성자에서 조상클래스의 생성자인 Point()를 찾을 수 없다는 내용이다. Point3D클래스의 생성자의 첫 줄이 생성자(조상의 것이든 자신의 것이든)를 호출하는 문장이 아니기 때문에 컴파일러는 다음과 같이 자동적으로 'super();'를 Point3D클래스의 생성자의 첫 줄에 넣어 준다.
Point3D(int x, int y, int z) { super(); this.x = x; this.y = y; this.z = z; }
| 그래서 Point3D클래스의 인스턴스를 생성하면, 생성자 Point3D(int x, int y, int x)가 호출되면서 첫 문장인 super();를 수행하게 된다. super()는 Point3D클래스의 조상인 Point클래스의 기본 생성자인 Point()를 뜻하므로 Point()가 호출된다. 그러나 Point클래스에 생성자 Point()가 정의되어 있지 않기 때문에 위와 같은 컴파일 에러가 발생한 것이다. 이 에러를 수정하려면, Point클래스에 생성자 Point()를 추가해주던가, 생성자 Point3D(int x, int y, int z)의 첫 줄에서 Point(int x, int y)를 호출하도록 변경하면 된다. [참고]생성자가 정의되어 있는 클래스에는 컴파일러가 기본 생성자를 자동적으로 추가하지 않는다.
Point3D(int x, int y, int z) { super(x, y); // 조상클래스의 생성자 Point(int x, int y)를 호출한다. this.z = z; }
| 위와 같이 변경하면 된다. 문제없이 컴파일 될 것이다. 조상클래스의 멤버변수는 이처럼 조상의 생성자를 이용해서 초기화 하도록 해야 하는 것이다.
| [예제7-8] PointTest2.java |
class PointTest2 { public static void main(String argsp[]) { Point3D p3 = new Point3D(); System.out.println("p3.x=" + p3.x); System.out.println("p3.y=" + p3.y); System.out.println("p3.z=" + p3.z); } }
class Point { int x=10; int y=20;
Point(int x, int y) { this.x = x; this.y = y; } }
class Point3D extends Point { int z=30;
Point3D() { this(100, 200, 300); // Point3D(int x, int y, int z)를 호출한다. } Point3D(int x, int y, int z) { super(x, y); // Point(int x, int y)를 호출한다. this.z = z; } }
|
| [실행결과] |
p3.x=100 p3.y=200 p3.z=300
| Point3D클래스의 인스턴스를 생성하면, 조상인 Point클래스의 인스턴스도 생성되므로 Point클래스의 생성자도 호출되고, Point클래스의 조상인 Object클래스의 생성자까지 호출된다. 이처럼 어떤 클래스의 인스턴스를 생성하면, 클래스의 상속관계를 최고조상인 Object클래스까지 거슬러 올라가면서 조상클래스의 인스턴스를 생성한다. 컴파일러는 Point클래스의 생성자인 Point(int x, int y)의 첫 줄에 super();를 자동적으로 삽입할 것이다. 이 super()는 Object클래스의 생성자인 Object()를 뜻한다.
Point(int x, int y) { super(); this.x = x; this.y = y; }
| 생성자의 호출은 계속 이어져서 결국 Object클래스의 생성자인 Object()까지 호출되어야 끝나는 것이다.
|
민준이
2009/10/19 11:07
2009/10/19 11:07
|
|
|
|
|
|
 |
http://webpro2004.com/trackback/116 |
|
|
|
|
| | |
| | |
|
싱클톤패턴!
|
| Category : 공부/JAVA |
Date : 2009/10/19 11:05 |
|
|
|
Singleton Pattern
특정 클래스에 대해서 객체가 하나 뿐인 특별한 객체를 만들 수 있게 해주는 패턴이다. 전역변수처럼 애플리케이션이 시작될 때 객체가 생성되지 않고 필요할 때 객체를 생성할 수 있다.
1. Singleton Pattern의 정의
해당클래스의 인스턴스가 하나만 만들어지고, 어디서든지 그 인스턴스에 접근할 수 있도록 하기 위한 패턴이다.
2. Singleton Pattern의 특징
클래스에서 자신의 단 하나뿐인 인스턴스를 관리하도록 만들어야 한다. 인스턴스가 절대 두 이 이상이 될 수 없도록 해준다. 객체 인스턴스가 여러 개 생기면은 의도하지 않은 버그가 생기게 된다. public으로 지정된 생성자가 없고 private로 지정되어 있다. 인스턴스가 필요하면 반드시 클래스 자신을 거쳐야 하며, 어디서든지 그 인스턴스에 접근할 수 있어야 한다. 싱글턴 인스턴스를 요청할 때에는 getInstance라는 정적 메소드를 사용한다.
3. Singleton Pattern의 다이어그램
4. Singleton Pattern의 구성
A. uniqueInstance 클래스 변수에 싱글턴의 유일무이한 인스턴스가 저장된다.
B. getInstance()메소드는 정적 메소드, 즉 클래스 메소드이다. 언제 어디서든 이 메소드를 호출할 수 있다. 전역 변수에 접근하는 것만큼이나 쉬우면서도 게으른 인스턴스 생성을 활용할 수 있다.
C. 일반적인 클래스를 만들 때와 마찬가지로 다양한 데이터와 메소드를 사용할 수 있다.
5. Singleton Pattern과 동기화
멀티 쓰레드 환경에서는 객체가 한 개 이상 생성 될 수 있다. 멀티 쓰레드 환경에서 객체가 한 개 이상 생성되는 것을 방지 하기 위해 동기화(Synchronized)를 사용한다. 하지만 동기화 방식을 사용하면 불필요한 오버헤드만 증가시킨다. 멀티 쓰레드를 사용하는 애플리케이션에서는 속도와 자원 문제를 파악해보고 적절한 구현 법을 사용해야 한다. 인스턴스를 필요로 할 때 생성하지 말고, 처음부터 만들어서 해결하는 방법도 있다.
참고 : HeadFirstDesignPattern |
민준이
2009/10/19 11:05
2009/10/19 11:05
|
|
|
|
|
|
 |
http://webpro2004.com/trackback/115 |
|
|
|
|
| | |
| | |
|
메소드 오버로딩!
|
| Category : 공부/JAVA |
Date : 2009/10/19 11:04 |
|
|
|
4.1 메서드 오버로딩이란? 메서드는 변수와 마찬가지로 같은 클래스 내에서 서로 구별될 수 있어야 하기 때문에 각기 다른 이름을 가져야 한다. 하지만, 자바에서는 한 클래스 내에 이미 사용하려는 이름과 같은 이름을 가진 메서드가 있더라도 매개변수의 개수 또는 타입이 다르면, 같은 이름을 사용해서 메서드를 정의할 수 있도록 했다. 이처럼, 한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것을 메서드 오버로딩(Method Overloading) 또는 간단히 오버로딩(Overloading)이라 한다. 오버로딩(Overloading)의 사전적 의미는 '과적하다.' 즉, 많이 싣는 것을 뜻한다. 보통 하나의 메서드 이름에 하나의 기능만을 구현해야하는데, 하나의 메서드 이름으로 여러 기능을 구현하기 때문에 붙여진 이름이라 생각할 수 있다. 앞으로는 메서드 오버로딩을 간단히 오버로딩이라고 하겠다.
4.2 오버로딩의 조건 같은 이름의 메서드를 정의한다고 해서 무조건 오버로딩인 것은 아니다. 오버로딩이 성립하기 위해서는 다음과 같은 조건을 만족해야한다.
- 메서드 이름이 같아야 한다. - 매개변수의 개수 또는 타입이 달라야 한다. - 매개변수는 같고 리턴타입이 다른 경우는 오버로딩이 성립되지 않는다. (리턴타입은 오버로딩을 구현하는데 아무런 영향을 주지 못한다.)
| 비록 메서드의 이름이 같다 하더라도 매개변수가 다르면 서로 구별될 수 있기 때문에 오버로딩이 가능한 것이다. 위의 조건을 만족시키지 못하는 메서드는 중복정의 된 것으로 간주되어 컴파일시에 에러가 발생한다. [참고]오버로딩된 메서드들은 매개변수에 의해서만 구별될 수 있다.
4.3 오버로딩의 예 오버로딩의 예로 가장 대표적인 것은 println메서드이다. 지금까지 여러분은 println메서드에 괄호 안에 값만 지정해주면 화면에 출력하는데 아무런 어려움이 없었다. 하지만, 실제로는 println메서드를 호출할 때 매개변수로 지정하는 값의 타입에 따라서 호출되는 println메서드가 달라진다. PrintStream클래스에는 어떤 종류의 매개변수를 지정해도 출력할 수 있도록 아래와 같이 10개의 오버로딩된 println메서드를 정의해놓고 있다.
void println() void println(boolean x) void println(char x) void println(char[] x) void println(double x) void println(float x) void println(int x) void println(long x) void println(Object x) void println(String x)
| println메서드를 호출할 때 매개변수로 넘겨주는 값의 타입에 따라서 위의 오버로딩된 메서드들 중의 하나가 선택되어 실행되는 것이다.
오버로딩에 관련된 몇 가지 예를 들어 자세히 살펴보도록 하자.
[보기1] int add(int a, int b) { return a+b; } int add(int x, int y) { return x+y; }
| 위의 두 메서드는 매개변수의 이름만 다를 뿐 매개변수의 타입이 같기 때문에 오버로딩이 성립하지 않는다. 매개변수의 이름이 다르면 메서드 내에서 사용되는 변수의 이름이 달라질 뿐, 아무런 의미가 없다. 그래서, 이 두 메서드는 정확히 같은 것이다. 마치 수학에서, f(x) = x + 1과 f(a) = a + 1이 같은 표현인 것과 같다. 컴파일하면, 'add(int,int) is already defined(이미 같은 메서드가 정의되었다).'라는 메시지가 나타날 것이다.
[보기2] int add(int a, int b) { return a+b; } long add(int a, int b) { return (long)(a + b); }
| 이번 경우는 리턴타입만 다른 경우이다. 매개변수의 타입과 개수가 일치하기 때문에, add(3,3)과 같이 호출하였을 때 어떤 메서드가 호출된 것인지 결정할 수 없기 때문에 오버로딩으로 간주되지 않는다. 이 경우 역시 컴파일하면, 'add(int,int) is already defined(이미 같은 메서드가 정의되었다).'라는 메시지가 나타날 것이다..
[보기3] long add(int a, long b) { return a+b; } long add(long a, int b) { return a+b; }
| 두 메서드 모두 int형과 long형 매개변수가 하나씩 선언되어 있지만, 서로 순서가 다른 경우이다. 이 경우에는 호출 시 매개변수의 값에 의해 호출될 메서드가 구분될 수 있으므로 중복된 메서드 정의가 아닌, 오버로딩으로 간주한다. 이처럼 단지 매개변수의 순서만을 다르게 하여 오버로딩을 구현하면, 사용자가 매개변수의 순서를 외우지 않아도 되는 장점이 있지만, 오히려 단점이 될 수도 있기 때문에 주의해야한다. 예를 들어 add(3,3L)과 같이 호출되면 첫번째 메서드가, add(3L, 3)과 같이 호출되면 두 번째 메서드가 호출된다. 단, 이 경우에는 add(3,3)과 같이 호출할 수 없다. 이와 같이 호출할 경우, 두 메서드 중 어느 메서드가 호출된 것인지 알 수 없기 때문에 메서드를 호출하는 쪽에서 컴파일 에러가 발생한다.
[보기4] int add(int a, int b) { return a+b; } long add(long a, long b) { return a+b; } int add(int[] a) { for(int i=0,result=0; i < a.length; i++) { result += a[i]; } return result; }
| 위 메서드들은 모두 바르게 오버로딩되어있다. 정의된 매개변수가 서로 다르긴 해도, 세 메서드 모두 매개변수로 넘겨받은 값을 더해서 그 결과를 돌려주는 일을 한다. 같은 일을 하지만 매개변수를 달리해야하는 경우에, 이와 같이 이름은 같고 매개변수를 다르게 하여 오버로딩을 구현한다. [참고]세 번째 메서드는 정수형 배열을 매개변수로 넘겨주면, 배열의 모든 원소들의 값을 더해서 결과를 반환하는 작업을 한다.
4.4 오버로딩의 장점 지금까지 오버로딩의 정의와 성립하기 위한 조건을 알아보았다. 그렇다면 오버로딩을 구현함으로써 얻는 이득은 무엇인가에 대해서 생각해보도록 하자.
만일 메서드도 변수처럼 단지 이름만으로 구별된다면, 한 클래스내의 모든 메서드들은 이름이 달라야한다. 그렇다면, 이전에 예로 들었던 10가지의 println메서드들은 각기 다른 이름을 가져야 한다. 예를 들면, 아래와 같은 방식으로 메서드 이름이 변경되어야 할 것이다.
void println() void printlnBoolean(boolean x) void printlnChar(char x) void printlnDouble(double x) void printlnString(String x)
| 모두 근본적으로는 같은 기능을 하는 메서드들이지만, 서로 다른 이름을 가져야 하기 때문에 메서드를 작성하는 쪽에서는 이름을 짓기도 어렵고, 메서드를 사용하는 쪽에서는 이름을 일일이 구분해서 기억해야하기 때문에 서로 부담이 된다. 하지만 오버로딩을 통해, 여러 메서드들이 println이라는 하나의 이름으로 정의될 수 있다면, println이라는 이름만 기억하면 되므로, 기억하기도 쉽고, 이름도 짧게 할 수 있어서 오류의 가능성을 많이 줄일 수 있다. 그리고, 메서드의 이름만 보고도 '이 메서드들은 이름이 같으니, 같은 기능을 하겠구나.'라고 쉽게 예측할 수 있게 된다. 또 하나의 장점은 메서드의 이름을 절약할 수 있다는 것이다. 하나의 이름으로 여러 개의 메서드를 정의할 수 있으니, 메서드의 이름을 짓는데 고민을 덜 수 있는 동시에 사용되었어야 할 메서드 이름을 다른 메서드의 이름으로 사용할 수 있기 때문이다.
| [예제6-15] OverloadingTest.java |
class OverloadingTest { public static void main(String args[]) { MyMath2 mm2 = new MyMath2(); System.out.println("mm2.add(3, 3) 결과:" + mm2.add(3,3)); System.out.println("mm2.add(3L, 3) 결과: " + mm2.add(3L,3)); System.out.println("mm2.add(3, 3L) 결과: " + mm2.add(3,3L)); System.out.println("mm2.add(3L, 3L) 결과: " + mm2.add(3L,3L));
int[] a = {100, 200, 300}; System.out.println("mm2.add(a) 결과: " + mm2.add(a)); } }
class MyMath2 { int add(int a, int b) { System.out.print("int add(int a, int b) - "); return a+b; } long add(int a, long b) { System.out.print("long add(int a, long b) - "); return a+b; } long add(long a, int b) { System.out.print("long add(long a, int b) - "); return a+b; }
long add(long a, long b) { System.out.print("long add(long a, long b) - "); return a+b; }
int add(int[] a) { // 배열의 모든 요소의 합을 결과로 돌려준다. System.out.print("int add(int[] a) - "); int result = 0; for(int i=0; i < a.length;i++) { result += a[i]; } return result; } } // end of class
|
| [실행결과] |
int add(int a, int b) - mm2.add(3, 3) 결과:6 long add(long a, int b) - mm2.add(3L, 3) 결과: 6 long add(int a, long b) - mm2.add(3, 3L) 결과: 6 long add(long a, long b) - mm2.add(3L, 3L) 결과: 6 int add(int[] a) - mm2.add(a) 결과: 600
| [참고]add(3L, 3), add(3, 3L), add(3L, 3L)의 결과는 모두 6L이지만, System.out.println(6L);을 수행하면 6이 출력된다. |
민준이
2009/10/19 11:04
2009/10/19 11:04
|
|
|
|
|
|
 |
http://webpro2004.com/trackback/114 |
|
|
|
|
| | |
| | |
|
이클립스 단축키 모음집
|
| Category : 공부/JAVA |
Date : 2009/10/14 20:57 |
|
|
|
기본적으로 Preference > General > Keys에서 대부분(모든)의 단축키 확인 가능합니다. Ctrl+Shift+L : 단축키 보기 Hint
[거의 달고 사는 단축키]
ctrl + s : 저장 및 컴파일 ctrl + i : 소스 깔끔 정리(인덴트 중심의 자동구문정리) ctrl + space : 어휘의 자동완성(Content Assistance) ctrl + 1 : Quick Fix(Rename에 주로 사용) ctrl + shift + M : 캐럿이 위치한 대상에 필요한 특정클래스 import ctrl + shift + O : 소스에 필요한 패키지의 자동 임포트 ctrl + / : 한줄 또는 선택영역 주석처리/제거 ctrl + Q : 마지막 편집위치로 가기 ctrl + L : 특정줄번호로 가기 ctrl + D : 한줄삭제 ctrl + H : Find 및 Replace ctrl + K : 다음찾기(또는, 찾고자 하는 문자열을 블럭으로 설정한 후 키를 누른다.) ctrl + shift + K : 이전찾기(또는, 찾고자 하는 문자열을 블럭으로 설정한 후 역으로 찾고자 하는 문자열을 찾아감.) alt + shift + j : 설정해 둔 기본주석 달기 Ctrl + 객체클릭(혹은 F3) : 클래스나 메소드 혹은 멤버를 정의한 곳으로 이동(Open Declaration)
[사용하면 유용한 단축키]
ctrl + shift + f : 소스 깔끔 정리 ctrl + 2 + R : Rename(리팩토링) ctrl + shift + / : 선택영역 block comment 설정 ctrl + shift + \ : 선택영역 block comment 제거 alt + shift + up : Enclosing Element 선택(괄호의 열고 닫기 쌍 확인에 유용함) ctrl + O : Outline창열기 Alt + ->, Alt + <- : 이후, 이전 해당프로젝트에서 alt + enter : Project 속성 sysout > Ctrl + Space : System.out.println(); try > Ctrl + Space : 기본 try-catch문 완성 for > Ctrl + Space : 기본 for문 완성 템플릿을 수정,추가 : Preferences > java > editor > Templates
[알고 있으면 아는척좀 할 수 있는 단축키]
ctrl + N : 새로운 파일 및 프로젝트 생성 ctrl + shift + s : 열려진 모든파일 저장 및 컴파일 alt + / : Word Completion alt + shift + R : Rename ctrl + shift + G : 특정 메써드나 필드를 참조하고 있는 곳을 찾는다. ctrl + shift + B : 현재커서위치에 Break point설정/해제 ctrl + alt + R ctrl + f11 : 실행 f11 : 디버깅 시작 f5 : step into f6 : step over f8 : 디버깅 계속 ctrl + . : 다음오류부분으로 가기 ctrl + , : 이전오류부분으로 가기 f12 : 에디터로 커서이동 ALT + UP,DOWN : 현재 줄 위치 이동 Ctrl + j : 검색할 단어를 입력하면서 실시간으로 검색 Ctrl + Shift + j : 검색할 단어를 입력하면서 실시간으로 거꾸로 검색 F4 : 클래스명을 선택하고 누르면 해당 클래스의 Hierarchy 를 볼 수 있다. ctrl + alt + up/down : 한줄 duplicate alt + shift + 방향 : 선택 ctrl + shift + g : 케럿이 위치한 객체가 참조 되는 곳을 찾아 준다
출처 : http://blog.theple.com/skyboarder76/capture/all.html?uid=12
[출처] [본문스크랩] 이클립스 단축키|작성자 fightingdk
------------------------------------------------
ctrl + shift + L 단축키 표시
ctrl + space 자동~ (코드어시스트)
ctrl + 1 빠른수정
ctrl + 7 주석설정/해제 (범위주석), ctrl+shift+/,\
ctrl + T 타입의 상속구조
ctrl + o 퀵 아웃라인 (메서드찾기)
F3 소스코드 네비게이션
ctrl 누른채로 클릭 하이퍼링크식 이동 (인수주욱,소스코드네비게이션)
ctrl + L 특정행 이동
ctrl + shift + F 지정코드형식으로 변경 (코드스타일)
ctrl + F6 에디터간 이동 (alt + tab처럼)
ctrl + F7 뷰간 이동
ctrl + F8 퍼스팩티브간 이동
F12 무조건 에디터로 이동 (커서)
ctrl + F 찾기
ctrl + J 간단찾기 (입력후 다시 ctrl+J, shift 같이 누르면 이전)
ctrl + K 블럭찾기 (shift같이 누르면 이전)
ctrl + H 상세검색 (파일하나 이상일때 유용,검색결과저장)
ctrl + alt + H 메서드 호출구조 (call hierarchy뷰)
ctrl + shift + O import정리 (추가,삭제, Organize Imports) |
민준이
2009/10/14 20:57
2009/10/14 20:57
|
|
|
|
|
|
 |
이 글에는 트랙백을 보낼 수 없습니다 |
|
|
|
|
| | |
|
|
|
|
|
최근 올라온 글 |
 |
|
|
|
|
|
|
|
|
|
|
블로그 최근 덧글 |
 |
|
|
|
|
|
|
|
|
|
|
최근 엮인글 |
 |
|
|
|
|
|
|
|
|
|
|
즐겨찾기
|
|
|
|
|
|
|
|
|
|
|
| Today :
|
12 |
|
| Yesterday :
|
43 |
|
| Total :
|
13715 |
|
|
|
|
|
|
|
| Powered by
|
Textcube 1.7.1 : Risoluto |
|
| Skin by
|
mulder21c |
|
|
|
|
|