TIL(Today I Learned)/java

[내일배움캠프/2주차] JAVA 문법종합반 3주차 강의 1. 클래스

jy3574 2024. 10. 18. 22:35

<목차>

1. 객체지향 프로그래밍에 대한 개념을 이해한다.

2. 클래스를 설계하는 방법에 대해 학습한다.

3. 객체의 구성요소(필드, 메서드, 생성자)에 대해서 학습한다.

4. 클래스 변수, 인스턴스 변수의 차이점에 대해서 학습한다.

5. 생성자와 생성자 오버로딩에 대해 학습한다.

6. this와 this() 키워드에 대해 학습한다.

7. 접근 제어자에 대해 학습한다.

8. package와 import에 대해 학습한다.


# 클래스 (설계도)

객체지향 프로그래밍 이해하기

-어떠한 제품을 만들기 위해 부품들을 하나씩 조립해서 완성시키는 것처럼

-소프트웨어도 필요한 객체를 만들고 하나씩 조립해서 하나의 완성된 프로그램을 만들 수 있다.

==이런 기법을 사용하는 것을 객체지향 프로그래밍이라고 한다.

 

1. 객체

-세상에 존재하는 물체, 식별이 가능한 것

-속성과 행위로 구성되어 있다.

---예를 들면 물리적으로 존재하는 자동차, 도서관, 계산기 등을 객체라고 볼 수 있다.

----운동, 강의와 같은 개념적인 것 또한 식별이 가능하기 때문에 객체라 볼 수 있다.

 

ex> 자동차의 속성과 행위

-자동차의 속성 : 회사, 모델, 색상, 가격, 속도 등

-자동차의 행위 : 가속, 브레이크, 기어변속, 조명, 경적 등

 

-Java에서는 이러한 속성과 행위를 필드와 메서드로 정의하여 구현할 수 있다.

-객체 모델링 : 현실 세계에 있는 객체를 소프트웨어의 객체로 설계하는 것 

 

2. 객체 간의 협력

-사람이라는 객체와 자동차라는 객체는 서로 행위를 통해 상호작용을 하며 협력할 수 있다.

---사람이 자동차의 가속 페달을 밟으면 자동차는 이에 반응하여 속도를 올리며 앞으로 이동한다.

---사람이 자동차의 브레이크 페달을 밟으면 자동차는 이에 반응하여 속도를 줄이며 정지한다.

-이렇듯 소프트웨어의 객체들끼리는 행위를 정의하는 java의 메서드를 통해 상호작용한다.

 

-소프트웨어의 객체들은 메서드를 통해 데이터를 주고 받을 수도 있음.

-메서드를 호출할 때 (괄호)안에 데이터를 넣어 호출할 수 있는데, 이때 이 괄호 안에 넣는 데이터를 '파라미터' 또는 '매개값'이라고 표현

-메서드에서 작업을 수행한 후 객체에게 실행 결과인 값을 반환할 수 있는데 이때 반환되는 값을 '리턴값'이라 표현

 

3. 객체 간의 관계

-객체는 수많은 관계를 맺고 있음

---예를 들어 사람객체는 자동차 객체를 사용하는 '사용의 관계'를 맺고 있음

----자동차를 만들기 위해서는 타이어, 차문, 핸들 등의 부품이 필요

 

*사용 관계

-사람(객체)은 자동차(객체)를 사용하는 '사용관계'

 

*포함 관계

-자동차(객체)에 포함되어 있는 타이어(객체), 문(객체), 핸들(객체) '포함관계'

 

*상속 관계

-공장이 자동차만 생산하는 것이 아닌 기차도 생산한다는 가정,

---자동차(객체)와 기차(객체)는 하나의 공통된 기계시스템(객체)를 토대로 만들어진다고 가정

----자동차(객체)와 기차(객체)는 기계 시스템(객체)를 상속받는 '상속관계' 라고 볼 수 있음.

 

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

-객체지향 프로그래밍의 필연적 개념 : 캡슐화, 상속, 다형성, 추상화

 

*캡슐화

-필드(속성)와 메서드(행위)를 하나로 묶어 객체로 만든 후 실제 내부 구현 내용은 외부에서 알 수 없게 감추는 것을 의미.

-외부 객체에서는 캡슐화된 객체의 내부 구조를 알 수 없기 때문에 노출시켜 준 필드 혹은 메서드를 통해 접근할 수 있다.

-필드와 메서드를 캡슐화해서 숨기는 이유는 외부 객체에서 해당 필드와 메서드를 잘못 사용하여 객체가 변화하지 않게 한다.

-java에서는 캡슐화된 객체의 필드와 메서드를 노출시킬지 감출지 결정하기 위해 접근제어자를 사용.

 

*상속

-객체지향 프로그래밍에는 부모 객체와 자식 객체가 존재.

-부모객체 : 가지고 있는 필드와 메서드를 자식 객체에게 물려주어 자식 객체가 이를 사용할 수 있도록 만든다. = 상속

-상속을 하는 이유

1️⃣ 각각의 객체들을 상속관계로 묶음으로써 객체 간의 구조를 파악하기 쉬워진다.

2️⃣ 필드와 메서드를 변경하는 경우 부모 객체에 있는 것만 수정하게 되면 자식 객체 전부 반영이 되기 때문에 일관성을 유지하기 좋다.

3️⃣ 자식 객체가 부모 객체의 필드와 메서드를 물려받아 사용할 수 있기 때문에 코드의 중복이 줄어들며 코드의 재사용성이 증가된다.

 

*다형성

-객체가 연산을 수행할 때 하나의 행위에 대해 각 객체가 가지고 있는 고유한 특성에 따라 다른 여러가지 형태로 재구성되는 것을 의미

---자동차 객체를 만들때 A자동차와 B자동차 객체의 경적소리가 다르다면 경적을 울리는 행위, horn()메서드의 구현을 다르게 재정의하여 사용할 수 있다.

 

*추상화

-객체에서 공통된 부분들을 모아 상위 개념으로 새롭게 선언하는 것을 의미

-공통적이고 중요한 것들을 모아 객체를 모델링한다.

---현실 세계의 여러 종류의 자동차들이 공통적으로 가지고 있는 가속, 브레이크, 속도 같은 것들을 모아 자동차라는 객체를 모델링 할 수 있다.

 

5. 객체와 클래스

-객체를 생성하기 위해서 설계도가 필요하다.

-소프트웨어에서 객체를 만들기 위해서는 설계도에 해당하는 '클래스'가 필요

-클래스를 토대로 생성된 객체를 해당 클래스의 '인스턴스'라고 부르며, 이 과정을 '인스턴스화'라고 부른다.

-동일한 클래스로 여러개의 인스턴스를 만들 수 있다.

-자동차 클래스를 통해 만들어진 하나의 자동차를 '인스턴스'라고 부르며, 이러한 여러개의 인스턴스를 통틀어서 자동차 '객체'라고 표현할 수 있음.

 

 

클래스 설계

-클래스는 필드, 생성자, 메서드로 구성

-클래스를 만들기 위해서는 4가지 step이 필요함.

 

1. 클래스 선언

2. 객체가 가지고 있어야 할 필드(속성)를 정의

3. 객체를 생성하는 방식을 정의(생성자)

4. 객체가 가지고 있어야 할 메서드(행위)를 정의

 

<자동차 예시>

1. 클래스 선언

public class Car {}

-자동차 클래스 선언, public 사용해서 공개된 클래스로...

 

2. 객체가 가지고 있어야 할 필드(속성)를 정의

-필드는 객체의 속성으로써 데이터를 저장하는 역할을 한다.

public class Car {
    String company; //자동차 회사명
    String model; //자동차 모델명
    String color; //자동차 색상
    double price; //자동차 가격
    double speed; //자동차 속도, km/h
    char gear; //기어의 상태, P/R/N/D
    boolean lights; //자동차 조명 on/off
}

-String 타입의 company 변수 선언 = 자동차 회사명을 저장

 

 

3. 객체를 생성하는 방식을 정의(생성자)

public class Car {
    String company; //자동차 회사명
    String model; //자동차 모델명
    String color; //자동차 색상
    double price; //자동차 가격
    double speed; //자동차 속도, km/h
    char gear; //기어의 상태, P/R/N/D
    boolean lights; //자동차 조명 on/off

    public Car() {} //기본 생성자
}

-자동차 객체의 생성 방식을 선언

---생성자는 반환 타입이 없고 이름은 클래스의 이름과 동일

---(괄호)안에 아무것도 없는 생성자를 '기본 생성자'라고 함

 

 

4. 객체가 가지고 있어야 할 메서드(행위)를 정의

public class Car {
    String company; //자동차 회사명
    String model; //자동차 모델명
    String color; //자동차 색상
    double price; //자동차 가격
    double speed; //자동차 속도, km/h
    char gear; //기어의 상태, P/R/N/D
    boolean lights; //자동차 조명 on/off

    public Car() {} //기본 생성자

    double gasPedal(double kmh) {
        speed = kmh;
        return speed;
    }
    double brakePedal() {
        speed = 0;
        return speed;
    }
    char changeGear(char type) {
        gear = type;
        return gear;
    }
    boolean onOffLights() {
        lights = !lights;
        return lights;
    }
    void horn() {
        System.out.println("빵빵");
    }
}

-반환 타입이 double 인 gasPedal(double kmh) 메서드를 선언

---매개변수인 kmh를 통해 매개값을 전달 받아 자동차의 속성인 speed 필드에 해당값을 저장

 

-반환 타입이 double 인 brakePedal() 메서드를 선언

---메서드가 호출되면 자동차의 속성인 speed 필드의 값을 0으로 바꾼 후 speed의 값을 반환

 

-반환 타입이 char인 changeGear(char type) 메서드를 선언

---매개변수인 type을 통해 매개값을 전달받아 메서드가 호출되면 자동차의 속성인 gear 필드에 해당 값을 저장

 

-반환 타입이 boolean인 onOffLights() 메서드를 선언

---매서드가 호출되면 자동차의 속성인 lights의 현재 논리 값을 반전 시킨 후 lights의 값을 반환

 

-반환 값이 없는 horn() 메서드를 선언

---"경적을 울린다" 밖에 없으니까 반환값이 필요없음

---메서드가 호출되면 자동차의 경적소리인 "빵빵"이 출력

 

 

객체 생성과 참조형 변수

 

1. 객체 생성

new Car(); //Car클래스 객체 생성

-객체 생성 연산자인 'new'를 사용하면 클래스로부터 객체를 생성할 수 있다.

-new 연산자 뒤에는 해당 클래스의 생성자 호출 코드를 작성한다.

-형태가 Car(); 기본생성자의 형태와 같기 때문에 new 연산자에 의해 객체가 생성되면서 기본 생성자가 호출된다.

 

2. 참조형 변수

Car car1 = new Car();
Car car2 = new Car();

-참조형 변수 = 주소값 저장

-Car 클래스의 객체인 car1,2 인스턴스 생성

-new 연산자를 통해서 객체가 생성되면 해당 인스턴스의 주소가 반환되기 때문에 해당 클래스의 참조형 변수를 사용하여 받아줄 수 있다.

 

3. 객체 배열

-객체는 참조형 변수와 동일하게 취급되기 때문에 배열 또는 컬렉션에도 저장하여 관리할 수 있다.

public class Main {
    public static void main(String[] args) {
        Car[] carArray = new Car[3]; //carArray 라는 이름의 길이가 3인 Car 배열을 만든다.
        Car car1 = new Car();
        car1.changeGear('P');
        carArray[0] = car1;

        Car car2 = new Car();
        car2.changeGear('N');
        carArray[1] = car2;
        
        Car car3 = new Car();
        car3.changeGear('D');
        carArray[2] = car3;

        for(Car car : carArray) {
            System.out.println("car.gear = " + car.gear);
        }
    }
}

<출력>

car.gear = P

car.gear = N

car.gear = D

 

 

객체의 속성 : 필드

 

1. 필드

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

-객체의 필드는 크게 '고유 데이터', '상태 데이터', '객체 데이터' 로 분류할 수 있다.

-소프트웨어를 만드는 부품(객체)데이터 라고 볼 수 있다.

public class Tire {
    public Tire() {}
}

-위 처럼 타이어, 문, 핸들에 대한 클래스 생성

public class Car {

//고유 데이터
    String company; //자동차 회사명
    String model; //자동차 모델명
    String color; //자동차 색상
    double price; //자동차 가격

//상태 데이터
    double speed; //자동차 속도, km/h
    char gear; //기어의 상태, P/R/N/D
    boolean lights; //자동차 조명 on/off

//객체 데이터
Tire tire;
Door door;
Handle handle;

-이렇게 가져와서 자동차 클래스에 저장

 

2. 필드의 초기값과 초기화

-우리가 선언한 클래스의 필드들은 기본적으로 초기값을 제공하지 않을 겨우 객체가 생성될 때 자동으로 기본값으로 초기화한다.

--초기값을 제공하는 방법은 '필드타입 필드명 = 값' 이렇게 직접 초기화 할 수 있다.

 

*필드 타입별 기본값

데이터 타입 기본값
byte 0
char \u0000(공백)
short 0
int 0
long 0L
float 0.0F
double 0.0
boolean false
배열 null
클래스 null
인터페이스 null

 

3. 필드 사용방법

-'필드를 사용한다'라는 의미는 필드의 값을 변경하거나 읽는 것을 의미

---클래스에 필드를 정의하여 선언했다고 해서 바로 사용할 수 있는 것은 아님

---클래스는 설계도일 뿐 실제로 필드의 데이터를 가지고 있는 것은 객체이다.

---따라서 객체를 생성한 후에 필드를 사용할 수 있다.

 

*외부접근

Car car = new Car();

-이렇게 객체를 생성했다면 참조변수 car를 이용하여 외부에서 객체 내부의 필드에 접근하여 사용할 수 있다. 

--이때 객체의 내부 필드에 접근하는 방법은 도트(.)연산자를 사용하면 된다.

car.color = "blue";

-필드 color 에 blue 데이터를 저장

 

*내부접근

-객체 내부 메서드에서도 내부 필드에 접근할 수 있다.

double brakePedal() {
    speed = 0;
    return speed;
}

 

 

객체의 행위 : 메서드

-메서드 : 객체의 행위를 뜻하며 객체 간의 협력을 위해 사용된다.

---메서드의 행위를 정의하는 방법은 {블록} 내부에 실행할 행위를 정의하면 된다.

 

1. 메서드 선언

리턴타입 매서드명(매개변수,...){
    실행할 코드 작성
}

 

*리턴 타입

double brakePedal(){...} // double 타입 반환
char changeGear(char type){...} //char 타입 반환
void horn(){...} // 반환할 값 없음

-리턴타입 : 메서드가 실행된 후 호출을 한 곳으로 값을 반환할 때 해당 값의 타입을 의미

---return 리턴타입의 반환값;

---주의할 점 : 메서드에 리턴 타입을 선언하여 반환할 값이 있다면 반드시 return 문으로 해당하는 리턴 타입의 반환값을 지정해야한다.

 

-반환할 값이 없을 때는 리턴 타입에 void를 작성해야한다.

---반환값이 없음으로 return 문을 반드시 지정할 필요는 없다.

---메서드는 실행할 때 return문을 만나면 종료하게 되는데 void 타입일 때 return; 을 사용하여 원하는 지점에서 메서드를 종료할 수도 있다.

 

*매개변수

double gasPedal(double kmh, char type) {
    speed = kmh;
    return speed;
}

-매개변수는 메서드를 호출할 때 메서드로 전달하려는 값을 받기 위해 사용되는 변수다.

-gasPedal(double kmh, char type) 메서드의 매개변수는 double 타입의 kmh, char 타입의 type이다.

---해당 매개변수에 값을 전달하기 위해서는 순서와 타입에 맞춰 값을 넣어주면 된다.

---gasPedal(100,'D');

-전달하려는 값이 없다면 생략 가능하다.

 

-가변 길이의 매개변수도 선언할 수 있다.

void carSpeeds(double ... speeds) {
    for (double v : speeds) {
        System.out.println("v =" + v);
    }
}

-double ... speeds 처럼 ...을 사용하면 아래처럼 매개값을 , 로 구분하여 개수 상관없이 전달 가능하다.

---carSpeeds(100,30,50);

---carSpeeds(110,120,150,50);

 

 

2. 메서드 호출 방법

-메서드를 호출한다 : 메서드 블록 내부에 작성된 코드를 실행한다는 의미

-필드와 마찬가지로 클래스의 메서드를 정의하여 선언했다고 해서 바로 사용할 수 있는 것은 아니다.

-클래스는 설계도일 뿐 메서드는 객체의 행위를 정의한 것이다.

-객체를 생성한 후에 메서드를 사용할 수 있다.

 

*외부 접근

Car car = new Car(0;

-이렇게 객체를 생성했다면 참조변수 car를 이용하여 외부에서 객체 내부의 메서드에 접근하여 사용할 수 있다.

--이때 객체의 내부 메서드에 접근하는 방법은 도트(.)연산자를 사용하면 된다.

---car.gasPedal();

-메서드가 매개변수를 가지고 있다면 반드시 호출할 때 매개변수의 순서와 타입에 맞게 매개값을 넣어줘야 한다.

---car.gasPedal(100,'D');

 

*내부 접근

-객체 내부 메서드에서도 내부 메서드에 접근하여 호출할 수 있다.

double gasPedal(double kmh, char type) {
    changeGear(type);
    speed = kmh;
    return speed;
}

-gasPedal() 메서드 내부에서 해당 객체의 changeGrear(type); 메서드를 호출할 수 있다.

 

*반환값 저장

double speed = car.gasPedal(100,'D');

-double 타입의 변수 speed를 사용하여 double gasPedal(double kmh, char type) 메서드의 double 타입의 반환값을 받아 저장할 수 있다.

 

 

3. 메서드 오버로딩

-오버로딩 : 함수가 하나의 기능만을 구현하는 것이 아니라 하나의 메서드 이름으로 여러 기능을 구현하도록 하는 Java의 기능

-한 클래스 내에 이미 사용하려는 이름과 같은 이름을 가진 메서드가 있더라도, 매개변수의 개수 또는 타입, 순서가 다르면 동일한 이름을 사용해서 메서드를 정의할 수 있다.

 

*오버로딩의 조건

-메서드의 이름이 같고, 매개변수의 개수, 타입, 순서가 달라야한다.

-'응답 값'만 다른 것은 오버로딩을 할 수 없다.

-접근 제어자만 다른 것도 오버로딩을 할 수 없다.

-오버로딩은 매개변수의 차이로만 구현할 수 있다.

 

*오버로딩의 장점

1️⃣ 메서드 이름 하나로 상황에 따른 동작을 개별로 정의할 수 있다.

-메세지 출력할 때 쓰는 println()이 있는데, 이것의 매개변수로는 int, double, String, boolean 등 다양하게 넣을 수 있다.

 

2️⃣메서드의 이름을 절약할 수 있다.

-만약 오버로딩이 안된다면 println() -> printlnInt(), printlnDouble() 처럼 메서드명이 길어지고 낭비 되었을 것이다.

 

 

4. 기본형 & 참조형 매개변수

*기본형 매개변수

-메서드를 호출할 때 전달할 매개값으로 지정한 값을 메서드의 매개변수에 복사해서 전달.

-매개변수의 타입이 기본형일 때는 값 자체가 복사되어 넘어가기 때문에 매개값으로 지정된 변수의 원본 값이 변경되지 않는다.

 

*참조형 매개변수

-메서드를 호출할 때 전달할 매개값으로 지정한 값의 주소를 매개변수에 복사해서 전달.

-매개변수를 참조형으로 선언하면 값이 저장된 곳의 원본 주소를 알 수 있기 때문에 값을 읽어오는 것은 물론 값을 변경하는 것도 가능하다.

-메서드의 매개변수 뿐만 아니라 반환 타입도 참조형이 될 수 있다.

---반환 타입이 참조형이라는 것은 반환하는 값의 타입이 "실제 값의 주소"라는 의미이다.

 

 

인스턴스 멤버와 클래스 멤버

*멤버 = 필드 + 메서드

-인스턴스 멤버 = 인스턴스 필드 + 인스턴스 메서드

-클래스 멤버 = 클래스 필드 + 클래스 메서드

 

*인스턴스 멤버 & 클래스 멤버

-필드와 메서드는 선언하는 방법에 따라서 인스턴스 멤버와 클래스 멤버로 구분할 수 있다.

-인스턴스 멤버 : 객체 생성 후에 사용할 수 있음.

-클래스 멤버 : 객체 생성 없이도 사용할 수 있음.

 

1. 인스턴스 멤버

-이때까지 앞에서 선언한 필드와 메서드는 전부 객체를 생성해야 사용할 수 있는 인스턴스 멤버

-객체의 인스턴스 필드는 각각의 인스턴스마다 고유하게 값을 가질 수 있다.

-객체가 인스턴스화 할때마다 객체의 메서드들은 인스턴스에 포함되어 매번 생성이 되는 것이 아니라 메서드 영역에 두고 모든 인스턴스들이 공유해서 사용(메모리 효율을 위해)

---대신 무조건 객체를 생성, 인스턴스를 통해서만 메서드가 사용될 수 있도록 제한을 걸어 둔 것

 

2. 클래스 멤버

-클래스는 java의 클래스 로더에 의해 메서드 영역에 저장되고 사용된다.

-클래스 멤버 : 메서드 영역의 클래스와 같은 위치에 고정적으로 위치하고 있는 멤버를 의미

-클래스 멤버는 객체의 생성 필요 없이 바로 사용 가능

 

*클래스 멤버 선언

-필드와 메서드를 클래스 멤버로 만들기 위해서는 static 키워드를 사용

-일반적으로 인스턴스마다 모두 가지고 있을 필요 없는 공용적인 데이터를 저장하는 필드는 클래스 멤버로 선언하는 것이 좋음

-인스턴스 필드를 사용하지 않고 실행되는 메서드가 존재한다면 static 키워드를 사용하여 클래스 메서드로 선언하는 것이 좋음

 

<주의할 점>

-클래스 멤버로 선언된 메서드는 인스턴스 멤버를 사용할 수 없다.

---클래스 멤버는 객체 생성 없이 바로 사용 가능하기 때문에 객체가 생성되어야 존재할 수 있는 인스턴스 멤버를 사용할 수 없다.

-인스턴스 멤버로 선언된 메서드는 클래스 멤버를 사용할 수 있다.

 

ex> car 클래스를 통해 제품을 만들때 만들어지는 자동차의 회사가 'genesis'로 고정되어 있다고 가정하면,

static String company = "Genesis";
String getCompany() {
    return company;
}

-모든 car 클래스의 객체마다 company 인스턴스 필드를 가지고 있을 필요 없이 클래스 필드로 만들어 공유하게 만든다면 메모리를 효율적으로 사용할 수 있음

또한, 인스턴스 메서드인 getCompany()는 클래스 필드인 company를 사용할 수 있음

static String setCompany(String companyName) {
    company = companyName;
    return company;
}

-이렇게 자동차 회사를 변경할 수 있는 클래스 메서드로 만들어서 사용할 수 있다. 이때, 인스턴스 필드인 model을 사용하려고 하면 오류 발생.

 

*클래스 멤버 사용

Car.company = "Audi";
String companyName = Car.setCompany("Benz");

-클래스 멤버를 사용하려면 클래스의 이름과 함께 도트. 연산자를 사용하면 된다.

-참조형 변수를 사용하여 클래스 멤버에 접근은 가능하지만 추천x, 클래스 이름으로 접근하는 것이 좋음

 

3. 지역변수

-메서드 내부에 선언한 변수를 의미한다.

-메서드가 실행될 때마다 독립적인 값을 저장하고 관리하게 된다.

-지역 변수는 메서드 내부에서 정의될 때 생성되어 메서드가 종료될때까지만 유지된다.

public class Main {
    public static void main(String[]args){
        Main main = new Main();

        System.out.println(main.getNumber());
    }
 
    public int getNumber(){
        int number = 1; //지역 변수
        number += 1;
        return number; //메서드 종료되면 지역변수 제거됨
    }
}

 

4. final 필드와 상수

-final 필드는 초기값이 저장되면 해당값을 프로그램이 실행하는 도중에는 절대로 수정할 수 없다.

-final 필드는 반드시 초기값을 지정해야한다.

 

*final 선언

final String company = "Genesis";

Car car = new Car();
System.out.println(car.company);

-필드 타입 앞에 final 키워드를 추가하여 final 필드를 선언할 수 있다.

-사용방법은 일반적인 인스턴스 필드와 동일하지만, 수정이 불가능하다.

---car.company = "Benz"; 이런식으로 수정하려면 오류가 발생

 

*상수

-상수의 특징은 값이 반드시 한 개이며 불변의 값을 의미

-인스턴스마다 상수를 저장할 필요가 없다.

static final String COMPANY = "GENESIS";

System.out.println(Car.COMPANY);

-final 앞에 static 키워드를 추가하여 모든 인스턴스가 공유할 수 있는 값이 한개이며 불변인 상수를 선언할 수 있다.

-사용방법은 일반적인 클래스 필드와 동일하지만, 수정이 불가능하다.

-일반적으로 상수는 대문자로 작성하는 것이 관례

 

 

생성자

 

1. 생성자 선언과 호출

public Car() {} // 선언

Car car = new Car(); // 호출

-생성자는 반환 타입이 없고 이름은 클래스의 이름과 동일하다.

-new 연산자에 의해 객체가 생성되면서 생성자가 호출된다. (Car(); 호출)

 

2. 기본 생성자

-기본생성자는 선언할 때 (괄호)안에 아무것도 넣지 않는 생성자를 의미한다.

-모든 클래스는 반드시 생성자가 하나 이상 존재한다.

-만약 클래스에 생성자를 하나도 선언하지 않았다면 컴파일러는 기본 생성자를 바이트 코드 파일에 자동으로 추가시켜준다.

따라서 이러한 경우는 기본 생성자 생략이 가능하다.

public class Car {
    public Car(String model){} // 생성자 선언, 생성자가 한개 이상 선언되었기 때문에 기본생성자를 추가하지 않음.
}

-반대로 단 하나라도 생성자가 선언되어 있다면 컴파일러는 기본 생성자를 추가하지 않는다.

public class Car {
    public Car() {} // 컴파일러가 추가시켜줌
}
class Car {
    Car(){} // 컴파일러가 추가시켜줌
}

-컴파일러에 의해 생성되는 기본 생성자는 해당 클래스의 접근 제어자 (public...)를 따른다.

 

3. 필드 초기화와 생성자 오버로딩

 

*필드 초기화

-생성자는 객체를 초기화하는 역할을 수행한다.

-객체를 만들 때 인스턴스마다 다른 값을 가져야 한다면 생성자를 통해서 필드를 초기화할 수 있다.

---예를 들어 만들어지는 자동차마다 모델, 색상, 가격이 다르다면 생성자를 사용하여 필드의 값을 초기화하는 것이 좋음

-인스턴스마다 동일한 데이터를 가지는 필드는 초기값을 대입하는 것이 좋다.

---예를 들어 자동차가 만들어질 때마다 기어의 상태를 P로 고정해야한다면 초기값을 직접 대입하는 것이 좋음

 

<주의할 점>

-생성자를 통해 필드 값을 초기화하고 기본 생성자를 작성하지 않았는데 기본 생성자를 호출한다면,

---한 개 이상의 생성자가 존재하기 때문에 컴파일러가 자동으로 기본 생성자를 추가하지 않는다.

----따라서  기본생성자가 존재하지 않기 때문에 오류가 발생한다.

 

*생성자 오버로딩

-생성자를 통해 필드를 초기화할때 오버로딩을 적용할 수 있다.

---예를 들어 자동차를 생성할 때 모델, 색상, 가격이 다른 자동차를 여러 대 생성할 수도 있고 색상만 다른 자동차를 여러 대 생성할 수도 있기 때문에 오버로딩을 사용하면 효율적으로 처리할 수 있다.

 

<주의할 점>

-오버로딩을 할 때 개수, 타입, 순서가 동일한데 매개변수명만 다르게 하는 경우는 오버로딩 규칙에 위배되기 때문에 오류가 발생한다.

public Car(String modelName, String colorName, double priceValue)
public Car(String colorName, String modelName, double priceValue)

-modelName 과 colorName 매개변수의 위치가 다르기 때문에 가능할 것 처럼 보이지만

String, String, double : 매개변수의 개수, 타입, 순서가 동일하기 때문에 중복이 불가능하다.

 

 

this
vs
this()

 

1. this

-this : 객체, 인스턴스 자신을 표현하는 키워드

-객체 내부 생성자 및 메서드에서 객체 내부 멤버에 접근하기 위해 사용될 수 있다.

-객체 내부 멤버에 접근할 때 this 키워드가 필수는 아니지만 상황에 따라 필수가 될 수 있다.

public Car(String model, String color, double price){
    model = model;
    color = color;
    price = price;
}

-위 코드처럼 생성자를 선언하는데 매개변수명과 객체의 필드명이 동일할 경우 오류가 발생하지는 않지만 생성자 블록 내부에서 해당 변수들은 객체의 필드가 아닌 가장 가까운 매개변수명을 가리키게 됨으로 자기 자신에게 값을 대입하는 상황이 되어버림.

 

-이럴때 this 키워드를 사용해 해결

Car returnInstance() {
    return this;
}

-this 키워드를 통해 변수명에 해당하는 객체의 필드에 접근하여 받아온 매개변수의 매개값을 객체의 필드에 대입하여 저장할 수 있다.

-this는 인스턴스 자신을 뜻하기 때문에 객체의 메서드에서 리턴 타입이 인스턴스 자신의 클래스 타입이라면 this를 사용하여 인스턴스 자신의 주소를 반환할 수도 있다.

 

2. this()

-this(...) : 객체, 인스턴스 자신의 생성자를 호출하는 키워드

-객체 내부 생성자 및 메서드에서 해당 객체의 생성자를 호출하기 위해 사용될 수 있다.

-생성자를 통해 객체의 필드를 초기화할 때 중복되는 코드를 줄여줄 수 있다.

public Car(String model){
    this.model = model;
    this.color = "red";
    this.price = 900000;
}

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

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

-이렇게 생성자를 선언한다고 했을 때, 코드의 중복이 발생

-이때 this() 키워드를 사용하면 코드의 중복을 제거할 수 있다.

public Car(String model){
    this(model, "red", 900000);
}

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

 

<주의할 점>

-this() 키워드를 사용해서 다른 생성자를 호출할 때는 반드시 해당 생성자의 첫 줄에 작성되어야한다.

---다른 생성자 호출 이전에 코드가 존재하면 오류가 발생

 

 

접근 제어자

-제어자는 클래스, 변수, 메서드의 선언부에 사용되어 부가적인 의미를 부여해준다.

-접근 제어자 : public, protected, default, private

-그 외 제어자 :  static, final, abstract

-하나의 대상에 여러개의 제어자를 조합해서 사용할 수 있으나, 접근 제어자는 단 하나만 사용할 수 있다.

 

1. 접근 제어자

-멤버 또는 클래스에 사용, 외부에서 접근하지 못하도록 제한한다.

-클래스, 멤버변수, 메서드, 생성자에 사용되고, 지정되어 있지 않다면 default다.

-public : 접근 제한이 전혀 없다.

-protected : 같은 패키지 내에서, 다른 패키지의 자손 클래스에서 접근이 가능하다.

-default : 같은 패키지 내에서만 접근이 가능하다.

-private : 같은 클래스 내에서만 접근이 가능하다.

 

*사용 가능한 접근 제어자

 

 

 

*접근 제어자를 이용한 캡슐화(은닉성)

 

 

 

 

*생성자의 접근 제어자

 

 

 

2. Getter, Setter

 

 

 

3. 제어자의 조합

 

 

 

 

 

 

 

 

package, import

 

1. 패키지

 

 

2. import

 


내용정리를 하다가 의문이 드는? 궁금한 점?에 대해서 찾아봤다.

 

1. 객체 vs 인스턴스

-객체 : 우리가 구현한 것

---마우스를 예로 들면 오른쪽 클릭, 왼쪽 클릭, 휠 을 가지고 클릭, 휠 동작을 할거다 라는 것을 정의만 하는 것

 

-인스턴스 : 객체의 실체

---실제로 동작 되도록 정의해서 실체를 만드는 것

 

2. 기본생성자가 필요한 이유

-*java reflection 때문에...?

-reflection은 기본생성자로 객체를 생성하기 때문

-reflection은 이미 로딩이 완료된 클래스에서 또 다른 클래스를 동적으로 로딩하여 생성자, 멤버필드, 멤버 메서드 등을 사용할 수 있도록 한다.

---그래서 reflection이 가져올 수 없는 정보 중 하나가 생성자의 인자(함수 호출시 함수에 전달되는 값)정보들이다.

----따라서 기본생성자 없이 파라미터가 있는 생성자만 존재한다면 객체를 생성할 수 없다.

=기본생성자로 객체를 생성한 후에 클래스의 정보를 가져와서 값을 할당하는 방식

 

*Java reflection

-구체적인 class type을 알지 못하더라도 해당 class의 method, type, variable 에 접근할 수 있도록 해주는 자바 API

-접근 제어자와 상관없이 클래스 객체를 동적으로 생성 = 컴파일된 바이트 코드를 통해 runtime에 동적으로 특정 클래스의 정보를 추출

 

3. 클래스를 여러개 쓰는 이유

-객체 데이터 내용을 정리하면서 굳이...클래스를 여러개를 써야 하나? 생각을 하고 찾아보게 되었더니...!! 따로 써야하는게 맞구나 싶었다.

-클래스를 따로 만드는 이유는 객체지향 프로그래밍이기 때문이다!!

---객체지향은 캡슐화와 재사용성을 중요하게 생각하는데, 이 두가지를 활용하면 복잡한 시스템을 작은 단위로 관리할 수 있기 때문에 각 단위가 자신의 역할을 하면서 코드가 유연해지고 유지 보수가 비교적 쉬워진다.

 

1️⃣작은 단위로 분리되서 맡은 역할을 한다.

-위 정리글의 예시의 자동차처럼 자동차와 타이어, 문, 좌석, 핸들 등은 부품으로 서로 다른 개체라고 볼 수 있다.

--따라서 이런 부품들을 개별적으로 관리하는 것이 조금 더 효율적이다.

---예를 들면 타이어에 공기압, 크기 등의 속성이 생긴다면 복잡해질 수 있기 때문에 따로 관리하는 것이 좋음

 

2️⃣재사용성, 유지보수

-타이어를 예로 들면 자동차 외에도 자전거, 오토바이 등등 다른 객체에 사용할 수 있다.

--이런 상황에서 타이어 클래스를 따로 분리해두면 한번만 정의하고 여러 곳에서 재사용할 수 있다.

-만약 타이어와 관련된 기능인 온도감지 등이 추가가 된다면 수정할 때 타이어 클래스에서만 수정하면 되니까 유지보수가 편해진다.