● static long currentTimeMills(): 현재의 시간을 ms로 리턴한다.(1/1,000 초) 1970년 1월 1일 부터의 시간을 long 타입으로 리턴해 준다.
상용예)
long cur1 =System.currentTimeMills(); ... long elapsedTime =System.currentTimeMills() - cur1; System.out.println(elapsedTime + "ms");
● static long nanoTime(): 현재의 시간을 ns로 리턴한다.(1/1,000,000,000 초) JDK 5.0 부터 추가된 메소드이다. currentTimeMills()메소드는 1ms 이하의 시간을 측정하기가 어려움. nanoTime()메소드는 수행된 시간 측정이 목적이기 때문에 오늘날짜 알아내는 부분에는 사용하면 안됨.
사용예)
long cur =System.nanoTime(); ... double elapsedTime(System.nanoTime() - cur) / 1000000.0; System.out.println(elapsedTime + "ms");
♣ JDK 5.0 이상에서는 시간 측정용으로 만들어진 nanoTime() 메소드를 하용하기를 권장한다...
우선 Panel 클래스는 Applet 클래스의 상위 클래스이다. Applet과 Frame은 가장 바깥쪽의 컨테이너 역활을 하는데 반해 Panel 컨테이너는 컴포넌트를 그룹별로 모을 때, 주로 사용한다. 보통 컴포넌트를 Panel 에 먼저 부착을 하고 Panel을 Applet 혹은 Frame 에 부착을 한다. 왜 이렇게 복잡하게 하느냐 싶겠지만, 실제로 Panel 은 불가피한 배치문제로 인해 많이 사용되고 있다. Panel 은 다른 컨테이너에 쉽게 부착을 할 수 있는데, 컴포넌트를 추가할 때와 마찬가지로 add() 메서드를 사용한다.
Frame 컨테이너는 어플리케이션의 대표적인 컨테이너이다. 이때까지 우리가 예제를 다룬것에서 그다지 벗어나지 않는다. Frame 컨테이너에서도 애플릿과 마찬가지로 add()를 이용해서 컴포넌트를 부착하면 된다. 다만 Frame 컨테이너는 부착을 할때 위치를 지정해 줘야한다. 또한 Frame 컨테이너의 크기를 결정해주는 setSize() 라든지 setBounds()를 반드시 코딩해 줘야하며 화면에 보이도록 setVisible(true)나 show() 메서드를 코딩해 줘야한다.
2> Frame 컨테이너 사용하기
Frame 클래스 import 하기(import java.awt.Frame;)
Frame 클래스 상속 받기
setSize() 혹은 setBounds()로 크기 결정하기 --> 중요
setVisible(true)나 show() 로 화면에 출력하기 --> 하지 않으면 화면에 보이지 않음.
// 반드시 크기결정과 화면출력을 해줘야한다. // 에러는 나지 않지만 실행시 화면이 보이지 않는다. lf.setSize(300,300); lf.show();
} }
<< 실행 결과 >>
실행결과를 보면 좀 황당하다는 생각이 들것이다. 왜냐면 맨 마지막에 부착을 했던 라벨만이 보이기 때문이다. 이것은 애플릿과 프레임의 배치 관리자가 달라서 그렇다. 다시 말하면 애플릿은 붙이는 순서대로 붙는 배치가 디폴트이고 (이것을 FlowLayout 이라한다.) 프레임은 5가지로 영역이 나누어서 붙는 배치가 디폴트이기 때문이다.(이것을 BorderLayout 이라한다.)
아직 배치에 관해 배우지 않았으므로 결과에는 신경을 쓰지 말고 넘어가자. 어쨌든 여기서 중요한 것은 애플릿으로 코딩한 것을 프레임으로 혹은 프레임을 애플릿으로 바꾸는 연습을 많이하고 그 원리를 이해해야한다.
인터페이스는 일종의 추상클래스이다. 인터페이스는 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다. 오직 추상메서드와 상수만을 멤버로 가질 수 있으며, 그 외의 다른 어떠한 요소도 허용하지 않는다. 추상클래스를 부분적으로만 완성된 '미완성 설계도'라고 한다면, 인터페이스는 구현된 것은 아무 것도 없고 밑그림만 그려져 있는 '기본 설계도'라 할 수 있다.
추상클래스처럼 인터페이스도 완성되지 않은 불완전한 것이기 때문에 그 자체만으로 사용되기 보다는 다른 클래스를 작성하는데 도움 줄 목적으로 작성된다.
7.2 인터페이스의 작성
인터페이스를 작성하는 것은 클래스를 작성하는 것과 같다. 다만 키워드로 class 대신 interface를 사용한다는 것만 다르다. 그리고 interface에도 클래스와 같이 접근제어자로 public 또는 default를 사용할 수 있다.
일반적인 클래스의 멤버들과 달리 인터페이스의 멤버들은 다음과 같은 제약사항을 가지고 있다.
- 모든 멤버변수는 publicstatic final 이어야 하며, 이를 생략할 수 있다. - 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.
[참고]인터페이스에 정의된 모든 멤버에 예외없이 적용되는 사항이기 때문에 제어자를 생략할 수 있는 것이며, 편의상 생략하는 경우가 많다. 생략된 제어자는 컴파일시에 컴파일러가 자동적으로 추가해준다.
interface PlayingCard { publicstaticfinalint SPADE = 4; finalint DIAMOND = 3; // public static final int DIAMOND=3; staticint HEART = 2; // public static final int HEART=2; int CLOVER = 1; // public static final int CLOVER=1;
publicabstractString 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); }
클래스에서의 상속과 마찬가지로 자손인터페이스(Fightable)은 조상인터페이스(Movable, Attackable)에 정의된 멤버를 모두 상속받는다. 그래서 Fightable자체에는 정의된 멤버가 하나도 없지만 조상인터페이스로 부터 상속받은 두 개의 추상메서드, move(int x, int y)와 attack(Unit u)를 멤버로 갖게 된다.
7.4 인터페이스의 구현
추상클래스에서와 같이 인터페이스의 추상메서드를 몸통을 구현하는 자손클래스를 작성해야한다. 이때는 인터페이스를 '구현(implement)한다'고 하며 키워드 implements를 사용한다.
class 클래스이름 implements 인터페이스이름 { // 인터페이스에 정의된 추상메서드를 구현해야한다. }
class Fighter implements Fightable { publicvoid move() { /* 내용 생략*/ } publicvoid attack() { /* 내용 생략*/ } }
class Fighter extends Unit implements Fightable { publicvoid move(int x, int y) { /* 내용 생략 */} publicvoid attack(Unit u) { /* 내용 생략 */} }
[참고]인터페이스의 이름에는 주로 Fightable과 같이 '~ 를 할 수 있는'의 의미인 'able'로 끝나는 것들이 많은데, 그 이유는 어떠한 기능 또는 행위를 하는데 필요한 메서드를 제공한다는 의미를 강조하기 위해서이다. 또한 그 인터페이스를 구현한 클래스는 '~를 할 수 있는' 능력을 갖추었다는 의미이기도 하다. 이름이 'able'로 끝나는 것은 인터페이스라고 쉽게 추측할 수 있지만, 모든 인터페이스의 이름이 반드시 'able'로 끝나야 하는 것은 아니다.
[예제7-23] FighterTest.java
class FighterTest { publicstaticvoid 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 instanceofObject) { System.out.println("f는 Object클래스의 자손입니다."); } } }
class Fighter extends Unit implements Fightable { publicvoid move(int x, int y) { /* 내용 생략 */} publicvoid 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); }
실제로 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 { publicvoid move(int x, int y) { /* 실제구현내용 생략 */} publicvoid attack(Unit u) { /* 실제구현내용 생략 */} }
오버라이딩을 할 때는 조상의 메서드보다 넓은 범위의 접근제어자를 지정해야한다는 것을 기억할 것이다. Movable인터페이스에 void move(int x, int y)와 같이 정의되어 있지만 사실 public abstract가 생략된 것이기 때문에 실제로 publicabstractvoid move(int x, int y)이다. 따라서 이를 구현하는 Fighter클래스에서는 void move(int x, int y)의 접근제어자를 반드시 public으로 해야 하는 것이다.
7.5 인터페이스를 이용한 다중상속
두 조상으로부터 상속받는 멤버 중에서 멤버변수의 이름이 같거나 메서드의 선언부가 일치하고 구현 내용이 다르다면 이 두 조상으로부터 상속받는 자손클래스는 어느 조상의 것을 상속받게 되는 것인지 알 수 없다. 어느 한 쪽으로부터의 상속을 포기하던가, 이름이 충돌하지 않도록 조상클래스를 변경하는 수 밖에는 없다.
자바에서는 이러한 충돌문제를 해결하기 위해서 단일 상속만을 허용하고, 인터페이스를 이용해서 단일 상속의 단점을 보완하도록 하였다.
인터페이스는 상수만을 정의할 수 있으므로 조상클래스의 멤버변수와 충돌하는 경우는 극히 드물고 추상메서드는 구현내용이 전혀 없으므로 조상클래스의 메서드와 선언부가 일치하는 경우에는 당연히 조상클래스 쪽의 메서드를 상속받으면 되므로 문제되지 않는다. 이렇게 해서 상속받는 멤버의 충돌은 피할 수 있지만, 다중상속의 장점을 잃게 된다는 것이 아쉽다.
만일 두 개의 클래스로부터 상속을 받아야 할 상황이라면, 두 조상클래스 중에서 비중이 높은 쪽을 선택하고 다른 한쪽은 클래스 내부에 멤버로 포함시키는 방식으로 처리하거나 어느 한쪽을 필요한 부분을 뽑아서 인터페이스로 만든 다음 구현하도록 한다.
다음과 같이 Tv클래스와 VCR클래스가 있을 때, TVCR클래스를 작성하기 위해 두 클래스로부터 상속을 받을 수만 있으면 좋겠지만 다중상속을 허용하지 않으므로, 한 쪽만 선택하여 상속받고 나머지 한 쪽은 클래스 내에 포함시켜서 내부적으로 인스턴스를 생성해서 사용하도록 한다.
이제 IVCR 인터페이스를 구현하고 Tv클래스로부터 상속받는 TVCR클래스를 작성한다. 이때 VCR클래스 타입의 참조변수를 멤버변수로 선언하여 IVCR인터페이스의 추상메서드를 구현하는데 사용한다.
publicclass TVCR extends Tv implements IVCR { VCR vcr = new VCR(); publicvoid play() { vcr.play(); // 코드를 작성하는 대신 VCR인스턴스의 메서드를 호출하면 된다. } publicvoid stop() { vcr.stop(); } publicvoid reset() { vcr.reset(); } publicint getCounter() { return vcr.getCounter(); } publicvoid 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 { publicvoid move(int x, int y) { /* 내용 생략 */} publicvoid attack(Fightable f) { /* 내용 생략 */} }
위와 같이 Fightable인터페이스를 구현한 Fighter클래스가 있을 때, attack메서드의 매개변수로 Fighter인스턴스를 넘겨 줄 수 있다. 즉, attack(new Fighter())와 같이 할 수 있다는 것이다.
리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다. 위의 코드에서는 method()의 리턴타입이 Fightable인터페이스기 때문에 메서드의 return문에서Fightable인터페이스를 구현한 Fighter클래스의 인스턴스를 반환한다.
class ParserManager { // 리턴타입이 Parseable인터페이스이다. publicstatic Parseable getParser(String type) { if(type.equals("XML")) { returnnew XMLParser(); } else { Parseable p = new HTMLParser(); return p; // return new HTMLParser(); 위의 두 줄을 간단히 할 수 있다. } } }
class XMLParser implements Parseable { publicvoid parse(String fileName) { /* 구문 분석작업을 수행하는 코드를 적는다. */ System.out.println(fileName + " - XML parsing completed."); } }
class HTMLParser implements Parseable { publicvoid 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메서드에서 returnnew XMLParser(); 대신 returnnew 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개의 메서드가 필요할 것이다.
그리고 GroundUnit의 자손 중에는 Marine과 같이 기계화 유닛이 아닌 클래스도 포함될 수 있기 때문에 repair메서드의 매개변수 타입으로 GroundUnit은 부적합하다. 현재의 상속관계에서는 이들의 공통점은 없다. 이 때 인터페이스를 이용하면 기존의 상속체계를 유지하면서 이들 기계화 유닛에 공통점을 부여할 수 있다. 다음과 같이 Repairable이라는 인터페이스를 정의하고 수리가 가능한 기계화 유닛에게 이 인터페이스를 구현하도록 하면 된다.
interface Repairable {}
class SCV extends GroundUnit implements Repairable { //... }
class Tank extends GroundUnit implements Repairable { //... }
class DropShip extends AitUnit implements Repairable { //... }
이제 이 세 개의 클래스에는 같은 인터페이스를 구현했다는 공통저이 생겼다. 인터페이스 Repairable에 정의된 것은 아무것도 없고, 단지 인스턴스의 타입체크에 사용된다. Repairable인터페이스를 중심으로 상속계층도를 그려보면 다음과 같다.
그리고, repair메서드의 매개변수의 타입을 Repairable로 선언하면, 이 메서드의 매개변수로 Repairable인터페이스를 구현한 클래스의 인스턴스만 받아들여질 것이다.
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클래스에 다음과 같은 내용의, 건물을 이동시킬수 있는, 새로운 메서드를 추가하고자 한다면 어떻게 해야할까?
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 { publicstaticvoid main(String args[]) { B b = new B(); b.methodB(); } }
class B { publicvoid 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 { publicabstractvoid methodB(); }
그 다음에는 클래스 B가 인터페이스 I를 구현하도록 한다.
class B implements I { publicvoid methodB() { System.out.println("methodB in B class"); } }
이제 클래스 A는 클래스 B 대신 인터페이스 I를 사용해서 작성할 수 있다.
class A { publicvoidmethodA(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 { publicabstractvoid play(); }
class B implements I { publicvoid play() { System.out.println("play in B class"); } }
class C implements I { publicvoid play() { System.out.println("play in C class"); } }
class InterfaceTest { publicstaticvoid 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 { publicstaticvoid main(String[] args) { A a = new A(); a.methodA(); } }
class A { void methodA() { I i = InstanceManager.getInstance(); i.methodB(); } }
interface I { publicabstractvoid methodB(); }
class B implements I { publicvoid methodB() { System.out.println("methodB in B class"); } }
class InstanceManager { publicstatic I getInstance() { returnnew B(); } }
Exception in thread "main" java.lang.NoClassDefFoundError: 파일명
발생되는 경우
클래스 파일을 찾을 수 없는 경우
조 언
실행하려는 클래스 파일 이름이 제대로 되어 있는지 확인한다. 또한, CLASSPATH 설정이 제대로 되어 있는지 확인하며 (도스모드에서 set명령어) 만약, 되어있지 않다면 설정한다. (CLASSPATH = jdk1.3/jre/lib/rt.jar; 2-1강좌 참조)
번 호
2
ERROR
cannot resolve symbol symbol : class in(에러가 난 부분) location : class StackTest(찾으려는 위치)
발생되는 경우
이해할 수 없는 클래스나 메소드, 변수명이 올경우
조 언
보통 이 에러는 철자가 틀렸을 경우에 많이 발생한다. 클래스, 메소드, 변수의 철자를 세심히 확인해 본다. 특히, 철자를 확인할때 대소문자 구분을 확실히 체크한다.(자바는 대소문자를 구별한다.) 그리고 클래스에서 발생할 경우 import를 해주었는지 확인해 봅니다.
번 호
3
ERROR
non-static variable 변수이름(or method 메소드이름) cannot br referenced from a static context
발생되는 경우
static 메소드 안에서 static 으로 선언되지 않은 메소드나 변수를 참조(사용)했을 경우. 특히, 메소드의 경우는 인스턴스를 사용하지 않고 static메소드 내에서 바로 선언한 경우.
조 언
static 선언자의 사용여부를 살펴보고 static 메소드 안에 static으로 선언되어지지 않은 메소드나 변수가 있느지 확인해본다. 만약 그런것이 있으면 메소드를 새로 만들어 그쪽에서 선언한다. 단, 인스턴스를 생성해서 불러줘야 한다는 것을 잊지 말아야한다.
번 호
4
ERROR
valiable 변수명 might not have been initialized
발생되는 경우
지역변수인 변수명의 변수가 초기화가 되어있지 않았을 경우
조 언
지역변수(메소드 내에서 선언한 변수)를 초기화 하지 않은채 선언했을 경우 발생한다. 멤버 필드가 아닌 경우는 반드시 변수 선언시 초기화를 해주어야 한다. (멤버 필드는 자바 프로그램 자체에서 자동으로 default값으로 초기화 해준다.)
번 호
5
ERROR
class 클래스명 is public, should be declared in a file named 클래스명.java
발생되는 경우
클래스명이 public으로 선언되었는데 파일명과 다를 경우
조 언
public으로 선언된 클래스가 있다면 반드시 그 클래스명과 파일명이 같아야 한다. 클래스명과 파일명의 대소문자 및 철자가 같은지 비교해 본다. 또한, public으로 선언된 클래스가 하나 이상 있는지 찾아본다.(반드시 하나만 있어야한다.)
번 호
6
ERROR
push(java.lang.object)[메소드(인자로 받을 수 있는 형)] in java.util.Stack(메소드의 클래스) cannot be applied to (int)[잘못 들어간 형]
발생되는 경우
메소드에서 인자를 받을 때 받을 수 있는 형이 아닌 자료형 또는 클래스형을 사용할 경우
조 언
사용하는 메소드의 API를 참고하여 어떤 형을 인자로 받을 수 있는지 찾아본다. API를 보지 못할 경우는 각 자료형으로 인자를 직접 바꾸어 본다.
번 호
7
ERROR
java.lang.NoSuchMethodError: main Exception in thread "main"
발생되는 경우
클래스 파일 안에서 main() 메소드를 찾을 수 없는 경우
조 언
자바 애플릿이 아닌 이상 자바 애플리케이션은 반드시 main() 메소드를 사용해야 합니다. main() 메소드를 빼먹지 않았는지 확인해 보십시요. 또한 public static void main(String[] args) 형식으로 씌어졌는지도 확인해 보십시요. (main 클래스는 반드시 위와 같은 형식으로 만들어져야 합니다.)
번 호
8
ERROR
unreported exception java.io.IOException(Exception명); must be caught or declared to be thrown
발생되는 경우
예외가 발생하는데 예외처리를 해주지 않았을 경우
조 언
예외를 발생하는 메소드 같은 경우는 반드시 예외처리를 해주어야 합니다. 예외를 발생하거나 예외처리를 해야하는 메소드는 API를 확인해 보시면 알 수 있습니다. 그렇지 않다면 컴파일 후 지금처럼 에러가 난 예외를 예외처리해 주시면 됩니다. 또한, 예외를 처리할 때는 메소드 차원에서 throws 예외명을 이용해서 처리할 수 있고 try{} catch{} 구문을 이용해서 직접 처리해 주셔도 됩니다.(예외 강좌를 참고하세요.) 특히, 예외도 클래스이므로 반드시 예외가 들어간 패키지를 import 해주어야 합니다.
번 호
9
ERROR
Note : Calculator.java(파일명) uses or overrides a deprecated API. Note : Recompile with -deprecation for details.
발생되는 경우
JDK 버전이 높아졌거나 보안등의 기타이유로 사용이 deprecated된 메소드를 사용한 경우
조 언
이건 예외라기 보다는 경고 입니다.(실행하면 됩니다.^^) JDK가 버전이 높아지거나 보안등의 이유에 따라 예전에 만들어졌지만 필요가 없어지거나 대체된 메소드가 생겨났습니다. 그런 메소드를 deprecated 되었다고 하는데 이것은 API상에 나왔있습니다. 또한, 컴파일할때 -deprecation 옵션주면 어떤 메소드가 deprecate됐는지 알수있읍니다. 사용이 중지 됐다고 보기 보다는 사용을 가능하면 하지 않게끔 해주는 거죠. 대치되었거나 버전 업된 메소드를 사용하시면 됩니다.
MouseEvent(클래스명) should be declared abstract; it does not define mouseDragged(java.awt.event.MouseEvent)[메소드명(메소드가 포함된 클래스)] in MouseEvent(클래스명)
발생되는 경우
implements한 Interface의 모든 메소드를 구현하지 않아서 발생됨
조 언
Interface는 모든 메소드가 선언만 되고 구현되지 않은 추상(abstract) 메소드입니다. 만약 Interface를 implements하려면 implements한 클래스가 Interface에서 선언한 모든 메소드를 구현해 주어야 합니다. 하나라도 빠질 경우 implements한 클래스도 추상 클래스로 보고 에러가 발생합니다. 에러에 구현해 주어야할 메소드명이 나오므로 그곳에 쓰여있는 메소드를 구현해 주면 됩니다. 만약, 그 메소드를 구현해 주었는데 에러가 나면 철자 및 대소문자를 다시 확인해 보십시요.
번 호
11
ERROR
incompatible types found : /null(입력한 자료형) required : int(요구하는 자료형)
발생되는 경우
입력을 했을때 맞지 않는 자료형이나 클래스형을 입력할 경우
조 언
incompatible 은 '성미가 맞지 않는','모순된' 이라는 뜻을 가진 단어 입니다. 단어뜻 처럼 입력 경우 required 에 나타난 자료형 및 클래스형을 요구하는데 found 에서 나타난 자료형 및 클래스형을 써주어서 입력을 하지 못하게 되어서 발생하는 에러입니다. found 에 나타난 자료형을 required 에 나타난 자료형으로 변경해 주시면 됩니다.
번 호
12
ERROR
package java.servlet(패키지명) does not exist
발생되는 경우
import한 패키지가 존재하지 않을 경우
조 언
import한 패키지가 존재하지 않을 경우에 발생하는 에러입니다. 철자와 대소문자를 먼저 확인하고 CLASSPATH 설정을 확인해 보시기 바랍니다. 또한 그 곳에 패키지가 jar파일로 있는지도 확인해 보셔야 합니다. (API에 나와있는 패키지는 rt.jar에 다 있습니다. 단 javax가 붙거나 다름으로 시작되는 확장 패키지는 설치해 주어야합니다.(javax.swing 제외))
번 호
13
ERROR
java.lang.NullPointerException Exception in thread "main"(메소드) java.lang.NullPointerException at java.awt.Container.addImpl(Container.java:341)... [에러가 일어난 부분]
발생되는 경우
참조하거나 사용한 클래스 또는 자료형이 초기화 되지 않은 경우
조 언
보통 이것은 awt나 배열 부분에서 자주 발생하는데 초기화를 해주지 않아서 많이 발생합니다. 자바의 변수들은 기본적으로(자동으로 초기화 되는 멤버필드등을 제외하고) 초기화를 요구합니다. 에러에 체크된 부분을 검토해 보시고 초기화를 해주십시요.
번 호
14
ERROR
';'(빠진 부분) expected
발생되는 경우
문법상으로 써야할 것을 쓰지 않은 경우 발생합니다.
조 언
주로 ';'을 안써주시거나 아님 '()'(괄호)를 열기만 하고 닫지 않은 실수를 할 경우 발생합니다. 대부분 이 에러가 발생한 경우는 에러에 나온것을 소스에 추가해 주시면 됩니다.
번 호
15
ERROR
unexpected type required : value(요구하는 타입) found : class(소스상 써준 타입)
발생되는 경우
써주어야 할 타입이 아닌 잘못된 타입을 써주었을 경우
조 언
unexpected type 에러를 해석하면 '기대하지 않은 타입'이란 뜻을 가지고 있습니다. 즉, 원하는 타입(required)이 아닌데 잘못된 타입(found)을 써준 경우 발생합니다. 에러 체크된 부분의 타입을 required 에서 나타난 타입으로 변경해 주시면 됩니다.
번 호
16
ERROR
java.lang.ArrayIndexOutOfBoundsException at Test.main(Test.java:10)[클래스.메소드(파일명:에러난 위치)] Exception in thread "main"(예외가 던져진 메소드)
발생되는 경우
배열의 범위를 넘어선 값을 넣었을 경우
조 언
위의 에러는 특이하게 컴파일은 이상없이 되지만 실행을 하면 발생하는 에러입니다. 배열의 범위를 넘어설 경우에 발생하므로 에러난 위치의 배열의 참조 범위를 확인해보시고 선언해둔 배열의 범위에 맞게 조정해 주시면 됩니다.
번 호
17
ERROR
illegal start of expression
발생되는 경우
선언자(modifier)를 잘못 집어 넣은 경우
조 언
에러의 단어뜻을 확인해 보면 '표현의 시작이 부적격 합니다.'하고 해석할 수 있습니다. 보통 선언자가 맞지 않거나 쓰일데가 아닌데 선언자를 줄 경우에 많이 발생합니다. 특히 메소드안에서 static 선언자를 쓴 경우에는 직격으로 볼수 있죠. 에러가 난 부분의 선언자를 제거하거나 맞는 것인지 다시 확인해 보십시요.
번 호
18
ERROR
java.io.InputStream(클래스) is abstract; cannot be instantiated
발생되는 경우
abstract로 선언된 클래스를 직접 new 명령어를 이용하여 인스턴스화 할 경우
조 언
abstract로 선언된 클래스를 직접 new 명령어를 이용하여 인스턴스화 할 경우에 발생하는 에러입니다. 왜냐하면, 추상 클래스는 직접 new 명령어를 이용하여 인스턴스화 할수 없기 때문입니다.(객체를 못만든다구요.) 이 경우에는 인스턴스를 다른 방법으로 생성하시면 됩니다. 예를 들어 인스턴스를 반환하는 메소드를 이용한다거나 상속을 통해서 상속받은 클래스의 인스턴스를 만들어 직접 인스턴스를 만드는 효과를 낼수도 있구요. 원하시는 방법으로 바꾸어 보시길...
번 호
19
ERROR
local variable name(변수명) is accessed from within inner class; needs to be declared final
발생되는 경우
Local Class의 변수를 final로 선언하지 않은 경우
조 언
Local Class의 변수는 참조변수의 참조값 변동을 방지하기 위하여 final 선언자를 붙여주어야 합니다. 변수에 final 선언자를 붙이면 변수는 값을 변동할 수 없는 상수 처럼 쓰이며 만약 이 값을 참조할 경우 자바는 이 값을 넘기는게 아니라 이 값의 복사본을 참조 값으로 넘기게 됩니다. 그러므로 Local Class에서 참조값 변동없이 변수를 참조할 수 있게 되는 것입니다. Local Class를 정의해준 곳을 살펴보고 final 선언자를 확실하게 확인하시기 바랍니다.
내부 클래스 안에서는 static 선언자를 쓸수 없습니다. 내부 클래스 안에서 사용된 static 선언자를 제거해 주십시요.
번 호
21
ERROR
referenceto List is ambiguous,both class java.util.List(클래스) in java.util(패키지) and class java.awt.List(클래스) in java.awt(패키지) match
발생되는 경우
클래스 사용시 다른 패키지내에 동일이름의 클래스들이 있어서 참조가 모호할 경우
조 언
예시를 보면 아시겠지만 import 한 패키지중에 같은 이름을 사용하는 클래스를 클래스 이름만으로 생성함으로서 참조가 모호해질 경우 발생하는 에러입니다. 이와 같은 경우는 import를 하나 제거 하거나 아님 java.util.List 이런식으로 직접 그 클래스의 패키지를 같이 써줌으로서 모호성을 제거할수 있습니다.
번 호
22
ERROR
m()(메소드명) in B(클래스명) cannot override m()(메소드명) in A(클래스명); attempting to use incompatiable return type
발생되는 경우
클래스를 상속받고서 메소드를 오버라이드 하고자할때 잘못한 경우
조 언
클래스를 상속받고서 메소드를 오버라이드 할 경우에는 지켜야 하는 규칙이 있습니다. 1. 메소드의 이름이 같아야 합니다. 2. 메소드의 파라미터 개수, 데이터형이 같아야 합니다. 3. 메소드의 리턴형이 같아야 합니다. 4. 상위 메소드와 동일하거나 더 구체적인 Exception을 발생시켜야 합니다. 5. 상위 메소드와 동일하거나 접근범위가 더 넣은 접근 제한자를 사용해야 합니다.
님의 메소드 오버라이드시 위 규칙을 잘 지켰는가를 다시 한번 확인해 보세요.
번 호
23
ERROR
getPathBetweenRows(int,int)(메소드) has protected access in javax.swing.jTree(클래스)
발생되는 경우
protected로 선언된 메소드를 상속 없이 직접 불러쓸 경우
조 언
protected로 선언되어 있는 메소드는 상속하거나 같은 package에 있을 때만 쓸 수 있습니다. 상속해서 다시 public 메소드로 값을 받던지 아니면 public 메소드 중에서 비슷한 기능을 하는 메소드가 있는지 찾아서 바꾸어주어야 합니다.
번 호
24
ERROR
invalid method declaration; return type required
발생되는 경우
리턴 타입을 쓰지 않아 메소드의 선언이 잘못된 경우
조 언
리턴 타입을 쓰지 않아 메소드의 선언이 잘못된 경우에 발생하는 에러이므로 에러가 발생한 메소드를 확인해보고 리턴 타입을 맞게 적어주어야 합니다.
번 호
25
ERROR
Error occurred during initialization of VM java.lang.ExceptionInInitializerError
발생되는 경우
static으로 선언된 변수중 초기화가 안되어 있는 것이 있는 경우
조 언
static으로 선언된 변수중에 초기화가 안된게 있는 경우에 발생하는 에러이므로 에러가 발생한 변수를 확인해보고 알맞은 초기화를 시켜주거나 변수의 위치를 자동 초기화가 가능한 메소드 밖의 클래스 변수로서 사용하게 합니다.
번 호
26
ERROR
Error opening registry key 'Software\JavaSoft\Java Runtime Environment' Error: could not find java.dll Error: could not find Java 2 Runtime Environment
발생되는 경우
중복설치 등으로 인해 레지스트리 키값이 잘못되어 있는 경우
조 언
중복설치 등으로 인해 레지스트리 키값이 잘못되어 있는 경우에 발생하는 에러이므로 레지스트리 편집기를 열어서 HKEY_LOCAL_MACHINE -> SOFTWARE -> JavaSoft에 보시면 3개의 키가 있을 겁니다. 그중에서 첫번째 키인 Java 런타임 환경 을 마우스 오른쪽 버튼으로 클릭하여 Java Runtime Environment로 이름을 바꿔주시면 됩니다.
번 호
27
ERROR
Error Registry Key 'Sofrware\JavaSofrware\Java Runtime Environment\CurrentVerison' has value '1.1',but '1.3' is requried. Error: could not find java.dll Error: could not find java 2 Runtime Enviroment.
발생되는 경우
중복설치 등으로 인해 레지스트리 키값의 자바 버전이 잘못되어 있는 경우
조 언
중복설치 등으로 인해 레지스트리 키값의 자바 버전이 잘못되어 있는 경우에 발생하는 에러이므로 레지스트리 편집기를 열어서 HKEY_LOCAL_MACHINE -> SOFTWARE -> JavaSoft -> Java Runtime Environment의 Current version의 값을 1.3으로 되어있는지 확인해 주시면 됩니다.
JDBC를 연결하는 중에 드라이버를 찾지 못할 경우에 발생하는 에러이므로 각 데이터 베이스에 맞는 드라이버가 제대로 다운로드 되었는지 확인해 보시고 드라이버의 위치가 클래스 패스에 잡혀 있는지 확인해주시면 됩니다.
번 호
29
ERROR
Method printIn(java.lang.String)(메소드명) not found in class java.io.PrintStream(클래스명)
발생되는 경우
자신이 사용한 클래스의 메소드가 맞지 않는(=없는)경우
조 언
자신이 사용한 클래스의 메소드가 맞지 않는(=없는) 경우에 발생하는 에러이므로 API를 통해서 사용하고자 하는 클래스와 메소드를 다시 한번 확인해 봅니다. 보통 이경우 메소드의 철자나 대소문자를 잘못 쓴 경우가 많으니 그점을 유심히 살표봅니다. 마지막으로 철자와 대소문자도 맞는다면 메소드의 인자의 객체형을 맞게 주었는지 확인해보면 됩니다.
자바를 하다보면 에러와 만나게 되는데요... 자바는 친절해서 몇번라인에 에러가 났으니 고쳐달라고 콘솔창에 표시를 합니다... 그래도 모르는 에러들이 있는데요.. 조금이라도 참고해 보아요
UNIX 플랫폼 또는 Win32 플랫폼 등과 같은 각 운영체제는 파일과 디렉토리 이름에 시스템 의존적인 경로명(system-dependent pathname)의 문자열을 사용합니다. 이는 프로그램 개발자로 하여금 시스템에 일일이 신경쓰도록 해야 하고, 프로그램의 이식성 또는 상호호환성을 어렵게 하는 원인 중의 하나가 됩니다. 이러한 문제를 해결하기 위해, 다시 말해서 플랫폼 독립적인 파일 또는 디렉토리 관리 기능을 제공하기 위해, 자바에서는 File 클래스를 제공해 주고 있으며, 이 클래스는 추상적이고 시스템(플랫폼) 독립적인 관점의 계층적인 경로명을 제공하고 있습니다. 이러한 추상 경로명은 다음과 같은 두 가지 요소로 구성되어 있습니다.
- 플랫폼 의존적인 prefix 문자열: UNIX 플랫폼 상에서의 루트 디렉토리를 나타내는 "/", 그리고 Win32 플랫폼 상에서는 디스크 드라이브명(disk-drive specifier) 또는 UNC 경로명을 사용할 경우에 사용하는 "\\" 등이 있습니다.
- 파일 또는 디렉토리를 나타내는 경로명: 이름을 나타내는 0개 이상의 문자열입니다.
prefix 개념은 UNIX 플랫폼상에서의 루트 디렉토리와 Win32 플랫폼 상에서의 드라이브명(drive specifiers), 루트 디렉토리, 그리고 UNC 경로명을 다루기 위해 사용됩니다. 예를 들면, 다음과 같습니다.
- UNIX 플랫폼: 절대 경로명의 prefix는 항상 "/"이고, 상대 경로명은 prefix를 갖지 않습니다. 이 때, 루트 디렉토리에 대한 자바의 추상 경로명은 prefix "/"와 빈 이름열(name sequence)을 갖습니다.
"/"
"/home/ywpark/"
- Win32 플랫폼: 경로명의 prefix는 드라이브 문자와 ":"로 구성된 드라이브명(drive specifier)과 절대 경로명일 경우 "\" 등을 포함합니다. UNC 경로명의 prefix는 "\\"이고, 호스트이름과 공유 이름은 이름열의 처음에 나타나는 두 개의 이름입니다. 드라이브를 갖지 않는 상대 경로명은 prefix가 없습니다.
"\\D:\ "
"\\Park\mp3z"
추상 경로명 내의 마지막 이름을 제외한 각 이름은 디렉토리를 의미하고, 마지막 이름은 디렉토리 이름일 수도 있고 파일 이름일 수도 있습니다. 경로명 문자열을 추상 경로명으로 변환하거나 또는 추상 경로명으로부터 경로명 문자열로 변환하는 것은 시스템에 의존적입니다.
추상 경로명 또는 문자열 형태의 경로명은 절대 경로일 수도 있고 상대 경로일 수도 있습니다. 절대 경로명은 파일의 완전한 위치를 나타내므로 더 이상의 정보가 필요 업습니다. 반면, 반면, 상대 경로명이 가리키고 있는 파일은 다른 경로명에서 얻은 정보를 이용하여 그 위치를 얻을 수 있습니다. 일반적으로, java.io 패키지 내에 있는 클래스들은 항상 시스템 속성인 user.dir이 가지고 있는 현재 사용자 디렉토리(current user directory)로부터 상대 경로명을 얻습니다. 일반적으로, 현재 사용자 디렉토리는 자바 가상 머신이 시작된(invoked) 디렉토리입니다.
추상 경로명이 경로명 문자열로 변환될 때, 각 이름은 디폴트 구분자 문자를 이용하여 서로 구분됩니다. 디폴트 이름 구분자 문자(name-separator character)는 시스템 속성으로 file.separator에 정의되어 있습니다. 반면, 경로명 문자열이 추상 경로명으로 변환될 때, 경로명 내의 이름들은 디폴트 이름 구분자 문자 또는 시스템에 의해 지원되는 다른 이름 구분자 문자 등에 의해 구분되어 있습니다.
나. File 클래스
File 클래스는 파일 및 디렉토리를 관리할 수 있도록 기능을 제공해 주는 클래스입니다. File 클래스는 파일의 복사 또는 이름 변경 등의 조작을 할 경우에 사용될 뿐, 파일의 내용을 입출력 하기 위한 메소드를 제공해 주지는 않습니다. 자바에서는 모든 데이터의 입출력을 스트림에 기반하여 수행하므로, File 클래스 내부적으로 이러한 메소드를 구현할 필요가 없기 때문입니다. 입출력 스트림과 관련된 내용은 뒤에서 자세히 살펴보도록 하겠습니다. 그런데, 이러한 File 클래스의 인스턴스는 변경 불가능합니다. 다시 말해서, 한 번 생성되면, File 객체에 의해 표현되는 추상 경로명은 절대로 변하지 않습니다. File 클래스에는 다음과 같은 종류의 경로 구분자와 이름 구분자를 제공해 주고 있습니다.
- static char separatorChar: 시스템에 해당하는 디폴트 이름 구분 문자로서, 시스템 속성인 file.separator의 값을 갖습니다.
UNIX 시스템: '/'
Win32 시스템: '\'
- public static final String separator: 시스템에 해당하는 디폴트 이름 구분 문자(name-separator character)로서, 편의상 문자열로 표현되어 있습니다.
UNIX 시스템: "/"
Win32 시스템: "\"
- public static final char pathSeparatorChar: 시스템에 해당하는 경로 구분 문자로서, 시스템 속성인 path.separator의 값을 갖습니다.
UNIX 시스템: ':'
Win32 시스템: ';'
- public static final String pathSeparator: 시스템에 해당하는 경로 구분 문자(path-separator character)로서, 편의상 문자열로 표현되어 있습니다.
UNIX 시스템: ":"
Win32 시스템: ";"
File 클래스에는 다음과 같은 세 종류의 생성자가 있습니다. 각각에 대하여 간단히 살펴보도록 하겠습니다.
- public File(String pathname): 주어진 경로명을 추상 경로명으로 변환하여 새로운 File 객체를 생성합니다. 만약, 주어진 문자열이 빈 문자열이라면, 빈 추상 경로명이 됩니다.
- public File(String parent, String child): 두 개의 문자열에 주어진 경로명을 이용하여 새로운 File 객체를 생성합니다. parent 문자열이 null이면, child 문자열을 경로명으로 하는 객체를 생성하고, 그렇지 않을 경우 parent 문자열은 디렉토리를 나타내고, child 문자열은 디렉토리 또는 파일일 수 있습니다.
- public File(File parent, String child): 주어진 File 객체와 문자열을 이용하여 새로운 File 객체를 생성합니다. parent 객체가 null이면, child 문자열을 경로명으로 하는 객체를 생성하고, 그렇지 않을 경우 parent 객체는 디렉토리를 나타내고, child 문자열은 디렉토리 또는 파일일 수 있습니다.
File 클래스에서 제공해주는 주요 메소드를 살펴보면, 다음과 같습니다.
- public String getName(): 추상 경로명이 나타내는 파일 또는 디렉토리의 이름을 얻습니다. 이 이름은 경로명 이름에 있는 마지막 이름입니다.
- public String getParent(): 추상 경로명의 부모 경로에 대한 경로명을 문자열로 얻습니다. 이 때, 부모 경로명은 추상 경로명이 갖는 prefix와 경로명에 있는 마지막 이름을 제외한 나머지 이름들을 포함하고 있습니다.
- public File getParentFile(): 추상 경로명의 부모 경로에 대한 추상 경로명(File 객체)을 얻습니다. 이 때, 부모 경로명은 추상 경로명이 갖는 prefix와 경로명에 있는 마지막 이름을 제외한 나머지 이름들을 포함하고 있습니다.
- public String getPath(): 추상 경로명을 경로명 문자열로 변환하여 얻습니다. 이 때, 디폴트 이름 구분 문자가 적용됩니다.
- public boolean isAbsolute(): 절대 경로명인지를 얻습니다. UNIX 시스템 상에서는 prefix가 "/"를 포함하고 있을 경우, Win32 시스템 상에서는 prefix가 "\\"와 드라이브 문자 또는 "\\"를 포함하고 있을 경우 절대 경로명입니다.
- public String getAbsolutePath(): 추상 경로명에 대한 절대 경로명을 문자열로 얻습니다. UNIX 시스템 상에서 상대 경로명일 경우, 현재 사용자 디렉토리와 결합하여 절대 경로명을 얻습니다. Win32 시스템 상에서의 상대 경로명은 드라이브 문자를 포함하여, UNIX 시스템과 마찬가지로 현재 사용자 디렉토리와 결합하여 얻습니다.
- public File getAbsoluteFile(): "new File(this.getAbsolutePath()())"와 같습니다.
- public String getCanonicalPath() throws IOException: 추상 경로명에 대한 유일한 정규 표현 경로명을 얻습니다.
- public File getCanonicalFile() throws IOException: "new File(this.getCanonicalPath()())"와 같은 효과를 얻습니다.
- public boolean isHidden(): 숨겨진(hidden) 파일인지를 얻습니다.
- public long lastModified(): 파일이 마지막으로 갱신된 시간을 얻습니다. 시간은 epoch (00:00:00 GMT, January 1, 1970)로부터 경과된 밀리초를 나타내며, 값이 0L이면 파일이 존재하지 않거나 I/O 에러가 발생했음을 나타냅니다.
- public long length(): 파일의 크기를 얻습니다.
- public boolean createNewFile() throws IOException: 추상 경로명에 해당하는 파일이 존재하지 않는 경우 비어있는 새로운 파일을 생성합니다.
- public boolean delete(): 추상 경로명이 가리키는 파일이나 디렉토리를 삭제합니다. 이 때, 비어있지 않은 디렉토리는 삭제할 수 없습니다.
- public void deleteOnExit(): 가상 머신이 종료될 때, 이 추상 경로명이 나타내는 파일을 삭제되도록 요청합니다. 이 메소드에 의해 파일이 지워지도록 요청되면, 이 요청은 취소될 수 없을므로, 주의를 기울여서 사용해야 합니다.
- public String[] list(): 추상 경로명이 나타내는 디렉토리 내의 파일과 디렉토리의 이름에 대한 문자열 배열을 얻습니다.
- public File[] listFiles(): 추상 경로명이 나타내는 디렉토리 내의 파일과 디렉토리의 이름에 대한 File 객체 배열을 얻습니다.
- public boolean mkdir(): 이 추상 경로명에 해당하는 디렉토리를 생성합니다.
- public boolean mkdirs(): 이 추상 경로명에 해당하는 디렉토리를 생성가능한지를 얻습니다. 만약, 경로명의 부모 경로명이 존재하지 않을 경우도 고려됩니다.
대부분의 언어에서 입출력을 수행하는 것은 결코 간단한 얘기는 아니다. 그러나 스트림을 지원하는 언어라면 조금은 얘기가 틀려진다. 스트림은 입출력을 위임받아 처리하는 하나의 단위를 의미하는데, 스트림의 정의는 바이트의 순차적 입출력 방식이다.
이것은 입출력을 수행할 데이터를 바이트의 배열 형태로 바꾸어 입출력을 수행하는 방식으로 데이터가 순서대로 입력과 출력이 이루어진다는 말이다. 이 스트림 방식을 사용하게되면 하드웨어가 어떻게 바뀌더라도 일관된 방식으로 입출력을 수행할 수 있다.
자바에서 입출력을 수행하기 위해 java.io 패키지로 제공되며 이 패키지를 이용하면 스트림 방식의 입출력이 가능하다.
2. InputStreamReader / OutputStreamWriter 클래스
Reader와 Writer 클래스는 Object 클래스의 바로 하위 클래스이다. 이것들로부터 바이트 입출력 스트림 상속시켰는데, Buffered Reader/Writer와 InputStreamReader/Writer가 대표적이다.
InputSreamReader와 Writer를 이용하여 바이트의 입출력을 수행해보자.
import java.io.*;
public class ReWriter {
public static void main(String[] args) {
InputStreamReader ir = new InputStreamReader(System.in);
OutputStreamWriter or = new OutputStreamWriter(System.out);
int temp;
try{
while((temp=ir.read())!=-1)
System.out.println((char)temp);
}catch(IOException e){}
}
}
위의 예에서 ir.read() 메서드의 리턴형은 byte형이다. 즉, 형 그대로 출력하게 되면 숫자가 나타나게 된다. 이것을 우리가 원하는 문자로 보여주기 위해서는 char형으로 캐스트 연산되어야 한다.
3. BufferedReader / BuffersWriter
위의 방식에서 더욱 개선된 버퍼를 이용한 입출력 방식이다. 버퍼는 임시기억장치를 의미하는데 입출력 장치과 CPU 사이에서 위치해, 조금 더 효과적인 입출력을 지원하는 방식이다. 예를 들어 입출력 장치에서 입출력이 일어나면 CPU는 입출력이 완료될때까지 대기해야 하는데 버퍼를 사용함으로서 이러한 단점을 줄이면서, 버퍼에 줄을 저장할 수 있기 때문에 줄 단위 입출력이 가능하다.
import java.io.*;
public class StringInput {
public static void main(String[] args){
String inputString;
String text="String Test..";
try{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(text)));
while((inputString = br.readLine()) != null)
bw.write(inputString + "\n");
bw.close();
br.close();
}catch(IOExcetion e){}
}
}
4. FileReader / FileWriter
기본 문자 인코딩을 이용해서 파일의 입출력을 담당하는 클래스이다. 이 클래스를 사용하면 파일에 읽고 쓰는 것이 가능하다. 다음은 파일 복사의 예이다.
import java.io.*;
public class FileCopy {
public static void main(String[] args) {
try {
FileReader input = new FileReader("FileCopy.java");
FileWriter output = new FileWriter("Test.txt");
int charsRead;
while((charsRead = input.read()) != -1) {
output.write(charsRead);
}
input.close();
output.close();
}
catch(IOException e) {
e.printStackTrace();
}
}
}
5. 직렬화(Serializable)
직렬화란 객체를 입출력하기 위한 방식이다. 실제로 위까지의 예제는 모두 기본 데이터형므로 바이트의 배열형식으로 내보는 것이 그리 어려운 얘기는 아니었다. 그러나 객체를 바이트 형의 배열로 변환하는 것은 그리 간단한 얘기가 아니다.
다행스럽게도 자바에서는 직렬화라는 방식의 객체를 바이트 배열로 변환해주는 클래스가 지원된다.
public class User {
private int number;
private String name;
private String subject;
public User(int number, String name, String subject){
어떠한 클래스가 어떠한 부모로부터 상속받은 클래스라고 할 때, 부모 클래스를 지칭하는 키워드이다. this 키워드와 보통 비교해 설명하는데, this는 자신이 소속된 메서드를 호출하는 객체를 지칭하게 되지만, super는 자신이 소속되어 있는 클래스의 부모 클래스를 지칭하게 되는 것이다.
2. final 키워드
final은 클래스명, 메서드명, 변수명 앞에 각각 올 수 있다. 이것은 어느 곳에 위치하느냐에 따라 의미가 달라진다.
1. 클래스명 앞에 붙는 경우
final class A{ ... }
2. 메서드명 앞에 붙는 경우
class A{
final void add(){...}
...
}
3. 변수명 앞에 붙는 경우
class A{
final float PI=3.14;
...
}
1번처럼 클래스명 앞에 final이 오게되면 A라는 클래스는 더 이상 상속될 수 없음을 의미한다. 2번 같은 경우 메서드명 앞에 오게되었는데 이럴경우는 add()메서드는 더 이상 오버라이딩 될 수 없음을 의미한다. 추상 메서드는 반드시 오버라이딩이 되어야 하기 때문에 추상 메서드 앞에는 final이 올 수 없다.
3번의 경우를 보자. 3번은 변수명 앞에 왔는데 이럴 경우에는 앞에서 얘기했던 상수화가 이루어진다. 다시 얘기하자면, 상수는 변수와 다르게 값이 변경되지 않는 수를 의미한다. 상수명은 관례상 대문자로 기술하며, 선언 후에 변경될 수 없기 때문에 선언과 동시에 반드시 초기화가 이루어져야 한다.
3. 접근제한자
우선 이 얘기를 하기 전에 먼저 클래스와 패키지에 대한 개념부터 정립하자. 자바에서 클래스는 하나의 파일로 가정한다. 예를 들어 지금까지 클래스명과 파일명을 같게 했고, 소스 파일을 컴파일한 후에 보면 파일명과 같은 이름으로 class파일이 생성되었음을 알고 있을 것이다. 즉, 하나의 클래스는 하나의 파일로 간주하게 되는 것이고, 이 클래스들의 집합을 패키지라고 하며, 윈도우로 따지자면 파일의 집합. 즉, 폴더(디렉토리)가 되는 것이다. 이 클래스와 패키지에 대해서는 뒤에 더 따질테니 현재는 이 정도만 잡고 넘어가자.
접근제한자의 접근 허용 범위는 public->protected->생략->private 순이다.
public - 모든 접근에 대해 허용하며, 멤버 메서드는 대부분 public으로 선언한다.
protected - 같은 패키지/클래스에 대해 허용.
외부패키지라도 상속받은 자식에 대해 허용
생략 - 같은 패키지/클래스에서만 접근 가능
private - 같은 클래스에서만 접근 가능.
멤버 변수를 대부분 private으로 선언한다.
또한 뒤에서 배울 내부 클래스도 접근가능하다.
4. static 키워드
class A{
int garo;
int sero;
public static void main(String args[]){
A ob1 = new A();
A ob2 = new A();
}
}
위에서 ob1과 ob2는 각각 리턴 받는 주소가 다르다. 이것은 A의 공간이 서로 다른 주소에 생성되었기 때문이다. ob1.garo=20 했을 경우 ob2.garo의 값이 동시에 20으로 바뀌지 않는 것도 위와 같은 이유 때문이다.
그러나 static으로 선언된 정적 멤버 변수/메서드는 그렇지가 않다. 이것들은 각각의 객체들이 값을 공유해서 사용하게 되며, 객체명으로 접근하기보다는 대부분 클래스명으로 접근하게 된다.
public class StaticEx {
private static int number=0;
private int number1=0;
public StaticEx() {
++number;
}
public static void main(String args[]){
StaticEx ob1 = new StaticEx();
System.out.println(ob1.number+"번 객체 생성");
System.out.println(ob1.number1+"번 객체 생성");
StaticEx ob2 = new StaticEx();
System.out.println(ob2.number+"번 객체 생성");
System.out.println(ob2.number1+"번 객체 생성");
StaticEx ob3 = new StaticEx();
System.out.println(ob3.number+"번 객체 생성");
System.out.println(ob3.number1+"번 객체 생성");
}
}
5. Utility API
유틸리티 API는 유틸리티를 모아놓은 패키지를 의미하는 것이다. java.util 패키지가 바로 그것인데, 이 패키지는 유틸리티를 모아놓다보니 클래스간의 연관성을 별로 없다. 그러나 이 클래스는 JDK1.1.x부터 지원되어왔던 것이고, 대부분의 자바 플랫폼에서도 별 무리 없이 사용할 수 있을 뿐더러, 상당히 유용한 기능들을 지원하므로 한번쯤 살펴보고 진행하자.
배열은 크기가 고정되어 있으며, 배열의 크기를 넘어설 수 없다. 그러나 Vector는 배열과 다르게 크기가 가변적이며, 필요시 크기를 늘려서 제한없이 사용할 수 있다. 따라서 크기를 알 수 없는 배열을 사용해야 할 경우 유리하다. 개수가 정해지지 않은 데이터를 한 곳에 모을 경우에는 편하지만 배열보다는 속도가 느리다. 벡터에서 전체 크기를 용량이라고 부르며, 저장된 객체를 합친만큼을 크기라고 부른다.
벡터에서 원소의 저장은 add()메서드로 이루어지며 제거는 remove()메서드를 사용한다. 저장된 객체는 get(int index)를 통해 얻을 수 있는데 Object 리턴형을 가지므로 저장했던 원래의 클래스로 형변환후 사용해야 한다.
객체를 자주 저장하거나 복원할 경우 속도를 증가시키기 위해서는 벡터를 생성할 당시 초기 크기나 증가량을 충분히 설정할 필요가 있다. 또한 많은 수의 객체를 저장하기 전에 ensureCapacity(벡터의 최소 용량) 메서드로 용량을 원하는 만큼 한꺼번에 키워주는 것도 권장한다. 용량에 여유가 있다면 버퍼에 할당하는 회수가 많이 줄어들기 때문에 속도를 높이는 한가지 방법이다.
public class User {
private int number;
private String name;
public User(int number, String name){
this.number=number;
this.name=name;
}
public void print(){
System.out.println(number+"번 이름 : "+name);
}
}
import java.util.Vector;
public class VectorEx {
public static void main(String args[]){
Vector v = new Vector();
User ob1 = new User(1, "홍길동");
User ob2 = new User(2, "이순신");
User ob3 = new User(3, "박문수");
User ob4 = new User(4, "김유신");
v.add(ob1);
v.add(ob2);
v.add(ob3);
v.add(ob4);
for(int i=0;i<v.size();i++){
User ob = (User)v.get(i);
ob.print();
}
}
}
2) Hashtable
해시 테이블은 키 값과 대응되는 값을 저장하는 구조체인 사전 기능을 구현한 클래스이다. 쉽게 생각하면 사전을 찾을때 단어를 찾아 단어의 뜻을 찾아내는 것과 같은 이치이다. 벡터는 인덱스라는 int형의 키를 가지고 있지만, 해시 테이블은 int형이 아닌 객체 자체를 키값으로 가질 수 있다는 장점이 있다.
키 값과 대응 값이 하나씩 짝지어지는 것을 매핑이라고 하며, 해시 테이블의 매핑은 항상 한 키에 여러개의 대응되는 값이 있어서는 않된다. 만약 같은 킷값을 이용하여 저장하려고 할 경우 먼저 있던 값에 덮어씌워지는 결과를 초래하게 된다. 또한 vector와 마찬가지로 get을 하게되면 Object형으로 리턴된다. 그러므로 클래스형으로 변환하여 사용해야 한다.
void clear() - 모든 킷값을 제거
Enumeration keys() - 해시 테이블의 키 값을 돌려줌
Enumeration elements() - 해시 테이블에 저장된 객체를 돌려줌
Object get(Object key) - 주어진 키에 대응되는 값을 돌려줌
Ojbect put(Object key, Object value) - 키와 대응하는 것을 저장한다
int size() - 해시 테이블의 대응 관계의 개수를 리턴한다.
3) Enumeration
위에서 리턴형 중에 Enumeration 클래스가 존재하는데 이 클래스는 hasMoreElements와 nextElements 두 메서드를 가지고 있다. 이를 이용하면 다음의 예와 같이 객체를 모두 얻어낼 수 있다.
위와 같이 선언하게 된다. 위와 같이 선언하는 방법을 동적할당이라고 하며, 자바에서는 동적할당을 기본으로 한다. 동적할당은 일반적인 정적할당과 달리 여러 가지 장점을 가지는데 그 중 하나가 컴파일시에 주소가 결정되는 것이 아닌 실행시에 결정된다는 장점이다. 그로 인해 많은 잇점을 얻을 수 있다. 또한 배열은 여러 가지 유용한 기능을 보유하고 있는데 아래의 예를 보자
public class ObjectA{
public static void main(String args[]){
String[] A = new String[3];
A[0) = "이순신“;
A(1) =“김좌진”;
A(2) = "박문수“;
System.out.println("크기 : " +A.length);
A.sort();
for(int i=0;i<A.length;i++)
System.out.println("배열 "+i+" : "+ A[i]);
}
}
위에서보면 배열명.length라는 변수와 배열명.sort()라는 메서드가 나왔다. 앞에는 배열의 크기를 알려주는 것이고, 두 번째는 배열안에 있는 요소를 정렬시키는 메서드이다. 이 둘은 배열을 사용하면서 우용하게 사용될 것이다.
2. 클래스 선언
객체지향 프로그래밍은 항상 클래스 정의로부터 시작된다. 클래스를 정의하게 되면 정의된 클래스는 보통 Ojbect형이라고 하며, 최상위 클래스는 Object 자료형으로부터 상속받게 된다.
public class ObjectA{
public int i; //멤버 변수
public int j; //멤버 변수
public void add(){ //멤버 메서드
int k=i+j;
System.out.println("k="+k);
}
}
위와 같이 클래스가 선언되었다. 이 클래스는 ObjectA라는 이름을 가지며, ObjectA라는 자료형이 되었다. 그럼 위와 같이 클래스를 선언한다고해서 바로 사용할 수 있을까? 절대 그렇지 않다. 위와 같이 클래스를 선언했다고 해서 메모리에 할당된 것이 아니기 때문이다. 클래스를 사용하기 위해서는 객체를 선언해야 한다. 객체의 선언은
클래스명 객체명 = new 클래스명();
혹은
클래스명 객체명 = null;
이라고 기술하면 된다.
그럼 new 키워드가 하는 일에 대해서 살펴보자. new 키워드가 하는 역할은 =을 기준으로 오른쪽의 클래스 크기만큼 메모리 어딘가에 공간을 생성한 후, 객체명에게 이 주소를 리턴시켜준다. 이렇게 함으로서 객체명으로 객체의 멤버에 접근이 가능하게 된다.
public class ObjectEx{
public static void main(String args[]){
ObjectA ob = new ObjectA();
ob.i = 19;
ob.j = 20;
ob.add();
}
}
3. 생성자 메서드
그럼 위의 예에서 객체를 선언할 때 쓰이는 문장 맨 뒤를 보자.
클래스명 객체명 = new 클래스명();
뒤에 클래스명() 에 붙는 가로의 역할이 바로 생성자 메서드 호출이다. 위에서 new가 하는 역할은 공간 할당 후 주소 리턴이라고 했다. 그러나 한가지 역할을 더 수행하게 되는데, 이것이 바로 생성자의 호출이다. 생성자 메서드를 정의해보자.
public class Con{
public int garo;
public int sero;
public Con(int garo, int sero){
this.garo=garo;
this.sero=sero;
}
public void width(){
int wid=garo*sero;
System.out.println("넓이 : "+wid);
}
}
생성자는 위의 예제중 Con()이라는 메서드이다. 생성자 메서드의 특징은 리턴형이 없으므로, 리턴을 할 수가 없다는 것과 메서드명이 클래스명과 같다. 생성자 메서드는 객체의 초기화를 위에 대부분 사용되며, 위에서 사용된 this 키워드는 Con 메서드를 호출한 객체를 의미한다.
public class ConEx{
public static void main(String args[]){
Con ob = new Con(10, 20);
ob.width();
}
}
4. 클래스 상속
우리가 흔히 상속이라고 하면 부모의 속성을 그대로 물려받는 것을 의미한다. 객체지향 프로그래밍에서도 역시 상속은 같은 의미로 사용된다. 부모 클래스로부터 파생시킨(상속시킨) 자식 클래스는 부모 클래스의 특성을 그대로 물려받게 된다. 이 상속은 매우 중요한 개념중의 하나로, 잘 알아두기 바란다. 상속은 이미 누군가 만들어놓은 클래스를 부모 클래스로해서 물려받아 사용하기 때문에, 부모 클래스를 재사용한다는 장점과 함께 자식 클래스에서 필요한 부분은 추가로 정의하게 되기 때문에 부모 클래스에서의 확장성이 있다.
public class Base{
public int garo;
public int sero;
public Base(int garo, int sero){
this.garo=garo;
this.sero=sero;
}
public void width(){
int wid=garo*sero;
System.out.println("넓이 : "+wid);
}
}
위와 같이 부모 클래스를 만들었다. 그럼 부모 클래스로부터 상속받는 자식 클래스를 정의해보자.
오버로딩이란 메서드의 중복을 말하는데, 같은 이름으로 여러 역할을 하도록 만드는 기술이다. 예를 들어
public class Poly {
public void width(int r){
float wid=3.14f*r*r;
System.out.println("원의 넓이 : " + wid);
}
public void width(int garo, int sero){
int wid=garo*sero;
System.out.println("사각형의 넓이 : " + wid);
}
public static void main(String args[]){
Poly poly = new Poly();
poly.width(10);
poly.width(20, 10);
}
위에서 width()를 보자. 그러면 width(r)과 width(garo, sero)가 보이는데 이 두 메서드가 오버로딩되었다고 말할 수 있는 것이다.
만약 width(10)으로 호출하게 되면 이 10이라는 것을 받을 수 있는 int 형 중 인수가 하나이기 때문에 width 메서드 중 위에 것이 호출되게 된다. 이것은 절차지향 프로그래밍과는 달리 객체지향 프로그래밍에서는 메서드의 이름으로 메서드가 같은 지를 판단하는 것이 아닌 메서드 인수의 타입이나 개수로 판단하기 때문에 가능한 것이다.
2. 오버라이딩
오버라이딩은 간단히 정의해서 부모 클래스에서 정의되어 있는 메서드를 같은 이름으로 다른 역할을 재정의하고 싶을때 사용하게 된다.
객체지향 프로그래밍은 유지보수에 초점이 맞추어져 있다. 그러다보니 비슷한 역할이라는 메서드의 이름을 통일하는 것을 더욱 좋게본다. 예를 들어 넓이를 구하는 의미인데, 어떤 메서드에서는 'width'라는 이름을 사용하고 어떤 메서드에서는 '넓이'라는 이름을 사용하면 나중에 같은 의미지만 헤메이게 될 소지가 있기 때문이다. 그러면 아래의 예를 보자.
public class Poly {
private int garo;
private int sero;
public Poly(int garo, int sero) {
this.garo=garo;
this.sero=sero;
}
public void width(){
int wid=garo*sero;
System.out.println("사각형의 넓이 : " + wid);
}
}
public class Tri extends Poly {
public Tri(int garo, int sero) {
this.garo = garo;
this.sero = sero;
}
public void width() {
float wid=garo*sero*0.5f;
System.out.println("삼각형의 넓이 : " + wid);
}
public static void main(String[] args) {
Tri tri = new Tri(20, 10);
tri.width();
}
}
위의 예에서 보듯, 부모에 있는 width는 사각형을 구하기 위한 메서드였다. 그러나 Poly로부터 상속받은 Tri에서는 삼각형을 구하고자 했다. 그렇기 때문에 width라는 이름으로 넓이를 통일시키고자 Poly의 width를 Tri에서 재정의를 통해 구현한 것이다.
3. 추상 클래스
추상 클래스는 선언만 되어 있고, 구현은 되어 있지 않은 메서드를 추상 메서드라고 하는데 추상 메서드를 하나라도 포함하게 되는 클래스를 추상클래스라고 한다. 추상메서드와 클래스는 항상 abstract라는 키워드를 붙여 줌으로서 선언하게 된다. 그럼 추상 클래스를 왜 사용해야할까? 아래의 예를 보면서 얘기해보자.
public abstract class Poly {
int garo;
int sero;
public abstract void width();
}
우리는 Poly 클래스에 이미 가로와 세로가 선언되어져 있고, 사각형의 넓이를 구할 수 있는 메서드가 정의되어 있다는 것을 알고 Poly를 이용해서 사각형 뿐만이 아닌 삼각형의 넓이도 구하려고 한다. 그런데 Poly에서는 삼각형과 사각형의 공식이 서로 틀려 어떻게 구현되어야 할지 감을 잡을 수가 없다. 그렇기 때문에 상속받는 자식에서 구현하라는 의미로 우리는 여기서 선언만 되어 있는 width를 선언하게 된것이다. 그리고 abstract라는 키워드가 메서드와 클래스의 앞에 각각 붙어져 있는 것을 확인할 수 있다.
가장 중요한 것은 추상 클래스로부터 상속받는 클래스에서는 추상 메서드는 반드시 오버라이딩 시켜서 사용해야 한다는 것이다. 그럼 상속받은 삼각형과 사각형 클래스를 각각 정의해보자.
public class Tri extends Poly {
public Tri(int garo, int sero) {
this.garo=garo;
this.sero=sero;
}
public void width(){
float wid=garo*sero*0.5f;
System.out.println("삼각형의 넓이 : " + wid);
}
public static void main(String[] args) {
Tri tri = new Tri(20, 10);
tri.width();
}
}
public class Rect extends Poly {
public Rect(int garo, int sero) {
this.garo=garo;
this.sero=sero;
}
public void width(){
int wid=garo*sero;
System.out.println("사각형의 넓이 : " + wid);
}
public static void main(String[] args) {
Rect rect = new Rect(20, 10);
rect.width();
}
}
4. 인터페이스
자바에서는 원칙적으로 다중 상속을 금하고 있다. extends 키워드로는 두 개 이상의 클래스로부터 상속받을 수 없다는 의미이다. 그러나 다중 상속을 꼭 해야하는 경우가 있다. 이럴때 사용할 수 있는 것이 인터페이스이다. 인터페이스는 추상메서드와 변수가 아닌 상수만을 소유할 수 있다. 인터페이스에서 변수를 선언하게 되면 상수화되기 때문에 상수 선언시 값초기화를 해주어야 한다. 또한 인터페이스로부터 상속받을 때는 extends 키워드가 아닌 implements 키워드를 사용한다. 역시 인터페이스에 있는 추상메서드도 반드시 오버라이딩 시켜야 하며, 추상 메서드라고 해서 abstract를 붙이지는 않는다.
Abstract클래스는 빈 깡통 클래스입니다. abstract클래스는 구현이 덜 되었거나 또는 아직은 미완성 클래스이기 때문에 우리는 이 클래스를 추상 클래스라 부릅니다. 어디가 미완성일까요? 미완성 메서드를 포함하고 있기 때문에 클래스자체가 미완성이 되는 것입니다. 미완성 메서드는 어디가 미완성일까요? 메서드의 몸체가 없습니다. 몸체 없는 메서드, 이를 우리는 추상 메서드라 부릅니다. 그리고 이 추상 메서드를 단 하나라도 포함하고 있는 클래스를 추상 클래스라고 합니다.
5.1.2 추상클래스와 추상메서드
추상클래스는 미완성 클래스이기 때문에 약점이 있습니다. 완전한 클래스가 아니기 때문에 절대 객체를 생성하지 못합니다. 당연한 것 아니겠습니까? 완성되지 않은 클래스이니 객체를 만들 수 없다는 것은 이치에 합당합니다. 이 미완성 클래스를 어디에 사용할까요? 사용방법은 다음과 같습니다.(이해하기 편하게 추상클래스를 추상아버지클래스, 이를 상속 받는 클래스를 아들클래스라고 하겠습니다. )
n추상아버지 클래스는 추상 메서드를 가지고 있습니다.
n아들 클래스는 추상 아버지클래스로부터 상속 받습니다.
n아들은 추상 아버지 클래스의 몸체 없는 메서드를 모두 메서드 재정의와 같이 다시 만듭니다. 이것을 우리는 구현한다고 말합니다. 몸체를 달아 주는 것입니다.
n아들 클래스는 완성된 클래스이기 때문에 객체를 생성할 수 있습니다.
다음은 추상 메서드를 만드는 방법을 보여 주고 있는 예입니다.
추상 메서드를 만드는 방법
abstract class 클래스이름 {
abstract 메소드선언;// 메소드의 몸체는 없음
abstract 메소드선언;// 메소드의 몸체는 없음
}
예)
public abstract class AbstractTest {
public abstract void abstractMethod();
}
메서드에 몸체가 붙지 않는다면 여러분은 반드시 abstract키워드를 붙여야 합니다. 그리고 이 abstract메서드를 하나라도 포함하고 있다면 클래스명 앞에 abstract를 붙여야 합니다. 안 붙이면 언제나 그런 것처럼 친절하게 에러를 발생 시킵니다. 아래의 에러는 추상클래스내에 추상 메서드에 abstract를 붙이지 않았을 때 발생하는 에러입니다.
C:\examples\5. Class for Polymorphism Java>javac AbstractTest.java
AbstractTest.java:2: missing method body, or declare abstract
public void abstractMethod();
^
1 error
아주 간단하죠. 추상 클래스는 그 자체로는 객체를 생성할 수 없다는 것을 한번 더 밝혀 두며 실제 이용하는 가장 간단한 예를 보도록 하겠습니다.
NewCan.java(추상 메서드를 만드는 방법)
public class NewCan extends EmptyCan{
public void sound(){
System.out.println("EmptyCan:빈깡통은 소리가 요란하다");
}
public void who(){
System.out.println("EmptyCan:나는 빈깡통입니다.");
}
public void sayHello(){
System.out.println("NewCan:추상클래스 테스트입니다.");
}
public static void main(String args[]) {
NewCan ecm = new NewCan();
ecm.who();
ecm.sound();
ecm.sayHello();
}
}
Empty.java(추상클래스)
public abstract class EmptyCan{
public abstract void sound();//몸체없음
public abstract void who();//몸체 없음
}
C:\examples\5. Class for Polymorphism Java>javac NewCan.java
C:\examples\5. Class for Polymorphism Java>java NewCan
EmptyCan:나는 빈깡통입니다.
EmptyCan:빈깡통은 소리가 요란하다
NewCan:추상클래스 테스트입니다.
추상클래스는 객체가 가지는 특성들을 추상화시켜 놓았을 뿐, 아직 구체화 시키지 못한 클래스이므로, 이 추상클래스를 상속하는 하위클래스에서 좀 더 구체화 시키도록 하는 방법을 사용합니다. 주의 할 것은 추상 메서드를 하나도 빼지 말고 전부 구현 해야 객체를 생성할 수 있습니다. 아들이 상속을 받아서 추상 메서드를 하나라도 남겨 두었다면 아들도 추상클래스가 됩니다. 부전자전이죠. 다음은 상속을 했지만 추상 메서드를 전부 구현 하지 않았다면 abstract클래스가 되는 것을 증명하는 예제입니다.
CompleteCan.java(추상 메서드를 만드는 방법)
public class CompleteCan extends IncompleteCan{
public void who(){
System.out.println("EmptyCan:나는 빈깡통입니다.");
}
public void sayHello(){
System.out.println("NewCan:추상클래스 테스트입니다.");
}
public static void main(String args[]) {
CompleteCan cc = new CompleteCan();
cc.who();
cc.sound();
cc.sayHello();
}
}
Incomplete.java(추상클래스)
public abstract class IncompleteCan extends EmptyCan{
public void sound(){
System.out.println("EmptyCan:빈깡통은 소리가 요란하다");
}
}
Empty.java(추상클래스)
public abstract class EmptyCan{
public abstract void sound();//몸체없음
public abstract void who();//몸체 없음
}
C:\examples\5. Class for Polymorphism Java>javac EmptyCan.java
C:\examples\5. Class for Polymorphism Java>javac IncompleteCan.java
C:\examples\5. Class for Polymorphism Java>javac CompleteCan.java
C:\examples\5. Class for Polymorphism Java>java CompleteCan
EmptyCan:나는 빈깡통입니다.
EmptyCan:빈깡통은 소리가 요란하다
NewCan:추상클래스 테스트입니다.
추상메소드는 추상클래스와 마찬가지로 아직 구현이 이루어지지 않고 단지 그 메서드의 이름만 가지고 있다는 뜻입니다. 그래서 몸체가 없다고 말하고 있는 것입니다. 이것을 역으로 말한다면 추상메서드가 되려면 몸체가 없어야 한다라는 명제를 얻을 수 있습니다. 보통 우리가 전문적인 용어로 이야기 할 때 추상메서드는 메서드의 프로토타입만 가지고 있다라고 이야기 합니다.
5.1.3 클래스의 구조를 디자인하기 위한 추상 클래스
추상클래스는 클래스의 구조를 잡기 위한 방법으로 사용됩니다. 하나의 클래스를 만든다고 가정한다면 하나의 클래스에 모든 것을 전부 넣을 수는 없습니다. 이러한 방법은 별로 좋지 않은 방법이죠. 클래스가 크다고 가정한다면 작업별로 클래스를 쪼개게 됩니다. 작업을 분할 하는 것은 수평적인 개념에서 비슷한 작업끼리 묶는 것입니다. 유유상종(類類相從)이라는 말이 적당할 것 같군요. 비슷한 작업끼리 묶어서 작업별 클래스를 만드는 것입니다.
저 또한 초보시절의 자바 프로그램은 비슷한 작업을 묶는 것으로 시작했습니다. 하지만 수평적으로 묶는 것 조차도 작업의 설계와 분석을 해야만 이루어지는 아주 어려운 일입니다. 이 단계를 거치고 나면 작업을 레벨단위로 묶는 것을 생각하게 됩니다. 거의 1년 이상 걸리더군요. 제가 머리가 나빠서 그런지도 모르겠지만 개인적으로는 상당히 어려운 일이었습니다. 레벨 단위의 작업으로 발전하기까지는 상당히 많은 시간과 생각이 필요한 것 같습니다. 작업을 단계별로 만들어 주는 역할을 하는 것이 바로 추상 클래스의 역할입니다.
레벨 단위의 구조를 살펴 보도록 하죠.
위의 그림은 도형을 그리기 위한 클래스를 디자인 하고 있습니다. 모든 클래스에서 공통되는 작업을 상위레벨에 놓고 그 클래스를 상속 받아 각각의 다른 클래스로 분류되어지고 있습니다. 상위레벨에 존재하는 작업은 구현할 필요성이 없다고 생각하여 abstract으로 만듭니다. 즉, 하위레벨에서 구현하겠다는 것입니다. 이렇게 하였을 때의 코드를 직접 만들어 보겠습니다. 물론 Shape클래스는 abstract으로 만들어질 것이며 그 하위의 Circle, Triangle, Rectangle은 모두 Shape의 abstract메서드를 구현 할 것입니다. 구현은 대충 아래와 같이 되어 질 것입니다.
FigureTest.java(abstract의 레벨기법을 테스트하기 위한 예제)
abstract class Shape {
public abstract void draw();
public abstract void delete();
}
class Circle extends Shape {
public void draw(){
System.out.println("원을 그립니다");
}
public void delete(){
System.out.println("원모양을 지웁니다");
}
}
class Triangle extends Shape {
public void draw(){
System.out.println("삼각형을 하나, 둘, 셋, 그립니다.");
}
public void delete(){
System.out.println("삼각형을 지웁니다");
}
}
class Rectangle extends Shape {
public void draw(){
System.out.println("사각형을 원, 투, 쓰리, 포 그립니다.");
}
public void delete(){
System.out.println("사각형을 지웁니다");
}
}
//abstract을 테스트하기 위한 클래스
public class FigureTest{
public static void main(String[] args){
Circle c = new Circle();
Triangle t = new Triangle();
Rectangle r = new Rectangle();
c.draw();
t.draw();
r.draw();
c.delete();
t.delete();
r.delete();
}
}
C:\examples\5. Class for Polymorphism Java>javac FigureTest.java
C:\examples\5. Class for Polymorphism Java>java FigureTest
원을 그립니다
삼각형을 하나, 둘, 셋, 그립니다.
사각형을 원, 투, 쓰리, 포 그립니다.
원모양을 지웁니다
삼각형을 지웁니다
사각형을 지웁니다
상속과 abstract을 설명하기 위해서 어디서나 흔히 볼 수 있는 예제입니다. 이 간단한 예제가 무엇을 의미하는지 제대로 안다면 자바의 강력한 힘을 발휘할 수 있습니다. 클래스의 수평적인 작업 분할과 수직적인 작업 분할을 동시에 이용한다면 여러분은 아주 효율적인 코드를 작성할 수 있습니다. 재사용성 100%와 유지, 보수, 관리 능력까지 갖춘 잘 설계된 프로그램을 하실 수 있을 것입니다.
5.1.4 결론
추상클래스에 대하여 정리 해 보죠.
n추상 메서드는 몸체 없는 프로토타입만을 가진 메서드입니다.
n추상 메서드는 반드시 메서드이름 앞에 abstract 키워드를 명시해야 합니다.
n추상 메서드를 단 하나라도 포함하고 있으면 이를 추상 클래스라고 합니다.
n추상 클래스는 클래스이름 앞에 abstract를 명시해야 합니다.
n상속을 이용하여 추상 메서드를 모두 구현한 뒤, 객체를 생성할 수 있습니다.
일반적인 특징은 이러하지만 추상클래스의 뒷면에 존재하는 느낌은 수직적인 작업의 분할이라는 아주 무서운 개념이 숨어 있습니다. 프로그램을 하면서 어떻게 계층적으로 프로그램을 할지를 결정하지말고 펜을 들고 작업을 분석하는 것이 옳을 것입니다. 그리고 작업 분석이 끝났다면 수직과 수평의 개념을 적용 시켜서 어느 정도 설계를 하는 것이 옳을 것입니다. 하지만, 기본적인 배경 없이는 아무것도 할 수 없으니 지금은 느낌을 얻으시기 바랍니다.
여러분이 클래스를 만들고 시간이 지나서 다시 여러분이 만든 클래스를 볼 기회 있을 것입니다. 절대 같은 디자인의 클래스는 만들 수 없습니다. 물론, 클래스를 만드는 기법은 비슷하지만 같은 디자인으로는 프로그램을 하려고 해도 잘 되지 않습니다. 약간의 시간이 지난 후, 과거와 현재를 비교해 보면 여러분 스스로 진화했다는 말을 사용해도 될 만큼 아주 많은 발전을 이룬 것을 볼 날이 있을 것입니다.
final 키워드는 대개 정적인 방식으로 많이 쓰입니다. 예를 들어 클래스 상수로 쓰이거나, 클래스의 상속이나 메소드 재정의(overriding)을 막기 위해서 자주 사용되죠.
상대적으로 잘 쓰이지 않지만, 초기화 이후에 값을 바꿀 수 없는 변수를 만드는 동적인 용도로도 사용이 가능합니다. 자바 언어 레퍼런스를 보면 다음과 같이 내용이 있습니다:
A variable can be declared final. A final variable may only be assigned to once. It is a compile time error if a final variable is assigned to unless it is definitely unassigned immediately prior to the assignment.
C나 C++에 const라는 키워드가 있는데 변수에 대해서는 final보다 const가 더 적절한 이름이라고 생각합니다. 하지만, 클래스나 메소드 정의에 부여하는 경우는 final이 아주 좋다고 생각합니다.
배열을 인자로 받아서 합계를 내는 함수가 있다고 해보죠.
int sum(int[] a) { int result = 0; for (int i : a) result += i; return result; }
이 경우 어떤 이유에서건 아래와 같이 구현된다면 해당 메소드를 사용자 입장에서는 의도하지 않은 결과를 접하게 될 가능성이 높습니다.
int sum(int[] a) { int[] others = {-1, -2, -3}; a = others;
int result = 0; for (int i : a) result += i;
return result; }
간단한 예를 통해서 보면 완전히 어리섞은 구현이긴 합니다. 하지만, 테스트가 없이 장문의 타이핑을 하는 환경에서는 이와 유사한 일이 일어나지 말라는 법은 없습니다.
int sum(final int[] a) { int result = 0; for (int i : a) result += i; return result; }
이와 같이 정의해주면 적어도 a에 새로운 할당을 시도할 수 없게 됩니다.
3 + 4 = ?
위와 같이 더하기라는 연산을 할 때 피 연산자인 3과 4는 변하지 않습니다. 3과 4가 변한다면 사람들은 위험해서(?) 덧셈을 하지 않겠죠. :)
sum()에 인자로 제공해주는 것은 피연산자와 같습니다. 의미적으로 상수가 전달되어야 하는 경우라고 할 수 있죠. 이럴 때 final을 붙여주면 보다 의미가 명확해질 수 있습니다.
아래와 같은 구문에 왜 final을 쓰면 안되느냐는 질문을 받은 일이 있습니다.
int sum(int[] a) { final int result = 0; for (int i : a) result += i; return result; }
자칫 범하기 쉬운 오류지만, result라는 변수의 용처를 명확하게 정립해보면 논리적인 오류임을 알 수 있습니다. result는 말 그대로 임시 저장소입니다. 지역 변수(local variable)은 임시(temp/temporary) 변수라고 하는데 딱 어울리는 용도로 사용한 것이죠.
세로 타원으로 그린 것이 for 문의 실행시의 논리적인 영역을 나타냅니다. 문맥(context)이라고 할 수도 있겠죠. 반복이 진행되면서 우측으로 이동하기 때문에 배열의 요소들의 이전 값을 기억하기 위해서는 별도의 저장소가 요구됩니다. 어떤 프로그래밍 언어를 배우거나 초기에 익히는 것이지만, 논리적으로 이러한 그림을 머리속에 그릴 수 있다면 더욱 복잡한 경우를 포용할 때 매우 유리합니다.
위와 같은 경우 a[0]의 값이 temp에 들어가 있어야 하고, 반복이 한 차례 더 수행되면 temp 값에 a[1]을 더한 값이 다시 할당되어져야 하니 final로 정의하는 것은 실수죠.
클래스는 크게 일반클래스와 추상클래스로 나뉘는데 추상클래스는 본문중에 '추상메소드'가 하나 이상 포함되는 경우를 말합니다. 인터페이스는 모든 메소드가 추상메소드인 경우 선언하는 경우가 많습니다.
추상메소드라 함은 메소드의 선언부만 있고 본문이 없는 것을 말합니다. 여기서 본문은 중괄호({})로 묶여진 몸체(body)부분을 말하는데 중괄호 안이 비어있더라도 이것이 존재한다면 그것은 추상메소드가 아닙니다. 추상메소드의 선언의 예는 다음과 같습니다.
abstract public void test( int a );
즉, 메소드의 선언 후에 세미콜른(;)만을 찍어 이를 선언만 하고 구현은 자식클래스에서 하게 하는 방법입니다. 일반 클래스의 경우 상속받은 자식클래스가 반드시 부모클래스의 메소드를 '오버라이딩(overriding)'할 필요가 없지만 추상클래스를 상속받은 자식클래스는 반드시 추상메소드를 오버라이딩하여야 하기에 메소드를 강제로 구현하게 할 때 많이 쓰입니다.
추상클래스와 인터페이스의 가장 큰 차이점은 바로 '클래스'냐 아니냐의 차이입니다. 추상클래스는 엄연한 클래스로 이를 구현하는 것은 '상속(extends)'입니다. 그러나 인터페이스는 '포함(implements)'라는 키워드를 통해 구현하게 되는데 이는 자바에서 매우 중요한 차이를 가집니다.
자바는 오로지 '단일 상속'만을 지원하기 때문에 추상클래스를 상속받는 클래스는 다른 클래스를 상속받을 수 없습니다. 그러나 인터페이스를 포함하는 클래스는 다른 클래스를 상속받을 수 있습니다.
추상클래스는 일반클래스와 달리 그 자신을 new 명령어를 통해 객체를 생성할 수 없습니다. 그러나 '다형성(polymorlphism)'을 통해 자식 클래스의 객체를 생성할 수는 있습니다.
인터페이스를 쓰는 가장 큰 이유는 다중상속을 지원하지 않는 자바에서 다중상속의 장점을 가져오기 위해서입니다. 다중상속이 가지는 단점은 배제하고 오직 장점만을 취하기 위해 인터페이스를 쓰는 것입니다.
상속이란 개념은 부모클래스의 속성과 메소드를 자식클래스가 물려받는다는 것인데 쉽게 예를 들면 '자동차'라는 클래스는 '색깔', '배기량' 등의 속성과 '기름을 넣는다', '달린다' 등의 메소드를 가질 수 있습니다. 이 클래스를 상속하는 '승용차' 클래스는 별도로 선언하지 않아도 부모클래스의 '색깔', '배기량'이라는 속성과 '기름을 넣는다', '달린다'라는 메소드를 가지게 되죠.
추상클래스는 이 부모클래스가 단순히 관념적인 성격이 강할 때 많이 쓰이는데 예컨데 '새'라는 클래스는 그냥 '난다'라는 메소드를 가지지만 새의 종류에 따라 나는 방법이 조금씩 틀려 자식클래스에서 어차피 구현해야 될 부분이므로 굳이 부모클래스에서 그 내용을 구현할 필요가 없기 때문에 사용합니다.
요사이 추세는 굳이 클래스간의 조직을 표현할 필요가 없을 경우는 추상클래스를 쓰는 대신 인터페이스를 쓰는 경우가 많습니다. 인터페이스는 위에서 말씀드린바와 같이 모든 메소드가 추상메소드라서 이를 포함하는 클래스는 이들을 모두 구현해야 합니다.
추상메소드는 멤버변수(어트리뷰트)를 가지지만 인터페이스는 오직 '상수'만을 가질 수 있습니다.
면접관 : static키워드에 대해서 설명해보세요. 응시자 : static키워드를 쓰면, 객체를 생성하지 않고도 변수나 함수를 사용할 수 있습니다.
면접관 : 왜 static키워드를 쓰나요? 응시자 : 객체를 생성하지 않아도 되니까 편리하고 속도도 빠릅니다.
면접관 : 그렇다면 모든 변수와 함수에 static을 붙이는 것이 좋겠네요? 응시자 : 가능한한 static을 붙이는 것이 좋다고 생각합니다.
면접관 : 어떤 경우에 static을 붙일 수 있고, 어떤 경우에 static을 붙일 수 없습니까? 응시자 : ...
면접관 : 만일 당신이 새로운 클래스를 작성한다고 할 때, 어떤 경우에 static키워드를 사용해야한다고 생각합니까? 응시자 : ...
대부분의 경우 위와 같은 내용으로 문답이 진행됩니다.
사실 응시자의 대답은 다 맞는 얘기입니다. 하지만, static의 핵심적인 개념을 모르기 때문에 어떤 경우에 왜 static을 사용해야하는지는 잘모르는 것 같습니다.
먼저 결론부터 간단히 정리하면 다음과 같습니다.
1.클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통적으로 사용해야하는 것에 static을 붙인다. - 인스턴스를 생성하면, 각 인스턴스들은 서로 독립적기 때문에 서로 다른 값을 유지한다. 경우에 따라서는 각 인스턴스들이 공통적으로 같은 값이 유지되어야 하는 경우 static을 붙인다.
2. static이 붙은 멤버변수는 인스턴스를 생성하지 않아도 사용할 수 있다. - static이 붙은 멤버변수(클래스변수)는 클래스가 메모리에 올라갈때 이미 자동적으로 생성되기 때문이다.
3. static이 붙은 메서드(함수)에서는 인스턴스 변수를 사용할 수 없다. - static이 메서드는 인스턴스 생성 없이 호출가능한 반면, 인스턴스 변수는 인스턴스를 생성해야만 존재하기 때문에... static이 붙은 메서드(클래스메서드)를 호출할 때 인스턴스가 생성되어있을수도 그렇지 않을 수도 있어서 static이 붙은 메서드에서 인스턴스변수의 사용을 허용하지 않는다. (반대로, 인스턴스변수나 인스턴스메서드에서는 static이 붙은 멤버들을 사용하는 것이 언제나 가능하다. 인스턴스변수가 존재한다는 것은 static이 붙은 변수가 이미 메모리에 존재한다는 것을 의미하기 때문이다.)
4. 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다. - 메서드의 작업내용중에서 인스턴스 변수를 필요로 한다면, static을 붙일 수 없다. 반대로 인스턴스변수를 필요로 하지 않는다면, 가능하면 static을 붙이는 것이 좋다. 메서드 호출시간이 짧아지기 때문에 효율이 높아진다. (static을 안붙인 메서드는 실행시 호출되어야할 메서드를 찾는 과정이 추가적으로 필요하기 때문에 시간이 더 걸린다.)
5. 클래스 설계시 static의 사용지침 - 먼저 클래스의 멤버변수중 모든 인스턴스에 공통된 값을 유지해야하는 것이 있는지 살펴보고 있으면, static을 붙여준다. - 작성한 메서드 중에서 인스턴스 변수를 사용하지 않는 메서드에 대해서 static을 붙일 것을 고려한다.
일반적으로 인스턴스변수와 관련된 작업을 하는 메서드는 인스턴스메서드(static이 안붙은 메서드)이고 static변수(클래스변수)와 관련된 작업을 하는 메서드는 클래스메서드(static이 붙은 메서드)라고 보면 된다.
클래스변수와 인스턴스변수의 차이를 이해하기 위한 예로 카드 게임에 사용되는 카드를 클래스로 정의해보자.
카드 클래스를 작성하기 위해서는 먼저 카드를 분석해서 속성과 기능을 알아 내야한다. 속성으로는 카드의 무늬, 숫자, 폭, 높이 정도를 생각할 수 있을 것이다. 이 중에서 어떤 속성을 클래스 변수로 선언할 것이며, 또 어떤 속성들을 인스턴스 변수로 선언할 것인지 생각해보자.
class Card { String kind ; // 카드의 무늬 - 인스턴스 변수 int number; // 카드의 숫자 - 인스턴스 변수 staticint width = 100 ; // 카드의 폭 - 클래스 변수 staticint height = 250 ; // 카드의 높이 - 클래스 변수 }
각 Card인스턴스는 자신만의 무늬(kind)와 숫자(number)를 유지하고 있어야 하므로 이들을 인스턴스변수로 선언하였고, 각 카드들의 폭(width)과 높이(height)는 모든 인스턴스가 공통적으로 같은 값을 유지해야하므로 클래스변수로 선언하였다.
만일 카드의 폭을 변경해야할 필요가 있을 때는 모든 카드의 width값을 변경하지 않고, 한 카드의 width값만 변경해도 모든 카드의 width값이 변경되는 셈이다.
[예제6-4] CardTest.java
class CardTest{ publicstaticvoid main(String args[]) { // 클래스변수(static 변수)는 객체생성없이 '클래스이름.클래스변수'로 직접 사용 가능하다. System.out.println("Card.width = " + Card.width); System.out.println("Card.height = " + Card.height);
class Card { String kind ; // 카드의 무늬 - 인스턴스 변수 int number; // 카드의 숫자 - 인스턴스 변수 staticint width = 100; // 카드의 폭 - 클래스 변수 staticint height = 250; // 카드의 높이 - 클래스 변수 }
[실행결과]
Card.width = 100 Card.height = 250 c1은 Heart, 7이며, 크기는 (100, 250) c2는 Spade, 4이며, 크기는 (100, 250) 이제 c1의 width와 height를 각각 50, 80으로 변경합니다. c1은 Heart, 7이며, 크기는 (50, 80) c2는 Spade, 4이며, 크기는 (50, 80)
Card클래스의 클래스변수(static변수)인 width, height 그리고 color는 Card클래스의 인스턴스를 생성하지 않고도 '클래스이름.클래스변수'와 같은 방식으로 사용할 수 있다. Card인스턴스인 c1과 c2는 클래스 변수인 width와 height를 공유하기 때문에, c1의 width와 height를 변경하면 c2의 width와 height값도 바뀐 것과 같은 결과를 얻는다. Card.width, c1.width, c2.width는 모두 같은 저장공간을 참조하므로 항상 같은 값을 갖게 된다. 인스턴스 변수는 인스턴스가 생성될 때 마다 생성되므로 인스턴스마다 각기 다른 값을 유지할 수 있지만, 클래스 변수는 모든 인스턴스가 하나의 저장공간을 공유하므로, 항상 공통된 값을 갖는다. [플래시동영상]자료실의 플래시동영상 MemberVar.swf을 꼭 보도록하자.
3.9 클래스메서드(static메서드)와 인스턴스메서드
변수에서 그랬던 것과 같이, 메서드 앞에 static이 붙어 있으면 클래스메서드이고 붙어 있지 않으면 인스턴스메서드이다. 클래스 메서드는 호출방법 역시 클래스변수처럼, 객체를 생성하지 않고도 '클래스이름.메서드이름(매개변수)'와 같은 식으로 호출이 가능하다. 그렇다면 어느 경우에 static을 사용해서 클래스메서드로 정의해야하는 것일까?
클래스는 '데이터(변수)와 데이터에 관련된 메서드의 집합'이라고 할 수 있다. 같은 클래스 내에 있는 메서드와 멤버변수는 아주 밀접한 관계가 있다. 인스턴스메서드는 인스턴스변수와 관련된 작업을 하는, 즉 메서드의 작업을 수행하는데 인스턴스변수를 필요로 하는 메서드이다. 그래서 인스턴스변수와 관계없거나(메서드 내에서 인스턴스변수를 사용하지 않거나), 클래스변수만을 사용하는 메서드들은 클래스메서드로 정의한다. 물론 인스턴스변수를 사용하지 않는다고 해서 반드시 클래스 메서드로 정의해야하는 것은 아니지만, 그렇게 하는 것이 일반적이다. 참고로 Math클래스의 모든 메서드는 클래스메서드임을 알 수 있다. Math클래스에는 인스턴스변수가 하나도 없거니와 Math클래스의 함수들은 작업을 수행하는데 필요한 값들을 모두 매개변수로 받아서 처리 하기 때문이다. 이처럼, 단순히 함수들만의 집합인 경우에는 클래스메서드로 선언한다.
[참고]인스턴스 변수 뿐만 아니라 인스턴스 메서드를 호출하는 경우에도 인스턴스 메서드로 선언되어야 한다. 인스턴스 메서드를 호출하는 것 역시 인스턴스 변수를 간접적으로 사용하는 것이기 때문이다.
[예제6-12] MyMathTest2.java
class MyMath2 { long a, b;
// 인스턴스변수 a, b를 이용한 작업을 하므로 매개변수가 필요없다. long add() { return a + b; } long subtract() { return a - b; } long multiply() { return a * b; } double divide() { return a / b; }
// 인스턴스변수와 관계없이 매개변수만으로 작업이 가능하다. staticlong add(long a, long b) { return a + b; } staticlong subtract(long a, long b) { return a - b; } staticlong multiply(long a, long b) { return a * b; } staticdouble divide(double a, double b) { return a / b; } }
인스턴스메서드인 add(), subtract(), multiply(), divide()는 인스턴스변수인 a와 b만으로도 충분히 원하는 작업이 가능하기 때문에, 매개변수를 필요로 하지 않으므로 괄호()에 매개변수를 선언하지 않았다. 반면에 add(long a, long b), subtract(long a, long b) 등은 인스턴스변수 없이 매개변수만으로 작업을 수행하기 때문에 static을 붙여서 클래스메서드로 선언하였다. MyMathTest2의 main메서드에서 보면, 클래스메서드는 객체생성없이 바로 호출이 가능했고, 인스턴스메서드는 MyMath2클래스의 인스턴스를 생성한 후에야 호출이 가능했다. 이 예제를 통해서 어떤 경우 인스턴스메서드로, 또는 클래스메서드로 선언해야하는지, 그리고 그 차이를 이해하는 것은 매우 중요하다.
3.10 클래스멤버와 인스턴스멤버간의 참조와 호출.
같은 클래스에 속한 멤버들간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다. 단, 클래스멤버가 인스턴스멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야 한다. 그 이유는 인스턴스멤버가 존재하는 시점에 클래스멤버는 항상 존재하지만, 클래스멤버가 존재하는 시점에 인스턴스멤버가 항상 존재한다는 것을 보장할 수 없기 때문이다.
[예제6-] ArrayEx.java
class MemberCall { int iv = 10; staticint cv = 20;
int iv2 = cv; // static int cv2 = iv; 에러. 클래스변수는 인스턴스 변수를 사용할 수 없음. staticint cv2 = new MemberCall().iv; // 굳이 사용하려면 이처럼 객체를 생성해야함.
staticvoid classMethod1() { System.out.println(cv); // System.out.println(iv); 에러. 클래스메서드에서 인스턴스변수를 바로 사용할 수 없음. MemberCall c = new MemberCall(); System.out.println(c.iv); // 객체를 생성한 후에야 인스턴스변수의 참조가 가능함. }
/* public - 외부에서 별다른 제약없이 이 클래스에 접근 할 수 있습니다. - private, protected, friendly
class - 현재 만들어지는 모듈은 클래스입니다. - interface
Hello - 클래스 이름, 파일명은 Hello.java로 대소문자도 일치해야합니다. */ public class Hello {
/* public: 외부에서 이 메소드는 아무런 제약없이 호출할 수 있습니다. static: 객체를 생성하지 않고도 메모리할당이 이루어져 사용할 수 있습니다. void : 이 메소드는 호출된후 처리결과를 리턴하지 않습니다. int, float, String등 각 타입이 가능 main : JVM에 의해서 최초로 자동으로 호출되는 메소드입니다. 반드시 한개만 존재해야 합니다. String[]: 문자열을 저장할 수 있는 배열을 의미합니다. 1차원 배열 args : 배열명입니다. 도스상에서 입력한 인수를 가져옵니다. */ public static void main(String[] args) { //System.out.println("AAA"): 화면에 AAA문자열을 출력합니다. // 문자열은 반드시 Double Quotation Mark로 감싸주어야 합니다. //System.out.println(10): 화면에 숫자 10이 출력합니다. //";"은 명령의 종결을 표시합니다. System.out.println("안녕하세요.!!!"); System.out.println(10); } //중괄호, 중괄호는 열린 갯수만큼 닫혀야합니다. } //중괄호안에 들어가는 코드는 일반적으로 4자정도의 들여쓰기를 //합니다.
// 단일행 주석
/* 여러행 주석1 여러행 주석2 */
/** * Documentation 주석 */
▩ 식별자 - 클래스, 메소드, 변수 이름 - 이름 자체로 어느정도 내용이 식별이 되어야 합니다.
1. 클래스 이름 - 첫자는 대문자가 아니어도 에러는 발생이 안되나 일반적으로 첫자는 대문자를 이용합니다. 또한 마디마다 대문자를 이용합니다.
3. 변수의 작성 규칙 - int kuk = 10; 이라고 선언 했을 경우 kuk는 변수가 됩니다. 또한 식별자로 포함이 됩니다. - String name="왕눈이"; 이런 경우는 객체변수에 해당이 됩니다. - 알파벳, 대소문자, 숫자, _, $만 이용 - 첫 문자가 숫자이면 안됨 - 대소문자 완벽히 구별(파일명, 폴더 모두 해당) - 키워드(예약어)와 같으면 안됨 - 길이 제한 없음(변수, 메소드명은 5자 ~ 15자 안팎) - 이름만 보고 알아 볼수 있도록 의미 부여 - 스칼라 표기법: 무조건 단어의 첫자를 대문자를 사용합니다. - 헝가리안 표기법: 단어의 약자를 추출하여 이용합니다. CompanyName --> CN
ALU <----┬---- int i=1234; <-┐ └---- int j=5678; │ │ i+j --------------------------┘ 6912
*/
i = i + j; System.out.println("i = i + j => " + i); } }
▩ 형변환 - 작은 타입의 데이터형은 큰 타입의 데이터형으로 아무런 작업 없이 변환할 수 있습니다. - 큰 타입의 데이터형을 작은 타입의 데이터형으로 변환할시에는 캐스트 연산자를 써서 형변환을
해야합니다. - Casting시에는 데이터 짤림이 발생하는지 확인해야 합니다. 데이터 짤림이 발생하면 캐스팅은
피해야 합니다. - 실수를 정수로 바꾸는 경우에 주로 사용합니다.
>>>>> Cast_Test.java public class Cast_Test { public static void main(String args[]) { int i = 1234; int k; float f = 10.5f; double d = 100.55;
// k = f; k는 정수만 저장함으로 ERROR k = (int)f; //작은 타입 = (작은 타입)큰타입 System.out.println(k); //10
f = i; //실수형 = 정수형 f = 1234
System.out.println(f); //1234.0
f = (float)d; System.out.println(f); //100.55 } }
public class Cast_Test2 { public static void main(String[] args) { //MSB LSB //3 2 1 //18765432187654321 //----------------- byte b = 100; // 01100100 8비트 필요 short s = 300; // 0000000100101100 16비트로 필요 int i = 70000;//10001000101110000 24비트가 필요
// 32+8+4=44 b = (byte)s; //00000001/00101100 System.out.println("b=" + b);
Exception in thread "main" java.lang.NoClassDefFoundError: 파일명
발생되는 경우
클래스 파일을 찾을 수 없는 경우
조 언
실행하려는 클래스 파일 이름이 제대로 되어 있는지 확인한다. 또한, CLASSPATH 설정이 제대로 되어 있는지 확인하며 (도스모드에서 set명령어) 만약, 되어있지 않다면 설정한다. (CLASSPATH = jdk1.3/jre/lib/rt.jar; 2-1강좌 참조)
번 호
2
ERROR
cannot resolve symbol symbol : class in(에러가 난 부분) location : class StackTest(찾으려는 위치)
발생되는 경우
이해할 수 없는 클래스나 메소드, 변수명이 올경우
조 언
보통 이 에러는 철자가 틀렸을 경우에 많이 발생한다. 클래스, 메소드, 변수의 철자를 세심히 확인해 본다. 특히, 철자를 확인할때 대소문자 구분을 확실히 체크한다.(자바는 대소문자를 구별한다.) 그리고 클래스에서 발생할 경우 import를 해주었는지 확인해 봅니다.
번 호
3
ERROR
non-static variable 변수이름(or method 메소드이름) cannot br referenced from a static context
발생되는 경우
static 메소드 안에서 static 으로 선언되지 않은 메소드나 변수를 참조(사용)했을 경우. 특히, 메소드의 경우는 인스턴스를 사용하지 않고 static메소드 내에서 바로 선언한 경우.
조 언
static 선언자의 사용여부를 살펴보고 static 메소드 안에 static으로 선언되어지지 않은 메소드나 변수가 있느지 확인해본다. 만약 그런것이 있으면 메소드를 새로 만들어 그쪽에서 선언한다. 단, 인스턴스를 생성해서 불러줘야 한다는 것을 잊지 말아야한다.
번 호
4
ERROR
valiable 변수명 might not have been initialized
발생되는 경우
지역변수인 변수명의 변수가 초기화가 되어있지 않았을 경우
조 언
지역변수(메소드 내에서 선언한 변수)를 초기화 하지 않은채 선언했을 경우 발생한다. 멤버 필드가 아닌 경우는 반드시 변수 선언시 초기화를 해주어야 한다. (멤버 필드는 자바 프로그램 자체에서 자동으로 default값으로 초기화 해준다.)
번 호
5
ERROR
class 클래스명 is public, should be declared in a file named 클래스명.java
발생되는 경우
클래스명이 public으로 선언되었는데 파일명과 다를 경우
조 언
public으로 선언된 클래스가 있다면 반드시 그 클래스명과 파일명이 같아야 한다. 클래스명과 파일명의 대소문자 및 철자가 같은지 비교해 본다. 또한, public으로 선언된 클래스가 하나 이상 있는지 찾아본다.(반드시 하나만 있어야한다.)
번 호
6
ERROR
push(java.lang.object)[메소드(인자로 받을 수 있는 형)] in java.util.Stack(메소드의 클래스) cannot be applied to (int)[잘못 들어간 형]
발생되는 경우
메소드에서 인자를 받을 때 받을 수 있는 형이 아닌 자료형 또는 클래스형을 사용할 경우
조 언
사용하는 메소드의 API를 참고하여 어떤 형을 인자로 받을 수 있는지 찾아본다. API를 보지 못할 경우는 각 자료형으로 인자를 직접 바꾸어 본다.
번 호
7
ERROR
java.lang.NoSuchMethodError: main Exception in thread "main"
발생되는 경우
클래스 파일 안에서 main() 메소드를 찾을 수 없는 경우
조 언
자바 애플릿이 아닌 이상 자바 애플리케이션은 반드시 main() 메소드를 사용해야 합니다. main() 메소드를 빼먹지 않았는지 확인해 보십시요. 또한 public static void main(String[] args) 형식으로 씌어졌는지도 확인해 보십시요. (main 클래스는 반드시 위와 같은 형식으로 만들어져야 합니다.)
번 호
8
ERROR
unreported exception java.io.IOException(Exception명); must be caught or declared to be thrown
발생되는 경우
예외가 발생하는데 예외처리를 해주지 않았을 경우
조 언
예외를 발생하는 메소드 같은 경우는 반드시 예외처리를 해주어야 합니다. 예외를 발생하거나 예외처리를 해야하는 메소드는 API를 확인해 보시면 알 수 있습니다. 그렇지 않다면 컴파일 후 지금처럼 에러가 난 예외를 예외처리해 주시면 됩니다. 또한, 예외를 처리할 때는 메소드 차원에서 throws 예외명을 이용해서 처리할 수 있고 try{} catch{} 구문을 이용해서 직접 처리해 주셔도 됩니다.(예외 강좌를 참고하세요.) 특히, 예외도 클래스이므로 반드시 예외가 들어간 패키지를 import 해주어야 합니다.
번 호
9
ERROR
Note : Calculator.java(파일명) uses or overrides a deprecated API. Note : Recompile with -deprecation for details.
발생되는 경우
JDK 버전이 높아졌거나 보안등의 기타이유로 사용이 deprecated된 메소드를 사용한 경우
조 언
이건 예외라기 보다는 경고 입니다.(실행하면 됩니다.^^) JDK가 버전이 높아지거나 보안등의 이유에 따라 예전에 만들어졌지만 필요가 없어지거나 대체된 메소드가 생겨났습니다. 그런 메소드를 deprecated 되었다고 하는데 이것은 API상에 나왔있습니다. 또한, 컴파일할때 -deprecation 옵션주면 어떤 메소드가 deprecate됐는지 알수있읍니다. 사용이 중지 됐다고 보기 보다는 사용을 가능하면 하지 않게끔 해주는 거죠. 대치되었거나 버전 업된 메소드를 사용하시면 됩니다.
MouseEvent(클래스명) should be declared abstract; it does not define mouseDragged(java.awt.event.MouseEvent)[메소드명(메소드가 포함된 클래스)] in MouseEvent(클래스명)
발생되는 경우
implements한 Interface의 모든 메소드를 구현하지 않아서 발생됨
조 언
Interface는 모든 메소드가 선언만 되고 구현되지 않은 추상(abstract) 메소드입니다. 만약 Interface를 implements하려면 implements한 클래스가 Interface에서 선언한 모든 메소드를 구현해 주어야 합니다. 하나라도 빠질 경우 implements한 클래스도 추상 클래스로 보고 에러가 발생합니다. 에러에 구현해 주어야할 메소드명이 나오므로 그곳에 쓰여있는 메소드를 구현해 주면 됩니다. 만약, 그 메소드를 구현해 주었는데 에러가 나면 철자 및 대소문자를 다시 확인해 보십시요.
번 호
11
ERROR
incompatible types found : /null(입력한 자료형) required : int(요구하는 자료형)
발생되는 경우
입력을 했을때 맞지 않는 자료형이나 클래스형을 입력할 경우
조 언
incompatible 은 '성미가 맞지 않는','모순된' 이라는 뜻을 가진 단어 입니다. 단어뜻 처럼 입력 경우 required 에 나타난 자료형 및 클래스형을 요구하는데 found 에서 나타난 자료형 및 클래스형을 써주어서 입력을 하지 못하게 되어서 발생하는 에러입니다. found 에 나타난 자료형을 required 에 나타난 자료형으로 변경해 주시면 됩니다.
번 호
12
ERROR
package java.servlet(패키지명) does not exist
발생되는 경우
import한 패키지가 존재하지 않을 경우
조 언
import한 패키지가 존재하지 않을 경우에 발생하는 에러입니다. 철자와 대소문자를 먼저 확인하고 CLASSPATH 설정을 확인해 보시기 바랍니다. 또한 그 곳에 패키지가 jar파일로 있는지도 확인해 보셔야 합니다. (API에 나와있는 패키지는 rt.jar에 다 있습니다. 단 javax가 붙거나 다름으로 시작되는 확장 패키지는 설치해 주어야합니다.(javax.swing 제외))
번 호
13
ERROR
java.lang.NullPointerException Exception in thread "main"(메소드) java.lang.NullPointerException at java.awt.Container.addImpl(Container.java:341)... [에러가 일어난 부분]
발생되는 경우
참조하거나 사용한 클래스 또는 자료형이 초기화 되지 않은 경우
조 언
보통 이것은 awt나 배열 부분에서 자주 발생하는데 초기화를 해주지 않아서 많이 발생합니다. 자바의 변수들은 기본적으로(자동으로 초기화 되는 멤버필드등을 제외하고) 초기화를 요구합니다. 에러에 체크된 부분을 검토해 보시고 초기화를 해주십시요.
번 호
14
ERROR
';'(빠진 부분) expected
발생되는 경우
문법상으로 써야할 것을 쓰지 않은 경우 발생합니다.
조 언
주로 ';'을 안써주시거나 아님 '()'(괄호)를 열기만 하고 닫지 않은 실수를 할 경우 발생합니다. 대부분 이 에러가 발생한 경우는 에러에 나온것을 소스에 추가해 주시면 됩니다.
번 호
15
ERROR
unexpected type required : value(요구하는 타입) found : class(소스상 써준 타입)
발생되는 경우
써주어야 할 타입이 아닌 잘못된 타입을 써주었을 경우
조 언
unexpected type 에러를 해석하면 '기대하지 않은 타입'이란 뜻을 가지고 있습니다. 즉, 원하는 타입(required)이 아닌데 잘못된 타입(found)을 써준 경우 발생합니다. 에러 체크된 부분의 타입을 required 에서 나타난 타입으로 변경해 주시면 됩니다.
번 호
16
ERROR
java.lang.ArrayIndexOutOfBoundsException at Test.main(Test.java:10)[클래스.메소드(파일명:에러난 위치)] Exception in thread "main"(예외가 던져진 메소드)
발생되는 경우
배열의 범위를 넘어선 값을 넣었을 경우
조 언
위의 에러는 특이하게 컴파일은 이상없이 되지만 실행을 하면 발생하는 에러입니다. 배열의 범위를 넘어설 경우에 발생하므로 에러난 위치의 배열의 참조 범위를 확인해보시고 선언해둔 배열의 범위에 맞게 조정해 주시면 됩니다.
번 호
17
ERROR
illegal start of expression
발생되는 경우
선언자(modifier)를 잘못 집어 넣은 경우
조 언
에러의 단어뜻을 확인해 보면 '표현의 시작이 부적격 합니다.'하고 해석할 수 있습니다. 보통 선언자가 맞지 않거나 쓰일데가 아닌데 선언자를 줄 경우에 많이 발생합니다. 특히 메소드안에서 static 선언자를 쓴 경우에는 직격으로 볼수 있죠. 에러가 난 부분의 선언자를 제거하거나 맞는 것인지 다시 확인해 보십시요.
번 호
18
ERROR
java.io.InputStream(클래스) is abstract; cannot be instantiated
발생되는 경우
abstract로 선언된 클래스를 직접 new 명령어를 이용하여 인스턴스화 할 경우
조 언
abstract로 선언된 클래스를 직접 new 명령어를 이용하여 인스턴스화 할 경우에 발생하는 에러입니다. 왜냐하면, 추상 클래스는 직접 new 명령어를 이용하여 인스턴스화 할수 없기 때문입니다.(객체를 못만든다구요.) 이 경우에는 인스턴스를 다른 방법으로 생성하시면 됩니다. 예를 들어 인스턴스를 반환하는 메소드를 이용한다거나 상속을 통해서 상속받은 클래스의 인스턴스를 만들어 직접 인스턴스를 만드는 효과를 낼수도 있구요. 원하시는 방법으로 바꾸어 보시길...
번 호
19
ERROR
local variable name(변수명) is accessed from within inner class; needs to be declared final
발생되는 경우
Local Class의 변수를 final로 선언하지 않은 경우
조 언
Local Class의 변수는 참조변수의 참조값 변동을 방지하기 위하여 final 선언자를 붙여주어야 합니다. 변수에 final 선언자를 붙이면 변수는 값을 변동할 수 없는 상수 처럼 쓰이며 만약 이 값을 참조할 경우 자바는 이 값을 넘기는게 아니라 이 값의 복사본을 참조 값으로 넘기게 됩니다. 그러므로 Local Class에서 참조값 변동없이 변수를 참조할 수 있게 되는 것입니다. Local Class를 정의해준 곳을 살펴보고 final 선언자를 확실하게 확인하시기 바랍니다.
내부 클래스 안에서는 static 선언자를 쓸수 없습니다. 내부 클래스 안에서 사용된 static 선언자를 제거해 주십시요.
번 호
21
ERROR
referenceto List is ambiguous,both class java.util.List(클래스) in java.util(패키지) and class java.awt.List(클래스) in java.awt(패키지) match
발생되는 경우
클래스 사용시 다른 패키지내에 동일이름의 클래스들이 있어서 참조가 모호할 경우
조 언
예시를 보면 아시겠지만 import 한 패키지중에 같은 이름을 사용하는 클래스를 클래스 이름만으로 생성함으로서 참조가 모호해질 경우 발생하는 에러입니다. 이와 같은 경우는 import를 하나 제거 하거나 아님 java.util.List 이런식으로 직접 그 클래스의 패키지를 같이 써줌으로서 모호성을 제거할수 있습니다.
번 호
22
ERROR
m()(메소드명) in B(클래스명) cannot override m()(메소드명) in A(클래스명); attempting to use incompatiable return type
발생되는 경우
클래스를 상속받고서 메소드를 오버라이드 하고자할때 잘못한 경우
조 언
클래스를 상속받고서 메소드를 오버라이드 할 경우에는 지켜야 하는 규칙이 있습니다. 1. 메소드의 이름이 같아야 합니다. 2. 메소드의 파라미터 개수, 데이터형이 같아야 합니다. 3. 메소드의 리턴형이 같아야 합니다. 4. 상위 메소드와 동일하거나 더 구체적인 Exception을 발생시켜야 합니다. 5. 상위 메소드와 동일하거나 접근범위가 더 넣은 접근 제한자를 사용해야 합니다.
님의 메소드 오버라이드시 위 규칙을 잘 지켰는가를 다시 한번 확인해 보세요.
번 호
23
ERROR
getPathBetweenRows(int,int)(메소드) has protected access in javax.swing.jTree(클래스)
발생되는 경우
protected로 선언된 메소드를 상속 없이 직접 불러쓸 경우
조 언
protected로 선언되어 있는 메소드는 상속하거나 같은 package에 있을 때만 쓸 수 있습니다. 상속해서 다시 public 메소드로 값을 받던지 아니면 public 메소드 중에서 비슷한 기능을 하는 메소드가 있는지 찾아서 바꾸어주어야 합니다.
번 호
24
ERROR
invalid method declaration; return type required
발생되는 경우
리턴 타입을 쓰지 않아 메소드의 선언이 잘못된 경우
조 언
리턴 타입을 쓰지 않아 메소드의 선언이 잘못된 경우에 발생하는 에러이므로 에러가 발생한 메소드를 확인해보고 리턴 타입을 맞게 적어주어야 합니다.
번 호
25
ERROR
Error occurred during initialization of VM java.lang.ExceptionInInitializerError
발생되는 경우
static으로 선언된 변수중 초기화가 안되어 있는 것이 있는 경우
조 언
static으로 선언된 변수중에 초기화가 안된게 있는 경우에 발생하는 에러이므로 에러가 발생한 변수를 확인해보고 알맞은 초기화를 시켜주거나 변수의 위치를 자동 초기화가 가능한 메소드 밖의 클래스 변수로서 사용하게 합니다.
번 호
26
ERROR
Error opening registry key 'Software\JavaSoft\Java Runtime Environment' Error: could not find java.dll Error: could not find Java 2 Runtime Environment
발생되는 경우
중복설치 등으로 인해 레지스트리 키값이 잘못되어 있는 경우
조 언
중복설치 등으로 인해 레지스트리 키값이 잘못되어 있는 경우에 발생하는 에러이므로 레지스트리 편집기를 열어서 HKEY_LOCAL_MACHINE -> SOFTWARE -> JavaSoft에 보시면 3개의 키가 있을 겁니다. 그중에서 첫번째 키인 Java 런타임 환경 을 마우스 오른쪽 버튼으로 클릭하여 Java Runtime Environment로 이름을 바꿔주시면 됩니다.
번 호
27
ERROR
Error Registry Key 'Sofrware\JavaSofrware\Java Runtime Environment\CurrentVerison' has value '1.1',but '1.3' is requried. Error: could not find java.dll Error: could not find java 2 Runtime Enviroment.
발생되는 경우
중복설치 등으로 인해 레지스트리 키값의 자바 버전이 잘못되어 있는 경우
조 언
중복설치 등으로 인해 레지스트리 키값의 자바 버전이 잘못되어 있는 경우에 발생하는 에러이므로 레지스트리 편집기를 열어서 HKEY_LOCAL_MACHINE -> SOFTWARE -> JavaSoft -> Java Runtime Environment의 Current version의 값을 1.3으로 되어있는지 확인해 주시면 됩니다.
JDBC를 연결하는 중에 드라이버를 찾지 못할 경우에 발생하는 에러이므로 각 데이터 베이스에 맞는 드라이버가 제대로 다운로드 되었는지 확인해 보시고 드라이버의 위치가 클래스 패스에 잡혀 있는지 확인해주시면 됩니다.
번 호
29
ERROR
Method printIn(java.lang.String)(메소드명) not found in class java.io.PrintStream(클래스명)
발생되는 경우
자신이 사용한 클래스의 메소드가 맞지 않는(=없는)경우
조 언
자신이 사용한 클래스의 메소드가 맞지 않는(=없는) 경우에 발생하는 에러이므로 API를 통해서 사용하고자 하는 클래스와 메소드를 다시 한번 확인해 봅니다. 보통 이경우 메소드의 철자나 대소문자를 잘못 쓴 경우가 많으니 그점을 유심히 살표봅니다. 마지막으로 철자와 대소문자도 맞는다면 메소드의 인자의 객체형을 맞게 주었는지 확인해보면 됩니다.
public static void main(String[] args) { Random rand = new Random(System.currentTimeMillis()); // seed값을 배정하여 생성 System.out.println(Math.abs(rand.nextGaussian())); // 난수값 출력
컴퓨터는 생각을 할 수 없습니다. 그래서 무작위로 수를 뽑는 것 역시 불가능합니다. 단, 엄청나게 긴 수열을 만들어 내어서 "난수"로 보이는 숫자를 만들어 낼 수 있는데 그것이 유사 난수(Pseudo random)이라고 합니다. 단, 이 수열의 집합은 Seed라는 지정번호에 좌우되는데(Seed가 같을 경우, random을
해도 같은 수열만 나온단 말이 되겠죠) Seed의 숫자에 따라서 생성되는 수열이 달라집니다.
프로그래머들은 Seed 번호를 대개 현재의 시각에 대응시킵니다. 그래서 한번의 Random
호출시마다 다른 난수가 생성됩니다. 즉 명령을 실행하라고 인간이 엔터키나 클릭을 한
시점의 시간을 1000분의 1초를 단위로 추출해서 그 수를 기본으로 난수를 만들기 때문에
언제나 실행 할때마다 전혀 다른 난수가 나오게 되는것입니다.
최종적으로는, seed를 and or xor 등등을 여러번 섞고 왼쪽 오른쪽으로 돌렸다가 비트단위로
- 단순성이라는 설계 목표에 따라 고급언어들에 들어있는 여러 가지 복잡한 기능을 제거 했습니다.
* 복잡한 동기화 요소 통합, 간단한 구문, 풍부한 라이브러리 클래스를 통한 편리한 프로그래밍등
- 자바에는 포인터의 개념이 없습니다.
* 유효하지 않는 메모리 참조를 막아 안전성을 높이기 위해 포인터 기능을 제거했습니다.
- 자바는 쓰레기 수집이란걸 통해서 자동적으로 메모리를 관리 하고 있습니다.
* 쓰레기 수집이 없다면 프로그래머가 사용하지 않는 메모리를 체크하고 반환하는 일을 수동적으로
처리해야할것이다. 비효율적인면도 있지만, 프로그래머가 보다 프로그래밍에 집중할 수 있도록
도와줍니다.
- 컴파일된후 나온 파일이 다르다. 자바는 컴파일 하게 되면 바이너리 파일로 되어 있습니다.
2. 객체지향 언어입니다.
- 프로그램을 기술할 때, 단계보다는 데이터와 데이터를 가공하는 함수들에 초점을 갖춘언어입니다.
- 상속, 캡슐화, 다형성이 잘 적용된 순수한 객체 지향언어입니다.
- 모든 기능을 클래스화 해서 분리 작업이 가능하기 때문에 협업에 효율적이고 재사용이 유리합니다.
- 협업이 가능해 개발 시간이 줄어듭니다.
3. 플랫폼에 독립적입니다.
- 다양한 네트워크환경 및 하드웨어에서 사용 가능하도록 개발되어있습니다.
* HTTP등과 같은 TCP/IP네트워크 환경에서 동작하는 많은 프로토콜을 지원하는 라이브러리를
가지고 있습니다.
- 플랫폼에 독립적인 바이트코드를 이용하여 자바 가상 기계를 통한 인터프리터 방식으로 실행합니다.
4. 견고하고 보안에 강합니다.
- 인터넷과 대규모 분산환경을 염두해 풍부하고 다양한 네트워크 프로그래밍 라이브러를 통해 보다 짧은 시간에 쉽게 네트워크 관련 프로그램을 개발할수 있지만 다른 언어보다 안정성에 대한 중요성이 요구됩니다.
* Java코드는 바이러스,파일의 삭제나 수정,데이타 파괴 작업이나 컴퓨터 오류 연산등을 방지할 수
있는 환경에서 실행되도록 설계되었습니다.
* virus-free혹은 tamper-free시스템들의구축을 가능케합니다. 인증기술등은 공중키암호화에 기반을
두고 있습니다.
- 컴파일 할때, 유형 검사를 하여 에러를 막을 뿐만 아니라 도구들까지 명확한 유형 선언을 하도록 합니다..
- 포인터 연산을 지원하지 않음으로써 잘못된 주소를 가르킬 가능성을 없앴습니다.
- 모든 메모리 접근을 Java 시스템이 관리하고 제한하며 또한 예외 처리를 하여 시스템 붕괴의 우려가 없습니다.
* 메모리를 덮어 쓰거나 데이터를 망가뜨리는 가능성을 제거했습니다.
- 반복적인 형 검사를 통해서 프로그램의 신뢰성을 향상시킵니다.
5. 동적이고 멀티 스레드를 지원합니다.
- 시스템과는 관계없이 멀티스레드 구현이 가능합니다.
- 라이브러리 갱신의 경우 재컴파일이 불필요합니다.
개발환경
기계 중립적이며, 이식성이 뛰어나다는게 다른 다른언어와 구별되는 개발환경 차이점입니다.
- Web의 서버-클라이언트 환경에서 이기종 서버-클라이언트의 지원은 매우 중요한 문제입니다. 일반적으로 네트웍은 다양한 CPU와 OS를 가진 시스템들로 구성되어 있습니다. Java 응용 프로그램이 네트웍상의 어디에서든지 수행이 되기 위해서는 컴파일러가 기계 구조에 중립적인 오브젝트 파일 포맷을 생성해야 합니다. 그러므로, 실행 파일이 기계 중립적인 이진 코드입니다. 이 클래스 파일을 기계 종속적인 Java 런타임이 인터프리트하여 실행시키게 됩니다.
- 유형 정의를 시스템에 무관하게 정의하고 있습니다.
* 네트웍을 통해 프로그램을 다운받아 하드웨어 에 관계없이 사용하기 위해서는 근본적으로 아키텍처에 독립적이고 이식성을 보장하는 구조가 요구됩니다
* Java는 이러한 문제를 해결하게 위해 하드웨어 아키텍쳐, 운영체제 인터페이스, 윈도우 시스템에 독립적인 바이트 코드를 사용합니다.
- Java 프로그램은 Windows95/NT, Solaris2.x, Mac OS7.5와 같은 Java가 지원되는 모든 플랫폼상에서 Java 컴파일러에 의해 바이트 코드 형태로 컴파일되고, 인터프리터가 동작하는 Java 가상 기계에 의해 어떤 기종의 시스템에서도 쉽게 해석됩니다.
자바는 C와 비교할 때 스트링 처리에 있어서 차이를 가지고 있는데, 그 차이점에 대해서 설명하세요.
C/C++에서는 문자의 배열이나, 자바에서는 객체로 취급합니다.
- Java는 모든 것을 객체로 구현합니다. 심지어는 문자열(String)도 클래스로 구현되어 있습니다.
기존 C, C++에서 문자열이란 끝이 널 문자인 문자 배열로 구현되었습니다.
그러나 Java에서는 문자열을 String, StringBuffer 개체로 구현합니다.
그러므로 메모리를 효율적, 안정적으로 사용 가능합니다.
- 객체이므로 NEW 연산자를 통해서 객체 생성과정이 필요하지만, 인용부호(“,”)를 이용하여 문자의 연속으로 생성이 가능합니다.
클래스와 객체가 가지는 차이점에 대해서 간단히 설명하시오
객체 : 프로그램은 객체로 구성
데이터와 그 데이터를 가공하는 함수를 한데 뭉친 것을 말합니다. 즉 가운데 데이터들을 가지고 있고,주변에는 데이터를 가공하는 Method들로 외부로 들어오는 호출을 모조리 막고(Encapsulation) 있음으로써 프로그램이 서로 연관되는 데이터와 함수들끼리 모을 수 있고, 서로간에 데이터를 바로 접근을 못하기 때문에 프로그램을 고치기가 용이합니다.
클래스 : 자바 프로그래밍의 기본 단위
객체 지향 설계는 객체의 생성을 위한 용기로서 클래스 개념을 사용합니다. 이것은 어떤 사물의 특징 목록으로 생각될 수 있습니다. 즉, 데이터 선언이 있고 그 데이터를 처리해 주는 Method들로 둘러싸인 아직 실제로 존재하지 않는 구조를 말합니다. (자료부분+메소드부분)
* 자료 : 객체의 구조를 기술합니다.(변수,상수로 구성)
클래스의 필드라고 부릅니다.
* 메소드 : 객체의 행위를 정의합니다.(작업의 처리 부분과 자료의 접근 형태 등을 기술하는 부분입니다.)
표준 C언어의 함수 형태입니다.
객체는 클래스가 실제화 된 것이므로 클래스의 인스턴스(instance)라고 부르고, 클래스는 객체를 정의하기 위한 기본이 되므로 객체를 정의하는 템플릿(template)이라고 부릅니다.
RECENT COMMENT