- 객체 지향 언어
- 기본 자료형을 제외한 모든 요소들이 객체로 표현된다.
- 객체 지향 특징인 캡슐화, 상속, 추상화, 다형성을 갖는다.
- 장점
- JVM에서 동작하기 때문에 운영체제에 독립적이다.
- GabageCollector를 통한 자동적인 메모리 관리가 가능하다.
- 단점
- JVM에서 동작하기 때문에 실행 속도가 상대적으로 느리다.
- 다중 상속이나 타입에 엄격하며, 제약이 많다.
- 객체 지향 프로그래밍
- 특징 : 캡슐화, 상속, 추상화, 다형성
- 캡슐화 : 데이터와 코드의 형태를 외부로부터 알 수 없게 하고, 데이터의 구조와 역할, 기능을 하나의 캡슐 형태로 만드는 방법
- 상속 : 부모 클래스에 정의된 변수 및 메서드를 자식 클래스에서 상속받아 사용하는 것
- 추상화 : 클래스들의 공통적인 특성(변수, 메소드)들을 묶어 표현하는 것. (추상클래스, 인터페이스)
- 다형성 : 다양한 형태로 표현이 가능한 구조를 말한다.
- JVM은 스택 기반으로 동작하며, Java Byte Code를 OS에 맞게 해석 해주는 역할을 하고 가비지컬렉션을 통해 자동적인 메모리 관리를 해줍니다.
- .java 파일로 개발
- build 시 java compiler가 javac 명령어를 통해 .class(바이트코드)를 생성
- Class Loader를 통해 JVM 메모리 내 로드
- 실행엔진이 바이트코드들을 인터프리터 방식 혹은 jit컴파일러 방식으로 실행
Primitive Type Wrapper Class byte Byte short Short int Integer long Long float Float double Double boolean Boolean char Character
- Wrapper Class는 Primitive Type을 객체화한 것
int primitive = 10; Integer wrapper = 10; wrapper = null; // null 값이 들어갈 수 있습니다. String str = wrapper.toString(); // 메서드를 사용할 수 있습니다. ArrayList<Integer> integerList = new ArrayList(); // 제네릭에 사용할 수 있습니다.int와 Integer를 예로 든다면 int의 경우 단순하게 숫자로서만 사용할 수 있지만 Integer의 경우 객체 형태로 생성되어 null 값이 들어갈 수 있고,. toString과 같은 메서드를 사용할 수 있으며, 제네릭 Integer의 형태로도 사용할 수 있게 됩니다.
- primitive type 은 stack에 적재되며, 비교 시 ==, null 값을 가질 수 없음.
- wrapper class type 은 heap에 적재되며, 비교 시 equals, null 값을 가질 수 있음
- Boxing : primitive -> wrapper class
- UnBoxing : wrapper class -> primitive
- Overloading : 매개변수의 개수나 타입을 다르게 하여 같은 이름의 메소드를 여러 개 정의
- Overriding : 상위 클래스에 있는 메소드를 하위 클래스에서 재정의 하는 것
- try-with-resources는 try-catch-finally의 문제점을 보완하기 위해 나온 개념입니다.
- try( ... ) 안에 자원 객체를 전달하면, try블록이 끝나고 자동으로 자원 해제 해주는 기능을 말합니다.
- 따로 finally 구문이나 모든 catch 구문에 종료 처리를 하지 않아도 되는 장점이 있습니다.
참조 타입일 경우 추가적인 작업
- 불변 객체는 객체 생성 이후 내부의 상태가 변하지 않는 객체를 말합니다.
- Java에서는 필드가 원시 타입인 경우 final 키워드를 사용해 불변 객체를 만들 수 있고,
- 참조 타입일 경우엔 추가적인 작업이 필요합니다.
불변 객체나 final을 굳이 사용해야 하는 이유
- 참조 변수가 일반 객체인 경우 객체를 사용하는 필드의 참조 변수도 불변 객체로 변경해야 합니다.
- 배열일 경우 배열을 받아 copy해서 저장하고, getter를 clone으로 반환하도록 하면 됩니다.
(배열을 그대로 참조하거나, 반환할 경우 외부에서 내부 값을 변경할 수 있음. 때문에 clone을 반환해 외부에서 값 변경하지 못하게 함)- 리스트인 경우에도 배열과 마찬가지로 생성시 새로운 List를 만들어 값을 복사하도록 해야 합니다.
배열과 리스트는 내부를 복사하여 전달하는데, 이를 방어적 복사(defensive-copy)라고 합니다.
- Thread-Safe하여 병렬 프로그래밍에 유용하며, 동기화를 고려하지 않아도 된다.
(공유 자원이 불변이기 때문에 항상 동일한 값을 반환하기 때문)- 실패 원자적인 메소드를 만들 수 있다.
(어떠한 예외가 발생되더라도 메소드 호출 전의 상태를 유지할 수 있어 예외 발생 전과 똑같은 상태로 다음 로직 처리 가능)- 부수효과를 피해 오류를 최소화 할 수 있다.
※ 부수효과 : 변수의 값이 바뀌거나 객체의 필드 값을 설정하거나 예외나 오류가 발생하여 실행이 중단되는 현상- 메소드 호출 시 파라미터 값이 변하지 않는다는 것을 보장할 수 있다.
- 가비지 컬렉션 성능을 높일 수 있다.
(가비지 컬렉터가 스캔하는 객체의 수가 줄기 때문에 Gc 수행 시 지연시간도 줄어든다.)
- 싱글톤 패턴은 단 하나의 인스턴스를 생성해 사용하는 디자인 패턴입니다.
- 인스턴스가 1개만 존재해야 한다는 것을 보장하고 싶은 경우와
- 동일한 인스턴스를 자주 생성해야 하는 경우에 주로 사용합니다. (메모리 낭비 방지)
- 싱글톤 패턴의 대표적인 예시는 Spring Bean
- 스프링은 요청할 때마다 새로운 객체를 생성해서 반환하는 기능도 제공한다.
(프로토타입 빈, @Scope("prototype"))
- 가비지 컬렉션은 JVM의 메모리 관리 기법 중 하나로 시스템에서 동적으로 할당됐던 메모리 영역 중에서 필요없어진 메모리 영역을 회수하여 메모리를 관리해주는 기법입니다.
- GC의 작업을 수행하기 위해 JVM이 어플리케이션의 실행을 잠시 멈추고, GC를 실행하는 쓰레드를 제외한 모든 쓰레드들의 작업을 중단 후 (Stop The World 과정) 사용하지 않는 메모리를 제거(Mark and Sweep 과정)하고 작업이 재개됩니다.
- GC의 작업은 Young 영역에 대한 Minor GC와 Old 영역에 대한 Major GC로 구분됩니다.
- SRP - 단일 책임 원칙 : 한 클래스는 하나의 책임만 가져야 한다.
- OCP - 개방-폐쇄 원칙 : 확장에는 열려있고, 수정에는 닫혀있어야 한다.
- LSP - 리스코프 치환 원칙 : 하위 타입은 항상 상위 타입을 대체 할 수 있어야 한다.
- ISP - 인터페이스 분리 원칙 : 인터페이스 내에 메소드는 최소한 일수록 좋다. (하나의 일반적인 인터페이스보다 여러 개의 구체적인 인터페이스가 낫다.) SRP와 같은 문제에 대한 두 가지 다른 해결책이다.
- DIP - 의존관계 역전 원칙 : 구체적인 클래스보다 상위 클래스, 인터페이스, 추상클래스와 같이 변하지 않을 가능성이 높은 클래스와 관계를 맺어라. DIP 원칙을 따르는 가장 인기 있는 방법은 의존성 주입(DI)이다.
- 자바의 메모리 공간은 크게 Method 영역, Stack 영역, Heap 영역으로 구분되고, 데이터 타입에 따라 할당됩니다.
- 메소드(Method) 영역 : 전역변수와 static변수를 저장하며, Method영역은 프로그램의 시작부터 종료까지 메모리에 남아있다. JVM이 동작해서 클래스가 로딩될 때 생성
- 스택(Stack) 영역 : 지역변수와 매개변수 데이터 값이 저장되는 공간이며, 메소드가 호출될 때 메모리에 할당되고 종료되면 메모리가 해제된다. LIFO(Last In First Out) 구조를 갖고 변수에 새로운 데이터가 할당되면 이전 데이터는 지워진다. 메소드가 호출될 때 할당
- 힙(Heap) 영역 : new 키워드로 생성되는 객체(인스턴스), 배열 등이 Heap 영역에 저장되며, 가비지 컬렉션에 의해 메모리가 관리되어 진다. Runtime에서 할당
- 여러 개의 쓰레드가 한 개의 자원을 사용하고자 할 때, 현재 데이터를 사용하고 있는 쓰레드를 제외하고 나머지 쓰레드들은 데이터에 접근할 수 없게 막는 개념입니다.
- 데이터의 thread-safe를 하기 위해 자바에서 Synchronized 키워드를 제공해 멀티 쓰레드 환경에서 쓰레드간 동기화를 시켜 데이터의 thread-safe를 보장합니다.
- Synchronized는 변수와 메소드에 사용해서 동기화 할 수 있으며, Synchronized 키워드를 남발하게 되면 오히려 프로그램의 성능저하를 일으킬 수 있습니다.
- String은 불변의 속성을 가지며, StringBuffer와 StringBuilder는 가변의 속성을 가집니다.
- StringBuffer는 동기화를 지원하여 멀티 쓰레드 환경에서 주로 사용하며,
- StringBuilder는 동기화를 지원하지 않아 싱글 쓰레드 환경에서 주로 사용합니다.
- 변수 또는 메소드의 접근 범위를 설정해주기 위해서 사용하는 Java의 예약어를 의미하며, 총 4 가지 종류가 있습니다.
- public - 접근 제한이 없다. (같은 프로젝트 내 어디서든 사용 가능)
- protected - 해당 패키지 내, 다른 패키지에서 상속받아 자손 클래스에서 접근 가능하다.
- (default) - 해당 패키지 내에서만 접근 가능
- private - 해당 클래스에서만 접근 가능
- static 키워드를 사용한 변수나 메소드는 클래스가 메모리에 올라갈 때 자동으로 생성되며 클래스 로딩이 끝나면 바로 사용할 수 있습니다. 즉, 인스턴스(객체) 생성 없이 바로 사용 가능합니다.
- 모든 객체가 메모리를 공유한다는 특징이 있고, GC 관리 영역 밖에 있기 때문에 프로그램이 종료될 때까지 메모리에 값이 유지된 채로 존재하게 됩니다.
- 리플렉션이란 구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API 입니다.
- 다수의 데이터를 쉽고 효과적으로 관리할 수 있는 표준화된 방법을 제공하는 클래스의 집합을 의미합니다.
- 자바 컬렉션에는 List, Set, Map 인터페이스를 기준으로 여러 구현체가 존재하고, 이에 더해 Stack, Queue 인터페이스도 존재합니다.
- hashCode() 메소드를 오버라이딩하여 리턴된 해시코드 값이 같은지를 보고 해시코드 값이 다르다면 다른 객체로 판단하고,해시코드 값이 같으면 equals() 메소드를 오버라이딩하여 다시 비교합니다. 이 두 개가 모두 맞으면 중복 객체입니다.
- 제네릭은 데이터의 타입을 하나로 지정하지 않고 사용할 때마다 범용적이고 포괄적으로 지정한다는 의미입니다.
- 제네릭 타입을 사용함으로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있어 에러를 사전에 방지할 수 있습니다.
SerialVersionUID를 선언해야 하는 이유
- 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 시스템에서도 사용할 수 있도록 바이트(byte) 형태로 데이터 변환하는 기술이며, 반대로 직렬화된 바이트 형태의 데이터를 다시 객체로 변환하는 과정을 '역직렬화'라고 합니다.
- (간단히) JVM의 메모리에 상주(힙 or 스택)되어 있는 객체 데이터를 바이트 형태로 변환하는 기술
- JVM은 직렬화와 역직렬화를 하는 시점의 클래스에 대한 버전 번호를 부여하는데, 만약 그 시점에 클래스의 정의가 바뀌어 있다면 새로운 버전 번호를 할당하게 됩니다. 그래서 직렬화할 때의 버전 번호와 역직렬화를 할 때의 버전 번호가 다르면 역직렬화가 불가능하게 될 수 있기 때문에 이런 문제를 해결하기 위해 SerialVersionUID를 사용합니다.
- 만약 직렬화할 때 사용한 SerialVersionUID의 값과 역직렬화 하기 위해 사용했던 SVUID가 다르다면 InvalidClassException이 발생할 수 있다.
[reference]
https://dev-coco.tistory.com/153
https://wildeveloperetrain.tistory.com/12
https://dev-coco.tistory.com/32