[즐거운자바] 객체지향(1) - 클래스와 인스턴스 + 메소드
국비지원에서 6개월간 개발공부를 했을 때, Java에 대해 기본적인 지식은 쌓을 수 있었다.
하지만 너무 기본만 배워서 진짜 '객체지향'문법이 어떤것인지 감이 오지 않았다.
그래서 객체지향에 대해 더 공부해보기 위해, 인프런의 무료 강의인 즐거운자바 를 듣게 되었다.
처음 Java를 공부할 때, 가장 이해가 어렵고, 와닿지 않았던 객체지향에 대해 중점적으로 스스로 공부해보면서 이해를 돕기 위한 설명을 같이 작성해보겠다.
<클래스>
클래스란, 설계 도면이다. 설계 도면은 이 객체가 어떤 정보를 가졌는지(필드), 어떤 기능이 있는지(메소드)에 대해 설명한다.
즉, 클래스는 필드(field)와 메소드(method)를 가지는 객체 설명서와 같다.
클래스는 대문자로 시작한다.
<클래스 예시>
'책(Book)' 클래스를 설계해보겠다.
책의 정보(필드)로는 제목(title), 가격(price)이 있고, 책의 기능(메소드)은 자동으로 읽어주는 기능(autoRead)이 있다.
그렇다면 클래스 설계도는 다음과 같이 그려주면 된다.
<클래스 작성 방식>
접근제한자 class 클래스이름 { 필드; 메소드; 생성자; }
public class Book {
// 필드(속성)
private String title;
private int price;
// 메서드(동작)
public void autoRead() {
...
}
// 생성자
public Book(String title, int price) {
this.title = title;
this.price = price;
}
// Getter 메서드로 필드에 접근
public String getTitle() {
return title;
}
public int getPrice() {
return price;
}
}
이 클래스를 실제로 코드에서 사용하기 위해서는, 객체 지향 프로그래밍의 원칙을 따르기 위해 인스턴스*(or 오브젝트)를 생성해야 한다.
(*인스턴스란, 실제 객체를 지칭한다고 생각하면 이해하기 편한 것 같다.)
인스턴스를 생성해야하는 이유는 아래에서 추가 설명하겠다.
<인스턴스를 만드는 방법 3가지>
1. new 연산자와 생성자 이용
2. 클래스 로더를 이용
3. 메모리에 있는 인스턴스를 복제(clone)
<인스턴스 생성 방식>
클래스명 변수명 = new 클래스명();
| | | \
참조타입 참조변수 new연산자 생성자
* New 연산자를 사용할 때마다 메모리에 인스턴스가 생성된다.
<인스턴스 예시>
아래와 같이 Book 인스턴스를 생성한다.
Book b = new Book();
내부 동작:
1. Book이라는 인스턴스를 Heap 메모리에 만든다.
2. b라는 레퍼런스 변수를 메모리에 참조한다.
3. Book이 갖고 있는 필드, 메소드 사용 가능! -> b.autoRead(); 와 같이 클래스이름.메소드이름() 으로 사용
<특징>
- static 필드(접근제한자 뒤에 static이 붙은 필드)는 클래스가 로딩될 때, 딱 한번 메모리에 올라가고 초기화된다.
- 인스턴스 메소드(접근제한자 뒤에 static이 붙지 않은 메소드)는 인스턴스를 생성하고 나서 레퍼런스 변수를 이용해야 사용할 수 있다.
- 인스턴스는 더이상 참조되는 것이 없을 때, 나중에(보통 메모리가 부족할때) 가비지 컬렉션(Garbage Collection)된다.
- 메소드 안에 선언된 변수들은 메소드가 실행될 때 메모리에 생성되었다가, 메소드가 종료될 때 사라진다.
*****<인스턴스 사용 이유>
1. 추상화: 클래스는 객체의 추상화를 제공하고, 인스턴스는 이러한 추상화의 실체. 클래스는 객체가 가져야 하는 속성과 동작을 정의하고, 인스턴스는 실제 데이터를 포함하며 클래스에서 정의한 동작을 수행.
클래스? Book 클래스 - field: title, price / method: autoRead 가 있다!
인스턴스? 어떤 Book이 있는지 데이터를 포함한다!
Book book1 = new Book("홍길동전", 1000);
Book book2 = new Book("Java 공부", 1200);
2. 데이터 캡슐화(정보 은닉): 클래스는 데이터와 관련된 메서드(동작)를 하나의 단위로 캡슐화한다. 인스턴스는 이 캡슐화된 데이터와 메서드에 접근할 때 사용된다. 클래스의 내부 데이터에 직접 접근하는 것을 제한하고, 대신 클래스의 메서드를 통해 데이터에 접근하도록 하는 것을 의미. 이로써 데이터의 보안성을 높이고 오류 방지 가능.
보안을 위해 Getter와 Setter를 이용해 직접 필드에 접근하지 못하도록 한다.
public class Book {
...
// Getter 메서드로 필드에 접근해 직접 Field에 접근하지 못하도록 함.
public String getTitle() {
return title;
}
public int getPrice() {
return price;
}
...
}
3. 재사용성: 클래스를 정의하면 동일한 클래스를 기반으로 여러 인스턴스를 생성할 수 있으며, 이를 통해 코드의 재사용성이 증가. 상속과 다형성을 통해 코드 재사용을 촉진.
클래스: 템플릿.
인스턴스: 템플릿에 맞는 데이터를 넣어 객체 생성.
동일한 패턴을 가진 객체를 반복해서 생성 가능!
4. 모듈화: 각 클래스는 독립적으로 작동할 수 있으며, 이러한 모듈화는 코드의 관리와 유지보수를 용이하게 한다.
큰 프로젝트를 작은 단위로 분해하고, 각 단위를 독립적으로 작업하며 다시 통합하여 전체 시스템을 개발.
5. 다형성: 동일한 인터페이스를 구현하는 다양한 클래스의 인스턴스를 사용하여 코드의 유연성을 높일 수 있다. 오버라이딩(Overriding)과 인터페이스 구현을 통해 구현된다.
하나의 기능을 다양하게 사용할 수 있다.
아래의 예시 참조.
아래는 도형의 넓이를 계산하는 코드이다.
1. interface를 통해 calculateArea()를 선언해주고,
2. 각 도형 클래스는 calculateArea()를 각각 구현하여 해당 도형의 넓이를 계산한다.
3. 다형성을 이용해 각 도형의 객체를 생성하고, 동일한 메서드를 호출하면, 각 도형별로 넓이를 계산할 수 있다.
interface Shape {
double calculateArea();
}
class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
class Rectangle implements Shape {
private double length;
private double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
public double calculateArea() {
return length * width;
}
}
// 다형성을 이용한 도형 객체 사용
Shape shape1 = new Circle(5.0);
Shape shape2 = new Rectangle(4.0, 6.0);
double area1 = shape1.calculateArea(); // 원의 넓이 계산
double area2 = shape2.calculateArea(); // 직사각형의 넓이 계산
<메소드 선언 방법>
[접근제한자] [static] 리턴type 메소드이름([매개변수, ....]) {
실행문
}
- static 메소드는 인스턴스를 생성하지 않아도 사용할 수 있다.
내부 동작:
1. 메소드 안에 선언된 변수(지역 변수)는 메소드가 종료되면 메모리에서 사라진다.
2. 메소드별로 지역변수 영역은 각각 다른 메모리 영역을 갖고 잇다.