티스토리 뷰
● 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 |