JAVA 객체와 클래스, 상속과 인터페이스
객체와 클래스
객체란 ?
객체란 말그대로 object를 뜻합니다. 현실세계에서의 객체 그대로 말입니다.
프로그래밍 기술의 발전속도에 비해 하드웨어의 발전속도는 매우 빨랐습니다. 그리고 그에 따른 하드웨어를 제어하기 위한 프로그래밍 기술을 만들기 위해 생각하던 도중, 현실세계의 모습을 적용하게 되었습니다. 현실세계에서 이 큰 지구에서 수많은 사람들, 즉 객체가 존재하는데 큰 혼란없이 그들은 모두 잘 살아간다는 것에서 아이디어를 얻은 것입니다.
예를 들어 이야기를 하자면, 은행을 보면,
계좌번호 : 111 상품코드 : 521
에금주 이름 : 탁보람 재고수량 : 312
잔액 : 30만원 -------------------
------------------------ 재고를 더한다.
예금한다 재고를 뺀다.
인출한다.
계좌번호 : 121 상품코드 : 544
에금주 이름 : 백장미 재고수량 : 312
잔액 : 20원 -------------------
------------------------ 재고를 더한다.
예금한다 재고를 뺀다.
인출한다.
이렇듯, 객체는 수만가지인데 그 객체끼리 데이터구조와 기능이 서로 같습니다. 이런 공통점을 따로 뽑아 정의하고, 그 정의를 클래스( class ) 라고 부릅니다.
위의 경우에는 공통된 데이터구조와 기능을 추출하는 경우
은행계좌클래스
-----------------
계좌번호
예금주 이름
잔액
------------------
예금한다.
이를 프로그램에 적용하면 객체지향 프로그래밍에서는 클래스의 코드를 작성해놓고 객체를 찍어내는 것입니다. 우리는 이러한 객체를 만들어야 하는데, 프로그램안에 존재하는 객체를 일일이 만들수 없습니다. 우리는 그저 명령어를 통해 프로그램이 객체를 만들도록 하는 것입니다. 하지만 컴퓨터는 저와 같은 클래스를 이해하지 못하기에 프로그래밍언어로 작성해주어야 합니다.
클래스의 코드를 컴퓨터로 이해 할수 있는 언어로 작성하는 것을 클래스의 선언이라고 합니다.
객체의 생성과 사용
객체를 생성하는 방법
객체를 생성하기 위해서는 자바의 new 연산자를 사용해야 합니다.
new StringBuffer ("Hey, JAVA")
객체생성때 클래스이름 객체 생성에 사용되는 값
필요한연산자
하지만 이렇게 객체를 생성하는 식만 가지고는 아무 소용이 없습니다. 객체를 데이터로 취급하기 때문에 생성된 객체를 다음과 같이 변수에 대입 할 수 있습니다.
obj = new StringBuffer("hey,Java");
객체를 객체를 생성하는 식
담는변수
자바에서 클래스는 객체를 생성하는 도구인 동시에 객체의 종류를 나타내는 데이터 타입입니다. 그렇기 때문에 객체를 담아둘 변수는 클래스 이름을 타입으로 삼아서 선언할 수 있습니다.
stringBuffer obj;
클래스이름을 변수이름
변수타입으로 사용
생성자
자바에서 객체가 생성되고 나서 해야 할일 을 클래스 안에 써두고 모든 객체들이 그 로직을 이용해서 초기화 작업을 할 수 있도록 만들어 둔 것을 말합니다.
생성자를 추가한 GoodsStock 클래스 – 상품재고 클래스
class GoodsStock
{
String goodsCode;
int stockNum;
GoodsStock(String code, int num){
goodsCode = code;
stockNum = num;
}
void addStock(int amount){
stockNum += amount;
}
int subtractStock(int amount){
if (stockNum < amount)
{
return 0;
}
stockNum -= amount;
return amount;
}
}
class ClassExample1
{
public static void main(String args[]){
GoodsStock obj;
obj = new GoodsStock("52135",200);
System.out.println("상품코드: " + obj.goodsCode);
System.out.println("재고수량: " + obj.stockNum);
obj.addStock(1000);
System.out.println("상품코드: " + obj.goodsCode);
System.out.println("재고수량: " + obj.stockNum);
}
}
▶둘 이상의 생성자를 갖는 클래스
[이름]가입자 정보 클래스 |
[데이터] 이름 아이디 패스워드 전화번호 주소 |
[기능] 패스워드를 바꾼다 전화번호를 등록한다 주소를 등록한다. |
class SubscriberInfo { String name, id, password; String phoneNo, address; SubscriberInfo(String name, String id, String password){ this.name = name; this.id = id; this.password = password; } SubscriberInfo(String name, String id, String password, String phoneNo, String address){ this.name = name; this.id = id; this.password = password; this.phoneNo = phoneNo; this.address = address; } void changePassword(String password){ this.password = password; } void setPhoneNo(String phoneNo){ this.phoneNo = phoneNo; } void setAddress(String address){ this.address = address; } } |
class ConstrExample2 { public static void main(String args[]){ SubscriberInfo obj1, obj2; obj1 = new SubscriberInfo("백장미", "Cham dodo", "dodo"); obj2 = new SubscriberInfo("탁보람", "tactactac", "tac", "060-555-6648", "숙사"); printSubscriberInfo(obj1); printSubscriberInfo(obj2); } static void printSubscriberInfo(SubscriberInfo obj){ System.out.println("이름 : " + obj.name); System.out.println("아이디 : " + obj.id); System.out.println("패스워드 : " + obj.password); System.out.println("전화번호 : " + obj.phoneNo); System.out.println("주소 : " + obj.address); System.out.println(); } } |
▶생성자의 구분 방법
클래스 안에 있는 생성자들은 파라미터 변수의 수, 타입, 순서로 구분됩니다. 하지만 파라미터 변수의 이름은 구분의 기준에 되지 않습니다.
class SubscriberInfo { String name, id, password; String phoneNo, address; SubscriberInfo(String name, String id, String password, String phoneNo){ this.name = name; this.id = id; this.password = password; this.password = phoneNo; } SubscriberInfo(String name, String id, String password, String address){ this.name = name; this.id = id; this.password = password; this.address = address; } void changePassword(String password){ this.password = password; } void setPhoneNo(String phoneNo){ this.phoneNo = phoneNo; } void setAddress(String address){ this.address = address; } } |
▶생성자에서 생성자 호출하기
생성자가 하는 일은 대개 비슷비슷 합니다. 이럴 때는 똑같은 명령문을 생성자마다 반복해서 쓰는 것보다 생성자에서 생성자를 호출하는 방법을 사용하는 것이 더 좋습니다.
this(name,id,password); |
class SubscriberInfo { String name, id, password; String phoneNo, address; SubscriberInfo(){ } SubscriberInfo(String name, String id, String password){ this.name = name; this.id = id; this.password = password; } SubscriberInfo(String name, String id, String password, String phoneNo, String address){ this(name, id, password); this.phoneNo = phoneNo; this.address = address; } void changePassword(String password){ this.password = password; } void setPhoneNo(String phoneNo){ this.phoneNo = phoneNo; } void setAddress(String address){ this.address = address; } } |
주의점!! 생성자 안에 쓰는 생성자 호출문은 반드시 그 생성자의 첫 번째 명령문이어야 한다는 점입니다.
▶필드
[이름]원 |
[데이터] 반지름 |
[기능] 면적을 구한다 |
class Circle { double radius; Circle(double radius){ this.radius = radius; } double getArea(){ double area; area = radius*radius*3.14; return area; } } |
이 클래스는 필드뿐만 아니라, 다른 종류의 변수들도 있습니다. 생성자에는 radius라는 파라미터 변수가 있고, getArea 메소드 안에는 area라는 로컬 변수가 있습니다. 이렇게 변수는 선언된 위치에 따라서 명칭도 다르지만 사용할 수 잇는 범위도 서로 다릅니다.
▶필드의 사용 범위
파라미터 변수는 해당 메소드나 생성자 안에서 사용할 수 있고, 로컬 변수는 선언된 위치부터 메소드의 끝 또는 선언된 블록의 끝까지 사용할 수 있습니다. 하지만 필드 같은 클래스 내에 서라면 순서에 상관없이 생성자나 메소드 안에서 얼마든지 사용할 수 있습니다.
class FieldExample2 { public static void main(String args[]){ Circle obj = new Circle(0.0); obj.radius = 5.0; double area = obj.getArea(); System.out.println(obj.radius); System.out.println(area); } } |
class Circle
{
private double radius;
Circle(double radius){
this.radius = radius;
}
double getArea(){
double area;
area = radius*radius*3.14;
return area;
}
}
private 필드인 radius를 사용하고 있기 때문에 이렇게 컴파일할 때 에러가 발생하는 것입니다. 자바에서는 이렇게 객체의 구성요소를 외부로부터 감추는 정보은닉 기술을 제공하고 있는데, 이 기술은 객체들이 필요이상으로 서로 복잡하기 얽히는 것을 방지하는 역할을 합니다.
▶메소드
클래스에서 정의된 기능은 자바 클래스의 메소드로 선언해야 합니다. 클래스에 있는 기능을 메소드로 구현하기 위해서는 메소드에 들어갈 로직을 구상하고, 그 로직을 실행하는 데 필요한 데이터가 무엇인지와 로직의 실행 결과를 어떤 타입으로 데이터로 산출해야 하는지에 대한 파악이 선행되어야 합니다.
class Account { String accountNo; // 계좌번호 String ownerName; // 예금주 이름 int balance; // 잔액 Account(String accountNo, String ownerName, int balance) { // 생성자 this.accountNo = accountNo; this.ownerName = ownerName; this.balance = balance; } void deposit(int amount) { balance += amount; } int withdraw(int amount) { if (balance < amount) return 0; balance -= amount; return amount; } } |
class MethodExample1 { public static void main(String args[]) { Account obj1 = new Account("111-222-33333333", "김영식", 200000); Account obj2 = new Account("555-666-77777777", "박진희", 1000000); obj1.deposit(1000000); obj2.withdraw(200000); printAccount(obj1); printAccount(obj2); } static void printAccount(Account obj) { System.out.println("계좌번호:" + obj.accountNo); System.out.println("예금주 이름:" + obj.ownerName); System.out.println("잔액:" + obj.balance); System.out.println(); } } |
▶클래스 내부에서의 메소드 호출
class Numbers { int num[]; Numbers(int num[]) { this.num = num; } int getTotal() { // 합계를 구하는 메서드 int total = 0; for (int cnt = 0; cnt < num.length; cnt++) total += num[cnt]; return total; } int getAverage() { // 평균을 구하는 메서드 int total; total = getTotal(); int average = total / num.length; return average; } } |
class MethodExample2 { public static void main(String args[]) { int arr[] = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; Numbers obj = new Numbers(arr); int total = obj.getTotal(); int average = obj.getAverage(); System.out.println("합계 = " + total); System.out.println("평균 = " + average); } } |
▶인위적으로 익셉션을 발생시키는 방법
인위적으로 익셉션을 발생시킬때는 throw문을 쓰면 됩니다.
throw new Exception(); |
class Account { String accountNo; String ownerName; int balance; Account(String accountNo, String ownerName, int balance) { this.accountNo = accountNo; this.ownerName = ownerName; this.balance = balance; } void deposit(int amount) { balance += amount; } int withdraw(int amount) throws Exception { if (balance < amount) throw new Exception("잔액이 부족합니다."); balance -= amount; return amount; } } |
class MethodExample5 { public static void main(String args[]) { Account obj = new Account("777-777-77777777", "최대박", 10); try { int amount = obj.withdraw(100000000); System.out.println("인출액:" + amount); } catch (Exception e) { String msg = e.getMessage(); System.out.println(msg); } } } |
▶메소드 오버로딩
자바에서 메소드 호출문에서 사용한 파리멑의 수, 타입, 순서와 메소드에 선언된 파라미터 변수의 수, 타입, 순서가 맞지 않으면 메소드를 호출할 수 없습니다. 자바의 이런 특성을 역으로 이용하면 한 클래스 내에 똑 같은 이름의 메소드를 여러 개 선언할 수 있습니다.
한 클래스 내에 똑 같은 이름의 메소드를 여러 개 선언할 수 있습니다.
class PhysicalInfo { String name; int age; float height, weight; PhysicalInfo(String name, int age, float height, float weight) { this.name = name; this.age = age; this.height = height; this.weight = weight; } void update(int age) { this.age = age; } void update(int age, float height) { this.age = age; this.height = height; } void update(int age, float height, float weight) { this.age = age; this.height = height; this.weight = weight; } } |
위의 클래스의 3개의 update 메소드들은 이름은 똑같지만 파라미터 변수의 수, 타입, 순서가 다르기 때문에 구분될 수 있습니다. 자바에서는 이렇게 메소드 구분에 사용되는 메소드 이름, 파라미터 변수의 수, 타입, 순서를 묶어서 메소드 시그니쳐라고 부릅니다.
class MethodExample6 { public static void main(String args[]) { PhysicalInfo obj; obj = new PhysicalInfo("해리", 10, 132.0f, 35.0f); printPhysicalInfo(obj); obj.update(11, 145.0f, 45.0f); printPhysicalInfo(obj); obj.update(12, 157.0f); printPhysicalInfo(obj); obj.update(13); printPhysicalInfo(obj); } static void printPhysicalInfo(PhysicalInfo obj) { // 객체의 필드 값을 출력하는 메서드 System.out.println("이름:" + obj.name); System.out.println("나이:" + obj.age); System.out.println("키:" + obj.height); System.out.println("몸무게:" + obj.weight); System.out.println(); // 줄 바꿈 문자 출력 } } |
▣클래스의 정적 구성 요소
클래스의 기본적인 용도는 객체를 생성하는 것이고, 클래스에 선언된 필드와 메소드는 객체를 생성하고나면 생성된 객체에 속하게 됩니다. 하지만. 때로는 클래스 자체에 속하는 구성요소를 선언해야 할 필요도 있습니다. 그런 구성요소를 클래스의 정적 구성 요소라고 부릅니다.
▶정적 필드
일반적으로는 필드는 객체의 고유한 데이터 값을 저장하기 위해 사용되지만, 경우에 따라서는 클래스 자체에 속하는 데이터를 저장할 변수도 필요합니다. 그럴 때는 클래스 안에 정적 필드를 선언 하면 됩니다.
class Accumulator { int total = 0; static int grandTotal = 0; // 정적 필드를 선언하는 선언문 void accumulate(int amount) { total += amount; grandTotal += amount; // 정적 필드에 amount 파라미터 값을 더하는 대입문 } } |
total 필드는 객체마다 따로 생기지만 grandTotal 필드는 특정 객체에 상관없이 클래스 자체에 하나만 생기는 정적 필드이기 때문입니다.
class StaticFieldExample1 { public static void main(String args[]) { Accumulator obj1 = new Accumulator(); Accumulator obj2 = new Accumulator(); obj1.accumulate(10); obj2.accumulate(20); System.out.println("obj1.total = " + obj1.total); System.out.println("obj1.grandTotal = " + obj1.grandTotal); ?? System.out.println("obj2.total = " + obj2.total); System.out.println("obj2.grandTotal = " + obj2.grandTotal); } } |
▶상수의 선언
정적 필드에 final 키워드 까지 붙이면, 그 필드는 소스 코드에서 주어진 초기값을 프로그램 실행 중에 절대 바꿀 수 없게 됩니다. 그래서 자바에서는 상수에 이름을 붙여서 사용하고 싶을 때 그 필드를 사용합니다.
class LimitedValue { final static int UPPER_LIMIT = 100000; //상수필드의 선언 int value; void setValue(int value) { if (value < UPPER_LIMIT) this.value = value; else this.value = UPPER_LIMIT; //상수 필드의 사용 } } |
- 명명관례에 따라 상수를 선언할때는 + 을 이용하여 이름을 붙이는 것이 좋습니다.
class StaticFieldExample2 { public static void main(String args[]) { LimitedValue v = new LimitedValue(); v.setValue(200000); System.out.println("v.value = " + v.value); System.out.println("상한값 = " + LimitedValue.UPPER_LIMIT); } } |
▶정작 메소드
static 키워드를 붙여서 선언한 필드를 정적 필드라고 부르듯이 static 키워드를 붙여서 선언한 메소드를 정적 메소드라고 부릅니다.
class Accumulator { int total = 0; static int grandTotal = 0; //정적 필드 선언 void accumulate(int amount) { total += amount; grandTotal += amount; } static int getGrandTotal() { // 정적 메서드 선언 return grandTotal; } } |
class StaticMethodExample1 { public static void main(String args[]) { Accumulator obj1 = new Accumulator(); Accumulator obj2 = new Accumulator(); obj1.accumulate(10); obj2.accumulate(20); int grandTotal = Accumulator.getGrandTotal(); // 정적 메서드 호출문 System.out.println("obj1.total = " + obj1.total); System.out.println("obj2.total = " + obj2.total); System.out.println("총계 = " + grandTotal); } } |
▶정적 초기화 블록
정적 필드는 특정 객체에 속하지 않기 때문에, 생성자에서 초기값을 대입하면 안 됩니다. 그렇기 때문에 정적 필드의 초기값을 필드 선언문에서 대입할 수 없을 때에는, 지금부터 배울 정적 초기화 블록을 사용해야 합니다.
class HundredNumbers { static int arr[]; static { arr = new int[100]; for (int cnt = 0; cnt < 100; cnt++) arr[cnt] = cnt; } } |
이런 경우에 클래스 안에 정적초기화 블록이 설정되면 자바 가상기계는 이 클래스가 사용되기 전에 한 번 이 블록 안의 명령문들을 실행합니다.
class StaticInitializerExample1 { public static void main(String args[]) { System.out.println(HundredNumbers.arr[35]); // 배열에 있는 항목들 중 System.out.println(HundredNumbers.arr[27]); // 3개의 항목을 가져와서 System.out.println(HundredNumbers.arr[63]); // 그 값을 출력합니다. } } |
상속과 인터페이스
- 클래스의 상속
- 클래스를 가져다가 확장해서 새로운 클래스를 만드는 기술을 상속(inheritance)라고 한다.
- 상속은 기존의 클래스가 가지고 있는 데이터 구조와 기능을 그대로 물려받아서 사용하는 기술이라는 의미이다.
[이름] 은행 계좌 클래스 |
[데이터] 계좌번호 예금주 이름 잔액 |
[기능] 예금한다. 인출한다. |
[이름] 직불 계좌 클래스 |
[데이터] 계좌벙호 예금주 이름 잔액 직불카드 번호 |
[기능] 예금한다. 인출한다. 직불카드 사용액을 지불한다. |
[데이터] 직불카드 번호 |
[기능] 직불카드 사용액을 지불한다. |
+ =
- 클래스 상속의 기초문법
다른 클래스를 상속하는 클래스 선언하기
다른 클래스를 상속하는 클래스를 선언하기 위해서는 그 클래스가 어떤 필드와 메소드를 갖고 있는지 알아야 한다.
class Account { String accountNo; String ownerName; int balance; void deposit(int amount) { balance += amount; } int withdraw(int amount) throws Exception { if (balance < amount) throw new Exception("잔액이 부족합니다."); balance -= amount; return amount; } } |
class CheckingAccount extends Account { String cardNo; int pay(String cardNo, int amount) throws Exception { if (!cardNo.equals(this.cardNo) || (balance < amount)) throw new Exception("지불이 불가능합니다."); return withdraw(amount); } } |
다른 클래스를 상속하는 클래스를 선언할 때는 클래스의 이름과 메소드 본체 사이에 extends 절을 넣어서 어느 클래스를 상속할 건지 표시해 줘야한다.
다른 클래스를 상속 받는 클래스를 선언할 때는 추가되는 필드와 메소드만 선언하면된다.
superclass - 다른 클래스에게 상속을 해주는 클래스.
subclass - 상속을 받는 클래스.
위 예에서 Account 클래스는 CheckingAccount의 슈퍼 클래스이고, CheckingAccount는 Account의 서브 클래스이다. 슈퍼 클래스의 변수와 메소드를 서브 클래스에서 선언돼 있는 것처럼 사용하고 있다. 이는 상속 때문에 가능한 것이다.
다른 클래스를 상속해서 만든 클래스를 다시 상속해서 또 다른 클래스를 선언 하는 것도 가능하며 이렇게 선언된 클래스는 자신의 슈퍼 클래스가 상속 받은 필드, 메소드까지 상속 받는다.
class InheritanceExample1 {
public static void main(String args[]) {
CheckingAccount obj = new CheckingAccount();
obj.accountNo = "111-22-33333333";
obj.ownerName = "홍길동";
obj.cardNo = "5555-6666-7777-8888";
obj.deposit(100000);
try {
int paidAmount = obj.pay("5555-6666-7777-8888", 47000);
System.out.println("지불액:" + paidAmount);
System.out.println("잔액:" + obj.balance);
}
catch (Exception e) {
String msg = e.getMessage();
System.out.println(msg);
}
}
}
상속과 생성자
다른 클래스를 상속하는 클래스에 생성자를 선언하는 방법은 일반 클래스의 경우와 마찬가지이다. 슈퍼클래스로부터 상속받은 필드들도 생성자 안에서 초기화 해야 한다.
class CheckingAccount extends Account { String cardNo; CheckingAccount(String accountNo, String ownerName, int balance, String cardNo) { this.accountNo = accountNo; this.ownerName = ownerName; this.balance = balance; this.cardNo = cardNo; } int pay(String cardNo, int amount) throws Exception { if (!cardNo.equals(this.cardNo) || (balance < amount)) throw new Exception("지불이 불가능합니다."); return withdraw(amount); } } |
class InheritanceExample2 { public static void main(String args[]) { CheckingAccount obj = new CheckingAccount("111-22-33333333", "홍길동", 0, "5555-6666-7777-8888"); obj.deposit(100000); try { int paidAmount = obj.pay("5555-6666-7777-8888", 47000); System.out.println("지불액:" + paidAmount); System.out.println("잔액:" + obj.balance); } catch (Exception e) { String msg = e.getMessage(); System.out.println(msg); } } } |
생성자가 있는 슈퍼클래스를 상속하는 방법
class Account { String accountNo; String ownerName; int balance; Account(String accountNo, String ownerName, int balance) { // 생성자 this.accountNo = accountNo; this.ownerName = ownerName; this.balance = balance; } void deposit(int amount) { balance += amount; } int withdraw(int amount) throws Exception { if (balance < amount) throw new Exception("잔액이 부족합니다."); balance -= amount; return amount; } } |
CheckAccount.java 를 컴파일 했을 때 에러 발생
자바 컴파일러가 컴파일을 할 때 생성자의 첫 번째 명령문이 슈퍼클래스의 생성자 호출문이 아니면 자동으로 슈퍼클래스의 no-arg constructor 호출문을 그 위치에 추가하기 때문에 에러가 발생한다.
서브클래스의 생성자 안에 슈퍼클래스의 생성자 호출문을 명시적으로 써넣음으로써 해결할 수 있다. 자바 컴파일러는 생성자 안에 슈퍼클래스의 생성자 호출문이 있으면 no-arg constructor 호출문을 추가하지 않는다.
class CheckingAccount extends Account { String cardNo; CheckingAccount(String accountNo, String ownerName, int balance, String cardNo) { super(accountNo, ownerName, balance); this.cardNo = cardNo; } int pay(String cardNo, int amount) throws Exception { if (!cardNo.equals(this.cardNo) || (balance < amount)) throw new Exception("지불이 불가능합니다."); return withdraw(amount); } } |
- 상속을 금지하는 final 키워드
클래스를 다른 프로그래머가 상속받아서 사용하지 못하도록 막아야 할 경우
final 키워드를 이용한 클래스의 상속 금지
클래슬르 선언할 때 class 키워드 앞에 final이라는 키워드를 쓰면 다른 클래스가 그 클래스를 상속할 수 없게 된다.
final 키워드를 붙여서 선언한 클래스를 상속할 경우 컴파일 에러가 발생한다.
final class Class_name |
final 키워드를 이용한 메소드의 오버라이딩 금지
final은 메소드의 오버라이딩을 금지할 때도 사용된다.
final 키워드를 붙여서 선언한 메소드를 오버라이딩할 경우 컴파일 에러가 발생한다.
final int method_name(int arg) { } |
- 인스턴스화를 금지하는 abstract 키워드
클래스를 가지고 객체를 만드는 일을 인스턴스화라고 한다.
클래스이 주된 용도는 인스턴스화지만 어떤 경우에는 클래스의 인스턴스화를 막아야 할 경우도 있다.
abstract 키워드를 이용한 클래스의 인스턴스화 금지
클래스 제일 앞에 abstract라는 키워드만 붙이면 된다.
abstract 키워드를 붙은 클래스를 추상 클래스라고 하며, 추상 클래스는 객체로 만들 수 없다.
abstract로 선언된 클래스를 인스턴스화하면 컴파일 에러가 발생한다.
메소드 본체가 없는 추상 메소드
메소드의 리턴 타입 앞에 abstract를 쓰고 메소드 본체가 올 자리에 세미콜론을 쓴다.
추상 메소드를 포함하는 클래스는 클래스 자체도 추상 클래스로 선언해야 한다.
abstract class MessageSender { String title; String senderName; MessageSender(String title, String senderName) { this.title = title; this.senderName = senderName; } abstract void sendMessage(String recipient); } |
class EMailSender extends MessageSender { String senderAddr; String emailBody; EMailSender(String title, String senderName, String senderAddr, String emailBody) { super(title, senderName); this.senderAddr = senderAddr; this.emailBody = emailBody; } void sendMessage(String recipient) { System.out.println("------------------------------"); System.out.println("제목: " + title); System.out.println("보내는 사람: " + senderName + " " + senderAddr); System.out.println("받는 사람: " + recipient); System.out.println("내용: " + emailBody); } } |
class SMSSender extends MessageSender { String returnPhoneNo; String message; SMSSender(String title, String senderName, String returnPhoneNo, String message) { super(title, senderName); this.returnPhoneNo = returnPhoneNo; this.message = message; } void sendMessage(String recipient) { System.out.println("------------------------------"); System.out.println("제목: " + title); System.out.println("보내는 사람: " + senderName); System.out.println("전화번호: " + recipient); System.out.println("회신 전화번호: " + returnPhoneNo); System.out.println("메시지 내용: " + message); } } |
class InheritanceExample6 { public static void main(String args[]) { EMailSender obj1 = new EMailSender("생일을 축하합니다", 고객센터", "admin@gmarket.co.kr", "10% 할인쿠폰이 발행되었습니다."); SMSSender obj2 = new SMSSender("생일을 축하합니다", "고객센터", "02-000-0000", "10% 할인쿠폰이 발행되었습니다."); obj1.sendMessage("ad@yeyeye.com"); obj1.sendMessage("gadfe@hahaha.com"); obj2.sendMessage("010-000-0000"); } } |
추상 메소드를 선언하는 목적
서브클래스에서 이 메소드를 반드시 구현하도록 만들기 위해서 선언한다.
추상 메소드를 구현하지 않으면 컴파일 에러가 발생한다.
슈퍼클래스의 메소드를 호출하는 척 하면서 컴파일러의 체크를 통과하고 프로그램이 실행될 때는 서브 클래스의 메소드가 호출 되도록 한다.
- 클래스 변수의 다형성
클래스는 객체를 생성하는 도구로도 사용되지만 객체의 타입으로도 사용된다.
자바에서는 클래스 변수에 그 클래스로 만든 객체뿐만 아니라 그 클래스의 서브 클래스 객체도 대입할 수 있다.
하나의 변수에 여러 종류의 데이터를 대입할 수 있는 성질을 변수의 다형성이라고 한다.
클래스 변수의 다형성
클래스 변수의 다형성을 활용하여 여러 종류의 객체들을 똑 같은 로직으로 처리하는 프로그램을 작성할 수 있다.
class InheritanceExample7 { public static void main(String args[]) { Account obj1 = new Account("111-22-333333", "임꺽정", 10000); CheckingAccount obj2 = new CheckingAccount( "444-55-666666", "홍길동", 20000, "5555-6666-7777-8888"); printAccountInfo(obj1); printAccountInfo(obj2);
} static void printAccountInfo(Account obj) { System.out.println("계좌번호:" + obj.accountNo); System.out.println("예금주 이름:" + obj.ownerName); System.out.println("잔액:" + obj.balance); System.out.println(); } } |
클래스 변수의 다형성을 활용할 때 주의할 점
자바 가상 머신은 객체의 메소드를 호출할 대변수의 타입에 상관없이 객체가 속하는 클래스의 메소드를 호출한다. 반면 자바 컴파일러는 객체가 아니라 변수의 타입만 가지고 그 메소드가 있는지 없는지 체크한다. 그래서 슈퍼클래스에서 해당 메소드를 빼버리면 프로그램을 컴파일 할 때 에러가 발생한다.
인터페이스
자바에서는 클래스의 다중 상속을 허용하지 않는다.
이미 다른 클래스를 상속받은 서브 클래스들의 공통점을 추출해서 또 다른 슈퍼클래스를 선언하고 싶은 경우 인터페이스로 만들면 된다.
인터페이스는 클래스들의 공통 기능만 표현할 수 있고, 공통 데이터는 표현할 수 없다.
인터페이스도 클래스와 마찬가지로 선언을 해야한다.
클래스와 인터페이스의 관계는 클래스 쪽에서 implements라는 자바 키워드와 인터페이스 이름을 쓰는 것으로 맺어진다.
- 인터페이스 선언의 기초 문법
인터페이스도 자바 문법에 맞게 선언을 해야만 프로그램에 사용할 수 있다.
인터페이스 선언 방법
인터페이스에 속하는 메소드는 무조건 추상 메소드로 선언해야한다.
인터페이스의 메소드는 무조건 추상 메소드이기 때문에 abstract 키워드를 쓰지 않아도 된다.
interface Lendable { abstract void checkOut(String borrower, String date); abstract void checkIn(); } |
인터페이스를 구현하는 클래스의 선언 방법은 클래스 본체 바로 앞에 implements 절을 쓰면 된다.
인터페이스 안의 메소드를 구현할 때는 public 키워드를 반드시 써야한다.
하나의 클래스가 여러 개의 인터페이스를 동시에 구현할 수도 있다.
class SeparateVolume implements Lendable { String requestNo; // 청구번호 String bookTitle; // 제목 String writer; // 저자 String borrower; // 대출인 String checkOutDate; // 대출일 byte state; // 대출상태 SeparateVolume(String requestNo, String bookTitle, String writer) { this.requestNo = requestNo; this.bookTitle = bookTitle; this.writer = writer; } public void checkOut(String borrower, String date) { // 대출한다 if (state != 0) return; this.borrower = borrower; this.checkOutDate = date; this.state = 1; System.out.println("*" + bookTitle + " 이(가) 대출되었습니다."); System.out.println("대출인:" + borrower); System.out.println("대출일:" + date + "\n"); } public void checkIn() { // 반납한다 this.borrower = null; this.checkOutDate = null; this.state = 0; System.out.println("*" + bookTitle + " 이(가) 반납되었습니다.\n"); } } |
인터페이스를 가지고 할 수 없는 일
인터페이스를 이용하여 객체를 만들 수 없다.
인터페이스는 객체를 만드는데 사용할 수 없기 때문에 생성자도 선언할 필요가 없다.
인터페이스를 가지고 할 수 있는 일
인터페이스를 구현하는 클래스의 선언 방법을 제한할 수 있다.
인터페이스 변수의 선언에도 사용된다.
- 인터페이스 변수의 다형성
인터페이스 변수에는 그 인터페이스를 구현하는 클래스의 객체라면 어떤 객체든지 다 대입할 수 있다.
인터페이스 변수의 다형성을 이용한 프로그램
class InterfaceExample2 { public static void main(String args[]) { Lendable arr[ ] = new Lendable[2]; arr[0] = new SeparateVolume("883ㅇ", "푸코의 진자", "에코"); arr[1] = new SeparateVolume("609.2", "서양미술사", "곰브리치"); checkOutAll(arr, "윤지혜", "20060315"); } static void checkOutAll(Lendable arr[], String borrower, String date) { for (int cnt = 0; cnt < arr.length; cnt++) arr[cnt].checkOut(borrower, date); } } |
- 인터페이스의 상수 필드
인터페이스에서는 상수 필드는 선언할 수 있다.
final static 키워드를 가지고 선언하는데 쓰지 않아도 컴파일할 때 자동으로 추가 된다.
인터페이스에 상수 필드를 선언하고 사용하는 예
인터페이스를 구현하는 클래스들이 주로 사용하는 상수는 인터페이스 안에 선언하는 것이 좋다.
인터페이스 안에 선언된 상수 필드는 인터페이스를 구현하는 클래스에 상속되기 때문에 그 클래스 안에서 사용할 수 있게 된다.
interface Lendable { final static byte STATE_BORROWED = 1; // 대출 중 final static byte STATE_NORMAL = 0; // 대출되지 않은 상태 void checkOut(String borrower, String date); void checkIn(); } |
class SeparateVolume implements Lendable { String requestNo; // 청구번호 String bookTitle; // 제목 String writer; // 저자 String borrower; // 대출인 String checkOutDate; // 대출일 byte state; // 대출상태 SeparateVolume(String requestNo, String bookTitle, String writer) { this.requestNo = requestNo; this.bookTitle = bookTitle; this.writer = writer; } public void checkOut(String borrower, String date) { if (state != STATE_NORMAL) return; this.borrower = borrower; this.checkOutDate = date; this.state = STATE_BORROWED; System.out.println("*" + bookTitle + " 이(가) 대출되었습니다."); System.out.println("대출인:" + borrower); System.out.println("대출일:" + date + "\n"); } public void checkIn() { this.borrower = null; this.checkOutDate = null; this.state = STATE_NORMAL; System.out.println("*" + bookTitle + " 이(가) 반납되었습니다.\n"); } } |
class InterfaceExample3 { public static void main(String args[]) { SeparateVolume obj = new SeparateVolume("863ㅂ", "나무", "베르베르"); printState(obj); obj.checkOut("이수경", "20060317"); printState(obj); } static void printState(SeparateVolume obj) { if (obj.state == Lendable.STATE_NORMAL) { System.out.println("------------------"); System.out.println("대출상태: 대출가능"); System.out.println("------------------"); } if (obj.state == Lendable.STATE_BORROWED) { System.out.println("------------------"); System.out.println("대출상태: 대출중"); System.out.println("대출인: " + obj.borrower); System.out.println("대출일: " + obj.checkOutDate); System.out.println("------------------"); } } } |
- 익셉션을 발생하는 추상 메소드
추상 메소드를 구현하는 메소드의 경우에는 throws 절을 써도 메소드 밖으로 익셉션을 던질 수 없을 때가 있다.
이를 해결하기 위해서 인터페이스에 그 메소드에 throws 절을 써야한다.
interface Lendable { abstract void checkOut(String borrower, String date) throws Exception ; abstract void checkIn(); } |
'프로그래밍언어 > JAVA' 카테고리의 다른 글
| [동영상 강좌] 이클립스로 간단한 프로젝트 만들기 (4) | 2011.01.04 |
|---|---|
| [동영상 강좌] 이클립스 wtp로 JSP연동하기 (0) | 2011.01.04 |
| [동영상강의] Eclipse에서 Jigloo 사용하기 -3강 (0) | 2011.01.04 |
| [동영상강의] Eclipse에서 Jigloo 사용하기 -2강 (0) | 2011.01.04 |
| [동영상강의] Eclipse에서 Jigloo 사용하기 -1강 (0) | 2011.01.04 |