본문 바로가기

Language/Java

JAVA - BASIC

 

1. JAVA 기본 개념

JDK(Java kit) = Library + JRE(Java Runtime Environment, 자바 실행 환경)

JAVA 5.0 : Generics, Annotations, Enumerations 추가

JAVA 8.0 : Lamda, Stream, Optional 추가

LTS : 8년동안 업데이트, 버그 수정 해주는 버전

 

 

특징

- 객체 지향 언어 (현실세계의 객체를 그대로 가져와 프로그램 작성) <-> 절차 지향(절차를 따져 프로그램 작성)

- 멀티 스레드 지원

- OS 플랫폼 독립성 (JVM을 통해 운영체제에 맞는 바이너리 코드(네이티브 코드)로 변경)

- 가비지 컬렉터 지원 (사용 안되는 인스턴스 청소해 메모리 정리)

- 강한 타입의 언어이기에 데이터 타입을 명확히하여 메모리 사용량을 고려해야 함.

<-> 느슨한 타입의 언어(python) 등은 고려사항이 적음

 

 

JVM의 동작 (컴파일, 인터프리터 방식 모두 채택)

- Editor 소스 코드 작성

- 컴파일러로 클래스 파일(바이트 코드, 전체를 번역) 생성

- Class Loader (다른 클래스 사용을 로딩해 클래스 파일에 사용, 합쳐주는 과정)

- 검증기

- 인터프리터 (바이너리 코드로 변경, 한줄씩 번역)

-> 속도가 늦어질 가능성이 존재

 

 

자바의 메모리 구조

- static 영역

  - 코드 segment : 코드들을 바이트 코드로 저장한 장소

  - 데이터 segment : static 키워드가 붙은 데이터들

- stack 영역 : 지역변수 및 매개 변수, 클래스의 인스턴스 참조변수가 할당되는 영역, 자동 초기화가 진행되지 않는다.

- heap 영역 : 만드는 모든 객체 인스턴스(참조 변수)는 힙에 할당, 자동 초기화가 진행된다.

 

기본 설명

- JVM이 관리한다

- stack, heap은 데이터 segment 영역임

- 자바에서 지역 변수 : 메서드 내의 변수

- 자바에서 매개 변수 : 메서드의 인자

 

심화 설명

스택 : 

- 스택은 메서드의 호출과 관련된 지역변수, 매개변수 및 임시 데이터를 저장하는 데 사용된다.

- 스택은 각 스레드마다 개별적으로 유지된다.

- 메서드 호출시마다 작은 메모리 블록이 스택에 할당된다.

- 메서드 종료시 이 블록이 소멸된다. 즉, 변수들도 소멸된다.

- 후입 선출 구조를 갖는다. 최근에 들어온 변수부터 실행한다는 뜻이다.

힙 : 

- 힙은 JVM의 가비지 컬렉터에 의해 관리된다.

- 가바지 컬렉터는 힙의 인스턴스와 연결된 참조변수가 스택에 없는 인스턴스를 기준으로 소멸 기준을 잡고 나중에 소멸시킨다.

- 힙에 할당된 객체는 명시적으로 해제하지 않거나 가비지 컬렉터가 수거해가지 않는 이상 계속 남아있다.

정적 영역 : 

- 자바의 바이트 코드가 저장된다.

- Static으로 설정한 정적 메서드, 정적 변수(모든 인스턴스들이 공유하는 변수), 정적 상수(static + final)이 저장된다. 

- Static 영역은 프로그램 시작에 메모리에 할당되고 종료시까지 계속 사용가능.

 

동작 예시

Member mem = new Member("name");

Stack : mem 변수 (Heap의 인스턴스를 참조)

Heap : Member("name") 인스턴스

-> 이 후, mem 변수가 사용이 되지 않는다면 자동 삭제, Heap에 존재하는 인스턴스는 가바지 컬렉터나 사용자가 정리

 

 

 

2. 클래스

  • 필드
  • 메서드 
  • 생성자
  • static, final
  • 접근 지정자
  • 열거형

 

 

 

2-1. 필드

 

필드의 구성

- 필드 명

- 필드 타입 (Primitive Type(기본), References Type(참조))

- 접근 지정자 (public, private, protected, default)

- final (더 이상 변경하지 않는다.)

- static 

 

 

필드 변수의 유형

- 지역 변수

- 매개 변수

- 인스턴스 변수 (클래스에 선언되어 있는 기본 변수(필드))

- 정적 변수 (클래스 내에 선언되어 static을 사용한 정적으로 생성해 놓은 변수)

 

 

필드 타입 : Primitive Type

- 정수형 : int(4바이트, 2의 31승까지 표현), short(2바이트, 2의 15승), long(8바이트, 2의 64승), byte(1바이트, 2의 7승)

- 실수형 : float(4바이트, 소수점 이하 7자리의 정밀도), double(8바이트, 15자리까지 정밀도)

-> 기본적으로 실수형은 double을 표현하므로 float사용시 float n = 1.234f 와 같이 f를 붙여야 함.

-> 정밀도가 예를 들어, 7자리이면 7자리까지는 오차를 없앤다.

- 문자형 : char

- 논리형 : boolean

 

활용

- 4바이트 = 8비트*4 = 32비트 = 2의 31승까지 양, 음 표현 가능

- int : 20억정도 까지 표현

- long : 20억 넘어가면 long으로 표현

 

 

필드 타입 : Reference Type

- 특정 객체의 참조 정보를 저장

- 기본 데이터 타입 이외의 타입을 의미

- 클래스의 인스턴스

- 4바이트

 

"인스턴스 변수는 4바이트가 스택에 설정되고 실제 인스턴스에 들어가는 데이터는 데이터 타입에 맞게 힙에 쌓인다."

 

 

상수

- 리터럴 상수

- 사용자 정의 상수 : 변수 + final (더 이상 변경하지 않는다, 자주 쓰이는 곳에 사용)

- 연산 : L-value의 타입에 R-value의 타입을 맞출 것.

- 형변환 : 암묵적 형변환(연산시 바이트가 큰 타입으로 자동 변환), 명시적 형변환(사용자가 (int) 등을 작성해서 변환)

-> long + float는 float로 변환되는 것 제외하고 규칙 지켜짐.

 

 

2-2. 메서드

 

메서드의 구성

- 파라미터

- 메서드 명

- 반환 타입

- 접근 지정자

- final

- static

 

 

메서드 오버로딩

- 메서드의 이름이 같지만 파리미터의 수, 순서를 달리하거나 파라미터의 타입을 달리하여 여러개의 같은 이름의 메서드를 만드는 것

- 메서드의 시그니처 : 메서드의 이름부터 파라미터를 의미.

 

 

메서드 바인딩

- 메서드 호출시 메서드가 정의되어 있는 코드를 수행하는 연결방식

- 정적 바인딩, 동적 바인딩 존재, 자바에서는 기본적으로는 동적 바인딩을 채택

- 정적 바인딩 : 프로그램 시작전 메서드의 실행 부분이 메서드가 정의된 위치와 이미 연결되어 있는 것

- 동적 바인딩 : 프로그래 동작 중에 메서드가 메서드가 정의된 위치와 연결되는 것

 

 

 

2-3. 생성자

 

생성자의 종류

- 생성자는 결국 필드를 초기화 하는 과정

- Default 생성자 : 인자 안넣는 기본 생성자, 작성안할시 자동 추가, 따라서 클래스는 하나 이상 생성자를 갖는다.

- 사용자 정의 생성자 : 인자 넣는 경우

(생성자 오버로딩 : 생성자는 항상 클래스의 이름으로 정해야하며 이 때, 여러 가지 생성자를 만드는 경우)

 

 

생성자 : this

- this.name = name; : 생성자내에서 이름 설정

- this(name); : 다른 생성자 중 이름으로만 이루어진 다른 생성자를 해당 생성자 내에서 호출 

-> 위와같은 this 생성자는 항상 메서드의 첫줄에 작성, 아닐시 오류 발생

 

 

 

2-4. Static, Final

 

static, final 공통

- 클래스, 필드, 메서드 모두 적용 가능

- 위치에 따라 기능이 다르다.

 

 

static + final 동시 적용 + field

- 정적 상수라고 불린다.

- 사용자 정의 상수라고도 하며 변경 불가능하고 자바 메모리에 static 영역에 들어가 사용되는 구조

- 프로그램 시작부터 종료까지 사용가능

 

 

static

- 정적 키워드로 정적 메서드, 정적 필드를 선언할 때 사용

- 인스턴스 필드, 인스턴스 메서드와 비교, 인스턴스를 생성하지 않음.

- static이 적용된 필드를 정적 필드 또는 클래스 변수라고 지칭한다.

- 정의되어 있는 클래스명을 활용해 전역 변수, 전역 메서드 처럼 사용 가능하다.

 

 

static 위치에 따른 역할

- static + field : 해당 클래스로 인스턴스화 된 객체들이 모두 공유하는 변수를 의미.

-> 인스턴스화되는 것이 아닌 static 영역에 들어가 객체들이 공유하는 구조임

- static + method : 해당 클래스의 인스턴스 생성없이 호출 가능하게 한다.

-> 사용하는 경우 : "메서드에 필요한 파라미터가 모두 명시적인 경우, 해당 메서드가 클래스의 정적 필드에만 접근하는 경우"

 

 

상세 설명(Static, Instance field <-> Static, Insatance method 관계)

Static Field - Static Method, Instance Method 둘 다 접근 가능, Static은 프로그램 시작시 지정된다고 생각하며 이해.

Instance Field - Static Mehod는 접근 불가능, Method가 이미 프로그램 시작시 정의되어 인스턴스 필드는 접근하지 못한다.

따라서, 특정 클래스에 해당 메서드가 정적 필드에만 접근하는 경우 정적 메서드를 사용하지만 인스턴스 필드에 접근이 필요한 경우 사용하지 못함을 인지해야 한다.

 

 

상세 설명(Static method <-> Insatance method 관계)

- Instance Method 에서는 Static Method를 접근 가능하다.

- Static Method에서는 Instance Method를 접근하지 못한다.

(Static Method가 생성되는 시점에 Instance Method는 존재하지 않기 때문이다.)

 

 

final

- (메서드의) 지역변수, 파라미터에도 적용 가능

- 필드에 정의시 한번의 초기화 후 다른 값 대입이 불가능하게 만드는 키워드

 

final 위치에 따른 역할

- final + class : 해당 클래스를 상속하는 것을 허용하지 않음

- final + 메서드 : 오버라이딩(재정의 및 확장) 금지

- final + 필드, 지역변수, 파라미터 : 한번 값 초기화 후 변경 불가능

-> 파라미터에 붙을시 메서드내에서 값의 변경을 막는다.

 

final 초기화 시점

- 필드 선언시 초기화 : private final String test = "test";

- 초기화 블록 사용 :

{

     test = "test";

}

- 생성자 영역에서 초기화 : this.test = "test";

 

 

 

 

2-5. 접근 지정자

public > protected > package, default > private

 

접근 지정자 private의 사용

- private + field : 기본적으로 클래스의 필드는 private로 설정하는게 기본이다.

- 이는 제약조건 체크를 위해 사용하는 경우가 많은데, 다른 클래스에서 직접적으로 접근하지 못하게 하고 getter/setter를 통해 접근하게 한다. 이 후, getter/setter안에 제약조건을 체크하는 메서드를 두어 체크한다.

- private + 생성자 : 기본적으로는 생성자는 public으로 하지만 사용자가 설정하지 않는 필드를 private 생성자로 설정해두고 다른 생성자로 객체를 생성하는 경우가 있다.

private Member(){
	this.userId = UUID.randomUUID().toString();
}

public Member(String name, String email){
    this();
    this.name = name;
    this.email = email;
}

 

접근 지정자 : protected

- 부모 클래스 + protected : 자식 클래스에서 접근 가능한 필드 또는 메서드, 보통은 메서드를 의미. (상속에서 사용하는 개념)

- 같은 패키지내의 클래스들도 해당 클래스의 필드와 메서드에 접근 가능하다.

 

 

접근 지정자 : default, package

- 같은 패키지 내의 클래스들 사용

 

 

2-6. 열겨형

- 간단하게 정리하면 아래와 같이 사용한다.

public class Worker{
	private Gender gender;


}

public enum Gender{
	Male, Female
}

 

 

 

 

 

 

3. 클래스의 관계

  • 패키지
  • 상속
  • 형변환
  • Object 클래스
  • 추상 클래스
  • 인터페이스

 

 

3-1. 패키지

- 클래스들을 그룹화

- 외부로부터 받는 클래스들과 현재 구현하는 클래스들을 구분

- 클래스 이름에 대한 유일성 보장 : 같은 이름의 클래스가 여러개 있을 때, 패키지를 통해 구분

 

Import

- 외부 패키지의 클래스를 사용할 경우 import하고 사용

- import static : 클래스 이름 빼고 사용 가능.

- import java.lang.*; 은 생략되어 있다.

 

 

3-2. 상속

명칭

- 상속 받는 클래스 : sub, derived, extended, child class

- 상속 제공 클래스 : super, base, parent class

 

기본 개념

- 클래스들의 공톡적인 구성요소를 정의하고 이를 대표하는 클래스를 생성해 다른 클래스에 이를 상속하는 개념

- extends 사용

- 자식 클래스는 부모 클래스의 private을 제외하고는 필드 및 메서드 그대로 사용가능

(super.부모필드명, super.부모메서드명)

 

- 자식 클래스는 부모 클래스의 필드 및 메서드를 확장, 재정의해서 사용

->오버라이딩, @Override 명시, 메서드 명, 파라미터, 반환타입 동일, 메서드의 접근 지정자는 부모 클래스의 접근 지정자와 비교해 같거나 더 넗어야 가능(부모 : public -> 자식 : protected, private 불가능)

-> 자식 클래스의 메서드에 super.method(); 작성시 확장 개념

-> 작성 없을 시는 재정의

 

 

추가적인 개념

- 자식 클래스에서 부모 클래스의 필드를 사용하길 원할시 super() 생성자를 통해 호출해 필드를 받는게 일반적

(이 때, 자식 클래스에 해당 부모 클래스의 필드를 작성할 필요는 없다.)

- 자식 클래스 인스턴스 생성시 부모 클래스의 인스턴스도 생성됨, 부모 클래스의 인스턴스가 먼저 생성됨.

(즉, 상속 관계에서 상위 클래스부터 순서대로 생성된다, 자식 클래스의 생성자에 super()를 작성하여 생성)

- super() 작성 안할시 부모 클래스의 default 생성자를 통해 부모 호출, default 없고 super 없을 시 에러 발생함.

- 자바 메모리 구조 관점에서 보면 부모 클래스 존재시 부모 클래스의 사이즈만큼 힙에 공간을 더 차지 함.

 

 

3-3. 형 변환

- 원시타입들 간 형변환 : (int), (double) 등

 

up-casting - 부모 클래스로 형변화해 참조

- up-casting : 자식 클래스가 부모 클래스의 타입으로 참조되는 것을 허용

-> 메서드 호출시 자식 클래스의 메서드는 호출 불가능하게 됨.

-> 하지만 오러라이딩된 메서드는 호출 가능하며 자식 클래스의 메서드를 호출하게 됨.

-> 만약 오버라이딩안한 메서드 호출 원할시 다운 캐스팅 후 사용

Worker person1 = new Police();)
if(Worker instanceof Police){
    
    Police person2 = (Police) person1;
    person2.catch();
    
    or
    
    ((Police)person1).catch();
}

 

down-casting - 자식 클래스로 다시 형변환

- down-casting : 형변환 필요, 위의 코드 작성하에 사용 가능.

-> 자식 클래스의 메서드 다시 호출 가능

(ex Police person2 = (Police) person1;)

 

 

 

상속의 이점

-> 부모 클래스부터 자식클래스까지 하나의 타입으로 관리 가능하다.

-> 저장시 같은 Collections/배열에 저장 가능하다.

-> 다형성 : 하나의 이름의 메서드로 다양한 메서드 동작 (상속, 재정의 메서드, 형변환(up-casting)이 전제가 되어야 함.)

Worker -> Police, Teacher

// 같은 배열에 담기 가능
Worker[] people = new Worker[2];
people[0] = new Police();
people[1] = new Teacher();

// 같은 메서드로 다른 역할 가능
for(Worker person : people){
	person.doWork();
}

 

 

3-4. Object 클래스

- 모든 자바 클래스가 직접,간접적으로 상속하는 자바내의 최상위 클래스

- 기본적으로 모든 클래스는 상위 클래스의 메서드들을 호출할 수 있다. 따라서, Object 클래스의 메서드를 재정의, 확장 또는 그대로 사용하여 사용 간능하다.

 

 

Object 클래스의 메서드 11가지

  • 1. protected Object clone() 
  • 2. boolean equals(Object object)
  • 3. protected finalize() : Deprecated, 사용 안함.
  • 4. Class<?> getClass() : 힙에 올라와 있는 인스턴스의 클래스를 유추하는 메서드, Reflection
  • 5. int hashCode()
  • 6. void notify() : Thread 관련 메서드
  • 7. void notifyAll() : Thread 관련 메서드
  • 8. String toString() : 해당 클래스의 설명을 문자열 타입으로 반환하는 메서드
  • 9. void wait() : Thread 관련 메서드
  • 10. void wait(long timeout) : Thread 관련 메서드
  • 11. void wait(long timeout, int nanos) : Thread 관련 메서드

 

 

1. toString()

- 해당 클래스의 설명을 문자열 타입으로 반환하는 메서드

String arr = new String("Hello World");

System.out.println(arr); -> arr.toString()이 생략된 구조임.

(기본적으로는 arr의 주소를 출력하게 됨.)

 

 

 

2. == 연산자 vs equals() vs hashCode()

 

  • == 연산자

   - 기본타입(원시타입, int, boolean, double 등)은 값이 같은지를 비교

   - 참조타입(객체의 인스턴스)은 인스턴스가 가리키는 힙의 주소를 비교, 따라서 인스턴스의 모든 필드가 같아도 false

 

 

  • equals() 메서드

   - 참조타입에 대해서 기본적으로는 주소를 비교

   - 하지만 재정의를 통해 값이 같은지 즉, 필드 값들이 같은지를 비교하는 메서드를 구현

   - 예를 들어, String 클래스의 equals 메서드 같은 경우는 값이 같은지를 비교 함.

 

 

  • hashCode() 메서드

   - 기본적으로 인스턴스를 생성시에 자바는 인스턴스의 필드값들과 해시함수를 이용해 해시 값을 만든다.

   - 이 해시값들을 비교해 두 객체가 같은지 비교하는 원리이다.

   - 하지만 hash 함수는 해시 충돌이 발생할 우려가 있기에 해시 값이 같으면 같은 객체라고 판단하기 어렵다.

   - 반대로 equals가 true라면 해시 값은 같음을 보장함.

   - 추가적으로 해시코드를 이용해 검색시 더 빠른 객체 탐색이 가능하다.

 

 

재정의 코드

// Worker Class : name, age

@Override
public boolean equals(Object o){
    // 해당 인스턴스를 입력 인스턴스로 그래도 입력시
    if(this == 0) return true;
    // 입력 인스턴스가 해당 인스턴스의 클래스가 아닐시
    if(!(o instanceof Worker)) return false;
    // down casting 후 필드 값 비교
    Worker person = (Worker) o;
    return Objects.equals(this.name, person.name) && this.age == person.age;
}

public int hashCode(){
	return Objects.hash(name, age);	
}

 

 

 

 

3. clone() 메서드

 

- 특정 인스턴스를 복제하기 위한 메서드

- 기본적으로 깊은 복제 개념임.

- 사용 원할시 해당 클래스에 Cloneable 인터페이스(Marker Interface : 구현된 메서드는 없지만 이를 구현한 클래스의 역할을 명시하기 위한 인터페이스) 구현해야 함. 

 

 

 

얕은 복제(Shallow Copy) vs 깊은 복제(Deep Copy)

 

- 기본적으로 깊은 복제는 복사, 얕은 복제는 참조 연결 개념이다.

- 가정1 : 인스턴스 A와 이를 복제하고자 하는 인스턴스 B가 있다. 이 때, 만약 A의 필드 중 다른 클래스를 참조하는 필드가 있다면 인스턴스 B가 그 필드 또한 참조할지 안할지를 결정, 참조한다면 해당 클래스에 대해서 Shallow Copy, 안한다면 Deep Copy가 된다. 또한, Shallow Copy를 한다면 만약 복제 후 다른 클래스를 참조하는 필드 변경시 인스턴스 B의 해당 인스턴스도 변경 됨.

- 가정2 : 처음 인스턴스는 깊은 복제, 인스턴스 안의 특정 필드에 대해서는 얕은 복제 또는 깊은 복제와 같은 방식으로도 복제가 가능하다 이 때는, 다른 필드 값에 대해서는 변경시 같이 변경 되지 않지만 해당 얕은 복제를 한 필드값이 변경할 때는 같이 변경되게 된다.

- 가정3 : 만약 해당 필드또한 깊은 복제를 원할 시 해당 필드의 클래스에 대한 새로운 인스턴스를 만들고 같은 값을 넣어 다시 set 해준다.

 

 

 

예시

(얕은 복제)

Worker person1 = new Worker("name");

Worker person2 = person1;

-> 이 때, 복제 개념보다는 동일한 힙의 메모리 위치를 참조하는 개념이라 person1 수정시 person2도 같이 수정 됨.

(깊은 복제)

Worker person2 = (Worker) person1.cloe();

-> 아래 코드에서 Object 클래스를 반환하니 다운 캐스팅 해야함.

-> 이 후, person1 변경 시 person2는 변경되지 않음.

 

 

clone() 재정의 코드

public class Worker implements Cloneable{

    @Override
    public Object clone() throws CloneNotSupportedException{
    	return super.clone();
    }
}

 

 

 

 

3-5. 추상 클래스

- 하나 이상의 추상 메서드를 갖는 클래스

- 상속 관계에서는 부모 클래스의 역할을 하는 클래스를 만들기 위해 사용된다.

- 자식에서 반드시 해당 메서드들은 재정의하라는 의미로 사용

- 추상 메서드, 일반 메서드 들로 구성가능하고 반드시, 하나 이상의 추상 메서드를 갖는다.

- 또한 메서드는 구현되어있지않고 정의만 되어있는 원리이며, 자식 클래스에서 재정의하여 사용하는 개념이다.

- new를 통한 인스턴스 생성이 불가능하다.

- 추상 클래스 또는 추상 메서드에는 abstract 키워드가 붙는다.

 

 

가정

- 만약 추상 클래스에 생성자가 존재한다면 해당 추상 클래스이 필드를 자식한테 상속하기 위한 용도이다. 자식의 생성자 호출안에 super()를 통해 해당 추상 클래스를 호출하고 추상 클래스의 필드들을 상속받는 구조이다.

 

 

 

 

3-6. 인터페이스

- 추상 메서드들로만 구성된 하나의 클래스 형태, 필드, static(자바 8 이후), default(자바 8 이후) 메서드는 구현 가능하지만 일반적으로는 추상메서드만 구현, default 메서드는 해당 메서드를 재정의 안해도 되는 경우시 사용, static 메서드는 일반 클래스의 static 메서드와 동일한 역할을 수행한다.

- 추상 클래스와의 차이로 선언시 interface를 사용하며 이를 사용하고자 하는 클래스들은 extends가 아닌 implements를 사용한다.

- 인터페이스의 추상 메서드 앞은 public abstract 가 생략된 구조

- 필드 정의시 public static final 생략 가능

- 상속과 달리 하나의 클래스는 여러개의 인터페이스를 구현가능하다. (상속의 단점을 보완)

(결과적으로 하나의 클래스는 필드 중복 방지를 이유로 하나의 부모 클래스, 추상 클래스 상속과 여러개의 인터페이스 구현이 가능하다.)

- 해당 인터페이스를 구현한 클래스는 무조건적으로 해당 추상메서드들을 재정의해야 함.

 

 

Spring에서의 인터페이스

- 스프링에서는 의존성 주입에서 인터페이스를 많이 사용한다.

- 예를 들어, Controller와 Service의 관계를 보면, Service 인터페이스를 Controller에 주입하고 Service 인터페이스를 구현한 ServiceImpl이라는 클래스를 생성하면 Controller에서 down casting 하여 결과적으로는 ServiceImpl의 메서드를 Controller에서 사용 가능한 구조가 되는 것이다.

- 만약 해당 인터페이스를 여러개의 클래스가 구현하고 있다면 해당 모든 클래스의 해당 메서드가 실행 됨.

- 이는 하나의 클래스 변경시 모든 클래스를 변경해야하므로 의존성이 커지게된다. 따라서 해당 인터페이스를 구현하는 클래스를 하나만 설정해 클래스들 간 느슨한 조합을 갖게하는 역할 또한 인터페이스가 수행 가능하다.

 

 

 

 

 

 

 

4. JAVA API

- 자바에서 제공해주는 주요 클래스 및 인터페이스 정리

 

  • String 클래스
  • Wrapper 클래스
  • 예외처리
  • Java Collection Framework
  • Generic
  • Collection 인터페이스
  • List 인터페이스
  • Set 인터페이스
  • Map 인터페이스
  • Iterator 인터페이스

 

 

4- 1.  String 클래스

- 자바 9부터는 byte[], 8을 포함 이전은 char[] 을 String으로 설정

(char은 2바이트 byte는 1바이트로 메모리 낭비를 줄이기 위함.)

 

 

 

 

Making & Basic Methods

 

Basic

- 기본적으로 리터럴 문자열은 힙에 존재하는 String pool에 데이터가 저장됨.

-> 중복된 문자열 (ex) "answer", "answer" 다른 참조변수에 담아서 저장)저장시 String Pool에 있는 값 가져와 변수가 참조

-> 따라서 == 연산자 실행시 true

-> 하지만 동일한 문자열을 new String("answer") 한다면 다른 String 객체가 힙에 생성되고 다른 문자열이 된다. 따라서 equals 사용

 

 

문자열 더하기

- concat, +연산자 사용시 기존의 객체의 참조를 끊고 새로운 객체를 참조하는 방식이다.

-> 반목문 사용시 memory Leak 발생(가바지 컬렉터가 수거를 안했다는 가정하에 계속 힙에 새로운 객체가 쌓이므로)

-> +연산자는 자바11 이후부터 새로운 객체 생성, 기존에는 StringBuilder와 같이 더해주는 방식

-> StringBuilder 클래스나 StringBuffer 클래스 사용

--> 두 클래스의 모든 메서드는 기능이 동일하나 동기화 특성이 다르다.

--> 가변성을 갖고 있어 새로운 객체를 생성하는 방식이 아닌 기존의 객체에 새로운 값을 추가하는 방식이다.

 

// 생성
// 1. 리터럴
String answer = "answer";
// 2. 객체 생성
String answer = new String("answer");
String answer = new String(charArray);



// 메서드
answer.length(); // 길이
answer = answer.toUpperCase(); // 모두 대문자로 변경
answer = answer.toLowerCase(); // 모두 소문자로 변경
answer.charAt(0); // 첫번째 인덱스의 값



// String Builder 사용
answer = answer.concat("is"); // answer is
answer += "is";
StringBuilder sb = new StringBuilder("answer");
sb.append("is"); // 추가
sb.capacity(); // 용량
sb.toString();

 

 

StringBuilder vs StringBuffer

- 동기화 처리 여부의 차이

- StringBuffer의 Method들은 public sycronized로 설정되어 동기화 처리가 되어있음.

StringBuilder : 비동기화 처리, 단일 스레드 환경에서 사용

StringBuffer : 동기화 처리, 멀티 스레드 환경에서 사용

 

 

Multi Tasking vs Multi Thread

Multi Tasking : CPU가 각각의 Process를 쪼개서 처리하는데 동시 처리처럼 보이는 방식

Multi Thread : Process를 쪼갠 Thread를 각각 처리하는데 동시 처리처럼 보이는 방식 (자바, Spring)

-> Data 손실의 여지가 있다. 동시 접근할 경우가 있기 때문에

-> 동기화 : 멀티 스레드 방식에서 특정 스레드가 데이터 (DB)에 접근할 때, 다른 스레드가 접근하지 못하게 하는 방식

 

 

 

4- 2. Wrapper 클래스

- 원시 데이터를 클래스로 사용할 수 있게 해주는 클래스

- Boxed Class 라고도 지칭한다.

 

  • boolean => Boolean
  • bte => Byte
  • short => Short
  • int => Integer
  • char => Character
  • long => Long
  • float => Float
  • double => Double

 

Auto Boxing and UnBoxing

- 위의 연결된 타입간의 가능

// Auto Boxing
Intger data = 8;
= Integer data = Integer.valeOf(8);

// UnBoxing
int new_data = data;
= int new_data = data.intValue();

// 소수 정확한 연산
BigDecimal.valueOf(3.14).add(BigDecmial.valueOf(1));

 

 

 

4- 3. 예외 처리 (Handling Exception)

- try-catch-finally, throw-throws를 사용.

- catch는 여러개 사용 가능, 파이프로 처리 가능(OR)

- finally는 예외 상관없이 실행할 내용, 예를들어, 파일열고 오류가 발생하더라도 파일은 닫아주는 경우에 사용

- Error 클래스를 상속한 클래스는 자바프로그램 내의 오류가 아닌 외부 오류로, 해결이 불가능

 

checked Exception : 프로그램에서 예외 처리를 강제, 반드시 해야함, 쉽게 생각해서 빨간줄, 컴파일도 안되는 방식

unchecked Exception : 프로그램에서 예외 처리를 강제하지는 않음, 반드시 하지 않아도 됨, 컴파일시나 프로그램 중 발생하는 에러

 

 

계층도, 상속도

Throwable

-> Exception, Error

Exception

-> IOException(Checked Exception), RuntimeException(Unchecked Exception)

RuntimeException

-> NullPointerExcepton 등

 

 

예외 클래스 대표 예시

- ArrayIndexOutOfBoundsException : 배열 접근시 잘못된 인덱스 값을 사용하는 경우

- ClassCastException : 허용할 수 없는 형변환을 한 경우

- NullPointerException : 참조변수가 Null로 초기화 된 경우에 메서드를 호출하는 경우

 

 

사용자 정의 Exception 클래스

- 동일하게 try-catch 또는 thorws-throw 사용

- 기본적으로 사용자 정의 예외 클래스는 extends Exception을 통해 Exception 클래스를 상속받음

- 클래스 안에 생성자를 만들고 생성자 안에는 Exception 클래스를 해당 클래스 넣기 위해 super(메시지)를 하여 넣는다. 이 후, 예외 발생시 throw에 객체를 생성해서 사용하는 방식

- 사용하고자 하는 메서드에서는 throw-throws를 사용

- throws 사용자 정의 Exception명을 메서드 끝에 작성 후 if등을 이용하여 예외발생 상황에 대해서 throw로 해당 사용자 정의 예외클래스를 throw new Exception명 처리

- throw는 JVM에게 예외가 발생했음을 알리는 방식

- throws는 예외의 전파라고도 하며 메서드를 호출한 위치에 발생한 예외상황을 전달하기 위해 사용한다. 이는 예외가 발생한 지점에서 해당 예외를 handling 할 수 있게 예외를 던져주는 개념이다. 따라서, 메서드를 호출한 지점에 try-catch를 사용해 해당 예외에 대한 처리를 한다.

 

 

 

4- 4. Collection Framework

- 객체들을 관리하기 위한 컨테이너 클래스들의 집합을 의미.

- 객체를 저장하면 원시타입은 저장하지 않는다.

- List, Set, Queue, Map 등의 인터페이스들이 있다.

 

- Collection Interface : 순서 없는 객체 집합 

- List Interfacec : 순서 지정이 가능한 객체 집합

- Set Interface : 중복 허용하지 않는 객체 집합

- Queue Inteface : 선입 선출하는 객체 집합

- Map Interface : 키-값 구조를 갖는 객체 집합

 

- Collection의 상위에는 Iterable가 있고 이는 Collection과 Iterator(ListIterator)를 두고 있다.

- 추가적으로 Stack Interface(후입 선출)도 존재

 

컬렉션 선택시 고려사항

- 객체의 추가, 삭제와 같은 데이터 변경 빈번한지

- 빠른 탐색의 필요한지

- 메모리 효율적으로 사용하고자 하는지

 

 

 

 

4- 5. Generic

- 다양한 타입의 객체를 다룰 수 있도록 타입 매개변수(T)를 사용하여 클래스, 인터페이스, 메서드를 정의하는 기능

- 데이터를 저장하는 시점에 어떤 데이터를 저장할 것인지 명시하여 정확한 데이터를 사용하는지를 컴파일시 확인 한다.

- 자바5에서 추가

- 컬렉션 이용시 반드시 사용

 

 

제네릭을 사용해야 하는 예시

- Object[] 배열에는 모든 타입을 섞어서 담을 수 있다 하지만, 꺼낼 때 어떤 타입인지 모르기에 여러가지 문제 발생

- 배열에 삽입시 모든 객체 들은 up-casting 되어 삽입 되는데 이는 부모 클래스인 Object클래스의 메서드만 사용 가능한 상황을 만든다. (오버라이딩 된 메서드 제외)

- 이런 경우 다시 꺼내 down-casting하여 자식 클래스의 메서드를 호출하고자 할 때, 클래스를 명시할 수 없어 down-casting을 하지 못하는 문제가 발생한다. 이를 위해 Generic을 사용한다.

 

T : 클래스

 

 

코드 예시

public class Box<T>{
    private T item;
    
    public Box(T item){
    	this.item = item;
    }
    
    public T getItem(){
    	return item;
    }
}

Box<Something> box = new Box(new Something(10));

 

 

 

 

4- 6. Collection Interface

Collection 인터페이스의 주요 메서드

  • add() : 요소를 추가
  • clear() : 모든 요소 제거
  • contains() : 요소 포함 여부
  • isEmpty() : 컬렉션이 비었는지
  • remove() : 특정 값 또는 인덱스 삭제
  • size() : 컬렉션의 크기
  • iterator() : 반복문에 사용
  • addAll() : 컬렉션을 추가
  • containsAll() : 파라미터로 전달된 컬렉션이 포함하였는지
  • removeAll() : 파라미터로 전달된 컬렉션을 삭제
  • retainAll() : 파라미터로 전달된 컬렉션과 일치하지 않는 요소 제거

 

 

4- 7. List Interface

- 기본적으로 저장요소를 순차적으로 관리

- 중복된 값Null값을 가질 수 있다.

- ArrayList, LikedList, Vector가 존재한다.

- Vector는 동기화 처리가 되어있고 나머지는 안되어있지만 나머지인 ArrayList, LinkedList를 동기화처리해서 사용하는 것이 일반적

 

List Interface Methods

  • add()
  • get()
  • set()
  • listIterator()
  • remove()
  • subList()

 

 

List Interface의 클래스들

 

1. ArrayList 클래스

- 내부적으로는 배열을 사용하여 저장하므로 길이가 고정되어있지만 Capacity(용량)가 있어 저장시 Capaity를 넘는 경우 자동으로 내부적으로 용량을 늘려 새로운 배열을 생성 후 요소를 담는다. 따라서 동적인 배열처럼 처리가 가능하다.

 

사용 특징

"데이터 삽입 및 삭제가 빈번한 경우 사용을 권장하지 않는다."

-> 기본적으로 데이터 삽입시 해당 위치에 데이터를 넣고 그 뒤에 데이터를 전부 하나하나 밀어내는 방식

-> 데이터 삭제시에는 해당 데이터를 빼내고 그 뒤에 데이터를 하나하나 앞으로 가져오는 방식

 

 

2. LinkedList 클래스

- 각 노드들을 연결하여 저장하는 방식이다.

- 용량 개념이 없다.

 

사용 특징

"데이터의 삽입, 삭제가 빈번한 경우 좀 더 용이하다."

-> 데이터를 삽입시 예를들어, 1, 3사이에 2를 넣는다고 가정하면 1이 가리키는 노드를 2로 변경하고 2가 가리기키는 노드를 3으로 변경하면 데이터가 삽입된다. 또한 삭제도 유사한 방식으로 동작하며 ArrayList와 비교했을 때, 빠르게 삽입, 삭제가 가능하다.

"메모리 사용량이 많다."

-> 각 요소당 포인터를 갖고있기에 추가적인 메모리가 소모된다.

 

정리

- 삽입 삭제 빈번시 LinkedList 클래스

- 메모리 사용이 많을시 즉, 많은 데이터 담을 시 ArrayList 클래스

 

 

 

 

4- 8. Set Interface

- 저장하는 요소, 객체의 중복을 허용하지 않는다.

- 내부적으로는 equals 메서드를 통해 중복여부를 체크한다. 즉, 모든 필드값이 같다면 삽입 불가능하다.

(-> equals는 메서드는 삽입하고자 하는 클래스 내부의 equals메서드를 사용하므로 클래스 내의 정의가 필요하다.)

- get메서드를 제공하지 않는다. 따라서 데이터 접근시 iterator와 같은 반복문 사용

-> 따라서 모든 데이터에 접근하는 경우가 아니라면 List인터페이스의 클래스들을 사용하는것이 효율적이다.

- HashSet, LinkedHashSet, TreeSet 클래스가 존재.

 

 

Hash + set

- 기본적으로 HashSet, LinkedHashSet은 해시함수를 통해 요소를 저장하고 관리한다.

- 해시함수를 통해 각 객체마다 고유한 해시코드를 생성하고 해시코드를 기반으로 하는 버킷이라는 공간에 저장한다.

- 버킷이라는 공간에 저장함으로써, 동일한 해시코드를 갖는 요소가 이미 버킷에 있으면 저장하지 않는다. 즉, 중복된 요소의 생성을 막는다.

- 또한, 해시코드를 기반으로 검색시 더 빠른 성능을 제공한다. 검색시 O(1) 시간 복잡도를 갖는다.

- 내부적으로 HashMap이 구성되어 있다고 생각하면 간편하고 실제로도 그렇다.

 

 

Set Intetface Methods

  • size()
  • isEmpty()
  • contains()
  • iterator()
  • add()
  • remove()

 

 

 

4- 8. Map Interface

- 키-값 형태로 데이터를 저장, 따라서 Collection을 상속하지 않는다.

- 키-값 순서쌍을 Entry라고 하며 Map 인터페이스 내부에 Entry 인터페이스로 존재한다.

- HashMap(순서를 보장하지 않는다.), LinkedHashMap(순서를 보장한다.), TreeMap(키 정렬, Comparable 인터페이스를 구현해야 함, 예를 들어, 키가 String이라면 Comparable을 이미 구현하고 있기에 문자열 정렬 기준으로 정렬된다.) 등이 있다.

 

Map Interface Methods

  • get()
  • put()
  • remove()
  • size()

 

Entry Interface Methods

  • getKey()
  • getValue()
  • setValue()

 

 

4- 9. Iterator Interface

- 객체가 가지고 있는 요소들을 순회할 수 있는 기능들을 명세한 인터페이스로써 반복적인 작업에 사용된다.

- Collections 인터페이스를 구현한 클래스들(List, Set, Queue)는 iterator라는 메서드를 가지고 있고 이를 사용한다.

- Map 인터페이스의 클래스들을 사용 원할시 values() 메서드를 사용한다.

 

 

Iterator Interface Methods

  • hasNext()
  • next()
  • remove()

 

예시 코드

// Collections
List<String> value = new ArrayList<>();
value.add("a");
value.add("b");
value.add("c");


Iterator<String> iterator = value.iterator();
while(iterator.hasNext()){
	String now = iterator.next();
}


// Map 1
Collection<String> value = map.values(); // map의 값들 담음
Iterator<String> iterator = value.iterator();

// Map 2
Iterator<String> iterator = map.values().iterator();

 

 

 

5. JAVA 8

- 자바8부터는 인터페이스를 배포 후 변경이 어렵다는 문제를 해결하기 위해서 자바1.8부터는 static, default메서드 사용을 가능하게 하였고 자바9부터는 private 메서드 또한 가능해졌다.

- 따라서, 인터페이스 내의 일반 메서드들을 정의하게 하여 사용가능하게 설정할 수 있다. 즉, 구현 클래스에서 재정의하지 않고도 바로 사용이 가능하다.

- 또한 default 메서드를 사용함으로써 이미 배포된 인터페이스에 새로운 기능을 추가하는 것이 가능하다. 재정의 또한 가능하다.

 

  • 함수형 프로그래밍
  • 람다
  • Stream API

 

 

5-1. 함수형 프로그래밍

명령형 프로그래밍 : '어떻게'에 집중, 기존의 방식 (for문, if문 등 사용)

선언형 프로그래밍 : '무엇'에 집중 (stream 등을 사용)

함수형 프로그래밍 : 선언형 프로그래밍이 특징을 따르는 프로그래밍

 

 

특징

  • 순수 함수 : 동일한 입력-동일한 결과, 부수 효과가 발생하지 않음.
  • 일급 객체 : 함수를 변수 혹은 데이터 구조에 담을 수 있음.
  • 영속 자료구조 : 특정 변수/객체의 자료를 변경시 원본을 변경하는 것이 아닌 복사본(새로운 인스턴스)을 사용해 변경한다.

 

중첩 클래스 (Nested Class)

- static class 

- non-static class : 멤버 클래스, 로컬 클래스, 익명 클래스

 

- 멤버 클래스 : 클래스 안에 클래스를 필드로 선언, Outer 클래스가 인스턴스화가 되어야 의미.

- 로컬 클래스 : 클래스 안 메서드 안에 클래스 선언, Outer 클래스의 해당 메서드가 호출되어야 의미

- 익명 클래스 : 메서드안에서 사용되지만 매개변수처럼 잠시 사용되고 사라지며, 이름이 없는 클래스

// 익명 클래스

memberService.register(new Member(){
	// 메서드 등 정의
	
})

 

 

 

 

5-2. 람다

- 익명 클래스를 좀 더 간소하게 작성 가능하다.

- 불필요한 코드를 줄일 수 있다.

- (변수명) -> 실행문;

 

 

함수형 인터페이스

- 람다식을 담을 수 있는 람다식의 자료형, public 추상메서드를 하나만 가지고 있는 인터페이스는 모두 함수형 인터페이스가 되는 것이 가능하다.

- Predicate<T> now = () -> {};

- @FunctionalInterface를 사용하여 명시하면 추상메서드 두개시 오류 처리 가능

- 람다식을 해당 추상 메서드에 넣어서 사용하는 개념이며 사용시 해당 추상 메서드를 호춣하면 람다식이 호출되는 개념이다.

 

 

자주 사용하는 함수형 인터페이스

  • Predicate<T> : test() 메서드를 사용, T->Boolean
  • Consumer<T> : accept() 메서드를 사용, T->Void
  • Function<T, R> : apply() 메서드를 사용, T->R, 반환값으로 리턴
  • Supplier<T> : get() 메서드를 사용, ()->T, 파라미터 없이 어떤값을 제공하는 경우

- T : 람다식의 반환타입

예를들어, T가 Void인 Consumer를 사용시 람다식의 실행문은 void타입(그냥 출력문 등)이 되어야한다.

- 원시형 타입의 작업은 앞에 타입명을 붙여준다 ex) IntPredicate

 

 

 

5-3. Stream Interface

- Collection Framework의 데이터들을 반복적으로 관리시 사용한다.

- 람다를 사용한다.

- 한번 생성한 스트림은 한번 사용후 다시 사용 불가능하니 다시 생성해서 사용해야 함.

- 가독성이 좋아진다.

 

1. Methods

  • void forEach(Consumer) : 스트링 항목들에 대한 순회
  • Stream concat(Stream, Stream) : 두개의 스트림을 하나의 스트림 결과 변환
  • Stream sorted() : 정렬하고 스트림 결과 반환
  • Stream filter(Predicate) : 파라미터 조건에 맞게 필터링 후 스트림 결과 반환
  • long count() : 항목의 개수
  • R collect(Collector) : 스트림의 항목을 Collection 타입의 객체로 변환
  • Object[] toArray() : 스트림 항목들을 배열 객체로 반환
  • Optional reduce(Binary Operator) : 람다 표현식을 기반으로 데이터를 소모하고 그 결과를 반환

 

2. 스트림 생성

// 1. 기존의 데이터가 있는 경우
List<Integer> test = new ArrayList<>();
test.add(1);
test.add(2);


List<Integer> = test.stream()
// 이 후 진행 
                    
                    
                   
// 2. 새로운 데이터를 스트림을 통해 생성하고자 하는 경우
Stream.Builder<Integer> test = Stream.builder();
builder.add(1);
builder.add(2);
// Or
builder.accpet(1);
builder.accpet(2);


Stream<Integer> new_test = test.build();
// 이 후 진행

 

 

3. 파이프 라인

- Collection Data -> Filter(<Predicate>) -> Sort(<Comparator>) -> Map(<Function>) -> Collect

 

 

4. 중간 연산

- 스트림을 반환

  • filter : 연산 인수(Predicate<T>)
  • map : 연산 인수(Function<T, R>)
  • limit
  • sorted : 연산 인수(Comparator<T>)
  • distinct 
  • peek : 연산 인수(Consumer<T>)
  • skip

 

 

5. Filtering

- 스트림으로 반환

- 예를 들어, 해당 데이터가 특정 기준보다 크다, 작다와 같은 기준으로 두고 필터링시 사용

- 다른 예시로는 해당 이름이 특정 이름이 같은지 등 동등 여부를 판단시에도 필터링 사용

- filter()로 데이터 추출, distinct()로 데이터 중복 제거

- distinct 사용시 사용하고자 하는 클래스 안에 equals가 정의되어 있어야 함.

 

 

6. Sorting

- 특정 조건에 맞게 정렬한다

- 스트림을 반환한다.

- 추가적으로 Set은 순서를 보장하지 않으므로 정렬이 되지 않는다.

- 정렬하고자 하는 클래스 안에 정렬 조건을 아래와 같이 작성해야 한다.

(Comparable 인터페이스의 compareTo 메서드 오버라이딩 해야 함, 또는 Comparator.coparing을 사용)

public class Worker implements Comparable<Worker>{

    @Override
    public int compareTo(Worker worker){
    	// this와 매개변수 비교한다
        // this가 크면 1 반환
        // 같으면 0 반환
        // 인자가 크면 -1 반환
    }
}

 

 

7. Maping

- 데이터를 다른 데이터로 변환해주는 연산

- 파라미터는 Function 함수형 인터페이스, 람다 표현식

  • map(클래스명::메서드명) : 예를들어, get메서드를 통해 객체들이 갖고 있는 특정 필드들로만 변환
  • mapToInt()
  • mapToDouble()
  • mapToLong()

 

 

8. 병렬 스트림

- 멀티 스레드 환경에서 한 스트림에 대해서 일부 요소(데이터)는 A 스레드가 나머지는 B 스레드가 처리하는 방식으로 하여 같은 결과를 좀 더 빠르게 도출해낼 수 있는 개념을 말한다.

- 하지만 모든 상황에서 사용 가능한 것은 아니다. 예를 들어, distinct 처리를 할 때, 스레드 A는 1~5 요소를 B는 6~10을 처리한다고 가정한다면 A가 처리하는 요소와 B가 처리하는 요소가 같은 경우 distinct 처리가 원활히 이루어지지 않을 수 있다. 따라서, 사용의 주의가 요망된다.

test.parallelStream();

 

 

9. 최종 연산

- void, Collection을 반환

- 결과물을 반환

- 스트림은 더 이상 재사용이 불가능하게 됨.

  • forEach() : 파라미터에 전달되는 람다식을 처리하고 void 반환
  • count() : 요소의 수를 반환
  • collect() : Collection 반환, Collectors.toList(), Collectors.toSet(), Collectors.toMap() 등 사용
  • sum() : 요소의 합을 반환
  • reduce() : 스트림의 요소를 하나씩 줄여가며 연산 수행 후 Optional 반환, 다양한 조건으로 최종연산 수행하여 임의의 데이터 반환
  • allMatch() : 파라미터에 전달되는 람다식 기준으로 스트림 데이터가 모두 일치하는지 확인, Boolean 반환
  • anyMatch() : 파라미터에 전달되는 람다식 기준으로 스트림 데이터가 하나라도 일치하는지 확인, Boolean 반환
  • noneMatch() : 파라미터에 전달되는 람다식 기준으로 스트림 데이터가 모두 일치하지 않는지 확인, Boolean 반환
  • findFirst : 스트림 데이터 중 가장 첫번째 데이터 반환

 

 

10. 추가 개념

  • sum() 사용시 Stream<Integer>의 형태는 바로 사용이 불가능하다. 따라서 .mapToInt(Integer::valueOf)로 설정하여 int로 변경 후 사용해야 한다.

 

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

Jar 파일 만들고 사용해보자  (0) 2023.10.09
완전탐색 재귀호출에서 전역필드에 값 추가  (0) 2023.06.09
JAVA - Grammar  (0) 2023.05.03
JAVA - Loop(for, iterator, stream)  (0) 2023.03.23
JAVA - Map(HashMap, LinkedHashMap)  (0) 2023.03.23