반응형
05-16 01:08
Today
Total
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
관리 메뉴

개발하는 고라니

[Java] 클래스와 객체 본문

Languages/Java

[Java] 클래스와 객체

조용한고라니 2021. 3. 1. 16:03
반응형

자바는 대표적인 객체지향 언어 중 하나이다. 그러나 '객체'가 무엇인지도 모르고 쓰는 경우가 있다. 우선 객체란 무엇일까? 사실 객체(Object)는 어려운 개념이 아니다. 우리 주변에 있는 모든 것이 객체이다. TV, PC, 노트북, 스마트폰, 사람, 의자 등등... 실세계는 객체들의 집합이다. 실세계의 객체들은 자신만의 고유한 특성(State)과 행동(Behavior)을 가지며 다른 객체들에게 행동을 요구하거나 정보를 주고받는 등 상호 작용을 하면서 살아간다. 컴퓨터 프로그램의 예를 들면, 테트리스 게임에 나오는 각 블록들, 한글 프로그램의 메뉴나 버튼들이다.

Java의 객체 지향 특성

객체지향 언어는 실세계의 객체를 프로그램 내에 표현하기 위해 클래스(Class)와 객체(Object) 개념을 도입하였다. 객체지향 언어는 다음과 같은 특성을 가진다.

 

● 캡슐화 (Encapsulation)

 

캡슐화란 객체를 캡슐로 싸서 내부를 보호하고 볼 수 없게 하는 것으로 객체의 가장 본질적인 특징이다. 캡슐로부터 보호받기 때문에 외부의 접근으로부터 안전하다. 객체는 캡슐화가 기본 원칙이지만, 외부와 상호작용을 위해 몇 부분만 공개 노출한다. (예: TV의 On/Off, 음량 조절 등)

 

Java에서 클래스는 객체의 모양을 선언한 '틀'이며 클래스 모양 그대로 생성된 '실체(Instance)'객체이다. Java는 필드(Field)와 메서드(Method)를 클래스 내에 모두 구현하므로 캡슐화를 통해 객체 내 필드에 대한 외부로부터의 접근을 제한한다.

 

● 상속 (Inheritance)

 

상속은 상위 개체의 속성이 하위 개체에 물려져 하위 개체가 상위 개체의 속성을 모두 가지는 관계이다. 상속을 이용하면 기존에 정의되어 있는 클래스의 모든 필드와 메소드를 물려받아 새로운 클래스를 생성할 수 있다.

 

기존에 정의되어있던 클래스부모 클래스(Parent Class) 또는 상위 클래스(Super Class), 기초 클래스(Base Class)라고도 한다.

상속을 통해 새롭게 작성되는 클래스자식 클래스(Child Class) 또는 하위 클래스(Sub Class), 파생 클래스(Derived Class)라고도 한다.

 

# 장점

 

  • 기존에 작성된 클래스를 재활용
  • 자식 클래스 설계 시 중복되는 멤버를 미리 부모 클래스에 작성해놓으면, 자식 클래스에서는 해당 멤버를 작성하지 않아도 됨
  • 클래스 간 계층적 관계를 구성함으로써 다형성의 문법적 토대 마련

출처 http://www.tcpschool.com/java/java_inheritance_concept

● 다형성 (Polymorphism)

 

다형성이란 하나의 객체가 여러 가지 타입을 가질 수 있는 것을 의미한다. 자바에서는 이러한 다형성을 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조할 수 있도록 하여 구현하고 있다.

List<String> list = new ArrayList<>();

또는 같은 이름의 메소드가 클래스 혹은 객체에 따라 다르게 구현되는 것을 말하기도 한다. 부모 클래스에 지정된 메서드를 자식 클래스가 상속을 받으면, 부모가 갖는 메서드를 그대로 사용할 수도 있지만, 메서드 오버라이딩(Method Overriding)을 하여 자식 클래스에서 자신의 특징에 맞게 동일한 이름으로 다시 구현할 수 있다.

메서드 오버로딩(Method Overloading)은 클래스 내에 같은 이름의 메서드를 여러개 만드는 것이다.

//메서드 오버로딩
void register(int a) {
...
}

void register(int a, String b) {
...
}

void register(int a, String b, Infomation c){
...
}

//메서드 오버라이딩
Class Parent{
  int a;
  public void speak(String a){

      System.out.println(a);
  }
}

Class Child extends Parent{
	
    @Overriding
    public void speak(String a){
    	
        String b = "Hello World!";
        
        System.out.println(a + b);
    }
}

절차 지향 / 객체 지향 

C처럼 실행하고자 하는 절차를 정하고, 이 절차대로 프로그래밍 하는 방법을 절차 지향 프로그래밍이라고 부른다. 절차 지향 프로그래밍은 목적을 달성하기 위한 일의 흐름에 중점을 둔다. 자판기 소프트웨어를 구현하는 경우를 보면 절차 지향 프로그래밍은 흐름도를 설계하고 흐름도상의 동작들을 함수로 작성하며, 흐름도에 따라 일련의 동작들이 순서대로 실행되도록 작성한다.

그러나 컴퓨터를 이용하여 문제를 해결하려는 실제 세상은 단순히 일련의 행위뿐 아니라 각 물체 간의 관계, 상호 작용 등 복잡하게 구성되어있다. 절차지향 언어로 실세계의 문제를 프로그래밍 하기에는 표현에 한계가 있다.

 

객체 지향 프로그래밍은 이러한 단점을 보완하고 실세계의 물체를 객체를 표현한다.

클래스와 객체

C 에서 구조체를 정의하기 위해 struct를 사용했다면, 자바에서는 'class'를 사용한다.

클래스는 다음과 같이 구성된다.

  • (접근 지정자) 클래스
    • (접근 지정자) 멤버 변수 = 필드
    • (접근 지정자) 멤버 함수 = 메서드

● 접근 지정자

 

자바에서 메서드는 반드시 접근 지정자와 함께 선언되어야 한다. 접근 지정자는 public/private/default/protected가 있다.

 public > protected > default > private 

  클래스 내부 동일 패키지 하위 클래스 그 외의 영역
public O O O O
protected O O O X
default O O X X
private O X X X

 

● 객체 생성

 

객체 생성은 객체에 대한 레퍼런스 변수 선언객체 생성의 두 과정으로 구분된다.

 

# 레퍼런스 변수 선언

객체를 생성하기 전 객체를 가리킬 레퍼런스 변수의 선언이 필요하다.

Class Student{
    int kor;
    int eng;
    
    public static void main(String[] args){
    
    	Student student; //레퍼런스 변수 student 선언
    }
}

이 선언만으로는 Student 객체가 생성되지 않는다. 변수 student는 Student 타입의 객체를 가리키는 레퍼런스 변수일 뿐 객체 자체는 아니다.

 

# new 연산자로 객체 생성

student = new Student();

new 연산자는 Student 타입의 크기만한 메모리를 할당받아 메모리에 대한 레퍼런스 (주소)를 반환한다. 레퍼런스 값은 student에 대입된다. 

 

# 객체 멤버 접근

//객체레퍼런스.멤버
int kor = student.kor;
int eng = student.eng;

생성자

이번 절에서는 중요한 것을 알아보고자 한다. 생성자는 정말 기본적이지만 매우 중요하기 때문이다.

 

앞에서 클래스는 객체를 생성하기 위한 틀이며, 객체는 틀로 찍어낸 실체라고 하였는데, 생성자객체가 생성될 때 초기화를 위해 실행되는 메서드이다. 즉, 인스턴스 변수의 초기화를 위한 메서드라고 할 수 있다. 클래스를 가지고 객체를 생성하면 해당 객체는 메모리에 즉시 생성되지만 생성된 객체는 모든 인스턴스 변수가 아직 초기화되지 않은 상태이다.

 

클래스 변수와 인스턴스 변수는 별도로 초기화하지 않으면, 아래와 같은 값으로 초기화된다.

변수의 타입 초기값
char '\u0000'
byte / short / int 0
long 0L
float 0.0f
double 0.0
boolean false
배열 / 인스턴스 등 null

 

생성자의 특징

 

  • 생성자의 이름은 클래스 이름과 동일하다
  • 생성자를 여러 개 작성할 수 있다 (오버로딩이 가능하다)
  • 생성자는 객체를 생성할 때 한 번만 호출된다
  • 생성자에 리턴 타입을 지정할 수 없다 (반환 값이 없으나 void도 불가)
  • 생성자는 초기화를 위한 데이터를 인자로 받을 수 있다.

 

 기본 생성자 (Default Constructor)

 

기본 생성자란 매개 변수가 없고 또한 실행 코드가 없어 아무일도 하지 않고 단순 리턴하는 생성자이다.

기본 생성자는 만약 클래스에 아무런 생성자가 정의되어있지 않다면 컴파일러가 자동으로 생성해준다.

하지만 다른 생성자가 있다면 기본 생성자는 자동으로 생기지 않으므로 반드시 명시해주어야 한다.

Class Student{
    int kor;
    int eng;
    
    public Student(){ //default 생성자
    
    }
}

 

● this 레퍼런스 / this()

 

this는 객체 자신에 대한 레퍼런스(주소)로서 메서드 안에서 사용된다. this는 컴파일러에 의해 자동으로 관리되므로, 개발자는 사용하기만 하면 된다.

public Student(){

    kor = 10;
    eng = 20;
}

public Student(int kor, int eng){

    this.kor = kor;
    this.eng = eng;
}

//this()를 사용한 생성자
public Student(int kor, int eng, int math){
    this(kor, eng);
    this.math = math;
}    

위의 예제처럼 생성자의 매개변수 이름과 인스턴스 변수의 이름이 같을 경우 인스턴스 변수 앞에 'this'를 붙여 구분해야한다. 이러한 this 참조 변수를 사용할 수 있는 영역은 인스턴스 메서드뿐이며, 클래스 메서드에서는 사용할 수 없다. 모든 인스턴스 메서드에는 this 참조 변수가 숨겨진 지역 변수로 존재하고 있다.

 

this() 메서드는 생성자 내부에서만 사용할 수 있으며, 같은 클래스의 다른 생성자를 호출할 때 사용한다.

 

● 객체 치환 시 주의 사항

 

객체의 치환은 객체를 복사하는 것이 아니다. 다음의 예제를 보자.

public class CallbyReference {

	static class Student{
		int kor, eng, math;
		
		public Student(int kor, int eng, int math) {
			
			this.kor = kor;
			this.eng = eng;
			this.math = math;
		}

		@Override
		public String toString() {
			return "Student [kor=" + kor + ", eng=" + eng + ", math=" + math + "]";
		}
		
	}
	
	
	public static void main(String[] args) {
		
		Student s1 = new Student(10, 10, 10);
		Student s2 = new Student(20, 20, 20);
		
		System.out.println("s1 : " + s1.toString());
		System.out.println("s2 : " + s2.toString());
		
		System.out.println("객체 치환 s1 -> s2");
		
		s1 = s2;
		
		System.out.println("s1 : " + s1.toString());
		System.out.println("s2 : " + s2.toString());
	}
}
//s1 : Student [kor=10, eng=10, math=10]
//s2 : Student [kor=20, eng=20, math=20]
//객체 치환 s1 -> s2
//s1 : Student [kor=20, eng=20, math=20]
//s2 : Student [kor=20, eng=20, math=20]

 

s1 = s2;

이 코드가 실행됨에 따라 원래 s1이 가리키던 객체는 아무도 가리키지 않으므로 더 이상 프로그램에서 접근할 수 없는 상태가 되었다. 따라서 이 객체는 가비지 (Garbage)가 되어 가비지 컬렉터에 의해 자동으로 수거된다.

 

즉, s1과 s2 모두 기존 s2가 가리키던 메모리 주소를 가리키고 있는 것이다.

메서드 (Method)

메서드에서는 개인적으로 상당히 중요한 개념에 대하여 다루고자 한다. 앞에서 언급한 메서드 오버로딩에 대해 다루고, 메서드의 인자를 전달할 때의 방식을 알아보자. 인자를 전달하는 방식은 아래와 같이 2가지가 있다.

 

> 값에 의한 호출 (Call by Value)

> 참조에 의한 호출 (Call by Reference)

 

자바의 메서드 호출 시 인자 전달 방식(argument passing)은 '값에 의한 호출'이다. 호출하는 실인자의 값이 복사되어 메서드의 매개 변수에 전달된다.

 

● 기본 타입 (Primitive Type)의 값이 전달되는 경우 : Call by Value

 

매개 변수가 byte, char, int ,double 등 기본 타입으로 선언되는 경우, 호출자(Caller)가 건네는 값이 메서드의 매개 변수에 복사되어 전달된다.

public class CallbyValue {

	static void increase(int val) {
		val += 10;
		
		System.out.println("val : " + val);
	}
	
	public static void main(String[] args) {
		
		int k = 10;
		
		increase(10);
		
		System.out.print("k : " + k);
	}
}
//val : 20
//k : 10

 

● 객체가 전달되는 경우 : Call by Reference

 

메서드의 매개 변수가 클래스 타입인 경우, 객체가 아니라 객체의 레퍼런스 (주소)값이 전달된다. Class / Interface / Array 3 가지 경우에는 참조에 의한 호출이 이루어진다.

public class CallbyReference {

	static class Student{
		int kor, eng, math;
		
		public Student(int kor, int eng, int math) {
			
			this.kor = kor;
			this.eng = eng;
			this.math = math;
		}

		@Override
		public String toString() {
			return "Student [kor=" + kor + ", eng=" + eng + ", math=" + math + "]";
		}
		
	}
	
	static void increase(Student s) {
		
		s.eng += 10;
		s.math += 20;
	}
	
	public static void main(String[] args) {
		
		Student student = new Student(10, 10, 10);
		
		System.out.println("increase 호출 전");
		System.out.println(student.toString());
		
		increase(student);
		System.out.println("increase 호출 후");
		System.out.println(student.toString());
	}
}
//increase 호출 전
//Student [kor=10, eng=10, math=10]
//increase 호출 후
//Student [kor=10, eng=20, math=30]

 

● 메서드 오버로딩 (Method Overloading)

 

클래스 내에 이름이 같지만 매개 변수의 타입이나 개수가 서로 다른 여러 개의 메서드를 작성할 수 있다. 이는 다형성의 한 종류이다.

  • 메서드 이름이 동일하여야 한다
  • 메서드 매개 변수의 개수나 타입이 서로 달라야 한다
  • 접근 지정자나 리턴 타입은 메서드 오버로딩에 해당되지 않는다

static 그리고 final

● static vs non-static

 

  non-static static
선언 class Sample{
    int n;
    void g() {...}
}
class Sample{
    static int n;
    static void g() {...}
}
공간적 특성 - 객체마다 별도 존재
- 인스턴스 멤버
- 클래스당 하나 생성
- 객체 내부가 아닌 별도의 공간에 생성
- 클래스 멤버
시간적 특성 - 객체 생성시 생김
- 객체가 생길 때 멤버도 생김
- 객체 생성 후 멤버 사용 가능
- 객체가 사라지면 멤버도 사라짐
- 클래스 로딩 시 멤버 생성
- 객체가 생기기 전 이미 생성
- 객체가 생기기 전 사용 가능
- 객체가 사라져도 멤버는 존재
- 멤버는 프로그램이 종료 시 사라짐
공유의 특성 - 공유 X
- 멤버는 객체 내 각각 공간 유지
- 동일한 클래스의 모든 객체들에 의해 공유 O
- 클래스 이름으로 접근 가능
- 객체의 멤버로 접근 가능

# static의 활용

  • 전역 변수 / 전역 함수
  • 공유 멤버를 만들고자 할 때

# static 메서드의 제약

  • static 메서드는 반드시 static 변수만 사용 (생성되는 시기가 다르므로)
    • 반대로 non-static 메서드는 static 변수도 사용 가능
  • static 메서드에서는 this를 사용할 수 없다
    • static 메서드는 객체 없이도 존재하기 때문 / this는 자기자신 객체를 지정하므로

 

● final

 

1) 클래스

클래스 앞에 final이 붙으면 상속받을 수 없다.

 

2) 메서드

메서드 앞에 final이 붙으면 더 이상 오버라이딩 할 수 없다.

 

3) 필드

필드 앞에 final이 붙으면 더 이상 수정할 수 없다. 즉 상수가 된다.

static final int PI = 3.14; //상수는 대문자로 표기하는 것이 관례

객체지향 프로그래밍

프로그램이란?

- 프로그램은 코딩(절차를 다루는 부분)과 절차를 나누고 정리하는 부분으로 구성되어 있다고 볼 수 있다

 

프로그램은 구조적인 방법과 객체지향적인 방법이 있다.

구조적인 방법과 객체지향 방법은 절차를 나누고 정리하는 방법에서 어떤 차이가 있을까?

 

절차를 나누는 방법(함수를 나누는 기준)에서의 차이

  • 구조적인 방법
    • 코드의 크기나 중첩 및 복잡도가 높아지는 부분에서 코드를 잘라 함수로 만든다.
  • 객체지향 방법
    • 객체가 주체가 되어 객체가 서비스하는 단위를 함수로 만든다.

(함수를) 정리하는 방법에서의 차이

  • 구조적인 방법
    • 데이터구조가 사용되면 함수가 외부 코드로부터 영향을 받는 이상현상이 발생하게된다. 따라서 데이터구조를 사용하는 함수들을 하나로 묶어야 하는 필요성이 발생한다.
  • 객체지향 방법
    • 객체라는 실세계에 반영함으로써 캡슐화된 함수를 그 객체의 행위로 보는 방식으로 정리한다. 



 # References 

TCPschool's Java

명품 자바 에센셜.황기태.생능 출판

 

반응형

'Languages > Java' 카테고리의 다른 글

[Java] Shuffle 메서드  (0) 2021.03.10
[Java] 상속  (0) 2021.03.08
[Java] 우선순위 큐(Priority Queue)  (0) 2021.01.28
[Java] 람다식  (0) 2021.01.26
[Java] StringTokenizer  (0) 2021.01.23
Comments