9.1 접근 제어자 (access modifier)
접근 제어자는 멤버 변수/함수 혹은 클래스에서 사용되며 외부에서의 접근을 제한하는 역할을 한다.
9.1.1 접근 제어자의 종류
•
private : 같은 클래스 내에서만 접근이 가능하다.
•
default(nothing) : 같은 패키지 내에서만 접근이 가능하다.
•
protected : 같은 패키지 내에서, 그리고 다른 패키지의 자손클래스에서 접근이 가능하다.
•
public : 접근 제한이 전혀 없다.
접근 범위에 대해 정리하면 다음과 같다.
(좁음) (넓음)
private. → default → protected → public
9.1.2 접근 제어자 예제
접근 제어자 예제를 위한 첫번째 패키지를 만든다.
첫 번째 패키지 내부에 AccessModifierTest 클래스를 만든다.
package com.fastcampus.de.java.clip9_1;
public class AccessModifierTest {
private void messageInside() {
System.out.println("This is private modifier");
}
void messageDefault() {
messageInside();
System.out.println("This is default(package-private) modifier");
}
protected void messageProtected() {
messageInside();
System.out.println("This is protected modifier");
}
public void messageOutside() {
messageInside();
System.out.println("This is public modifier");
}
}
Java
복사
clip9_1.AccessModifierTest.java
첫 번째 패키지 내부에 Anonymous 클래스를 만든다.
AccessModifierTest 클래스의 객체를 만들고 각 함수를 호출가능한지 확인한다.
package com.fastcampus.de.java.clip9_1;
public class Anonymous {
public void call() {
AccessModifierTest accessModifierTest = new AccessModifierTest();
// accessModifierTest.messageInside(); compile error
accessModifierTest.messageDefault();
accessModifierTest.messageProtected();
accessModifierTest.messageOutside();
}
public static void main(String[] args) {
Anonymous anonymous = new Anonymous();
anonymous.call();
}
}
Java
복사
clip9_1.Anonymous.java
두 번째 패키지를 만든다.
두 번째 패키지 내부에 Anonymous 클래스를 만든다.
AccessModifierTest 클래스의 객체를 만들고 각 함수를 호출가능한지 확인한다.
package com.fastcampus.de.java.clip9_2;
import com.fastcampus.de.java.clip9_1.AccessModifierTest;
public class Anonymous {
public void call() {
AccessModifierTest accessModifierTest = new AccessModifierTest();
// accessModifierTest.messageInside(); compile error
// accessModifierTest.messageDefault();
// accessModifierTest.messageProtected();
accessModifierTest.messageOutside();
}
}
Java
복사
clip9_2.Anonymous.java
두 번째 패키지 내부에 AccessModifierTest를 상속( extends ) 받은 Child 클래스를 만든다. Child 클래스의 함수 내에서 this 키워드를 이용해서 AccessModifierTest에 선언한 각 함수를 호출 가능한지 확인한다.
package com.fastcampus.de.java.clip9_2;
import com.fastcampus.de.java.clip9_1.AccessModifierTest;
public class Child extends AccessModifierTest{
public void call() {
// this.messageInside();
// this.messageDefault();
this.messageProtected();
this.messageOutside();
}
public static void main(String[] args) {
Child child = new Child();
child.call();
}
}
Java
복사
clip9_2.Child.java
왜 접근 제어자를 사용하지?
•
객체지향 프로그래밍이란 객체들 간의 상호작용을 코드로 표현하는 것이다.
•
이때 객체들간의 관계에 따라서 접근 할 수 있는 것과 아닌 것, 권한을 구분할 필요가 생긴다.
•
클래스 내부에 선언된 데이터의 부적절한 사용으로부터 보호하기 위해서!
◦
이런 것을 캡슐화(encapsulation)라고 한다.
•
접근 제어자는 캡슐화가 가능할 수 있도록 돕는 도구이다.
9.2 추상 클래스 (abstract class)
9.2.1 추상 클래스의 정의
•
추상 클래스
추상 클래스는 추상 메소드를 선언할 수 있는 클래스를 의미한다. 또한 추상 클래스는 클래스와는 다르게 상속받는 클래스 없이 그 자체로 인스턴스를 생성할 수는 없다.
•
추상 메소드
추상 메소드는 메소드 시그니처(method signature)의 설계만 되어있으며 수행되는 코드에 대해서는 작성이 안된 메소드이다.
→ 미완성으로 남겨두는 이유는 상속받는 클래스 마다 반드시 동작이 달라지는 경우에 상속받는 클래스 작성자가 반드시 작성하도록 하기 위함이다.
•
추상 메소드 형식
abstract 리턴타입 메소드이름();
Java
복사
9.2.2 추상 클래스 예제
abstract class Bird {
private int x, y, z;
void fly(int x, int y, int z) {
printLocation();
System.out.println("이동합니다.");
this.x = x;
this.y = y;
if (flyable(z)) {
this.z = z;
} else {
System.out.println("그 높이로는 날 수 없습니다");
}
printLocation();
}
abstract boolean flyable(int z);
public void printLocation() {
System.out.println("현재 위치 (" + x + ", " + y + ", " + z + ")");
}
}
class Pigeon extends Bird {
@Override
boolean flyable(int z) {
return z < 10000;
}
}
class Peacock extends Bird {
@Override
boolean flyable(int z) {
return false;
}
}
public class Main {
public static void main(String[] args) {
Bird pigeon = new Pigeon();
Bird peacock = new Peacock();
System.out.println("-- 비둘기 --");
pigeon.fly(1, 1, 3);
System.out.println("-- 공작새 --");
peacock.fly(1, 1, 3);
System.out.println("-- 비둘기 --");
pigeon.fly(3, 3, 30000);
}
}
Java
복사
•
fly(x, y, z) 함수는 Bird 를 상속받는 모든 클래스에서 동일한 동작을 한다. 다만, 그 안에서 호출된 flyable(z) 의 동작만 그것을 구현하는 자식 클래스에서 구현한대로 동작하는 것.
•
공작새(peacok)는 새이지만 전혀 날 수가 없다. 그래서 공작새의 flyable() 은 항상 false 를 리턴해서 언제나 x,y 좌표로만 움직인다. 반면에, 비둘기(pigeon)는 일정 높이까지는 날아갈 수 있기 때문에 그 기준(여기서는 10000)이 되기 전까지는 z좌표로도 움직일 수 있다.
•
이것을 새의 종류마다 중복코드 없이 구현하려면 추상 클래스와 추상 메소드를 이용해서 위와 같이 구현할 수 있다. 이렇게 코드를 짜면, 중복코드가 없으면서도 새의 종류마다 주어진 위치까지 날 수 있는지를 판단할 수 있는 유연성을 허용하며 구현할 수 있다.
9.3 인터페이스 (Interface)
9.3.1 인터페이스의 정의
•
인터페이스는 객체의 특정 행동의 특징만 정의하고 실제 구현은 구현(implements)한 클래스에게 맡기는 문법이다.
•
인터페이스는 함수의 특징(method signature)인 리턴타입, 메소드 이름만을 정의한다.
•
함수의 내용은 없다.
•
인터페이스를 구현(implements)하는 클래스는 인터페이스에 존재하는 메소드의 내용( {} 중괄호 안의 내용)을 반드시 구현해야한다.
•
인터페이스를 구현하겠다고 선언한 클래스가 인터페이스에 선언된 메소드를 구현하지 않으면 컴파일 에러가 발생한다.
•
인터페이스는 default 키워드로 기본 구현을 정의할 수 있다.
•
인터페이스는 정적 메소드(static method)를 정의하고 구현할 수 있다.
•
인터페이스는 추상 메소드(abstract method)를 정의할 수 있다. (가능은 하지만, 인터페이스의 메소드 선언과 기능이 동일하기 때문에 추상메소드는 굳이 선언하지 않는 것을 추천)
•
인터페이스가 가진 멤버 변수는 정적 변수(static variable)이다.
•
인터페이스의 메소드는 구현하는 클래스에서 public 접근제어자를 가진다.
•
인터페이스 형식
interface 인터페이스{
리턴타입 메소드이름();
default 리턴타입;
}
class 클래스 implements 인터페이스 {
@Override
public 리턴타입 메소드이름() {
// TODO implements
}
}
Java
복사
9.3.2 인터페이스 예제
public interface Bird {
String STATIC_VARIABLE = "STATIC";
void fly(int x, int y, int z);
default void printBreed() {
System.out.println("나는 새 중에 " + getBreed() + " 입니다.");
}
String getBreed();
static void staticMethod() {
System.out.println("This is static method");
}
abstract void abstractMethod();
}
public interface Pet {
String getHome();
}
public class Pigeon implements Bird, Pet{
private int x, y, z;
@Override
public void fly(int x, int y, int z) {
System.out.println("이동합니다.");
this.x = x;
this.y = y;
this.z = z;
printLocation();
}
@Override
public String getBreed() {
return "비둘기";
}
@Override
public void abstractMethod() {
System.out.println("this is abtract method implemented from Pigeon");
}
public void printLocation() {
System.out.println("현재 위치 (" + x + ", " + y + ", " + z + ")");
}
@Override
public String getHome() {
return "도곡동";
}
}
public class Main {
public static void main(String[] args) {
Bird bird = new Pigeon();
bird.fly(1, 2, 3);
// bird.printLocation(); // compile error
bird.printBreed();
bird.abstractMethod();
Bird.staticMethod();
System.out.println(Bird.STATIC_VARIABLE);
}
}
Java
복사
interface인 Bird 타입으로 선언한 bird 변수는 실제로 Pigeon 객체이지만, interface인 Bird 에 선언되지 않은 printLocation() 이라는 함수는 호출할 수 없다.
interface type으로 선언되어있는 부분에서는 실제 객체가 무엇이든지, interface에 정의된 행동만 할 수 있다.
@Override
앞에서 배웠던 메소드 오버라이딩 개념이 abstract method, interface의 함수를 구현하는 데에도 사용된다.
9.3.3 인터페이스 vs 추상클래스
•
인터페이스
1.
구현하려는 객체의 동작의 명세
2.
다중 구현 가능
3.
implements를 이용하여 구현
4.
메소드 시그니처(이름, 파라미터, 리턴 타입)에 대한 선언만 가능
•
추상클래스
1.
클래스를 상속받아 이용 및 확장을 위함
2.
다중 상속 불가능, 단일 상속
3.
extends를 이용하여 구현
4.
추상메소드에 대한 구현 가능
5.
캡슐화(encapsulation)를 깰 가능성이 있다.
Quiz
1.
사람은 자식, 부모님, 조부모님이 있다.
2.
모든 사람은 이름, 나이, 현재 장소 정보(x,y좌표)가 있다.
3.
모든 사람은 걸을 수 있다. 장소(x, y좌표)로 이동한다.
4.
자식과 부모님은 뛸 수 있다. 장소(x, y좌표)로 이동한다.
5.
조부모님의 기본 속도는 1이다. 부모의 기본 속도는 3, 자식의 기본 속도는 5이다.
6.
뛸 때는 속도가 기본 속도 대비 +2 빠르다.
7.
수영할 때는 속도가 기본 속도 대비 +1 빠르다.
8.
자식만 수영을 할 수 있다. 장소(x, y좌표)로 이동한다.
위 요구사항을 만족하는 클래스들을 바탕으로, Main 함수를 다음 동작을 출력( System.out.println )하며 실행하도록 작성.
이동하는 동작은 각자 순서가 맞아야 한다.
1.
모든 종류의 사람의 인스턴스는 1개씩 생성한다.
2.
모든 사람의 처음 위치는 x,y 좌표가 (0,0)이다.
3.
모든 사람의 이름, 나이, 속도, 현재 위치를 확인한다.
4.
걸을 수 있는 모든 사람이 (1, 1) 위치로 걷는다.
5.
뛸 수 있는 모든 사람은 (2,2) 위치로 뛰어간다.
6.
수영할 수 있는 모든 사람은 (3, -1)위치로 수영해서 간다.
Answer
public class Human {
String name;
int age;
int speed;
int x, y;
public Human(String name, int age, int speed, int x, int y) {
this.name = name;
this.age = age;
this.speed = speed;
this.x = x;
this.y = y;
}
public Human(String name, int age, int speed) {
this(name, age, speed, 0, 0);
}
public String getLocation() {
return "(" + x + ", " + y + ")";
}
protected void printWhoAmI() {
System.out.println("My name is " + name + ". " + age + " aged.");
}
}
Java
복사
Human.java
public interface Walkable {
void walk(int x, int y);
}
Java
복사
Runnable.java
public interface Runnable {
void run(int x, int y);
}
Java
복사
Swimmable.java
public class GrandParent extends Human implements Walkable {
public GrandParent(String name, int age) {
super(name, age, 1);
}
@Override
public void walk(int x, int y) {
printWhoAmI();
System.out.println("walk speed: " + speed);
this.x = x;
this.y = y;
System.out.println("Walked to " + getLocation());
}
}
Java
복사
GrandParent.java
public class Parent extends Human implements Walkable, Runnable{
public Parent(String name, int age) {
super(name, age, 3);
}
@Override
public void run(int x, int y) {
printWhoAmI();
System.out.println("run speed: " + (speed + 2));
this.x = x;
this.y = y;
System.out.println("Ran to " + getLocation());
}
@Override
public void walk(int x, int y) {
printWhoAmI();
System.out.println("walk speed: " + speed);
this.x = x;
this.y = y;
System.out.println("Walked to " + getLocation());
}
}
Java
복사
Parent.java
public class Child extends Human implements Walkable, Runnable, Swimmable{
public Child(String name, int age) {
super(name, age, 5);
}
@Override
public void swim(int x, int y) {
printWhoAmI();
System.out.println("swimming speed: " + (speed + 1));
this.x = x;
this.y = y;
System.out.println("Swum to " + getLocation());
}
@Override
public void run(int x, int y) {
printWhoAmI();
System.out.println("run speed: " + (speed + +2));
this.x = x;
this.y = y;
System.out.println("Ran to " + getLocation());
}
@Override
public void walk(int x, int y) {
printWhoAmI();
System.out.println("walk speed: " + speed);
this.x = x;
this.y = y;
System.out.println("Walked to " + getLocation());
}
}
Java
복사
Child.java
public class Main {
public static void main(String[] args) {
Human grandParent = new GrandParent("할아버지", 70);
Human parent = new Parent("엄마", 50);
Human child = new Child("나", 20);
Human[] humans = { grandParent, parent, child };
for (Human human : humans) {
System.out.println(human.name + ", 나이: " + human.age + ", 속도: " + human.speed + ", 장소: " + human.getLocation());
}
System.out.println("<활동 시작>");
for (Human human : humans) {
if (human instanceof Walkable) {
((Walkable) human).walk(1, 1);
System.out.println(" - - - - - - ");
}
if (human instanceof Runnable) {
((Runnable) human).run(2, 2);
System.out.println(" - - - - - - ");
}
if (human instanceof Swimmable) {
((Swimmable) human).swim(3, -1);
System.out.println(" - - - - - - ");
}
}
}
}
Java
복사
Main.java
instanceof는 변수의 객체가 어떤 타입인지 확인하는 키워드이다. 변수의 구현 클래스와 같은 클래스, 부모(상속 받은) 클래스, 구현 인터페이스에 해당하면 true를 리턴한다.
명시적 캐스팅(형변환)은 변수 앞에 괄호로 원하는 타입을 넣으면 된다. 캐스팅할 수 없는 타입으로 캐스팅을 시도하면, ClassCastException 이 발생한다. ClassCastException 은 런타임 예외이다. 캐스팅 하기 전에 instanceof 로 확인 해야 안전한 코드를 작성할 수 있습니다.