티스토리 뷰

728x90

서문



자바의 정석 기초편 챕터 7편을 기재합니다.
목적은 공부한 내용을 기록하는 것에 있기 때문에 완전한 문장이 아닐 수도 있습니다.
또한 모든 내용을 적은 것은 아닙니다.









자바의 정석 ( CH.7 ) - 객체지향 2



상속

  • 기존의 클래스로 새로운 클래스를 작성하는 것(코드의 재사용)
  • 두 클래스의 부모와 자식으로 관계를 맺어지는 것
  • 자손은 조상의 모든 멤버를 상속받음(생성자, 초기화블럭 제외)
  • 자손의 멤버가 조상의 멤버보다 작을 수 없음(같거나 많음)
  • 자손의 변경이 조상에 영향을 끼치지 않음

class Child extends Parent {
}

-----------------------------

class Parent{
  int age;
}

class Child extends Parent{ // 이미 Child는 parent의 age를 가지고 있다.

}

-----------------------------

class Parent {
  int age;
}

class Child extends Parent{
  void play(){
    System.out.println("자식!!");
  }
}

-----------------------------

// 3차원멤버를 가진 class를 만들 때 2가지 방법이 있음
class Point{ // 2차원 멤버를 가진 class
  int x;
  int y;
}

// 1방법. 새로운 클래스에 모든 멤버를 담는 방법
class Point3d{
  int x;
  int y;
  int z;
}

// 2방법. Point를 상속한 후 필요한 z변수를 만들어서 구성
class Point3d extends Point{
  int z;
}

- 1번 방법과 2번 방법의 차이

  - 1번은 독립적인 class를 생성했기에 영향 받을 클래스가 존재하지 않음
  - 2번은 부모클래스가 변경될 경우 자식 클래스도 변경의 영향을 받기 때문에 영향을 받음


Point3d p = new Point3d();

  - 상속의 여부와 관련없이 객체를 생성했을 때 참조변수와 객체의 연결은 동일한 매커니즘으로 동작함




포함 관계

  • 클래스의 멤버로 참조변수를 선언하는 것
  • 작은 단위의 클래스를 만들고, 이 들을 조합해서 클래스를 생성

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

class Point{
  int x; // 원점의 x좌표
  int y; // 원점의 y좌표
}

class Circle{
  Point c = new Point(); // 원점
  int r; // 반지름
}

- Circle은 Point와 포함관계에 있다고 할 수 있음
- 상속관계와 달리 포함관계는 참조변수와 객체가 연결되는 과정이 다름 (circle은 두 번의 참조를 통해 변수 x,y,r을 가진다고 할 수 있다. c(0x100) -> Point(0x200) -> x,y)

-----------------------------------------

class Car{
  Engine e = new Engine();
  Door[] d = new Door[4];
}
  • 상속관계 : "~은(는) ~이다." ( is - a )
  • 포함관계 : "~은(는) ~을 가지고 있다." ( has - a ), 거의 대부분
    • 원은 점이다. x
    • 원은 점을 가지고 있다. ok


단일 상속

  • Java는 단일상속만을 허용 (c++은 다중 상속 허용)

  • 인터페이스를 사용하면 단일상속처럼 하면서도 다중상속의 효과를 낼 수 있음


  • 다중상속은 여러 문제가 있음

    • 충돌

  • 비중이 높은 클래스 하나만 상속처리하고 나머지는 포함관계로 처리함


class TvDVD extends Tv, DVD{  // ERROR, 조상은 하나만 허용

}

----------------------------

class Tv{
  boolean power;
  int channel;

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

}

class DVD{
  boolean power;

  void power(){ power != power; }
  void play(){}
  void stop(){}
  void rew(){}
  void ff(){}
}

class TvDVD extends Tv{
  DVD dvd = new DVD();

  void play(){
    dvd.play();
  }

  void stop(){
    dvd.stop();
  }

  void rew(){
    dvd.rew();
  }

  void ff(){
    dvd.ff();
  }
}


Object class - 모든 클래스의 조상


  • 부모가 없는 클래스는 자동적으로 Object 클래스를 상속받음
  • 모든 클래스는 Object클래스에 정의된 11개의 메서드를 상속받음
  • toString(), equals(Object obj), hashCode(), ...
class Tv{

}

class Tv extends Object{}

class SmartTv extends Tv{

}

public class InheritanceTest{
  public static void main(String[] args){
    Circle c = new Circle();

    System.out.println(c.toString());
    System.out.println(c); // println는 참조변수가 들어오면 toString()이 호출된다.
  }
}

> Circle@15db9742
> Circle@15db9742


오버라이딩( overriding )

  • 상속받은 조상의 메서드를 자신에 맞게 변경하는 것(내용만)

class Point{
  int x;
  int y;

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

class Point3D extends Point{
  int z;


  String getLocation(){ // 오버라이딩, 조상의 메서드를 z를 더해서 변경
    return "x : " + x + ", y : " + y + ", z : " + z;
  }
}

-----------------------------------------------------

class MyPoint extends Object{
  int x;
  int y;

  MyPoint(){
  }


  MyPoint(int x, int y){
    this.x = x;
    this.y = y;
  }

  public String toString(){
    return "x : " + x + ", y : " + y;
  }
}

public class OverrideTest{
  public static void main(String[] args){
    MyPoint p = new MyPoint();
    p.x = 3;
    p.y = 5;

    System.out.println("p.x = " + p.x);
    System.out.println("p.y = " + p.y);
    System.out.println(p.toString());


    MyPoint p2 = new MyPoint(3,5);
    System.out.println(p2);

  }
}


> p.x = 3
> p.y = 5
> x : 3, y : 5

> x : 3, y : 5


오버라이딩의 조건


  • 선언부가 조상 클래스의 메서드와 일치해야 함
  • 접근 제어자가 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없음
  • 예외는 조상 클래스의 메서드보다 많이 선언할 수 없음 (같거나 작음)

1. 조상의 메서드 선언부와 일치해야함

class Parent{
  int x;
  int y;

  public getLocation(){
    return " x : " + x ", y : " + y;
  }
}

class Child extends Parent{
  int z;

  public getLocation(){
    return " x : " + x ", y : " + y + ", z : " + z;
  }
}

---------------------------------------------------

2. 접근 제어자가 조상 메서드보다 좁은 범위로 설정할 수 없음
(public, protected, (default), private)

class Parent{
  public void parentMethod() {
    ...
  }
}

class Child extends Parent{
  private void parentMethod() {
    ...
  }
}


---------------------------------------------------

3. 예외는 조상 메서드의 예외보다 작거나 같아야 함

class Parent{
  void parentMethod() throws IOException, SQLException {
    ...
  }
}

class Child extends Parent{
  void parentMethod() throws IOException{
    ...
  }
}


오버로딩 vs 오버라이딩


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

class Child extends Parent{
  void parentMethod(){... 변경} // 오버라이딩, 조상의 메서드를 상속받은 것을 내용만 변경해서 사용
  void parentMethod(int i){} // 오버로딩, 이름이 같은 것을 매개변수만 바꾸어 사용

  void childMethod(){} // 메서드 새로 정의한 것
  void childMethod(int i){} // 오버로딩
  void childMethod(){} // 중복정의
}


참조 변수 super


  • 객체 자신을 가리키는 참조변수, 인스턴스 메서드(생성자) 내에만 존재
  • static 메서드에서는 사용 불가
  • 조상의 멤버를 자신의 멤버와 구별할 때 사용

1. 조상에도 x가 있고 자식에게도 x가 있을 땐 this 는 자식, super는 조상으로 구별
class Ex{
  public static void main(String[] args){
    Child c = new Child();
    c.method();

  }
}

class Parent{
  int x = 10; // super.x
}

class Child extends Parent{
  int x = 20; // this.x

  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

---------------------------------------------

2. 자식에 x가 없을 땐 this와 super는 다 조상을 가리키는 것

class Ex2{
  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


super() - 조상의 생성자


  • 조상의 생성자를 호출할 때 사용

  • 조상의 멤버는 조상의 생성자를 호출해서 초기화


  • 생성자의 첫 줄에는 반드시 생성자를 호출해야 함, 그렇지 않으면 컴파일러가 생성자의 첫 줄에 super()를 호출

  • class를 만들 때, 꼭 기본 생성자를 만들어주자


1. 자손의 생성자에서 조상의 멤버를 초기화 ( error는 아니지만 권장 x)
class Point{
  int x,y;

  Point(int x, int y){
    this.x = x;
    this.y = y;
  }
}

class Point3D extends Point{
  int z;

  Point3D(int x, int y, int z){
    this.x = x; // 조상 멤버 초기화
    this.y = y; // 조상 멤버 초기화
    this.z = z;
  }
}

-----------------------------------

2. 조상의 멤버 초기화는 조상이 하기로 

class Point{
  int x, y;

  Point(int x, int y){
    this.x = x;
    this.y = y;
  }
}

class Point3D extends Point{
  int z;

  Point3D(int x, int y, int z){
    super(x, y); // 조상클래스의 생성자 Point(int x, int y)를 호출
    this.z = z; // 자식의 멤버변수 초기화
  }
}


--------------------------------

3. 생성자의 첫 줄에 생성자를 호출

class Point{
  int x;
  int y;

  Point(){
    this(0,0);
  }

  Point(int x, int y){
    this.x = x; // 첫 줄에 생성자가 없음, 아래처럼 컴파일러가 자동으로 super()을 넣어줌
    this.y = y;
  }
}

class Point extends Object{
  int x;
  int y;

  Point(){
    this(0,0); // 생성자 호출
  }

  Point(int x, int y){
    super(); // 이걸로 변환된다.
    this.x = x;
    this.y = y;
  }
}

-----------------------------------------

3-1. 생성자를 안했을 때 error 예시

class Point{
  int x;
  int y;

  Point(int x, int y){
    // 생성자가 없음
    this.x = x;
    this.y = y;
  }

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

class Point3D extends Point{
  int z;

  Point3D(int x, int y, int z){
    // 생성자가 없음
    this.x = x;
    this.y = y;
    this.z = z;
  }

  String getLocation(){
    return "x : " + x + ", y : " + y + ", z : " + z;
  }
}

class PointTest{
  public static void main(String[] args){
    Point3D p3 = new Point3D(1,2,3); // ERROR 발생, 즉 Point3D 생성자를 호출할 때 컴파일러가 super();를 추가해주고 Point로 가보니 기본 생성자가 없기 때문에 error가 발생한 것
  }
}

> error constructor Point() 
> location : class Point
>            Point3D(int x, int y, int z){
}



패키지(package)


  • 서로 관련된 클래스의 묶음
  • JAVA 8 기준, 약 4000개 클래스
  • 클래스는 클래스파일(*.class), 패키지는 폴더, 하위 패키지는 하위 폴더
  • 클래스의 실제 이름은 패키지를 포함(java.util.String)
  • rt.jar는 클래스들을 압축한 파일(JDK설치경로\jre\lib)
  • JAVA 9부터 MODULE개념으로


  • 패키지의 선언
    • 패키지는 소스파일의 첫 번째 문장으로 단 한 번 선언
    • 같은 소스 파일의 클래스들은 모두 같은 패키지에 속함
    • 패키지 선언이 없으면, 이름없는(unnamed, default) 패키지에 속함
    • cmd로 java 코드를 실행할 때, 그 실행할 파일이 속한 bin폴더까지 가서 실행, 아니면 환경설정으로 설정
package com.code.book;

public class PackageTest{
  public static void main(String[] args){
    System.out.println("Hello, World!!")
  }
}


클래스 패스 (classpath)


  • 클래스파일(*.class)의 위치를 알려주는 경로
  • 환경변수 classpath로 관리하며 경로간의 구분자는 ';'를 사용
  • classpath에 패키지의 루트를 등록해주어야 함
  • 검색창에 "시스템 환경 변수 편집"에서 등록


import 문


  • 클래스를 사용할 때 패키지이름을 생략할 수 있음

  • java.lang패키지의 클래스는 import하지 않고도 사용할 수 있도록 되어 있음

  • String, Object, System, Thread 등등

  • import문은 패키지문과 클래스 선언문 사이에 선언

  • import문은 컴파일 시에 처리되므로 프로그램의 성능에 영향이 없음

  • 이름이 같은 클래스가 속한 두 패키지를 import할 떄 클래스 앞에 패키지명을 붙여주어야 함

    • java.sql.Date
    • java.util.Date

  • static import

// import를 사용하지 않고 클래스 사용

class ImportTest{
  java.util.Date today = new java.util.Date();
}

----------------------------------------

// import문 사용

import java.util.Date;

class ImportTest{
  Date today = new Date();
}


---------------------------------------

// static import 문

import static java.lang.Math;
System.out.println(random()) // Math.random()인데 Math를 생략 가능


제어자(modifier)


  • 클래스와 클래스 멤버(멤버변수, 메서드)에 부가적인 의미를 부여하는 것

  • 접근 제어자 : public, protected, (default), private 중 하나만 사용

  • 그 외 : static, final, abstract, native, transient, synchronized, volatile, strictfp


  • 하나의 대상에 여러 제어자가 사용될 수 있음 (접근 제어자는 하나만)

  • 보통 접근제어자 + 그 외 제어자 순으로 사용

public class ModifierTest{
  public static final int WIDTH = 200;

  public static void main(String[] args){
    System.out.println("WIDTH = " + WIDTH);
  }
}


static - 클래스의, 공통적인


대상 의미
멤버변수 - 모든 인스턴스에 공통적으로 사용되는 클래스 변수가 됨
- 클래스 변수는 인스턴스를 생성하지 않고도 사용 가능
- 클래스가 메모리에 로드될 때 생성
메서드 - 인스턴스를 생성하지 않고도 호출이 가능한 static 메서드가 됨
- static 메서드 내에서는 인스턴스멤버들을 직접 사용할 수 없음
class StaticTest{
  static int width = 200; // cv ( static변수 ), 간단초기화
  static int height = 120; // cv ( static변수 )

  static { // 클래스 초기화 블럭, 복잡 초기화
    // static 변수 초기화 수행
  }

  static int max(int a, int b){ // 클래스 메서드 ( static메서드 )
    return a > b ? a : b;
  }
}


final - 마지막의, 변경될 수 없음


  • String, Math 등등
대상 의미
클래스 변경될 수 없는 클래스, 확장될 수 없는 클래스를 의미, 그래서 final로 지정된 클래스는 다른 클래스의 조상이 될 수 없음
메서드 변경될 수 없는 메서드, final로 지정된 메서드는 오버라이딩을 통해 재정의 될 수 없음
멤버변수 변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가 됨
지역변수 변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가 됨
1. final이 클래스에 사용

final class FinalTest{ // 조상이 될 수 없는 클래스
  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(); // ERROR, 추상 클래스로 인스턴스 생성불가


접근 제어자 (access modifier)


  • private : 같은 클래스 내에서만 접근이 가능
  • (default) : 같은 패키지 내에서만 접근이 가능
  • protected : 같은 패키지 내에서, 그리고 다른 패키지의 자손클래스에서 접근이 가능
  • public : 접근 제한이 없음
제어자 같은 클래스 같은 패키지 자손 클래스 전체
public 가능 가능 가능 가능
protected 가능 가능 가능 불가능
(default) 가능 가능 불가능 불가능
private 가능 불가능 불가능 불가능
public class AccessModifierTest{ // class 앞에는 public 또는 default

  // 나머지에는 public, default, protected, private 사용 가능
  int iv;
  static int cv;

  void method{

  }

}

---------------------------------------

package pkg1;

class MyParent{
  private int prv; // 같은 클래스
          int dft; // 같은 패키지
  protected int prt; // 같은 패키지 + 자손(다른 패키지)
  public int pub; // 제한 없음

  public void printMembers(){
    System.out.println(prv); // ok
    System.out.println(dft); // ok
    System.out.println(prt); // ok
    System.out.println(pub); // ok
  }
}

public class MyParentTest{
  public static void main(String[] args){
    MyParent p = new MyParent();
    System.out.println(p.prv); // error, 같은 클래스가 x
    System.out.println(p.dft); // ok
    System.out.println(p.prt); // ok
    System.out.println(p.pub); // ok

  }
}


캡슐화와 접근 제어자


  • 접근 제어자를 사용하는 이유?

    • 외부로부터 데이터를 보호하기 위해

      • 데이터에 직접접근은 막고 메서드를 통해 간접접근하는 것을 권장
    • 외부에는 불필요한, 내부적으로만 사용되는 부분을 숨기기 위해

public class Time{
  private int hour; // 외부에서의 직접 접근을 막음
  private int minute;
  private int second;

  public int getHour(){ return hour;} // 외부에서 메서드에 접근하는 것은 가능, 메서드를 통한 간접접근은 허용해야함
  public void setHour(int hour){
    if (isValidTime(hour)) return;
    this.hour = hour;
  }

  private boolean isValidTime(int hour){
    return hour < 0 || hour > 23;
  }
}


다형성 (polymorphism) 가장 중요한 개념


  • 여러 가지 형태를 가질 수 있는 능력

  • 조상 타입 참조 변수로 자손 타입 객체를 다루는 것


  • 객체와 참조변수의 타입이 일치할 때와 일치하지 않을 때 차이?

    • Tv t = new SmartTv(); (가능)
    • Tv t 는 Tv가 가진 5개 속성과 기능만 사용할 수 있음
    • SmartTv S 는 Tv가 가진 5개 속성 및 기능과 SmartTv가 가진 2개의 속성 및 기능을 사용할 수 있음

  • 자손타입의 참조변수로 조상 타입의 객체를 가리킬 수 없음

    • SmartTv S = new Tv(); (불가능)
    • Tv는 5개 밖에 없는데 2개 기능이 추가될 수 없음
class Tv{
  boolean power;
  int channel;

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

}

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

Tv t = new Tv();
SmartTv s = new SmartTv();

// 자식 타입으로
Tv t = new SmartTv(); // 불일치, 조상 자손의 관계에서 가능
SmartTv s = new Tv(); // error, 스마트티비 리모콘으로 tv를 운영하기에 쓸 수 없는 기능이 있다.


참조변수의 형변환

  • 사용할 수 있는 멤버의 갯수를 조절하는 것(주소나, 값이 변하는 것은 아님, 리모콘의 기능만 바꾸는 것)
  • 조상 자손 관계의 참조변수는 서로 형변환 가능
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");
  }
}

FireEngine f = new FireEngine();
Car c = (Car) f; // upscaling, 소방차는 차이기에 차의 기능을 할 수 있음
FireEngine f2 = (FireEngine)c; // downscaling, 차에서 소방차로 바꿔서 사용 가능
Ambulance a = (Ambulance)f2; // error


-------------------------------------

Car car = null; // 참조 주소 값 null

FireEngine fe = new FireEngine(); // fe 객체 생성
FireEngine fe2 = null; // fe 참조 주소 값 null

fe.water();

car = fe; // Car car = (Car)fe와 같은 것, 기능이 감소하는 것이기 때문에 형변환 생략가능(항상 안전)
car.water(); // error, Car는 소방차의 기능을 사용할 수 없다.

fe2 = (FireEngine)car; // FireEngine fe2 = (FireEngine)car, 기능을 늘리는 건 안전하지 않기 때문에 생략 불가
fe2.water(); // Car -> FireEngine 했기 때문에 소방차 기능을 사용할 수 있음

-------------------------------------------------------

Car car3 = null;
FireEngine fe3 = null;

Car car4 = (Car) fe3; // 객체가 없어도 형변환은 가능
FireEngine fe4 = (FireEngine)car4;

car4.drive() // error, null이기 때문에
fe4.water() // error, null이기 때문에

car4 = new Car();
fe4 = new FireEngine();

car4.drive(); // 가능
fe4.water(); // 가능

--------------------------------------------------------

Car car5 = new Car(); // Car 객체 생성
FireEngine fe = (FireEngine)car5; // 형변환 실행 error, ClassCastException , 컴파일 오류가 아니다.

fe.water(); // Car 인스턴스에 water는 없다.


instanceof 연산자

  • 참조변수의 형변환 가능여부 확인에 사용, 가능하면 true 반환
  • 형변환하기 전에 instanceof 연산자로 확인하고 하는 것을 권장
  • 조상은 다 true
  • 자신도 true

void doWork(Car c){
  if (c instanceof FireEngine){ // 형변환이 가능한지 확인
    FireEngine fe = (FireEngine)c; // 형변환
    fe.water();
  }
}

FireEngine fe = new FireEngine();

System.out.println(fe instanceof Object); // 조상에 대해 다 true
System.out.println(fe instanceof Car); // 조상에 대해 다 true
System.out.println(fe instanceof FireEngine); // 자기 자신도 true


> true
> true
> true


Q. 참조변수의 형변환을 왜 할까?

  • 참조변수을 변경하므로써 사용할 수 있는 멤버의 갯수를 조절하기 위해서

Q. instanceof 연산자는 언제 사용할까?

  • 형변환 하기 전에 형변환 확인을 위해 사용


매개변수의 다형성


  • 참조형 매개변수는 메서드 호출 시, 자신과 같은 타입 또는 자손타입의 인스턴스를 넘겨줄 수 있다.

  • 여러 종류의 객체를 배열로 다룰 수 있음 (Vector class (가변 변수))
class Product{
  int price;
  int bounsPoint;
}

class Tv extends Product{ 
  super(100);
  public String toString(){
    return "Tv";
  }
}
class Computer extends Product{
  super(200);
  public String toString(){
    return "Computer";
  }
}
class Audio extends Product{
  super(50);
  public String toString(){
    return "Audio";
  }
}

class Buyer{
  int money = 1000;
  int bounsPoint = 0;

  // void buy(Tv t){
  //   money -= t.price;
  //   bounsPoint += t.bounsPoint;
  // }

  // void buy(Computer c){
  //   money -= c.price;
  //   bounsPoint += c.bounsPoint;
  // }

  // void buy(Audio a){
  //   money -= a.price;
  //   bounsPoint += a.bounsPoint;
  // }

  void buy(Product p){ // 조상타입으로 모든 자손의 매개객체를 대표할 수 있음
    money -= p.price;
    bounsPoint += p.bounsPoint;
  }
}

---------------------------------------------------------

public class MyClass {
    public static void main(String args[]) {
        Buyer b = new Buyer();

        b.buy(new Tv());
        b.buy(new Computer());
        b.buy(new Audio());
        b.summary();

    }
}

class Product{
  int price;
  int bonusPoint;

  Product(int price){

      this.price = price;
      this.bonusPoint = (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 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;
  }

  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].toString() + ", ";
    }

    System.out.println("구입하신 물품의 총금액은 " + sum + "만원입니다.");
    System.out.println("구입하신 제품은 " + itemList + "입니다.");
  }

}


> 구입하신 물품의 총금액은 350만원입니다.
> 구입하신 제품은 Tv, Computer, Audio, 입니다.


추상 클래스(abstract class)


  • 미완성 설계도, 미완성 메서드를 가지고 있는 클래스
  • 미완성 메서드를 가지고 있다면 미완성 설계도
  • 다른 클래스 작성에 도움을 주기 위한 것, 인스턴스 생성 불가
  • 상속을 통해 추상 메서드를 완성해야 인스턴스 생성가능
  • 꼭 추상메서드가 없어도 추상 클래스는 만들 수 있음

abstract class Player{ // 추상 클래스
  abstract void play(int pos); // 몸통{}이 없는 메서드
  abstract void stop();
}

Player p = new Player(); // error

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(){/*내용 생략*/} // 추상메서드 구현
}

abstract class AbstractPlayer extends Player{ // 1개만 구현했기 때문에 abstract를 써줘야 함, 아직 미완성
  void play(int pos){ /*내용 생략*/ } // 추상메서드 구현, 1개만 구현
}


---------------------------------------------------

abstract class Player{
  boolean pause;
  int currentPos;

  Player(){ // 생성자
    pause = false;
    currentPos = 0;
  }

  abstract void play(int pos); // 추상 메서드
  abstract void stop(); // 추상 메서드

  void play(){ // 인스턴스 메서드
    play(currentPos); // 추상메서드 호출 (선언부만 알아도 호출은 가능 ) 나중에 자손이 구현부를 만들어줄테니
  }

}

--------------------------------------------------

abstract class Player{
  abstract void play(int pos); // 추상메서드, 구현부가 없음
  abstract void stop(); // 추상메서드, 구현부가 없음
}

class AudioPlayer extends Player{
  void play(int pos){System.out.println(pos + "위치에서 재생을 시작합니다.")} // 추상메서드 구현
  void stop(){System.out.println("재생을 멈춥니다.")} // 추상메서드 구현
}

public static void main(String[] args){
  Player ap = new AudioPlayer(); // 추상클래스지만 리모콘으로 사용 가능
  ap.play(100);
  ap.stop();
}


> 100위치에서 재생을 시작합니다.
> 재생을 멈춥니다.


추상 클래스의 작성


  • 여러 클래스에 공통적으로 사용될 수 있는 추상클래스를 바로 작성하거나 기존클래스의 공통 부분을 뽑아서 추상클래스로 생성
1. 기존의 클래스만을 생성

class Marine{
  int x, y;
  void move(int x, int y){}
  void stop(){}
  void stimPack(){}
}

class Tank{
  int x, y;
  void move(int x, int y){}
  void stop(){}
  void changeMode(){}
}

class DropShip{
  int x, y;
  void move(int x, int y){}
  void stop(){}
  void load(){}
  void unload(){}
}

--------------------------------

2. 기존 클래스의 공통부분을 추상클래스로 뽑아서 생성

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); // Unit에는 move라는 것이 존재
}


Object[] group2 = new Object[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); // error, object에는 move가 없다.
}



추상클래스

  • 추상클래스
    1. 관리가 용이하다.
    2. 설계도를 쉽고 간결하게 작성가능.
    3. 중복되는 코드 제거.
    4. 자손클래스를 이용해서 단계별 작성이 가능하여, 중간 수정이 쉽다. (유연함)

GregorianCalendar cal = new GregorianCalendar(); // 구체클래스, 분명한 클래스를 반환
Calendar cal = Calendar.getInstance(); // 추상클래스, 어떠한 자손 클래스를 반환할 지 불분명

public static Calendar getInstance(Locale aLocale){
  return createCalendar(TimeZone.getdefault(), aLocale);
}


private static Calendar createCalendar(TimeZone zone, Locale aLocale){
  if ( caltype != null){
    switch (caltype){
      case "buddhist": // 불교력
        cal = new BuddhistCalendar(zone, aLocale);
        break;
      case "japanese": // 일본력
        cal = new JapaneseImperialCalendar(zone, aLocale);
        break;
      case "gregory": // 서양력
        cal = new GregorianCalendar(zone, aLocale);
        break;
    }
  }
}


인터페이스 (Interface)


  • 추상메서드의 집합
  • 구현된 것이 하나도 없는 설계도, 껍데기(모든 멤버가 public)
  • 상수, static 메서드, default 메서드 등 사용 가능, 하지만 핵심은 추상 메서드


  • iv주위로 method가 존재

  • 멤버로 바로 접근하지 않고 메서드를 통해 간접접근을 권장

  • 캡슐화, 데이터를 보호하기 위해서

    • t.hour x
    • t.getHour() o
  • 추상클래스와의 차이

    • 추상클래스는 클래스이며 클래스의 구성요소를 다 가질 수 있지만 미완성 클래스이고 멤버, 생성자, 추상메서드 등을 가진다.
    • 인터페이스는 추상메서드(상수 등도 가질 수 있다.)만 가지고 있다.


1. 인터페이스의 기본 구조

interface InterfaceEx{
  public static final 타입 상수이름 = 값; // 상수
  public abstract 메서드이름(매개변수목록); // 추상메서드
}


------------------------------------------

2. 인터페이스의 예시

interface PlayingCard{
  public static final int SPADE = 4; // 항상 public, static, final이다. 그래서 생략 가능
  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(); // public abstract는 생략가능
  String getCardKind(); // public abstract String getCardKind();
}


인터페이스의 상속


  • 인터페이스의 조상은 인터페이스만 가능( Object가 최고 조상 아님)

  • 다중상속이 가능

    • 선언부가 같고(이름,매개변수이 같고) 내용이 다를 때 "충돌"이 발생하는데, 추상메서드는 선언부가 같으며 구현부가 없으므로 "충돌"이 발생할 가능성이 없기 때문에 다중상속 가능
interface Fightable extends Movable, Attackable{} // 다중상속이 가능

interface Movable{
  void move(int x, int y);
}

interface Attackable{
  void attack(Unit u);
}


인터페이스의 구현

  • 인터페이스에 정의된 추상메서드를 구체화하는 것, 완성하는 것

1. 인터페이스 구현의 기본 구조

class 클래스이름 implements 인터페이스이름 {
  // 인터페이스에 있는 추상메서드를 모두 완성시켜야함
}

---------------------------------------

2. 인터페이스 구현 예시

interface Fightable extends Movable, Attackable{} 

interface Movable{
  void move(int x, int y);
}

interface Attackable{
  void attack(Unit u);
}

class Fighter implements Fightable{
  public void move(int x, int y){ /* 내용 구현 */ } // Movable
  public void attack(Unit u){/ /* 내용 구현 */ } // Attackable
}

-----------------------------------------

2.1 만약 다 구현하지 않았을 경우, abstract를 추가

abstract class Fighter implements Fightable{
  public void move(int x, int y){ /* 내용 구현 */ } // Movable
  // public void attack(Unit u){/ /* 내용 구현 */ } // Attackable
}


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

  • 인터페이스도 구현 클래스의 부모? yes, 하지만 좀 다름
  • 다중상속에 대한 관점이 다름
  • 매개변수로 인터페이스가 사용되었을 때, 인터페이스를 구현한 클래스의 인스턴스에만 가능
class Fighter extends Unit implements Fightable{
  public void move(int x, int y){ /* 내용 생략 */ }
  public void attack(Fightable f){ /* 내용 생략 */ } // **매개변수로 인터페이스가 들어있다. 이건 Fightable을 구현한 클래스의 인스턴스에만 가능하다.**
}

// Unit과 Fightable은 Fighter의 부모관계 클래스 및 인터페이스, 다형성이 적용된다.
Unit u = new Fighter();
Fightable f = new Fighter(); // Fightable에 정의된 메서드만 사용가능, Fighter가 더 많은 기능이 있어도 리모콘이 Fightable이기 때문에 Fightable이 가진 메서드만 사용

------------------------------------------------------

2. Fightable 인터페이스를 구현한 Fighter를 반환

Fightable method(){
  return new Fighter();
}

class Fighter extends Unit implements Fightable{
  public void move(int x, int y){ /* 내용 생략 */ }
  public void attack(Fightable f){ /* 내용 생략 */ } // **매개변수로 인터페이스가 들어있다. 이건 Fightable을 구현한 클래스의 인스턴스에만 가능하다.**
}


인터페이스의 장점


  • 두 대상(객체)간의 '연결, 대화, 소통'을 돕는 중간역할을 함

  • 선언(설계)와 구현을 분리시킬 수 있게 해줌(변경 용이, 유연, 느슨한 결합)

    • 예컨대 tv의 인터페이스는 삼성이든, LG이든 다 비슷하거나 똑같다. 구현체인 삼성이나 LG는 자신만의 구현체를 만들지만 껍데기는 tv다. 즉 인터페이스는 거의 바뀌지 않으며, 구현체만 바뀌는 것이다.
  • 개발 시간을 단축

  • 표준화 가능(JDBC, 여러 db(구현체)를 변경하기에 용이하게 만든 interface의 집합)

  • 서로 관계없는 클래스들을 관계 맺어줌( 공통요소로 묶을 수 있음 )


1. 직접적인 결합
class A {
  public void methodA(B b){
    b.methodB(); // b를 직접 호출
  }
}

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

-----------------------------------------

2. 간접적인 결합

interface I { void methodB();}

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

class A {
  public void methodA(I i){
    i.methodB(); // 실행부는 i로
  }
}

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

public static void main(String[] args){
  A a = new A();
  a.methodA(new B()); // B구현체를 사용해서 메서드를 사용
  a.methodA(new C()); // C구현체를 사용해서 메서드를 사용
}

----------------------------------------------------

3. 관계 없는 것들을 공통요소로 묶을 수 있음

interface Repairable{}

class SCV extends GroundUnit implements Repaireble{}
class Tank extends GroundUnit implements Repaireble{} 
class DropShip extends AirUnit implements Repaireble{}


void repair(Repairable r){
  if (r instanceof Unit){
    Unit u = (Unit)r;
    while (u.hitPoint!=u.MAX_HP){
      u.hitPoint++;
    }
  }
}

  • 변화에 빠른 대응, 코드의 변경을 줄여 인재(인간이 행하는 오류)를 최소화시키는 것이 핵심이라고 생각함.


default method, static method

  • 인터페이스에 default, static method 추가 기능(JDK 1.8 이상)
  • 인터페이스에 새로운 메서드(추상)를 추가하기 어려움
    • 해결책 - dafault method!!
    • 충돌문제가 발생, 구현체마다 다르다면?
        1. 여러 인터페이스의 디폴트 메서드간의 충돌문제
        • 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩하여 사용
        1. 디폴트 메서드와 조상 클래스의 메서드간의 충돌
        • 조상 클래스의 메서드가 우선권을 가지고 디폴트 메서드는 무시됨
interface MyInterface{
  void method();
  void newMethod(); // 새로운 추상 메서드를 추가하면 이와 연결된 구현체들에서 모두 구현을 진행해야 하는 불편함이 생김
}

interface MyInterface2{
  void method();
  default void newMethod(){ // 아예 인터페이스에서 구현을 해버려서 각각의 구현체들의 변경을 막을 수 있음 (하지만 구현체마다 다르다면..?)
  }
}


내부 클래스(inner class)


  • 클래스 안에 클래스
  • 내부 클래스의 장점
    • 내부 클래스에서 외부클래스의 멤버에 접근이 용이
    • 코드의 복잡성을 줄임(캡슐화)
    • class B가 class A에서만 사용되고 굳이 외부에 둘 필요가 없다면 내부클래스로 만들어서 사용할 수 있음

1. 기본적인 클래스들

class A{

}

class B{

}

----------------------

2. 내부 클래스

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

  }
}

----------------------

3-1. 내부 클래스 예시 - 외부

class AAA{
  int i = 100;
  BBB b = new BBB();
}

class BBB{
  void method(){
    AAA a = new AAA();
    System.out.println(a.i);
  }
}

class CCC{
  BBB b = new BBB(); // 접근 가능
}

public class InnerTest{
  public static void main(String[] args){
    BBB b = new BBB();
    b.method(); 
  }
}

> 100

-----------------------------------

3-2 내부 클래스 예시 - 내부

class AAA{
  int i = 100;
  BBB b = new BBB();

  class BBB{
  void method(){
    System.out.println(i);
    }
  }
} 

// class CCC{
//   BBB b = new BBB(); // 접근 불가, 하는 방법은 있음
// }

public class InnerTest{
  public static void main(String[] args){
    AAA a = new AAA();
    a.b.method();
  }
}

> 100

----------------------------------------

4. 내부 클래스의 종류

class Outer{
  int iv = 0; // 인스턴스 
  static int cv = 0; // 스태틱

  void method(){
    int lv = 0; // 로컬
  }
}

class Inner{
  class InstanceInnerClass{} // 인스턴스 클래스
  static class StaticInnerClass{} // 스태틱 클래스

  void method(){
    class LocalInnerClass{} // 로컬 클래스
  }
}

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



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

  • 내부 클래스의 제어자는 변수에 사용가능한 제어자와 동일
  • public protected default private
  • 기본적으로 iv, cv, lv와 비슷하다고 생각하면 쉬움
1. 내부 클래스 제어자와 접근성 예시

class Ex{
  class InstanceClass{
    int iv = 100; // ok
    static int cv = 100; // error, static 변수 선언 x
    static final int CONST = 100; // final static은 상수이므로 o
  }

  static class StaticClass{
    int iv = 200; // ok
    static int cv = 200; // static클래스만 static멤버를 정의할 수 있음, 객체 생성 없이 사용할 수 있는게 static 멤버... 그러므로 static class에서만 static 멤버를 사용할 수 있음
  }

  void method(){
    class LocalClass{
      int iv = 300; // ok
      static int cv = 300; // error, static변수를 선언 x
      static final int CONST = 300; /// static final은 상수이므로 o
    }
  }
}

---------------------------------------

2. 내부 클래스 제어자와 접근성 예시 2

class Ex2 {
  class InstanceClass{}
  static class StaticClass{}

  InstanceClass ic = new InstanceClass();
  static StaticClass sc = new StaticClass();

  static void staticMethod(){
    InstanceClass obj1 = new InstanceClass(); // 불가, 스태틱 클래스에서는 인스턴스 클래스 사용 불가
    StaticClass obj2 = new StaticClass(); // 가능
  }

  void instanceMethod(){
    InstanceClass obj1 = new InstatnceClass(); // 가능
    StaticClass obj2 = new StaticClass(); // 가능, 인스턴스에서 스태틱은 사용 가능

    LocalClass obj3 = new LocalClass(); // 불가능, 내부 클래스는 메서드 안에서만
  }

  void myMethod(){
    class LocalClass{}
    LocalClass lc = new LocalClass();
  }
}


----------------------------------------------------

3. 내부 클래스 제어자와 접근성 예시 3

- 내부클래스에서 외부클래스의 private 변수에 접근 가능
- 지역클래스에서는 상수에만 접근이 가능
- 상수(constant pool)이 존재해서 따로 관리

class Outer{
  private int outerIv = 0;
  static int outerCv = 0;

  class InstanceClass{
    int ilv = outerIv; // 내부클래스에서 외부클래스의 PRIVATE 변수에 접근 가능
    int ilv2 = outerCv;
  }

  static class StaticClass{
    int siv = outerIv; // 불가능, 스태틱 클래스에서는 외부 클래스의 인스턴스 멤버 접근이 불가능
    static int scv = outerCv; // 가능
  }

  void myMethod(){
    int lv = 0; // 지역변수, 메서드 종료시 소멸
    final int LV = 0; // 지역상수, jdk 1.8부터는 final 생략 가능하고 final이 없으면 변수지만 변경되지 않는다면 상수 취급

    class LocalClass{
      int liv = outerIv; // 가능
      int liv2 = outerCv; // 가능, 내부클래스의 객체가 외부 클래스의 지역변수보다 오래 살아남을 수 있으므로 변수는 불가

      // int liv3 = lv; // 불가능, (JDK 1.8부터 에러는 아님)
      int liv4 = LV; // 상수는 가능
    }
  }
}

-------------------------------------------

4. 내부 클래스의 제어자 예시 4

class Outer{
  class InstanceClass{
    int iv = 100;
  }

  static class StaticClass{
    int iv = 200;
    static int cv = 200;
  }

  void myMethod(){
    class LocalClass{
      int iv = 400;
    }
  }
}

class Ex4{
  public static void main(String[] args){
    Outer oc = new Outer();
    Outer.InstanceClass ic = oc.new InstanceClass();

    System.out.println("ic.iv = " + ic.iv);
    System.out.println("Outer.StaticClass.cv = " + Outer.StaticClass.cv );

    Outer.StaticClass sc = new Outer.StaticClass();
    System.out.println("sc.iv = " + sc.iv);

  }
}


>ic.iv = 100
>Outer.StaticClass.cv = 200
>sc.iv = 200

참고 : 생성된 class 종류;

Ex4.class
Outer.class
Outer$InstanceClass.class
Outer$StaticClass.class
Outer$1LocalClass.class // 1은 여러 개가 있을 수 있으므로


----------------------------------------

5. 내부 클래스의 제어자 예시 5

class Outer{
  int value = 10;

  class Inner{
    int value = 20;

    void method(){
      int value = 30;
      System.out.println("            value : " + value); // 가까운 순서
      System.out.println("       this.value : " + this.value);
      System.out.println(" Outer.this.value : " + Outer.this.value);
    }
  }
}

class Ex5{
  public static void main(String[] args){
    Outer ot = new Outer();
    Outer.Inner inner = ot.new Inner();
    inner.method();
  }
}


>            value : 30
>       this.value : 20
> Outer.this.value : 10


익명 클래스 (Anonymous class)

  • 이름이 없는 일회용 클래스, 정의와 생성을 동시에

1. 익명 클래스 기본적인 구조

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

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

--------------------------

2. 익명 클래스 예시

class Ex{
  Object iv = new Object(){ void method(){} }; // 익명 클래스
  static Object cv = new Object(){ void method(){} }; // 익명 클래스

  void myMethod(){
    Object lv = new Object(){ void method(){} }; // 익명 클래스
  }
}

컴파일 후 
Ex.class
Ex$1.class // 익명
Ex$2.class // 익명
Ex$3.class // 익명


--------------------------

3. 익명 클래스 예시 2

import java.awt.*; // java의 윈도우 프로그래밍
import java.awt.event.*;

class Ex2{
  public static void main(String[] args){
    Button b = new Button("Start");
    b.addActionListener(new EventHandler());
  }
}


// 한번만 사용할 것인데 클래스를 만든다는 것이 불필요
class EventHandler implements ActionListener{ // EventHandler 이름을 없애고, 부모인 ActionListener(또는 인터페이스)를 사용해서 익명 클래스 만들 수 있음
  public void actionPerformed(ActionEvent e){
    System.out.println("ActionEvent occured!");
  }
}

b.addActionListener(new ActionListener(){ // 익명 클래스
  public void actionPerformed(ActionEvent e){
    System.out.println("ActionEvent occured!!!" );
  }
})



  • 자바의 정석 6-7장 반복 복습, 가장 핵심내용



댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함