본문 바로가기
JAVA/이것이 자바다

[Chapter 06] 클래스

by Dream Jin 2022. 12. 31.

Chapter 06 클래스

6.1 객체 지향 프로그래밍

  • 소프트웨어를 개발할 때에도 부품에 해당하는 객체들을 먼저 만들고, 이 객체들을 하나씩 조립해서 완성된 프로그램을 만드는 기법 객체 지향 프로그래밍 이라고 한다.

객체란?

  • 객체란 속성(Data=field) + 동작(Method)로 이루어 진것.
  • 즉 물리적으로 존재하거나, 개념적인 것 중에서 다른 것과 식별 가능한 것.

객체의 상호작용

  • 현실 세계에서 일어나는 모든 현상은 객체와 객체 간의 상호작용으로 이루어져 있다.

객체 간의 관계

  1. 집합 관계
    완성품과 부품의 관계를 의미한다 . 자동차 객체를 예로 들었을 경우 → 엔진, 타이어, 핸들
  2. 사용 관계
    다른 객체의 필드를 읽고 변경하거나 메소드를 호출하는 관계 자동차 객체의 경우 → 사람
  3. 상속 관계
    부모와 자식 관계를 말한다. 자동차 객체 → 기계 객체

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

  1. 캡슐화
    객체의 데이터(필드), 동작(메소드)를 하나로 묶고 실제 구현 내용을 외부에 감추는 것. → 객체를 보호하고 이를 위해 접근 제한자(Access Modifier)를 사용한다.
  2. 상속
    부모 객체는 자기가 가지고 있는 필드와 메소드를 자식 객체에게 물려주어 자식 객체가 사용할 수 있도록 한다. 이것을 상속 이라고 한다.
    상속의 이유?
  3. 코드의 재사용성을 높여 준다. → 잘 개발된 부모 객체의 필드와 메소드를 자식이 그대로 사용할 수 있어 중복 코딩을 하지 않아도 된다.
  4. 유지 보수 시간을 최소화 해준다. → 부모 객체의 필드와 메소드를 수정하면 모든 자식 객체들은 수정된 필드와 메소드를 사용 할 수 있다.
  5. 다형성
    → Polymorphism → 사용방법은 동일하나, 실행 결과가 다양하게 나오는 성질이다.
    상속과 인터페이스 구현을 통하여 얻을수 있으며, 자동차에 어떤 타이어를 장착하던 사용방법은 동일하지만, 성능이 달라지는것이 이에 해당한다.

6.2 객체와 클래스

  • 현실 세계에서 자동차를 생성 하기 위해선 설계도가 필요 하듯, 객체 지향 프로그래밍에서도 객체를 생성하려면 설계도에 해당하는 클래스가 필요하다.
  • 개발자는 → 클래스를 설계 한다. → 클래스를 통해 생성된 객체를 인스턴스 (인스턴스화) 라고 한다.
  • 클래스로부터 생성된 객체를 해당 클래스의 인스턴스 라고 부른다.

6.3 클래스 선언

  • 클래스 선언은 객체를 생성하기 위한 설계도를 작성 하는 것 이다. 어떻게 객체를 생성(생성자) 하고, 객체가 가져야 할 데이터(필드) 가 무엇이고, 객체의 동작(메소드)는 무엇인지 정의하는 내용이 포함되어야 한다. 클래스 선언은 소스파일명과 동일하게 작성.
public class 클래스명{
} -> 클래스 명은 첫 문자를 대문자로 하고 캐멀 스타일로 작성한다. 
  • 하나의 소스 파일 안에는 여러개의 클래스를 선언할 수 있다, 하지만 공개 클래스는 1개만 선언할 수 있다.
public class SportsCar{
}
//공개 클래스는 1개만

class Tire{
}
//1개의 소스 파일에 여러개 생성 가능

→ 컴파일시 .class의 수 만큼 바이트 코드 파일이 bin폴더 안에 생성된다.

6.4 객체 생성과 클래스 변수

  • 클래스로 부터 객체를 생성하려면 new 연산자가 필요하다.
클래스 변수 = new 클래스();
//클래스도 하나의 type이다. 

클래스의 두가지 용도

  1. 라이브러리 클래스: 실행할 수 없으며 다른 클래스에서 이용하는 클래스
  2. 실행 클래스 : main() 메소드를 가지고 있는 실행 가능한 클래스

6.5 클래스의 구성 멤버

클래스의 구성요소 3가지

  1. 필드: 객체의 데이터가 저장되는 곳 → Data
    객체의 데이터를 저장하는 역할로서, 변수 선언과 비슷한 형태 이지만 쓰임새는 다르다
  2. 생성자: 객체 생성 시 초기화 역할 담당 → 어떻게 객체를 만들것인지 객체를 만드는 것
    생성자는 new 연산자로 객체를 생성할 때 객체의 초기화 역할을 담당한다. 선언 형테는 메소드와 비슷하지만, 리턴 타입이 없고 이름은 클래스의 이름과 동일하다
  3. 메소드: 객체의 동작으로 호출 시 실행하는 블록 → 동작
    객체가 수행할 동작으로서, 다른 언어에서는 함수라고도 부르며 객체 내부의 함수를 메소드라고 부른다.
public class Class Name{

//필드 선언
int fieldName;

//생성자 선언
ClassName(){...}

//메소드 선언
int methodName(){...}
}

6.6 필드 선언과 사용

  • 필드(Field)는 객체의 데이터를 저장하는 역할로서, 객체의 데이터는 3가지로 분류된다. 고유한 데이터를 가지는 고유데이터, 수시로 값이 바뀌는 상태 데이터, 집합 관계에 있는 부품 데이터등이 있다.
//자동차 클래스를 예시로

public class Car{

//고유한 데이터를 저장하는 필드
String company;
String model;
String color;
int maxSpeed;

//수시로 바뀌는 상태 데이터를 저장하는 필드
int speed;
int rpm;

//부품 데이터를 저장하는 필드로서 
//각기 다른 class에서, class를 type으로 사용
Body body;
Engine engine;
Tire tire;
}

필드 선언

  • 필드를 선언하는 방법은 변수를 선언하는 방법과 동일하다. 클래스 블록에서 선언되어야만 필드 선언 이다.
타입 필드명 [ = 초기값 ] ;

class class Car{
Stirng model; //null
int speed = 300; //300
boolean start = true; //true
Tire tire; //null

→ 타입은 필드에 저장할 데이터의 종류를 결정하고, 초기값이 없는경우 객체 생성시 사용되는 기본값으로 초기화 된다.

필드와, 변수의 차이

  • 변수는 생성자와 메소드 블록에서 선언되며, 생성자와 메소드 호출 시에만 생성,
  • 필드는 클래스 블록에서 선언되며, 객체 내부에서 존재하고 내,외부에서 모두 사용가능
구분 필드 변수
선언 위치 클래스 선언 블록 생성자, 메소드 선언 블록
존재 위치 객체 내부에 존재 생성자, 메소드 호출 시에만 존재
사용 위치 객체 내,외부 모두 사용 가능 생성자, 메소드 블록 내부에서만 사용

필드 사용

  • 필드를 사용한다는 것은 필드 값을 읽거나, 변경 한다는 것을 말한다.

→ 클래스에서 필드에 초기값을 준다는 것은 보통 모든 객체가 동일한 필드 값을 가질때 이용한다.

6.7 생성자 선언과 호출

  • new 연산자는 객체를 생성한 후 연이어 생성자를 호출해서 객체를 초기화하는 역할을 한다.
  • 객체 초기화란? → 필드를 초기화 하거나, 메소드를 호출하여 객체를 사용할 준비를 하는 것
클래스 변수 = new 클래스();
                                //생성자 호출

생성자가 성공적으로 실행이 끝나면 new연산자는 객체의 주소를 리턴한다. 
따라서 변수는 객체의 주소값이 대입되어 객체의 필드나, 메소드에 접근할때 이용된다.

기본 생성자

  • 생성자를 선언하지 않을시 컴파일 단계에서 바이트코드 파일에 자동으로 생성되는 생성자.
Car.java
public class Car{
}

Car.class
public class Car{
    public Car(){} //자동으로 추가된다.
}

따라서 
Car myCar = new Car();
에서 기본 생성자를 호출할 수 있다.

생성자 선언

  • 객체를 다양하게 초기화 하기 위해, 직접 생성자를 초기화 할수 있다.
  • 생성자는 메소드와 비슷한 모양을 가지고 있으나, 리턴 타입이 없고, 클래스 이름과 동일하다.
public class Car{

    Car(String model, String color, int maxSpeed){...}
-> 클래스에서 생성자를 개발자가 선언하면, 컴파일러는 기본생성자를 만들지 않는다.
}

-> car myCar = new Car("그랜저", "검정", 300);
-> 이렇게 생성자를 호출할때 3개의 값이 model, color, maxspeed 매개변수로 대입된다.

필드 초기화

  • 객체마다 동일한 값을 가지고 있다면 필드 선언 시 초기값을 대입하는것이 좋고, 객체마다 다른 값을 가진다면 필드를 초기화 하는것이 좋다.
  • 객체 안에서 자기 자신을 가르킬대는 this를 사용한다.
public Korean(String name, String ssn){
    this.name = name;
    this.ssn = ssn;
}
-> 매개변수로 받은 값을 name와 ssn에 저장한다. 

public Korean(String name, String ssn){
name = name;
ssn = ssn;
}
-> 이렇게 해버리면 JVM에서 필드를 필드로 생각하지 않고 모두 지역 변수로 생각한다.

생성자 오버로딩

  • 생성자 오버로딩 이란? → 매개변수를 달리하여 생성자를 여러개 선언 하는 것.
  • 생성자 오버로딩이 필요한 이유 → 매개값으로 객체의 필드를 다양하게 초기화하기 위하여
  • 매개변수의 타입, 개수, 순서가 다르게 여러 개의 생성자를 선언한다.
public class Car{
//이름 동일, ()존재, 매개변수 확인
Car(){..}
Car(String model){...}
Car(String model, String color){...}
Car(String model, String color, int maxSpeed){...}
}
-> 예시
Car car1 = new Car();
Car car2 = new Car("그랜저");
Car car3 = new Car("그랜저", "흰색");
Car car4 = new Car("그랜저", "흰색", 300);
---
Car(String model, String color){...}
Car(String color, String model){...} //타입과 갯수가 구분되지 않으므로 오버로딩이 아니다
                                                                                            //컴파일 오류 발생

다른 생성자 호출

  • 생성자 오버로딩이 많아질 수록 생성자 간의 중복 코드가 많이 발생할수 있는데, 이때 this를 사용하여 공통 코드를 개선할 수 있다.
Car(String model){
    this.model = model;
    this.color = "은색";
    this.maxSpeed = 250;
}

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

Car(String model, String color, int maxSpeed){
    this.model = model;
    this.color = color;
    this.maxSpeed = maxSpeed;
}
------
this를 사용한 개선된 코드
Car(String model){
    this(model, "은색", 250);
}

Car(String model, String color){
    this(model, color, 250);
}

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

6.8 메소드 선언과 호출

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

메소드 선언

리턴타입 메소드명 (매개변수,...){
//실행 블록
실행 코드 작성
}

//리턴타입 메소드명 -> 선언부 라고 말하며, 리턴 타입에는 메소드가 리턴하는 값의 타입을 표시
//매개변수란 메소드가 실행할 때 필요한 매개값을 전달받기 위한 변수

int add(int x, int y){
    int result = x+y;
    return result;
}

add(3,5)-> 메소드 호출

리턴 타입

  • 리턴 타입은 메소드가 실행한 후 호출한 곳으로 전달하는 결과값의 타입을 말한다.
  • 리턴값이 없을때 메소드는 void로 작성한다
void powerOn(){...} //리턴값이 없는 메소드
double divide(int x, int y){...}// double형의 리턴값을 만환하는 메소드

-> return값으로 오는 type이 return type으로 자동형 변환을 할수 있다면 자동 타입 변환 가능

메소드명

  • 메소드 명은 첫 문자를 소문자로 시작하는 캐멀 스타일 run(), setSpeed(), getName()

매개변수

  • 매개변수는 메소드를 호출할 때 전달한 매개값을 받기 위해 사용한다. → 전달한 매개변수가 없다면 생략 가능

실행블록

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

메소드 호출

  • 메소드를 호출한다는 것은 실행 블록을 실행시키는 것 이다.
  • 메소드 호출은 클래스에서 메소드를 선언 하였다고 해서 바로 호출하는 것이아니라, 객체가 존재 하여야 호출이 가능하다. 메소드란 객체의 동작이므로 객체가 존재하여야 한다.
  • 메소드의 경우 같은 객체 내,외부 모두에서 호출이 가능하다 사용방법이 다르다.
  • 객체 내부에서 호출시 메소드명을 통한 호출이 가능하지만, 외부에서 호출할 경우 참조 변수와 도트(.) 연산자를 통하여 호출한다.
타입 변수 = 메소드();

//객체 내부
//생성자
Calculator(){
    //메소드호출
    powerOff();
}

void powerOn(){...}
void powerOff(){...}
int plus(int x, int y){...}
double divide(int x, int y){...}

void method(){
    powerOn;
int r1 = plus(3,5); //메소드 호출
double r2 = divide(15,3); //메소드 호출
}
//객체 외부
void methoed(){
    Calculator calc = new Calculator();
//객체 외부에서는 도트(.)연산자 이용
calc.powerOn();
    int r1 = calc.plus(3.5);
    double r2 = calc.divide(15,3);
}

가변길이 매개변수

  • 메소드를 호출할 때에는 매개변수의 개수에 맞게 매개값을 제공해야 한다.
  • 하지만 가변길이 매개변수를 가지고있다면 매개변수의 갯수에 영향이 없다.
//일반적인 2개의 매개변수
int sum(int x, int y){}
//가변길이 매개변수
int sum(int ... values){}
-> 
가변길이 매개변수의 경우 사전에 정한 타입만 배열형태로 저장되고, 배열로 넣을 수도 잇다.
int result = sum(new int[]{1,2,3);

return

  • return문은 메소드의 실행을 강제 종료하고, 호출한 곳으로 돌아간다는 의미
return [리턴값]; 
-> 리턴값이 없다면 void로 메소드를 선언, 그렇지 않다면 리턴값의 type으로 선언

메소드 오버로딩

  • 메소드 오버로딩은 메소드 이름은 같되, 매개변수 타입, 개수, 순서가 다른 메소드를 여러개 선언하는것. → 다양한 매개값들을 처리하기 위함.
class 클래스{
    리턴타입 메소드이름 (타입 변수, ...){}
    //무관      동일     타입, 갯수, 순서 가 달라야한다.
    리턴타입 메소드이름 (타입 변수, ...){}
  • 대표적으론 메소드 오버로딩의 예로는 System.out.println() 메소드가 있다.
  • void println(), void println(double x), void println(int x), void println(String x)

6.9 인스턴스 멤버

  • 필드와 메소드는 선언 방법에 따라 인스턴스 멤버와 / 정적 멤버로 분류할 수 있다.
  • 인스턴스 멤버 → 객체 생성 후 사용 가능
  • 정적 멤버 → 객체 없이도 사용 가능
구분 설명
인스턴스 멤버 객체에 소속된 멤버
(객체가 가지고 있는 멤버 → 객체 생성 후 사용 가능)  
정적 멤버 클래스에 고정된 멤버
(객체 없이도 사용 가능)  

인스턴스 멤버 선언 및 사용

  • 인스턴스 멤버란 객체에 소속된 멤버로서, 객체가 있어야만 사용 가능한 멤버이다.
  • 객체에 포함된과, 소속된의 차이를 주의하여야 한다.
  • 인스턴스 멤버의 경우 객체에 포함되지 않더라고 객체가 존재하고, 그 객체에 소속 되면 사용 가능하다. 포함의 경우 항상 그 객체 내부에 존재하여야 하는 것이다. 따라서 차이가 있다.

this키워드

  • 객체 내부에서 인스턴스 멤버에 접근하기 위해 this 키워드를 사용한다. 우리가 “나” 라고 자신을 칭하듯 객체는 자신을 this라고 칭한다.
  • this는 객체에 소속된 멤버만 사용 가능하고, 정적 멤버는 사용할 수 없다.

6.10 정적 멤버

  • 필드와 메소드를 의미한다.
  • 자바는 클래스파일을 로더를 통하여 메소드 영역에 저장하고 사용하는데, 정적 멤버란 이때 메소드영역의 클래스에 고정적으로 위치하는 멤버를 말한다. 따라서 정적 멤버는 객체를 생성할 필요 없이 클래스를 통해 바로 사용 가능하다.
  • 정적 필드와 메소드를 선언하려면 static 키워드를 추가하면 되고, 객체마다 가지고 있을 필요성이 없는 공용적인 필드는 정적 필드로 선언하는 것이 좋다.
public class 클래스{

    static 타입 필드 [=초기값];

    static 리턴타입 메소드 (매개변수,...){...}
}

public class Calculator{
    String color; //색깔은 다를 수 있다. -> 인스턴스 필드
    void setColor(String color){this.color = color;} -> 인스턴스 메소드 -> 객체가 있어야 사용가능
    static int plus(int x, int y){return x+y;} //정적 메소드
    static double pi = 3.14159; //파이값은 동일하다

정적 멤버 사용

  • 정적 멤버를 사용할때에는 새롭게 객체를 생성하지 않고 바로 도트(.), 와 연산자로 접근 가능하다. 객체를 생성하여 참조변수로 접근도 가능은 하지만 사용하진 않는다.
public class Calculator{
    static double pi = 3.14159;
    static int plus(int x, int y){...}
    static int minus(int x,int y){...}
}

double result1 = 10*10*Calculator.pi; //이렇게 사용

Calculator myCalc = new Calculator(); 
double result1 = 10*10*Calculator.pi; //이렇게 사용하지 않는다

인스턴스 멤버 사용 불가

  • 정적 필드와, 메소드는 객체 없이도 사용이 가능하다는 특징 때문에, 내부에 인스턴스 필드나 인스턴스 메소드를 사용 할 수 없다. 또한 this 역시 객체가 필요하므로 사용할 수 없다.
static void Method3(){
    //정적 메소드에서 인스턴스 멤버를 사용하기 위해선 객체를 생성하여야 한다.
    ClassName obj = new ClassName();

    obj.field1 = 10;
    obj.method1();
}

6.11 final 필드와 상수

  • 인스턴스 필드와 , 정적 필드는 언제든지 값을 변경할 수 있다. 값을 변견하는 것을 막고 읽기만 허용해햐 할 때 final 필드와 상수를 선언하여 사용한다.
final 타입 필드 [=초기값];
//final 필드에서 초기값을 주는 방법은 2가지 밖에 없다.
1. 필드 선언시 초기값 대입
2. 생성자에서 초기값 대입

상수 선언

  • 우리 주변의 불변의 값 원주율(pi), 무게, 둘레등 이러한 불변의 값들을 상수 라고 부른다.
  • 상수는 객체마다 저장할 필요가 없고, 여러개의 값을 가질수도 없으므로 static 이면서 final인 특성을 가져야 한다.
  • 상수의 경우 이름의 모두를 대문자로 작성하는 것이 관례이다.

6.12 패키지

  • 자바에서 패키지는 단순히 디렉토리만을 의미하는 것이 아닌, 클래스의 일부분으로서 클래스를 식별하는 용도로 사용된다.

패키지 선언

  • 항상 소스 파일 최상단에 위치해야 한다.
  • 패키지 이름은 모두 소문자로 작성하고, 회사 도메인 이름의 역순이 관례이다

import 문

  • 다른 패키지에 있는 클래스를 사용하려면 import 문을 사용하여 어떤 패키지의 클래스를 사용하는지 명시해야 한다.
  • 동일한 패키지에 포함되어있는 다수의 클래스를 사용할때에는 *을 사용할 수 있다. 단 하위 패키지들은 포함하지 않는다.
import com.hankook.* //honkook의 하위 패키지들은 포함하지 않는다.
import com.hankook.project.* //하위 패키지를 사용할꺼면 이렇게 해주어야 한다. 

-> 만약 2가지 모두 동일한 클래스의 이름이 존재한다면, 어떤 패키지의 클래스를 사용할껀지
-> 전체 경로를 적어준다. 

6.13 접근 제한자

  • 중요한 필드와 메소드가 외부로 노출되지 않도록 하기 위해 객체의 무결성(결점이 없는 성질)을 유지하기 위해 접근제한자를 사용한다.
접근 제한자 제한 대상 제한 범위
public 클래스, 필드, 생성자, 메소드 없음
protected 필드, 생성자, 메소드 같은 패키지 이거나, 자식 객체만 사용 가능
(default) 클래스, 필드, 생성자, 메소드 같은 패키지
private 필드, 생성자, 메소드 객체 내부

6.14 Getter와 Setter

  • 객체의 필드를 외부에서 마음대로 읽고 변경할 경우 객체의 부결성이 깨질 수 있으므로, 이를 막기 위한 방법
  • Getter : 어떤 메소드에서 값을 가지고 오는것
  • Setter: 어떤 메소드 값을 변경하는 것.
  • 즉 필드값을 private으로 외부에서 변경하지 못하게 하고, Setter로 변경하고, Getter로 가져온다.
private int maxSpeed; //필드 접근 제한자 private

//Getter
public int getMaxSpeed(){
    return maxSpeed;
}
//메소드 이름을 get+필드이름(첫 글자 대문자)

//Setter
public void setMaxSpeed(int maxSpeed){
    this.maxSpeed = maxSpeed;
}
//메소드 이름을 set+필드이름(첫 글자 대문자)

//만약 boolean 타입일 경우 get을 is로 시작하는 것이 관례이다. Setter은 동일
isMaxSpeed

6.15 싱글톤 패턴

  • 애플리케이션 전체에서 단 한개의 객체를 만드는데 사용하는 패턴 이다.
  • 핵심은 생성자를 private으로 접근 제한해서 외부에서 new연산자로 생성자를 호출할수 없게 하는 것이다.
public class 클래스{
    private static 클래스 singleton = new 클래스();
//private 접근 권한을 갖는 정적 필드를 선언과 초기화 한다.

    private 클래스(){}
//private 접근 권한을 갖는 생성자 생성

    public static 클래스 getInstance(){
        return singleton;
    }
}
//public 접근 권한을 갖는 정적 메소드를 선언하여 객체를 얻는다.
//이 방법을 통해 getInstance() 메소드만을 통하여 외부에서 객체를 얻을 수 있다. 

'JAVA > 이것이 자바다' 카테고리의 다른 글

자바 개념 정리  (0) 2023.03.01
[Chapter 05] 참조 타입  (0) 2022.12.31
[Chapter 04] 조건문과 반복문  (0) 2022.12.03
[Chapter 03] 연산자  (0) 2022.12.03
[Chapter 02] 변수와 타입  (0) 2022.12.03

댓글