<예제 7-1 >
✍️ 입력
class Tv {
boolean power; // 전원상태(on/off)
int channel; // 채널
void power() { power = !power; }
void channelUp() { ++channel; }
void channelDown() { --channel; }
}
class SmartTv extends Tv { // CaptionTv는 Tv에 캡션(자막)을 보여주는 기능을 추가
boolean caption; // 캡션상태(on/off)
void displayCaption(String text) {
if (caption) { // 캡션 상태가 on(true)일 때만 text를 보여 준다.
System.out.println(text);
}
}
}
class Ex7_1 {
public static void main(String args[]) {
SmartTv stv = new SmartTv();
stv.channel = 10; // 조상 클래스로부터 상속받은 멤버
stv.channelUp(); // 조상 클래스로부터 상속받은 멤버
System.out.println(stv.channel);
stv.displayCaption("Hello, World");
stv.caption = true; // 캡션(자막) 기능을 켠다.
stv.displayCaption("Hello, World");
}
}
💻 출력
11
Hello, World
-> 다중상속을 하게 되면 메소드 등의 이름이 겹칠 경우 무엇을 써야 할 지 모른다. 불리한 점이 많이 생김!
-> println은 참조변수를 출력할때 toString()메소드를 적용해서 출력한다.
ex) Sys..println(c) == Sys..println(c.toString())
<예제 7-2 >
✍️ 입력
class Ex7_2 {
public static void main(String args[]) {
Child c = new Child();
c.method();
}
}
class Parent { int x=10; }
class Child extends Parent {
int x=20;
void method() {
System.out.println("x=" + x);
System.out.println("this.x=" + this.x);
System.out.println("super.x="+ super.x);
}
}
💻 출력
x=20
this.x=20
super.x=10
<예제 7-3 >
✍️ 입력
class Ex7_3 {
public static void main(String args[]) {
Child2 c = new Child2();
c.method();
}
}
class Parent2 { int x=10; }
class Child2 extends Parent2 {
void method() {
System.out.println("x=" + x);
System.out.println("this.x=" + this.x);
System.out.println("super.x="+ super.x);
}
}
💻 출력
x=10
this.x=10
super.x=10
-> point3D 클래스에서 생성자를 써주지 않으면 부모의 있는 기본 생성자를 가져온다.(super()) but 현재 문제에서는 부모클래스(point)에 기본 생성자가 존재하지 않으므로 오류가 난다.
-> 해결방법
1. 부모클래스에 기본 생성자를 넣어준다.
-> 컴파일 단계에서 생성자가 없으면 기본 생성자를 만들어준다. 하지만 Point클래스에는 생성자가 존재하여 기본생성자를 만들어 주지 않는다.
2. 조상의 매개변수가 있는 생성자를 사용한다.
기본생성자
를 잘 만들도록 하자!
-> 패키지 최상위 폴더의 이전폴더로 경로 이동 후 프로젝트를 실행시키자.
<예제 7-6 >
✍️ 입력
import static java.lang.System.out;
import static java.lang.Math.*;
class Ex7_6 {
public static void main(String[] args) {
// System.out.println(Math.random());
out.println(random());
// System.out.println("Math.PI :"+Math.PI);
out.println("Math.PI :" + PI);
}
}
💻 출력
0.9201018187215498
Math.PI :3.141592653589793
-> 클래스는 public과 default만 가능하다.
-> 상속을 통해 확장될 것이 예상되는 클래스라면 멤버에 protected를 사용하자.
<예제 7-7 >
✍️ 입력
class Ex7_7 {
public static void main(String args[]) {
Car car = null;
FireEngine fe = new FireEngine();
FireEngine fe2 = null;
fe.water();
car = fe; // car = (Car)fe;에서 형변환이 생략됨
// car.water();
fe2 = (FireEngine)car; // 자손타입 ← 조상타입. 형변환 생략 불가
fe2.water();
}
}
class Car {
String color;
int door;
void drive() { // 운전하는 기능
System.out.println("drive, Brrrr~");
}
void stop() { // 멈추는 기능
System.out.println("stop!!!");
}
}
class FireEngine extends Car { // 소방차
void water() { // 물을 뿌리는 기능
System.out.println("water!!!");
}
}
💻 출력
water!!!
water!!!
-> fe2 = (FireEngine)car; : car(4개 멤버)를 fe2(5개 멤버)로 바꾸면 불안전하기 때문에 형변환 해주어야 한다,
<예제 7-8 >
✍️ 입력
class Product {
int price; // 제품의 가격
int bonusPoint; // 제품구매 시 제공하는 보너스점수
Product(int price) {
this.price = price;
bonusPoint = (int)(price/10.0); // 보너스점수는 제품가격의 10%
}
}
class Tv1 extends Product {
Tv1() {
// 조상클래스의 생성자 Product(int price)를 호출한다.
super(100); // Tv의 가격을 100만원으로 한다.
}
// Object클래스의 toString()을 오버라이딩한다.
public String toString() { return "Tv"; }
}
class Computer extends Product {
Computer() { super(200); }
public String toString() { return "Computer"; }
}
class Buyer { // 고객, 물건을 사는 사람
int money = 1000; // 소유금액
int bonusPoint = 0; // 보너스점수
void buy(Product p) {
if(money < p.price) {
System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
return;
}
money -= p.price; // 가진 돈에서 구입한 제품의 가격을 뺀다.
bonusPoint += p.bonusPoint; // 제품의 보너스 점수를 추가한다.
System.out.println(p + "을/를 구입하셨습니다.");
}
}
class Ex7_8 {
public static void main(String args[]) {
Buyer b = new Buyer();
b.buy(new Tv1());
b.buy(new Computer());
System.out.println("현재 남은 돈은 " + b.money + "만원입니다.");
System.out.println("현재 보너스점수는 " + b.bonusPoint + "점입니다.");
}
}
💻 출력
Tv을/를 구입하셨습니다.
Computer을/를 구입하셨습니다.
현재 남은 돈은 700만원입니다.
현재 보너스점수는 30점입니다.
<예제 7-9 >
✍️ 입력
class Product2 {
int price; // 제품의 가격
int bonusPoint; // 제품구매 시 제공하는 보너스점수
Product2(int price) {
this.price = price;
bonusPoint = (int)(price/10.0);
}
Product2() {} // 기본 생성자
}
class Tv2 extends Product2 {
Tv2() { super(100); }
public String toString() { return "Tv"; }
}
class Computer2 extends Product2 {
Computer2() { super(200); }
public String toString() { return "Computer"; }
}
class Audio2 extends Product2 {
Audio2() { super(50); }
public String toString() { return "Audio"; }
}
class Buyer2 { // 고객, 물건을 사는 사람
int money = 1000; // 소유금액
int bonusPoint = 0; // 보너스점수
Product2[] cart = new Product2[10]; // 구입한 제품을 저장하기 위한 배열
int i =0; // Product배열에 사용될 카운터
void buy(Product2 p) {
if(money < p.price) {
System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
return;
}
money -= p.price; // 가진 돈에서 구입한 제품의 가격을 뺀다.
bonusPoint += p.bonusPoint; // 제품의 보너스 점수를 추가한다.
cart[i++] = p; // 제품을 Product[] cart에 저장한다.
System.out.println(p + "을/를 구입하셨습니다.");
}
// 뒷 페이지에 계속됩니다.
void summary() { // 구매한 물품에 대한 정보를 요약해서 보여 준다.
int sum = 0; // 구입한 물품의 가격합계
String itemList =""; // 구입한 물품목록
// 반복문을 이용해서 구입한 물품의 총 가격과 목록을 만든다.
for(int i=0; i<cart.length;i++) {
if(cart[i]==null) break;
sum += cart[i].price;
itemList += cart[i] + ", ";
}
System.out.println("구입하신 물품의 총금액은 " + sum + "만원입니다.");
System.out.println("구입하신 제품은 " + itemList + "입니다.");
}
}
class Ex7_9 {
public static void main(String args[]) {
Buyer2 b = new Buyer2();
b.buy(new Tv2());
b.buy(new Computer2());
b.buy(new Audio2());
b.summary();
}
}
💻 출력
Tv을/를 구입하셨습니다.
Computer을/를 구입하셨습니다.
Audio을/를 구입하셨습니다.
구입하신 물품의 총금액은 350만원입니다.
구입하신 제품은 Tv, Computer, Audio, 입니다.
-> 새로운 클래스를 작성하는데 있어서 바탕이 되는 조상 클래스로서 중요한 의미를 갖는다.
<예제 7-10 >
✍️ 입력
public class Ex7_10 {
public static void main(String[] args) {
Unit[] group = { new Marine(), new Tank(), new Dropship() };
for (int i = 0; i < group.length; i++)
group[i].move(100, 200);
}
}
abstract class Unit {
int x, y;
abstract void move(int x, int y);
void stop() { /* 현재 위치에 정지 */ }
}
class Marine extends Unit { // 보병
void move(int x, int y) {
System.out.println("Marine[x=" + x + ",y=" + y + "]");
}
void stimPack() { /* 스팀팩을 사용한다. */ }
}
class Tank extends Unit { // 탱크
void move(int x, int y) {
System.out.println("Tank[x=" + x + ",y=" + y + "]");
}
void changeMode() { /* 공격모드를 변환한다. */ }
}
class Dropship extends Unit { // 수송선
void move(int x, int y) {
System.out.println("Dropship[x=" + x + ",y=" + y + "]");
}
void load() { /* 선택된 대상을 태운다. */ }
void unload() { /* 선택된 대상을 내린다. */ }
}
💻 출력
Marine[x=100,y=200]
Tank[x=100,y=200]
Dropship[x=100,y=200]
if) Unit을 Object로 바꾼다면?
-> Object클래스에 move메서드가 선어되어 있지 않으므로 오류가 날 것이다.
-> 리모콘에 버튼이 없는 거랑 마찬가지임!
-> 만일 클래스가 인터페이스의 메소드를 모두 구체화하지 않았다면 클래스를 추상화시켜줘야 한다. (abstract class)
-> 메소드 구체화는 곧 오버라이딩이기 때문에 접근제어자를 public으로 해주어야 한다! why? 인터페이스는 무조건 public이기 때문이다. (오버라이딩 할 때 자식은 부모의 접근제어자보다 좁지 않아야 한다는 조건!)
-> 암기!
1. 인터페이스를 매개변수로 하고 있다면?
-> 인터페이스를 구현한 클래스의 인스턴스를 넣어줘야 한다.
2. 인터페이스를 반환하는 메서드가 있다면?
-> 인퍼테이스를 구현하고 있는 클래스의 인스턴스를 반환해야 한다.
-> 껍데기 부분과 알맹이 부분을 분리하면 수정을 줄일 수 있다.
-> 외우기 귀찮다면 그냥 필요한거 오버라이딩 해버리면 그만임!
<예제 7-11 >
✍️ 입력
class Ex7_11 {
public static void main(String[] args) {
Child3 c = new Child3();
c.method1();
c.method2();
MyInterface.staticMethod();
MyInterface2.staticMethod();
}
}
class Child3 extends Parent3 implements MyInterface, MyInterface2 {
public void method1() {
System.out.println("method1() in Child3"); // 오버라이딩
}
}
class Parent3 {
public void method2() {
System.out.println("method2() in Parent3");
}
}
interface MyInterface {
default void method1() {
System.out.println("method1() in MyInterface");
}
default void method2() {
System.out.println("method2() in MyInterface");
}
static void staticMethod() {
System.out.println("staticMethod() in MyInterface");
}
}
interface MyInterface2 {
default void method1() {
System.out.println("method1() in MyInterface2");
}
static void staticMethod() {
System.out.println("staticMethod() in MyInterface2");
}
}
💻 출력
method1() in Child3
method2() in Parent3
staticMethod() in MyInterface
staticMethod() in MyInterface2
<예제 7-12 >
✍️ 입력
class Ex7_12 {
class InstanceInner {
int iv = 100;
// static int cv = 100; // 에러! static변수를 선언할 수 없다.
final static int CONST = 100; // final static은 상수이므로 허용
}
static class StaticInner {
int iv = 200;
static int cv = 200; // static클래스만 static멤버를 정의할 수 있다.
}
void myMethod() {
class LocalInner {
int iv = 300;
// static int cv = 300; // 에러! static변수를 선언할 수 없다.
final static int CONST = 300; // final static은 상수이므로 허용
}
}
public static void main(String args[]) {
System.out.println(InstanceInner.CONST);
System.out.println(StaticInner.cv);
}
}
💻 출력
100
200
-> 내부 클래스 중에서 스태틱 클래스만 static멤버를 가질 수 있다. 드문 경우지만 내부 클래스에 static변수를 선언해야 한다면 스태틱 클래스로 선언해야 한다.
-> why?
클래스 멤버를 사용할 때 객체생성을 안해줘도 되는데 내부클래스가 스테틱클래스가 아니라면 객체를 생성하고 클래스 멤버를 사용해야 하기 때문임!
-> 다만 final과 static이 동시에 붙은 변수는 상수(constant)이므로 모든 내부 클래스에서 정의가 가능하다.
<예제 7-13 >
✍️ 입력
class Ex7_13 {
class InstanceInner {}
static class StaticInner {}
// 인스턴스멤버 간에는 서로 직접 접근이 가능하다.
InstanceInner iv = new InstanceInner();
// static 멤버 간에는 서로 직접 접근이 가능하다.
static StaticInner cv = new StaticInner();
static void staticMethod() {
// static멤버는 인스턴스멤버에 직접 접근할 수 없다.
// InstanceInner obj1 = new InstanceInner();
StaticInner obj2 = new StaticInner();
// 굳이 접근하려면 아래와 같이 객체를 생성해야 한다.
// 인스턴스클래스는 외부 클래스를 먼저 생성해야만 생성할 수 있다.
Ex7_13 outer = new Ex7_13();
InstanceInner obj1 = outer.new InstanceInner();
}
void instanceMethod() {
// 인스턴스메서드에서는 인스턴스멤버와 static멤버 모두 접근 가능하다.
InstanceInner obj1 = new InstanceInner();
StaticInner obj2 = new StaticInner();
// 메서드 내에 지역적으로 선언된 내부 클래스는 외부에서 접근할 수 없다.
// LocalInner lv = new LocalInner();
}
void myMethod() {
class LocalInner {}
LocalInner lv = new LocalInner();
}
}
💻 출력
없음.
<예제 7-14 >
✍️ 입력
class Outer {
private int outerIv = 0;
static int outerCv = 0;
class InstanceInner {
int iiv = outerIv; // 외부 클래스의 private멤버도 접근가능하다.
int iiv2 = outerCv;
}
static class StaticInner {
// 스태틱 클래스는 외부 클래스의 인스턴스멤버에 접근할 수 없다.
// int siv = outerIv;
static int scv = outerCv;
}
void myMethod() {
int lv = 0;
final int LV = 0; // JDK1.8부터 final 생략 가능
class LocalInner {
int liv = outerIv;
int liv2 = outerCv;
// 외부 클래스의 지역변수는 final이 붙은 변수(상수)만 접근가능하다.
// int liv3 = lv; // 에러!!!(JDK1.8부터 에러 아님)
int liv4 = LV; // OK
}
}
}
💻 출력
없음.
-> 지역 클래스는 final이 붙은 지역변수만 접근가능한데 그이유는 메서드가 수행을 마쳐서 지역변수가 소멸된 시점에도, 지역 클래스의 인스턴스가 소멸된 지역변수를 참조하려는 경우가 발생 할 수 있다.
<예제 7-15 >
✍️ 입력
class Outer2 {
class InstanceInner {
int iv = 100;
}
static class StaticInner {
int iv = 200;
static int cv = 300;
}
void myMethod() {
class LocalInner {
int iv = 400;
}
}
}
class Ex7_15 {
public static void main(String[] args) {
// 인스턴스클래스의 인스턴스를 생성하려면
// 외부 클래스의 인스턴스를 먼저 생성해야 한다.
Outer2 oc = new Outer2();
Outer2.InstanceInner ii = oc.new InstanceInner();
System.out.println("ii.iv : "+ ii.iv);
System.out.println("Outer2.StaticInner.cv : "+Outer2.StaticInner.cv);
// 스태틱 내부 클래스의 인스턴스는 외부 클래스를 먼저 생성하지 않아도 된다.
Outer2.StaticInner si = new Outer2.StaticInner();
System.out.println("si.iv : "+ si.iv);
}
}
💻 출력
ii.iv : 100
Outer2.StaticInner.cv : 300
si.iv : 200
-> 컴파일 시 생성되는 클래스 파일은 다음과 같다.
<예제 7-16 >
✍️ 입력
class Outer3 {
int value = 10; // Outer3.this.value
class Inner {
int value = 20; // this.value
void method1() {
int value = 30;
System.out.println(" value :" + value);
System.out.println(" this.value :" + this.value);
System.out.println("Outer3.this.value :" + Outer3.this.value);
}
} // Inner클래스의 끝
} // Outer3클래스의 끝
class Ex7_16 {
public static void main(String args[]) {
Outer3 outer = new Outer3();
Outer3.Inner inner = outer.new Inner();
inner.method1();
}
}
💻 출력
value :30
this.value :20
Outer3.this.value :10
-> 조상클래스의 이름이나 구현하고자 하는 인터페이스의 이름을 사용해서 정의하기 때문에 하나의 클래스로 상속받는 동시에 인터페이스를 구현하거나 둘 이상의 인터페이스를 구현할 수 없다.
[출처] 자바의 정석 <기초편> (남궁 성 지음)