본문 바로가기

JAVA

[이것이 자바다] 06 클래스 (Class), 객체지향

* 본 포스팅은 제가 국비지원교육을 받으며 노션에 정리한 내용을 옮겨놓은 것입니다.
발전을 위한 피드백과 지적은 언제나 환영합니다. 

 

6.1 객체지향 프로그래밍

6.1.1 객체란?

객체 → 사물

객체지향 → 사물을 만드는 방법들

 

ex)

사람

이름, 나이 (속성) → 필드

웃다, 걷다 (동작) → 메소드

 

자동차

색상, 모델명 (속성) → 필드

달린다, 멈춘다 (동작) → 메소드

 

6.1.2 객체의 상호작용

객체들은 각각 독립적으로 존재하고, 다른 객체와 서로 상호작용.

이 상호작용 수단이 메소드

객체가 다른 객체의 기능을 이용하고 싶을 때에는? 메소드 호출

 

int result = Calculator.add(10, 20);
// Calculator 클래스의 add 메소드 호출 (매개변수 두개)
-> 변수 result 에 저장

 

6.1.3 객체간의 관계

  • 사용관계 : 객체간의 상호작용 (사람과 자동차)
  • 상속관계 : 상위(부모) 객체를 기반으로 하위(자식) 객체를 생성하는 관계 일반적으로 상위 객체는 종류, 하위 객체는 구체적인 사물을 의미함 "자동차는 기계의 종류이다" → 기계(상위)와 자동차(하위)는 상속 관계에 있음

 

6.1.4 객체 지향 프로그램의 특징

 

  1. 캡슐화(Encapsulation) : 객체의 필드, 메소드를 하나로 묶고 실제 구현 내용을 감추는 것 함부로 건드릴 수 없도록 접근제한자 (Access Modifier) 를 사용하여 코드를 클래스라는 캡슐에 씌운다.
  1. 상속(Inheritance) : 상위 객체인 부모의 기능을 하위 객체인 자식이 사용할 수 있도록 하는 것. 재사용을 통해 하위객체를 쉽고 빠르게 설계 가능, 반복된 코드의 중복을 줄여줌.
  1. 다형성(Polymorphism) : 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질. 부모 타입에는 자식 객체가 대입될 수 있고, 인터페이스 타입에는 모든 구현 객체가 대입될 수 있다.

 

 

6.2 객체와 클래스

자바에서는 설계도가 클래스

클래스 : 객체를 생성하기 위한 필드와 메소드가 포함.

인스턴스 : 클래스로부터 만들어진 객체

(자동차 객체는 자동차 클래스의 인스턴스)

인스턴스화 : 클래스로부터 객체를 만드는 과정

 

하나의 클래스로 여러개의 인스턴스를 만들 수 있다.

Print p1 = new Print();
Print p2 = new Print();
//p1, p2 : 인스턴스

 

6.3 클래스 선언

  • 일반적으로 하나의 파일에는 하나의 클래스
  • 파일 이름과 동일한 이름의 클래스 선언에만 public 접근 제한자를 붙일 수 있다.

 

public 없으면 가능 (파일명 ClassName)

 

public 있으면 컴파일 오류

6.4 객체 생성과 클래스 변수

new 클래스명();

→ 메모리에 올라가지만 어디인지는 알 수 없다. 한번 쓰고 버리는 경우에 사용하고, idle time 에 garbage collection을 돌려서 정리함. (자바는 누군가가 참조하지 않은 객체는 정리한다)

 

//1번 방법
Car car;
car = new Car();

//2번 방법
Car car = new Car();

→ new 연산자는 heap 영역에 객체를 생성시킨 후 객체의 주소를 리턴하는데, 이 리턴한 주소를 저장하는 곳이 변수이다. (예시에서는 변수 car)

 

이렇게 new 연산자로 객체를 생성하고 리턴한 객체의 주소를 변수에 저장하면, 힙 영역에 있는 객체의 주소를 스택 영역의 변수가 저장한다. (주소만 기억하고 있다!)

※기본형 8가지 빼고는 다 참조형!

 

이렇게 하나의 클래스로 인스턴스를 여러개 생성할 경우, 각각의 Print 객체는 자신만의 고유 데이터를 가지면서 메모리에서 활동하게 된다.

 

Print p1 = new Print();
Print p2 = new Print();
//p1, p2 : 인스턴스

p1과 p2가 참조하는 Print 객체는 완전히 독립된 서로 다른 객체이다.

 

  • 같은 객체인데 왜 서로 다른 주소를 참조할까? ⇒ New 로 생성했기 때문에.
  • 라이브러리용과 실행용 클래스 : 프로그램 전체에서 사용되는 클래스가 100개라면 99개가 라이브러리, 1개가 실행 클래스 실행클래스는 main() 메소드에서 실행 진입점만 제공.
  • 라이브러리 코드 : 필드, 생성자, 메소드

 

6.5 클래스의 구성 멤버

 

6.5.1 필드

  • 변수는 생성자와 메소드 내에서만 사용되고 생성자와 메소드가 실행 종료되면 자동 소멸.
  • 필드는 생성자와 메소드 전체에서 사용되며 객체와 함께 존재함

 

6.5.2 생성자

  • 생성자는 new 연산자로 호출되는 특별한 중괄호 { } 블록
  • 객체 생성시 초기화를 담당
  • 필드를 초기화하거나, 메소드를 호출해서 객체를 사용할 준비를 함.
  • 클래스이름과 같고 리턴 타입이 없다.

 

6.5.3 메소드

  • 필드를 읽고 수정하는 역할
  • 다른 객체를 생성해서 다양한 기능을 수행
  • 객체 간의 데이터 전달의 수단. 외부로부터 매개값을 받아 실행 후 어떤 결과를 리턴할 수 있음.

 


 

6.6 필드

  • 객체의 고유 데이터, 객체가 가져야 할 부품, 객체의 현재 상태 데이터를 저장하는 곳.

 

6.6.1 필드 선언

  • 로컬 변수 : 생성자와 메소드 블럭 내부에 선언된 것
  • 필드 : 멤버 변수
  • 초기값이 지정되지 않으면 객체 생성 시 자동으로 기본 초기값으로 설정된다.

 

6.6.2 필드 사용

자기 클래스 내에서 사용할 경우에는 그냥 사용

외부 클래스에서 사용할 경우에는 우선 객체 생성 후에, 클래스 통해서 필드 값 불러와야함

//ex) Person 클래스에서 Car 클래스에 있는 speed 필드값을 사용하고 싶을때.
Car mycar = new Car();
mycar.speed = 60;

 

6.7 생성자

  • 생성자는 객체의 초기화를 담당한다.
  • 객체 초기화 : 필드를 초기화하거나, 메소드를 호출해서 객체를 사용할 준비를 하는 것.
  • 생성자를 실행시키지 않고 클래스로부터 객체를 만들 수 없음

 

6.7.1 기본 생성자

  • 모든 클래스는 생성자가 반드시 존재하며 하나 이상을 가질 수 있다. ⇒ 중복선언 (overloading 가능)
  • 생성자를 따로 기술하지 않더라도 JVM이 잠깐 빌려준다. (하나라도 있으면 빌려주지 X)

 

6.7.2 생성자 선언

  • 생성자 선언 안 해도 JVM이 빌려주는데 굳이 선언하는 이유? ⇒ 매개변수를 이용해 다양하게 오버로딩하기 위해서.
public class Car(String model, String colour, int speed){ ... }
// 3개의 매개변수값을 가진 클래스 Car

Car mycar = new Car ("Hyundai", "red", 300); // parameter값을 전달
  • 이렇게 Car 클래스에 매개변수 세개 가진 생성자를 명시적으로 선언했으므로 매개변수가 없는 기본 생성자는 호출할 수 없다.
Car mycar = new Car(); // 불가함! 기본 생성자를 호출할 수 없다

 

6.7.3 필드 초기화

  • 필드 초기화 방법 1) 필드를 선언할 때 초기값을 주는 방법 2) 생성자에서 초기값을 주는 방법
public Class Korea(){
String nation = "대한민국";
String name;
String ssn;
} // 생성자에서 초기값 주기

-------------------------

Korean k1 = new Korea();
Korean K2 = new Korea();
// 두 인스턴스는 같은 필드값이 저장되어 있다

 

But, 저렇게 생성자 내에서 초기값을 먼저 줘버리면 외부에서 제공되는 다양한 값에 의해서 초기화되기 힘들다. ⇒ 매개값으로 이 값들을 받아 초기화하기

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

 

매개변수의 이름은 관례적으로 필드의 이름과 동일하게 선언한다.

그러나 같은 이름이면 로컬변수가 우선순위가 높기 때문에 생성자 내부에서 해당 필드로 접근할 수 없다. ⇒ "this"로 "나는 필드입니다"라는 것을 명시해주기

Public Korean(String name, String ssn){
	this.name(필드변수) = name(매개변수);
	this.ssn = ssn;
}

6.7.4 생성자 오버로딩(Overloading)

  • 매개변수를 달리하는 생성자를 여러개 선언하는 것.
  • 매개변수의 개수, 타입, 순서가 다르게 선언.
  • 생성자뿐만 아니라 메소드도 오버로딩이 가능하다 (But 반환타입은 상관 x, 기준은 무조건 매개변수)

 

Car(String model, String colour){...}
Car(String colour, String model){...} // 오버로딩이 아님 (타입 같으니까)
  • new 연산자로 생성자를 호출할 때 제공되는 매개값의 타입과 수에 의해 호출될 생성자가 결정된다.

 

6.7.5 다른 생성자 호출(this())

  • 중복된 코드를 제거하기 위해서 필드 초기화 내용은 한 생성자에 집중적으로 작성하고 나머지 생성자는 초기화 내용을 가지고 있는 생성자를 호출하는 방법으로 개선.
  • 메소드는 생성자 호출 X
  • 부모 클래스는 어떻게 부를까? ⇒ super(소괄호), super.필드명, super.메소드명( )

 

public class Car {

String company = "현대자동차";
String model;
String color;
int maxSpeed;

//생성자
Car(){
	}

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 메소드 (p.214~)

 

  • 메소드 선언부 (리턴타입, 메소드이름, 매개변수 선언) = 메소드 시그너쳐
  • 메인에서 메소드를 호출할때도 클래스 객체를 생성하고 변수를 이용하여 호출
Calculator a = new Calculator();
a.powerOn(); // 객체 통하지 않고 메소드 호출할 순 없다 (static의 경우 제외)

 

매개변수의 수를 모를경우

int 배열 abc를 매개변수로 선언하고 배열의 합계를 return값으로 주었음

⇒ 매개변수의 수를 모르거나 변수들이 가변적이면 배열을 선언한다.

 

메인에서 배열 생성하여 매개변수로 대입한 예시

 

메인에서 배열 선언하기 번거로우면 다음과 같이 할 수도 있다

 

매개변수에 아무숫자나, 아무 개수나 넣을 수 있음!

6.8.2 리턴(return)문

  • return 문 이후의 실행문은 결코 실행되지 않는다 (Unreachable code)
  • if문의 경우 return문이 두개 이상 나올 수 있음
package practice;

public class GasExam {

	int gas;
	
	void setGas(int gas) {
		this.gas = gas;
	}
	
	boolean isLeftGas() {
		if(gas==0) {
			System.out.println("가스가 없습니다");
			return false;
		}else {
			System.out.println("가스가 있습니다");
			return true;
		}
	}
	
	void run() {
		while(true) { // break나 return 없는 이상 무한루프
			if(gas>0) {
				System.out.println("달립니다. (gas 잔량 : "+gas+")");
				gas -= 1;
			} else {
				System.out.println("멈춥니다. (gas 잔량 : "+gas+")");
				return; // return문이 없는 if에 return문으로 강제종료
			}
		} // end of while
	}
	
	public static void main(String[] args) {

		GasExam g = new GasExam();
		g.setGas(10);
		
		boolean gState = g.isLeftGas();
		if(gState) {
			System.out.println("출발합니다");
			g.run();
			
		}
		
		if(g.isLeftGas()) {
			System.out.println("가스를 주입할 필요가 없습니다");
		}else {
			System.out.println("가스를 주입하세요");
		}
		
	}

}

 

👉
예제1) 반환형 Exam1.java 1. 두 정수를 전달 받아 합계를 반환하는 메소드 구현 (Sum) 2. 두 정수(x, y) 를 전달받아 x~y까지의 합계를 구하여 리턴(sum2) 3. 가로, 세로값을 실수형으로 전달받아 사각형의 넓이를 계산하여 리턴 (sum3) 4. 구구단 수를 하나 전달받아 구구단을 하나의 문자열로 만들어 리턴 (gugudan)

 

6.8.3 메소드 호출

  • 클래스 내부 다른 메소드에서 호출할 경우 : 단순히 메소드 이름만 호출
boolean isLeftGas() {
		if(gas==0) {
			System.out.println("가스가 없습니다");
			run(); // 클래스 내 다른 메소드에서 호출
			return false;
		}else {
			System.out.println("가스가 있습니다");
			return true;
		}
	}
  • 클래스 외부 (메인도 포함) 에서 호출할 경우 : 클래스로부터 객체를 생성한 뒤, 참조 변수를 이용하여 메소드를 호출한다. 객체가 존재해야 메소드도 존재하기 때문
  • 메소드는 객체에 소속된 멤버!

 

6.8.4 메소드 오버로딩 (p. 230~ )

  • 클래스 내에 같은 이름의 메소드를 여러 개 선언하는 것
  • 매개변수의 타입, 개수, 순서 중 하나가 달라야 오버로딩
  • 매개값을 다양하게 받아 처리할 수 있도록 하기 위해서 (int, String, boolean 등 다양한 매개변수를 받기 위해)

 

매개변수 중 하나가 타입이 다르니까 오버로딩
리턴타입이 다르더라도 매개변수 타입, 개수, 순서 다 같아서 오버로딩 X

 

 

위와 같이 매개변수 타입이 다른 메소드가 두개 있을 경우,
다음과 같은 코드를 실행하면 둘 중 어떤 메소드가 실행될까?

System.out.println(g.plus(10, 15.5));

=> int 타입은 double로 자동변환될 수 있으므로, 범위가 넓은 double 타입 매개변수의 메소드 실행

 

 

  • 메소드 오버로딩의 가장 대표적인 예 : System.out.println( ) 메소드!
많은 타입을 다 매개변수로 받을 수 있다 (오버로딩 많이 많이)

 

6.9 인스턴스 멤버와 this (p. 233~)

  • 인스턴스 멤버 : 객체(인스턴스)를 생성한 후 사용할 수 있는 필드와 메소드 (인스턴스 필드, 인스턴스 메소드)
  • 인스턴스 필드와 메소드는 객체에 소속된 멤버이기 때문에 객체 없이는 사용할 수 없다.

 

public class Car {
	//필드
	int gas;

	//메소드
	void setSpeed(int speed){ ... }

}
--------------------------------------------------------
// 인스턴스 1 : mycar
Car myCar = new Car();
myCar.gas = 10;
myCar.setSpeed(50);

// 인스턴스 2 : yourcar
Car youCar = new Car();
yourCar.gas = 20;
yourCar.setSpeed(100);
  • 인스턴스 필드 gas는 객체마다 따로 존재하고, 인스턴스 메소드 setSpeed( ) 는 객체마다 존재하지 않고 메소드 영역에 저장되고 공유된다.

 

6.10 정적 멤버와 static (p. 236~)

  • 정적 멤버 : 클래스에 고정된 멤버로서 객체를 생성하지 않고 사용할 수 있는 필드와 메소드 (정적 필드, 정적 메소드 = 클래스형 변수, 클래스형 메소드)
  • 객체에 소속된 멤버가 아니라 클래스에 소속된 멤버이기 때문에 클래스 멤버라고도 한다.

 

👉
(예제) static 변수 하나의 은행에 여러 계정을 통해 입출금 테스트 Bank에 저장된 예금액을 입금(deposit(int)), 출금(withdraw(int))을 처리후 prn() 을 통해 잔액 표시 - 클래스 - Bank.java - Account.java - StaticExam.java

 

6.10.1 정적 멤버 선언

  • 정적 필드와 정적 메소드는 클래스에 고정된 멤버이므로 클래스 로더가 클래스(바이트 코드)를 로딩해서 메소드 메모리 영역에 적재할 때 클래스별로 관리된다. (그래서 메모리 차지 높음)
  • 객체마다 가지고 있어야 할 데이터라면 인스턴스 필드, 객체마다 가지고 있을 필요성이 없는 공용적인 데이터라면 정적 필드로 선언!

 

6.10.2 정적 멤버 사용

  • 클래스가 메머리로 로딩되면 정적 멤버를 바로 사용할 수 있다.
Car.color;
Car.run(매개변수);

=> 객체 생성하지 않고 바로 클래스 이름을 통해 사용할 수 있다.

 

6.10.3 정적 초기화 블록

  • 정적 필드는 필드 선언과 동시에 초기값을 준다
static double pi = 3.14159;
  • 인스턴스 필드는 생성자에서 초기화하지만, 정적 필드는 객체 생성 없이도 사용해야 하므로 생성자에서 초기화 작업을 할 수 없다. (생성자는 객체 생성시에만 실행되기 때문에!)

 

public class Television {

	static String company = "samsung";
	static String colour = "red";
	static String info;
	static { // 정적 초기화 블록
		info = company + "-" + colour;
	}
}
  • 주의할 점 : 정적 메소드와 정적 블록을 선언할 때 내부에 인스턴스 필드나 인스턴스 메소드를 사용할 수 없다. (객체가 없어도 실행된다는 특징 때문에)
  • 정적 메소드 내에서는 this 키워드도 사용이 불가능하다.
static void Method3 {
	this.field1 = 10; (x)
	this.method1(); (x)
	field2 = 20;
	method2();
}

 

  • 인스턴스 변수/메소드는 객체가 생성된 이후에 사용할 수 있고, 정적(static)은 객체가 생성되기 전에 메모리에 올라간다.

 

static void Method3(){
	ClassName obj = new ClassName();
	obj.field1 = 10;
	obj.method1();
	}
  • 정적 메소드와 정적 블록에서 인스턴스 멤버를 사용하고 싶다면 위와 같이 객체를 먼저 생성하고 참조 변수로 접근해야 함.
  • main도 static이기 때문에 객체를 생성하기 전까지는 바로 사용할 수 없는 것.

 

6.10.5 싱글톤(Singleton)

  • 단 하나의 객체만 만들도록 보장해야 하는 경우가 있는데, 단 하나의 객체만 생성된다고 해서 이 객체를 싱글톤(singleton)이라고 한다.

 

public class 클래스 {
	//정적 필드
	private static 클래스 singleton = new 클래스();

	//생성자
	private 클래스(){}

	//정적 메소드
	static 클래스 getInstance(){
			return singleton;	
	}
}

1) 자신의 클래스 타입인 정적필드 하나 선언, 자신의 객체 생성 2) 생성자도 private 3) getInstance( ) : 외부에서 호출할 수 있는 정적 메소드. 자신이 참조하고 있는 자신의 객체를 리턴 ⇒ 외부에서 객체를 얻는 유일한 방법은 getInstance( ) 메소드를 호출하는 것!

⇒ 단, getInstance( ) 메소드는 단 하나의 객체만 리턴하기 때문에 아래 코드에서 변수1과 2는

동일한 객체를 참조

클래스 변수1 = 클래스.getInstance();
클래스 변수2 = 클래스.getInstance();

 

  • 결론 : 객체가 아무리 많이 만들어졌어도 싱글톤에서는 하나의 객체만 접근해서 쓴다!

 

6.11 final 필드와 상수

6.11.1 final 필드

  • final : 초기값 = 최종적인 값 (수정 불가)

1) 변수에 붙이는 경우 :값을 변경할 수 없음 (상수화 된다) ex) final double PI = 3.14; ex2) final static double PI = 3.14;(완벽)

2) 메소드에 붙이는 경우 :자식클래스가 재정의하지 못하게 함. (override)

3) 클래스에 붙이는 경우 상속을 하지 못함. (클래스가 거기서 끝) 상속 -> 부모 클래스의 기능을 확장해서 자식 클래스가 쓰는 개념. final 붙어있으면 재건축, 클래스 확장 X

 

6.11.2 상수(static final)

  • final 필드는 객체마다 저장되고, 생성자의 매개값을 통해서 여러가지 값을 가질 수 있기 때문에 상수가 될 수 없다.
  • 상수는 static 이면서 final
  • 객체마다 저장 x, 클래스에만 포함

 

6.13 접근제한자

  • public : 모든 클래스에서 접근 가능
  • protected : 자식 클래스, 동일한 패키지, 상속된 패키지 ("우리 가족만")
  • default : 같은 패키지내에서만 접근 가능
  • private : 같은 클래스 (외부는 전부 접근 x)

 

6.13.1 클래스의 접근 제한

  • 클래스에 적용할 수 있는 접근 제한자는 public, default

 

6.13.2 생성자의 접근 제한

  • 자동으로 생성되는 기본 생성자의 접근 제한은 클래스의 접근 제한과 동일

 

6.13.3 필드와 메소드의 접근 제한

  • public, protected, default, private 접근 제한

 

6.14 Getter와 Setter 메소드

  • 외부에서 마음대로 객체를 읽고 변경할 경우 무결성이 깨지는 문제때문에, 데이터는 외부에서 접근할 수 없도록 막고 메소드는 공개해서 외부에서 메소드를 통해 데이터에 접근하도록 유도함. (Setter)
vode setSpeed(double speed){
		if(speed < 0) {
			this.speed = 0;
			return;
		}else {
			this.speed = speed;
		}
}
// 매개값이 음수일 경우 speed 필드에 0으로 저장하고, 메소드 실행 종료

 

  • 객체 외부에서 객체의 필드값을 사용하기에 부적절할때, 메소드로 필드값을 가공한 후 외부로 전달 (Getter)
double getSpeed(){
		double km = speed*1.6;
		return km;
}
// 필드값인 마일을 km 단위로 환산 후 외부로 리턴

 

  • 클래스를 선언할 때 가능하면 필드를 private으로 선언해서 외부로부터 보호하고, 필드에 대한 Setter와 Getter 메소드를 사용하여 필드값을 안전하게 변경/사용하는 것이 좋다.

 

6.15 어노테이션

  • @Override : 메소드가 오버라이드 (재정의) 된 것임을 컴파일러에게 알려주어 컴파일러가 검사하도록 해준다.

 

 

Notion2Tistory

 

boltlessengineer.github.io

이것이 자바다 (신용권의 Java 프로그래밍 정복)

 

이것이 자바다

『이것이 자바다』은 15년 이상 자바 언어를 교육해온 자바 전문강사의 노하우를 아낌 없이 담아낸 자바 입문서이다. 자바 입문자를 배려한 친절한 설명과 배려로 1장에 풀인원 설치 방법을 제

book.naver.com