추상 클래스(abstract class)

  • 미완성 설계도로 완성된 제품을 만들 수 없듯이 추상클래스로 인스턴스는 생성할 수 없다.

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

  • 추상 클래스는 새로운 클래스를 작성하는데 있어서 바탕이 되는 조상클래스로서의 중요한 의미를 갖는다

abstract class 클래스이름 {
    // ...
}
  • 키워드 'abstract'를 붙이기만 하면 된다

  • 추상 클래스에도 생성자가 있으며, 멤버변수와 메서드를 가질 수 있다

추상메서드(abstract method)

  • 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨 둔 메서드. 미완성 메서드임

    • 미완성으로 남겨둔 이유는?
    • 메서드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문에 조상 클래스에서는 선언부만을 작성하고, 주석을 덧붙여 어떤 기능을 수행할 목적으로 작성되었는지 알려주고, 실제 내용은 상속받는 클래스에서 구현하도록 비워 두는 것
/* 주석 */
abstract 리턴타입 메서드이름();
  • 추상 메서드 역시 키워드 'abstract'를 앞에 붙여주고, 괄호{} 대신 문장의 끝을 알리는 ';'를 적어준다

  • 자손 클래스는 오버라이딩을 통해 조상인 추상클래스의 추상메서드를 모두 구현해주어야 한다.

    • 상속받은 추상메서드 중 하나라도 구현하지 않는다면, 자손 클래스 역시 추상클래스로 지정해 주어야 한다

구현부가 없는 메서드가 갖는 의미

  • 메서드를 작성할 때 실제 작업내용인 구현부보다 더 중요한 부분이 선언부이다.

    • 사용하는 쪽에서 구현을 알 필요가 없고, 선언부만 알고 있으면 되기 때문

    • 인터페이스를 생각해보면 된다

추상클래스의 작성

  1. 여러 클래스에 공통적으로 사용될 수 있는 클래스를 바로 작성하기도 하고

  2. 기존의 클래스의 공통적인 부분을 뽑아서 추상클래스로 만들어 상속하도록 하는 경우도 있다

  • 상속이 자손 클래스를 만드는데 조상 클래스를 사용하는 것이라면, 이와 반대로 추상화는 기존의 클래스의 공통부분을 뽑아내서 조상 클래스를 만드는 것이라고 할 수 있다

추상메서드로 선언하는 이유

  • 추상메서드로 선언하는 것이나 그냥 빈 몸통을 가진 메서드로 만들어 놓는 것이나 별 차이가 없어 보인다고요?

    • abstract를 붙여서 추상메서드로 선언하는 이유는 자손 클래스에서 추상 메서드를 반드시 구현하도록 강요하기 위해서이다

    • 만약 추성메서드로 정의되어 있지 않고 빈 몸통만 가지도록 정의되어 있다면, 상속받는 자손 클래스에서는 이 메서드들이 온전히 구현된 것으로 인식하고 오버라이딩을 통해 자신의 클래스에 맞도록 구현하지 않을 수도 있기 때문이다

공통 부분 뽑아내기

  • 서로 다른 종류의 인스턴스를 하나의 묶음으로 다룸으로써 조상 클래스타입의 배열에 자손 클래스의 인스턴스를 담을 수 있게 된다

    • 이렇게 되면 여러 타입의 인스턴스의 공통적인 부분을 다루기 편해진다 (ex. for문 내에서 사용)
  • Object 클래스 타입의 배열로도 서로 다른 종류의 인스턴스를 하나의 묶음으로 다룰 수 있지만, Object 클래스에는 공통 메서드가 정의되어 있지 않기 때문에 에러가 발생한다

참조) Java의 정석 3rd edition(남궁성, 도우출판)

'Programming Language > Java' 카테고리의 다른 글

[Java] 08. OOP - 다형성(polymorphism)  (0) 2024.06.24
[Java] 07. OOP - 상속과 접근제어자  (0) 2024.03.12
[Java] 06. OOP - 클래스와 객체  (0) 2024.03.12
[Java] 05. 배열  (0) 2024.03.12
[Java] 04. 변수  (0) 2024.03.12

다형성(polymorphism)

  • 여러가지 형태를 가질 수 있는 능력
  • 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 구현
  • 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하였다
    • 공통으로 포함된 인스턴스만 접근할 수 있다
  • 같은 타입의 인스턴스라도 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다
  • 반대로 자손 타입의 참조변수로 조상 타입을 참조할 수 없음(컴파일 에러)
  • 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다

참조변수의 형변환

  • 기본형 변수와 같이 참조변수도 형변환이 가능함
  • 단, 서로 상속 관게에 있는 클래스 사이에서만 가능함 (부모 <-> 자식)
  • Up-casting : 자손타입을 부모타입에 // 형 변환 생략 가능(별 문제가 없기 때문에)
  • Down-casting : 부모타입을 자손타입에 // 형 변환 생략 불가(인스턴스 개수가 자손이 더 많기 때문)
    • 형변환을 수행하기 전에 instanceof 연산자를 사용하여 참조변수가 참조하고 있는 실제 인스턴스 타입을 확인하는 것이 좋음
  • 형변환은 참조변수의 타입을 변환하는 것이지, 인스턴스를 변환하는 것은 아니 때문에 인스턴스에 아무런 영향을 미치지 않음
  • Tv t = new CaptionTv(); 도 Tv t = (Tv)new CaptionTv();의 생략된 형태임
class CastingTest1 {
    public static void main(String args[]) {
        Car car = null;
        FireEngine fe    = new FireEngine();
        FireEngine fe2    = null;

        fe.water();
        car = fe;                // car = (Car)fe; 에서 형변환이 생략된 형태
//        car.water();            // 컴파일 에러. Car 타입의 참조변수로는 water() 호출 불가
        fe2 = (FireEngine)car;    // 자손타입 <- 조상타입
        fe2.water();
    }
}

class Car {
    String color;
    int door;

    void drive() {                // 운전하는 기능
        System.out.println("drive, Brrrr~");
    }

    void stop() {                // 멈추는 기능
        System.out.println("stop!!!");
    }
}

class FireEngine extends Car {    // 소방차
    void water() {                // 물을 뿌리는 기능
        System.out.println("water~!");
    }
}
  • 서로 상속관계에 있는 클래스 타입의 참조변수간의 형변환은 양방향으로 자유롭게 수행될 수 있음
  • But 참조변수가 참조하고 있는 인스턴스의 자손타입으로 형변환을 하는 것은 허용되지 않는다
    • 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요함
class CastingTest2 {
    public static void main(String args[]) {
        Car car            = new Car();
        Car car2        = null;
        FireEngine fe     = null;

        car.drive();
        fe = (FireEngine)car;        // 컴파일은 OK. 실행 시 에러 발생
        fe.drive();
        car2 = fe;
        car2.drive();
    }
}

instanceof 연산자

  • 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof연산자를 사용한다.
    • 주로 조건문에 사용된다
    • instanceof의 왼쪽에는 참조변수를, 오른쪽에는 타입(클래스명)이 피연산자로 위치한다
void doWork(Car c) {
    if (c instanceof FireEngine) {
        FireEngine fe = (FireEngine)c;
        fe.water();
        ///...
    } else if (c instanceof Ambulance) {
        Ambulance a = (Ambulance)c;
        a.siren();
        //...
    }
    // ...
}
  • instanceof 연산자를 이용해서 참조 변수가 가리키는 인스턴스 타입을 체크하고, 적절히 형변환하여 사용해야 한다
  • 조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있기 때문에, 참조변수의 타입과 인스턴스의 타입이 항상 일치하지는 않는다는 것을 배웠다
    • 조상타입의 참조변수로는 실제 인스턴스의 멤버들을 모두 사용할 수 없기 때문에, 실제 인스턴스와 같은 타입의 참조 변수로 형변환을 해야지만 인스턴스의 모든 멤버들을 사용할 수 있다. (다운 캐스팅을 해야 한다는 말)
  • ex) instanceof 연산자를 사용하여 어떤 자식 클래스인지 식별하고 다운 캐스팅하는 구조로 사용해봤던 기억이 있네요

Example 7.17

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

        if(fe instanceof Object) {
            System.out.println("This is an Object instance.");
        }

        System.out.println(fe.getClass());
        System.out.println(fe.getClass().getName());    // 클래스의 이름을 출력
    }
}    // class

class Car {}
class FireEngine extends Car {}
  • FireEngine 클래스는 Object 클래스와 Car 클래스를 포함하고 있는 셈이기 때문에, instanceof 연산의 결과가 True가 되는 것이다

instanceof연산의 결과가 true라는 것은 검사한 타입으로의 형변환을 해도 아무런 문제가 없다는 뜻이다.

참고) 참조변수.getClass().getName()은 참조변수가 가리키고 있는 인스턴스의 클래스 이름을 문자열(String)으로 반환한다

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

  • 조상 클래스에 선언된 멤버변수가 같은 이름의 인스턴스변수를 자손 클래스에 중복으로 정의했을 때, 조상타입의 참조변수로 자손 인스턴스를 참조하는 경우와 자손 타입의 참조 변수로 자손 인스턴스를 참조하는 경우는 서로 다른 결과를 얻는다
    • cf) 메서드의 경우 조상 클래스의 메서드를 자손의 클래스에서 오버라이딩한 경우에도 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드(오버라이딩된 메서드)가 호출되지만, 멤버변수의 경우 참조변수의 타입에 따라 달라진다.
    • 조금 더 간단히 구별하자면 메서드우변의 인스턴스 타입에 따라 달라지고, 멤버변수좌변의 참조변수 타입에 따라 달라진다

Example 7.18

class BindingTest{
    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("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("Child Method");
    }
}

실행결과
p.x = 100
Child Method
c.x = 200
Child Method

Example 7.19

class BindingTest2 {
    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("c.x = " + c.x);
        c.method();
    }
}

class Parent {
    int x = 100;

    void method() {
        System.out.println("Parent Method");
    }
}

class Child extends Parent { }

실행결과
p.x = 100
Parent Method
c.x = 100
Child Method

참조변수의 타입에 따라 결과가 달라지는 경우는 조상 클래스의 멤버변수와 같은 이름의 멤버변수를 자손 클래스에 중복해서 정의한 경우뿐이다.

Example 7.20

class BindingTest3{
    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);  // this.x와 같다
        System.out.println("super.x=" + super.x);
        System.out.println("this.x="  + this.x);
    }
}

인스턴스변수에 직접 접근하면, 참조변수의 타입에 따라 사용되는 인스턴스변수가 달라질 수 있으므로 주의하자

  • 이를 위해 멤버변수들을 주로 private로 접근을 제한하는 것이 좋다

매개변수의 다형성

Product, Tv, Computer, Audio, Buyer 클래스가 정의되어 있다고 가정하자.

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;
}
  • 구매 함수를 아래와 같이 작성한다면 제품의 종류가 늘어날 때 마다 Buyer클래스에 새로운 buy 메서드를 추가해야 한다
void buy(Tv t) {
        money = money - t.price;
        bonusPoint += t.bonusPoint;
}

void buy(Computer c) {
        money -= c.price;
        bonusPoint += c.bonusPoint;

// Audio 구매 함수는 생략
}
  • 메서드의 매개변수에 다형성을 적용하면 다음과 같이 간단히 처리할 수 있다
void buy(Product p) {
        money = money - p.price;
        bonusPoint = bonusPoint + p.bonusPoint;
}
  • 매개변수가 Product타입의 참조변수라는 것은, 메서드의 매개변수로 Product 클래스의 자손타입의 참조변수면 어느 것이나 매개변수로 받아들일 수 있다는 뜻이다.
  • 앞으로 다른 제품 클래스를 추가할 때 Product 클래스를 상속받기만하면 buy(Product p) 메서드의 매개변수로 받아들여질 수 있다.

Example 7.21

class Product {
    int price;            // 제품의 가격
    int bonusPoint;        // 제품 구매 시 제공하는 보너스 점수

    Product(int price) {
        this.price = price;
        bonusPoint = price/10;        // 보너스 점수는 제품 가격의 10%
    }
}

class Tv extends Product {
    Tv() {
        // 조상클래스의 생성자 Product(int price)를 호출
        super(100);        // Tv의 가격 100만원
    }

    // Object 클래스의 toString()을 오버라이딩
    public String toString() { return "Tv"; }
}

class Computer extends Product {
    Computer() { super(200); }

    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 PolyArgumentTest {
    public static void main(String args[]) {
        Buyer b = new Buyer();

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

        System.out.println("현재 남은 돈은 " + b.money + "만원 입니다.");
        System.out.println("현재 보너스 점수는 " + b.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();
  • 조상타입의 참조변수 배열을 사용하면, 공통의 조상을 가진 서로 다른 종류의 객체를 배열로 묶어서 다룰 수 있다
  • 배열의 크기 제한이 거슬린다면, Vector클래스를 이용하면 된다.
    • Vector클래스는 내부적으로 Object타입의 배열을 가지고 있다.

Example 7.23

import java.util.*;        // Vector 클래스를 사용하기 위해서 추가

class Product {
    int price;            // 제품의 가격
    int bonusPoint;        // 제품 구매 시 제공하는 보너스 점수

    Product(int price) {
        this.price = price;
        bonusPoint = price/10;
    }

    Product() {
        price = 0;
        bonusPoint = 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;            // 보너스 점수
    Vector item = new Vector();    // 구입한 제품을 저장하는 데 사용될 Vector 객체

    void buy(Product p) {        // 제품 구입
        if(money < p.price) {
            System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
            return;
        }
        money -= p.price;                            // 가진 돈에서 구입한 제품의 가격 차감
        bonusPoint += p.bonusPoint;                    // 가진 보너스 점수에 제품의 보너스 점수 추가
        item.add(p);                                // 구입한 제품을 Vector에 저장
        System.out.println(p + "을/를 구입하셨습니다.");
    }

    void refund(Product p) {    // 제품 환불
        if(item.remove(p)) {                        // 제품을 Vector에서 제거
            money += p.price;
            bonusPoint -= p.bonusPoint;
            System.out.println(p + "을/를 반품하셨습니다.");
        } else {                                    // 제거에 실패한 경우
            System.out.println("구입하신 제품중 해당 제품이 없습니다.");
        }
    }

    void summary() {            // 구매한 물품에 대한 정보 요약, 출력
        int sum = 0;            // 구입한 물품의 가격 합계
        String itemList = "";    // 구입한 물품 목록

        if(item.isEmpty()) {    // Vector가 비어있는지 확인
            System.out.println("구입하신 제품이 없습니다.");
            return;
        }

        // 반복문을 이용해서 구입한 물품의 총 가격과 목록을 생성
        for (int i=0; i<item.size(); i++) {
            Product p = (Product)item.get(i);        // Vector의 i번째에 있는 객체를 얻음 (Product로 형변환했음을 관찰)
            sum += p.price;
            itemList += (i==0) ? "" + p : ", " + p;
        }
        System.out.println("구입하신 물품의 총 금액은 " + sum + "만원 입니다.");
        System.out.println("구입하신 제품은 " + itemList + "입니다.");
    }
}

class PolyArgumentTest3 {
    public static void main(String args[]) {
        Buyer b = new Buyer();
        Tv tv = new Tv();
        Computer com = new Computer();
        Audio audio = new Audio();

        b.buy(tv);
        b.buy(com);
        b.buy(audio);
        b.summary();
        System.out.println();
        b.refund(com);
        b.summary();
    }
}

'Programming Language > Java' 카테고리의 다른 글

[Java] 09. OOP - 추상 클래스(abstract class)  (0) 2024.06.24
[Java] 07. OOP - 상속과 접근제어자  (0) 2024.03.12
[Java] 06. OOP - 클래스와 객체  (0) 2024.03.12
[Java] 05. 배열  (0) 2024.03.12
[Java] 04. 변수  (0) 2024.03.12

상속과 접근제어자

상속(inheritance)

  • 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것
  • class 이름 뒤에 상속받고자 하는 클래스의 이름을 키워드 'extends'와 함께 써 주기만 하면 된다
class Child extends Parent {
    // ...
}
  • 조상 클래스 : 상속해주는 클래스 (a.k.a 부모, 상위(super), 기반(base) 클래스)
  • 자손 클래스 : 상속 받는 클래스 (a.k.a 자식, 하위(sub), 파생된(derived) 클래스)
  • 생성자와 초기화 블럭은 상속되지 않음. 멤버만 상속됨
  • 자손 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다
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) {    // 캡션상태가 on(true)일 때만 text를 보여줌
            System.out.println(text);
        }
    }
}

class CaptionTvTest {
    public static void main(String args[]) {
        CaptionTv ctv = new CaptionTv();
        ctv.channel = 10;
        ctv.channelUp();
        System.out.println(ctv.channel);
        ctv.displayCaption("Hello, World");
        ctv.caption = true;    // 캡션(자막) 기능 활성화
        ctv.displayCaption("Hello, World");
    }
}

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

  • 포함관계를 맺어주면 상속하지 않아도 클래스를 재사용할 수 있다
class Car {
    Engine e = new Engine(); // 엔진
    Door[] d = new Door[4]; // 문, 문의 개수를 넷으로 가정하고 배열 처리
    // ...
}

클래스간의 관계 결정하기

  • 원은 점이다(is a) vs 원은 점을 가지고 있다(has a)
  • 상속관계 : is-a
  • 포함관계 : has-a

단일 상속

  • c++은 다중상속이 허용되지만 자바에서는 단일 상속만을 허용한다 (다이아몬드 관계)
  • 하나 이상의 클래스로부터 상속을 받을 수 없다
  • 다중상속이 필요할 것 같으면 포함관계를 이용해보자

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

  • Object 클래스는 모든 클래스 상속 계층도의 최상위에 있는 조상클래스임 (아무것도 extends 안하면 자동으로 Object 클래스를 extends 함)

오버라이딩

  • 조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것을 오버랑이딩이라고 함

오버라이딩의 조건

  • 자손 클래스에서 오버라이딩하는 메서드는 조상 클래스의 메서드와
    • 이름이 같아야 한다
    • 매개변수가 같아야 한다
    • 반환 타입이 같아야 한다
  • 부모보다 접근 제어자가 같거나 커야한다
  • 조상 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.
  • 인스턴스 메서드를 static 메서드로 또는 그 반대로 변경할 수 없다.
    • static메서드의 경우 각 클래스에서 별개의 static 메서드를 정의한 것이지 오버라이딩이 아님

super

  • super는 자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수이다.
  • this()와 마찬가지로 super() 역시 생성자이다.
  • super()는 조상클래스의 생성자를 호출하는데 사용됨
  • Object 클래스를 제외한 모든 클래스의 생성자는 첫 줄에 반드시 자신의 다른 생성자 또는 조상의 생성자를 호출해야 함
    • 그렇지 않으면 컴파일러는 생성자의 첫 줄에 super();를 자동적으로 추가함
  • example
class PointTest2 {
    public static void main(String args[]) {
        Point3D p3 = new Point3D();
        System.out.println("p3.x = " + p3.x);
        System.out.println("p3.y = " + p3.y);
        System.out.println("p3.z = " + p3.z);
    }
}

class Point {
    int x = 10;
    int y = 20;

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

class Point3D extends Point {
    int z = 30;

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

패키지

  • 패키지란 클래스의 묶음임
  • 패키지에는 클래스 또는 인터페이스를 포함시킬 수 있으며, 관련 클래스끼리 그룹 단위로 묶어 놓음으로써 클래스를 효율적으로 관리한다
  • 클래스가 물리적으로 하나의 클래스파일(.class) 인 것과 같이 패키지는 물리적으로 하나의 디렉토리임
  1. 하나의 소스파일에는 첫 번째 문장으로 단 한 번의 패키지 선언만을 허용함
  2. 모든 클래스는 반드시 하나의 패키지에 속해야 한다
  3. 패키지는 점(.)을 구분자로 하여 계층구조로 구성할 수 있다.
  4. 패키지는 물리적으로 클래스 파일(.class)를 포함하는 하나의 디렉토리이다.

패키지의 선언

package 패키지명;
  • 소스파일에서 주석과 공백을 제외한 첫 번째 문장이어야 하고, 단 한번만 선언될 수 있다.
  • 대소문자를 모두 허용(소문자 원칙)
  • 자바에서 패키지를 따로 선언하지 않으면 이름없는 패키지에 속하게 됨
  • '-d' 옵션을 추가하여 컴파일 한다
package com.javachobo.book;

class PackageTest {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}
C:\jdk1.8\work>javac -d . PackageTest.java

import문

  • 다른 패키지의 클래스를 사용하려면 패키지 명이 포함된 클래스 이름을 사용해야 한다
  • 매 번 이름을 사용하기 불편함 --> import문을 통해 미리 명시해주자
import 패키지명.클래스명;
or
import 패키지명.*;
  • '*'를 사용하는 것이 하위 패키지의 클래스까지 포함하는 것은 아니다

static import문

  • static import문을 사용하면 static 멤버를 호출할 때 클래스 이름을 생략할 수 있다.
import static java.lang.System.out;
import static java.lang.Math.*;

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

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

제어자

  • 접근 제어자 - public, protected, default, private
    • 접근 제어자는 4가지 중 하나만 선택해서 사용할 수 있음
  • 그 외 - static, final, abstract, .....

static

  • '클래스의', '공통적인'의 의미를 가짐

final

  • '마지막의' or '변경될 수 없는'의 의미를 가짐
  • 변수에 사용하면 값을 변경할 수 없는 상수가 됨
  • 메서드에 사용되면 오버라이딩을 할 수 없게 됨
  • 클래스에 사용되면 자신을 확장하는 자손 클래스를 정의하지 못하게 된다.
  • final이 붙은 인스턴스 변수의 경우 생성자에서 초기화 되도록 할 수 있음
    • 클래스 내에 매개변수를 갖는 생성자를 선언하여, 인스턴스를 생성할 때 fianl 이 붙은 멤버변수를 초기화한다
    • 인스턴스마다 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;
    }

    Card() {
        this("HEART", 1);
    }

    public String toString() {
        return KIND + " " + NUMBER;
    }
}

class FinalCardTest {
    public static void main(String args[]) {
        Card c = new Card("HEART", 10);
//        c.NUMBER = 5;
        System.out.println(c.KIND);
        System.out.println(c.NUMBER);
        System.out.println(c);            // System.out.println(c.toString());
    }
}

abstract

  • '미완성'의 의미를 가지고 있음
  • 메서드의 선언부만 작성하고 실제 수행내용은 구현되지 않은 추상 메서드를 선언하는데 사용됨
  • 클래스에 abstract -> 클래스 내에 추상 메서드가 선언되어 있음을 의미함
  • 인스턴스를 생성하지 못하게 하는 목적으로, 완성된 클래스에 abstract를 붙어 추상클래스로 만드는 경우도 있다

접근 제어자(access modifier)

  • 해당하는 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한하는 역할을 한다
  • 따로 지정되어 있지 않으면 default임
클래스, 메머변수, 메서드, 생성자에서 접근 제어자를 사용할 수 있음
private - 같은 클래스 내에서만 접근이 가능하다
default - 같은 패키지 내에서만 접근이 가능하다
protected - 같은 패키지 내에서, 그리고 다른 패키지의 자손 클래스에서 접근이 가능하다
public - 접근 제한이 전혀 없다

public > protected > (default) > private
  • 접근 제어자를 통해 클래스 내부에 선언된 데이터를 보호할 수 있음
    • 객체지향의 캡슐화에 해당하는 내용
  • 임시로 사용되는 것들을 숨길 수도 있음
  • 접근 제어자의 범위에 따라 테스트 하는 범위가 달라진다
  • 생성자에 접근 제어자를 사용하여 인스턴스의 생성을 제한할 수도 있다
    • 생성자가 private -> 외부에서 인스턴스 생성 제한
    • 인스턴스를 생성해서 반환해주는 public메서드를 제공함으로써 외부에서 이 클래스의 인스턴스를 사용할 수 있도록 해야함
    • 이 클래스는 private static 이어야 함
    • 생성자가 private인 경우 다른 클래스의 조상이 될 수 없음
      • 이런 경우 클래스에 final을 추가해 상속할 수 없는 클래스 라는 것을 밝혀주자
final class Singleton {
    private static Singleton s = new Singleton();

    private Singleton() {
        // ...
    }

    public static Singleton getInstance() {
        if (s==null)
            s = new Singleton();
        return s;
    }
}

class SingletonTest {
    public static void main(String args[]) {
//        Singleton s = new Singleton();
        Singleton s = Singleton.getInstance();
    }
}

제어자 조합 시 주의사항

  1. 메서드에 static과 abstract를 함께 사용할 수 없음
  • static 메서드는 몸통이 있는 메서드에만 사용할 수 있음
  1. 클래스에 abstract와 final을 동시에 사용할 수 없다
  • 서로 모순됨(상속으로 구현 vs 상속 불가)
  1. abstract 메서드의 접근 제어자가 private 일 수 없다
  • 자손 클래스에서 구현해야 하는데 private??
  1. 매서드에 private와 final을 같이 사용할 필요는 없다
  • 접근 제어자가 private인 메서드는 오버라이딩 될 수 없기 때문이다. 둘 중 하나만 써도 충분함

'Programming Language > Java' 카테고리의 다른 글

[Java] 09. OOP - 추상 클래스(abstract class)  (0) 2024.06.24
[Java] 08. OOP - 다형성(polymorphism)  (0) 2024.06.24
[Java] 06. OOP - 클래스와 객체  (0) 2024.03.12
[Java] 05. 배열  (0) 2024.03.12
[Java] 04. 변수  (0) 2024.03.12

클래스와 객체

클래스와 객체의 정의

  • 클래스의 정의 : 객체를 정의해 놓은 것
  • 클래스의 용도 : 객체를 생성하는데 사용된다

객체와 인스턴스

  • 클래스의 인스턴스화(instantiate) : 클래스로부터 객체를 만드는 과정
  • 인스턴스 : 어떤 클래스로부터 만들어진 객체
  • ex) Tv클래스로부터 만들어진 객체를 Tv클래스의 인스턴스라고 함

인스턴스의 생성과 사용

클래스명 변수명; // 참조변수 선언
변수명 = new 클래스명();

Tv t;
t = new Tv();

객체 배열

  1. 객체 배열을 다루기 위한 참조 변수 배열을 생성
  2. 객체를 생성해서 배열의 각 요소에 저장
Tv[] tvArr = new Tv[3]; // 참조변수 배열을 생성
// 객체를 생성해서 배열의 각 요소에 저장
tvArr[0] = new Tv();
tvArr[1] = new Tv();
tvArr[2] = new Tv();

// 다음과 같이 배열의 초기화 블럭을 사용하면 한 줄로 간단히 할 수 있다.
Tv[] tvArr = { new Tv(), new Tv(), new Tv() };

// 객체의 수가 많으면 for문을 사용하자

변수와 메서드

  • 인스턴스 변수
    • 클래스 영역에서 선언됨
    • 클래스의 인스턴스를 생성할 때 만들어짐
  • 클래스 변수
    • 인스턴스 변수 앞에 static을 붙여 선언
    • 클래스 변수는 모든 인스턴스가 공통된 저장공간(변수)을 공유하게 된다.
    • 클래스 변수는 인스턴스변수와 달리 인스턴스를 생성하지 않고도 언제라도 바로 사용할 수 있음
    • 클래스이름.클래스변수와 같은 형식으로 사용
      • 참조변수를 통해 클래스변수를 사용할 수 있지만, 인스턴스 변수로 오해하기 쉬우므로 클래스이름.클래스변수 형식을 사용하자
    • 클래스가 메모리에 로딩될 때 생성되어 프로그램이 종료될 때 까지 유지됨
    • public을 앞에 붙이면 같은 프로그램 내에서 어디서나 접근할 수 있는 전역변수의 성격을 가짐

메서드

  • 같은 클래스 내의 메서드끼리는 참조변수를 사용하지 않고도 서로 호출이 가능하지만, static 메서드는 같은 클래스 내의 인스턴스 메서드를 호출할 수 없다.

JVM의 메모리구조

  • 메서드 영역(method area)
    • 프로그램 실행 중 어떤 클래스가 사용되면, JVM은 해당 클래스의 클래스파일(*.class)을 읽고 분석하여 클래스에 대한 정보를 클래스변수와 함께 이곳에 저장
  • 힙(heap)
    • 인스턴스가 생성되는 공간. 프로그램 실행 중 생성되는 인스턴스는 모두 이곳에 생성된다.
  • 호출 스택(call stack)
    • 메서드의 작업에 필요한 메모리 공간을 제공
    • 메서드가 호출되면 호출스택에 호출된 메서드를 위한 메모리가 할당됨
    • 이 메모리는 작업을 수행하는 동안 지역변수(매개변수 포함)들과 연산의 중간결과 등을 저장하는데 사용된다.
    • 메서드가 작업을 마치면 할당되었던 메모리 공간은 반환되어 비워진다.
    • 스택 구조로 함수들이 실행된다

기본형 매개변수와 참조형 매개변수

  • 매개변수의 타입이 기본형(primitive type)일 때는 기본형 값이 복사됨
    • 변수의 값을 읽기만 할 수 있다.
  • 매개변수의 타입이 참조형(reference type)일 때는 인스턴스의 주소가 복사됨
    • 변수의 값을 읽고 변경할 수 있음
class Data { int x; }

class ReferenceParamEx {
    public static void main(String[] args) {
        Data d = new Data();
        d.x = 10;
        System.out.println("main() : x = " + d.x);

        change(d);
        System.out.println("After change(d)");
        System.out.println("main() : x = " + d.x);
    }

    static void change(Data d) {    // 참조형 매개변수
        d.x = 1000;
        System.out.println("change() : x = " + d.x);
    }
}
  • 배열 또한 매개변수로 참조형 변수를 사용하면 값을 변경할 수 있다.
class ReferenceParam2 {
    public static void main(String[] args) {
        int[] x = { 10 };    // 크기가 1인 배열, x[0] = 10
        System.out.println("main() : x = " + x[0]);

        change(x);
        System.out.println("After change(x)");
        System.out.println("main() : x = " + x[0]);
    }

    static void change(int[] x) {    // 참조형 매개변수
        x[0] = 1000;
        System.out.println("change() : x = " + x[0]);
    }
}

참조형 반환타입

  • 매개변수 뿐만 아니라 반환타입도 참조형이 될 수 있다. (객체의 주소를 반환)
class Data { int x; }

class ReferenceReturnEx {
    public static void main(String[] args) {
        Data d = new Data();
        d.x = 10;

        Data d2 = copy(d);
        System.out.println("d.x = " + d.x);
        System.out.println("d2.x = " + d2.x);
    }

    static Data copy(Data d) {
        Data tmp = new Data();
        tmp.x = d.x + 10;

        return tmp;
    }
}

클래스 메서드와 인스턴스 메서드

  • 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통적으로 사용해야 하는 것에 static을 붙인다
  • 클래스 변수는 인스턴스를 생성하지 않아도 사용할 수 있다
  • 클래스 메서드는 인스턴스 변수를 사용할 수 없다
  • 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다
    • 메서드 호출시간이 짧아지므로 성능이 향상된다

클래스 멤버와 인스턴스 멤버간의 참조와 호출

  • 같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다
  • 단, 클래스 멤버가 인스턴스 멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야 한다.
class MemberCall {
    int iv = 10;
    static int cv = 20;

    int iv2 = cv;
//    static int cv2 = iv;                    // 컴파일 에러. 클래스변수는 인스턴수변수를 사용할 수 없음
    static int cv2 = new MemberCall().iv;    // 이처럼 객체를 생성해야 사용 가능

    static void staticMethod1() {
        System.out.println(cv);
//        System.out.println(iv);                // 컴파일 에러. 클래스메서드에서 인스턴스변수 사용 불가
        MemberCall c = new MemberCall();
        System.out.println(c.iv);            // 이처럼 객체를 생성해야 사용 가능
    }

    void instanceMethod1() {
        System.out.println(cv);
        System.out.println(iv);                // 인스턴스메서드에서는 인스턴스변수를 바로 사용 가능
    }

    static void staticMethod2() {
        staticMethod1();
//        instanceMethod1();                    // 컴파일 에러. 클래스메서드에서는 인스턴스메서드 호출 불가
        MemberCall c = new MemberCall();
        c.instanceMethod1();                // 이처럼 객체를 생성해야 호출 가능
    }

    void instanceMethod2() {                // 인스턴스메서드에서는
        staticMethod1();                    // 인스턴스메서드와 클래스메서드 모두
        instanceMethod1();                    // (객체를 생성하지 않고도) 바로 호출 가능
    }
}
  • Tip
MemberCall c = new MemberCall();
int result = c.instanceMethod();

// 위 두 줄을 아래와 같이 한 줄로 할 수 있다.

int result = new MemberCall().instanceMethod();
// 다만 참조변수를 선언하지 않았기 때문에 생성된 MemberCall인스턴스는 더 이상 사용할 수 없다 

오버로딩

  • 기본적인 내용은 cpp와 동일

가변인자와 오버로딩

  • JDK 1.5부터 메서드의 매개변수 개수를 동적으로 지정해 줄 수 있게 됨
  • 이 기능을 가변인자 라고 한다
  • 가변인자는 '타입... 변수명'과 같은 형식으로 선언하며, printf()가 대표적인 예이다
  • 가변인자 이외에도 매개변수가 더 있다면, 가변인자를 매개변수 중에서 제일 마지막에 선언해야 한다
public PrintStream printf(String format, Object... args) { ... }
  • 가변인자는 내부적으로 배열을 이용함
    • 꼭 필요한 경우에만 가변인자를 사용하자
    • 가변인자 대신 배열을 사용하는 경우, 반드시 인자를 지정해 줘야하는 불편함이 있음
class VarArgsEx {
    public static void main(String[] args) {
        String[] strArr = { "100", "200", "300" };

        System.out.println(concatenate("", "100", "200", "300"));
        System.out.println(concatenate("-", strArr));
        System.out.println(concatenate(",", new String[]{"1", "2", "3"}));
        // 다음은 비허용
        // System.out.println(concatenate(",", {"1", "2", "3"}));
        System.out.println("[" + concatenate(",", new String[0]) + "]");
        System.out.println("[" + concatenate(",") + "]");
    }

    static String concatenate(String delim, String... args) {
        String result = "";

        for (String str : args) {
            result += str + delim;
        }

        return result;
    }

/*
    static String concatenate(String... args) {
        return concatenate("", args);
    }
*/
}    // class

생성자

  • 인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메서드'
  • Cpp와 유사
  • 연산자 new가 인스턴스를 생성하는거지, 생성자가 인스턴스를 생성하는 것이 아님
  • 기본 생성자가 컴파일러에 의해서 추가되는 경우는 클래스에 정의된 생성자가 하나도 없을 때 뿐임

생성자에서 다른 생성자 호출하기 - this(), this

  • 생성자 간에도 서로 호출이 가능함
  • 조건
    • 생성자의 이름으로 클래스이름 대신 this를 사용한다
    • 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다.
  • 잘못된 예시
Car(String color) {
    door = 5; // 첫 번째 줄
    Car(color, "auto", 4); // 에러1. 생성자의 두 번째 줄에서 다른 생성자 호출
                            // 에러2. this(color, "auto", 4);로 해야함
}

Car(String color, String gearType, int door) {
    this.color = color;
    this.gearType = gearType;
    this.door = door;
}
  • this 는 참조변수로 인스턴스 자신을 가리키고, 이를 통해 인스턴스 변수에 접근할 수 있다.
    • this : 인스턴스 자신을 가리키는 참조변수(주소가 저장되어 있음)
    • this(), this(argument) : 생성자, 같은 클래스의 다른 생성자를 호출할 때 사용함
  • static 메서드에서는 this 역시 사용할 수 없다

생성자를 이용한 인스턴스 복사

  • 생성자를 이용해 현재 사용하고 있는 인스턴스와 같은 상태를 갖는 인스턴스를 하나 더 만들고자 할 때 생성자를 이용할 수 있다.
class CarTest3 {
    public static void main(String[] args) {
        Car c1 = new Car();
        Car c2 = new Car(c1);    // c1의 복사본 c2 생성
        System.out.println("c1의 color = " + c1.color
                         + ", gearType = " + c1.gearType
                         + ", door = " + c1.door);
        System.out.println("c2의 color = " + c2.color
                         + ", gearType = " + c2.gearType
                         + ", door = " + c2.door);
        c1.door = 100;            // c1의 인스턴스변수 door의 값 변경
        System.out.println("c1.door = 100; 수행 후");
        System.out.println("c1의 color = " + c1.color
                         + ", gearType = " + c1.gearType
                         + ", door = " + c1.door);
        System.out.println("c2의 color = " + c2.color
                         + ", gearType = " + c2.gearType
                         + ", door = " + c2.door);
    }
}

class Car {
    String color;        // 색상
    String gearType;    // 변속기 종류 - auto(자동), manual(수동)
    int door;            // 문의 개수

    Car() {
        this("white", "auto", 4);
    }

    Car(String color, String gearType, int door) {
        this.color = color;
        this.gearType = gearType;
        this.door = door;
    }

    Car(Car c) {        // 인스턴스의 복사를 위한 생성자
//        this.color = c.color;    // this 붙여도 되고 안붙여도 됨
//        gearType = c.gearType;
//        door = c.door;
        /* 아래와 같이 기존의 코드를 활용하는 게 바람직 */
        this(c.color, c.gearType, c.door);
    }
}

변수의 초기화

  • 멤버변수는 초기화를 하지 않아도 자동적으로 변수의 자료형에 맞는 기본값으로 초기화가 이루어짐
  • 지역변수는 사용하기 전에 반드시 초기화해야 한다
class InitTest {
    int x;
    int y = x;

    void method() {
        int i; // 지역 변수
        int j = i; // 에러, 지역변수를 초기화 하지 않고 사용
    }
}
  • 멤버변수의 초기화 방법에는 여러 가지가 있음
  1. 명시적 초기화
  2. 생성자
  3. 초기화 블럭 (인스턴스 초기화 블럭, 클래스 초기화 블럭)

명시적 초기화

  • 변수를 선언과 동시에 초기화 하는 것을 명시적 초기화라고 함
class Car {
    int door = 4; // 기본형 변수의 초기화
    Engine e = new Engine(); // 참조형 변수의 초기화
}
  • 복잡한 초기화 작업이 필요할 때는 '초기화 블럭' 또는 생성자를 사용해야 함

초기화 블럭

  • 인스턴스 초기화 블럭은 단순히 클래스 내에 블럭{}을 만들고 그 안에 코드를 작성하기만 하면 된다
  • 클래스 초기화 블럭은 인스턴스 초기화 블럭 앞에 단순히 static만 덧붙이자
  • 초기화 블럭 내에서는 메서드 내에서와 같이 조건문, 반복문, 예외처리구문 등을 자유롭게 사용할 수 있음
class InitBlock {
    static { /* 클래스 초기화 블럭 */}

    { /* 인스턴스 초기화 블럭*/ }

    //...
}
class StaticBlockTest {
    static int[] arr = new int[10];

    static {
        for (int i=0; i<arr.length; i++) {
            // 1과 10 사이의 임의의 값을 배열 arr에 저장
            arr[i] = (int)(Math.random()*10) + 1;
        }
    }

    public static void main(String args[]) {
        for (int i=0; i<arr.length; i++)
            System.out.println("arr["+i+"] : " + arr[i]);
    }
}
  • 인스턴스 초기화 블럭에서는 모든 생성장에서 공통으로 수행되야 하는 코드를 넣는데 사용한다

참조) Java의 정석 3rd edition(남궁성, 도우출판)

'Programming Language > Java' 카테고리의 다른 글

[Java] 08. OOP - 다형성(polymorphism)  (0) 2024.06.24
[Java] 07. OOP - 상속과 접근제어자  (0) 2024.03.12
[Java] 05. 배열  (0) 2024.03.12
[Java] 04. 변수  (0) 2024.03.12
[Java] 03. 화면에서 입력받기  (0) 2024.03.12

배열


배열 생성 방법

타입[] 변수이름 = new 타입[길이]
  • 길이가 0인 배열을 생성할 수 있다

  • JVM이 모든 배열의 길이를 별도로 관리하고, '배열이름.length'를 통해서 배열의 길이에 대한 정보를 얻을 수 있다.

  • 배열은 한 번 생성하면 길이를 변경할 수 없다.

  • 공간이 부족해 배열의 길이를 변경해야 하는 경우 더 큰 길이의 새로운 배열을 생성한 다음 기존 배열의 값을 새로운 배열에 복사한다

  • 유효한 인덱스 범위를 넘어서면 ArrayIndexOutOfBoundsException이라는 예외가 발생한다

  • 코드를 변경해 배열의 길이가 바뀔 때, '배열이름.length'는 for문에서 알아서 값을 update 해준다.

배열의 초기화

  • 배열 생성과 동시에 자동적으로 자신의 타입에 해당하는 기본값으로 초기화 됨

  • 선언과 생성을 동시에 하는 경우 new int[]를 생략할 수 있음

int[] score = new int[]{50, 60, 70, 80, 90};
int[] score = {50, 60, 70, 80, 90}; // new int[]를 생략할 수 있음
  • 선언과 생성을 따로 하는 경우 new int[]를 생략할 수 없음
int[] score;
score = new int[]{50, 60, 70, 80, 90}; 
// 아래 코드는 에러 발생
score = {50, 60, 70, 80, 90};// new int[]를 생략할 수 없음
  • 다음 코드는 모드 길이가 0인 배열을 생성함
int[] score = new int[0]; 
int[] score = new int[]{}; 
int[] score = {}; // new int[]가 생략됨
  • 배열의 요소를 출력하는 두 가지 방법

    • 반복문
int[] iArr = {100, 95, 80, 65, 60};

for (int i = 0; i < iArr.length; i++) {
    System.out.print(iArr[i] + ", ");
}

System.out.println();
  • Arrays.toString(배열이름) 메서드 사용
int[] iArr = {100, 95, 80, 65, 60};
// [100, 95, 80, 65, 60] 이 출력된다
System.out.println(Arrays.toString(iArr));
  • iArr(참조변수)을 출력하면 타입@주소 형태로 출력된다

    • 예외적으로 char 배열은 println 메서드로 출력하면 각 요소가 구분자없이 그대로 출력된다
char[] chArr = { 'a', 'b', 'c', 'd'};
System.out.println(chArr); // abcd가 출력된다.

배열의 복사

  • C와 동일

  • System.arraycopy()를 사용하면 지정된 범위의 값들을 한 번에 통째로 복사하기 때문에, for문을 통해 복사하는 것 보다 효율적이다

System.arraycopy(num, 0, newNum, num.length);
// num[0] 에서 newNum으로 num.length 개의 데이터를 복사

String 클래스 배열과 char 배열

  • String 클래스만 다음과 같이 큰 따옴표만으로 간략이 표현하는 것이 허용됨
String[] name = new String[3];
name[0] = new String("Kim");
name[0] = "Kim";
name[1] = new String("Park");
name[1] = "Park";
name[2] = new String("Yi");
name[2] = "Yi";
  • String 클래스는 char 배열에 기능을 추가한 것이다.

  • String 클래스의 주요 메서드

    • char charAt(int index) // 문자열에서 해당 index에 있는 문자를 반환한다

    • int length() // 문자열의 길이를 반환한다.

    • String substring(int from, int to) // 문자열에서 해당 범위에 있는 문자열을 반환한다(to는 범위에 포함되지 않음)

      • (1,4)면 4는 범위에 포함되지 않는다
    • boolean equals(String str) // 문자열의 내용이 같은지 확인한다

    • char[] toCharArray() // 문자열을 문자배열(char[])로 변환해서 반환한다

다차원 배열

2차원 배열

// 타입[][] 변수이름;
int[][] score = new int[4][3]; // 4행 3열

// 타입 변수이름[][];
int score[][];

// 타입[] 변수이름[];
int[] score[];

2차원 배열의 초기화

int[][] arr = new int[][] { {1, 2, 3}, {4, 5, 6} };
int[][] arr = { {1, 2, 3}, {4, 5, 6} }; // new int[][]가 생략됨

// 가독성을 위해 다음과 같이 줄 바꿈을 해주는 것도 좋다
int[][] arr = { 
    {1, 2, 3}, 
    {4, 5, 6} 
}; // new int[][]가 생략됨

2차원 배열의 길이

  • int score[5][3]에서 length 값은?

    • score.length 의 값은 5

    • score[0].length, score[1].length, score[2].length, score[3].length, score[4].length 의 값은 3

  • 1차원 배열처럼 범위 기반 for문을 사용하면 안 된다

// 에러 발생
for (int i : score) { // 에러, 2차원 배열의 각 요소는 1차원 배열
    sum += i;
}
  • 2차원 배열을 1차원 배열로 받은 후, 1차원 배열 level에서 범위 기반 for문을 사용하자
for (int[] tmp : score) { // score의 각 요소(1차원 배열 주소)를 tmp에 저자
    for (int i : tmp) { // tmp는 1차원 배열을 가리키는 참조변수
        sum += i;
    }
}

가변 배열

  • 자바는 2차원 이상의 배열을 '배열의 배열'의 형태로 처리한다는 사실을 위에서 확인했다.

  • 2차원 이상의 다차원 배열을 생성할 때 마지막 차수의 길이를 지정하지 않고, 추후에 각기 다른 길이의 배열을 생성할 수 있다

    • 유동적인 가변 배열을 구성할 수 있음
int[][] score = new int[5][];
score[0] = new int[4];
score[1] = new int[3];
score[2] = new int[2];
score[3] = new int[2];
score[4] = new int[3];

참조) Java의 정석 3rd edition(남궁성, 도우출판)

변수


  • 단 하나의 값을 저장할 수 잇는 메모리 공간

  • 지역변수는 사용되기 전에 초기화를 반드시 해야 함

  • 클래스변수와 인스턴스변수는 초기화를 생략할 수 있다

변수의 명명규칙

  • 변수의 이름처럼 프로그래밍에서 사용되는 모든 이름을 '식별자(identifier)'라고 함

  • 식별자는 같은 영역 내에서 서로 구분(식별)될 수 있어야 한다.

  • 식별자를 만들 때는 다음과 같은 규칙을 지켜야 함

    • 대소문자가 구분되며 길이에 제한이 없다

    • 예약어(a.k.a. keyword, reserved word)를 사용해서는 안 된다(ex. true는 불가능, True는 가능)

      • 클래스나 변수, 메서드의 이름으로 예약어를 사용할 수 없다
    • 숫자로 시작해서는 안 된다

    • 특수문자는 '_'와 '$' 만을 허용한다

  • 프로그래머들에게 권장하는 암묵적인 규칙(not mandatory)

    • 클래스 이름의 첫 글자는 항상 대문자로

    • 여러 단어로 이루어진 이름은 단어의 첫 글자를 대문자로

    • 상수의 이름은 모두 대문자로 한다. 여러 단어로 이루어진 경우 '_'로 구분한다

    • 용도를 알기 쉽게 '의미있는 이름'으로

변수의 타입

  • 자료형은 크게 '기본형'과 '참조형' 두 가지로 나뉨

  • 기본형 변수는 실제 값(data)를 저장

  • 참조형 변수는 '어떤 값이 저장되어 있는 주소'를 값으로 갖는다

  • 자바는 C와 달리 참조형 변수 간의 연산이 불가능

참조형 변수

  • 참조형 변수를 선언할 대는 변수의 타입으로 클래스의 이름을 사용하므로, 클래스의 이름이 참조변수의 타입이 된다.

    • 새로운 클래스를 작성한다는 것은 새로운 참조형을 추가하는 셈이다.
    클래스이름 변수이름; // 변수의 타입이 클래스 이름인 것들은 모두 참조변수
    • 변수이름 앞에 타입을 적어 참조변수를 선언한다
    Date today = new Date(); // Date 객체를 생성해서, 그 주소를 today에 저장
    • 객체를 생성하는 연산자 new의 결과는 생성된 객체의 주소임

    • 이 주소가 today 참조변수에 저장됨

기본형

  • 논리형 : boolean(1byte, C++에선 bool)

  • 문자형 ; char(2byte)

  • 정수형 : byte(1byte), short(2byte), int(4byte), long(8byte)

  • 실수형 : float(4byte), double(8byte)

    • float의 정밀도 : 7자리

    • double의 정밀도 : 15자리

상수와 리터럴

상수

  • 상수(constant)는 변수와 마찬가지로 '값을 저장할 수 있는 공간'이지만, 변수와 달리 한 번 갓을 저장하면 다른 값으로 변경할 수 없음

  • 변수의 타입 앞에 키워드 'final'을 붙여 줌으로써 상수를 선언한다

final int MAX_SPEED = 10; // 상수 MAX_SPEED의 선언
  • 상수는 반드시 선언과 동시에 초기화 해야 함

리터럴

  • 원래 12, 13, 123, 'A'와 같이 자체로 값을 의미하는 것이 '상수'인데, 프로그래밍에서 상수를 위와 같이 정의함

    • 이를 구분하기 위해 리터럴이라는 용어를 사용한다
  • 리터럴에도 타입이 존재하고, 리터럴에 접미사를 붙여서 타입을 구별한다

    • 정수형의 경우, long 타입의 리터럴에 접미서 'l' 또는 'L'을 붙이고, 접미사가 없으면 int 타입 리터럴이다

    • 16진수의 경우 접두사 '0x' 또는 '0X'을, 8진수의 경우 '0'을 붙인다. (2진수는 0b, .....)

    • JDK1.7부터 정수형 리터럴의 중간에 구분자'_'를 넣을 수 있게 되서 큰 수자를 편하게 읽을 수 있다.

    • 실수형에서 float 타입의 리터럴에는 접미사 'f' 또는 'F'를 붙이고, double탕입의 리터럴에는 접미사 'd' 또는 'D'를 붙인다.

    • double이 기본 자료형이기 때문에 접미사d를 생략하면 double타입 리터럴이다

    float pi = 3.14; // 에러
    double rate = 1.618; // 가능(접미사 d를 생략한 case)
  • 리터럴에 소수점이나 10의제곱을 나타내는 기호 E 또는 e, 그리고 f, F, d, D를 포함하고 있으면 실수형 리터럴로 간주한다.

타입의 불일치

  • 타입이 달라도 저장범위가 넓은 타입에 좁은 타입의 값을 저장하는 것은 허용된다
int i = 'A'; // 가능
long l = 123; // 가능
double d = 3.14f; // 가능
  • 리터럴의 값이 변수의 타입의 범위를 넘어서거나, 리터럴의 타입이 변수의 타이보다 저장범위가 넓으면 컴파일 에러 발생
int i = 0x123456789; // 에러 (int 타입의 범위 초과)
float f = 3.14; // 에러 (float타입보다 double 타입의 범위가 넓음)
  • byte와 short타입의 리터럴은 따로 존재 X --> int 타입의 리터럴을 사용
byte b = 65; // 가능 (byte 타입에 저장 가능한 범위의 int타입 리터널)
short s = 0x1234; // 가능 (short 타입에 저장 가능한 범위의 int타입 리터널)

문자 / 문자열 리터럴

  • 문자열 리터럴은 ""안에 아무런 문자도 넣지 않는 것을 허용함

  • 문자 리터를은 반드시 ''안에 하나의 문자가 있어햐 한다

    • String은 클래스 이므로 String str = ";와 같은 표현이 불가능 할 것 같지만 이와 같은 표현도 허용한다
    String str = ""; // 가능
    String name = new String("Java"); // String 객체 생성
    • 덧셈 연사자를 이용하여 문자열을 결합할 수 있다
    String name = "Ja" + "Va";
    String str = name + 8.0;
    
    문자열 + any type -> 문자열 + 문자열 -> 문자열
    any type + 문자열 -> 문자열 + 문자열 -> 문자열

형식화된 출력 - printf()

  • System.out.printf()를 통해 원하는 형식에 맞춰 출력 가능 (c와 유사)

  • println()과 달리 출력 후 줄바꿈을 하지 않음

    • 지시자 %n(==\r\n) 또는 \n을 따로 넣어줘야 한다
  • 예제 코드

class PrintfEx1 {
    public static void main(String[] args) {
        byte b = 1;
        short s = 2;
        char c = 'A';

        int finger = 10;
        long big = 100_000_000_000L;    //long big = 100000000000L;
        long hex = 0xFFFF_FFFF_FFFF_FFFFL;

        int octNum = 010;                // 8진수 10, 10진수로는 8
        int hexNum = 0x10;                // 16진수 10, 10진수로는 16
        int binNum = 0b10;                // 2진수 10, 10진수로는 2

        System.out.printf("b=%d%n", b);
        System.out.printf("s=%d%n", s);
        System.out.printf("c=%c, %d%n", c, (int)c);
        System.out.printf("finger=[%5d]%n", finger);
        System.out.printf("finger=[%-5d]%n", finger);
        System.out.printf("finger=[%05d]%n", finger); // 남은 공간을 0으로 채워줌
        System.out.printf("big=%d%n", big);
        System.out.printf("hex=%#x%n", hex);    // '#'은 접두사 (16진수 0x, 8진수 0)
        System.out.printf("octNum=%o, %d%n", octNum, octNum);
        System.out.printf("hexNum=%x, %d%n", hexNum, hexNum);
        // 10진수를 2진수로 출력해주는 지시자가 없기 때문에 2진 문자열로 변경한 후 문자열로 출력해야 함
        System.out.printf("bigNum=%s, %d%n", Integer.toBinaryString(binNum), binNum);
    }
}

실행 결과

b=1
s=2
c=A, 65
finger=[   10]
finger=[10   ]
finger=[00010]
big=100000000000
hex=0xffffffffffffffff
octNum=10, 8
hexNum=10, 16
bigNum=10, 2
  • C에서는 char 타입의 값을 %d로 출력할 수 있지만, 자바에서는 허용되지 않는다. 형변환을 해줘야 함
  • 예제 코드
class PrintfEx2 {
    public static void main(String[] args) {
        String url = "www.codechobo.com";

        float f1 = .10f;    // 0.10, 1.0e-1
        float f2 = 1e1f;    // 10.0, 1.0e1, 1.0e+1
        float f3 = 3.14e3f;
        double d = 1.23456789;

        System.out.printf("f1=%f, %e, %g%n", f1, f1, f1);
        System.out.printf("f2=%f, %e, %g%n", f2, f2, f2);
        System.out.printf("f3=%f, %e, %g%n", f3, f3, f3);

        System.out.printf("d=%f%n", d);
        System.out.printf("d=%14.10f%n", d);    // 전체 14자리 중 소수점 10자리

        System.out.printf("[12345678901234567890]%n");
        System.out.printf("[%s]%n", url);
        System.out.printf("[%20s]%n", url);   // Default : 오른쪽 정렬
        System.out.printf("[%-20s]%n", url);    // 왼쪽 정렬
        System.out.printf("[%.8s]%n", url);        // 왼쪽에서 8글자만 출력
    }
}

실행 결과 

f1=0.100000, 1.000000e-01, 0.100000
f2=10.000000, 1.000000e+01, 10.0000
f3=3140.000000, 3.140000e+03, 3140.00
d=1.234568 // 마지막 자리 반올림! (%f는 기본적으로 소수점 이하 6자리까지 출력함)
d=  1.2345678900 // 14자리중 소수점 10자리
[12345678901234567890]
[www.codechobo.com]
[   www.codechobo.com]
[www.codechobo.com   ]
[www.code]

참조) Java의 정석 3rd edition(남궁성, 도우출판)

+ Recent posts