자바 도전기-13

김치전사·2022년 1월 14일
0

자바도전기

목록 보기
13/17

객체지향 개념 심화를 공부한다

상속

상속의 정의와 장점

상속이란, 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다.

class Child extends Parent{
    		///...
}

이 두 클래스는 서로 상속 관계에 있다고 하며, 상속해주는 클래스를 '조상 클래스'라 하고 상속 받는 클래스를 '자손 클래스'라 한다

조상 클래스 : 부모(parent)클래스, 상위(super)클래스, 기반(base)클래스
자손 클래스 : 자식(child)클래스, 하위(sub)클래스, 파생된(derived)클래스

자손 클래스는 조상 클래스의 모든 멤버를 상속받기 때문에, Child클래스는 Parent클래스의 멤버들을 포함한다고 할 수 있다.

class Parent{
	int age;
}

class Child extend Parent{ 
	void play(){
		System.out.println("놀자~");
	}
}

조상 클래스가 변경되면 자손 클래스는 자동적으로 영향을 받게 되지만, 자손 클래스가 변경되는 것은 조상 클래스에 아무런 영향을 주지 못한다.

-생성자와 초기화 블럭은 상속되지 않는다. 멤버만 상속된다.
-자손 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다.

만약 두개의 자손 클래스에 공통적으로 추가되어야 하는 멤버가 있다면, 이 두 클래스에 각각 따로 추가해주는 것보다는 이들의 공통조상인 Parent클래스에 추가하는 것이 좋다.

자손 클래스의 인스턴스를 생성하면 조상 클래스의 멤버와 자손 클래스의 멤버가 합쳐진 하나의 인스턴스로 생성된다

클래스간의 관계 - 포함관계

상속이외에도 클래스를 재사용하는 또 다른 방법이 있는데, 그것은 클래스간의 '포함(Composite)'관계를 맺는 것이다.

class Circle{
	int x;//원점의 x좌표
	int y;//원점의 y좌표
 	int r;//반지름(radius)
}

class Point{
	int x;
   	int y;
}

class Circle{
	Point c = new Point();
   	int r;
}

하나의 거대한 클래스를 작성하는 것보다 단위별로 여러 개의 클래스를 작성한 다음, 이 단위 클래스들을 포함관계로 재사용하면 보다 간결하고 손쉽게 클래스를 작성할 수 있다.

클래스간의 관계 결정하기

'~은 ~이다(is -a)'와 '~은 ~을 가지고 있다(has -a)'를 넣어서 문장을 만들어보면 클래스 간의 관계가 보다 명확해 진다.

원(Circle)은 점(Point)이다.-Circle is a Point.
원(Circle)은 점(Point)을 가지고 있다.-Circle has a Point.
상속관계 : '~은 ~이다.(is -a)'
포함관계 : '~은 ~을 가지고 있다.(has -a)'

단일 상속(single inheritance)

자바에서는 오직 단일 상속만을 허용한다. 그래서 둘 이상의 클래스로부터 상속을 받을 수 있다.
단일 상속이 하나의 조상 클래스만을 가질 수 있기 때문에 다중상속에 비해 불편한 점도 있지만, 클래스 간의 관계가 보다 명확해지고 코드를 더욱 신뢰할 수 있게 만들어 준다는 점에서 다중상속보다 유리하다.

Object클래스 - 모든 클래스의 조상

Object클래스는 모든 클래스 상속계층도의 최상위에 있는 조상클래스이다.

class Tv{
	...
}

class Tv extends Object{
	...
}

오버라이딩(overriding)

오버라이딩이란?

조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것을 오버라이딩이라고 한다.
상속받은 메서드를 그대로 사용하기도 하지만, 자손 클래스 자신에 맞게 변경해야하는 경우가 많다. 이럴 때 조상의 메서드를 오버라이딩한다.

class Point{
	int x;
   	int y;
    
   	String getLocation(){
   		return "x :"+x+", y :"+y;
   	}
}

class Point3D extends Point{
	int z;
   	
   	String getLocation(){//오버라이딩
   		return "x :"+x+", y :"+y+", z :"+z;
   	}
}

오버라이딩의 조건

오버라이딩은 메서드의 내용만을 새로 작성하는 것이므로 메서드의 선언부는 조상의 것과 완전히 일치해야 한다.

자손 클래스에서 오버라이딩하는 메서드는 조상 클래스의 메서드와
-이름이 같아야 한다
-매개변수가 같아야 한다
-반환타입이 같아야 한다

  1. 접근 제어자는 조상 클래스의 메서드보다 좁은 범위로 변경 할 수 없다.
    -조상 클래스에 정의된 메서드의 접근 제어자가 protected라면, 이를 오버라이딩하는 자손 클래스의 메서드는 접근 제어자가 protected나 public이어야 한다.
  2. 조상 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다
class Parent{
	void parentMethod() throws IOException, SQLException{
   		...
   	}
    	...
}
class Child extends Parent{
	void parentMEthod() throws IOException{
   		...
    }
    ...
}

조상 클래스의 메서드를 자손 클래스에서 오버라이딩할 때
1. 접근 제어자를 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없다.
2. 예외는 조상 클래스의 메서드보다 많이 선언할 수 없다,
3. 인스턴스메서드를 static메서드로 또는 그 반대로 변경할 수 없다.

오버로딩 vs 오버라이딩

오버로딩은 기존에 없는 새로운 메서드를 추가하는 것이고, 오버라이딩은 조상으로부터 상속받은 메서드의 내용을 변경하는 것이다

오버로딩(overloading) : 기존에 없는 새로운 메서드를 정의하는 것(new)
오버라이딩(overriding) : 상속받은 메서드의 내용을 변경하는 것(change, modify)

class Parent{
    void parentMethod(){}
}

class Child extends Parent{
    void parentMethod(){}//오버라이딩
    void parentMethod(int i){}//오버로딩
}

super

super는 자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수이다.
멤버변수와 지역변수의 이름이 같을 때 this를 붙여서 구별했듯이 상속받은 멤버와 자신의 멤버와 이름이 같을 때는 super를 붙여서 구별할 수 있다.
조상의 멤버와 자신의 멤버를 구별하는데 사용된다는 점을 제외하고는 super와 this는 근본적으로 같다.

class Point{
    int x;
    int y;
     String getLocation(){
         return "x :"+x+", y :"+y;
     }
}

class Point3D extends Point{
    int z;
    String getLocation(){//오버라이딩
        return super.getLocation()+", z :"+z;
    }
}

super() - 조상 클래스의 생성자

this()와 마찬가지로 super() 역시 생성자이다.
this()는 같은 클래스의 다른 생성자를 호출하는데 사용되지만, super()는 조상 클래스의 생성자를 호출하는데 사용된다.

Object클래스를 제외한 모든 클래스의 생성자 첫 줄에 생성자,this() 또는 super(),를 호출해야 한다. 그렇지 않으면 컴파일러가 자동적으로 'super();'를 생성자의 첫줄에 삽입한다

  1. 클래스-어떤 클래스의 인스턴스를 생성할 것인가?
  2. 생성자-선택한 클래스의 어떤 생성자를 이용해서 인스턴스를 생성할 것인가?
Point3D(int x, int y, int z){
	super(x ,y);//조상클래스의 생성자 Point(int x, int y)를 호출한다
   	this.z=z;

package와 import

패키지(package)

패키지란, 클래스의 묶음이다.
패키지에는 클래스 또는 인터페이스를 포함시킬 수 있으며, 서로 관련된 클래스들끼리 그룹 단위로 묶어 놓음으로써 클래스를 효율적으로 고나리할 수 있다.
클래스가 물리적으로 하나의 클래스파일(.class)인 것과 같이 패키지는 물리적으로 하나의 디렉토리이다

-하나의 소스파일에는 첫 번쨰 문장으로 단 한번의 패키지 선언만을 허용한다
-모든 클래스는 반드시 하나의 패키지에 속해야 한다.
-패키지는 점(.)을 구분자로 하여 계층구조로 구성할 수 있다
-패키지는 물리적으로 클래스 파일(.class)을 포함하는 하나의 디렉토리이다

패키지의 선언

package 패키지명;

하나의 소스파일에 단 한번만 선언될 수 있다

import문

import문의 역할은 컴파일러에게 소스파일에 사용된 클래스의 패키지에 대한 정보를 제공하는 것이다.

import문의 선언

일반적인 소스파일(*.java)의 구성은 다음의 순서로 되어 있다.
1. package문
2. import문
3. 클래스 선언

import 패키지명.클래스명;
	또는
import 패키지명.*;

한 패키지에서 여러 클래스를 사용하는 경우 클래스의 이름을 일일이 지정해주는 것보다 '패키지명.*'과 같이 하는 것이 편리하다

import java.text.SimpleDateFormat;
import java.util.Date;

public class ImportTest {

    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));
        System.out.println("현재 시간은 "+time.format(today));
    }
}

static import문

import문을 사용하면 클래스의 패키지명을 생략할 수 잇는 것과 같이 static import문을 사용하면 static멤버를 호출할 때 클래스 이름을 생략할 수 있다.

import static java.lang.Math.*;
import static java.lang.System.out;

public class StaticImportEx1 {
    public static void main(String[] args) {
        out.println(random());

        out.println("Math.PI :"+PI);
    }
}

제어자(modifier)

제어자란?

제어자(modifier)는 클래스, 변수 또는 메서드의 선언부에 함께 사용되어 부가적인 의미를 부여한다. 제어자의 종류는 크게 접근 제어자와 그 외의 제어자로 나눌 수 있다.

접근 제어자 : public, protected, default, private
그 외 : static, final, abstract, native, transient, synchronized, volatile, strictfp

static - 클래스의, 공통적인

static은 클래스의 또는 공통적인의 의미를 가지고 있다.

static이 사용될 수 있는 곳 - 멤버변수, 메서드, 초기화 블럭

제어자대상의미
static멤버변수-모든 인스턴스에 공통적으로 사용되는 클래스변수가 된다
-클래스변수는 인스턴스를 생성하지 않고도 사용 가능하다
-클래스가 메모리에 로드될 때 생성된다
static메서드-인스턴스를 생성하지 않고도 호출이 가능한 static 메서드가 된다
-static메서드 내에서는 인스턴스멤버들을 직접 사용할 수 없다

인스턴스 멤버를 사용하지 않는 메서드는 static을 붙여서 static메서드로 선언하는 것을 고려해보도록 하자

class StaticTest{
    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이 사용될 수 있는 곳 - 클래스, 메서드, 멤버변수, 지역변수

제어자대상의미
final클래스변경될 수 없는 클래스, 확장될 수 없는 클래스가 된다
그래서 final로 지정된 클래스는 다른 클래스의 조상이 될 수 없다
final메서드변경될 수 없는 메서드, final로 지정된 메서드는 오버라이딩을 통해 재정의 될 수 없다
final멤버변수변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가 된다
final지역변수변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가 된다
final class FinalTest{//조상이 될 수 없는 클래스
    final int MAX_SIZE = 10;//값을 변경할 수 없는 멤버변수(상수)
    
    final void getMaxSize(){//오버라이딩할 수 없는 메서드(변경불가)
        final int LV = MAX_SIZE;//값을 변경할 수 없는 지역변수(상수)
        return MAX_SIZE;
    }
}

생성자를 이용한 final멤버 변수의 초기화
final이 붙은 변수는 상수이므로 일반적으로 선언과 초기화를 동시에 하지만, 인스턴스변수의 경우 생성자에서 초기화 되도록 할 수 있다.
클래스 내에 매개변수를 갖는 생성자를 선언하여, 인스턴스를 생성할 때 final이 붙은 멤버변수를 초기화하는데 필요한 값을 생성자의 매개변수로부터 제공받는 것이다.

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;
    }
}
class FinalCardTest{
    public static void main(String[] args){
        Card c =  new Card("HEART",10);
        c.NUMBER=5;//error
    }
}

abstract - 추상의, 미완성의

abstract는 '미완성'의 의미를 가지고 있다. 메서드의 선언부만 작성하고 실제 수행내용은 구현하지 않은 추상 메서드를 선언하는데 사용된다.

abstract가 사용될 수 있는 곳 - 클래스,메서드

제어자대상의미
abstract클래스클래스 내에 추상 메서드가 선언되어 있음을 의미한다
abstract메서드선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알린다

추상 클래스는 아직 완성되지 않은 메서드가 존재하는 '미완성 설계도'이므로 인스턴스를 생성할 수 있다.

abstract class AbstractTest{//추상 클래스
    abstract void move();//추상 메서드
}

접근 제어자(access modifier)

접근 제어자는 멤버 또는 클래스에 사용되어, 해당하는 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한하는 역할은 한다.

접근 제어자가 사용될 수 있는 곳 - 클래스, 멤버변수, 메서드, 생성자
private - 같은 클래스 내에서만 접근이 가능하다
default - 같은 패키지 내에서만 접근이 가능하다
protected - 같은 패키지 내에서, 그리고 다른 패키지의 자손클래스에서 접근이 가능하다
public - 접근 제한이 전혀 없다.

접근 범위가 넓은 쪽에서 좁은 쪽의 순으로 왼쪽부터 나열하면 다음과 같다
public > protected > (default) > private

대상사용가능한 접근 제어자
클래스public, (default)
메서드public, protected, (default), private
멤버변수public, protected, (default), private
지역변수없음

접근 제어자를 이용한 캡슐화
클래스나 멤버, 주로 멤버에 접근 제어자를 사용하는 이유는 클래스의 내부에 선언된 데이터를 보호하기 위해서이다.
이것을 데이터 감추기(data hiding)이라고 하며, 객체지향개념의 캡슐화(encapsulation)에 해당하는 내용이다.

접근 제어자를 사용하는 이유
-외부로부터 데이터를 보호하기 위해서
-외부에는 불필요한, 내부적으로만 사용되는, 부분을 감추기 위해서

public class Time{
    private int hour;//접근 제어자를 private로 하여 
    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;
    }
    public int getMinute(){return minute;}
    public void setMinute(int minute){
        if(minute<0||minute>49) return;
        this.minute=minute;
    }
    public int getSecond(){return second;}
    public void setSecond(int second){
        if(second<0||second>49){
            return;}
        this.second=second;
    }
}

만일 상속을 통해 확장될 것이 예상되는 클래스라면 멤버에 접근 제한을 주되 자손클래스에서 접근하는 것이 가능하도록 하기 위해 private대신 protected를 사용한다. private이 붙은 멤버는 자손 클래스에서도 접근이 불가능하기 때문이다
보통 멤버변수의 값을 읽는 메서드의 이름을 'get멤버변수이름'으로 하고, 멤버변수의 값을 변경하는 메서드의 이름을 'set멤버변수이름'으로 한다.
get으로 시작하는 메서드를 겟터(getter), set으로 시작하는 메서드를 셋터(setter)라고 부른다

제어자(modifier)의 조합

대상사용가능한 제어자
클래스public, (default), final, abstract
메서드모든 접근 제어자, final, abstract, static
멤버변수모든 접근 제어자, final, static
지역변수final
  1. 메서드에 static과 abstract를 함께 사용할 수 없다
    -static메서드는 몸통이 있는 메서드에만 사용할 수 있기 때문이다
  2. 클래스에 abstract와 final을 동시에 사용할 수 없다
    -클래스에 사용되는 final은 클래스를 확장할 수 없다는 의미이고 abstract는 상속을 통해서 완성되어야 한다는 의미이므로 서로 모순되기 때문이다
  3. abstract메서드의 접근 제어자가 private일 수 없다
    -abstract메서드는 자손클래스에서 구현해주어야 하는데 접근 제어자가 private이면, 자손클래스에서 접근할 수 없기 때문이다
  4. 메서드에 private과 final을 같이 사용할 필요는 없다
    -접근 제어자가 private인 메서드는 오버라이딩될 수 없기 때문이다. 이 둘 중 하나만 사용해도 의미가 충분하다

다형성(polymorphism)

다형성이란?

객체지향개념에서 다형성이란 '여러 가지 형태를 가질 수 있는 능력'을 의미하며, 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현하였다.
조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하였다

class Tv{
    boolean power;
    int channel;

    void power(){power=!power;}
    void channelUp(){++channel;}
    void channelDown(){--channel;}
}

class CaptionTv extends Tv{
    String text;
    void caption(){}
}

Tv t = new Tv();
CaptionTv c = new CaptionTv();
Tv t = new CaptionTv();//조상 타입의 참조변수로 자손 인스턴스를 참조

CaptionTv c = new Tv();//에러 Tv의 멤버 개수보다 참조변수c가 사용할 수 있는 멤버 개수가 더 많기 때문이다

둘 다 같은 타입의 인스턴스지만 참조변수의 타입에 따라 사용할 수 잇는 멤버의 개수가 달라진다
참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다

조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있다
반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참보할 수 없다

참조변수의 형변환

기본형 변수와 같이 참조변수도 형변환이 가능하다.
단, 서로 상속관계에 있는 클래스사이에서만 가능하기 때문에 자손타입의 참조변수를 조상타입의 참조변수로, 조상타입의 참조변수를 자손타입의 참조변수로의 형변환만 가능하다.

자손타입->조상타입(Up-casting) : 형변환 생략가능
자손타입<-조사타입(Down-casting) : 형변환 생략불가

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");
    }
}

class Ambulance extends Car{
    void siren(){
        System.out.println("siren");
    }
}

1. Car car = null;
2. FireEngine fe = new FireEngine();//FireEngine인스턴스를 생성하고 FireEngine타입의 참조변수가 참조하도록 한다
3. car = fe;//참조변수 fe가 참조하고 있는 인스턴스를 참조변수 car가 참조하도록 한다
4. fe2 = (FireEngine)car;//참조변수 car가 참조하고 잇는 인스턴스를 참조변수 fe2가 참조하도록 한다

서로 상속관계에 있는 타입간의 형변환은 양방향으로 자유롭게 수행될 수 있으나, 참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않는다
그래서 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요하다

instanceof연산자

참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof연산자를 사용한다.
주로 조건문에 사용되며, instanceof의 왼쪽에는 참조변수를 오른쪽에는 타입(클래스명)이 피연산자로 위치한다.
연산의 결과로 boolean값인 true와 false 중의 하나를 반환한다

public class InstanceofTest {
    public static void main(String[] args) {
        FireEngine fe = new FireEngine();

        if(fe instanceof FireEngine){
            System.out.println("this is a fireengine instance");
        }
        if(fe instanceof Car){
            System.out.println("this is a car instance");
        }
        System.out.println(fe.getClass().getName());
    }
}

어떤 타입에 대한 instanceof연산의 결과가 true라는 것은 검사한 타입으로 형변환이 가능하다는 것을 뜻한다

참조변수와 인스턴스의 연결

메서드의 경우 조상 클래스의 메서드를 자손의 클래스에서 오버라이딩한 경우에도 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드(오버라이딩된 메서드)가 호출되자만, 멤버변수의 경우 참조변수의 타입에 따라 달라진다
결론부터 말하자면, 멤버변수가 조상 클래스와 자손 클래스에 중복으로 정의된 경우, 조상타입의 참조변수를 사용했을 때는 조상 클래스에 선언된 멤버변수가 사용되고, 자손타입의 참조변수를 사용했을 때는 자손 클래스에 선언된 멤버변수가 사용된다.

public class BindingTest {
    public static void main(String[] args) {
        Parent p = new Child();
        Child c = new Child();

        System.out.println("p.x = "+p.x);//100
        p.method();//child method
        System.out.println("c.x = "+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");
    }
}

같은 child인스턴스를 참조하지만 x의 값은 다르다

매개변수의 다형성

참조변수의 다형적인 특징은 매세드의 매개변수에도 적용된다

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;//보너스점수
    
    void buy(Product p){
        money = money - p.price;
        bonusPoint = bonusPoint + p.bonusPoint;
    }
}

메서드의 매개변수에 다형성을 적용하면 하나의 메서드로 간단히 처리할 수 있다

여러 종류의 객체를 배열로 다루기

Product p1 = new Tv();
        Product p2 = new Computer();
        Product p3 = new Audio();
        
        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[] item = new Product[10];
    int i=0;
    
    void buy(Product p){
        if(money<p.price){
            System.out.println("잔액이 부족하여 물건을 살 수 없습니다");
            return;
        }
        money -= p.price;
        bonusPoint += p.bonusPoint;
        item[i++]=p;
        System.out.println(p+"을 구입했습니다"); 
    }
}

모든 제품클래스의 조상인 Product클래스 타입의 배열을 사용함으로써 구입한 제품을 하나의 배열로 간단하게 다룰 수 있게 된다

메서드/생성자설명
Vector()10개의 객체를 저장할 수 있는 Vector인스턴스를 생성한다
10개 이상의 인스턴스가 저장되면, 자동적으로 크기가 증가된다
boolean add(Object o)Vector에 객체를 추가한다. 추가에 성공하면 결과값으로 true, 실패하면 false를 반환한다
boolean remove(Object o)Vector에 저장되어 있는 객체를 제거한다. 제거에 성공하면 true, 실패하면 false를 반환한다
boolean isEmpty()Vector가 비어있는지 검사한다. 비어있으면 true, 비어있지 않으면 false를 반환하다
Object get(int index)지정된 위치(index)의 객체를 반환한다. 반환타입이 Object타입이므로 적절한 타입으로의 형변환이 필요하다
int sizeVector에 저장된 객체의 개수를 반환한다

추상클래스(abstract class)

추상클래스란?

클래스를 설계도에 비유한다면, 추상클래스는 미완성 설계도에 비유할 수 있다. 미완성 설계도란, 단어의 뜻 그래도 완성되지 못한 채로 남겨진 설계도를 말한다
추상클래스는 키워드 'abstract'를 붙이기만 하면 된다.

abstract class 클래스이름{
	...
}

추상클래스는 상속을 통해서 자손클래스에 의해서만 완성될 수 있다

추상메서드(abstract method)

메서드는 선언부와 구현부(몸통)로 구성되어 있다고 했다. 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨 둔 것이 추상메서드이다.

//주석을 통해 어떤 기능을 수행할 목적으로 작성하였는시 설명한다 
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 class Unit{
    int x,y;
    abstract void move(int x, int y);
    void stop(){ }
}
class Marin 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(){}
}

인터페이스(interface)

인터페이스란?

인터페이스는 일종의 추상클래스이다
인터페이스는 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다.
오직 추상메서드와 상수만을 멤버로 가질 수 있으며, 그 외의 다른 어떠한 요소도 허용하지 않는다.
인터페이스는 구현된 것은 아무 것도 없고 밑그림만 그려져 있는 '기본 설계도'라 할 수 있다.
**인터페이스도 추상클래스처럼 완성되지 않은 불완전한 것이기 대문에 그 자체만으로 사용되기 보다는 다른 클래스를 작성하는데 도움 줄 목적으로 작성된다

인터페이스의 작성

interface 인터페이스이름{
    public static final 타입 상수이름 =;
    public abstract 메서드이름(매개변수목록);
}

모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다
-모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.

interface PlayingCard{
    public static final int SPADE = 4;
    final int DIAMOND = 3;//public static final int DIAMOND = 3;
    static int HEART = 2;//public static final int HEART = 2;
    int CLOVER = 1;//public static final int CLOVER = 1;
    
    public abstract String getCardNumber();
    String getCardKind();//public abstract String getCardKind(); 
}

인터페이스의 상속

인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와 달리 다중상속, 즉 여러 개의 인터페이스로부터 상속을 받는 것이 가능하다

interface Movable{
    // 지정된 위치(x,y)로 이동하는 기능의 메서드
    void move(int x, int y);
}

interface  Attackable{
    //지정된 대상을 공격하는 기능의 메서드
    void attack(Unit u);
}

interface  Fightable extends Movable, Attackable{}

클래스의 상속과 마찬가지로 자손 인터페이스(Fightable)는 조상 인터페이스(Movable, Attackable)에 정의된 멤버를 모두 상속받는다.

인터페이스의 구현

인터페이스도 추상클래스처럼 그 자체로는 인스턴스를 생성할 수 없으며, 추상클래스가 상속을 통해 추상메서드를 완성하는 것처럼, 인터페이스도 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야 한다.
인터페이스는 구현한다는 의미의 키워드 'implements'를 사용할 뿐이다.

class 클래스이름 implements 인터페이스이름{
    //인터페이스에 정의된 추상메서드를 구현해야 한다
}

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){}
}

상속과 구현을 동시에 할 때
class Fighter extends Unit implements Fightable{
    public void move(int x, int y){}
    public void attack(Unit u){}
}

오버라이딩 할 때는 조상의 메서드보다 넓은 범위에 접근 제어자를 지정해야 한다.

인터페이스를 이용한 다중상속

인터페이스는 static상수만 정의할 수 있으므로 조상클래스의 멤버변수와 충돌하는 경우는 거의 없고 충돌된다 하더라도 클래스 이름을 붙여서 구분이 가능하다.
만일 두 개의 클래스로부터 상속을 받아야 할 상황이라면, 두 조상클래스 중에서 비중이 높은 쪽을 선택하고 다른 한쪽은 클래스 내부에 멤버로 포함시키는 방식으로 처리하거나 어느 한족의 필요한 부분을 뽑아서 인터페이스로 만든 다음 구현하도록 한다

public 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--;}
}

public class VCR{
    protected int counter;//VCR의 카운터
    
    public void play(){
        //tape 재생
    }
    
    public void stop(){
        //재생을 멈춤
    }
    
    public void reset(){
        counter=0;
    }

    public int getCounter() {
        return counter;
    }

    public void setCounter(int c) {
        counter = c;
    }
}

public interface IVCR{
    public void play();
    public void stop();
    public void reset();
    public int getCounter();
    public void setCounter(int c);
}

public class TVCR extends Tv implements IVCR{
    VCR vcr= new VCR();//코드를 작성하는 대신 VCR인스턴스의 메서드를 호출한다.
    
    public void play(){
        vcr.play();
    }
    
    public void stop(){
        vcr.stop();
    }
    
    public void reset(){
        vcr.reset();
    }
    
    public int getCounter(){
        return vcr.getCounter();
    }
    
    public void setCounter(int c){
        vcr.setCounter(c);
    }
}

인터페이스를 이용한 다형성

인터페이스 역시 클래스의 조상이라 할 수 있으므로 해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로의 형변환도 가능하다
리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.

인터페이스의 장점

-개발시간을 단축시킬 수 있다
-표준화가 가능하다
-서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다
-독립적인 프로그래밍이 가능하다

인터페이스의 이해

-클래스를 사용하는 쪽(User)와 클래스를 제공하는 쪽(Provider)이 있다
-메서드를 사용(호출)하는 쪽(User)에서는 사용하려는 메서드(Provider)의 선언부만 알면 된다.(내용은 몰라도 된다)

class A{
    public void methodA(I i){
        i.methodB();
    }
}

class B implements I{
    public void methodB(){
        System.out.println("methodB()");
    }
}

interface I{
	public abstract void methodB();
}

class InterFaceTest{
    public static void main(String[] args) {
        A a = new A();
        a.methodA(new B());
    }
}

클래스 A를 작성하는데 클래스 B가 사용되지 않고도 B를 호출할 수 있다
클래스 A는 인터페이스를 통해 실제로 사용하는 클래스의 이름을 몰라도 되고 심지어는 실제로 구현된 클래스가 존재하지 않아도 문제되지 않는다.
클래스 A는 오직 직접적인 관계에 있는 인터페이스 I의 영향만 받는다

디폴트 메서드와 static메서드

원래는 인터페이스에 추상 메서드만 선언할 수 있는데, JDK1.8부터 디폴트 메서드와 static메서드도 추가할 수 있게 되었다.
디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 때문에 디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다

interface MyInterface{
	void method();
   	void newMethod();//추상 메서드
}

interface MyInterface{
	void method();
   	default void newMethod(){}
}
  1. 여러 인터페이스의 디폴트 메서드 간의 충돌
    -인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야 한다
  2. 디폴트 메서드와 조상 클래스의 메서드 간의 충돌
    -조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다

내부 클래스(inner class)

내부 클래스란?

내부 클래스는 클래스 내에 선언된 클래스이다.
한 클래스를 다른 클래스의 내부 클래스로 선언하면 두 클래스의 멤버들 간에 서로 쉽게 접근할 수 있다는 장점과 외부에는 불필요한 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있다는 장점을 얻을 수 있다

내부 클래스의 장점
-내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
-코드의 복잡성을 줄일 수 있다(캡슐화)

class A{//외부 클래스
	...
   	class B{//내부 클래스
   		...
   	}
   	...
}

내부 클래스의 종류와 특징

내부 클래스의 종류는 변수의 선언위치에 따른 종류와 같다.

내부 클래스특 징
인스턴스 클래스
(instance class)
외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 인스턴스멤버처럼 다루어진다. 주로 외부 클래스의 인스턴스멤버들과 관련된 작업에 사용될 목적으로 선언된다
스태틱 클래스
(static class)
외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 static멤버처럼 다루어진다. 주로 외부 클래스의 static멤버, 특히 static메서드에서 사용될 목적으로 선언된다
지역 클래스
(local class)
외부 클래스의 메서드나 초기화블럭 안에 선언하며, 선언된 영역 내부에서만 사용될 수 있다
익명 클래스
(anonymous class)
클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스(일회용)

내부 클래스의 선언

변수가 선언된 위치에 따라 인스턴스변수, 클래스변수(static변수), 지역변수로 나뉘듯이 내부 클래스도 이와 마찬가지로 선언된 위치에 따라 나뉜다.

class Outer{
	class InstanceInner {}
   	static class StaticInner {}
   	
   	void myMethod(){
   		class LocalInner{}
  	}
}

내부 클래스의 제어자와 접근성

인스턴스클래스와 스태틱 클래스는 외부 클래스의 멤버변수와 같은 위치에 선언되며, 또한 멤버변수와 같은 성질을 갖는다

class Outer{
    private int iv=0;
    protected static int cv = 0;
    
    void myMethod(){
        int lv =0;
    }
}

class Outer{
    private  class InstanceInner{}
    protected static class StaticInner{}
    
    void myMethod(){
        class LocalInner{}
    }
}

내부 클래스도 클래스이기 때문에 abstract나 final같은 제어자를 사용할 수 있을 뿐만 아니라, 멤버변수들처럼 private, protected과 접근제어자도 사용이 가능하다
내부 클래스 중에서 스태틱 클래스만 static멤버를 가질 수 있다.
인스턴스멤버는 static멤버도 접근이 가능하지만, static멤버는 인스턴스멤버는 접근이 불가능하다

익명 클래스(anonymous class)

익명 클래스는 특이하게도 다른 내부 클래스들과는 달리 이름이 없다.
클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한번만 사용될 수 있고 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이다.

new 조상클래스이름(){
    //멤버선언
}

    또는

new 구현인터페이스이름(){
    //멤버선언   
}

이름이 없기 때문에 생성자도 가질 수 없으며, 조상클래스의 이름이나 구현하고자 하는 인터페이스의 이름을 사용해서 정의하기 때문에 하나의 클래스로 상속받는 동시에 인터페이스를 구현하거나 둘 이상의 인터페이스를 구현할 수 없다.

class InnerEx{
    Object iv = new Object(){void method(){}};
    static Object cv = new Object(){void method(){}};
    
    void myMethod(){
        Object lv = new Object(){void method(){}};
    }
}

익명 클래스는 이름이 없기 때문에 '외부 클래스명$숫자.class'의 형식으로 클래스파일명이 결정된다

profile
개인공부 블로그입니다. 상업적 용도 X

0개의 댓글