티스토리 뷰

● Java 장점

- 운영체제가 독립적
- 객체지향언어
- 비교적 배우기 쉬움
- 네트워크와 분산처리를 지원
- 자동 메모리 관리
>> 가비지컬렉터(garbage collector)
- 멀티쓰레드 지원
>> 여러 쓰레드에 대한 스케줄링을 자바 인터프리터가 담당
- 동적 로딩 지원

 

● JVM 역할(Java Virtual Machine)

- 자바 애플리케이션을 실행시키기 위한 소프트웨어
기능 1 -> 하드웨어 기계어로 변환해주어 속도 향상해주는 JIT 컴파일러
기능 2 -> 각 운영체제에 맞게 실행

 

JDK 역할(Java Development Kit)

- JVM과 자바클래스 라이브러리 외에 자바를 개발하는데 필요한 프로그램이 설치

JDK bin 역할
- javac.exe >> 자바 컴파일러, 자바 소스코드를 바이트코드로 컴파일한다.
- java.exe   >> 자바 인터프리터, 컴파일러가 생성한 바이트코드를 해석하고 실행한다.
- javap.exe >> 역어셈블러, 컴파일된 클래스 파일을 원래의 소스로 변환한다.
- jar.exe    >> 압축프로그램, 클래스 파일과 프로그램의 실행에 관련된 파일을 하나의 jar. 압축하거나 해체한다.

 

JRE

- 자바실행환경, 자바로 작성된 응용프로그램이 실행되기 위한 최소환경

 

JVM 메모리 구조

1. 메서드 영역(method area)
- 해당 클래스의 클래스 파일(*. class)을 읽어서 분석하여 클래스에 대한 정보를 저장하는 영역

2. 힙(heap)
- 인스턴스가 생성되는 공간

3. 호출 스택(call stack 또는 execution stack)
- 메서드가 작업에 필요한 메모리 공간을 제공한다.
- 지역변수들과 연산의 중간결과 등을 저장하는데 사용
- 메서드가 작업을 마치면 할당되었던 메모리 공간은 비워진다.

 

● 오버로딩

- 하나의 메서드 이름으로 여러 기능을 구현하는 것

void println( )
void printlnBoolean(boolean x)
void printlnChar(char x)
void printlnDouble(double x)
void printlnString(String x)

하나의 메서드로 정의하면
>> void println( )

1. 오버로딩 조건
- 메서드 이름이 같아야 함
- 매개변수의 개수 또는 타입이 달라야 한다. (타입은 상관 X)

예시)

int add(int a, int b)
int add(int x, int y)

- 매개 변수 타입이 같아서 X

int add(int a, int b)
long add(int a, int b)

- 리턴 타입만 다른 경우 X

 

올바른 예시)

long add(int a, long b)
long add(long a, int b)

- 같은 메서드 O , 다른 매개변수 타입 O

 

2. 오버로딩 장점
- 메서드 하나의 이름으로 정의할 수 있어서 기억하기 쉽고 짧게 지을 수 있어서 오류 가능성 줄인다.
- 여러 개의 메서드를 지을 필요가 없어서 다른 이름을 짓는데 고민할 필요 없다.

 

 

● 오버라이딩

- 자식이 상속받은 부모 메서드의 내용을 변경하는 것
- 상속받은 메서드를 그대로 사용하기도 하지만, 자신이 쓰고 싶은 값에 맞게 쓰는 경우에 쓰인다.

class Point {
	int x=10;
	int y=11;
	
	int getLocation() {
		return x+y;
	}
	
}
class Points extends Point {
	int z=3;
	
	int getLocation() {   // 오버라이딩
		return x+y+z;
	}
}

 

조건
1. 이름이 같아야 한다.
2. 매개 변수가 같아야 한다.
3. 반환 타입이 같아야 한다.
4. 자식이 많은 수의 예외를 선언 X (예외 레벨에 따라)

 

오버로딩 vs 오버라이딩 차이

- 오버로딩은 기존에 없는 메서드를 추가하는 것
- 오버라이딩은 부모로부터 상속받은 메서드의 내용을 변경하는 것

class Parent {
	void parentMethod( ) { }
}
class Child extends Parent {
	void parentMethod( ) { }           // 오버라이딩
	void parentMethod(int i) { }       // 오버로딩
}

 

생성자

- 생성자는 인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메서드' 이다.
- 주로 연산자 new에 의해서 메모리(heap)에 인스턴스가 생성된다.
- JPA 제공하는 어노테이션
1. @NoArgsConstructor - 파라미터가 없는 기본 생성자
2. @AllArgsConstructor - 모든 필드 값을 파라미터로 받는 생성자
3. @RequiredArgsConstructor - final이나 @NonNull인 필드 값만 파라미터로 받는 생성자

 

인스턴스

- 어떤 클래스로부터 만들어진 객체를 그 클래스의 '인스턴스'라고 한다.
- Tv클래스로부터 인스턴스를 생성하고 인스턴스의 속성과 메서드를 사용한다.

class Tv {
   int channel;
   void channelDown( ) { --channel; }
}
class TvTest {
   Tv t = new Tv( );  // 인스턴스 생성
   t.channel = 7;
   t.channelDown( );
}

 

인터페이스 장점

- 개발 시간을 단축시킬 수 있다.
- 표준화가 가능하다.
- 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.
- 독립적인 프로그래밍이 가능하다.

 

1. 인터페이스를 사용하는 이유
>> 첫번째 이유

- 건물을 표현하는 클래스중에서 Barrack 클래스만 건물 이동 메서드를 추가할 경우가 필요하다. 
이럴 경우 인터페이스를 통해 메소드를 추가할 수 있다.

interface Liftable {
	void move(int x, int y);
}

class LiftableImpl implements Liftable {
	public void move(int x, int y) { ... }  // 이동 메소드 추가
}

class Barrack extends Building implements Liftable { 
	LiftableImpl l = new LiftableImpl( );
	
	void move(int x, int y) { 
          l.move(x,y); 
   }
}

상속은 그대로이지만 인터페이스를 통해 따로 메소드를 추가할 수 있는 것을 볼 수 있다.

 

>> 두번째 이유

class A {
	public void methodA(B b) {
		b.methodB();
	}
}
class B {
	public void methodB() {
		System.out.println("methodB()");
	}
}

클래스 A, B는 직접적인 관계이므로 B의 methodB( ) 선언부가 변경되면 클래스 A도 변경되어야 한다.
하나가 변경되면 다른 하나가 변경되는 문제점이 발생된다.
이 때 클래스 A가 클래스 B를 직접 호출하지 않고 인터페이스를 매개체로 해서 접근할 수 있다.
그러면 B가 변경되어도 A는 전혀 영향을 받지 않는다.

interface I {
	void methodB( );
}
class B implements I {
	public void methodB( ) {
		System.out.println("methodB( )");
	}
}

 

 

얕은 복사 vs 깊은 복사

- 얕은 복사는 '주소값'을 복사한다. 즉, 객체는 참조하지 않고 데이터만 참조한다. 그래서 실제 값이 같다.
- 깊은 복사는 '실제값'을 새로운 메모리 공간에 복사한다. 즉, 실제값이 다르다.

>> 얕은 복사

class Book {
	private String title;
	private String author;

	public Book (String title, String author) {
		this.title = title;
		this.author = author;
	}
}

class Test {
	public static void main(String args[]) {
		Book library = new Book("태백산맥1","조정래");
		Book copy = library;  // 얕은 복사

		System.out.println(library.getTitle()+library.getAuthor());   // 태백산맥1조정래
		System.out.println(copy.getTitle()+copy.getAuthor());      // 태백산맥1조정래

		copy.setTitle("22");

       		 System.out.println(library.getTitle()+library.getAuthor());   // 22조정래
		System.out.println(copy.getTitle()+copy.getAuthor());      // 22조정래
	}
}

얕은 복사는 데이터를 복사하고 객체는 참조하지 않기 때문에 두 개의 값이 바뀌었다.
한 개의 Book 인스턴스를 가리키게 되므로 완전한 복제라고 볼 수 없다.
코드를 짜면 참조값이 아닌 실제 값을 복사해야 하는 경우가 있다.
그럴 경우 깊은 복사를 해야한다.

 

>> 깊은 복사

class Book {
	private String title;
	private String author;

	public Book (String title, String author) {
		this.title = title;
		this.author = author;
	}
	public Book deppCopy() {
		Object obj = null;
		
		try {
			obj=super.clone();
		} catch (Exception e) {}

		Book b= (Book) obj;
		b = new Book(this.title,this.author);
			
		return b;
	}
}

class Test {
	public static void main(String args[]) {
		Book library = new Book("태백산맥1","조정래");
		Book copy = library.deppCopy();  // 깊은 복사

		copy.setTitle("22");

        	System.out.println(library.getTitle()+library.getAuthor());   // 태백산맥1조정래
		System.out.println(copy.getTitle()+copy.getAuthor());      // 22조정래
	}
}

새로운 Book 인스턴스를 참조하여 새로운 값이 출력이 되었다. 
원본이 참조하고 있는 객체까지 복사한 것이다.

 

 

List 인터페이스

1. Vector
- Java 1 버전대에서 나와서 호환성이 떨어지지만 동기화를 보장해주어 공유 자원이나 복수 사용자가 존재할 때 유용하다. 단일 스레드의 경우 자동으로 동기화를 보장하는 것이 오히려 성능이 저하가 된다.

2. ArrayList
- 인덱스를 가지고 있어서 검색에 용이하다. 중간에 데이터가 삽입/삭제할 경우 전부 한 칸씩 당기거나 밀리기 때문에 삽입/삭제가 빈번한 데이터인 경우에는 부적합하다. Vector와 달리 동기화를 보장하지 않는다.

3. LinkedList
- ArrayList의 단점을 극복하기 위해 나온 것으로, 노드들이 줄줄이 연결되어 있다. 삽입/삭제할 때는 중간에 해당 노드의 주소지만 바꿔주면 되므로 삽입/삭제가 빈번한 데이터에 적합하다. 하지만 검색할 경우 처음부터 끝까지 줄줄이 이동해야 돼서 적합하지 않다.

4. Vector vs ArrayList 차이
- Vector는 한 번에 하나의 스레드만 접근 가능하지만 ArrayList는 동시에 여러 스레드가 작업할 수 있다.
- Vector는 동기화 되어있기 때문에 한 번에 하나의 스레드만 접근할 수 있어서 Thread Safe(스레드 안전) 하지만,
ArrayList는 동기화 되지 않기 때문에 명시적으로 동기화를 해줘야 한다.
- ArrayList는 동기화 되지 않기 때문에 Vector 보다 빠르다.
- 크기 증가에서 차이가 있다. Vector는 100% 증가, ArrayLisr는 50% 증가한다.

큰 관점에서 바라볼 때 동기화 차이, 크기 증가 차이, 호환성 차이가 있다.
단일 스레드 -> Vector 자동으로 동기화 돼서 부적합, ArrayList 수동 동기화 해야돼서 적합
멀티 스레드 -> Vector 자동으로 동기화 돼서 적합, ArrayList 수동 동기화 해야돼서 부적합

크기 차이 관점으로 보면 Vector < ArrayList 효율적이다.

Vector 클래스는 컬렉션 프레임워크 이전에 나온 클래스이고, 
현재 소스 코드 호환 때문에 남아 있는 클래스 이기 때문에 ArrayList를 사용하는 게 좋다.

5. LinkedList vs ArrayList 차이
- 순차적으로 추가/삭제하는 경우 ArrayList 더 빠르다.
- 중간 데이터를 추가/삭제하는 경우에는 LinkedList 더 빠르다.
>> 각 요소 간의 연결만 변경해주면 되기 때문에 처리속도가 상당히 빠르다.
>> 반면에 ArrayList는 각 요소들을 재배치하여 추가할 공간을 확보하거나 빈 공간을 채워야 하기 때문에 처리속도가 느리다.
- 낮은 접근성
>> LinkedList는 불연속적으로 위치한 각 요소들이 서로 연결된 것이라 데이터를 차례대로 따라가야만 원하는 값을 얻을 수 있다. 그래서 저장한 데이터가 많아질수록 접근 시간이 길어진다는 단점이 있다.
>> 반면에 ArrayList는 배열의 각 요소들이 연속적으로 메모리상에 존재하기 때문에 원하는 요소의 주소를 곧바로 읽을 수 있다.

데이터가 많고 빠른 접근성이 필요하다. -> ArrayList
데이터가 적은데 추가/삭제가 많다. -> LinkedList

 

 

Map 인터페이스

1. HashMap
- HashMap은 Map interface를 implements 한 클래스로서 중복을 허용하지 않는다.
- HashMap은 O(1)과 같은 get과 put 같은 기본 연산에 대해 일정한 시간 성능을 나타낸다.
널(null) 허용한다. 
- 내부 hash 값에 따라서 키순서가 정해지므로 특정 규칙없이 출력된다.
- 검색과 데이터 처리하는 경우에 적합하다. 

2. Hashtable
- HashMap의 구버전이다. 
널(null) 허용하지 않는다.
- 멀티 스레드 안전하다. 

3. TreeMap
- 널(null) 허용하지 않는다.
- TreeMap은 get 및 put 메서드에 대한 log(n) 시간 보장 비용을 제공한다.
- HashMap과 다른 점은 SortedMap을 implements 하였으므로, key 값들에 대한 정렬이 이루어진다는 점이다. 
- 정렬 상태로 출력된다. 
범위 검색이나 정렬이 필요한 경우에 사용하는 것이 좋다.

 

 

Iterator란

Iterator는 이런 집합체로부터 정보를 얻어낸다고 볼 수 있다. 집합체를 다룰 때는 개별적인 클래스에 대해 데이터를 읽는 방법을 알아야 하기 때문에 각 컬렉션에 접근이 힘들어진다.를 쓰게 되면 어떤 컬랙션이라도 동일한 방식으로 접근이 가능하여 그 안에 있는 항목들에 접근할 수 있는 방법을 제공한다.(다형성)

Iterator 메소드에는 hasNext(), next(), remove()가 있다.

 

람다식란

JDK1.8부터 추가된 람다식으로, 객체지향언어인 동시에 함수형 언어가 되었다. 
메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로, 람다식을 '익명 함수'이라고도 한다.

int max(int a, int b) {
    return a > b ? a : b;
}
람다식으로 하면
(a, b) -> a > b ? a : b

ArrayList<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; i++) {
    list.add(i);
}
for(int i=0; i<10; i++) {
  System.out.println(i);
}
람다식으로 하면
ArrayList<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; i++) {
   list.add(i);
}
list.forEach( i -> System.out.println(i); }    // 간결하게 바뀜

 

스트림이란

Collection이나 Array에 데이터를 담고 원하는 결과를 얻기 위해 for문과 Iterato를 이용해서 코드를 작성하면 코드가 길고 알아보기 어렵다. 또한 같은 기능의 메소드들이 중복해서 사용되고 있다. 예를 들어 Collection.sort( ) , Arrays.sort( ) 사용한다. 이러한 문제점을 해결하기 위해 만든 것이 스트림이다. 스트림은 데이터 소스를 추상화하고 데이터를 다루는데 자주 사용되는 메서드들을 정의하였다.

Arrays.sort(strArr);
Collection.sort(strList);

for(String str : strArr)
    System.out.println(str);
for(String str : strList)
    System.out.println(str);

스트림으로 하게 되면
strStream.soted( ).foreach(System.out::println);
strStream2.soted( ).foreach(System.out::println);

스트림을 사용한 코드가 간결하고 이해하기 쉬우며 재사용이 높다는 것을 알 수 있다.

 

 

 

 

'∙Java & Spring' 카테고리의 다른 글

ThreadPoolTaskExecutor 이용하여 성능 개선하기  (2) 2022.05.29
Junit Mock 사용하기  (0) 2022.05.06
인프런 스프링 핵심 원리 정리  (0) 2022.04.20
JWT 구조  (0) 2022.04.16
자바의 정석 정리(1)  (0) 2022.03.02