[이것이자바다] Chapter 06. 클래스

kims·2023년 11월 28일
0

이것이자바다

목록 보기
6/9
post-thumbnail

6.1 객체 지향 프로그래밍

  • 객체를 먼저 만들고, 이 객체를 조립해 프로그램을 만드는 기법

1) 객체란?

  • 다른 것과 식별 가능한 것
  • 객체는 속성(=필드)동작(=메서드)으로 구성된다.
  • 현실 세계의 객체를 소프트웨어 객체로 설계하는 것을 객체 모델링이라고 한다.

2) 객체의 상호작용

  • 객체들은 다른 객체와 서로 상호작용하면서 동작한다.
  • 메서드는 객체들 사이의 상호작용 수단이다.
    즉, 메서드 호출을 통해 객체들은 데이터를 서로 주고받는다.
  • 매개값은 메서드가 실행할 때 필요한 값이다.
  • 리턴값은 메서드의 실행 결과이며, 호출한 곳으로 돌려주는 값이다.

3) 객체 간의 관계

  • 관계의 종류에는 집합 관계, 사용 관계, 상속관계가 있다.
구분설명
집합 관계완성품과 부품의 관계
사용 관계다른 객체의 필드를 읽고 변경하거나 메서드를 호출하는 관계
상속 관계부모와 자식 관계

4) 객체 지향 프로그래밍의 특징

① 캡슐화

  • 필드와 메서드를 하나로 묶고 실제 구현 내용을 외부에 감추는 것
  • 필드와 메서드를 보호하는 이유는 외부의 잘못된 사용으로 객체가 손상되는 것을 방지하기 위해서이다.
  • 자바는 접근제한자를 사용해 캡슐화된 멤버의 노출 여부를 결정한다.

② 상속

  • 부모 역할의 상위 객체와 자식 역할의 하위 객체가 있다.
  • 부모 객체는 자신의 필드메서드를 자식 객체에게 물려주어 사용할 수 있도록 한다.
  • 부모 객체의 필드와 메서드를 그대로 사용할 수 있어 자식 객체에서는 중복 코딩을 하지 않아도 되어 코드의 재사용성을 높아준다.
  • 부모 객체의 필드와 메서드를 수정하면 자식 객체들은 수정된 필드와 메서드를 그대로 사용할 수 있어 유지 보수 시간을 최소화시켜 준다.

③ 다형성

  • 사용 방법은 동일하지만 실행 결과가 다양하게 나오는 성질
  • 다형성을 구현하기 위해서는 자동 타입 변환재정의 기술이 필요한데, 상속과 인터페이스 구현으로 얻어진다.

6.2 객체와 클래스

  • 객체를 생성할 때 설계도에 해당하는 클래스가 필요하다.
  • 클래스로부터 생성된 객체를 해당 클래스의 인스턴스라고 부른다.
  • 인스턴스화는 클래스로부터 객체를 만드는 과정을 뜻한다.

6.3 클래스 선언

  • 객체 생성을 위한 설계도를 작성하는 작업
  • 클래스명은 첫 문자를 대문자로 하고 캐멀 스타일로 작성한다.
  • 하나의 소스 파일에 복수 개의 클래스를 선언할 때 소스 파일명과 동일한 클래스만 공개 클래스로 선언할 수 있다. 따라서, Tire 클래스도 공개 클래스로 선언하고 싶다면 별도의 파일로 생성해야 한다.

6.4 객체 생성과 클래스 변수

  • 객체 생성 연산자인 new 연산자와 생성자 호출 코드인'클래스()'를 통해 객체를 생성하고 객체의 주소를 리턴한다.
    클래스 변수 = new 클래스()

6.5 클래스의 구성 멤버

출처👉[이것이자바다] 6.5 클래스의 구성 멤버

1) 필드

  • 객체의 데이터를 저장하는 역할

2) 생성자

  • new 연산자로 객체를 생성할 때 객체의 초기화 역할을 담당

3) 메서드

  • 메서드는 객체가 수행할 동작

6.6 필드 선언과 사용

  • 클래스 블록에서 선언되어야만 필드 선언이 된다.
  • 초기값을 제공하지 않을 경우 필드는 객체 생성 시 자동으로 기본값으로 초기화된다.
public class Car {
    String model = "Hyundai";
    String color;

    int speed;

}

public class CarEx {
    public static void main(String[] args) {
        Car car = new Car();
        System.out.println(car.model);  // Hyundai
        System.out.println(car.color);  // null
        System.out.println(car.speed);  // 0
    }
}

1) 필드 사용

  • 클래스로부터 객체가 생성된 후 필드 값을 읽고 변경하는 것이 가능하다.
  • 도트. 객체 접근 연산자로 필드를 읽고 변경할 수 있다.
public class CarEx {
    public static void main(String[] args) {
        Car car = new Car();
        System.out.println(car.model);  // Hyundai

        car.model = "Tesla";
        System.out.println(car.model);  // Tesla
    }
}

6.7 생성자 선언과 호출

  • new 연산자는 객체를 생성한 후 생성자를 호출해 객체를 초기화한다.
  • 객체 초기화란 객체를 사용할 준비를 하는 것

1) 기본 생성자

  • 모든 클래스는 기본 생성자가 존재하며, 하나 이상 가질 수 있다.
  • 클래스에 생성자 선언이 없으면, 컴파일러기본 생성자를 바이트 코드 파일에 자동으로 추가시킨다.
  • 개발자가 명시적으로 선언한 생성자가 있다면 기본생성자를 추가하지 않는다.

2) 생성자 선언

  • 생성자는 리턴 타입이 없고 클래스 이름과 동일하다.

3) 필드 초기화

  • 필드 선언 시 초기값을 대입하는 것이 가능하다.
  • 생성자의 매개변수명이 필드명과 동일하기 때문에 this 키워드를 필드명 앞에 붙여준다.
    this 키워드는 현재 객체를 뜻한다.
public class Car {
    String model = "Hyundai";
    String color;

    int speed;

    public Car(String model, String color, int speed) {
        this.model = model;
        this.color = color;
        this.speed = speed;
    }
}

4) 생성자 오버로딩

  • 생성자 오버로딩이란 매개변수를 달리하는 생성자를 여러 개 선언하는 것. (매개변수의 타입, 개수, 순서가 다르다.)
    단, 매개변수의 타입과 개수, 순서가 동일한데 매개변수명만 바꾸는 것은 생성자 오버로딩이 아니다.
public class Car {
    String model = "Hyundai";
    String color;

    int speed;

    public Car() {
    }

    public Car(String model) {
        this.model = model;
    }

    public Car(String model, String color) {
        this.model = model;
        this.color = color;
    }

    public Car(String model, String color, int speed) {
        this.model = model;
        this.color = color;
        this.speed = speed;
    }
}

public class CarEx {
    public static void main(String[] args) {
        Car car = new Car();
        System.out.println(car.model);  // Hyundai

        Car car3 = new Car("Chevrolet");
        System.out.println(car3.model); // Chevrolet
        System.out.println(car3.color); // null
        System.out.println(car3.speed); // 0

        Car car2 = new Car("BMW", "green");
        System.out.println(car2.model); // BMW
        System.out.println(car2.color); // green
        System.out.println(car2.speed); // 0

        Car car1 = new Car("Kia", "red", 120);
        System.out.println(car1.model); // Kia
        System.out.println(car1.color); // red
        System.out.println(car1.speed); // 120
    }
}

5) 다른 생성자 호출

  • 생성자 오버로딩이 많아지면 생성자 간 중복 코드가 발생한다.
    이 경우에는 공통 코드를 한 생성자에 집중시키고, 나머지 생성자는 this()를 사용해
    공통 코드 생성자를 호출하는 방법으로 개선할 수 있다.
  • this는 생성자의 첫 줄에 작성되며 다른 생성자를 호출하는 역할
public class Car {
    String model = "Hyundai";
    String color;

    int speed;

    public Car() {
    }

    public Car(String model) {
        this(model, "gray", 200);
    }

    public Car(String model, String color) {
        this(model, color, 170);
    }

    public Car(String model, String color, int speed) {
        this.model = model;
        this.color = color;
        this.speed = speed;
    }
}

public class CarEx {
    public static void main(String[] args) {
        Car car = new Car();
        System.out.println(car.model);  // Hyundai

        Car car3 = new Car("Chevrolet");
        System.out.println(car3.model); // Chevrolet
        System.out.println(car3.color); // gray
        System.out.println(car3.speed); // 200

        Car car2 = new Car("BMW", "green");
        System.out.println(car2.model); // BMW
        System.out.println(car2.color); // green
        System.out.println(car2.speed); // 170

        Car car1 = new Car("Kia", "red", 120);
        System.out.println(car1.model); // Kia
        System.out.println(car1.color); // red
        System.out.println(car1.speed); // 120
    }
}

6.8 메서드 선언과 호출

  • 메서드 선언은 객체의 동작을 실행 블록으로 정의하는 것
  • 메서드 호출은 실행 블록을 실제 실행하는 것

1) 리턴 타입

  • 호출한 곳으로 전달하는 결과값의 타입
  • 리턴 타입이 있는 메서드는 반드시 실행 블록 안에 return 문을 지정해야 한다.
  • 리턴 값이 없는 경우에는 void로 작성

2) 메서드명

  • 첫 문자는 소문자로 시작

3) 매개변수

  • 매개값을 받기 위해 사용
  • 전달할 매개값이 없다면 매개변수 생략 가능

4) 실행 블록

  • 메서드 호출 시 실행되는 부분

5) 메서드 호출

  • 메서드는 객체가 존재하지 않으면 호출할 수 없다. 즉, 클래스로부터 객체를 생성된 후 호출 가능
  • 객체 내부에서는 메서드명으로만 호출한다.
  • 외부 객체에서는 참조 변수와 도트.연산자를 이용해 호출한다.

6) 가변길이 매개변수

  • 매개변수의 개수와 상관없이 매개값을 줄 수 있다.
  • 매서드 호출 시 매개값을 쉼표로 구분해 개수와 상관없이 제공할 수 있다.
  • 매개값들은 자동으로 배열 항목으로 변환되어 메서드에서 사용된다.
public class Computer {

    void count(int ... values) {
        System.out.println(values.length);
    }

    int sum(int ... values) {
        count(values);  // 객체 내부 호출

        int sum = 0;
        for (int i = 0; i < values.length; i++) {
            sum += values[i];
        }

        return sum;
    }

}

public class ComputerEx {
    public static void main(String[] args) {
        Computer computer = new Computer();
        int result = computer.sum(1, 3, 5, 7, 9, 10);     // 외부 객체 호출
        System.out.println(result);
        // 6
        // 35

        int result2 = computer.sum(new int[]{2, 4, 6});   // 직접 배열을 매개값으로 제공
        System.out.println(result2);
        // 3
        // 12
    }
}

7) return문

  • 메서드의 실행을 강제 종료하고 호출한 곳으로 돌아간다는 의미
  • 메서드 선언에 return 타입이 있는 경우 return문 뒤에 리턴 값을 지정해줘야 한다.

8) 메서드 오버로딩

  • 메서드 이름은 같지만(리턴타입은 무관) 매개변수의 타입, 개수, 순서가 다른 메서드를 여러개 선언하는 것.
  • 메서드 오버로딩의 목적은 다양한 매개값을 처리하기 위해서이다.

6.9 인스턴스 멤버

  • 필드와 메서드는 선언 방법에 따라 인스턴스 멤버정적 멤버로 구분할 수 있다.
  • 인스턴스 멤버필드는 객체가 생성될 때마다 새로 Heap 영역에 생성되고,
    메서드는 클래스가 로드될 때 Method 영역에 할당되며, 객체가 생성되면 객체에 소속되어 공유해서 사용한다.
    즉, 객체 없이는 사용하지 못하도록 제한이 걸려있다.
  • 정적 멤버Method 영역에 생성되어 모든 객체에 공유된다.
구분설명
인스턴스(instance) 멤버객체에 소속된 멤버로 객체를 생성해야만 사용할 수 있다.
정적(static) 멤버클래스에 고정된 멤버로 객체 없이도 사용할 수 있다.

출처👉geeksforgeeks

1) this 키워드

  • 객체 내부에서는 인스턴스 멤버에 접근하기 위해 this를 사용한다.
  • 객체는 자신을 this라고 한다.

6.10 정적 멤버

  • Method 영역의 클래스에 고정적으로 위치하는 멤버로 객체를 생성할 필요 없이 클래스를 통해 바로 사용이 가능하다.

1) 정적 멤버 선언

  • static 키워드를 사용해 정적 멤버를 선언한다.
  • 객체마다 가지고 있을 필요가 없는 공용적인 필드는 정적 필드로 선언하는 것이 좋다.
  • 인스턴스 필드를 이용하지 않는 메서드는 정적 메서드로 선언하는 것이 좋다.

2) 정적 멤버 사용

  • 클래스가 메모리로 로딩되면 정적 멤버를 바로 사용할 수 있다.
  • 클래스명과 도트. 연산자로 접근하여 사용한다.

3) 정적 블록

  • 일반적으로 정적 필드는 필드 선언과 동시에 초기값을 주지만 정적 블록을 이용해 초기화할 수도 있다.
  • 정적 블록은 클래스가 메모리로 로딩될 때 자동으로 실행된다.

4) 인스턴스 멤버 사용 불가

  • 정적 메서드나 정적 블록은 내부에 인스턴스 필드나 인스턴스 메서드를 사용할 수 없다.
  • 객체 자신의 참조인 this도 사용할 수 없다.
  • main()메서드도 정적 메서드이므로 객체 생성 없이 인스턴스 필드와 메서드를 바로 사용할 수 없다.
public class Bird {
    String color;

    void fly() {
        System.out.println("날다");
    }

    public static void main(String[] args) {
        // color = "red";  // 컴파일 에러 : non-static variable color cannot be referenced from a static context
        // fly();          // 컴파일 에러 : non-static method fly() cannot be referenced from a static context

        Bird bird = new Bird();
        bird.color = "red";
        System.out.println(bird.color);   // red
        bird.fly();                       // 날다
    }
}

6.11 final 필드와 상수

  • final 필드와 상수를 선언해 값 변경을 막고 읽기만 허용할 수 있다.

1) final 필드 선언

  • final 필드는 초기값이 저장되면 최종값이 된다. 즉, 실행 도중에 수정할 수 없다.
  • 필드에 초기값을 주는 방법은 2가지뿐이다.
    필드 선언 시 초기값 대입
    생성자에서 초기값 대입
public class Korean {
    // 인스턴스 final 필드 선언
    final String nation = "Korea";
    final String ssn;

    // 인스턴스 필드 선언
    String name;

    public Korean(String ssn, String name) {
        this.ssn = ssn;
        this.name = name;
    }
}

public class KoreanEx {
    public static void main(String[] args) {
        Korean person = new Korean("111111-2222222", "홍길동");
        // person.ssn = "333333-4444444";      // 컴파일 에러 : cannot assign a value to final variable ssn
        System.out.println(person.nation);     // Korea
        System.out.println(person.ssn);        // 111111-2222222
        System.out.println(person.name);       // 홍길동

        person.name = "김영희";
        System.out.println(person.name);       // 김영희
    }
}

2) 상수 선언

  • 불변의 값
  • 상수는 static이면서 final인 특성을 가져야 한다. 즉, 상수는 객체마다 저장할 필요가 없고, 여러 개의 값을 가지면 안 된다.
  • 상수 이름은 모두 대문자로 작성하는 것이 관례이다.
  • 일반적으로 상수는 선언과 동시에 초기값을 주지만 정적 블록을 이용해 초기화할 수도 있다.
public class KoreanEx {
    public static void main(String[] args) {
        System.out.println(Korean.city);        // Seoul
        System.out.println(Korean.nation);      // Korea

        // Korean.city = "Busan";      // 컴파일 에러 : cannot assign a value to final variable city
    }
}

6.12 패키지

  • 패키지는 클래스 일부분이자 클래스를 식별하는 용도
  • 주로 개발 회사의 도메인 이름의 역순으로 작성하고, 마지막에는 프로젝트 이름을 붙여주는 것이 일반적이다.
  • 상위 패키지와 하위 패키지를 도트.로 구분한다.

1) 패키지 선언

  • package 키워드와 함께 패키지 이름을 기술한 것으로, 소스 파일 최상단에 위치해야 한다.
  • 컴파일러는 클래스 패키지 선언을 보고 디렉토리를 자동 생성시킨다.
  • 모두 소문자로 작성하는 것이 관례이다.

2) import 문

  • 다른 패키지에 있는 클래스를 사용하려면 import 문을 이용해서 어떤 패키지를 사용하는지 명시해야 한다.
  • 동일한 패키지에 포함된 다수의 클래스를 사용해야 한다면 클래스 이름을 생략하고 *를 사용할 수 있다.
  • import 문은 하위 패키지를 포함하지 않는다.
package ch06.sec12.hyundai;

import ch06.sec12.hankook.SnowTire;
import ch06.sec12.kumho.*;

public class Car {

    Tire tire1 = new Tire();
    ch06.sec12.hankook.Tire tire2 = new ch06.sec12.hankook.Tire();

    SnowTire tire3 = new SnowTire();

    AllSeasonTire allSeasonTire = new AllSeasonTire();

}

6.13 접근 제한자

  • 필드와 메서드가 외부로 노출되지 않도록 해 객체의 무결성(결점이 없는 성질)을 유지하기 위해서
    자바는 접근 제한자를 사용한다.
  • default는 접근 제한자가 아니라 접근 제한자가 붙지 않은 상태를 말한다.
접근 제한자제한 범위
public없음
protected같은 패키지이거나 자식 객체만 사용 가능
default같은 패키지
private객체 내부

출처👉codingeek - Java Access Modifiers- public, protected, private and default

6.14 Getter와 Setter

  • 객체의 필드를 외부에서 읽고 변경할 경우 객체의 무결성(결점이 없는 성질)이 깨질 수 있다.
    따라서, 외부에서의 직접적인 필드 접근을 막고 Setter 메서드를 통해서만 필드 값을 변경할 수 있도록 한다.
  • 외부에서 객체의 필드를 읽을 때에도 Getter 메서드를 통해서만 접근한다.
package ch06.sec14;

public class Car {

    private int speed;
    private boolean stop;

    String model;

    public int getSpeed() {
        return speed;
    }

    public void setSpeed(int speed) {
        if (speed < 0) {
            this.speed = 0;
            return;
        }

        this.speed = speed;
    }

    public boolean isStop() {
        return stop;
    }

    public void setStop(boolean stop) {
        this.stop = stop;

        if (stop == true) {
            this.speed = 0;
        }
    }

    @Override
    public String toString() {
        return "Car{" +
                "speed=" + speed +
                ", stop=" + stop +
                ", model='" + model + '\'' +
                '}';
    }
}

package ch06.sec14;

public class CarEx {
    public static void main(String[] args) {
        Car car = new Car();
        car.model = "hyundai";
        System.out.println(car);    // Car{speed=0, stop=false, model='hyundai'}

        // car.speed = 2;           // 컴파일 에러 : java: speed has private access in ch06.sec14.Car
        car.setSpeed(3);
        System.out.println(car);    // Car{speed=3, stop=false, model='hyundai'}

        car.setStop(true);
        System.out.println(car);    // Car{speed=0, stop=true, model='hyundai'}

        System.out.println(car.getSpeed());     // 0
    }
}

6.15 싱글톤 패턴

  • 단 1개의 객체만 생성해서 사용하는 패턴
  • 생성자를 private 접근 제한해서 외부에서 new 연산자로 생성자를 호출할 수 없도록 막는 것이다.
    대신 싱글톤 패턴이 제공하는 정적 메소드를 통해 간접적으로 객체를 얻을 수 있다.
    일반적으로 정적 메서드명은 getInstance로 작성한다.
package ch06.sec14;

public class SingletonEx {
    private static SingletonEx singletonEx = new SingletonEx();
    
    private SingletonEx() {}
    
    public static SingletonEx getInstance() {
        return singletonEx;
    }

}

package ch06.sec14;

public class Ex {
    public static void main(String[] args) {
        SingletonEx instance1 = SingletonEx.getInstance();
        SingletonEx instance2 = SingletonEx.getInstance();

        System.out.println(instance1 == instance2); // true
    }
}

profile
기술로 세상을 이롭게

0개의 댓글