chapter 6 객체지향 1

JMG·2022년 1월 16일
0

자바의 정석

목록 보기
9/13

📖 객체지향 언어

80년대 초 소프트웨어의 위기 - 빠른 변화를 못쫓아감
해결책으로 객체지향 언어를 도입(절차적 -> 객체지향)
c++ -> Java(1996)

📌 객체지향을 사용하는 이유

코드의 재사용성이 높고 유지보수가 용이, 중복 코드를 제거해줍니다.
객체지향 언어 = 기존 프로그래밍 언어 + 객체지향개념(규칙)

📌 객체지향의 핵심개념

  1. 캡슐화
  2. 상속
  3. 추상화
  4. 다형성(중요)

📖 클래스와 객체

구분정의용도
클래스클래스란 객체를 정의해 놓는것(설계도)클래스는 객체를 생성하는데 사용
객체실제로 존재하는것, 사물 또는 개념(실제 제품)객체가 가지고 있는 기능과 속성에 따라 다름

📖 객체의 구성요소

속성과 기능
실제 사는 세상을 구현하다가 등장한 개념입니다.

객체 = 속성(변수) + 기능(메서드)

tv로 예를 들자면..
속성: 크기, 길이, 높이, 색상, 볼륨, 채널
기능: 켜기, 끄기, 볼륨높이기, 볼륨낮추기

📖 객체와 인스턴스

세세한 차이가 있으나 거의 같은 의미입니다.

  • 객체: 모든 인스턴스를 대표하는 일반적 용어
  • 인스턴스: 특정 클래스로부터 생성된 객체
    클래스 -> (인스턴스화) -> 인스턴스(객체)

📌 클래스와 객체

클래스가 왜 필요한가?
객체를 생성하기 위해(TV 설계도)

객체가 왜 필요한가?
객체를 사용하기 위해(TV 제품)

객체를 사용한다는 의미는?
객체가 가진 속성과 기능을 사용하려한다.

📖 하나의 소스파일에 여러 클래스 작성하기

📌 올바른 작성 방법

public 클래스가 있는 경우 소스파일의 이름은 반드시
public class이름과 일치해야 합니다.

//Hello2.java
public class Hello2 { }
class Hello3 { }

public class가 하나도 없는 경우, 소스파일의 이름은
'Hello2.java', 'Hello3.java'둘 다 가능합니다.

class Hello2 { }
class Hello3 { }

📌 잘못된 작성 방법

하나의 소스파일 안에 둘이상의 public class가 있는경우

public class Hello2 { }
public class Hello3 { }

소스파일의 이름이 public class와 일치하지 않는경우

//Hello2.java
public class Hello1 { }
class Hello2 { }

소스파일의 이름과 클래스의 이름의 대소문자를 구분하지 않은경우

//hello1.java
public class Hello1 { }

main함수가 있는 클래스로 소스파일의 이름을 생성해줘야합니다.
하나의 소스파일에는 하나의 클래스만 작성하는 것이 바람직합니다!

📖 객체의 생성과 사용

클래스명 변수명; // 클래스의 객체를 참고하기 위한 참조변수를 선언
변수명 = new 클래스명(); // 클래스의 객체를 생성 후, 객체의 주소를 참조변수에 저장

📌 참조변수에 메모리주소가 저장된다는 개념이 중요합니다.

예제)

//클래스 생성
class Tv {
	String color;
	boolean power;
	int channel;
	
	void power() { power = !power; }
	void channelUP() { channel++; }
	void channelDown() { channel--; }
}

//객체의 생성
Tv t; // Tv클래스 타입의 참조변수 t를 선언
t = new Tv(); // Tv 인스턴스를 생성한 후, 생성된 Tv인스턴스의 주소(메모리주소)를 t(리모콘같은 존재)에 저장


//객체의 사용
t.channel = 7; // Tv인스턴스의 멤버변수 channel의 값을 7로 한다.
t.channelDown(); // Tv인스턴스의 메서드 channelDown()을 호출한다.
System.out.println("현재 채널은 " + t.channel + " 입니다."); // "현재 채널은 6 입니다."



//객체를 2개생성하기
Tv t1 = new Tv();
Tv t2 = new Tv();

t1.channel = 7;
t2.channel = 10;
System.out.println("t1.channel : "+t1.channel+", t2.channel : "+t2.channel); // "t1.channel : 7, t2.channel : 10"
//프로그램을 종료하면 가비지 컬렉터가 객체의 참조변수들을 메모리에서 정리해준다.

📖 객체배열

객체배열 == 참조변수 배열

//객체배열 생성
Tv[] tvArr = new Tv[3];

//객체를 생성해서 배열의 각 요소에 저장
tvArr[0] = new Tv();
tvArr[1] = new Tv();
tvArr[2] = new Tv();

//아래와 같이 작성할 수도 있다.
Tv[] tvArr = {new Tv(), new Tv(), new Tv()};

📌 각 배열의 요소에 객체를 생성해주지 않으면 참조변수로서 사용이 불가능합니다.

📖 클래스의 정의

  • 설계도
  • 클래스 == 데이터(변수) + 함수
  • 사용자가 직접 정의하는 '사용자 정의 타입'

데이터의 발전과정
1. 변수 - 하나의 데이터를 저장할 수 있는 공간
2. 배열 - 같은 타입의 여러 데이터를 하나로 저장할 수 있는 공간
3. 구조체 - 서로 관련된 여러 타입의 데이터를 하나로 저장할 수 있는 공간
4. 클래스 - 데이터와 함수의 결합(구조체 + 함수들)

사용자 정의 타입이란?
원하는 타입을 직접 만들 수 있습니다.
연관성 높은 변수나 함수를 묶어서 클래스를 만들어서 원하는 타입으로 참조변수를 생성할 수 있습니다.

예제)

//객체가 등장하기전
int hour = 12;
int minute = 34;
int second = 56;

//변수로 생성하는 것보다 클래스로 묶어서 생성하는 것이 관리에 용이하다
class Time {
	int hour;
	int minute;
	int second;
}

Time t = new Time();
t.hour = 12;
t.minute = 34;
t.second = 56;

📖 선언 위치에 따른 변수의 종류

변수의 종류선언위치생성시기
클래스 변수클래스 영역클래스가 메모리에 올라갈 때
인스턴스 변수클래스 영역인스턴스가 생성되었을 때
지역 변수클래스 영역 이외의 영역변수 선언문이 수행 되었을 때
class Variables { //클래스영역
	
	//클래스 영역에서는 선언문만 가능하다!
	static int cv; // 클래스 변수
	int iv;	//인스턴스변수
	
	void method() { //메서드 영역
		int lv = 0; // 지역변수
		//메서드 종료시 자동제거된다.
	}

}

📌 객체(인스턴스)를 간단히 설명하면 인스턴스변수(iv)를 묶어놓은것과 같습니다.

📖 클래스 변수(cv)와 인스턴스 변수(iv)의 특징

인스턴스변수

  • 개별적인 속성을 가져야할 때 사용한다.
  • 인스턴스가 생성될때마다 메모리에 새롭게 만들어진다.

클래스 변수

  • 공통적인 속성을 가져야할 때 사용한다.
  • 클래스를 생성할때 한번만 생성되고 공유된다.
  • 클래스 그 자체로 접근이 가능하다.

예제)

class Card {
	
	String kind; //무늬
	int number;	//숫자
	
	static int width = 100; //폭
	static int height = 250;	//높이

}

Card c = new Card(); //객체 생성
c.kind = "HEART";
c.number = 5;

//객체를 이용해서 클래스변수를 조작할 수 있다.
//그러나 권장되는 방법은 아니다.
//다른 공동 개발자들이 해당 변수가 인스턴스 변수로 착각할 수도 있다.
c.width = 200;
c.height = 300;

//클래스 변수는 클래스로 접근해서 조작하는 것을 권장한다.
Card.width = 200;
Card.height = 300;

📖 메서드

  1. 메서드란 문장들을 묶어놓은 것 입니다.
  • 작업단위로 문장들을 묶어서 이름을 붙인것.
  • 중복되는 코드를 줄여주는 역할을 한다.

예제)

public class MethodTest {

	//배열을 만드는 함수
	static int[] makeArr() {
		int[] numArr = new int[10];
		for(int i=0; i<10; i++) {
			numArr[i] = (int)(Math.random() * 10);
		}
		return numArr;
	}

	//배열을 출력하는 함수
	static void printArr(int[] num Arr){

		for(int i=0; i<10; i++)	{
			System.out.printf("%d", numArr[i]);
			System.out.println();
		}

	
	}

	public static void main(String args[]) {
		
		int[] numArr1 = makeArr(); //배열생성
		printArr(numArr1); //배열 출력
		int[] numArr2 = makeArr(); //배열생성
		printArr(numArr2);  //배열 출력
		//......
	}
}
  1. 값(입력)을 받아서 처리하고, 결과를 반환(출력)

메서드 = 선언부 + 구현부

  • 입력하는 변수는 0~n개 가능
  • 반환하는 변수는 0~1개만 가능
  • 반환하는 변수가 0개인 경우 반환타입은 void
반환타입(출력) 메서드이름(타입 변수명1, 타입 변수명2, ....)//선언부 {

	//메서드 호출시 수행될 코드
	return 반환값(반환타입과 동일해야함)
	
} // 구현부


//예시
int add(int x, int y) {
	int result = x + y;
	return result;
}
//x, y, result 전부 지역변수이다.
//위 변수들은 함수 블럭안에서만 사용할 수 있다.

메서드의 장점

  • 코드의 중복을 줄일 수 있다.
  • 코드의 관리가 쉽다.
  • 코드를 재사용할 수 있다.
  • 코드가 간결해져서 이해하기가 쉬워진다.

메서드의 작성방법

  • 반복적으로 수행되는 여러 문장을 메서드로 작성
  • 하나의 메서드는 한 가지 기능만 수행하도록 작성하는 것이 좋다.

📖 메서드의 호출

매개변수가 있는 경우
메서드의 매개변수(파라미터)는 메서드가 선언한 갯수와 타입에 맞게 사용해야 합니다.

return값이 없는경우
메서드이름(값1, 값2, .....); // 메서드를 호출하는 방법

return값이 있는경우
변수 = 메서드이름(값1, 값2, .....); // 메서드를 호출하는 방법

예제)
public class Ex6_4 {
	public static void main(String[] args) {
		
		MyMath mm = new MyMath();
		long result1 = mm.add(5L, 3L);
		long result2 = mm.subtract(5L, 3L);
		long result3 = mm.multiply(5L, 3L);
		long result4 = mm.divide(5L, 3L);

		System.out.println("add(5L, 3L) = "+result1);
		System.out.println("subtract(5L, 3L) = "+result2);
		System.out.println("multiply(5L, 3L) = "+result3);
		System.out.println(" mm.divide(5L, 3L) = "+result4);

	}
}

class MyMath {

	long add(long a, long b) {
		return a + b;
	}

	long subtract(long a,long b) {
		return a - b;
	}

	long multiply(long a, long b) {
		return a * b;
	}

	long divide(long a, long b) {
		return a / b;
	}

}

//결과 
//add(5L, 3L) = 8
//subtract(5L, 3L) = 2
//multiply(5L, 3L) = 15
//mm.divide(5L, 3L) = 1

메서드의 실행흐름

  • main메서드에서 add를 호출한다. 인수 5L과 3L이 메서드 add의 매개변수 a, b에
    각각 복사(대입)된다.
  • 메서드 add의 괄호{ }안에 있는 문장들이 순서대로 수행된다.
  • add의 모든 문장이 실행되거나 return문을 만나면, 호출한 메서드(main메서드)로 돌아와서
    이후의 문장들을 실행한다.

📖 return문

실행 중인 메서드를 종료하고 호출한 곳을 되돌아갑니다.

void printGugudan(int dan) {
	if(!(2<= dan && dan <= 9)) {
		return; // dan의 값이 2~9가 아닌경우, 호출한 곳으로 되돌아간다.
	}

	for(int i=1; i<=9; i++) {
		System.out.printf("%d * %d = %d\n", dan, i, dan * i);
	}
	return; 반환타입이 void이므로 생략가능, 컴파일러가 자동추가
}

반환타입이 void가 아닌 경우, 반드시 return문이 필요합니다.

//조건문의 경우 반드시 모든 조건 상황에 return문을 작성하거나 조건문 밖에 작성해야한다.
int max(int a, int b) {
	
	if(a > b) {
		return a;
	} else {
		return b;
	}
}

반환값
return에 들어가는 값은 반드시 함수를 선언할때 작성한 반환타입과 일치해야합니다.

📖 호출스택(call stack)

스택(stack)
밑이 막힌 상자. 위에 차곡차곡 쌓인다.

호출스택(call stack)
메서드 수행에 필요한 메모리가 제공되는 공간
메서드가 호출되면 호출스택에 메모리 할당, 종료되면 해제

아래 있는 메서드가 위의 메서드를 호출한 순서대로 쌓인다.
맨 위의 메서드 하나만 실행중, 나머지는 대기중

📖 기본형 매개변수

기본형 매개변수(기본형 8가지타입) - 변수의 값을 읽기만 할 수 있습니다.
예제)

class Data { int x; }

class Ex6_6 {
	public static void main(String[] args) {
		Data d = new Data();
		d.x = 10;
		System.out.println("main() : x = "+d.x);
		
		change(d.x);
		System.out.println("After change(d.x)");
		System.out.println("main() : x = "+d.x);
	}
	
	static void change(int x) { //기본형 매개변수
		x = 1000;
		System.out.println("change() : x = "+x);
	}
}
			

결과)
main() : x = 10
change() : x = 1000
After change(d.x)
main() : x = 10

📖 참조형 매개변수

변수의 값을 읽고 변경할 수 있습니다.
예제)

class Data { int x; }

class Ex6_7 {
	public static void main(String[] args) {
		Data d = new Data();
		d.x = 10;
		System.out.println("main() : x = "+d.x);
		
		change(d);
		System.out.println("After change(d.x)");
		System.out.println("main() : x = "+d.x);
	}
	
	static void change(Data d) { //참조형 매개변수
		d.x = 1000;
		System.out.println("change() : x = "+d.x);
	}
}

결과)
main() : x = 10
change() : x = 1000
After change(d.x)
main() : x = 1000

📖 참조형 반환타입

예제)

class Ex6_8 {
	public static void main(String[] args) {

		Data d = new Data();
		d.x = 10;

		Data d2 = copy(d);
		System.out.println("d.x = "+d.x);
		System.out.println("d2.x = "+d2.x);
		
	}
	
	static Data copy(Data d) { //참조형 매개변수
		Data tmp = new Data(); // 새로운 객체 tmp를 생성한다.
		
		tmp.x = d.x; // d.x의 값을 tmp.x에 복사한다.
		
		return tmp; // 복사한 객체의 주소를 반환한다.
	}
}

결과)
d.x = 10
d2.x = 10

📖 static메서드와 인스턴스 메서드

인스턴스 메서드

  • 인스턴스 생성 후, '참조변수.메서드 이름()'으로 호출
  • 인스턴스 맴버(iv, im)와 관련된 작업을 하는 메서드
  • 메서드내에서 인스턴스 변수(iv)사용가능

static메서드(클래스메서드)

  • 객체생성없이 '클래스이름.메서드이름()'으로 호출(ex) Math.ramdom())
  • 인스턴스 맴버(iv, im)와 관련없는 작업을 하는 메서드
  • 메서드내에서 인스턴스 변수(iv)사용불가

예제)

class MyMath2 {
	
	long a, b; // 인스턴스 변수

	long add() { // 인스턴스 메서드
		return a + b; // 인스턴스 메서드는 인스턴스 변수사용가능
	}

	static long add(long a, long b) { // 클래스메서드(static메서드)
		return a + b;
	}

}

//사용법

MyMath2.add(200L, 100L); //클래스메서드 호출

MyMath2 mm = new MyMath2(); //인스턴스(참조변수)를 먼저 생성해야한다.

mm.a = 200L; // 인스턴스 변수 대입
mm.b = 100L;

mm.add(); // 인스턴스메서드 호출

📌 static을 언제 붙여야 할까?

static변수는 속성(멤버 변수)중에서 공통 속성에 붙인다.
static메서드는 인스턴스변수를 사용하지 않는 메서드에 붙인다.

📖 메서드간의 호출과 참조

static메서드는 인스턴스 변수를 사용할 수 없습니다.
예제)

class TestClass {
	
	int iv; // 인스턴스 변수
	static int cv; // static 변수
	
	void instanceMethod() {}
	static void staticMethod() {]

	void instanceMethod2() { // 인스턴스 메서드
		System.out.println(iv); //인스턴스 변수를 사용할 수 있다.
		System.out.println(cv); //클래스 변수를 사용할 수 있다.
		instanceMethod(); // 인스턴스 메서드를 호출할 수 있다.
		staticMethod(); // static 메서드를 호출할 수 있다.
	}

	static void staticMethod2() { // static메서드
		//System.out.println(iv); //에러!! 인스턴스 변수를 사용할 수 없다.(객체생성이 필요)
		System.out.println(cv); //클래스 변수는 사용할 수 있다.
		//instanceMethod(); //에러!! 인스턴스 메서드를 호출할 수 없다.
		staticMethod(); // static 메서드를 호출할 수 있다.
	}
	
}

결론

  • static메서드는 인스턴스변수와 인스턴스 메서드를 사용할 수 없다.
  • 인스턴스 멤버들은 인스턴스 생성 후 에만 사용이 가능한데 static메서드는
  • 인스턴스와 상관없이 클래스에서 사용하기 때문에 인스턴스멤버를 사용할 수 없다.

📖 오버로딩(overloading)

한 클래스 안에 같은 이름의 메서드를 여러 개 정의하는 것을 의미합니다.(중복정의)

대표적인 예로 println이 있다.
void println()
void println(boolean x)
void println(char x)
void println(char[] x)
void println(float x)
void println(int x)
void println(long x)
void println(Object x)
void println(String x)

오버로딩이 성립하기 위한 조건 3가지 조건

  • 메서드의 이름이 같아야한다.
  • 매개변수의 개수 또는 타입이 달라야 한다.
  • 반환 타입은 영향없다.
//다음과 같은 케이스는 모호하여 에러가 발생한다.(ambiguous)
long add(int a, long b) { return a+b; }
long add(long a, int b) { return a+b; }
add(3, 3);//에러발생!

오버로딩의 올바른 예
매개변수는 다르지만 같은 의미의 기능수행할 때 사용합니다.

예제)

class MyMath {

	int add(int a, int b) {
		System.out.println("int add(int a, int b)");
		return a + b;
	}

	long add(long a, long b) {
		System.out.println("long add(long a, long b)");
		return a + b;
	}

	int add(int[] a) {
		System.out.println("int add(int[] a)");
		int result = 0;
		for(int i : a) {
			result += i;
		}
		return result;
	}

}

📖 생성자(constructor)

인스턴스가 생성될 때마다 호출되는 '인스턴스 초기화 메서드'
인스턴스 생성시 수행할 작업(iv 초기화에 사용)

Time t = new Time();
t.hour = 12;
t.minute = 34;
t.second = 56;

//생성자를 이용하면 아래와 같이 개선할 수 있다.
Time t = new Time(12, 34, 56); 

생성자(construnct) 생성규칙

  • 이름이 클래스 이름과 같아야 한다.
  • return값이 없다.(void 안붙임)
  • 모든 클래스는 반드시 생성자를 가져야 한다.(안만들었다면 기본생성자가 생성됨)
클래스이름(타입 변수명, 타입 변수명) {
	//인스턴스 생성 시 수행될 코드
	//주로 인스턴스 변수의 초기화 코드를 적는다.
}

기본 생성자(default constructor)

  • 매개변수가 없는 생성자
  • 생성자가 하나도 없을 때만 컴파일러가 자동으로 추가
  • 만약 클래스를 만들 때 생성자를 생성해줬다면 컴파일러가 기본 생성자를 만들지 않는다.(중요)
클래스이름() { }

Point() {} //Point라는 클래스의 기본 생성자

매개변수가 있는 생성자
예제)

class Car {
	String color;
	String gearType;
	int door;
	
	Car() {} //기본 생성자
	Car(String c, String g, int d) { // 매개변수가 있는 생성자
		color = c;
		gearType = g;
		door = d;
	}
}

Car c = new Car("white, "auto", 4);

📖 생성자 this()

생성자에서 다른 생성자 호출할 때 사용합니다.
다른 생성자 호출시 첫 줄에서만 사용가능합니다.
코드의 중복을 제거할 때 사용합니다.

예제)

class Car2 {

	String color;
	String gearType;
	int door;

	Car2() { //Car2(String color, String gearType, int door)호출
		this("white", "auto", 4);
	}
	
	Car2(String color) { //Car2(String color, String gearType, int door) 호출
		this(color, "auto", 4);
	}

	Car2(String color, String gearType, int door) {
		this.color = color;
		this.gearType = gearType;
		this.door = door;
	}

}

📖 참조변수 this

인스턴스 자신을 가리키는 참조변수를 의미합니다.
인스턴트 메서드(생성자포함)에서 사용가능합니다.
지역변수(lv)와 인스턴스변수(iv)를 구별할 때 사용합니다.
this()와 전혀 관계없습니다.

예제)

Car2(String color, String gearType, int door) {
		this.color = color; // 인스턴스 변수 = 지역 변수
		this.gearType = gearType;
		this.door = door;
}

📖 참조변수 this와 생성자 this()

this

  • 인스턴스 자신을 가리키는 참조변수, 인스턴스의 주소가 저장되어 있다.
  • 모든 인스턴스 메서드에 지역변수로 숨겨진 채로 존재한다.
    (long a, b; // this.a, this.b(this 생략가능)
  • 클래스 메서드에서는 사용불가

this(), this(매개변수)

생성자, 같은 클래스의 다른 생성자를 호출할 때 사용한다.

📖 변수의 초기화

지역변수(lv)는 수동 초기화 해야합니다.(사용전에 꼭!)
멤버변수(iv, cv)는 자동 초기화됩니다.

class InitTest {
	int x; // 인스턴스 변수
	int y = x; // 인스턴스 변수
	
	void method1() {
		int i; // 지역변수
		int j = i; // 에러!! 지역변수를 초기화하지 않고 사용
	}
}

📖 멤버변수의 초기화

  1. 명시적 초기화(=)
class Car {
	int door = 4; // 기본형(primitive type) 변수의 초기화
	Engine e = new Engine(); // 참조형(reference type) 변수의 초기화

2.초기화 블럭
복잡한 초기화에 사용한다.

  • 인스턴스 초기화 블럭 : { }
  • 클래스 초기화 블럭 : static { }
class StaticBlockTest {
	static int[] arr = new int[10];  // 명시적 초기화

	static { // 클래스 초기화 블럭 - 배열 arr을 난수로 채운다.
		for(int i=0; i<arr.length; i++) {
			arr[i] = (int)(Math.random() * 10) +1;
		}
	}

}
  1. 생성자
    인스턴스 변수 초기화에 사용한다.
    복잡한 초기화에 사용한다.

클래스 변수의 초기화 시점 : 클래스가 처음 로딩될 때 단 한번
인스턴스 변수의 초기화 시점 : 인스턴스가 생성될 때 마다

예제)

class InitTest {

	static int cv = 1; // 명시적 초기화
	int iv = 1; // 명시적 초기화

	static { cv = 2; } // 클래스 초기화 블럭
	{ iv = 2; } // 인스턴스 초기화 블럭
	
	InitTest () { //생성자
		iv = 3;
	}
}

초기화 순서
1. 클래스변수 다음 인스턴스 변수가 초기화
2. 자동 -> 간단 -> 복잡

profile
초보개발 블로그입니다~

0개의 댓글