자바에서 클래스란 객체를 정의하는 기본적인 요소로, 생성자, 객체의 필드(변수)와 메소드가 포함되어있다. '추상화'라는 개념을 구현한 것으로 볼 수 있다.
해당 클래스를 바탕으로 생성자를 통해 만들어지는 것 하나하나가 다 객체이다. ArrayList나 LinkedList같은 자료구조도 자바에서는 클래스로 구현되어 있으며, 각 자료구조도 하나의 객체로 볼 수 있다.
각 객체는 생성자를 통해서 생성된다고 하였다. 그렇다면 생성자는 무엇인가?
public class Book {
String title;
String author;
int year;
int isbn;
int price;
public Book() {}
}
책이라는 실제 존재하는 대상을 Book이라는 클래스로 추상화하였다. 책이 가진 특성은 많겠지만 나는 제목, 작가, 발행년도, isbn, 가격 이정도를 책이 가진 특성이라고 규정하여 클래스를 만들었다. 그리고 이 객체를 생성하기 위해서는 pulic Book() {}과 같은 형태의 생성자를 이용해야 하며, 생성자는 객체를 초기화하기 위해 사용한다.
- 생성자의 이름은 클래스 이름과 동일해야 함
- 생성자는 객체가 생성될 때 자동으로 한 번 호출되며 리턴 타입이 없음
- 생성자는 각 클래스에 1개는 꼭 있어야 하며, 여러개의 생성자를 만들 수 있다.
기본 생성자는 위와 같이 매개 변수가 없는 생성자다. 클래스에 규정하지 않으면 컴파일러가 자동으로 생성해준다. 그러나 생성자 오버로딩을 통해 매개변수가 있는 생성자를 만들 경우, 기본 생성자는 자동 생성되지 않으므로 필요한 경우 직접 정의하여야 한다. 컴파일러가 만들어주는 자동 기본 생성자의 경우 각 변수 데이터 타입의 기본값(ex.int의 경우 0 등)으로 초기화 된다.
위에서 만들어놓은 클래스로 Book 객체를 만든다고 가정하자. 내가 생성자를 따로 정의하지 않으면 대충 ["title = null", "author = null", "year = 0", "isbn = 0", "price = 0"] 과 같은 느낌의 객체가 생성될 것이다.
그치만 내가 필요한 책은 이런게 아니다!
public Book(String title, String author, int year, int isbn, int price) {
this.title = title;
this.author = author;
this.year = year;
this.isbn = isbn;
this.price = price;
}
나의 책은 이렇게 초기화 될 것이다. 그리고 이렇게 만들어진 클래스와 생성자를 통해 다른 곳에서 이 Book 객체가 필요할 때 생성해서 사용할 것이다. 어떻게?
오버로딩: 같은 이름의 메소드를 각각 다른 매개변수를 통해 다른 동작을 하도록 하는 것
오버라이딩: 부모 클래스에서 상속받은 메소드를 자식 클래스에서 재정의 하는 것
바로 new 연산자를 통해서
public class Paju {
public static void main(String[] args) {
Book goodBook = new Book("좋은책", "아무개", 2023, 1234, 10000);
}
}
이제 goodBook 객체가 만들어졌고, 이 객체는 heap 영역에 저장되었을 것이다.
각 클래스는 title, author 등과 같은 변수를 가질 수 있고, 해당 객체의 제목이 무엇인지, 작가가 무엇인지 찾는 멤버 함수도 가질 수 있다. 그런데 객체 지향의 특징 중 하나인 캡슐화 즉, 정보의 은닉에 대해서 생각해보자. 이 Book이라는 클래스가 가진 정보를 외부에서 보지 못하게 하려면 어떻게 해야할까? 그때 필요한 것이 바로 접근제어자이다.
해당 클래스에서만 접근이 가능해당 패키지 내에서만 접근이 가능동일 패키지의 클래스 또는 해당 클래스를 상속받은 다른 패키지의 클래스에서만 접근 가능어떤 클래스에서라도 접근이 가능private -> default -> protected -> public 순으로 보다 많은 접근을 허용
객체의 기본 설계도. 추상 메소드로만 이루어져 실제 구현부 코드가 없으며, 해당 인터페이스를 상속한 구현 클래스에서 이를 구현한다. 따라서 인터페이스는 인스턴스화 할 수 없다. 인터페이스에 정의된 메소드들은 구현 클래스에서 전부! 오버라이딩 해야 한다.
Java8에서는 인터페이스 메소드 추가 시 해당 인터페이스를 상속 받은 모든 클래스에서 해당 메소드를 구현하여야 하여 static method 또는 default method를 통해 직접 구현이 가능하다.
객체의 미완성 설계도. 추상 클래스는 abstract 키워드를 붙여 표현하며, 추상 메소드가 없어도 추상 클래스로 지정할 수 있다. 추상 클래스는 인터페이스와 같이 new를 통해 인스턴스화 할 수 없다. 또한, 추상 메소드의 경우 인터페이스와 같이 구현부를 작성할 수 없으나 추상 메소드가 아닌 일반 메소드는 구현 할 수 있다. 추상 메소드는 당연히 자식 클래스에서 반드시 재정의 해야 한다.
클래스 안에 선언된 클래스로, 중첩 클래스 또는 인스턴스 클래스라고 부르기도 한다.
static으로 선언된 내부 클래스로, 정적 중첩 클래스 또는 스태틱 클래스라고 부르기도 한다. static 메소드와 마찬가지로 클래스명으로 호출할 수 있다.
메소드 안에서 지역변수처럼 클래스를 선언하여 사용할 수 있으며, 지역 중첩 클래스 또는 지역 클래스라고 부른다. 해당 클래스는 메소드 안에서만 객체 생성 및 메소드 호출이 가능하다.
익명 중첩 클래스라고 부르기도 한다. 추상 클래스를 상속받는 클래스를 굳이 만들어낼 필요가 없는 경우 사용한다. 추상 클래스를 상속받는 클래스가 해당 클래스 안에서만 사용되고 다른 클래스에서는 전혀 사용할 필요가 없을 경우 추상 클래스 그대로 선언 후 {}; scope 안에 메소드를 오버라이딩하여 사용한다.
클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한 번만 사용될 수 있고 오직 하나의 객체만을 사용할 수 있는 일회용 클래스이다.
불변 객체와 관련된 내용은 이번 자바를 정리하며 처음으로 '개념'적으로 접근해 볼 수 있었다. final 키워드와 String 참조값등 여러군데서 흩어진 정보들을 가지고는 있었지만 이들을 한 데 모아 '불변 객체'라는 개념으로 정리해보지는 못했던 것이다. 그리고 이번 기회를 통해 이를 공부할 수 있었는데 꽤나 내용이 방대하여 공부하며 정리가 잘 되었던 포스팅 두가지를 적어놓으려 한다.
결국은 자바의 기본과 다 맞닿아 있는 얘기들이었다. 이 중 String constant pool은 처음 접하는 개념이었는데 참 재밌는 내용이었다. literal String과 new String의 차이도 처음 알게 되었다.
원시타입은 또한 Wapper 클래스에 의해 Boxing, Unboxing의 개념을 갖으며 참조형 변수로 사용이 가능하다.
쉽게 말해 static이 붙은 변수다. 모든 객체 전체가 공유하는 변수로 한 클래스의 객체를 여러개 만들어도 딱 1개의 변수만 생성된다. 클래스가 메모리에 올라갈 때 Static 영역에 저장되며 프로그램 종료 시까지 살아있다.
static method의 경우 객체를 생성하지 않고도 클래스 이름으로 사용이 가능하다.
클래스 내부, 모든 메소드 및 블록 외부에서 선언된 static이 붙지 않은 변수다. 인스턴스가 생성될 때 변수의 타입에 따라 해당 메모리 영역에 적재되고, GC에 의해 수거된다.
메소드, 생성자, 초기화 블럭 안에서 선언된 변수들이다. 변수 선언문이 수행되었을 때 메모리에 적재되며 해당 메소드 종료 시 혹은 블럭 종료 시 바로 메모리가 해제된다.