Lang/Java

객체와 클래스

soooy0 2024. 12. 3. 16:27

객체 지향 프로그래밍

- 어떤 제품을 만들 때, 부품을 먼저 만들고 그 부품을 하나씩 조립하여 완성품을 만든다. 마찬가지로 부품에 해당하는 객체를 먼저 만들고, 이 객체들을 하나씩 조립하여 완성된 프로그램을 만드는 것을 객체 지향 프로그래밍이라고 한다. 여러개의 부품인(객체)를 만들고 그것을 이리저리 조합해서 만든다고 생각하자.

 

객체란?

- 물리적으로 존재하거나 개념적인 것 중에서 다른 것과 식별 가능한 것을 말한다. 속성(필드)과 동작(기능, 메서드)

- 데이터(필드)와 메소드로 구성된 덩어리

- 현실세계의 객체를 소프트웨어 객체로 설계하면? 객체 모델링

 

:: 객체의 상호작용

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

- 그렇다면, 객체들이 상호작용할 수 있는 수단은 메서드가 되는 것.

- 메소드 호출을 통해 객체들은 서로 데이터를 주고 받음, 전달하고자 하는 데이터를 메서드의 매개변수를 통해 보냄, 매개값은 메소드가 실행될 때 필요한 값이므로 리턴값은 메서드의 실행결과이자 호출한 곳으로 돌려주는 값.

- 리턴 받은 것은 변수에 대입하여 사용한다.

 

:: 객체 간의 관계

- 객체는 단독 혹은 다른 객체와 관계를 맺고 있다.

1. 집합관계

- 완성품과 부품의 관계

- 자동차: 엔진, 타이어, 핸들 .. -> 자동차와 부품들은 집합관계

 

2. 사용관계

- 다른 객체의 필드를 읽고 변경하거나 메소드를 호출하는 관계를 말한다.

- 사람이 자동차에게 달려, 멈춰. 등의 메소드를 호출하면 사람과 자동차는 사용관계

 

3. 상속관계

- 자동차가 기계의 특징을 물려받는다면, 기계(상위)와 자동차(하위)는 상속관계이다. 그렇다면 하위에는 기계에 속하는 다른 것들이 존재할 수 있는 것. 예를 들면 suv, 카니발 등등..

 

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

1. 캡슐화

- private 접근제어자를 통해 객체를 하나로 묶고 실제 구현 내용을 외부에서 보이지 않도록 감추는 것.

- 외부 객체는 객체가 노출해서 제공하는 필드와 메서드만 이용 가능

- 그럼 왜 캡슐화 하는가? 외부의 잘못된 사용으로 인해 객체가 손상되지 않도록 하는 데에 이유가 있음

- 객체의 노출 여부를 결정하기 위해 접근제한자를 사용하는 것.

 

2. 상속

- 상속 extends로, 부모 역할을 하는 상위 객체의 것을 물려받아 자식 역할을 하는 하위 객체가 상위의 필드와 메서드를 사용할 수 있도록 하는 것이다.

- 말은 상속이지만 사실상, extend는 상속이 아닌 확장의 의미이다. 상위의 것을 더 확장하여 하위의 것을 만든다는 개념으로 생각하자

- 그렇다면 상속을 하는 이유는?

  -> 코드의 중복성 제거

  -> 코드의 재사용성 높여줌

  -> 상위 객체의 필드와 메소드를 수정하면, extends한 하위의 객체들도 모두 수정된 필드와 메서들르 사용할 수 있으므로 유지보수 시간을 단축할 수 있다.

 

3. 다형성

- 사용방법은 동일하지만, 실행 결과가 다양하게 나오는 성질

- 자동차 부품을 바꾸면 성능이 다르게 나오듯, 프로그램을 구성하는 객체를 바꾸면 프로그램 실행 성능이 다르게 나올 수 있음.

- 또 다르게 쉽게 생각하면, 아이폰이라는 공통된 설계 틀을 가지고 아이폰 12기가, 256기가, 512기가 이렇게 만들 수 있는 것

- 다형성을 구현하기 위해서는 자동형변환과 오버라이딩이 필요하다. (상속, 인터페이스)

 

객체와 클래스

- class는 객체를 생성하기 위한 설계도에 해당!

- 클래스로부터 객체를 생성하는 과정을 인스턴스화라고 한다.

- 동일한 클래스로 여러개의 인스턴스를 생성 가능하며, 동일한 설계도로 여러대의 자동차를 만드는 것과 동일

  • 라이브러리 클래스: 실행할 수 없으며 다른 클래스에서 이용하는 클래스
  • main 클래스: 실행 클래스로서, main 메소드를 가지고 있다. 
  • 인스턴스: 클래스로부터 생성된 인스턴스 즉 객체를 의미함.
  • public class: 어느 위치에 있던 패키지와 상관없이 사용할 수 있는 클래스, 파일명과 동일한 클래스만 public class로 선언 가능

- 어떻게 객체를 생성하고(생성자), 객체가 가져야 할 데이터가 무엇이고(필드), 객체의 동작은 무엇인지(메서드) 정의하는 내용이 포함된다.

- 하나의 소스파일에 복수의 클래스를 선언할 수 있음

 

클래스 구성

- 객체를 생성할 생성자, 객체에 포함될 필드와 메소드 코드로 구성

- 객체를 생성하려면, 객체 생성자인 new연산자가 필요하다. 연산자 뒤에는 생성자 호출코드인 ()괄호가 온다.

- new 연산자는 객체를 생성시킨 후, 객체의 주소를 리턴하기 떄문에 클래스 변수에 대입될 수 있는 것이다.

Tv tv = new Tv();

- 이제 TV클래스 타입인 tv변수에는 객체의 주소가 저장되어, 해당 객체 안에 들어 있는 필드와 메소드들을 사용할 수 있게 된다.

더보기

여기서 만약, TvExam이라는 실행 클래스를 만들어서, tv클래스를 사용한다면, 

tv클래스는 라이브러리 클래스이고, TvExam은 main클래스가 된다.

일반적으로 자바프로그램은 하나의 메인 클래스, 여러개의 라이브러리 클래스로 구성되며,

실행클래스는 실행하면서 라이브러리 클래스를 내부에서 이용한다.

 

1. 생성자
  • 객체 생성 후 초기화 역할을 함.
  • 객체 초기화란 필드를 초기화하거나 메소드를 호출해서 객체를 사용할 준비를 하는 것
  • new 연산자 뒤에 괄호가 생성자를 호출하여 객체를 초기화 하는 것임 !!
  • 생성자가 실행을 끝내면, new 연산자는 객체의 주소를 리턴한다. 리턴된 주소는 참조변수에 대입되어 객체의 필드나 메소드에 접근할 때 이용된다.
  • 모든 클래스는 생성자가 존재하며, 하나 이상을 가질 수 있다. 클래스에 생성자 선언이 없으면 컴파일러는 기본 생성자를 자동으로 추가시킴.
    • 그래서! 생성자를 따로 선언하지 않더라도 Car mycar = new Car(); 이렇게 뒤에 생성자를 호출할 수 있는 것임. 만약 기본 생성자가 없었다면 오류가 났을 것.
  • 그렇다면 생성자가 이미 선언되어 있으면?! 기본 생성자를 추가시키지 않음. --> 클래스가 public 이면, 생성자도 public이 추가 됨. public 없으면 생성자도 안함.
  • 그럼 개발자는 왜 명시적으로 생성자를 선언하는가? 객체를 다양하게 초기화하기 위해서이다.
생성자 선언

- 생성자는 메소드와 비슷한 모양이지만, 리턴타입이 없고 클래스 이름과 동일하다. 매개변수는 생성자를 호출할 때, 매개값을 생성자 블록 내부로 전달하는 역할을 한다.

public class Car {

	Car(String model, String color, int speed){///}
    
    Car myCar = new Car("그랜저", 검정", 300);
}

- 생성자 호출시, 매개변수 타입은 생성자 선언 시 설정한 매개값의 종류와 순서에 맞게 작성하면 된다.

 

필드 초기화
  • 객체마다 동일한 값을 갖고 있다면 필드 선언 시 초기값을 대입하는 것이 좋다.
  • 객체마다 다른 값을 가져야 한다면 생성자에서 필드 초기화(국적은 대한민국으로 공통이지만, 민증번호와 이름은 각각 다름)
String name;
String ssn;

public Korean(String n, String s) {
	name = n;
    ssn = s;
}
  • 이렇게 작성하면, 매개변수 이름이 너무 짧고, 가독성이 좋지 않다. 따라서 초기화시킬 필드명과 동일한 이름을 사용하는 것이 좋다.
String name;
String ssn;

public Korean(String name, String ssn) {
	// 이렇게 작성하면 어떤 것이 필드이고 어떤 것이 매개변수인지 구분이 어렵다.
    // name = name;
    // ssn = ssn;
    
    //this를 사용하여 필드명과 매개변수를 구분해준다. this를 붙인 쪽이 현재 객체의 데이터 필드를 의미한다.(현재 객체)
    this.name = name;
    this.ssn = ssn;
}
  • 위 코드 처럼 작성하면, 생성자 호출 시 매개변수로 받은 데이터가 현재 객체의 데이터 필드의 데이터로 대입되는 것이다.
생성자 오버로딩
  • 매개값을 받아 객체의 필드를 다양하게 초기화하려면 생성자 오버로딩을 통해 여러 개의 생성자를 정의해준다. 매개변수의 개수, 이름, 타입, 순서가 다르게 여러개의 생성자를 선언하는 것이다.
  • 따라서 생성자가 여러개라면, 생성자 호출 시 제공되는 매개변수 값의 타입과 개수에 따라 실행될 생성자가 결정된다.

생성자 오버로딩이 많아서 중복된 코드가 발생한다면?

- 매개변수의 수만 달라지고 필드 초기화 내용이 중복된다면, 공통코드를 한 생성자에 집중 작성 후 나머지 생성자는 this를 사용하여 공통 코드를 가지고 있는 생성자를 호출하여 개선한다.

  • 'this() : this(”철수”, 10, ssn) 이렇게 클래스 내 다른 생성자 호출 가능 단, 생성자 제일 첫줄에 존재해야 한다. 그 다음 추가적인 실행문 적을 수 있음.
  • 생성자 매개변수에는 어떠한 타입도 가능하다. (배열 객체도 가능!)
더보기

오버로딩 조건!

 

선언부:

- 리턴타입 무관

- 메서드 이름 동일

- 이외 매개변수의 개수, 타입 달라야 함(그래서 순서만 바꾸는 거 안됨)

 

구현부: - 비슷하거나 혹은 같게

 

오버라이딩 조건!

선언부:

- 메서드 이름, 매개변수까지 동일하게

구현부:

- 구현부 내용을 다르게 + extends 관계일 때 가능, 상위에서 받은 것을 하위에서 오버라이딩

 

2. 필드
  • 객체에 포함될 필드, 데이터를 저장하는 역할
  • 필드는 객체의 데이터이므로 객체가 존재하지 않으면 필드도 존재하지 않는다. 따라서 클래스에 객체를 생성한 이후에만 객체에 포함된 필드 데이터를 사용할 수 있다.
더보기

필드와(로컬)변수

 

로컬 변수는 성성자와 메소드 블록에서 선언되며 생성자와 메소드 호출 시에만 생성되고 사용된다.

팔드는 클래스 블록에서 선언되며 객체 내부에 존재하고, 객체 내/외부에서 사용 가능하다.

  • 타입은 필드를 저장할 데이터 종류를 결정하며, 기본타입/참조타입 모두 가능하다.
필트 타입의 초기화
  • 정수타입: 0
  • 실수타입: 0.0
  • boolean 타입: false
  • 참조타입 : null

 

필드 초기화
  • 멤버변수 선언과 동시에 초기화 (하드코딩으로, 거의 사용하지 않는다.)
  • 객체 생성 후 멤버변수에 값 대입 (하)
  • 생성자 이용 (중)
  • 초기화 블럭(하)
  • getter setter 메서드(상상상상)
필드 사용
  • 필드값을 읽고 사용하는 것을 필드 사용이라고 한다.
  • 클래스에 선언했다고 바로 필드를 사용할 수 있다는게 아닌거 알지? 왜냐? 필드는 객체의 데이터이므로 객체가 존재해야 필드도 존재하니까!!!

 

3. 메서드

 

  • 메소드: 객체가 수행할 동작을 실행 블록에 정의하는 것
  • 함수라고도 하는데, 객체 내부의 함수는 메소드라고 한다.
  • 객체의 동작을 실행블럭 안에 정의해 둠
  • 메소드는 객체 내부, 혹은 다른 객체에서도 호출 될 수 있기 때문에 객체 간의 상호작용하는 방법을 정의한 것이다.
메소드 선언

 

리턴값

  • 리턴값이 없으면 리턴타입은 void
  • 리턴타입은 메소드가 실행한 후 호출한 곳으로 전달하는 결과값의 타입을 말한다. 메소드는 반드시 호출한 곳으로 값을 돌려준다.
  • 리턴 타입이 있는 메소드는 반드시 return문으로 리턴값을 지정한다.
  • return문 이후에 적힌 코드는 절대 실행되지 않는다. 메서드의 실행을 즉시 종료시키고 호출한 곳으로 돌아가기 때문.
boolean isLeftGas(){
	if(gas == 0){
    	System.out.println("gas가 없습니다.");
        return false;
    }
    
    System.out.println("gas가 있습니다.");
    return true;
}

// gas가 0이라면 if문 실행 -> "gas가 없습니다." 출력 후 호출한 쪽에 false를 리턴하며 그대로 메서드 종료
// gas가 0이 아니라면 if문 실행 안함 -> "gas가 있습니다." 출력 후 호출한 쪽에 true를 리턴하며 메서드 종료

매개변수

  • 필요한 데이터를 외부로부터 제공받기 위해 사용됨
  • 메소드의 실행블록에 작성한 코드를 실행하기 위해서 필요한 데이터를 외부로부터 제공받아서 연산하는 것

메소드 호출

  • 당연히 클래스에 메소드를 선언했다고 바로 호출할 수 있는게 아님. 왜겠어? 메소드는 객체의 동작이기 때문에 객체가 생성된 이후에 사용 가능하기 떄문
  • 객체가 생성된 후에 다른 메소드 내부에서 호출 될 수 있고, 객체 외부에서도 호출 가능
  • 객체 내부에서는 단순히 메소드명으로 호출, 외부 객체에서는 참조변수/ .연산자를 이용해 호출
// 내부 객체

// 생성자
Calculator() {
	powerOff();
}

// 메소드
void powerOn(){..}
void powerOff(){..}
int plus(int x, int y){..}
double devide(int x, int y){..}

void method(){

	powerOn();
    int r1 = plus(3, 5);
    double r2 = divide(15, 3);
}


// 외부 객체
void method(){

	Calculator calc = new Calculator();

    calc.powerOn();
    int r1 = calc.plus(3, 5);
    double r2 = calc.divide(15, 3);

}
  • 메소드의 리턴값을 저장할 변수는 리턴값의 타입과 동일하거나, 자동 형변환이 가능해야 함.
    int sum(int ... values){}
    //메소드가 가변길이 매개변수를 갖고 있다면, 
    //매개변수의 개수와 상관없이 매개값을 줄 수 있다. 
    //매개값들은 자동으로 배열로 형변환되어 메소드에 사용된다. 따라서 메소드 호출 시, 직접 배열을 매개값으로 제공해도 됨
    • 메소드의 return: 메소드의 실행을 강제 종료하고 호출한 곳으로 돌아간다.
메소드 오버로딩

- 메소드 명은 같지만 타입, 개수, 순서가 다른 메소드를 여러개 선언하는 것

- 리턴타입 무관, 메소드명 동일, 타입/개수/순서 다르게

 

필드와 메소드의 분류

  • 필드,메소드는 인스턴스 멤버/정적 멤버로 구분 가능
  • 인스턴스 멤버: 객체에 소속되어 있으며, 객체 생성 필수
  • 정적 멤버: 클래스에 고정되어 있으며, 객체 없이도 사용 가능 
1. 인스턴스 멤버
  • 객체에 소속된 멤버 (객체를 생성해야만 사용할 수 있는 멤버)
  • 외부 클래스에서 사용하기 위해서는 객체를 먼저 생성하고, 객체의 번지수가 저장된 참조변수로 접근해서 사용해야 한다.
  • 인스턴스 필드는 객체마다 각각 소속되어 있지만, 메서드는 각 객체마다 존재하지 않고 메소드 영역에 저장되어 공유된다.
  • —> 메서드는 코드의 덩어리이므로, 객체마다 저장한다면 중복저장으로 인해 메모리 효율이 떨어지기 때문이다. 따라서 메소드 코드는 메소드 영역에 두어 공유해서 사용하고, 객체 없이 사용하지 못하도록 제한을 걸어둔 것이다.
this 키워드
  • 인스턴스 멤버에 접근하기 위해 this를 사용한다.
  • 나 자신을 의미하며, 매개변수명과 인스턴스 멤버 필드명이 동일할 경우, 인스턴스 멤버 필드임을 강조함으로써 매개변수와 구분하기 위해 사용한다. 매개변수와 겹치는 경우가 아니라면 this는 생략 가능하다.
2. 정적 멤버(static)

 

  • 자바는 클래스 로더를 이용하여 클래스를 메소드 영역에 저장하고 사용한다.
  • 정적 멤버는 바로 이 메소드 영역의 클래스에 고정적으로 위치하는 멤버를 말한다. 따라서 객체 생성이 필요 없다.
  • 필드나 메소드 앞에 static 키워드를 붙이면 된다.
  • 객체마다 값을 가지고 있을 필요성이 없는 공용적인 필드를 정적필드로 선언한다. 왜냐하면 static은 하나의 저장공간(같은 주소)을 공유하기 때문에 값이 공통되기 때문이다.
  • 인스턴스 멤버(필드)를 이용하지 않는(=매개변수로만 처리 가능한) 메소드는 정적 메소드로 선언하는 것이 좋다.
사용
  • 객체 생성, 참조변수 없이 바로 >> 클래스명.연산자 <<로 접근 가능하다.
  • 정적 멤버(필드)는 필드 선언과 동시에 초기값을 주는 것이 일반적이다. 만약 초기화가 복잡하다면  정적 초기화 블럭을 사용한다.
  • 정적 초기화 블록은 클래스가 메모리에 로딩될 때 자동으로 실행되며, 여러 개일 경우 선언된 순서대로 실행된다.
더보기

정적 필드는 객체 생성을 하지 않아도 되기 때문에, 생성자에서 초기화 작업을 하지 않는다!! 생성자는 객체 생성 이후 실행되기 때문이다!!

  • 정적 메소드와 정적 필드, 정적 블록은 내부에 인스턴스 필드, 인스턴스 메소드를 사용할 수 없다. this 또한 사용할 수 없다.(객체 자신의 참조이기 때문) 내부에서 사용하려면 객체를 생성하여 참조변수로 접근하면 된다. 
  • 더보기
    그래서 main메서드가 있는 실행 클래스도 main메서드가 static 이기 때문에 객체 생성 없이는 인스턴스 필드, 메서드를 main()에서 사용할 수 없는 것이다!!
  • 만약 public static void 로 메서드를 선언하면 접근제한자 public 으로 모든 곳에서 제어 가능하지만, 그냥 static void 라고만 쓰면 default접근 제한자로 설정된다.

 

final필드와 상수

  • 인스턴스, static 필드는 언제든지 값을 변경할 수 있다. 하지만 값을 변경하는 것을 막고 읽기만 허용해야하는 경우에 final과 상수를 사용한다.

 

final
  • final이 사용될 경우, 초기값이 최종값이 되어 프로그램 실행 도중에 수정할 수 없게 된다.
  • 초기값 주는 방법: 1. 필드 선언 시에 값 대입, 2. 생성자에서 초기값 대입(복잡할 경우)
상수
  • 불변의 값을 저장하는 필드
  • 상수는 객체마다 저장할 필요가 없고, 여러개의 값을 가져도 안된다. 따라서 static이면서 final의 속성을 가진다. 즉 >>static final 타입 상수 = 값; << 이렇게 선언한다.
  • 초기값 주는 방법: 1. 필드 선언 시에 값 대입, 2. static초기화 블럭에서 초기화(복잡할 경우)
  • static 필드이기 때문에 >>클래스.상수<< 로 접근해서 사용할 수 있다.
  • 상수의 경우 언제 어디서나 사용가능해야 하므로 static으로 선언 (인스턴스에 의존적이지 않고 독립적으로 사용가능하도록 선언하는 것.)

패키지

  • 패키지는 단순히 디렉토리만을 의미하는 것은 아니다. 클래스를 식별하는 용도이기도 하다.
  • 패키지는 주로 개발 회사의 도메인 이름의 역순으로 만든다. 
  • 마지막에는 프로젝트 이름을 붙여준다. 소문자로 작성하는 것이 관례이다. com.samsung.projectname
  • import문을 사용하여 외부 패키지에 있는 클래스를 사용할 수 있다.

접근제한자

  • 객체의 필드를 외부에서 변경하거나 메소드를 호출할 수 없도록 막아야 할 필요가 있다. 중요한 필드, 메소드가 외부로 노출되지 않도록 하여 객체 무결성을 유지하기 위해서이다.
  • public, protected, defalut, private
    public 접근 제한 전혀 없음
    protected 같은 패키지 내에서 혹은 다른 패키지의 하위 클래스에서 접근 가능
    default 같은 패키지 내에서만 접근 가능
    private 같은 클래스 내에서만 접근 가능

    클래스  public, default, final, abstract
    메서드 public, protected, default, private, final, abstract, static
    멤버변수 public, protected, default, private, final, static
    지역변수 final
    • 클래스 선언 시, public 접근 제한자를 생략했다면, 클래스는 default 접근 제한을 가진다. 이 경우 같은 패키지에서는 제한 없이 사용함 but 다른 패키지에서 사용 불가
  • 생성자의 접근 제한
    • 생성자는 public, default, private 접근 제한을 가질 수 있다.
    • 생성자가 어떤 접근 제한을 갖느냐에 따라 호출 가능 여부가 결정된다.
  • 필드와 메서드의 접근 제한
    • 필드, 메서드는 public, default, private 접근 제한을 가질 수 있다.
    • 필드와 메소드도 어떤 접근 제한을 갖느냐에 따라 호출 가능 여부가 결정된다.

Getter, Setter

  • 객체의 필드를 외부에서 마음대로 읽고 변경하면, 객체 무결성 조건이 깨진다. (ex. 자동차의 속력은 음수가 될 수 없는데 외부에서 음수로 바꿈)
  • Getter 와 Setter 는 public 이어야 한다. 그래야 가져오니까!
Getter
  • 외부에서 객체의 필드를 읽을 때 메소드가 필요한 경우가 있다. 필드 값이 객체 외부에서 사용하기 부적절한 경우, 메소드로 적절한 값으로 변환해서 리턴할 수 있기 때문. 이러한 역할을 Getter메소드가 한다.

💡 필드 타입이 boolean일 경우에는 Getter는 get말고 is로 시작하는것이 관례 getstop() —> x // isstop() —> o

Setter
  • 직접적인 외부에서의 필드 접근을 막고 메소드를 통해 필드에 접근하는 것을 선호한다. 메소드는 데이터를 검증해서 유효한 값만 필드에 저장할 수 있기 때문이다. 이러한 역할을 Setter 메소드가 한다.
private 타입 fieldName;

//Getter
public 타입 getFieldName(){
return fieldName;
}

//Setter
public void setFieldName(타입 fieldName){
this.fieldName = fileName;