public class ModifierTest { public static final int WIDTH = 200; public static void main(String[] args) { System.out.println("WIDTH=" + WIDTH); }}
클래스와 클래스의 멤버(멤버 변수, 메서드)에 부가적인 의미 부여
접근 제어자 - publicprotected(default)private
그 외 - staticfinalabstractnativetransientsynchronizedvolatilestrictfp
하나의 대상에 여러 제어자를 같이 사용가능(접근 제어자는 하나만)
static - 클래스의, 공통적인
대상
의미
멤버변수
모든 인스턴스에 공통적으로 사용되는 클래스 변수가 된다. 클래스 변수는 인스턴스를 생성하지 않고도 사용 가능하다. 클래스가 메모리에 로드될 때 생성된다.
메서드
인스턴스를 생성하지 않고도 호출이 가능한 static 메서드가 된다. static 메서드 내에서는 인스턴스 멤버들을 직접 사용할 수 없다.
final - 마지막의, 변경될 수 없는
대상
의미
클래스
변경될 수 없는 클래스, 확장될 수 없는 클래스가 된다. 그래서 final로 지정된 클래스는 다른 클래스의 조상이 될 수 없다.
메서드
변경될 수 없는 메서드, final로 지정된 메서드는 오버라이딩을 통해 재정의 될 수 없다.
멤버변수 지역변수
변수 앞에 final 이 붙으면, 값을 변경할 수 없는 상수가 된다.
final class AbstractTest { // 조상이 될 수 없는 클래스 final int MAX_SIZE = 10; // 값을 변경할 수 없는 멤버변수 (상수) final void getMaxSize() { // 오버라이딩을 할 수 없는 메서드 (변경불가) final int LV = MAX_SIZE; // 값을 변경할 수 없는 지역변수 (상수) return MAX_SIZE; }}
abstract - 추상의, 미완성의
대상
의미
클래스
클래스 내에 추상 메서드가 선언되어 있음을 의미한다.
메서드
선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알린다.
abstract class AbstractTest { // 추상 클래스 (추상 메서드를 포함한 클래스) abstract void move(); // 추상 메서드 (구현부가 없는 메서드)}AbstractTest a = new AbstractTest(); // 에러. 추상 클래스의 인스턴스 생성불가
21. 접근 제어자
private - 같은 클래스 내에서만 접근 가능
(default) - 같은 패키지 내에서만 접근 가능
protected - 같은 패지키 내에서, 그리고 다른 패키지의 자손클래스에서 접근 가능
public - 접근 제한이 없음
제어자
같은 클래스
같은 패키지
자손 클래스
전체
public
⭕
⭕
⭕
⭕
protected
⭕
⭕
⭕
(default)
⭕
⭕
private
⭕
22. 캡슐화
캡슐화와 접근 제어자
public class Time { // 외부에서 직접 접근하지 못하도록 private private int hour; private int minute; private int second; // 메서드를 통해 간접 접근 허용 public int getHour() { return hour; } public void setHour(int hour) { if (hour < 0 || hour > 23) { return; } this.hour = hour; }}
접근 제어자를 사용하는 이유
외부로부터 데이터를 보호하기 위해서
외부에는 불필요한, 내부적으로만 사용되는, 부분을 감추기 위해서
23. 다형성 (polymorphism)
개념
조상 타입 참조 변수로 자손 타입 객체를 다루는 것
Tv t = new SmartTv(); // 타입 불일치class Tv { boolean power; int channel; void power() { power = !power; } void channelUp() { ++channel; } void channelDown() { --channel; }}class SmartTv extends Tv { boolean caption; void displayCaption(String text) { if (caption) { System.out.println(text); } }}
특징
SmartTv s = new SmartTv(); // 참조변수와 인스턴스의 타입이 일치Tv t = new SmartTv(); // 조상타입 참조변수로 자손타입 인스턴스 참조// 조상타입 Tv참조변수로 자손타입 SmartTv인스턴스를 참조할 경우 조상의 멤버만 사용할 수 있음
객체와 참조변수의 타입이 일치할 때와 일치하지 않을 때의 차이?
Tv t = new SmartTv(); // OK.SmartTv s = new Tv(); // 에러.
자손 타입의 참조변수로 조상 타입의 객체를 가리킬 수 없다.
장점
다형적 매개변수 - 메서드 호출 시, 자신과 같은 타입 또는 자손 타입의 인스턴스를 넘길 수 있음
하나의 배열에 여러 종류의 객체를 저장하여 다룰 수 있음
24~25. 참조변수의 형변환
참조변수의 형변환 시 변하는 것
달라지는 것 : 객체의 타입
그대로인 것 : 객체 자체, 주소값
참조변수의 형변환을 하는 이유
사용할 수 있는 멤버의 갯수를 조절하기 위해서
조상 자손 관계의 참조변수는 서로 형변환 가능
class Car { }class FireEngine extends Car { }class Ambulance extends Car { }FireEngine f = new FireEngine();Car c = (Car)f; // OK. 조상인 Car타입으로 형변환 (업캐스팅)FireEngine f2 = FireEngine(c); // OK. 자손인 FireEngine타입으로 형변환 (다운캐스팅)Ambulance a = (Ambulance)f; // 에러. 상속관계가 아닌 클래스 간의 형변환 불가
형변환 시 실제 인스턴스가 무엇인지가 중요
public class Ex7_7 { public static void main(String[] args) { Car car = null; FireEngine fe = new FireEngine(); FireEngine fe2 = (FireEngine)car; // 다운캐스팅 Car car2 = (Car)fe2; // 업캐스팅 car2.drive(); // NullPointerException 발생. // 왜? 기존 인스턴스인 car이 null이기 때문에 Car c = new Car(); FireEngine f = (FireEngine)c; // 형변환 실행 에러 ClassCastException 발생. f.water(); // 컴파일은 OK. // 실제 객체는 Car인스턴스이기 때문에 f.water() 사용불가 }}
26. instanceof 연산자
참조변수의 형변환 가능 여부 확인에 사용. 가능하면 true 반환
형변환 전에 반드시 instanceof 로 확인해야 함
void doWork(Car c) { if (c instanceof FireEngine) { // 1. 형변환이 가능한지 확인 FireEngine fe = (FireEngine)c; // 2. 형변환 fe.water(); }}FireEngine fe = new FireEngine();System.out.println(fe instanceof Object); // trueSystem.out.println(fe instanceof Car); // trueSystem.out.println(fe instanceof FireEngine); // trueObject obj = (Object)fe; // OK.Car c = (Car)fe; // OK.
27~28. 매개변수의 다형성
class Product { int price; int bonusPoint;}class Tv extends Product { }class Computer extends Product { }class Audio extends Product { }class Buyer { int money = 1000; int bonusPoint = 0;}// Product의 자손 타입인 Tv, Computer, Audio 모두 매개변수로 받을 수 있음void buy(Product p) { money -= p.price; bonusPoint += p.bonusPoint;}Buyer b = new Buyer();Tv tv = new Tv();Computer com = new Computer();b.buy(tv);b.buy(com);
참조형 매개변수는 메서드 호출 시, 자신과 같은 타입 또는 자손 타입의 인스턴스를 넘길 수 있음
27~28. 여러 종류의 객체를 배열로 다루기
Product p[] = new Product[3];p[0] = new Tv();p[1] = new Computer();p[2] = new Audio();
class Buyer { int money = 1000; int bonusPoint = 0; Product[] cart = new Product[10]; int i = 0; void buy(Product p) { if(money < p.price) { System.out.println("잔액부족"); return; } money -= p.price; bonusPoint += p.bonusPoint; cart[i++] = p; }}
조상타입의 배열에 자손들의 객체를 담을 수 있음
31~32. 추상 클래스, 추상 메서드
추상 클래스 (abstract class)
abstract class Player { // 추상 클래스 (미완성 클래스) abstract void play(int pos); // 추상 메서드 (몸통{}이 없는 미완성 메서드) abstract void stop(); // 추상 메서드}
개념
미완성 설계도. 미완성 메서드를 갖고 있는 클래스
public abstract class AbstractClass { private final String name; public AbstractClass(final String name) { this.name = name; } abstract void method(); public String getName() { return name; }}
Abstract Class, 추상 클래스란 하나 이상의 abstract 메서드를 선언한 클래스로써 이를 상속하는 자손 클래스에서 완성하도록 유도하는 클래스다.
일반 클래스에 abstract 메서드를 추가했기 때문에 인스턴스화가 불가하고 미완성 설계도라고도 불린다.
단일 상속만 가능하다.
추상 클래스를 상속하는 집합간에는 연관 관계가 있다.
Player p = new Player(); // 에러. 추상 클래스의 인스턴스 생성 불가
다른 클래스 작성에 도움을 주기 위한 것. 인스턴스 생성 불가.
class AudioPlayer extends Player { void play(int pos) { // 추상 메서드를 구현 } void stop() { // 추상 메서드를 구현 }}AudioPlayer ap = new AudioPlayer(); // OK.
상속을 통해 추상 메서드를 완성해야 인스턴스 생성 가능
추상 메서드 (abstract method)
// 주석을 통해 어떤 기능을 수행할 목적으로 작성하였는지 설명abstract 리턴타입 메서드이름();
개념
미완성 메서드. 구현부 (몸통 { }) 가 없는 메서드
abstract class Player { // 추상 클래스 abstract void play(int pos); // 추상 메서드 abstract void stop(); // 추상 메서드}class AudioPlayer extends Player { void play(int pos) { // 추상 메서드를 구현 } void stop() { // 추상 메서드를 구현 }}// 추상 메서드를 1개 밖에 구현하지 않았기 때문에 abstractabstract class AbstractPlayer extends Player { void play(int pos) { // 추상 메서드를 구현 }}
꼭 필요하지만 자손마다 다르게 구현될 것으로 예상되는 경우
abstract class Player { // 추상 클래스 boolean pause; int currentPos; Player() { // 추상 클래스도 생성자가 있어야 함 pause = false; currentPos = 0; } // 지정된 위치(pos)에서 재생하는 기능을 작성해야 함 abstract void play(int pos); // 추상 메서드 // 재생을 즉시 멈추는 기능을 작성해야 함 abstract void stop(); // 추상 메서드 void play() { play(currentPos); // 추상 메서드 사용 가능 // 왜? 어짜피 추상 클래스는 객체 생성이 불가능 // 인스턴스 메서드를 호출 할 수 있다는 것은 // 자손이 완성된 클래스를 만들었다는 의미기 때문에 }}
추상 메서드 호출 가능 (호출 할 때는 선언부만 필요)
추상 메서드를 선언함으로써 자손들에게 있어서 각자 필수 기능을 작성하게 끔 강제성을 부여
33~34. 추상 클래스의 작성
여러 클래스에 공통적으로 사용될 수 있는 추상 클래스를 바로 작성하거나 기존 클래스의 공통 부분을 뽑아서 추상 클래스를 만든다.
abstract class Unit { int x, y; abstract void move(int x, int y); void stop() { }}class Marine extends Unit { void move(int x, int y) { } void stimPack() { }}class Tank extends Unit { void move(int x, int y) { } void changeMode() { }}class Dropship extends Unit { void move(int x, int y) { } void load() { } void unload() { }}Unit[] group = new Unit[3];group[0] = new Marine();group[1] = new Tank();group[2] = new Dropship();for (int i = 0; i < group.length; i++) { group[i].move(100, 200);}
추상화 ↔ 구체화
추상화된 코드는 구체화된 코드보다 유연하다. 변경에 유리
GregorianCalendar cal = new GregorianCalendar(); // 구체적Calendar cal = Calendar.getInstance(); // 추상적
35~37. 인터페이스의 선언, 상속, 구현
인터페이스
interface PlayingCard { public static final int SPADE = 4; final int DIAMOND = 3; // public static final 생략 가능 static int HEART = 2; // public static final 생략 가능 int CLOVER = 1; // public static final 생략 가능 public abstract String getCardNumber(); String getCardKind(); // public abstract 생략 가능}
추상 메서드의 집합 (프로그래밍 관점)
구현된 것이 전혀 없는 설계도. 껍데기 (모든 멤버가 public)
interface Interface { /* public static final */ String name = "name"; /* public abstract */ void method(); /* public */ static void staticMethod() { System.out.println("do something"); } /* public */ default void defaultMethod() { System.out.println("do something"); }}
Interface 란 상수와 abstract 메서드의 집합으로 Abstract Class 와 동일하게 이를 구현하는 클래스에서 abstract 메서드를 완성하도록 강제하지만 Abstract Class 보다 추상화 정도가 높다.
추상 클래스가 미완성 설계도라면 인터페이스는 기본 설계도라고 할 수 있다.
다중 상속이 가능하다.
인터페이스를 구현하는 집합간에는 관계가 없을 수 있다.
JDK1.8 static 메서드와 default 메서드를 사용할 수 있게 되었다.
default 메서드의 목적은 구현체에 공통적으로 사용될 메서드를 인터페이스에 선언하기 위함이다.
class Fighter implements Fightable { // 인터페이스에 정의된 추상메서드를 모두 구현해야 함 public void move(int x, int y) { // 내용 구현 } public void attack(Unit u) { // 내용 구현 }}
일부만 구현하는 경우, 클래스 앞에 abstract 를 붙여야 함
abstract class Fighter implements Fightable { public void move(int x, int y) { // 내용 구현 }
추상 클래스와 인터페이스의 차이점
추상 클래스는 일반 클래스와 같지만 추상 메서드를 갖고 있음
인터페이스는 추상 메서드 (static 메서드, default 메서드, 상수) 만 갖고 있음
iv, 생성자, 인스턴스 메서드 등을 가질 수 없음
38. 인터페이스와 다형성
인터페이스도 구현 클래스의 부모
class Fighter extends Unit implements Fightable { public void move(int x, int y) { // 내용 구현 } public void attack(Fightable f) { // 내용 구현 }}
// 조상 클래스 -> 자손 객체Unit u = new Fighter();// 인터페이스도 가능. 하지만, 인터페이스에 선언된 기능만 사용 가능Fightable f = new Fighter();f.move(100, 200);f.attack(new Fighter());
인터페이스를 메서드의 리턴타입으로 지정할 수 있음
// Fightable 인터페이스를 구현한 클래스의 인스턴스를 반환Fightable method() { return new Fighter();}class Fighter extends Unit implements Fightable { public void move(int x, int y) { // 내용 구현 } public void attack(Fightable f) { // 내용 구현 }}
39. 인터페이스의 장점
두 대상(객체) 간의 ‘연걸, 대화, 소통’을 돕는 ‘중간 역할’
선언(설계)와 구현을 분리 시킬 수 있음
// 껍데기 + 알맹이class B { public void method() { System.out.println("methodInB"); }}
// 껍데기interface I { public void method();}// 알맹이class B implements I { public void method() { System.out.println("methodInB"); }}
인터페이스 덕분에 B가 변경되어도 A는 안 바꿀 수 있게 된다. (느슨한 결합)
A가 B에 의존적일 때
class A { public void methodA(B b) { b.methodB(); }}class B { public void methodB() { System.out.println("methodB()"); }}class InterfaceTest { public static void main(String args[]) { A a = new A(); a.methodA(new B()); }}
// B와 관계 없어진 Aclass A { public void methodA(I i) { i.methodB(); }}interface I { void methodB();}class B implemetns I { public void methodB() { System.out.println("methodB()"); }}// B를 C로 변경 시 A는 변경 없음class C implemetns I { public void methodB() { System.out.println("methodB() in C"); }}
개발 시간을 단축
A가 B에 의존적일 때 B의 인터페이스를 미리 구현함으로써 B가 완성되기 전 A를 개발
변경에 유리한 유연한 설계가 가능
표준화가 가능 (A가 의존하는 B와 C의 공통 표준을 요구할 수 있음)
서로 관계없는 클래스들을 관계를 맺어줄 수 있음
// GroundUnit 자손으로 Marine, SCV, Tank 가 있고// SCV, Tank 만 repair() 메서드를 추가하고 싶을 때// repairable 이라는 인터페이스를 구현함으로써 관계없는 클래스 간에 공통점 생성 가능void repair(Repariable r) { if (r instanceof Unit) { Unit u = (Unit)r; while (u.hitPoint != u.MAX_HP) { u.hitPoint++; } }}
40~41. default 메서드, static 메서드
인터페이스에 default 메서드, static 메서드 추가 가능. (JDK 1.8 부터)
인터페이스에 새로운 메서드(추상 메서드)를 추가하기 어려움.
기존에 인터페이스를 구현했던 다수의 클래스가 인터페이스에 새로운 메서드가 생겼을 때 각자 해당 메서드를 구현해야 했음
class A { // 외부 클래스 class B { // 내부 클래스 // 객체 생성 없이 A의 멤버 접근 가능 ... }}
장점
내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있음
코드의 복잡성을 줄일 수 있음 (캡슐화)
특징
내부 클래스의 종류와 유효범위(scope)는 변수와 동일
class Outer { class InstanceInner { } static class StaticInner { } void myMethod() { class LocalInner { } }}
내부 클래스
특징
인스턴스 클래스
외부 클래스의 멤버변수 선언위치에 선언. 외부 클래스의 인스턴스 멤버처럼 다루어짐. 주로 외부 클래스의 인스턴스 멤버들과 관련된 작업에 사용
static 클래스
외부 클래스의 멤버변수 선언위치에 선언 외부 클래스의 static 멤버처럼 다루어짐. 주로 외부 클래스의 static 멤버, 특히 static 메서드에 사용될 목적으로 선언
local 클래스
외부 클래스의 메서드나 초기화 블럭 안에 선언 선언된 영역 내부에서만 사용 가능
익명 클래스
클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스 (일회용)
45~50. 내부 클래스의 제어자와 접근성
내부 클래스의 제어자는 변수에 사용 가능한 제어자와 동일
일반 클래스는 default 랑 public 만 가능
내부 클래스는 protected 랑 private 도 가능
class Outer { private class InstanceInner { } protected static class StaticInner { } void myMethod() { class LocalInner { } }}
내부 클래스의 제어자와 접근성 - 예제
class Ex7_12 { class InstanceInner { int iv = 100; // static int cv = 100; // 에러. static 변수를 선언할 수 없음 final static int CONST = 100; // 상수는 허용 } 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; // 상수는 허용 } } public static void main(String[] args) { System.out.println(InstanceInner.CONST); System.out.println(StaticInner.cv); // System.out.println(LocalInner.CONST); // 에러. 메서드 내에서만 가능 }}
51~52. 익명 클래스
이름이 없는 일회용 클래스. 정의와 생성을 동시에
new 조상클래스이름() { // 멤버 선언}new 구현인터페이스이름() { // 멤버 선언}
익명 클래스 - 예제
import java.awt.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;public class Ex7_18 { public static void main(String[] args) { Button b = new Button(); // 클래스 정의와 객체 생성 따로 b.addActionListener(new EventHandler()); // 클래스 정의와 객체 생성 동시에 b.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("ActionEvent occurred!!"); } }); }}class EventHandler implements ActionListener { public void actionPerformed(ActionEvent e) { System.out.println("ActionEvent occurred!!"); }}