◎ 상속
- 상속이란, 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것
- 상속을 통해서 클래스를 작성하면 적은 양의 코드로 새로운 클래스를 작성할 수 있고 코드 추가 및 변경 용이
- 코드 재사용성을 높이고 중복제거, 생산성, 유지보수에 크게 기여
- 자손은 조상의 모든 멤버를 상속받음 (생성자, 초기화블럭은 상속되지 않음)
- 자손의 멤버개수는 조상보다 같거나 많음
- 생성자가 있는 클래스를 상속 받았다면 부모 클래스의 생성자를 자손 클래스에 실행 해야함
- 생성자가 있는 클래스를 상속할 때 상위 클래스의 생성자를 실행해야 합
상속 사용하는 방법
// 새로 작성하려는 클래스의 이름은 Child 이고 상속받고자 하는 기존 클래스 이름이 Parent
class Child extends Parent{
}
조상 클래스
- 부모(parent) 클래스, 상위(super) 클래스, 기반(base) 클래스
자손 클라스
- 자식(child) 클래스, 하위(sub) 클래스, 파생된(derived) 클래스
예제1
class Tv{
boolean power; // 전원상태 (on/off)
int channel; // 채널
void power() {power = !power;}
void channelUp() {++channel;}
void channelDown() {--channel;}
}
class CaptionTv extends Tv{
boolean caption; // 캡션상태 (on/off)
void displayCaption(String text){
if (caption) System.out.println(text);
}
}
class prac{
public static void main(String[] args){
CaptionTv ctv = new CaptionTv();
ctv.channel = 10; // 조상 클래스로부터 상속받은 멤버
ctv.channelUp(); // 조상 클래스로부터 상속받은 멤버
System.out.println(ctv.channel); // 11
ctv.displayCaption("Hello World");
ctv.caption = true;
ctv.displayCaption("Hi"); // Hi
}
}
◎ 클래스간의 관계 ㅡ 포함관계
- 포함이란, 한 클래스의 멤버변수로 다른 클래스를 선언하는 것
- 클래스 간의 포함관계를 맺어주는 것은 한 클래스의 멤버변수로 다른 클래스타입의 참조변수를 선언하는 것
- 클래스의 멤버로 참조변수를 선언한 것
- 하나의 거대한 클래스를 작성하는 것보다 단위별로 여러 개의 클래스를 작성한 다음, 이 단위 클래스들을 포함관계로 재사용하는것이 간결하고 손쉽게 클래스를 작성할 수 있음
클래스간의 관계 결정하기 ㅡ 상속 vs 포함
- 가능한 많은 관계를 맺어 재사용성을 높이고 관리하기 쉽게 할 것
예제1
class prac{
public static void main(String[] args){
Point[] p = {
new Point(100, 100),
new Point(140, 50),
new Point(200, 100)
};
Triangle t = new Triangle(p);
Circle c = new Circle(new Point(150,150), 50);
t.draw(); // (100,100), (140,50), (200,100), black
c.draw(); // (150, 150), 50, black
}
}
class Shape{
String color = "black";
void drwa(){
System.out.printf("%s%n", color);
}
}
class Point{
int x;
int y;
Point(int x, int y){
this.x = x;
this.y = y;
}
Point(){
this(0, 0);
}
String getXY(){
return "("+ x + "," + y + ")";
}
void draw(){
System.out.printf("%(d, %d)", x, y);
}
}
class Circle extends Shape{
Point center;
int r;
Circle(Point center, int r){
this.center = center;
this.r = r;
}
Circle(){
this(new Point(0, 0), 100);
}
void draw() {
System.out.printf("(%d, %d), %d, %s%n", center.x, center.y, r, color);
}
}
class Triangle extends Shape{
Point[] p = new Point[3];
Triangle(Point[] p){
this.p = p;
}
void draw(){
System.out.printf("%s, %s, %s, %s%n", p[0].getXY(), p[1].getXY(), p[2].getXY(), color);
}
}
예제2
class prac{
public static void main(String[] args){
Deck d = new Deck(); // 카드 한 벌(Deck)을 만듦
Card c = d.pick(0); // 섞기전에 제일 위의 카드 뽑음.
// 참조변수 출력이나 덧셈연산자를 이용한 참조변수와 문자열의 결합값을 출력시 toString()이 자동으로 호출
System.out.println(c); // Spade,1 . System.out.println(c.toString()); 과 같음
d.Shuffle(); // 카드를 섞음
c = d.pick(); // 섞은 후 카드를 랜덤으로 뽑음
System.out.println(c); // Diamond,9 . 값이 매번 바뀜
}
}
class Card{
static final int KIND_MAX = 4; // 카드 무늬 수
static final int NUM_MAX = 13; // 무늬별 카드 수
static final int SPADE = 4;
static final int DIAMOND =3;
static final int HEART = 2;
static final int CLOVER = 1;
int kind;
int number;
Card(int kind, int number){
this.kind = kind;
this.number = number;
}
Card(){
this(SPADE, 1);
}
public String toString(){
String[] kinds = {"", "Clover", "Heart", "Diamond", "Spade"};
String numbers = "0123456789XJQK"; // 숫자 10은 X로 표현
return kinds[this.kind] + "," + numbers.charAt(this.number);
}
}
class Deck{
final int CARD_NUM = 52; // 카드의 개수
Card[] cardArr = new Card[CARD_NUM]; // Card객체 배열
Deck(){ // Deck 카드 초기화
int i =0;
for(int k=Card.KIND_MAX; k>0; k--)
for(int j=1; j<=Card.NUM_MAX; j++) cardArr[i++] = new Card(k,j);
}
Card pick(int index){ // 지정된 위치(index)에 있는 카드 하나 꺼내서 반환
return cardArr[index];
}
Card pick(){ // Deck에서 카드 하나를 선택
int index = (int)(Math.random() * CARD_NUM);
return pick(index);
}
void Shuffle(){ // 카드의 순서를 섞음
for(int i=0; i<CARD_NUM; i++){
int r =(int)(Math.random()*CARD_NUM);
Card temp = cardArr[r];
cardArr[r] = cardArr[i];
cardArr[i] = temp;
}
}
}
◎ 단일상속
- 다른 객체지향언어인 C++에서는 여러 조상 클래스로부터 상속 받는것이 가능한 '다중상속'을 허용하지만 자바에서는 오직 단일 상속만 허용
- 클래스 간의 관계가 명확해지는 장점을 가짐
예제1
class Tv{
boolean power; // 전원상태 (on/off)
int channel; // 채널
void power() {power = !power;}
void channelUp() {++channel;}
void channelDown() {--channel;}
}
class VCR{
boolean power;
int counter=0;
void power() {power = !power;}
void play() {}
void stop() {}
void rew() {}
void ff() {}
}
class TVCR extends Tv{
VCR vcr = new VCR(); // VCR 클래스를 포함 시킴
void play(){
vcr.play();
}
void stop(){
vcr.stop();
}
void rew(){
vcr.rew();
}
void ff(){
vcr.ff();
}
}
다중상속을 허용하지 않으므로 Tv클래스를 조상으로하고, VCR 클래스는 TVCR 클래스에 포함시킴. TVCR클래스에 VCR클래스의 메서드와 일치하는 선언부를 가진 메서드를 선언하고 내용은 VCR 클래스의 것으로 호출함. 외부적으로 TVCR 클래스의 인스턴스를 사용하는 것처럼 보이지만 내부적으로는 VCR 클래스의 인스턴스를 생성해서 사용하는 것임
◎ Object 클래스 ㅡ 모든 클래스의 조상
- obejct 클래스는 모든 클래스 상속계층도의 최상위에 있는 조상클래스
- object 클래스는 모든 클래스의 조상클래스
- 다른 클래스로부터 상속 받지 않는 모든 클래스들은 자동적으로 object 클래스로부터 상속받음
◎ 오버라이딩(overriding)
- 오버라이딩이란, 조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것
- 오버라이딩 조건
- 자손 클래스에서 오버라이딩하는 메서드는 조상 클래스의 메서드와 이름이 같아야함
- 매개변수가 같아야함
- 반환타입이 같아야함
- 선언부가 서로 일치
- 접근 제어자를 조상 클래스의 메서드보다 좁은 범위로 변경 할 수 없음
- 예외는 조상 클래스의 메서드보다 많이 선언할 수 없음
- 인스턴스 메서드를 static 메서드로 또는 그 반대로 변경할 수 없음
- 접근 제어자(access modifier)와 예외(exception)는 제한된 조건 하에서만 다르게 변경 가능
- 접근 제어자는 조상클래스의 메서드보다 좁은 범위로 변경할 수 없음
- 만약 조상 클래스에 정의된 메서드의 접근 제어자가 protected라면, 이를 오버라이딩 하는 자손 클래스의 메서드는 접근 제어자가 protected나 public 이어야함.
- 대부분의 경우 같은 범위의 접근 제어자가 사용
- 접근 제어자 범위 : public > protected > (defualt) > private
- 조상 클래스의 메서드보다 많은 수의 예외를 선언할 수 없음
- 조상 클래스의 예외 개수 >= 자손 클래스의 예위 개수
- 접근 제어자는 조상클래스의 메서드보다 좁은 범위로 변경할 수 없음
- static 메서드는 오버라이딩이 불가능
- static 메서드는 클래스 단위 성격을 지니고, 오버라이딩은 객체 단위 성격을 지님
오버로딩 vs 오버라이딩
- 오버로딩 : 기존에 없는 새로운 메서드를 정의하는 것 (new)
- 오버라이딩 : 상속받은 메서드의 내용을 변경하는 것 (change, modify)
class Parent{
void parentMethod() {}
}
class Child extends Parent{
void parentMethod() {} // 오버 라이딩
void parentMethod(int i) {} // 오버로딩
void childMethod() {}
void childMethod(int i) {} // 오버로딩
}
◎ super
- super는 자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조 변수
- 멤버변수와 지역변수의 이름이 같을 때 this를 붙여서 구별했듯이 상속받은 멤버와 자신의 멤버와 이름이 같을 때는 super를 붙여서 구별할 수 있음
- 조상 클래스로부터 상속받은 멤버도 자손 클래스 자신의 멤버이므로 super 대신 this로 사용할 수 있음
- 조상 클래스의 멤버와 자손 클래스의 멤버가 중복 정의되어 서로 구별하는 경우에만 super를 사용할 것
- 조상의 멤버와 자신의 멤버를 구별하는데 사용된다는 점을 제외하고는 super와 this는 근본적으로 같음
- 모든 인스턴스메서드에서는 자신이 속한 인스턴스의 주소가 지역변수로 저장되는데, 이것이 참조변수인 this와 super의 값임
- this와 마찬가지로 super 역시 static 메서드에서 사용할 수 없고 인스턴스 메서드에서만 사용 가능
- 특히 조상클래스의 메서드를 자손 클래스에서 오버라이딩한 경우에 super를 사용할 것
예제1
class prac{
public static void main(String[] args){
Child c = new Child();
c.method();
}
}
class Parent {
int x = 10;
}
class Child extends Parent {
void method(){
System.out.println(x);
System.out.println(this.x);
System.out.println(super.x);
}
}
/* 출력값
10
10
10
*/
예제2
class prac{
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);
System.out.println(this.x);
System.out.println(super.x);
}
}
/* 출력값
20
20
10
*/
super.x 는 조상 클래스로부터 상속받은 멤버변수 x를 뜻함. this.x는 자손 클래스에 선언된 멤버변수를 뜻함
예제3
class Point{
int x;
int y;
String getLocation(){
return x + "," + y;
}
}
class Point3D extends Point {
int z;
String getLocation() { // 오버라이딩
return super.getLocation() + "," + z; // 조상의 메서드 호출
}
}
◎ super()
- super()는 this()와 마찬가지로 생성자임
- this()는 같은 클래스의 다른 생성자를 호출하는데 사용되지만, super()는 조상 클래스의 생성자를 호출되는데 사용
- 자손 클래스의 인스턴스를 생성하면, 자손의 멤버와 조상의 멤버가 모두 합쳐진 하나의 인스턴스가 생성됨. 그래서 자손 클래스의 인스턴스가 조산 클래스의 멤버들을 사용할 수 있음. 이 때 조상 클래스 멤버의 초기화작업이 수행되어야하기 때문에 자손 클래스의 생성자에서 조상 클래스의 생성자가 호출 되어야함
- 생성자의 첫 줄에서 조상 클래스의 생성자를 호출해야하는 이유는 자손 클래스의 멤버가 조상 클래스의 멤버를 사용할 수 있으므로 조상 멤버들이 먼저 초기화되어 있어야 함
- Object클래스를 제외한 모든 클래스의 생성자는 첫 줄에 반드시 자신의 다른 생성자 또는 조상의 생성자를 호출해야함. 그렇지 않으면 컴파일러는 생성자의 첫 줄에 'super();'를 자동적으로 추가 됨
class prac{
public static void main(String[] args){
Point3D p3 = new Point3D();
System.out.println(p3.x); // 100
System.out.println(p3.y); // 200
System.out.println(p3.z); // 300
}
}
class Point{
int x;
int y;
Point(int x, int y){
this.x = x;
this.y = y;
}
}
class Point3D extends Point{
int z;
Point3D(){
this(100, 200, 300); // Point3D(int x, int y, int z) 호출
}
Point3D(int x, int y, int z){
super(x,y); // Point(int x, int y) 호출
this.z = z;
}
}
◎ package
- 패키지란, 클래스의 묶음
- 패키지에는 클래스 또는 인터페이스를 포함시킬 수 있으며, 서로 관련된 클래스들끼리 그룹단위로 묶어 놓음으로써 클래스를 효율적으로 관리
- 하나의 소스파일에는 첫 번째 문장으로 단 한 번의 패지키 선언만 허용
- 모든 클래스는 반드시 하나의 패캐지에 속해야함
- 패키지는 점(.)을 구분자로 하여 계층구조로 구성할 수 있음
- 패키지는 물리적으로 클래스파일(.clss)을 포함하는 하나의 디렉토리
패키지 선언
- package 패키지명;
- 패키지 설치 파일 경로 : JDK설치경로\jre\lib
- 패키지 선언문은 반드시 소스파일에서 주석과 공백을 제외한 첫 번째 문장이어야함
- 하나의 소스파일에 단 한번만 선언 될 수 있음
- 패키지명은 대소문자를 모두 허용하지만, 클래스명과 쉽게 구분하기 위해서 소문자하는것을 원칙으로 함
- 패키지를 선언하지 않으면 자바에서 이름없는 패키지(default package)를 기본적으로 제공
- 간단한 프로그램은 패키지를 지정하지 않아도 별 문제 없지만, 큰 프로젝트나 Java API와 같은 클래스 라이브러리를 작성하는 경우에는 미리 패키지를 구성하여 적용해야 함
bin폴더
- 컴파일된 클래스 파일(*.class)이 있는곳
- 패키지 루트(패키지 시작폴더의 상위 폴더)
- 컴퓨터가 이해할 수 있는 코드
src 폴더
- src: 소스코드가 들어있음
- 사람이 이해할 수 있는 코드
패키지 파일로 가는법
- 이클립스에서 파일을 클릭하고 우클릭 > show in > system explorer
◎ 이클립스 없이 cmd로 java 실행시키기
-d 옵션
- -d 옵션은 소스파일에 지정된 경로를 통해 패키지의 위치를 찾아서 클래스파일을 생성케함
- 만일 지정된 패키지와 일치하는 디렉토리가 존재하지 않는다면 자동적으로 생성
- -d 옵션 뒤에 해당 패키지 루트 디렉토리의 경로를 적음
- javac -d [패키지 루트의 디렉토리 위치] [소스파일 위치주소/소스파일 명.java]
예제
package show.sh;
public class cococaca {
public static void main(String[] args) {
System.out.println("ww");
}
}
cococaca 소스 파일을 만듦 → cmd → cd [패키지 루트 디렉토리 위치] → java -d . [../src/cococaca.java] → 패키지 루트 디렉토리에 show 폴더 밑에 sh 폴더 밑에 소스파일 클래스가 생김
클래스패스(classpath)
- -d 옵션으로 패키지 디렉토리안에 클래스파일을 생성케 했으면 이제 이 데릭토리를 클래스패스에 포함시켜서 클래스파일을 찾을 수 있도록 해야함
- 클래스패스는 컴파일러(javac.exe)나 JVM 등이 클래스 위치를 찾는데 사용되는 경로
클래스패스 지정 방법 1
- 환경 변수 → 시스템 변수 → 새로 만들기 → 변수 이름 : CLASSPATH , 변수값 : .;[패지키루트디렉토리위치]
- 클래스패스 추가하려면 변수값에 .;[패지키루트 디렉토리위치];[패지키루트 디렉토리위치] 이런식으로 붙여줌
클래스 패스 지정 방법 2
- C:\windows>SET CLASSPATH .;[패지키루트 디렉토리위치];
- 클래스패스 위치 학인하는방법
- C:\windows>echo %classpath% → .;[패지키루트 디렉토리위치]; 가 나옴
- %classpath% 가 나오면 클래스패스 지정이 안된것
- C:\windows>echo %classpath% → .;[패지키루트 디렉토리위치]; 가 나옴
클래스 패스 지정 방법 3
- -cp 옵션을 이용해서 일시적으로 클래스패스를 지정할 수 있음
- C:\windows>java -cp [패키지루트 디렉토리위치] [상위패키지폴더.하위패키지폴더.소스파일명]
◎ import문
- 소스코드를 작성할 때 다른 패키지의 클래스를 사용하려면 패키지명이 포함된 클래스 이름을 사용해야함. 그러나 import문을 사용하면 소스코드에 사용되는 클래스이름에서 패키지명을 생략 가능
- import문 역할은 컴파일러에게 소스파일에 사용된 클래스의 패키지에 대한 정보를 제공하는 것
- 'ctrl + shift + o' 누르면 자동으로 import문을 추가해줌
- import문을 많이 사용하면 컴파일 시간이 아주 조금 더 걸림
- java.lang은 자동으로 추가되는 패키지
import문 선언
- 모든 소스파일(.java)에서 import문은 package 문 다음에, 그리고 클래스 선언문 이전에 위치해야함
- import문은 package문과 달리 한 소스파일에 여러 번 선언 가능
- 일반적인 소스 파일 구성
- ① package문
- ② import문
- ③ 클래스 선언
- import문 선언 2가지 방법
- import 패키지명.클래스명;
- import 패키지명.*;
예제1
import java.util.Date;
import java.text.SimpleDateFormat;
class prac{
public static void main(String[] args){
Date today = new Date();
SimpleDateFormat date = new SimpleDateFormat("yyyy/MM//dd");
SimpleDateFormat time = new SimpleDateFormat("hh:mm:ss a");
System.out.println(date.format(today)); // 2021/10//19 . 현재 날짜
System.out.println(time.format(today)); // 09:16:44 오후 . 현재 시각
}
}
static import문
- static import문을 사용하면 static 멤버(static 메서드, static 변수)를 호출할 때 클래스 이름 생략 가능
- 특정 클래스의 static 멤버를 자주 사용할 때 편리
- static import문을 사용하면 코드가 간결해져서 좋지만 어떤 클래스의 static멤버를 쓰는건지 모르므로 꼭 필요한 경우 아니면 안쓰는걸 권장
예제1
import static java.lang.System.out;
import static java.lang.Math.*;
class prac{
public static void main(String[] args){
// System.out.println(Math.random());
out.println(random()); // 0.5073103325445774 . 랜덤 수
// System.out.println(Math.PI);
out.println(PI); // 3.141592653589793
}
}
◎ 제어자 (modifier)
- 제어자는 클래스, 변수 또는 메서드의 선언부에 함께 사용되어 부가적인 의미 부여
- 제어자 종류는 크게 접근 제어자와 그외의 제어자로 나눌 수 있음
- 제어자는 클래스나 멤버변수와 메서드에 주로 사용
- 하나의 대상에 대해서 여러 제어자를 조합하여 사용 가능. 단, 접근 제어자는 4가지 중 하나만 사용할 수 있음
- 제어자들 간의 순서는 관계 없지만 주로 접근 제어자를 제일 먼저 씀
◎ 그 외 제어자
static ㅡ 클래스의, 공통적인
- static이란 '클래스의' 또는 '공통적인' 의미
- static이 붙은 멤버변수와 메서드, 그리고 초기화 블럭은 인스턴스가 아닌 클래스에 관계 된 것
- 인스턴스 메서드와 static 메서드의 근본적 차이는 메서드 내에서 인스턴스 멤벌르 사용하는가 여부에 있음
- 인스턴스 멤버를 사용하지 않는 메서드는 static을 붙일 것. 인스턴스 메서드보다 static 메서드가 편리하고 속도도 더 빠름
class prac{
static int width = 200; // 클래스 변수 (static 변수)
static int height = 120; // // 클래스 변수 (static 변수)
static { // 클래스 초기화 블럭
// static 변수의 복잡한 초기화 수행
}
static int max (int a, int b){ // 클래스 메서드(static메서드
return a>b ? a: b;
}
}
final ㅡ 마지막의, 변경될 수 없는
- final이란, '마지막의' 또는 '변경될 수 없는' 의미
- 거의 모든 대상에 사용 가능
- 변수에 사용하면 값 변경이 안되는 상수가 됨
- 메서드에 사용하면 오버라이딩을 할 수 없음
- 클래스에 사용하면 조상 클래스가 될 수 없음
- 대표적으로 final 클래스로는 String과 Math가 있음
fianl class prac { // 조상이 될 수 없는 클래스
final int MAX_SIZE = 10; // 값을 변경할 수 없는 멤버변수(상수)
fianl void getMaxSize () { // 오버라이딩을 할 수 없는 메서드(변경불가)
final int LV = MAX_SIZE; // 값을 변경할 수 없는 지역변수(상수)
return MAX_SIZE;
}
}
생성자를 이용한 final 멤버 변수 초기화
- final이 붙은 변수는 상수이므로 일반적으로 선언과 초기화를 동시에 하지만, 인스턴스 변수의 경우 생성자에서 초기화 되도록 할 수 있음
- 클래스 내에 매개변수를 갖는 생성자를 선언하여, 인스턴스를 생성할 때 final이 붙은 멤버변수를 초기화하는데 필요한 값을 생성자의 매개변수로 받을 것
class prac{
public static void main(String[] args){
Card c = new Card("Spade", 7);
// c.NUMBER = 5; // 에러. NUMBER는 final이 붙은 상수이므로 값 변경 불가능
System.out.println(c.NUMBER); // 7
System.out.println(c.KIND); // Spade
System.out.println(c); // Spade 7
}
}
class Card{
final int NUMBER;
final String KIND;
static int width= 100;
static int height = 250;
Card(String kind, int num){
KIND = kind;
NUMBER = num;
}
Card(){
this("Heart", 1);
}
public String toString() {return KIND + " " + NUMBER;}
}
abstract ㅡ 추상의, 미완성의
- abstract이란 '미완성' 의미
- 메서드의 선언부만 작성하고 실제 수행내용은 구현하지 않은 추상 메서드로 쓰임
- 클래스에 사용하여 클래스 내에 추상 메서드가 존재한다는 것을 쉽게 알수있도록 표기
- 추상 클래스는 아직 완성되지 않은 메서드가 존재하는 '미완성 설계도' 이므로 인스턴스 생성 불가능
◎ 접근 제어자 (access modifier)
- 접근 제어자는 멤버 또는 클래스에 사용되어, 해당하는 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한
- 클래스, 멤버변수, 메서드, 생성자에 접근 제어자가 지정되어 있지 않다면, 접근 제어자가 default 임을 뜻함
- 하나의 소스파일(*.java)에는 public 클래스가 단 한개만 존재할 수 있음. 소스파일의 이름은 반드시 public 클래스의 이름과 같아야함
- 접근 제어자 범위
- public > protected > (defalut) > private
- 접근 제어자를 사용하는 이유
- 외부로부터 데이터 보호
- 외부에는 불필요한, 내부적으로만 사용되는 부분을 감춰서 복잡성을 줄이기 위해
예제1
public class prac{
public static void main(String[] args){
Time t = new Time(12, 40, 35);
System.out.println(t); // 12 : 40 : 35
// t.hour = 10; // 에러. 변수 hour의 접근 제어자가 private 이므로 접근 불가능
t.setHour(t.getHour() + 1);
System.out.println(t); // 13 : 40 : 35
}
}
class Time{
// 접근 제어자를 private으로 하여 외부에서 직접 접근하지 못하게 막음
private int hour, minute, second;
Time(int hour, int minute, int second){
setHour(hour);
setMinute(minute);
setSecond(second);
}
public int getHour() {return hour;}
public void setHour(int hour) {
if(hour < 0 || hour > 23) return;
this.hour = hour;
}
public int getMinute() {return minute;}
public void setMinute(int minute){
if(minute < 0 || minute > 59) return;
this.minute = minute;
}
public int getSecond() {return second;}
public void setSecond(int second) {
if(second < 0 || second > 59) return;
this.second = second;
}
public String toString(){
return hour + " : " + minute + " : " + second;
}
}
- Time 클래스의 모든 멤버변수의 접근 제어자를 pricate으로 하고, 이들을 다루기 위해서 public 메서드를 추가함.
- 't.hour = 13;' 과 같이 멤버변수로의 직접적인 접근을 불허. 메서드를 통해 간접적 접근만 허용
- 보통 멤버변수의 값을 읽는 메서드의 이름을 'get멤버변수이름'으로 하고, 멤버변수의 값을 변경하는 메서드의 이름을 'set멤버변수이름' 으로 함
- get으로 시작하는 메서드는 '게터(getter)', set으로 시작하는 메서드는 '셋터(setter)'라고부름
생성자의 접근 제어자
- 생성자에 접근제어자를 사용함으로써 인스턴스의 생성 제한 가능
- 생성자의 접근 제어자를 private으로 지정하면 외부에서 생성자에 접근할 수 없으므로 인스턴스 생성 불가능. 그러나 클래스 내부에서는 인스턴스 생성 가능
- 생성자가 private인 클래스는 다른 클래스의 조상이 될 수 없음
- 자손 클래스의 인스턴스를 생성할 때 조상 클래스의 생성자를 호출해야만 하는데, 생성자의 접근 제어자가 private이므로 자손 클래스에서 호출하는것이 불가능하기 때문
- 생성자가 private인 경우 클래스 앞에 final 을 추가해서 상속할 수 없는 클래스라는걸 표기할 것
final class Singleton{
//getInstance() 에서 사용될 수 있도록 인스턴스가 미리 생성되어야 하므로 static 이어야함
private static Singleton s = new Singleton();
private Singleton() {}
// 인스턴스를 생성하지 않고도 호출할 수 있어야하므로 static이어야함
public static Singleton getInstance() {
return s;
}
}
- 이처럼 생성자를 통해 직접 인스턴스를 생성하지 못하게 하고 public 메서드를 통해 인스턴스에 접근하게 함으로써 사용할 수 있는 인스턴스의 개수 제한 가능
예제1
class prac{
public static void main(String[] args){
// Singleton s = new Singleton(); // 에러. Singleton()의 접근제어자가 private임
Singleton s = Singleton.getInstance();
}
}
final class Singleton{
private static Singleton s = new Singleton();
public static Singletone getInstance(){
if(s == null) s = new Singleton();
return s;
}
}
제어자의 조합
- 메서드에 static과 abstract를 함께 사용 못함
- static 메서드는 몸통이 있는 메서드에만 사용할 수 있기 때문
- 클래스에 abstract와 final을 동시에 사용 못함
- final은 클래스에서 확장할 수 없다는 의미이고, abstract는 상속을 통해서 완성되어야 한다는 의미로 서로 모순
- abstract메서드의 접근 제어자가 private일 수 없음
- abstract 메서드는 자손클래스에서 구현해주어야하는데 접근 제어자가 private이면, 자손클래스에서 접근할 수 없기 때문
- 메서드에 private와 final을 같이 사용할 필요 없음
- 접근 제어자가 private인 메서드는 오버라이딩 될 수 없기 때문에 둘 중 하나만 써도 됨
◎ 다형성
- 다형성이란 '여러가지 형태를 가질 수 있는 능력'을 의미
- 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 하는 것
- 조상타입의 참조변수로 자손타입의 객체를 다룰 수 있는 것
- 조상타입의 참조변수로 자손타입의 객체를 다룰 시 자손클래스의 멤버변수를 사용 못함
- 반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수 없음
- 다형성 장점
- 다향적 매개변수
- 하나의 배열로 여러 종류 객체 다루기
참조변수의 형변환
- 사용할 수 있는 멤버의 개수를 조절하는 것
- 조상 자손관계의 참조변수는 서로 형변환 가능
- 참조변수간의 형변환시 캐스트연산자를 사용하며, 괄호() 안에 변환하고하는 타입의 이름(클래스명)을 적음
- ex) 자손인스턴스명 = (자손클래스명)조상인스턴스명 // 형변환 생략 불가능
- ex) 조상인스턴스명 = (조상클래스명)자손인스턴스명 // 형변환 생략 가능
- 형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것이 아니기에 참조변수의 형변환은 인스턴스에 아무련 영향을 미치지 않음
- 형변환시 인스턴스의 타입이 무엇인지 중요(자손타입이어야함)
예제1
class prac{
public static void main(String[] args){
Car car = null;
FireEngine fe = new FireEngine();
FireEngine fe2 = null;
fe.water(); // water
car = fe; // car = (Car)fe; 에서 형변환 생략된 형태
// car.water(); // 에러. Car타입의 참조변수로는 water()를 호출할 수 없음
fe2 = (FireEngine)car; // 조상 → 자손 형변환
fe2.water(); // water
}
}
class Car{
String color;
int door;
void drive() {System.out.println("drive");}
void stop() {System.out.println("stop");}
}
class FireEngine extends Car{
void water() {System.out.println("water");}
}
예제2
class prac{
public static void main(String[] args){
Car car = new Car();
Car car2 = null;
FireEngine fe = null;
car.drive(); // drive
fe = (FireEngine)car; // 컴파일은 에러표시 없음. 그러나 실행 시 에러 발생
fe.drive();
car2 = fe;
car2.drive();
}
}
class Car{
String color;
int door;
void drive() {System.out.println("drive");}
void stop() {System.out.println("stop");}
}
class FireEngine extends Car{
void water() {System.out.println("water");}
}
- 'fe = (FireEngine)car;' 여기서 에러가난 이유는 형변환 오류 때문임. 캐스트 연산자를 이용해서 조상타입의 참조변수를 자손타입의 참조변수로 형변환하는 것이기 때문에 문제가 없어보이지만, 문제는 참조변수 car가 잠조하고 있는 인스턴스가 Car 타입의 인스턴스라는데 있음
- 컴파일 시에는 참조변수간의 타입만 체크하기 때문에 실행 시 생성될 인스턴스의 타입에 대해서 알지 못함. 그래서 컴파일 시에는 문제가 없었지만 실행 시 에러가 발생함
- 서로 상속관계에 있는 타입간의 형변환은 양방향으로 자유롭게 수행될 수 있으나, 참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않음
- 'Car car = new Car();' 를 'Car car = new FireEngine();' 으로 변경하면 컴파일할 때 뿐 만 아니라 실행할 때도 에러가 발생하지 않음
instanceof 연산자
- 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof 연산자 사용
- 형변환 가능여부를 알기 위해 instanceof 연산자 사용
- 이항연산다이며 연산 결과는 boolean 값인 true와 false 중 하나를 반환하므로 주로 조건문에 사용
- '참조변수 instanceof 클래스명'
- 값이 null인 참조변수에 대해 instanceof 연산을 수행하면 false 결과가 나옴
- instanceof를 이용한 연산결과로 true를 얻었다는 것은 참조변수가 검사한 타입으로 형변환이 가능하다는 것
예제1
class prac{
public static void main(String[] args){
FireEngine fe = new FireEngine();
if (fe instanceof FireEngine) System.out.println("f FireEngine"); // f FireEngine
if (fe instanceof Car) System.out.println("f Car"); // f Car
if (fe instanceof Object) System.out.println("f Object"); // f Object
System.out.println(fe.getClass().getName()); // FireEngine
}
}
class Car{}
class FireEngine extends Car{}
- '참조변수.getClass().getName()' 은 참조변수가 가리키고 있는 인스턴스의 클래스 이름을 문자열(String)로 반환
참조변수와 인스턴스의 연결
- 멤버변수가 조상 클래스와 자손 클래스에 중복으로 정의된 경우. 조상타입의 참조변수를 사용 했을 때는 조상 클래스에 선언된 멤버변수가 사용되고, 자손타입의 참조변수를 사용 했을 때는 자손 클래스에 선언된 멤버변수가 사용됨
- 메서드의 경우 조상 클래스의 메서드를 자손의 클래스에서 오버라이딩한 경우에도 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드(오버라이딩된 메서드)가 호출되지만, 멤버변수의 경우 참조변수의 타입에 따라 달라짐
- 참조변수의 타엡에 따라 결과가 달라지는 경우는 조상 클래스의 멤버변수와 같은 이름의 멤버변수를 자손 클래스에 중복해서 정의한 경우뿐
예제1
class prac{
public static void main(String[] args){
Parent p = new Child();
Child c = new Child();
System.out.println(p.x); // 100
p.method(); // Child Method
System.out.println(c.x); // 200
c.method(); // Child Method
}
}
class parent{
int x = 100;
void method() {
System.out.println("Parent Method");
}
}
class Child extends Parent{
int x = 200;
void method() {
System.out.println("Child Method");
}
}
- 메서드인 method()의 경우 참조변수의 타입에 관계없이 항상 실제 인스턴스의 타입인 Child 클래스에 정의된 메서드가 호출
- 인스턴스 변수인 x는 참조변수의 타입에 따라 달라짐
예제2
class prac{
public static void main(String[] args){
Parent p = new Child();
Child c = new Child();
System.out.println(p.x); // 100
p.method(); // Parent Method
System.out.println(c.x); // 100
c.method(); // Parent Method
}
}
class Parent{
int x = 100;
void method() {
System.out.println("Parent Method");
}
}
class Child extends Parent {}
- Child 클래스에는 아무런 멤버도 정의되어 있지 않고 단순히 조상으로부터 멤버들을 상속 받음. 그래서 참조변수의 타입에 관계없이 조상의 멤버들을 사용
예제3
class prac{
public static void main(String[] args) {
Parent p = new Child();
Child c = new Child();
System.out.println("p.x = " + p.x);
p.method();
System.out.println();
System.out.println("c.x = " + c.x);
c.method();
}
}
class Parent{
int x = 100;
void method() {
System.out.println("Parent Method");
}
}
class Child extends Parent{
int x = 200;
void method() {
System.out.println("x = " + x); // x 는 thi.s 와 같음
System.out.println("super = " + super.x);
System.out.println("this.x = " + this.x);
}
}
/* 출력값
p.x = 100
x = 200
super = 100
this.x = 200
c.x = 200
x = 200
super = 100
this.x = 200
*/
매개변수의 다형성
- 참조형 매개변수는 메서드 호출시, 자신과 같은 타입 또는 자손타입의 인스턴스를 넘겨 줌
예제1
class Product{
int price; // 제품 가격
int BonusPoint; // 제품구매 시 제공하는 보너스 포인트
Product(int price){
this.price = price;
BonusPoint = (int)(price/10.0);
}
}
class Tv extends Product{
Tv() {super(100);} // Tv 가격을 100으로 함
public String toString(){return "Tv";}
}
class Computer extends Product{
Computer() {super(200);}
// Object 클래스의 toString()을 오버라이딩
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 prac{
public static void main(String[] args){
Buyer b = new Buyer();
b.buy(new Tv()); // Tv을/를 구입
b.buy(new Computer()); // Computer을/를 구입
System.out.println(b.money); // 700
System.out.println(b.BonusPoint); // 30
}
}
여러 종류의 객체를 배열로 다루기
- 조상타입의 배열에 자손들의 객체를 담을 수 있음
- 조상타입의 참조변수 배열을 사용하면, 공통의 조상을 가진 서로 다른 종류의 객체를 배열로 묶을 수 있음
예제1
class Product{
int price; // 제품 가격
int bonus_point; // 제품구매 시 보너스 점수
Product(int price){
this.price = price;
bonus_point = (int)(price/10.0);
}
}
class Tv extends Product{
Tv() {super(100);}
public String toString() {return "Tv";}
}
class Computer extends Product{
Computer() {super(200);}
public String toString() {return "Computer";}
}
class Audio extends Product{
Audio() {super(50);}
public String toString() {return "Audio";}
}
class Buyer{
int money = 1000; // 소유 금액
int point = 0; // 보너스 점수
Product[] item = new Product[10]; // 구입한 제품을 저장하기 위한 배열
int i=0; // Product 배열에 사용될 카운터
void buy(Product p){
if(money < p.price) {
System.out.println("잔액부족");
return;
}
money -= p.price;
point += p.bonus_point;
item[i++] = p;
System.out.println(p + " 구입");
}
void summary() { // 구매한 물품에 대한 정보 요약
int sum=0; // 구입한 물품 가격 합계
String itemList= ""; // 구입한 물품목록
for(int i=0; i<item.length; i++){
if(item[i] == null) break;
sum += item[i].price;
itemList+= item[i] + ", ";
}
System.out.println("구입한 총 금액 : " + sum);
System.out.println("구입한 제품 : " + itemList);
}
}
class prac{
public static void main(String[] args){
Buyer b = new Buyer();
b.buy(new Tv());
b.buy(new Computer());
b.buy(new Audio());
b.summary();
}
}
Vector
- 모든 종류의 객체들을 저장할 수 있는 클래스
- java.util 패키지 소속
- 사용시 import java.util.*; 써줘야함
- 내부적으로 Object 타입의 객체배열을 가지고 있어서 어떤 종류의 객체도 담을 수 있음
- Vector클래스의 기본 생성자를 이용하면 기본적으로 10개의 객체를 저장할 수 있는 인스턴스 생성. 10개를 초과시 자동적으로 배열 크기가 늘어남
Vetoer 클래스 메소드
예제1
import java.util.*;
class Product{
int price; // 제품 가격
int point; // 제품 구매시 보너스 점수
Product(int price){
this.price = price;
point = price/10;
}
}
class Tv extends Product{
Tv(){super(200);}
public String toString(){return "Tv";}
}
class Audio extends Product{
Audio(){super(150);}
public String toString(){return "Audio";}
}
class Computer extends Product{
Computer(){super(100);}
public String toString(){return "Computer";}
}
class Buyer { // 고객
int money = 1000; // 보유금액
int b_point = 0; // 소유 보너스 점수
Vector item = new Vector(); // 구입한 제품을 저장하는데 사용될 Vector 객체
String itemList = "";
int sum = 0;
void buy(Product p){
money -= p.price;
b_point += p.point;
item.add(p); // 구입한 제품을 Vector 객체에 저장
System.out.println(p + "을/를 구입");
}
void refund(Product p) {
if(item.remove(p)){ // 제품을 Vector 객체에 제거
money += p.price;
b_point -= p.point;
System.out.println(p + "을/를 반품");
} else {
System.out.println("해당 물품 구입한 적 없음");
}
}
void summary(){
if(item.isEmpty()) {
System.out.println("구입 물품 없음");
return;
}
for(int i=0; i<item.size(); i++){
Product k = (Product)item.get(i);
itemList += (i==0) ? "" + k : ", " + k;
sum += k.price;
}
System.out.println("구입한 물품 : " + itemList);
System.out.println("물품 총액 : " + sum);
}
}
class prac{
public static void main(String[] args){
Buyer b = new Buyer();
Computer com = new Computer();
Audio au = new Audio();
Tv t = new Tv();
b.buy(com);
b.buy(au);
b.buy(t);
b.refund(t);
b.summary();
}
}
/* 출력값
Computer을/를 구입
Audio을/를 구입
Tv을/를 구입
Tv을/를 반품
구입한 물품 : Computer, Audio
물품 총액 : 250
*/
추상클래스(absctract class)
- 클래스를 설계도에 비유한다면, 추상 클래스는 미완성 설계도
- 클래스가 미완성이라는 것은 멤버의 개수에 관련없이, 단지 미완성메서드(추상메서드)를 포함하고 있다는 것
- 다른 클래스(자손 클래스)를 작성하는데 도움을 줄 목적으로 사용됨
- 클래스 선언부에 'abstract' 붙이면 됨
- 추상클래스는 추상메서드가 있으니 상속을 통해서 구현해주어야한 다는 것을 의미
- 추상클래스는 추상메서드를 포함하고 있다는 것 제외하고 일반 클래스와 동일
- 일반 클래스와 동일하게 생성자도 있으며, 멤버변수와 메서드도 가질 수 있음
- 추상메서드가 없어도 추상클래스로 지정 가능
- 추상클래스로 지정되면 클래스의 인스턴스를 생성할 수 없음
- 상속이 자손 클래스를 만드는데 조상 클래스를 사용했다면, 이와 반대로 추상화는 기존의 클래스(자손 클래스)의 공통부분을 뽑아내서 조상 클래스를 만드는 것
추상메서드(abstract method)
- 선언부만 작성하고 구현부가 없는 메소드
- 메서드를 미완성상태로 남겨놓는 이유는 메서드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문
- 꼭 필요하지만 자손마다 다르게 구현 될 것으로 예상되는 경우 사용
- 조상 클래스의 추상메서드에서는 선언부만 작성하고, 주석을 덧붙여 어떤 기능을 수행할 목적으로 작성 되었는지 알려주고, 실제 내용은 상속받는 클래스에서 구현하도록 비워 두는것
- 추상메서드에서도 선언부에 'abstract' 를 붙여야함
- 추성메서드는 구현부가 없으므로 괄호{} 대ㅔ신 문장의 끝을 알리는 ';' 를 적음
- 일반 메서드에서 추상 메서드 호출 가능
/* 주석을 통해 어떤 기능을 수행할 목적으로 작성했는지 설명 */
abstract 리턴타입 메서드이름();
abstract class Player{
// 플레이 기능
abstract void play(int pos); // 추상메서드
// 멈춤 기능
abstract void stop(); // 추상메서드
}
class AudioPlayer extends Player{
void play(int pos) {/* 내용생략 */}
void stop() {/*내용생략 */}
}
abstract class AbstractPlayer extends Player{
void play(int pos) {/* 내용 생략 */}
}
추상메서드와 빈 메서드의 차이
조상클래스의 빈 메서드를 자손클래스에서 오버라이딩하여 자신의 클래스에 맞게 구현하면 추상메서드로 선언하는 것과 별 차이 없어보일 수 있음. 그래도 abstract를 붙여서 추상 메서드로 선언하는 이유는 자손 클래스에서 추상메서드를 반드시 구현하도록 강요하기 위한 것
만일 추상메서드로 정의하지 않고 빈 메서드로 정의한다면, 상속 받는 자손 클래스에서는 이 메서드들이 온전히 구현된 것으로 인식하여 오버라이딩을 통해 자신의 클래스에 맞도록 구현하지 않을 수 있기 때문
반면 abstract를 사용해서 추상메서드로 정의하면, 자손 클래스를 작성할 때 내용을 구현해주어야한 다는 것을 인식하고 자신의 클래스에 맞게 구현 할 것
abstract void play(); // 추상 메서드
void stop(){} // 빈 메서드
예제1
abstract class Unit{
int x, y;
abstract void move(int x, int y); // 가로로 x쪽 만큼, 세로로 y만큼 이동하는 기능
}
class Marine extends Unit{
void move(int x, int y){
System.out.println("" + x + ", " + y);
}
void stimPack() {/* 내용생략 */}
}
class Tank extends Unit{
void move(int x, int y) {
System.out.println("" + x + ", " + y);
}
void changeMode(){/* 내용생략 */}
}
class Dropship extends Unit{
void move(int x, int y){
System.out.println("" + x + ", " + y);
}
void load() {/* 내용생략 */}
void unload() {/* 내용생략 */}
}
class prac{
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);
}
}
}
/* 출력값
100, 200
100, 200
100, 200
*/
인터페이스 (interface)
- 인터페이스는 추상메서드의 집합 (상수를 가질수도 있지만 본질은 추상메서드의 집합)
- 인터페이스는 일종의 추상 클래스
- 인터페이스 일반메서드 또는 멤버변수를 가지지 못함
- 오직 추성메서드와 상수만 멤버로 가질 수 있음. 그 외 어떤 다른 요소도 가질 수 없음
- 추상클래스가 부분적으로만 완성된 미완성 설계도라면 인터페이스는 구현된 것이 아무것도 없고 밑그림만 그려져 있는 기본 설계도
- 인스턴스를 생성할 수 없고, 클래스 작성에 도움을 줄 목적으로 사용
- 미리 정해진 규칙에 맞게 구현하도록 표준 제시
- 접근 제어자로 'public' 또는 'defalut' 만 사용 가능
- 인터페이스도 클래스처럼 상속 가능함. 클래스와 달리 다중상속 허용
- 인터페이스는 Object 클래스처럼 최고 조상이 없음
- 일부로 결합도가 낮은 코드를 만들어 협업과 유지보수가 용이해지도록 만드는 것
- 인터페이스를 상속받는 클래스는 인터페이스의 추상메소드를 다 구현해야하지만 일부만 구현하고 싶다면 클래스 앞에 abstract를 붙이고 추상 메소드로 남겨둘 것
- 인터페이스 이름을 정할 때 '~able' 로 하는것이 일반적
- 인터페이스가 어떤 기능 또는 행위를 하는데 필요한 메서드를 제공한다는 의미를 강조하기 위함
- 인터페이스를 구현한 클래스는 ~를 할 수 있는 능력을 갖추었다는 의미이기도 함
인터페이스 조건
■ 모든 멤버변수는 'public static fianl' 이어야 하며, 이를 생략할 수 있음
┖ 인터페이스 변수는 아무 인스턴스도 존재하지 않는 시점이기 때문에 스스로 초기화 될 권한이 없음. 그래서 public static final 를 사용해 구현 객체의 같은 상태를 보장함
■ 모든 메서드는 'public abstract' 이어야 하며, 이를 생략할 수 있음
■ JDK1.8부터 인터페이스에 추상메서드 뿐만 아니라 static 메서드와 디폴트 메서드를 사용할 수 있게 됨
┖ 이유: 인터페이스에 새로운 추상메서드를 추가할 때 기존에 인터페이스를 상속받고 있는 클래스들에서 추상메소드를 다 구현 해줘야하기 때문에 새로운 추상 메서드를 추가하기 어려웠음. 그래서 인터페이스에 디폴트 메서드를 예외로 쓸 수 있게 하여 새로운 메소드 추가 가능
디폴트 메서드 사용시 'default' 를 적어줘야함 생략 불가능
┖ 인터페이스에 디폴트 메소드 사용시 기존 메서드와 충돌할 때 해결 책 : 그냥 오버라이딩 할 것
① 여러 인터페이스의 디폴트 메서드간의 중복 문제 → 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야 함
② 디폴트 메서드와 조상 클래스의 메서드간의 충돌 → 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시됨
인터페이스 상속
- 인터페이스 조상은 인터페이스만 가능, 클래스와 달리 다중 상속 가능
인터페이스 구현
- 인터페이스의 추상 메서드 몸통{} 만들기 (= 추상메서드가 상속을 통해 추상 메서드를 완성하는 것과 같은 개념)
- 인터페이스 구현 = 클래스 상속
- extends 대신 'implements' 를 사용
- 만약 인터페이스 메소드를 구현하는 클래스에서 일부만 구현하고자 한다면 클래스 선언부에와 메소드 선언부에 'abstract' 를 붙여서 추상 클래스로 선언 해야함
- 상속과 구현 동시에 가능
- 인터페이스 구현시 메서드를 오버라이딩 할 때 'public' 을 붙여야함. 인터페이스의 추상 메소드가 public 메소드 이기 때문
예제1
class Unit{
int currentHp, x, y; // 유닛 체력, 유닛 위치(x좌표), 유닛 위치(y좌표)
}
interface Movable{
void move(int x, int y);
}
interface Attackable{
void attack(Unit u);
}
interface Fightable extends Movable, Attackable{
}
class Fighter extends Unit implements Fightable{
public void move(int x, int y){ } // 오버라이딩할 때 조상 메서드보다 넓은 범위의 접근 제어자 지정
public void attack(Unit u) { } // 인터페이스 메소드가 public 이기 때문에 오버라이딩할 때 public 적어야함
}
class prac{
public static void main(String[] args){
Fighter f = new Fighter();
if(f instanceof Unit) System.out.println("f는 Unit 클래스 자손");
if(f instanceof Fightable) System.out.println("f는 FFightable 인터페이스 구현");
if(f instanceof Movable) System.out.println("f는 Movable 인터페이스 구현");
if(f instanceof Attackable) System.out.println("f는 Attackable 인터페이스 구현");
if(f instanceof Object) System.out.println("f는 Object 클래스 자손");
}
}
/*출력값
f는 Unit 클래스 자손
f는 FFightable 인터페이스 구현
f는 Movable 인터페이스 구현
f는 Attackable 인터페이스 구현
f는 Object 클래스 자손
*/
인터페이스를 이용한 다중상속
- 인터페이스를 통해서 다중상속을 할 수 있지만 인터페이스를 이용한 다중상속을 구현하는 경우는 많이 없음
- 인터페이스는 static 상수만 정의할 수 있으므로 조상클래스의 멤버변수와 충돌하는 경우가 거의 없고 충돌된다 하더라도 클래스이름을 붙여서 구분 가능
- 인터페이스의 추상메서드는 구현내용이 전혀 없으므로
예제1
class Tv{
protected boolean power;
protected int channel;
protected int volume;
public void power() {power = !power;}
public void channelUp() {channel++;}
public void channelDown() {channel--;}
public void volumeUp() {volume++;}
public void volumeDown() {volume--;}
}
class VCR{
protected int counter; // VCR의 카운터
public void play() {}
public void stop() {}
public void reset() {}
public int getCounter(){return counter;}
public void setCounter(int c) {counter = c;}
}
interface IVCR{
void play();
void stop();
void reset();
void getCounter();
void setCounter(int c);
}
class TVCR extends Tv implements IVCR{
VCR vcr = new VCR();
public void play() { vcr.play();} // VCR 인스턴스 메서드 호출
public void stop() {vcr.stop();}
public void reset() {vcr.reset();}
public void getCounter() {vcr.getCounter();}
public void setCounter(int c) {vcr.setCounter(c);}
}
class prac{
public static void main(String[] args){
}
}
① TVCR 클래스를 만들 때 Tv 클래스와 VCR 클래스 중 관련이 높은 쪽인 Tv 클래스를 조상 클래스로 지정함.
② 이후 VCR 인터페이스를 VCR 클래스 메소드와 동일하게 만듦
③ TVCR 클래스 안에서 VCR 클래스 인스턴스를 생성해서 사용할 수 있도록 함
④ VCR클래스 타입의 참조변수를 멤버변수로 선언하여 IVCR 인터페이스의 추상 메서드를 구현하는데 사용
이처럼 TVCR 클래스에 VCR클래스의 인스턴스를 사용해서 IVCR 인터페이스를 구현함으로써 다중상속 구현
IVCR 인터페이스를 새로 만들지 않고 VCR 클래스를 TVCR 클래스에 포함시키는 것만으로도 충분하지만, 인터페이스를 이용할 시 다형적 특성을 이용할 수 있다는 장점이 있음
인터페이스를 이용한 다형성
- 인터페이스를 클래스 조상으로 여길 수 있으므로 인터페이스 타입의 참조변수로 구현한 클래스의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로 형변환도 가능
- 인터페이스 타입의 매개변수가 갖는 의미는 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공한다는 것
- 메서드의 리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것
예제
abstract class Unit{
int x, y;
abstract void move(int x, int y);
void stop(){
System.out.println("멈춰");
}
}
interface Fightable{
void move(int x, int y);
void attack(Unit u);
}
class Fighter extends Unit implements Fightable{
public void move(int x, int y){
System.out.println(""+ x + ", " + y + "이동");
}
public void attack(Unit u){
System.out.println(u + " 공격");
}
// 메서드의 리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것
Fightable getFightable(){
return new Fighter();
}
public String toString(){return "Fighter";}
}
class prac{
public static void main(String[] args){
Fighter f = new Fighter();
f.move(100, 200); // 100, 200이동
f.attack(f); // Fighter 공격
f.stop(); // 멈춰
Unit u = new Fighter();
u.move(150, 250); // 150, 250이동
u.stop(); // 멈춰
}
}
예제2
interface Parseable{
void parse(String fileName);
}
class ParserManager{
public static Parseable getParser(String type){
if(type.equals("XML")) return new XMLParser();
else return new HTMLParser();
}
}
class XMLParser implements Parseable{
public void parse(String fileName){
System.out.println(fileName);
}
}
class HTMLParser implements Parseable{
public void parse(String fileName){
System.out.println(fileName);
}
}
class prac {
public static void main(String[] args){
Parseable parser = ParserManager.getParser("XML");
parser.parse("document.xml"); // document.xml
parser = ParserManager.getParser("HTML");
parser.parse("document.html"); // document.html
}
}
인터페이스 장점
- 개발시간 단축
- 메서드 선언부만 알면 협업시 동시 개발 가능
- 변경에 유리한 유연한 설계 가능
- 표준화 가능
- 프로젝트 기본틀을 인터페이스로 작성함으로서 일관되고 정형화된 프로그램 개발 가능
- 서로 관계없는 클래스들에게 관계를 맺어줄 수 있음
- 독립적인 프로그래밍 가능
- 인터페이스를 이용하면 클래스의 선언과 구현을 분리시킬 수 있기 때문에 실제구현에 독립적인 프로그램 작성 가능
- 클래스와 클래스간의 직접적인 관계를 인터페이스를 이용해서 간접적인 관계로 변경하면, 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍 가능
예제1
class Unit{
intt hitPoint;
final int MAX_HP;
Unit(int hp){
Max_hp = hp;
}
}
interface Repairable{}
class GroundUnit extends Unit{
GroundUnit(int hp){
super(hp);
}
}
class AirUnit extends Unit{
AirUnit(int hp){
super(hp);
}
}
class Tank extends GroundUnit implements Repairable{
Tank(){
super(150);
hitPoint = MAX_HP;
}
public String toString(){
return "Tank";
}
}
class Dropship extends AirUnit implements Repairable{
Dropship(){
super(125);
hitPoint = MAX_HP;
}
public String toString(){
return "Dropship";
}
}
class Marine extends GroundUnit{
Marine(){
super(40);
hipPoint = MAX_HP;
}
}
class SCV extends GroundUnit implements Repairable{
SCV(){
super(60);
hipPoint = MAX_HP;
}
void repair(Repairable r){
if(r instanceof Unit){
Unit u = (Unit)r;
while(u.hitPoint!= u.MAX_HP){
u.hipPoint++;
}
System.out.println(u.toString() + " 수리 완료");
}
}
}
class prac{
public static void main(String[] args){
Tank tank = new Tank();
Dropship d ropship = new Dropship();
Marine marine = new Marine();
SCV scv = new SCV();
scv.repair(tank); // Tank 수리 완료
scv.repair(dropship); // Dropship 수리 완료
scv.repair(scv); // SCV 수리 완료
}
}
예제2
interface I {
void play();
}
class A {
void autoPlay(I i){
i.play();
}
}
class B implements I{
public void play(){
System.out.println("Play in B class");
}
}
class C implements I {
public void play(){
System.out.println("Play in C class");
}
}
class prac{
public static void main(String[] args){
A a = new A();
a.autoPlay(new B()); // Play in B class
a.autoPlay(new C()); // Play in C class
}
}
예제3
interface I {
void methodB();
}
class B implements I {
public void methodB(){
System.out.println("methodB in B class");
}
public String toString(){
return "class B";
}
}
class InstanceManager{
public static I getInstance(){
return new B(); // 다른 인스턴스를 바꾸려면 여기만 변경하면 됨
}
}
class A {
void methodA(){
I i = InstanceManager.getInstance();
i.methodB();
System.out.println(i.toString());
}
}
class prac{
public static void main(String[] args) {
A a = new A();
a.methodA();
}
}
/* 출력값
methodB in B class
class B
*/
예제4
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 method2() {
System.out.println("method1() in MyInterface2");
}
static void staticMethod(){
System.out.println("staticMethod() in MyInterface2");
}
}
class Parent {
public void method2() {
System.out.println("method2() in Parent");
}
}
class Child extends Parent implements MyInterface, MyInterface2{
public void method1(){
System.out.println("method1() in Child"); // 오버라이딩
}
}
class prac{
public static void main(String[] args){
Child c = new Child();
c.method1();
c.method2();
MyInterface.staticMethod();
MyInterface2.staticMethod();
}
}
/* 출력값
method1() in Child
method2() in Parent
staticMethod() in MyInterface
staticMethod() in MyInterface2
*/
- MyInterface 인터페이스의 method1 메서드와 MyInterface2 method1 메서드가 Child 클래스에서 충돌나니깐 Child 클래스에서 오버라이딩을 통해서 해결
- MyInterface2 인터페이스의 method2와 Parent 클래스의 method2 메서드가 Child 클래스에서 충돌이 나지만 우선순위가 조상 클래스인 Parent 클래스에 있으므로 Parent 클래스의 method2가 호출 됨
추상클래스 vs 인터페이스
- 추상클래스는 iv, 생성자를 가짐. 인터페이스는 iv, 생성자를 가질 수 없음
- 인터페이스 경우, 상속받은 클래스는 반드시 인터페이스에 있는 메서드를 모두 구현해야함
- 추상 클래스 경우, 상속 받는 클래스는 추상메서드만 구현해도 됨
- 일반적으로 부모 클래스의 내부 구현을 자식클래스들이 상속 받아 공유할 필요가 있으면 추상클래스를 사용하고, 공유할 내부 구현이 없다면 더 추상적인 인터페이스를 사용할 것
- 객체지향 설계에서 말하는 유연한 설계란 기존 코드의 변경이 적고 확장가능한 것을 말하기 때문에, 이를 실천하기 위해서는 추상적인 것을 의존하게 만드는것으로 가능함. 의존 대상이 구체적이기보단 추상적일수록 더욱 좋음.
추상 클래스 사용 케이스
- 관련성이 높은 클래스 간에 코드를 공유하고 싶은 경우
- 추상 클래스를 상속 받을 클래스들이 공통으로 가지는 메소드와 필드가 많거나 public 이외의 접근자(protected, private) 선언이 필요한 경우
- non-static, non-final 필드 선언이 필요한 경우 (각 인스턴스에서 상태변경을 위한 메소드가 필요한 경우)
- 상속 받아서 기능을 확장시키는 것 (부모 유전자를 물려 받는 것)
- 상속 관계를 쭉 타고 올라갔을때 같은 조상클래스를 상속하는데 기능까지 완변히 똑같은 기능이 필요한 경우
인터페이스 사용 케이스
- 서로 관련성이 없는 클래스들이 인터페이스를 구현하게 되는 경우
ex) A 인터페이스가 존재한다고 가정할 때, A 인터페이스를 상속받은 여러 클래스들은 서로간의 관련성이 없음 - 특정 데이터 타입의 행동을 명시하고 싶은데, 어디서 그 행동이 구현되는지는 신경쓰지 않는 경우
- 다중상속을 허용하고 싶은 경우
- 구현하는 모든 클래스에 대해 특정한 특서드가 반드시 존재하도록 강제하는 역할(부모로부터 유전자를 물려받는것이 아니라 사교적으로 필요에 따라 결합하는 관계). 즉, 구현 객체가 같은 동작을 한다는 것을 보장하기 위함
- 상속 관계를 쭉 타고 올라갔을때 다른 조상클래스를 상속하는데 같은 기능이 필요할 경우 인터페이스 사용
내부 클래스
- 내부 클래스란 클래스 안에 선언된 클래스
- 내부 클래스는 사용 빈도가 높지 않으므로 기본 원리와 특징만 이해할 것
- 특정 클래스 내에서만 주로 사용되는 클래스를 내부 클래스로 선언할 것
- GUI 어플리케이션(AWT, Swing)의 이벤트 처리에 주로 사용됨
- 내부 클래스 장점
- 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근 가능
- 코드 복잡성 줄일 수 잇음 (캡슐화)
- 인스턴스 클래스는 외부 클래스의 인스턴스 멤버를 객체 생성 없이 바로 사용할 수 있지만, static 클래스는 외부 클래스의 인스턴스 멤버를 객체생성 없이 사용할 수 없음
- 인스턴스 클래스는 static 클래스의 멤버들을 객체생성 없이 사용할 수 있지만, static 클래스는 인스턴스 클래스의 멤버들을 객체생성 없이 사용할 수 없음
- 내부 클래스 중에서 static 클래스만 static 멤버를 가질 수 있음
- 내부 클래스에서 static 변수를 선언해야한다면 static 클래스로 선언할 것
- 'final'과 'static' 이 동시에 붙은 변수는 상수 이므로 모든 내부 클래스에서 정의 가능
- 지역 내부 클래스는 메서드 내에서만 사용 가능
내부 클래스의 종류와 특징
- 내부 클래스 종류는 내부 클래스의 선언 위치에 따라 구분됨
- 인스턴스 클래스와 스태틱 클래스는 외부 클래스의 멤버변수와 같은 위치에 선언되며, 멤버변수와 같은 성질
- 내부 클래스가 외부 클래스의 멤버로 간주
- 내부클래스는 클래스이기 때문에 abstract나 final 같은 제어자를 사용할 수 있을뿐만 아니라, 멤버변수들처럼 private, protected과 접근 제어자도 사용 가능
각 내부 클래스의 선언위치에 따라 같은 선언 위치의 변수와 동일한 유효범위와 접근성을 가짐
class Outer{
class InstanceInner{} // 인스턴스 클래스
static class StaticInner{} // static 클래스
void method() {
class LocalInner{} // 지역 클래스
}
}
예제1
class prac{
class InstanceInner{
int iv = 100;
// static int cv = 100; // 에러. static 변수 선언 불가
final static int CONST = 100; // fianl static은 상수이므로 선언 가능
}
static class StaticInner{
int iv = 200;
static int cv = 200;
}
void myMethod() {
class LocalInner{
int iv = 300;
// static int cv = 300; // 에러. static 변수 선언 불가
final static int CONST = 300; // fianl static은 상수이므로 선언 가능
}
}
public static void main(String[] args){
System.out.println(InstanceInner.CONST); // 100
System.out.println(StaticInner.cv); // 200
}
}
예제2
class prac{
class InstanceInner{} // 인스턴스 클래스
static class StaticInner{} // static 클래스
// 인스턴스 멤버 간에는 서로 직접 접근 가능
InstanceInner iv = new InstanceInner();
// static 멤버 간에는 서로 직접 접근 가능
StaticInner cv = new StaticInner();
static void staticMethod(){
// static 멤버는 인스턴스 멤버에 직접 접근 불가능
// InstacneInner obj1 = new InstanceInner();
StaticInner obj2 = new StaticInner();
// 굳이 static 멤버로 인스턴스 멤버에 접근하려면 객체를 생성해야함
// 인스턴스 클래스는 외부 클래스를 먼저 생성해야 접근 가능
prac pr = new prac();
InstanceInner obj3 = pr.new InstanceInner();
}
void myMethod(){
class LocalInner{} // 지역 클래스
LocalInner lv = new LocalInner();
}
void instanceMethod(){
// 인스턴스 메서드에서는 인스턴스 멤버와 static 멤버 모드 접근 가능
InstanceInner obj4 = new InstanceInner();
StaticInner obj5 = new StaticInner();
// 지역 클래스는 해당 메서드 내에서만 접근 가능. 다른 메서드에서 접근 못함
// LocalInner lv2 = new LocalInner();
}
}
- 인스턴스 멤버는 같은 클래스에 있는 인스턴스 멤버와 static 멤버 모두 직접 호출 가능
- static 멤버는 인스턴스 멤버를 직접 호출 할 수 없음
- 인스턴스 클래스는 외부 클래스의 인스턴스 멤버를 객체생성 없이 바로 사용할 수 있음
- static 클래스는 외부 클래스의 인스턴스 멤버를 객체생성 없이 사용 불가능
- 인스턴스 클래스는 스태틱 클래스의 멤버를 객체 생성 없이 사용할 수 있음
- static 클래스는 인스턴스 클래스의 멤버를 객체생성 없이 사용 불가능
예제3
class prac{
private int outerIv = 0;
private static int outerCv = 0;
class InstanceInner{
// 외부클래스의 private 멤버도 접근 가능
int iiv = outerIv;
int iiv2 = outerCv;
}
static class StaticInner{
// int siv = oyterIv; // 에러. static 클래스는 외부 클래스의 인스턴스 멤버에 접근 못함
static int scv = outerCv;
}
void myMethod(){
int lv = 0;
int lv2 = 0;
final int LV= 0; // JDK1.8 부터 final 생략 가능
lv = 5;
class LocalInner{
int liv = outerIv;
int liv2 = outerCv;
// 외부 클래스의 지역변수는 final 붙은 변수(상수)만 접근 가능
int liv3 = LV;
// int liv 4 = lv; // 에러. lv는 초기화를 2번 이상 했기 때문에 final로 인식 안됨
// int liv5 = lv2; // 에러. JDK1.8부터는 에러 아님.
}
}
}
- 내부 클래스에서 외부클래스의 변수들에 대한 접근성을 보여주는 예제
- 외부 클래스의 'outerLv' 와 'outerCv' 한테 접근제어자 private가 붙었을 지라도 내부 클래스에서 접근 가능
- 지역클래스에서 외부 클래스의 인스턴스 멤버와 static 멤버 둘 다 사용 가능
- 지역클래스가 포함된 메서드에 정의된 지역 변수 사용 가능. 단, final 붙은 지역변수(상수)만 접근 가능
- JDK1.8부터 지역 클래스에 접근하려는 변수 앞에 final을 생략할 수 있음. 단, 초기화를 1번만 해야 컴파일이 'final'을 자동 추가하고 지역 클래스에 접근 가능
- 'final'이 붙은 변수(상수)는 'constant pool'에 따로 저장되기 때문에 일반적인 지역변수와 달리 수명이 긺. 그래서 지역 내부 클래스에서 접근 가능
예제4
class Outer{
class InstanceInner{
int iv = 100;
}
static class StaticInner{
int iv = 200;
static int cv = 300;
}
void myMethod() {
class LocalInner{
int iv = 400;
}
}
}
class prac {
public static void main(String[] args){
// 인스턴스 클래스의 인스턴스를 생성하려면 외부 클래스의 인스턴스를 먼저 생성해야함
Outer oc = new Outer();
Outer.InstanceInner ii = oc.new InstanceInner();
System.out.println(ii.iv); // 100
System.out.println(Outer.StaticInner.cv); // 300
// static 내부 클래스의 인스턴스는 외부 클래스를 먼저 생성하지 않아도 됨
Outer.StaticInner si = new Outer.StaticInner();
System.out.println(si.iv); // 200
}
}
- 외부 클래스가 아닌 다른 클래스에서 내부 클래스를 생성하고 내부 클래스의 멤버에 접근 하는 예제
- 사실 외부 클래스가 아닌 다른 클래스에서 내부 클래스에 접근하려는것 자체가 내부 클래스로 사용하면 안된다는것을 의미
예제5
class Outer{
int value = 10; // Outer.this.value
class Inner{
int value = 20; // this.value
void method1(){
int value = 30;
System.out.println(value);
System.out.println(this.value);
System.out.println(Outer.this.value);
}
}
}
class prac{
public static void main(String[] args){
Outer out = new Outer();
Outer.Inner in = out.new Inner();
in.method1();
}
}
/* 출력값
30
20
10
*/
- 내부 클래스와 외부 클래스에 선언된 변수의 이름이 같을 때 변수 앞에 'this.변수명' 또는 '외부클래스명.this.변수명' 을 붙여서 서로 구별 가능
익명함수
- 이름이 없는 일회용 클래스
- 정의와 생얼을 동시에
- 이름이 없기에 생성자도 가질 수 없음
- 단 하나의 클래스를 상속 받거나 단 하나의 인터페이스만 구현 가능
예제1
class prac{
Object iv = new Obejct(){ void method(){} }; // 익명 클래스
static Object cv = new Object(){ void method(){} } // 익명 클래스
void myMethod(){
Object lv = new Object(){ void method(){} } // 익명 클래스
}
}
예제2
'[자바] > 자바의 정석 - 3판' 카테고리의 다른 글
Chapter 09 java.lang 패키지와 유용한 클래스 (0) | 2021.10.30 |
---|---|
Chapter08 예외처리 (0) | 2021.10.27 |
Chapter06. 객체지향 (0) | 2021.09.25 |
Chapter.05 배열 (0) | 2021.08.29 |
Chapter04. 조건문과 반복문 (0) | 2021.08.27 |