728x90
반응형

Java 컬렉션 (Collection) 정리

Java Collection은 Java 언어에서 데이터를 저장, 처리, 관리하는 데 사용되는 API 모음이다.

특히나 자료구조에 대해서 이해한다면 쉽게 이해해서 사용할 수 있다.

객체들을 효율적으로 추가, 삭제, 검색할 수 있도록 java.util 페키지에 collection과 관련된 인터페이스와 클래스를 포함시켰기 때문에 사용할 수 있다.

(매번 찾아보는 것보다 한 번 정리해야겠다는 생각이 들어서 쓰게 되었다.)

데이터 구조엔 여러가지 종류들이 있다 : ArrayList, LinkedList, Hashtable, Tree ...

java가 제공하는 집합 인터페이스 : List, Set, Queue, Map 그리고 각각의 구현체들



List

  • 순서 있는 집합 → 어느 요소가 어디에 삽입되는지 제어가능!
  • 중복된 요소들을 허용
  • 어느 위치에 어떤 객체가 있는지를 신경씀
List<String> words = List.of("Apple", "Bat", "Cat"); // of는 정적 메소드
words.size(); // 3
words.isEmpty(); // false
words.get(0); // Apple
words.contains("Dog"); // false
words.indexOf("Cat"); // 2

of 메소드
: Java 9부터 추가된 List 인터페이스의 메소드. 주어진 요소들을 포함하는 불변리스트를 생성함.
리스트 생성하고 초기화할 때 사용하면 좋다. 그리고 null 요소도 허용하지 않는다.
List.of로 만든 instance는 수정할 수 없다. (불변 리스트)

immutable List
불변 클래스란 무엇일까? -> String, BigDecimal, Wrapper 클래스는 불변이다.
이 의미는 특정 클래스의 인스턴스를 만든 순간부터 이 값을 바꿀 수 없게 된다는 것이다.

가변 리스트를 만들고 싶으면 ArrayList나 LinkedList, Vector를 만들어야 한다.

List<String> wordsArrayList = new ArrayList<String>(words);
List<String> wordsLinkedList = new LinkedList<String>(words);
List<String> wordsVector = new Vector<String>(words);

ArrayList와 LinkedList의 차이

  • ArrayList는 접근은 빠르지만 삽입 삭제가 느리다. 메모리가 연속적
  • LinkedList는 접근은 느리지만 삽입 삭제가 빠르다. 메모리가 따로따로. 서로 연결되는 메모리 주소를 기억하고 있다.
  • java에서의 LinkedList는 요소들이 양면으로 연결되어 있다. forward로 향하는 연결과 backward로 향하는 연결이 둘 다 존재한다. (doubly linked)
  • 변경점이 적다면 ArrayList, 삽입과 제거가 빈번하다면 LinkedList를 사용하면 된다.

Array : 배열은 모든 요소들을 순서대로 저장하고 언제나 요소 하나를 제거하면 그 요소를 제거한 후 그 다음 요소를 왼쪽으로 밀어야 한다. 하지만, 배열의 특정한 위치에 있는 요소를 갖고 오는 건 쉽다.

LinkedList : 각자 요소들은 다음 요소들과 연결되어 있다. 삽입과 삭제가 더 쉽다. 하지만 특정 위치에 있는 요소를 갖고 오거나 찾는 것은 연결점들을 다 건너 다니면 무엇이 있는지 찾아야 한다.


Vector랑 ArrayList는 언제 구분해서 써야할까?

Vecotr 클래스는 자바1때부터 있었다. ArrayList는 자바1.2부터.(오래 전부터 있었다는 뜻)

그럼 Vector의 문제가 무엇이여서 ArrayList가 나타난 것일까?

  • vector는 모든 메서드들이 synchronize가 되어있는데, ArrayList는 Synchronize되어 있지 않다.
  • vector는 내부의 'add', 'remove', 'set'과 같은 변경 작업을 할 때 내부적으로 synchronized를 사용한다.
  • Synchronize 키워드는 어떤 차이를 만들까?
    • 예를 들어서 한 클래스 안에 25개의 동기화된 메소드들이 있다고 하자.
    • 만약 이 Vector 클래스의 인스턴스가 여러 스레드 사이에 공유된다면, 스레드 중 단 하나만이 이 25개의 메소드들을 실행할 수 있다.
    • 즉, 이 동기화된 메소드들 안에서는 한순간에 오직 하나의 스레드만 코드를 실행시킬 수 있다.
    • 이렇게 하는 이유는 프로그램이 안전하길 바라기 때문이다.
    • 우리의 프로그램은 쓰레드 하나가 사용하든 15개의 쓰레드가 사용하든 행동방식이 바뀌면 안되고, Synchronized가 그 역할을 하려 하는 것이다.
    • Vector는 Thread-safe하다. 여러 쓰레드들 사이에서 데이터를 공유하는 상황에서 Vector를 사용할 수 있다.
    • 그러나 ArrayList는 안전하지 않다. 그치만, 보안은 언제나 성능에 타격을 준다.(보안 상승, 성능 하락 / 보안 하락, 성능 향상)
    • 왜냐하면 스레드 하나가 동기화된 메소드를 실행 중일때 그럼 다른 스레드들은 그 스레드가 동기화된 메소드의 실행을 완료할때까지 기다릴 수 있기 때문이다.
    • 안전이 필요하지 않는 이상 ArrayList가 더 낫다.
    • 동기화는 기초적인 안전 구현방식 중 하나이다.
      • 그래서 만약 멀티 쓰레드 환경이 아닐 때 Vector 클래스를 사용하게 되면 성능이 떨어지게 된다.
    • 이를 위해, ArrayList를 위해 Collections 클래스에서는 synchronizedList 메소드를 제공한다.
    • ArrayList클래스를 멀티스레드 환경에서 사용해야 한다면 CopyOnWriteArrayList 또는 Collections.synchronizedList를 사용해야 한다.

반복문

java Collection 인터페이스의 Iterator는 Collection 요소를 하나씩 반복적으로 접근할 수 있다.

  • hasNext() : 다음 요소가 있는지 true, false를 반환함
  • next() : 다음 요소 반환
    for(int i=0;i<words.size();i++){
      System.out.println(words.get(i));
    }
    

for(String word:words){
System.out.println(words.get(i));
}

// Iterator 사용하기
Iterator wordsIterator = words.iterator();
while(wordsIterator.hasNext()){
System.out.println(wordsIterator.next());
}


그럼 3가지 중 어떤 루프를 써야할까?

```java
while(wordsIterator.hasNext()){
    System.out.println(wordsIterator.next());
}
List<String> words = List.of("Apple","Bat","Cat");
List<String> wordsAl = new ArrayList<>(words);
for(String word:words){
    if(word.endsWith("at"))
        System.out.println("word = " + word);
}

밑의 방법은 추천하지 않는다.

for(String word:wordsAl){
    if(word.endsWith("at")){
        wordsAl.remove(word);
    }
}

해당 방법은 개선된 loop의 중간에서 변경점을 만들면 단어를 제거함으로써 반복이 어떻게 진행되는지가 바뀔 수가 있기 때문에, 반복자를 사용하는 것이 좋다.

Iterator<String> iterator = wordsAl.iterator();
while (iterator.hasNext()) {
    if(iterator.next().endsWith("at")){
        iterator.remove();
    }
}

List 안에는 primitive type를 포함할 수 없다. List 인터페이스는 제네릭 타입을 지원하기 때문에, List 내부에는 오직 객체만 포함시킬 수 있다.
그래서 사용하려면 primitive type을 객체로 래핑한 Wrapper 클래스를 사용하여 포함해야 한다.

AutoBoxing : primitive 타입의 값을 해당하는 wrapper 클래스의 객체로 바꾸는 과정을 의미한다.

java 컴파일러는 primitive type이 아래 두 가지 경우에 해당될 때 autoBoxing을 적용한다.

  1. primitive type이 Wrapper 클래스의 타입의 파라미터를 받는 메서드를 통과할 때
  2. primitive type이 Wrapper 클래스의 변수로 할당될 때

List를 만들려 할 때 일어나는 일은 다 AutoBoxing되어 Wrapper Class가 생성되는 것이다.


sort static solution
Collections.sort는 static method이다. (즉, Collection을 따로 생성자를 통해 생성하지 않아도 사용할 수 있다. )

List는 Collection Interface를 연장한다. 즉, 이것은 Collection Interface에 있는 모든 것을 구현하고, 객체의 위치에 상관하는 메서드를 제공한다.


정리 : List interface의 구현

→ ArrayList : 배열을 기초적 데이터 구조로 사용. LinkedList에 비해서 삽입과 삭제가 느리다.

but, 특정 위치의 특정 요소에 접근하고 싶다면 매우 빨리 수행할 수 있다.

→ LinkedList : 특정 위치의 요소 찾는 것은 느리지만, 요소의 삽입과 제거는 훨씬 빠르다

→ Vector : 다중 스레드 시나리오에서는 성능 하락.



Set

  • 용도 : 중복이 허용되지 않는 집합을 만들 때 사용한다.
    Unique things only : 중복이 허용되지 않는다!
  • List interface와 비교했을 때, Set interface는 위치 접근을 허용하지 않는다. Set은 그냥 랜덤위치에 저장한다. (갖고 있다는 사실이 중요)
  • Set은 기본적으로 변경을 허용하지 않는다. 따라서, 다음 코드는 되지 않는다.
Set<String> set = Set.of("Apple","Banana","Cat");
set.add("Apple"); // 오류남. -> 왜냐하면 변경을 허용하지 않기 때문
  • HashSet : Set의 여러 구현 중 하나.
  • 안에 요소들이 있는지 없는지는 신경쓰지만, 위치는 신경쓰지 않는다.
set.add(2,"Apple");
  • 특정 위치에 요소를 추가할 수 없다. Set은 유일한 값들을 저장하는데 사용된다.


Queue

작업하고 싶은 순서대로 정렬할 때 사용된다.

컬렉션의 메서드를 모두 지원한다.

우선순위 Queue : 지정한 순서대로 정렬되어 있다.

물론 Comparator로 순서를 지정할 수 있다.

Queue<String> queue = new PriorityQueue<>();
queue.poll(); // 아무것도 없어서 안빠짐
queue.offer("Apple");
queue.add("Banana");
System.out.println("queue = " + queue); // queue = [Apple, Banana]

queue.addAll(List.of("Zebra", "Sero"));
System.out.println("queue = " + queue); // queue = [Apple, Banana, Zebra, Sero]

System.out.println(queue.poll()); // Apple
System.out.println(queue.poll()); // Banana
System.out.println(queue.poll()); // Sero
System.out.println(queue.poll()); // Zebra
System.out.println(queue.poll()); // null

우선순위 대로 poll해서 뺐을 때, 나오는 걸 볼 수 있다.

Queue<String> queue = new PriorityQueue<>();
queue.addAll(List.of("Zebra", "Monkey", "Cat"));
System.out.println(queue.poll()); // Cat
System.out.println(queue.poll()); // Monkey
System.out.println(queue.poll()); // Zebra

Comparator 지정하기 -> 이 코드에서는 길이가 짧은 순으로 나오게 할 수 있다.

static class StringLengthComparator implements Comparator<String> {

    @Override
    public int compare(String o1, String o2) {
        return Integer.compare(o1.length(), o2.length());
    }
}

public static void main(String[] args) {

    Queue<String> queue = new PriorityQueue<>(new StringLengthComparator());
    queue.addAll(List.of("Zebra", "Monkey", "Cat"));
    System.out.println(queue.poll()); // Cat
    System.out.println(queue.poll()); // Zebra
    System.out.println(queue.poll()); // Monkey
}


Map

→ Map은 Collection interface를 연장하지 않는다! Map은 Collection 프레임워크의 일부이지만 Collection 인터페이스를 구현하지는 않는다!

키-쌍을 저장하는데 사용한다.

HashMap(정렬되지 않고, 순서없음)

  • HashMap과 Hashtable의 차이 : 둘 다 해싱 기법을 사용했다는 점에서는 동일하다Hashtable(HashMap과 같지만, 모든 메서드가 동기화되어 있어 스레드가 안전)
  • Hashtable은 Vector와 비슷하다 → 동기화되어 있음. Hashtable의 모든 메소드는 동기화 되어 있다.
    • 스레드가 HashMap에 비해 더 안전하고, HashMap과 동일하게 Hashtable도 분류되어 있지도 않고 순서가 있지도 않다.
  • Hashmap은 열쇠를 null 값과 저장할 수 있게 해준다. → Hashmap 안에는 key와 null값을 저장할 수 있다. (Hashtable에서는 할 수 없다.)LinkedHashMap(삽입 순서 기억)
  • LinkedHashset과 비슷하게 순서가 유지된다. 그냥 삽입 순서대로 들어간 것이기 때문에 HashMap에 비해서는 삽입과 제거가 느리다. 하지만 요소간에 연결이 되어있어 요소를 도는 이터레이션은 훨씬 빠르다.TreeMap(데이터를 정렬된 순서로 저장)
  • 기반 데이터 구조는 Tree, 정렬된 순서로 저장된다.
  • 트리가 존재할 때는, 데이터가 정렬되어 있기 때문에, 다른 인터페이스(NavigableMap)도 구현한다.

Hashtable

배열과 비슷하게 고정된 위치들과, LinkedList의 장점을 합친 것이다. 각각의 위치를 양동이라고 하면 양동이에 여러 가지를 저장할 수 있는 것이다.
양동이에 차곡차곡 여러 가지를 쌓을 수 있는 것이다.
그럼 어떤 양동이에 저장할지 결정하느냐? 이때 나오는 것이 해싱 함수를 사용한다.
만약 배열의 크기가 13이라고 생각해보자. (index는 그럼 0부터 12까지 있는 것이다.)

만약 15를 어디에 저장해야 할지 결정한다면 어느 양동이에 15를 저장해야 할지 어떻게 정할까? 여기에 13개의 양동이가 있으므로, 15를 13으로 나누고 나머지를 구해서 양동이에 요소를 넣는 것이다.

15는 그럼 인덱스 2의 양동이에 들어가게 되는 것이다.

즉, 해싱 함수는 어느 양동이에 요소가 들어갈지 정해주는데 사용된다.

2를 삽입한다면 2번 자리에 저장하고 싶을 것이니까 아까 저장한 15위에 2를 붙이면 된다.

34를 지우고 싶으면 8인덱스에 와서 요소를 지운다. (34나누기 13의 나머지는 8이니까!)

HashTable의 장점은 요소들을 쉽게 삽입할 수 있고, 검색과 제거 또한 훨씬 쉽게 할 수 있다.

HashTable은 매우 빠른 검색능력을 제공한다. 요소의 삽입은 때때로는 LinkedList보다 느릴 수 있지만, 배열에 비해서는 훨씬 빠르다.
HashTable의 효율성은 언제나 해싱 함수의 효율성에 기반한다.(위에 예시로 든 건 그냥 설명을 위해서이지 해싱함수마다 다르다.)

Java에서는 해싱함수를 해시코드란 것을 이용하여 구현한다. 객체 클래스를 보면, hashcode()라는 메서드가 있다.

hashcode는 어느 양동이에 객체가 저장되는지를 결정하는데 사용된다. 위에서 얘기하였던 나머지를 이용해서 저장하는 것은 그냥 예시일 뿐이다. 해싱 함수들은 hashcode()를 이용해서 java에서 구현할 수
있다.

Map<String, Integer> map = Map.of("A", 3, "B", 5, "z", 10);
// map.put("R", 1); of를 써서 삽입할 수 없음
System.out.println(map.get("z"));
System.out.println(map.size());
System.out.println(map.containsKey("A"));
System.out.println(map.containsValue(3));
System.out.println(map.keySet());
System.out.println(map.values());

/*
10
3
true
true
[z, A, B]
[10, 3, 5]
*/
Map<String, Integer> hashmap = new HashMap<>(map);
hashmap.put("F", 5);
System.out.println(hashmap);
hashmap.put("z", 11);
System.out.println(hashmap);

/*
{A=3, z=10, B=5, F=5}
{A=3, z=11, B=5, F=5}
*/

HashMap, LinkedHashMap, TreeMap 서로 비교

Map<String, Integer> hashmap = new HashMap<>();
hashmap.put("Z", 5);
hashmap.put("A", 15);
hashmap.put("F", 25);
hashmap.put("L", 250);
System.out.println("hashmap = " + hashmap); // hashmap = {A=15, F=25, Z=5, L=250} -> 순서 맘대로

LinkedHashMap<String, Integer> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("Z", 5);
linkedHashMap.put("A", 15);
linkedHashMap.put("F", 25);
linkedHashMap.put("L", 250);
System.out.println("linkedHashMap = " + linkedHashMap); // linkedHashMap = {Z=5, A=15, F=25, L=250} -> 순서 유지

TreeMap<String, Integer> treeMap = new TreeMap<>();
treeMap.put("Z", 5);
treeMap.put("A", 15);
treeMap.put("F", 25);
treeMap.put("L", 250);
System.out.println("treeMap = " + treeMap); // treeMap = {A=15, F=25, L=250, Z=5} -> 정렬해서 가짐

어떤 문장이 주어졌을 때 나오는 Character와 단어 수 구하기

String temp = "This is a great thing! This has never happened before.";
Map<Character, Integer> occurances = new HashMap<>();

char[] chars = temp.toCharArray();

for (char character : chars) {
    Integer integer = occurances.get(character);
    if (integer == null) {
        occurances.put(character, 1);
    }else{
        occurances.put(character, integer + 1);
    }
}
System.out.println("occurances = " + occurances);

// occurances = { =9, a=4, !=1, b=1, d=1, e=7, f=1, g=2, h=5, i=4, n=3, .=1, o=1, p=2, r=3, s=4, T=2, t=2, v=1}
String temp = "This is a great thing! This ahs never happened before.";
Map<String, Integer> stringOccurances = new HashMap<>();
String[] words = temp.split(" ");

char[] chars = temp.toCharArray();

for (String word : words) {
    Integer integer = stringOccurances.get(word);
    if (integer == null) {
        stringOccurances.put(word, 1);
    }else{
        stringOccurances.put(word, integer + 1);
    }
}
System.out.println("stringOccurances = " + stringOccurances);

// stringOccurances = {a=1, never=1, before.=1, This=2, is=1, great=1, ahs=1, thing!=1, happened=1}


정리

Hash → 이걸 보게 되면 순서도 없고 정렬도 되어있지 않다.

Linked → 요소들이 서로 연결되어 있다. 순서는 확실히 유지된다. 정렬된 방식으로 저장하지는 않는다.

Tree→ 데이터가 트리 구조에 정렬된 상태로 저장된다. 기본적으로 정렬되어 있기 때문에 여러 메서드 사용할 수 있다.

728x90
반응형

'백엔드 > Java' 카테고리의 다른 글

[Java] 추상클래스 vs 인터페이스  (0) 2023.03.23
oop의 특징 (캡슐화, 상속, 추상화, 다형성)  (0) 2023.03.17
Array, ArrayList  (0) 2023.03.17
Java 실행 원리와 JVM  (0) 2022.11.13
static keyword 정적 키워드  (0) 2022.11.06
728x90
반응형

추상클래스

추상 클래스는 공통되는 부분을 모아서 추상 클래스에 정의하고, 그 외의 부분을 자식 클래스에서 확장하여 사용하는 개념으로 보면 된다.

예를 들어서, 내가 조교를 하면서 교수님께 메일을 보내는 경우이다.

나는 항상 “교수님, 안녕하십니까?”를 메일 제일 처음에 붙이고, 제일 마지막에 “항상 좋은 강의 감사합니다.” 를 붙인다. 그리고 중간의 내용은 언제나 바뀔 수 있다. 중간의 내용은 추상 메서드로 만든다.

public abstract void 교수님께메일 {
    public void 머릿말(){
        System.out.println("교수님, 안녕하십니까?");
    }
    public abstract void 내용();
    public void 맺음말(){
        System.out.println("항상 좋은 강의 감사합니다.");
    }
}

그럼 ‘교수님께 메일’ 이라는 abstract class를 만들고, ‘추가 수강 신청’과 “강의 질문” class는 abstract를 구현한 클래스이다.

public class 추가_수강_신청 extends 교수님께메일{
    @Override
    public void 내용() {
        System.out.println("혹시 이번 강의 추가신청 가능할까요?");
    }
}

public class 강의_질문 extends 교수님께메일{
    @Override
    public void 내용() {
        System.out.println("혹시 이번 강의 질문드려도 될까요?");
    }
}

그럼 이런 클래스를 활용하려면 객체를 만들어서 ‘머릿말’, ‘내용’, ‘맺음말’을 사용하면 된다.

추가_수강_신청 pm = new 추가_수강_신청();
pm.머릿말();
pm.내용();
pm.맺음말();

강의_질문 pm2 = new 강의_질문();
pm2.머릿말();
pm2.내용();
pm2.맺음말();

추상 클래스는 위의 예에서 보듯이, 필수적으로 구현해야 하는 기능을 공통적으로 묶고, 만약 그 안에 구현해야 되는 내용이 있고 내용이 조금 다르다면, 해당 내용을 추상 메서드로 선언하여 구현하면 된다.

인터페이스

그럼 인터페이스는 언제 사용할까? 대상이 만약 교수님이 아니라, 다른 모든 사람들에게 보낸다고 생각했을때, 그때도 머릿말, 내용, 맺음말은 필요하다. 그러나 해당 대상은 교수님이 아니라 형식만 interface로 선언해주고, 구현 내용은 다르게 해주는 것이다.

public interface 메일형식 {
    void 머릿말();
    void 내용();
    void 맺음말();
}
public class 친구에게 implements 메일형식{
    @Override
    public void 머릿말(){
        System.out.println("ㅎㅇ");
    }
    @Override
    public void 내용(){
        System.out.println("낼 머함");
    }
    @Override
    public void 맺음말(){
        System.out.println("");
    }

}

public class 교수님에게 implements 메일형식{
    @Override
    public void 머릿말(){
        System.out.println("교수님 안녕하십니까?");
    }
    @Override
    public void 내용(){
        System.out.println("낼 미팅 날짜 잡아도 되겠습니까?");
    }
    @Override
    public void 맺음말(){
        System.out.println("감사합니다.");
    }

}

메일은 동일한 목적을 가지는 머릿말, 내용, 맺음말이 동작이 준비되지만 저마다의 방식으로 가능하다.

또한, 동물 예시도 들고왔다.

public abstract class Animal {
    public abstract void eat();
    public void sleep() {
            // 일반 메소드 동작 정의
    }
}

그런 다음에 이 Animal 클래스를 확장하여 개, 고양이, 물고기 등으로 확장하면, 각 동물들이 먹는 기능을 이렇게 따로 정의할 수 있다.

public class Dog extends Animal {
    public void eat() {
        System.out.println("개밥");
    }
}

public class Cat extends Animal {
    public void eat() {
        System.out.println("고양이밥");
    }
}

public class Fish extends Animal {
    public void eat() {
        System.out.println("물고기밥");
    }
}

기본적인 구현은 Animal 에서, 고유의 동작은 Dog, Cat, Fish 에서 정의된다.

이번에는 같은 동물인데 인터페이스를 활용할 수 있다. 간단히 소리를 내는 하나의 추상 메소드만 정의하였다.

public interface Audible {
    void makeSound();
}

그런 다음에 각 동물에 대해 소리를 내는 기능을 추가한 뒤 makeSound() 메소드를 정의하였다.

public class Dog extends Animal implements Audible {
    ...

    public void makeSound() {
        System.out.println("멍");
    }
}

public class Cat extends Animal implements Audible {
    ...

    public void makeSound() {
        System.out.println("냐옹");
    }
}

그러면 강아지와 개는 소리를 내는 기능이 더해졌고 각 클래스 내에서 어떤 소리를 내는지 정의하였습니다.

그런데 물고기는 소리를 따로 내지 않는다. 별도의 인터페이스를 만들어서 활용해 보겠다.

public interface SilentAnimal {

}

그리고 Fish 클래스에 이를 구현하도록 해볼게요.

public class Fish extends Animal implements SilentAnimal{
    public void eat() {
        System.out.println("사료");
    }
}
Animal[] animals = new Animal[3];
animals[0] = new Dog();
animals[1] = new Cat();
animals[2] = new Fish();

for (Animal animal : animals) {
    if (animal instanceof Audible) {
        ((Audible) animal).makeSound();// 개, 고양이
    } else if (animal instanceof SilentAnimal){
        System.out.println("소리를 내지 않는 동물");// 물고기
    }
}

[정리]

  • 추상클래스는 다중 상속이 안되지만, 인터페이스는 다중 상속이 가능하다.
  • 추상 클래스는 객체로 생성될 수 없는 클래스로 자식 클래스에서 확장될 수 있도록 만들어진 클래스이다.
  • 추상 클래스는 일반 메서드와 추상 메서드 모두 가질 수 있어서 기본적인 구현을 추상 클래스에서 하고, 하위 클래스에서는 고유의 동작을 확장하기 위해 사용한다.
  • 인터페이스는 클래스는 반드시 메소드들의 동작을 정의해야 하며, 해당 클래스는 동일한 사용 방법과 동작을 보장할 수 있다.
  • 인터페이스를 통해 다형성이 가능해진다.
728x90
반응형

'백엔드 > Java' 카테고리의 다른 글

[Java] Java Collection 한번에 정리하기  (0) 2023.04.03
oop의 특징 (캡슐화, 상속, 추상화, 다형성)  (0) 2023.03.17
Array, ArrayList  (0) 2023.03.17
Java 실행 원리와 JVM  (0) 2022.11.13
static keyword 정적 키워드  (0) 2022.11.06
728x90
반응형

개념적인 이야기보다 제가 놓치기 쉬운 것들을 기록하였습니다.

상속

  • java의 모든 클래스는 Object 클래스를 상속받는다.
  • Object 클래스의 메서드 오버라이딩
    • toString()
  • super : 상위 클래스의 값을 가져올 수 있도록 도와준다.
  • 다중 상속 : C++은 다중 상속이 되지만, 자바에서는 허용하지 않는다. (다중 상속을 하면 프로그램이 복잡해져서 그런가..?)
    • 그래서 차례대로 하나씩 상속시키면 된다. (상속 계층)
    • 하위 클래스 변수를 담을 수 있는 상위 클래스 참조 변수를 만들 수 있다! → 다형성 핵심 ( 부모는 자식을 언제나 담을 수 있다 )
  • instanceof : 현재 객체가 상위 클래스의 인스턴스인지 확인할 수 있다. 맞으면 true를 리턴해줌. (하위 클래스) instanceof (상위 클래스) → true

추상화

  • 추상 메소드를 정의하려면, 추상 클래스를 정의해야 한다.
  • 하위 클래스들이 추상 메소드의 사용방법을 적용한다. → 추상클래스를 상속해서 구상 클래스 만듬.
  • 추상클래스는 새로운 인스턴스를 만들 수 없다.
  • 추상클래스 안에는 비추상적 메소드도 가질 수 있다.

다형성

인터페이스

  • 하나에 여러 개의 적용법이 있을 수 있다. —> 다형성의 핵심 개념. 같은 것에 여러 가지 구현을 부여할 수 있다.
  • 인터페이스는 공통적인 시행 가능 행동들을 대표하는 것이다. 공통 행동을 클래스에게 전달하는 역할.
  • 특정 클래스가 확실히 구현할 메소드들이 무엇인지, 시스템 안의 모든 다른 클래스들은 그 특정 클래스가 모든 메소드들을 담을 것을 기대한다.
  • 인터페이스는 또다른 인터페이스를 상속할 수 있다.
  • 인터페이스 안에서는 변수가 아니라 상수만 선언할 수 있다.

인터페이스 vs 추상화

  • 별다른 관계는 없다. 문법이 비슷해보일 뿐이다.
  • 인터페이스는 언제 사용하는 것일까? : 두 시스템 사이에 소통하길 원하거나 소통 방식을 정하고 싶을때. → implements
  • 추상클래스는 높은 단계의 구조를 제공하고 싶어 할 때 사용. 구현의 세세한 부분들은 하위 클래스에 맡기고 싶을 때. → 상속 extends
  • 인터페이스 안에는 어떤 메소드도 private으로 선언할 수 없다. 모든 것은 public
  • 인터페이스 안에는 변수들을 넣을 수 없다. 모든 것은 상수.
  • 한 클래스는 여러 인터페이스들은 구현할 수 있지만, 여러 추상적 클래스들을 상속할 수는 없다.

다형성

  • 다형성은 인터페이스에 적용되는 만큼 상속 개념에도 적용된다.
728x90
반응형

'백엔드 > Java' 카테고리의 다른 글

[Java] Java Collection 한번에 정리하기  (0) 2023.04.03
[Java] 추상클래스 vs 인터페이스  (0) 2023.03.23
Array, ArrayList  (0) 2023.03.17
Java 실행 원리와 JVM  (0) 2022.11.13
static keyword 정적 키워드  (0) 2022.11.06
728x90
반응형

Array

  • 선언은 2가지 방식으로 할 수 있다.
int[] marks = {1,2,3,4,5};
int[] marks = new int[5];
  • java의 배열의 length는 속성이지, 메서드가 아니다.
marks.length
  • 배열의 요소들을 출력하고 싶다면, for문을 사용해서 인덱스마다 출력해도 되고, Arrays.toString(배열이름); 을 사용해도 된다. (배열의 콘텐츠 불러오기)
System.out.println("Arrays.toString(persons) = " + Arrays.toString(persons));
// Arrays.toString(marks) = [1, 2, 3, 4, 5]
  • 배열의 모든 값을 한번에 바꾸는 방법도 있다. Arrays.fill(배열이름, 바꾸고 싶은 값);
int[] marks2 = {100, 99, 95, 96, 100};
for (int mark : marks2) {
    System.out.println("mark = " + mark);
}

Arrays.fill(marks2, 100);
for (int mark : marks2) {
    System.out.println("mark = " + mark);
}
  • 두 배열이 같은 요소를 가지는지 확인할 수 있다. Arrays.equals(배열1, 배열2)
Arrays.equals(marks, marks2)
  • 배열 정렬 Arrays.sort(배열이름);
  • inline : 함수의 내용을 호출을 통해서 실행시키는 것이 아니라, 호출하는 코드 자체가 함수 내용의 코드가 된다.
int[] marks = {100, 90, 80, 20, 40};
Student student = new Student("김태헌", marks);
// inline
Student student = new Student("김태헌", new int[] {100, 90, 80, 20, 40});
  • 가변 변수를 인자로 입력받기 ( 배열의 크기가 다양할 때 사용하면 좋다 ) … 을 사용하면 된다. 가변 인수는 항상 메소드의 마지막에 와야 한다!
void print(int... values){
    System.out.println("Arrays.toString(values) = " + Arrays.toString(values));
}

student.print(1,2,3,4,5); // Arrays.toString(values) = [1, 2, 3, 4, 5]
student.print(1,2); // Arrays.toString(values) = [1, 2]

int sum(int... values) {
    int sum = 0;
    for (int value : values) {
        sum += value;
    }
    System.out.println("sum = " + sum);
    return sum;
}
student.sum(1,2,3,4,5,346,234,23,42,34,23,423); // sum = 1140
  • Array에 다른 요소 하나 추가하는 방법 → 배열 자체를 새로 만들어야 한다. (배열을 한 번 생성 했다면 이후에 요소의 개수를 변경할 방법은 없다)
  • Array에 요소를 지우고 싶은 경우에도 새로운 배열을 만들면 된다
  • 이 과정이 귀찮으므로 ArrayList에 있는 메서드를 사용하면 된다.

ArrayList

  • ArrayList는 다음과 같이 선언한다.
    add와 remove를 이용해서 추가하고 제거할 수 있다.
ArrayList arrayList = new ArrayList();
arrayList.add("Apple");
arrayList.add("Banana");
arrayList.add(1); // String과 Int가 섞여서 들어갈 수 있다. 그치만 같은 자료형이 들어가는 것이 좋다.
System.out.println("arrayList = " + arrayList);
arrayList.remove("Apple");
System.out.println("arrayList = " + arrayList);
  • ArrayList에는 다양한 자료형이 들어가도 되지만 같은 자료형이 들어가는 것이 좋으므로, 미리 자료형을 선언한다. 그럼 다른 자료형이 들어가면 오류가 난다.
ArrayList<String> items = new ArrayList<>(); // 제네릭
728x90
반응형
728x90
반응형

JVM은 다른 프로그램을 실행시키는 것이 목적이다. 두 가지 기능이 있다고 말할 수 있는데,

  1. 자바 프로그램이 어느 기기나 운영체제 상에서도 실행될 수 있도록 한다
    • 자바와 os 사이에서 중개자 역할을 수행하여 OS에 상관없이 재사용을 가능하게 해준다.
  2. 프로그램 메모리를 관리하고 최적화 한다

JVM은 코드를 실행하고, 해당 코드에 대해 런타임 환경을 제공하는 프로그램에 대한 사양이다.

JVM은 보통 어떤 기기상에서 실행되고 있는 프로세스, 특히 자바 앱에 대한 리소스를 대표하고 통제하는 서버를 지칭한다.

자바 애플리케이션을 클래스 로더를 통해 읽어들이고, 자바 API와 함께 실행하는 역할을 한다.

  1. 프로그램이 실행되면 JVM은 OS로부터 프로그램에 필요한 메모리를 할당받는다.
  2. javac(자바 컴파일러)가 자바 소스코드를 읽고, 자바 바이트코드(.class)로 변환한다.
  3. 변경된 class 파일들을 클래스 로더를 통해 JVM 메모리 영역으로 로딩한다.
  4. 로딩된 class 파일들은 Execution engine을 통해 해석된다.
  5. 해석된 바이트 코드는 메모리 영역에 배치되어 수행된다. 이 때 JVM은 쓰레드 동기화나 가비지 컬렉션 같은 메모리 관리 작업을 수행한다.

[클래스 로더] : JVM은 런타임 시에 처음으로 클래스를 참조할 때 해당 클래스를 로드하고 메모리 영역에 배치시키는데 이 동적 로드를 담당하는 부분이 바로 클래스 로더이다.

Runtime Data Access

: JVM이 운영체제 위에서 실행되면서 할당받는 메모리 영역이다.

PC 레지스터, JVM 스택, 네이티브 메서드 스택, 힙, 메서드 영역

  • PC 레지스터 : 스레드가 어떤 명령어로 실행되어야 할지 기록하는 부분 (JVM 명령의 주소를 가진다)
  • 스택 : 지역변수, 매개변수, 메서드 정보, 임시 데이터 등을 저장
  • 네이티브 메서드 스택 : 실제 실행할 수 있는 기계어로 작성된 프로그램을 실행시키는 영역
  • 힙 : 런타임에 동적으로 할당되는 데이터가 저장되는 영역. 객체나 배열 생성이 여기에 해당함. ( 힙에 할당된 데이터들이 가비지 컬렉터의 대상이 된다. JVM 성능에 영향을 미친다 )
  • 메서드 영역 : JVM이 시작될 때 생성되고, JVM이 읽은 각각의 클래스와 인터페이스에 대한 런타임 상수 풀, 필드 및 메서드 코드, 정적 변수, 메서드의 바이트 코드 등을 보관한다.

Garbage Collection

C나 C++은 개발자가 직접 메모리를 관리했지만 자바에서는 JVM이 프로그램 메모리를 관리한다. JVM은 Garbage Collection이라는 프로세스를 통해 메모리를 관리하는데 참조되지 않은 객체들을 탐색하여 삭제하고 삭제된 객체의 메모리를 반환하고 힙 메모리를 재사용하는 과정을 거친다.

 

 

https://gyoogle.dev/blog/computer-language/Java/Java 를 참조했습니다

728x90
반응형
728x90
반응형

static이란 ‘고정된’이란 의미를 가지고 있다. Static 키워드를 사용해서 Static 변수와 Static 메소드를 만들 수 있는데 다른 말로 ‘정적 필드’와 ‘정적 메소드’ 라고도 하며, 합쳐서 정적 멤버라고 한다.

static field와 static method는 객체에 소속된 멤버가 아니라, 클래스에 고정된 멤버이다. 이 의미는 클래스 로더가 클래스를 로딩해서 메소드 메모리 영역에 적재할 때, 클래스 별로 관리된다. 따라서 클래스의 로딩이 끝나는 즉시 바로 사용할 수 있다.

Static member 선언

객체(인스턴스)로 생성할 것인지, static으로 생성할 것인지의 결정 기준은 공용으로 사용할 것이냐 아니냐로 결정하면 된다. 그냥 생성하면 java에서는 객체로 생성된다. static으로 생성하려면 static 키워드를 추가적으로 붙여서 공용으로 사용할 수 있다.

static field 사용 예시

먼저 Friend 클래스를 만들어 보았다. Friend에서는 static 변수인 numberOfFriends를 두었다. 이렇게 두면, Main에서 Friend의 static field를 어떻게 사용할 수 있는지 확인해보자.

package com.company;

public class Friend {

    String name;
    static int numberOfFriends;
    Friend(String name){
        this.name = name;
    }
}
package com.company;

public class Main {

    public static void main(String[] args) {

        System.out.println(Friend.numberOfFriends);
    }
}

Friend객체를 새로 생성하지 않고서 바로 numberOfFriends를 Friend 클래스에서 호출할 수 있다.

그럼 객체를 만들 때, 친구의 수를 한 명씩 늘리고, 이 변수를 공유할 수 있도록 해보자.

package com.company;

public class Friend {

    String name;
    static int numberOfFriends;
    Friend(String name){
        this.name = name;
        numberOfFriends++;
    }
}

////Main
package com.company;

public class Main {

    public static void main(String[] args) {
        Friend friend1 = new Friend("Jake");
        Friend friend2 = new Friend("Heon");
        System.out.println(Friend.numberOfFriends);
    }
}

이렇게 하면 Friend의 numberOfFriend는 2가 출력되는 것을 확인할 수 있다.

우리가 별 생각없이 쓰던 Math.round()같은 함수도 static 함수이기때문에 사용할 수 있었던 것이다.

(Math 객체를 따로 만들지 않고 바로 Math.round()와 썼었다)

static 멤버의 특성

Static 키워드를 통해 생성된 정적멤버들은 Heap 영역이 아닌 Static 영역에 할당된다. Static 영역에 할당된 메모리는 모든 객체가 공유하여 하나의 멤버를 어디서든지 참조할 수 있는 장점을 가지지만 Garbage Collectior의 관리 영역 밖에 존재하기 때문에 Static영역에 있는 멤버들은 프로그램의 종료될때까지 메모리가 할당된 채로 존재하게 된다. 즉, 너무 남발하게 되면 문제가 생긴다.

  1. 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통적으로 사용해야하는 것에 static을 사용한다. : 객체를 생성하면 각 객체들은 서로 독립적이라서 서로 다른 값을 유지하기 때문에 공통적인 값을 필요로 하는 경우에 static을 붙인다.
  2. static이 붙은 멤버변수는 인스턴스를 생성하지 않아도 사용할 수 있다. : static이 붙은 멤버 변수는 클래스가 메모리에 올라갈 때 이미 자동적으로 static 영역에 생성된다.
  3. static이 붙은 메서드에서는 인스턴스 변수를 사용할 수 없다. : 인스턴스 변수는 인스턴스를 생성해야만 존재하기 때문에 static이 붙은 메서드를 호출할 때 인스턴스가 생성되어있음을 보장할 수 없기 때문에 static이 붙은 메서드에서 인스턴스 변수의 사용을 허용하지 않는다. (반대로 인스턴스 변수나 인스턴스 메서드에서는 static이 붙은 메서드를 사용할 수 있다.)
  4. 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다. : 메서드의 작업내용중에서 인스턴스 변수를 필요로 한다면 static을 붙일 수 없다. 반대로 인스턴스 변수를 필요로 하지 않는다면 가능하면 static을 붙이는 것이 좋다. 메서드 호출시간도 짧아져서 효율이 높아진다. (static을 안붙인 메서드는 실행 시 호출되어야할 메서드를 찾는 과정이 추가적으로 필요하다.)
  5. 인스턴스 변수와 관계없거나, 클래스변수만을 사용하는 메서드들은 클래스메서드(static method)로 정의한다.
728x90
반응형

+ Recent posts