230117 - 변수, 클래스, 객체 - 공개

블랑·2023년 1월 18일
0

Daily Study

목록 보기
4/5

1. 객체지향 프로그래밍

추상화

Abstraction : 컴퓨터 과학에서 추상화(abstraction)는 복잡한 자료, 모듈, 시스템 등으로부터 핵심적인 개념 또는 기능을 간추려 내는 것을 말한다.

  • 객체에서 공통된 속성과 행위를 추출 하는 것
  • 공통의 속성과 행위를 찾아서 타입을 정의하는 과정
  • 추상화는 불필요한 정보는 숨기고 중요한 정보만을 표현함으로써 프로그램을 간단하게 만드는 것

축구, 야구, 농구 등은 모두 '스포츠'에 해당된다. '스포츠'라는 추상화 집합을 만들어두고 해당 스포츠들이 가진 공통적인 특징들을 만들어서 활용한다.

정의

객체지향 프로그래밍 : 주변의 많은 것들을 객체화해서 프로그래밍 하는 것.
객체 지향 프로그래밍 (Object-Oriented Programming, OOP)은 프로그래밍에서 필요한 데이터를 추상화 시켜 상태와 행위를 가진 객체로 만들고, 객체들간의 상호작용을 통해 로직을 구성하는 프로그래밍 방법이다.

장점

  • 블록 형태의 모듈화된 프로그래밍 - 모듈화 되었기에 기존 베이스를 기반으로 작업 용이함.
  1. 신뢰성 높은 프로그래밍 가능
  2. 추가/수정/삭제 용이
  3. 재사용성이 높음

마치 레고 블럭을 갈아끼우는 것처럼, 모듈화된 내용이 존재하기에 다음과 같은 장점을 가질 수 있는 것이다.

현실 세계 객체, 클래스, 프로그램의 객체(instance, object)의 관계

  • 현실 객체의 속성과 기능 등은 추상화(abstraction)되어 클래스에 정의된다.
  • 추상화된 클래스는 구체화되어 프로그램의 객체가 된다.

현실에서는 설계도 등을 사용하여 제품을 일정하게 생성한다.
이것을 프로그램으로 따지면 설계도는 종류(Type)가 되고 설계도를 통해 나온 결과물을 객체라고 부른다.

다음과 같은 예시로 확인해보자. 현실 세계에서는 다음과 같은 사람이 있다. 이를 객체화하면 다음과 같다.

철수					Person(클래스)			

이름: 철수				String name;
나이: 40				 	int age;
배고픔: X				boolean isHungry;
--------				--------
먹는다					void eat()
일한다					void work()

이렇게 정의된 클래스는 메모리 공간에 할당되어 구체화하여 쓸 수 있다.

  • 클래스
  1. 객체를 정의해 놓은것 = 설계도, 틀
  2. 클래스는 직접 사용할 수 없고 blueprint를 제공한다.
  • 객체(instance, object)
  1. 클래스를 데이터 타입으로 메모리에 생성된 것

실습

  • Person 클래스 선언
public class Person {
	//속성
	String name;
	int age;
	boolean isHungry;
	
	//동작
	void work() {
		isHungry = true;
	}
	void eat() {
		isHungry = false;
	}

}
  • Person 클래스 사용하여 객체 생성
		Person p = new Person(); //p라는 Person 타입의 객체 선언
		p.name = "홍길동";
		p.isHungry = true;
		p.eat();

		System.out.println(p.name+" : "+p.isHungry+" : "+p.age);

		Person p2 = new Person();
		p2.name = "장길산";
		System.out.println(p.name+" : "+p.isHungry+" : "+p.age);

객체 생성과 메모리 구조

JVM의 메모리 구조는 복잡하다. 해당 목차에서는 최대한 간략한 부분만을 서술해 보겠다.

    1. class area
      클래스 원형을 로딩 (현실에서의 설계도.. 붕어빵을 만드는 것을 예로 들면 원판)
      Field, Method, 타입 정보
      상수 풀
    1. method stack
      메서드들의 실행 공간
      thread 별로 별도 관리된다.
      스택 구조, 메서드 호출 순서대로 쌓이는 구조
      메서드 프레임에 로컬 변수도 쌓이는 구조(지역변수)
    1. heap
      객체를 저장하기 위한 영역
      thread에 의해 공유
      생성된 객체는 프로그래머가 삭제할 수 없고 가비지 컬렉터(GC)만이 제어 가능하다.
		Person p = new Person(); //p라는 Person 타입의 객체 선언
		p.name = "홍길동";
		p.isHungry = true;
		p.eat();
		
		System.out.println(p.name+" : "+p.isHungry+" : "+p.age);
		
		Person p2 = new Person();
		p2.name = "장길산";
		System.out.println(p.name+" : "+p.isHungry+" : "+p.age);

2. 변수

변수의 정의

인스턴스 멤버 변수

객체 개개인을 위한 변수 - name, age.. 등 표준 타입에 각자의 내용이 필요할 경우를 위한 변수 선언
인스턴스 멤버 변수는 객체 생성 전에는 메모리에 존재하지 않는다.

클래스 멤버 변수

Static Keyword : '정적' 함수 선언. 고정된 것이 아님!! 거의 안 바뀐다. ex)국적
개별 객체와는 달리 모든 객체가 공유된다는 점이 중요하다.

인스턴스 변수가 각각 가지는 독립적인 내용 (이름, 나이 등등은 사람마다 다르니 인스턴스..)이라면 클래스 멤버 변수는 Static의 개념을 가진 공통적인 내용 위주로 선언하는 듯 하다. (성별, 국적 등은 거의 바뀌지 않으니 Static의 개념이 들어갈 수도)

지역 변수 & 파라미터 변수

선언 위치 : 클래스 영역 {} 이외의 모든 중괄호 안에 선언되는 변수들
명시적 초기화 필요, 외부 접근 불가

여기서부터 Offline 수업

3. 메서드

메서드 정의

메서드는 반복되는 코드의 중복 사용을 방지하고 유지보수가 용이하게 한다.

Variable arguments

메서드 선언 시 몇 개의 인자가 들어올 지 모를 경우(혹은 가변적) 사용한다.
python의 args*와 유사하며 ...을 사용해 호출 시 넘긴 값에 따라 자동으로 생성 후 초기화한다.

	VariableTest vt = new VariableTest();
    vt.variableArgs(1,2,3);
    vt.variableArgs(1,2);
    vt.variableArgs(1,2,3,4,5);
    
    ...
    
    public void variableArgs(int ... params){ //params 이름의 배열형이 생성된다.
    	int sum = 0;
        for(반복){
        	sum += i;
        }
        return sum;
    }

printf 등도 ...을 사용해 다양한 가변 값을 받는 것이다.

메소드 오버로딩 (중요)

포크.. 젓가락.. 숟가락 등 모든 것을 메소드로 만든다고 가정해보자.

이렇게 하면 관리할 요소가 늘어난다.
음식 종류마다 무엇을 먹을 지 도구를 전부 추가할 것인가? 사라지면 쓸데없는 메소드가 된다.

이러한 도구의 기능 자체는 비슷하다. 'eat'에 중점을 두니 도구 정보를 eat 안쪽에 매개변수로 지정하자. 이를 오버로딩이라고 한다.

자주 사용하는 println 역시도 오버로딩의 좋은 예 중 하나이다.

다형성 성립 : 메소드 이름은 같지만 매개변수의 타입을 다르게 받거나 개수가 다르면 오버로딩의 특징을 가진다.

/*
 * 오버로딩 vs 오버라이딩 (재정의 - 상속관계)
 * 
 * 객체지향의 특징 : 다형성
 * 
 * 이름은 동일한데 하는 일이 다르다.
 * 
 * 필수 조건
 * - 메소드 이름이 같다.
 * - 매개변수의 개수 또는 타입이 달라야 한다.
 *
 * - 반환 타입은 상관없다
 */

public class Exam01 {
	
	static void printArray(int[] arr) { //오버로딩 1
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
	}
	static void printArray(int[] arr, int begin) { //오버로딩 2
		for (int i = begin; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
	}
	static void printArray(int[] arr, int begin, int end) { //오버로딩 2
		for (int i = begin; i < end; i++) {
			System.out.println(arr[i]);
		}
	}
	
	public static void main(String[] args) {
		//동일 메소드를 입력값만 다르게 해서 다른 동작을 하게 한다 = 다형성
		printArray(new int[] {1, 10, 100});
		printArray(new int[] {1, 10, 100}, 1);
		printArray(new int[] {1, 10, 100}, 1, 2);
	}
}

하지만 이 스크립트의 일정 부분은 분명 반복이 가능해 보인다.
printArray 메소드의 입력값을 받는 부분만 다르고 내용은 똑같지 않은가?

	static void printArray(int[] arr) { //오버로딩 1
		printArray(arr, 0, arr.length); //함수의 인자 값만 넘겨 준다.
	}
	static void printArray(int[] arr, int begin) { //오버로딩 2
		printArray(arr, begin, arr.length); //함수의 인자 값만 넘겨 준다.
	}
	static void printArray(int[] arr, int begin, int end) { //오버로딩 2
		for (int i = begin; i < end; i++) {
			System.out.println(arr[i]);
		}
	}
	
	public static void main(String[] args) {
		//동일 메소드를 입력값만 다르게 해서 다른 동작을 하게 한다 = 다형성
		printArray(new int[] {1, 10, 100});
		printArray(new int[] {1, 10, 100}, 1);
		printArray(new int[] {1, 10, 100}, 1, 2);
	}
}

다음과 같이 인자 값을 넘겨줌으로써 반복 구문을 상당 수 제거할 수 있다.

4. 생성자

생성자 정의와 예시

/*
 *  생성자
 *  
 *  - 생성자가 호출되는 시점은?? 객체가 생성될 때
 *  - 일반 메소드(인스턴스 메소드)와의 차이점
 *    : 일반 메소드는 객체가 생성된 이후에 호출된다. 
 *      생성자 메소드는 객체 생성 시 한번만 호출이 가능하다. = 호출 후 메모리에 올라간다.
 *  
 *  	타입 a = new 타입(); << 타입()를 생성자라고 한다.
 *  
 *  - 생성자의 특징
 *  1. 이름이 클래스와 동일하다.
 *  2. return 타입이 없다. void와 다르다. void는 반환 값이 없다는 뜻. 생성자는 반환 자체를 하지 않는다.
 *  
 */
	public static void main(String[] args) {
		Dog d1 = new Dog(); //생성자 메소드가 호출되고, Dog 클래스에 해당 생성자를 찾으러 간다.
		//메소드는 소문자로 호출되고, 대문자는 생성자를 뜻한다. 자바 변수 선언 문법을 준수하자.
	}

}
  • Dog 클래스
public class Dog { 
	Dog() { //Dog() 생성자
		System.out.println("Dog()생성자 호출");
	}
	void Dog() {
		System.out.println("만약  void 처럼 반환 타입을 주면 호출되지 않는다. 생성자로 보지 않기 때문.");
	}
}

기본(default) 생성자

다만 매번 클래스 선언 시 아무 역할이 없는 생성자를 선언하는 것은 귀찮은 일이다.
이를 해결하기 위해 자바 컴파일러가 생성자를 임의로 정의해준다. 이를 기본 생성자라고 한다.

/*
 *  생성자
 *  
 *  - 생성자가 호출되는 시점은?? 객체가 생성될 때
 *  - 일반 메소드(인스턴스 메소드)와의 차이점
 *    : 일반 메소드는 객체가 생성된 이후에 호출된다. 
 *      생성자 메소드는 객체 생성 시 한번만 호출이 가능하다.
 *  
 *  	타입 a = new 타입(); << 타입()를 생성자라고 한다.
 *  
 *  - 생성자의 특징
 *  1. 이름이 클래스와 동일하다.
 *  2. return 타입이 없다. void와 다르다. void는 반환 값이 없다는 뜻. 생성자는 반환 자체를 하지 않는다.
 *  3. 디폴트 생성자
 *  	형태 : 클래스접근제한자 클래스명() - 매개변수가 없는 형태
 *  	조건 : 클래스에 정의된 생성자가 존재하지 않을 때
 *  
 */


public class Dog { 
	Dog(int age) { //Dog() 생성자
		System.out.println("Dog()생성자 호출");
	}
}

- 만약 다음과 같이 int형을 받는 생성자를 선언하면 다음과 같이 오류가 뜬다.
기본 생성자가 생성되지 않았기 때문이다.
  • 오버로딩 지원 특징
	public static void main(String[] args) {
		Dog d1 = new Dog("멍멍이"); 
		Dog d2 = new Dog("댕댕이", 3); 
	}
public class Dog {
	Dog(String name) { //Dog() 생성자
		System.out.println("이름 : "+name);
	}


	Dog(String name, int age) { // Dog() 생성자
		System.out.println("이름/나이 : "+name+"/"+"age");
	}
}

생성자에서 받는 변수를 로컬로 선언하면 그 생성자에서만 쓸 수 있다. 이것은 클래스 내부 메소드 등의 실행에서 불편함을 초래한다.
따라서 클래스 내부 전체에서 쓸 수 있는 변수를 선언한다.

  • 로컬 변수 변환
public class Dog {
	String name;
	int age;
	
	Dog(String n) {
		name = n;
		System.out.println("이름 : "+name);
	}

--> 하지만 해당 방식은 권장되지 않는다. 사용자쪽에서 변수 해석에 어려움이 있기 때문.
따라서 this를 사용한다.

this.

public class Dog {
	String name;
	int age;
	
	Dog(String name) {
		name = name; //앞 name은 전역변수 위쪽, 뒤는 매개변수를 의미
		System.out.println("이름 : "+name);
	}
}
	   
    

this 사용

public class Dog {
	String name;
	int age;
	Dog() {
		this.name = "무명";
		this.age = -1;
	}
	
	Dog(String name) {
		this.name = name; //this 사용
		this.age = -1;
	}
	
	Dog(String name, int age) {
		this.name = name;
		this.age = age;
	}
}

중복 코드 age가 보이지 않는가? 코드 중복 문제를 해결하기 위해 다음과 같이 할 수 있다.

public class Dog {
	String name;
	int age;
	Dog() {
		this("무명",-1);
	}
	
	Dog(String name) {
		this(name,-1);
	}
	
	Dog(String name, int age) {
		this.name = name;
		this.age = age;
	}
}

클래스 로딩 : class Area : 공유 영역(static) 등

Dog와 같은 클래스는 처음 동작할 때 class Area에 올라간다.
static에서는 this 접근을 할 수 없다. static = class Area에 먼저 올라가므로 객체 선언을 언제 할 지 모르기 때문. (정확하지 않음)


this도 다음과 같은 예제와 같이 제약 사항이 있다.

this의 제약 사항

 *     this의 사용상 제약
 *     - this.변수 또는 메소드의 호출은 static 영역에서는 사용이 불가능하다.
 *     - this() 생성자 호출은 생성자 안에서만 호출이 가능하다. 
 *     - 생성자 안에서 첫번째 구문으로 호출되어야 한다.
 
 

static block 예제

/*
 	static 블럭
 	- 클래스 정보가 로딩될 때 '한 번만' 실행
 	- 형태 : static {}
 */

package day2;

class StaticSub {
	static int v = 100;
	StaticSub() {
		System.out.println("1. StaticSub 생성자 호출");
	}
	
	static {
		System.out.println("2. 클래스 정보가 로딩될 때 실행 - 생성하려면 먼저 class Area에 정보가 실리기 때문");
	}
}

public class Exam03 {

	public static void main(String[] args) {
		System.out.println("3. main start");
		StaticSub ss = new StaticSub();
		StaticSub ss2 = new StaticSub();
		
		System.out.println("4. ss 객체 생성 후 메인 메서드");
	}

}

/*
 	static 블럭
 	- 클래스 정보가 로딩될 때 '한 번만' 실행
 	- 형태 : static {}
 */

package day2;

class StaticSub {
	static int v = 100;
	StaticSub() {
		System.out.println("1. StaticSub 생성자 호출");
	}
	
	static {
		System.out.println("2. 클래스 정보가 로딩될 때 실행 - 생성하려면 먼저 class Area에 정보가 실리기 때문");
	}
}

public class Exam03 {

	public static void main(String[] args) {
		System.out.println("3. main start");
		System.out.println(StaticSub.v);
		/*
		StaticSub ss = new StaticSub();
		StaticSub ss2 = new StaticSub();
		*/
		
		System.out.println("4. ss 객체 생성 후 메인 메서드");
	}

}

5. 실습 - 게시판 만들기

이해 안 됐던 거(주소 참조)

		if (list.length == pos) {
			
			list = Arrays.copyOf(list, pos * 2);
			
			//Board[] temp = new Board[pos * 2];
			//System.arraycopy(list, 0, temp, 0, pos); //아래 for 대신 해당 메소드 사용할 수도 있음.
			/*
			for (int i = 0; i < pos; i++) {
				temp[i] = list[i];
			}		
			list = temp; //리스트의 주소값 = temp 주소값
			== 넓혀진 temp의 배열 안에 list 인덱스 요소(주소값)을 저장하고, list 자체의 주소값을 temp의 주소값으로 변경
			== (구)list 배열은 쓰지 않고 GC에 의해 사라지게 됨.
			*/
		}

참조형의 특성을 다시 한 번 확인해 봐야 할 것 같다.

BoardUI.java

package board.step01;

import java.util.Arrays;
import java.util.Scanner;

class BoardUI {
	Scanner sc = new Scanner(System.in);
	Board[] list = new Board[2]; //객체 저장용
	int pos = 0;
	
	void start() {
		System.out.println("게시판 관리 프로그램 V1 ");
		while (true) {
			switch (menu()) {
			case 1:
				list();
				break;
			case 2:
				write();
				break;
			case 3:
				quit();
				return;
			}
		}
	}

	private void quit() {
		System.out.println("종료 메뉴 선택");
	}

	private void write() {
		// 배열의 크기가 꽉 찬 상태라면 배열의 크기를 늘려주자.
		// 배열 크기 늘림 = 새로운 배열 생성 후 기존 배열 내부의 주소 복사
		if (list.length == pos) {
			
			list = Arrays.copyOf(list, pos * 2);
			
			//Board[] temp = new Board[pos * 2];
			//System.arraycopy(list, 0, temp, 0, pos); //아래 for 대신 해당 메소드 사용할 수도 있음.
			/*
			for (int i = 0; i < pos; i++) {
				temp[i] = list[i];
			}		
			list = temp; //리스트의 주소값 = temp 주소값
			== 넓혀진 temp의 배열 안에 list 인덱스 요소(주소값)을 저장하고, list 자체의 주소값을 temp의 주소값으로 변경
			== (구)list 배열은 쓰지 않고 GC에 의해 사라지게 됨.
			*/
		}
		
		System.out.print("글쓴이 : ");
		String writer = sc.nextLine();
		System.out.print("제목 : ");
		String title = sc.nextLine();
		System.out.print("내용 : ");
		String content = sc.nextLine();
		
		Board b = new Board(writer, title, content);
		b.no = Sequence.nextVal(); //글번호 갱신
		list[pos++] = b;
		System.out.println("등록되었습니다.");
	}

	private void list() {
		System.out.println("------------------");
		System.out.println("글번호\t제목\t글쓴이");
		System.out.println("------------------");
		if(pos == 0) {
			System.out.println("등록된 게시글이 존재하지 않습니다.");
		}
		for (int i = pos - 1; i >= 0; --i) {
			Board b = list[i];
			System.out.println(b.no + "\t" + b.title + "\t" + b.writer);
		}
		System.out.println("------------------");
	}

	private int menu() {
		System.out.println("------------------");
		System.out.println("1. 목록                        ");
		System.out.println("2. 등록                        ");
		System.out.println("3. 종료                        ");
		System.out.println("------------------");
		System.out.print("메뉴(번호) :       ");
		return Integer.parseInt(sc.nextLine());
	}
}

Board.java

package board.step01;

class Board {
	int no;
	String writer;
	String content;
	String title;
	
	public Board() {	
	}
	public Board(String writer, String content, String title) {
		this.writer = writer;
		this.content = content;
		this.title = title;
	}
}

Sequence.java

package board.step01;

class Sequence {
	private static int no = 1;
	
	static int nextVal() {
		return no++;
	}
	
	/*nextVal은 인스턴스 변수라 이러면 객체를 만들어야 하는데, 변수 하나를 위해 이렇게 생성하는 것은 비효율적이다.
	이런 메소드는 클래스 메소드로 빼도 된다.
	int nextVal() {
		return no++;
	}
	*/
}
profile
안녕하세요.

0개의 댓글