|  컬렉션이란?

- 자바에서 자료구조를 구현한 클래스를 말한다.

- 컬렉션을 추상적으로 구분한 분류는 크게 아래와 같다.

- 단, 실제 Collection을 상속하는 건 List와 Set, Queue이며 Map은 별도의 인터페이스이다.

- Collections를 통해서 List를 정렬할 수 있으며,

  Set이나 Map의 경우 TreeSet과 TreeMap을 쓰면 data가 정렬되어 저장된다.

구분 하위 클래스 중복 순서
List ArrayList, LinkedList, Vector, Stack(Vector 상속) o o
Set HashSet, LinkedHashSet, TreeSet x -
Map Hashtable, HashMap, LinkedHashMap, TreeMap key x, value o -

 

|  List / Set / Map 코드

import java.util.*;

public class CollectionTest {
    public static void main(String[] args) {

        // Collections
        // ㄴ List, Set, Queue가 상속 / Map은 별도

        // 1. List
        // : 순서대로 데이터를 저장하기 위한 리스트 (중복o, 순서o)
        // : ArrayList, LinkedList, Vector, Stack(Vector 상속)

        // A. ArrayList
        // 순차적인 추가/삭제는 빠르나 중간에 데이터를 추가/삭제하게 되면 linkedList보다 느리다.
        ArrayList<Integer> arrayList = new ArrayList<Integer>();
        arrayList.add(4);
        arrayList.add(2);
        arrayList.add(3);
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(1);
        System.out.println("arrayList = " + arrayList);

        // B. LinkedList
        LinkedList<Integer> linkedList = new LinkedList<>();
        linkedList.add(4);
        linkedList.add(2);
        linkedList.add(3);
        linkedList.add(1);
        linkedList.add(2);
        linkedList.add(1);
        System.out.println("linkedList = " + linkedList);

        // 2. Set
        // : 중복을 제거하기 위해 사용한다. 수학의 집합과 연관되어 있다. (중복x)
        // : HashSet, LinkedHashSet, TreeSet

        // A. HashSet
        // - 항상 데이터가 정렬되지 않지만 대체로 정렬되어 나옴 (정렬x, 저장순서 유지x)
        System.out.print("hashSet : ");
        HashSet<Integer> hashSet = new HashSet<>();
        hashSet.add(4);
        hashSet.add(2);
        hashSet.add(3);
        hashSet.add(1);
        hashSet.add(2);
        hashSet.add(1);

        Iterator<Integer> hashSetIterator = hashSet.iterator();
        while (hashSetIterator.hasNext()) {
            System.out.print(hashSetIterator.next() + " ");
        }
        System.out.println();

        // B. LinkedHashSet : HashSet를 상속한다.
        // - 기존 데이터의 저장순서가 그대로 유지된다. (정렬x, 저장순서 유지O)
        System.out.print("linkedHashSet : ");
        LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>();
        linkedHashSet.add(4);
        linkedHashSet.add(2);
        linkedHashSet.add(3);
        linkedHashSet.add(1);
        linkedHashSet.add(2);
        linkedHashSet.add(1);

        Iterator<Integer> linkedHashSetIterator = linkedHashSet.iterator();
        while (linkedHashSetIterator.hasNext()) {
            System.out.print(linkedHashSetIterator.next() + " ");
        }
        System.out.println();

        // C. TreeSet
        // - 데이터가 정렬이 되어 출력된다. (정렬O, 저장순서 유지x)
        System.out.print("TreeSet : ");
        TreeSet<Integer> treeSet = new TreeSet<>();
        treeSet.add(4);
        treeSet.add(2);
        treeSet.add(3);
        treeSet.add(1);
        treeSet.add(2);
        treeSet.add(1);

        Iterator<Integer> treeSetIterator = treeSet.iterator();
        while (treeSetIterator.hasNext()) {
            System.out.print(treeSetIterator.next() + " ");
        }
        System.out.println();

        // 3. Map
        // : key 과 value가 entry 형태로 저장된다. (Entry가 Map의 내부 인터페이스다)
        // : Hashtable, HashMap, LinkedHashMap, TreeMap

        // A. HashMap
        // : HashTable과 HashMap은 사용면에서 비슷한데 차이점은 아래와 같다.
        // : Hashtable은 Thread-safe & key에 null미사용, HashMap은 Thread-safe X & key에 null사용
        // - 데이터가 정렬되지 않은 상태로 출력된다 (정렬x, 저장순서 유지x)
        System.out.println("=== HashMap === ");
        HashMap<String, Integer> hashMap = new HashMap<>();
        hashMap.put("고객3", 1000);
        hashMap.put("고객2", 2000);
        hashMap.put("고객1", 3000);
        hashMap.put("고객0", 4000);

        for (String key : hashMap.keySet()) {
            System.out.println(key + ":" + hashMap.get(key));
        }

        // B. LinkedHashMap
        // - key 데이터의 저장 순서가 유지된다. (정렬x, 저장순서 유지o)
        System.out.println("=== LinkedHashMap === ");
        LinkedHashMap<String, Integer> linkedHashMap = new LinkedHashMap<>();
        linkedHashMap.put("고객3", 1000);
        linkedHashMap.put("고객2", 2000);
        linkedHashMap.put("고객1", 3000);
        linkedHashMap.put("고객0", 4000);

        for (String key : linkedHashMap.keySet()) {
            System.out.println(key + ":" + linkedHashMap.get(key));
        }

        // C. TreeMap
        // - key 데이터가 정렬된다 (정렬o, 저장순서 유지x)
        System.out.println("=== TreeMap === ");
        TreeMap<String, Integer> treeMap = new TreeMap<>();
        treeMap.put("고객0", 1000);
        treeMap.put("고객1", 2000);
        treeMap.put("고객2", 3000);
        treeMap.put("고객3", 4000);

        for (String key : treeMap.keySet()) {
            System.out.println(key + ":" + treeMap.get(key));
        }

        // - Tip : TreeMap의 key 데이터를 내림차순으로 저장하려면?
        System.out.println("=== TreeMap Reverse === ");
        treeMap = new TreeMap(Comparator.reverseOrder()); // new TreeMap(Comparator)
        treeMap.put("고객0", 1000);
        treeMap.put("고객1", 2000);
        treeMap.put("고객2", 3000);
        treeMap.put("고객3", 4000);

        for (String key : treeMap.keySet()) {
            System.out.println(key + ":" + treeMap.get(key));
        }
    }
}

>> 결과

arrayList = [4, 2, 3, 1, 2, 1]
linkedList = [4, 2, 3, 1, 2, 1]
hashSet : 1 2 3 4 
linkedHashSet : 4 2 3 1 
TreeSet : 1 2 3 4 
=== HashMap === 
고객2:2000
고객1:3000
고객3:1000
고객0:4000
=== LinkedHashMap === 
고객3:1000
고객2:2000
고객1:3000
고객0:4000
=== TreeMap === 
고객0:1000
고객1:2000
고객2:3000
고객3:4000
=== TreeMap Reverse === 
고객3:4000
고객2:3000
고객1:2000
고객0:1000

 

|  Collections.sort

import java.util.*;

class Customer implements Comparable<Customer>{
    private int no;
    private String name;

    public Customer(int no, String name) {
        this.no = no;
        this.name = name;
    }

    @Override
    public int compareTo(Customer o) {
        return this.no - o.no;
    }

    public void print() {
        System.out.println(this.no + "=" + this.name);
    }
}

public class CollectionsSortTest {
    public static void main(String[] args) {
        // Collection에서 Sort를 하는 방식
        
        // A. List를 정렬할 경우 - Collections.sort(List);
        // : 오직 List의 경우에만 Collections.sort()를 쓸 수 있다.
        // : Map의 Set의 경우 TreeMap, TreeSet을 쓰는 것이 좋다.
        List<Integer> list = new ArrayList<>(Arrays.asList(4, 2, 5, 1, 2, 7));
        Collections.sort(list);
        System.out.println("list = " + list);
        
        // B. 객체를 정렬할 경우 - 객체 내에서 Comparable인터페이스 구현
        // (사실 아래의 경우에는 ArrayList를 쓰고 sort를 하기보다 PriorityQueue를 쓰는게 더 효율적이다)
        ArrayList<Customer> customers = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            int no = (int)(Math.random() * 9999);
            String name = "이름없음" + no;
            customers.add(new Customer(no, name));
        }

        Collections.sort(customers);

        for (Customer one : customers) {
            one.print();
        }
        
    }
}

>> 결과

list = [1, 2, 2, 4, 5, 7]
1606=이름없음1606
2047=이름없음2047
4702=이름없음4702
6804=이름없음6804
9098=이름없음9098

 

|  자바 컬렉션들의 시간 복잡도

1. List

  add() remove() get() contains()
ArrayList O(1) or O(n) O(1) or O(n) O(1) O(n)
LinkedList O(1) O(1) O(n) O(n)

2. Set

  add() contains() next()
HashSet O(1) O(1) O(h/n)
LinkedHashSet O(1) O(1) O(1)
TreeSet O(log n) O(log n) O(log n)

3. Map

  add() containsKey() next()
HashMap O(1) O(1) O(h/n)
LinkedHashMap O(1) O(1) O(1)
TreeMap O(1) O(1) O(h/n)

4. Queue

  offer() peak() poll() size()
LinkedList O(1) O(1) O(1) O(1)
ArrayDequeue O(1) O(1) O(1) O(1)
PriorityQueue O(log n) O(1) O(log n) O(1)

 

 

 

 

[ 참고 및 출처 ]

부트 캠프 강의를 듣고 정리한 내용입니다.

자바 컬렉션의 시간 복잡도

https://hbase.tistory.com/185

https://velog.io/@sojukang/Map-%EB%82%B4%EB%A6%BC%EC%B0%A8%EC%88%9C%EC%9C%BC%EB%A1%9C-%EC%A0%95%EB%A0%AC-%EC%A0%80%EC%9E%A5

|  제네릭이란?

[사전] Generic = 일반적이다
[정의] 하나의 코드로 여러 타입을 동시에 처리하는 기술

1.  타입 변수 표기법

- 제네릭에 지정하는 타입 변수 이름은 지정하는 사람의 마음이지만,

  적어도 그 타입 이름을 사용하는 클래스 또는 인터페이스 내에서는 통일성있게 사용하게 한다.

- 일반적으로 변수명과의 구분을 위해 한 개의 대문자로 표시하는 것이 관례이다.

- 타입 매개 변수는 기초 자료형으로 객체화될 수 없다.

 

T Type
E Element(요소)
N Number
K Key
V Value
S,U,V.....  

 

2. 제네릭 클래스

- 최상위 클래스 Object를 사용하면 어떤 타입의 인스턴스건 참조할 수 있다. (업캐스팅, 다형성)

- 이러한 원리를 이용해서 클래스를 작성할때 데이터 타입을 Object로 하면 여러 타입의 데이터를 담을 수 있게 된다.

public class Box {
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

}

- 다만, 이럴 경우, 아래와 같이 객체 안의 데이터를 가져올 때 형변환이 불가피하게 된다.

import LibraryPractice.MyClass.Box;

public class GenericTest {
    public static void main(String[] args) {
        Box box = new Box();
        box.setData(123);
        int x = (Integer)box.getData(); //cast(형변환)필요
    }
}

- 이럴 때 제네릭을 사용하여 미리 타입을 지정한다면 사용시의 형변환의 번거러움을 줄일 수 있게 된다.

public class Box<T> {
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

}
import LibraryPractice.MyClass.Box;

public class GenericTest {
    public static void main(String[] args) {
        Box<Integer> box = new Box<>();
        box.setData(123);
        int x2 = box.getData(); // 형변환 필요 없음
    }
}

- 만약에 여러 개의 맴버변수에 서로다른 제네릭을 지정해야하는 경우(ex. key와 value가 타입이 다른 경우), 아래와 같이 작성할 수 있다.

public class Box3<K, V> {
    private K key;
    private V value;

    public void setData(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey(V key) {
        return this.key;
    }

    public V getValue(K key) {
        return this.value;
    }

}

 

3. 제네릭 메소드

- 제네릭 메소드의 경우 아래와 같이 반환타입 앞에 <E> 와 같은 형식으로 작성을 해준다.

public class Basket<T> {

    T data;

    public Basket(T data){
        this.data = data;
    }

    // 외부 배열 print
    public <E> void print(E[] arr){
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
    }

}

- 만약에 타입 변수를 특정 타입(예를 들어 String)으로 제한하고 싶은 경우, 아래와 같이 사용할 수 있다.

public <E extends String> 반환타입 method(E item){
	/* 생략 */
}

 

[ 제네릭 메소드로 만든 코드 ]

import java.util.ArrayList;
import java.util.Arrays;

public class Basket2<T> {
    private ArrayList<T> basket;

    public Basket2(T[] arr) {
        this.basket = new ArrayList<T>(Arrays.asList(arr));
    }

    public <E extends String> void printWithOwner(E str) { // E를 String으로 제한
        if (str.length() == 0) {
            System.out.println("please type Owner Name");
            return;
        }

        StringBuffer sb = new StringBuffer();
        sb.append(str + " : ");
        for (int i = 0; i < basket.size(); i++) {
            if (i != basket.size() - 1) {
                sb.append(basket.get(i) + ", ");
            }else{
                sb.append(basket.get(i));
            }
        }
        System.out.println(sb);
    }

}
import LibraryPractice.MyClass.Basket2;

public class GenericTest2 {
    public static void main(String[] args) {

        String owner = "sir.Tim Berners-Lee";
        String[] fruit = {"apple", "orange", "dragon fruit"};

        Basket2<String> basket = new Basket2<>(fruit);
        basket.printWithOwner(owner);

    }
}

>> 결과

sir.Tim Berners-Lee : apple, orange, dragon fruit

|  날짜, 시간 관련 클래스

1. 날짜 데이터는 언제 쓸까?

- 날짜 및 시간에 관한 데이터는 아래의 예시와 같이 다양한 웹 or 앱 서비스에서 사용하고 있다. 

- 일반적인 회원가입일자 
- 쇼핑몰의 주문일자
- 그룹웨어 회의일자 및 시간/장소대여일자 
- 모바일앱 특정시간에 알람 
- 등등

2. Java의 날짜 및 시간 관련 클래스

- 날짜 객체는 JDK업데이트 과정에서 Data -> Calender -> LocalDate로 발전하였다.

(1) Date today = new Date();  와 같이 Date 객체만을 사용해서 날짜를 표현

(2) Date today = Calendar.getInstance().getTime(); - Calendar를 통해 싱글톤 방식으로 인스턴스 생성하여 사용

(3) LocalDate today = LocalDate.now(); - Calendar를 보완한 LocalDate를 사용

- JDK8 이후부터는 공식적으로 Calendar는 deprecated(더 이상 사용하지 않는) 객체로 지정되었지만 

  국내의 현실 개발 환경에서는 아직까지 Calendar를 사용하는 경우도 많으며, Date객체만 사용하는 경우도 더러 있다고 한다.

  JAVA 8 이전 (deprecated) JAVA 8 이후
날짜 클래스 java.util의 Data와 Calendar java.time의 LocalDate/ LocalTime/ LocalDateTime
포맷 클래스 SimpleDataFormat DateTimeFormatter
*TimeStamp.of()를 쓰기도 한다.
제점/보 Thread-safe X
0부터 시작하는 월 지정 인덱스
Data, Calendar 클래스 사용의 혼란 
Thread-safe O
월이 1, 2, 3...으로 시작한다

isBefore(), isAfter()와 같은 메소드 추가

 

|  Date와 Calendar  사용

- Calendar의 단점 중에 하나가 월을 0부터 표현한다는 것이었다.

- 아래 코드를 보면 알 수 있겠지만, 연도나 일은 그럼에도 1부터 시작하는 걸 볼 수 있다.

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class CalendarTest {
    public static void main(String[] args) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd E요일 HH:mm:ss:SSS");

        Date today = Calendar.getInstance().getTime();
        System.out.println(simpleDateFormat.format(today));

        Calendar calendar = Calendar.getInstance();
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);
        int day = calendar.get(Calendar.DATE);
        System.out.printf("%d년 %d월 %d일\n", year, month, day); // 월이 0부터 시작
        month += 1;
        System.out.printf("%d년 %d월 %d일\n", year, month, day); // 1을 더해주어야 한다.

    }
}

>> 결과

2022-08-10 수요일 00:10:32:924
2022년 7월 10일
2022년 8월 10일

 

|  LocalDate / LocalTime / LocalDateTime 사용

- 간단하게 생성하는 법, 연/월/일을 얻는 법을 작성해보았다. 

더보기

|  LocalDate를 활용해 달력 출력하기

- 먼저 구하려는 날짜의 LocalDate를 생성하고, lenthOfMonth()로 그 달의 일수를 구한다.

// 해당 달의 일수 구하기 : lengthOfMonth()
LocalDate date = LocalDate.of(2012,12,1);
int lengthOfMonth = date.lengthOfMonth();

- 해당 월의 1일자가 몇 요일인지, getDayOfWeek().getValue()로 구한다.

int dayOfWeek = date.getDayOfWeek().getValue(); //월화수목.. = 1234..

- 주차를 구하는 방법은 임의로 만들었는데, (전체월 일수 + (7 - 1일자 요일번호))를 7로 나누어 반올림하였다.

int weeksOfMonth = Math.round((lengthOfMonth + (7 - dayOfWeek))/7f);

- 이 세가지를 구한 후, 2차원 배열을 만들고, 1일자의 시작 요일에 맞춰 입력 후, 출력은 이중 for문으로 했다.

- 전체 코드

- 출력 결과  *한글와 숫자깨짐은 무시한 결과다.

[달력 출력 프로그램]
달력의 년도를 입력해 주세요.(yyyy):2022
달력의 월을 입력해 주세요.(mm):7
[2022년 07월]
일 월 화 수 목 금 토  
               01 02 
03 04 05 06 07 08 09 
10 11 12 13 14 15 16 
17 18 19 20 21 22 23 
24 25 26 27 28 29 30

 

|  Date -> LocalDateTime 또는 LoalDateTime -> Date로 변환 

- JDK8부터 Date객체에 from() 메소드가 추가되어 LocalDateTime -> Date로 변환이 가능해졌다.

- Date -> LocalDateTime

LocalDateTime.ofInstant(date.toInstant(), /* ZoneId */);

- LocalDateTime -> Date

Date.from(local.atZone(/*ZoneId*/).toInstant())

 

- atZone()을 넣는 것은 시간대를 구분하기 위해서 넣는 것이다. (아래는 컴퓨터의 시간대 적용)

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.Date;

public class LocalDateTest {
    public static void main(String[] args) {

        // A. Date와 LocalDateTime으로 현재시각 구하기
        // (1) Date 타입의 날짜
        Date date = Calendar.getInstance().getTime();
        // (2) LocalDateTime 타입의 날짜
        LocalDateTime localDateTime = LocalDateTime.now();

        // B. LocalDateTime -> Date
        Date localToDate = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());

        // C. Date -> LocalDateTime
        LocalDateTime dateToLocal = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());

    }
}

 

 

[ 참고 및 출처 ]

- 부트 캠프 강의를 듣고 정리한 내용입니다.

- 날짜 타입 변환 관련 :  https://recordsoflife.tistory.com/336

- [기본기를 쌓는 정아마추어 코딩블로그:티스토리], https://jeong-pro.tistory.com/163 

- [슬기로운 개발생활:티스토리] https://dev-coco.tistory.com/31

- [삼바:티스토리], https://samba-java.tistory.com/18

|  Java.lang 패키지

- 가장 기본이 되는 클래스를 담고 있으며, import 없이 사용 가능하다.

- Oracle에서 제공하는 java.lang 패키지에 대한 상세 정보는 아래 링크에 (JDK8 기준)

   https://docs.oracle.com/javase/8/docs/api/java/lang/package-summary.html

- 아래 표는 위 링크에서 소개하는 java.lang의 인터페이스와 클래스 중 자주 사용할만한 것만 일부를 가져온 표이다.

Interface Class
Comparable Object
Cloneable Math
Runnable Process
Readable Thread
Thread.UncaughtExceptionHandler String
Iterable System
... Character, Integer, Long....

 

|  Object

메소드 내용
clone 현재 객체를 복사한 객체를 반환
equals 참조되는 주소값을 비교 (* 주의 : null값은 비교할 수 없다)
getClass 현재 객체타입의 런타임 객체를 반환
hashCode 객체의 해쉬코드를 반환
toString java.lang.Object@해쉬코드를 반환
오버라이딩하여 주로 사용

 

|  String

1. String은 힙에 저장되며, 두 가지 방식으로 저장된다.

종류 저장 영역
문자열 리터럴 Heap 영역 내 "String Constant Pool"에 생성
문자열 객체 Heap 영역 내 별도의 객체로 생성
// s1과 s2는 같은 곳을 가리킨다.
String s1 = "Spring";
String s2 = "Spring";

// s3와 s4는 다른 곳을 가리킨다.
String s3 = new String("Spring");
String s4 = new String("Spring");

 

2. String 메소드들

contains trim compareTo toString
indexOf
lastIndexOf
substring equals
equalsIgnoreCase
toLowerCase
toUpperCase
startsWith
endsWith
split valueOf toCharArray
length charAt replace
replaceAll
concat *

* concat : 문자열을 덧붙이는 메소드로 +를 사용하는 것과 동일하다.(자바에서는 잘 사용하지 않는다)

  pf. mariaDB, mysql에서 문자열을 조합할 때 concat('a', 'b', 'c')를 사용한다. (Oracle은 ||를 통해 문자열을 조합)

 

3. 자바는 기본적으로 UTF-16 인코딩 방식을 다른다.

더보기

※ EUC-KR과 UTF-8방식에 대하여 

> 확장된 아스키 코드와 유니코드의 역사
유니코드 이전에는 각 나라별로 확장된 아스키코드(Extended Ascii)를 사용했다.(아스키 코드의 문자는 1byte)

- 나라별로 인코딩 방식이 상이하다보니 전 세계의 데이터가 모두 다르게 읽히는 문제가 발생했다.
- 이를 해결하고자 유니코드를 만들었다. (유니코드의 문자는 2byte)
- 하지만 유니코드는 영문자의 경우 사용하는 메모리 공간이 1byte -> 2byte가 된다는 단점이 있어,
  영어권 같은 나라에서는 유니코드를 사용하는 것이 큰 메리트가 되지 않았다.
- 그래서 나온 것이 UTF-8. UTF-8은 가변 길이를 제공하여, 각 나라의 언어별로 최적화된 문자 크기를 할당한다.
  ex. 영문자의 경우 1byte, 한글의 경우 3byte

> EUC-KR과 UTF-8. 
- EUC-KR은 유니코드가 나오기 이전에 사용하던 한글 인코딩 방식(Extended Ascii)이다.
  * 자바의 KSC5601기법은 곧 EUC-KR 인코딩을 말하며, 윈도우OS에서 MS949와 동일한 기법을 사용한다.
  * 윈도우의 기본 인코딩 방식은 MS949다. 이클립스를 키면 늘 이 인코딩이 디폴트로 설정되어 있어 UTF-8로 바꾸는게 필요

- EUC-KR을 사용하면 온전한 한글을 만들기가 어렵다. (ex.'뷁' 이라는 문자가 없음)
- 당연한 얘기지만, 인코딩 방식이 EUC-KR일 때, 디코딩을 UTF-8로 하면 문자가 깨지게 된다. (제대로 인식을 못함)
- 그렇기에 항상 에디터를 쓸 때 사전에 인코딩 방식을 UTF-8로 설정을 해두는 것이다.

 

4. 자바에서 인코딩을 변환하는 메소드 getBytes()

import java.nio.charset.Charset;

public class StringTest {
    public static void main(String[] args) {
        String sample = "행복한개발자가되자"; // 설정에서 UTF-8 방식으로 바꾼 상태

        try {
            System.out.println("Default Encoding : " + Charset.defaultCharset());
            byte[] default_encoding = sample.getBytes();
            byte[] euc_kr = sample.getBytes("EUC-KR");
            System.out.println(">> default decoding : " + new String(default_encoding));
            System.out.println(">> EUC-KR decoding : " + new String(euc_kr));
        } catch (Exception e) {
            System.out.println("Encoding Error");
            System.out.println(e.getMessage());
        }
    }
}

>> 결과

Default Encoding : UTF-8
>> default decoding : 행복한개발자가되자
>> EUC-KR decoding : �ູ�Ѱ����ڰ�����

 

|  StringBuffer

1. 문자열 불변 법칙

String 불변의 법칙이란?
- 문자열 할당 후 수정이 있을 때, 기존 메모리에서 데이터를 수정하지 않고,
- 새로운 메모리 공간을 할당하여 그곳의 주소를 가리키도록 하는 방식

>> 문자열의 변경(추가/삭제/수정)이 빈번한 경우, 단순 덧셈 시 시간/공간복잡도에 영향을 준다.

- 앞서 문자열 리터럴의 경우, 힙의 string constant pool에 저장되며,

  서로 다른 참조변수(ex. s1, s2)가 같은 메모리 공간을 참조한다고 했다.

- 문자열에 수정이 있어날 때마다 메모리 공간을 재설정해주지 않으면 s1에서 수정한게 s2에 영향을 미치기 때문에

  String에 수정사항이 발생할 때마다 매번 메모리를 재할당해준다. (문자열 불변 법칙)

- 문자열 불변 법칙의 특성 때문에 많은 문자열 데이터를 수정/삭제해야하는 경우, 단순 연산을 사용하면(ex. +) 시간복잡도/공간복잡도 면에서 효율성이 떨어질 수 있다.

- 그래서 사용하는 게, StringBuilder/StringBuffer다.

public class StringBufferTest {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer();
        sb.append("hello");
        sb.append(System.lineSeparator());
        sb.append("this is StringBuffer!");

        System.out.println(sb);
    }
}

>> 결과

hello
this is StringBuffer!

 

2. StringBuilder와 StringBuffer의 차이점

  StringBuilder StringBuffer
공통점 char[] 배열로 이루어진 버퍼로, String 추가/수정/삭제 연산시 시간/공간 사용을 절약

* 디폴트로 16의 사이즈를 가지며 추가적으로 늘릴 수 있다.
차이점 Thread-safe Thread-safe x
public synchronized StringBuffer append(String str) {
    /*생략*/
}

public AbstractStringBuilder append(Object obj) {
    /*생략*/
}

 

|  Math

- 수학 계산에 사용되는 클래스. 

- 아래는 Math를 사용하여 소수점 n번째까지 반올림/올림/버림을 하는 법과,

- 두 좌표 간의 거리를 구하는 법을 작성한 코드이다.

public class MathTest {
    public static void main(String[] args) {
    
    	// 1. 반올림, 올림, 버림
        double d = 123.2364;
        int n = 3; // 소수점 자릿수
        
        // 소수점 n번째까지 반올림
        double d1 = Math.round(d * Math.pow(10, n)) / Math.pow(10, n);
        System.out.println(d1);

        // 소수점 n번째까지 올림
        n = 2;
        double d2 = Math.ceil(d * Math.pow(10, n)) / Math.pow(10, n);
        System.out.println(d2);

        // 소수점 n번째까지 버림
        double d3 = Math.floor(d * Math.pow(10, n)) / Math.pow(10, n);
        System.out.println(d3);
        
        // -----------------------------------------------------------
        
        // 2. 피타고라스의 법칙을 활용하여 두 좌표의 거리 구하기 
        int[][] pos = {{2, 1}, {5, 5}};

        int xDistance = pos[1][0] - pos[0][0];
        int yDistance = pos[1][1] - pos[0][1];
        double distance = Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2));

        System.out.println(distance);
    }
}

 

[ 참고 및 출처 ]

부트캠프 강의를 들은 후 여러 자료를 참고하여 정리하였습니다.

- 인코딩 관련 참조

https://codechacha.com/ko/java-convert-bytes-to-string/

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=pjok1122&logNo=221505713248 

https://corock.tistory.com/139

https://dogcowking.tistory.com/289

- 문자열 불변 법칙

https://starkying.tistory.com/entry/what-is-java-string-pool

https://readystory.tistory.com/139

|  캡슐화 (정보 은닉)

앞에서 상속과 다형성을 이야기할 때, 캡슐화의 원칙에 대해 언급했었다.

- 상속의 단점으로 상위 객체의 정보가 하위 객체에서 노출될 수 있고, 그것은 곧 캡슐화의 원칙을 깨는 것이라고 했다.

- 다형성 또한 instanceof를 통해 부모가 참조하고 있던 실제 자식 인스턴스를 외부에서 노출하면 캡슐화의 원칙이 깨졌다.

여기에서도 유추할 수 있는 캡슐화의 의미는 "정보 은닉"이다.

캡슐화 : 정보를 객체 내부로 숨겨 외부로부터 감추는 것

 

|  접근 제어자와 참조변수

1. 접근 제어자의 UML 표현

- private 자기 자신만 접근 가능
~ default 같은 패키지 내
# protected 상속 또는 같은 패키지 내
+ public 모두가 접근 가능
_ static -

 

- 캡슐화를 이루기 위해서는

  먼저 맴버 변수에 private 접근제어자를 사용하여 외부에서 맴버변수에 접근하지 못하도록 해야한다.

- private의 경우, 파생 클래스의 경우에도 접근이 불가하다.

 

2. 참조변수의 복사

기본 타입의 변수 값을 가져올 때는 call by value라 하여 값을 복사한다.

int x = 10;
int y = x; // y = 10; call by value
int z = x + y; // 20

x값을 y에 줄 때는 값을 복사해서 전달한다. 하지만 참조변수를 쓸 때는 call by reference라 하여 값을 복사하지 않는다.

// list1과 list2는 모두 같은 곳을 가리킨다
List<Integer> list1= new ArrayList();
list2 = list1;

참조변수는 포인터, 곧 주소를 담고 있는 변수이고, 그 주소는 실제 값이 저장되어 있는 메모리 공간을 의미한다.

그렇기에 포인터 = 가리킨다, 라는 말을 사용한다. 

 

|  캡슐화와 응집도

- 캡슐화가 되려면 앞서서 "실질적인 정보 은닉"이 이루어져야한다.

- 위에서 배운 접근제어자 private과 게터/세터를 사용하면 아래와 같이 클래스를 만들 수 있다.

public class User{

    private String name;
    
    public String getName(){ 
    	return this.name;
    }
    
    public String setName(String name){
    	this.name = name;
    }
}

- 응집도는 정보은닉에서 더 나아간 개념으로, 하나의 모듈은 하나의 기능을 수행하는 것을 의미한다.

- 응집도에 대한 자세한 내용 : https://computer-science-student.tistory.com/140

 

|  캡슐화의 장점

장점 불필요한 부분을 은닉하여, 사용자가 오용하지 않게 방지한다.
객체의 내부가 변경되어도, 사용법이 바뀌거나 다른 객체에 영향을 주지 않는다. (낮은 결합도)
큰 시스템일지라도 컴포넌트별로 작게 분리하여 개발이 가능하다. 
캡슐화를 하게 되면 개발 속도를 높이고 성능을 최적화할 수 있다.

 

 

[참조]

스프링 입문을 위한 자바 객체 지향의 원리와 이해

https://mangkyu.tistory.com/195

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=knix008&logNo=220699796327 

https://siyoon210.tistory.com/33

https://bperhaps.tistory.com/entry/%EC%BA%A1%EC%8A%90%ED%99%94%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-%EC%96%B4%EB%96%A4-%EC%9D%B4%EC%A0%90%EC%9D%B4-%EC%9E%88%EB%8A%94%EA%B0%80

|  다형성 (사용 편의성)

다형성이란, 하나의 객체가 여러가지 타입을 가질 수 있는 것을 의미한다.

다형성은 오버로딩과 오버라이딩을 통해 구현할 수 있다.

오버라이딩이 가능한 이유는 뭘까? 그건 동적 바인딩 때문이다.

여기서 동적 바인딩이란 메서드가 실행 시점에서 성격이 결정되는 것을 말한다.

종류 정적바인딩(static binding) 동적바인딩(Dynamic binding)
정의 컴파일 시간에 성격이 결정 실행 시간(런타임)에 성격이 결정
예시 C언어 컴파일 시간에 변수의 데이터타입이 결정
ex. int *p;   --  미리 데이터타입 지정
javascript 런타임에 값에 따라 변수의 데이터타입 결정
ex. var c = 1;  --  미리 데이터타입 지정x
ex. Person p = new Person(), p.method()
-- p 생성 전엔 p가 가리키는 곳 어딘지 알 수 없음
장단점 컴파일 시간에 많은 정보가 확정되어있어 실행 효율↑
값이 변하지 않아서 안정적
런타임에 자유롭게 성격이 바뀌므로 적응성↑
메모리 공간의 낭비, 느린 속도

- 자바에서는 static 메소드의 경우 정적바인딩을, 일반 메소드의 경우 동적바인딩을 사용한다.

 

|  다형성의 장단점

장점 유지보수가 쉽다 하나의 타입으로 여러 가지의 객체를 관리할 수 있어 유지보수가 쉽다.
재사용성이 증가 객체를 재사용하기가 쉬워 개발자의 코드 재사용성이 높아진다.
느슨한 결합 부모타입으로 자식인스턴스를 참조할 경우 클래스간 의존성이 낮아지며,
확장성이 높고, 결합도가 낮아진다.
단점 프로그램의 가독성이 낮아짐 실제 인스턴스가 무엇인지 instanceof가 없으면 알기 어렵다
디버깅의 어려움 -

 

|  다형성 구현 - 오버로딩과 오버라이딩

다형성을 개념적으로, 구현적으로 나누기도 하는 걸 봤었는데, 결국 핵심은 오버로딩과 오버라이딩이었다.

여기에 어떤 분들은 추가적으로 함수형 인터페이스를 함께 소개하기도 해서, 아래와 같이 넣어보았다.

 

오버로딩(중복 정의) 같은 이름의 메소드를 매개변수를 다르게 해서 적재
오버라이딩(재정의)  똑같은 선언부의 메소드를 다른 내용으로 재정의 
함수형 인터페이스 한 개의 추상 메소드를 가지고 있는 인터페이스

 

1. 오버로딩 

오버로딩의 조건

1. 메소드의 이름이 같아야 한다.
2. 매개 변수의 갯수 또는 타입이 달라야한다.
3. 반환 타입이 다른 것은 관계 없다.

- 오버로딩의 대표적인 예로, PrintStream의 println()이 있다.

public class PrintStream extends FilterOutputStream
        implements Appendable, Closeable
{

    public void println() {
        newLine();
    }

    public void println(boolean x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

    public void println(char x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

    public void println(int x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

    public void println(long x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

}

 

2. 오버라이딩

오버라이딩의 조건

1. 상위 클래스를 상속 또는 구현해야한다.
2. 물려 받은 메소드를 구현할 때는 선언부가 동일해야 한다.
3. 하위 클래스는 상위 클래스보다 접근제어자가 더 공개적이거나 같아야 한다.
(ex. 상위가 public -> 하위도 public, 상위가 protected -> 하위는 protected or public)
4. final과 private인 메소드는 오버라이딩이 불가하다.

[1] 상위 클래스를 상속하고 메소드를 오버라이딩하는 경우 

- 지난번 상속에서 사용한 Vehicle를 조금 변형해서 '움직인다(move)' 기능만 만들어 보았다.

- Vehicle을 상속하는 Car, Train, Airplane은 move를 오버라이딩하여 저마다의 소리를 낸다.

package polymorphism;

class Vehicle {
	
	public void move() {
		System.out.println("unknown");
	}
	
}

class Car extends Vehicle{
	
	public void move() {
		System.out.println("부릉부릉");
	}
}

class Train extends Vehicle{
	
	public void move() {
		System.out.println("칙칙폭폭");
	}
}

class Airplane extends Vehicle{
	
	public void move() {
		System.out.println("위잉위잉");
	}
}

- 메인 함수에서의 출력

 각 하위 인스턴스들을 부모 참조변수인 Vehicle로 통일하여 참조하는 걸 아래에서 볼 수 있다.

 이렇게 부모 참조변수로 자식 인스턴스를 참조하는 것을 업캐스팅이라고 한다.

업캐스팅
의미 상위 객체 참조 변수로 하위 인스턴스를 참조하는 것
제약사항 상위 객체 참조 변수를 쓰면, 하위 객체의 맴버를 참조할 수 없다. (= 하위 객체 변수를 car.xxx식으로 쓸 수 없다)

ex. Vehicle을 상속받은 Car객체엔 Car만의 맴버 "gasDisplacement"와 "drive()"가 있다.
      Vehicle car = new Car(); <-- 이 경우에, car.gasDisplaement와 car.drive() 를 쓸 수가 없다 
package polymorphism;

public class Test {
	public static void main(String[] args) {
		Vehicle car = new Car();
		car.move();
		System.out.println();
		
		Vehicle train = new Train();
		train.move();
		System.out.println();
		
		Vehicle air = new Airplane();
		air.move();
		System.out.println();
	}
}
부릉부릉

칙칙폭폭

위잉위잉

 

[2] 인터페이스를 사용하는 경우

- 위와 같이만 만들 경우에는 사실 다형성의 장점인, '느슨한 결합'을 온전히 지키기 어렵다.

- 왜냐하면 여전히 부모의 객체를 수정할 때, 자식에게 미치는 영향이 있기 때문에 (객체 간 의존성 ↑) 자식 입장에서 부모의 정보를 잘 알고 있어야 하기 때문이다.

- 그렇기에 공통 기능, '움직인다(move)'를 뽑아 movable인터페이스를 만드는 것이 느슨한 결합을 만들기에 더 좋다.

package polymorphism;

public interface Movable {
	public abstract void move();
}

class Car implements Movable{

    @Override
    public void move() {
    	System.out.println("부릉부릉");
    }
}

class Train implements Movable{
    
    @Override
    public void move() {
    	System.out.println("칙칙폭폭");
    }
}

class Airplane implements Movable{
    
    @Override
    public void move() {
    	System.out.println("위이위잉");
    }
}

- 메인 함수에서의 출력

package polymorphism;

import java.util.Arrays;
import java.util.List;

public class Test {
	public static void main(String[] args) {
            List<Movable> movables = Arrays.asList(new Car(), new Airplane(), new Train());
            for (Movable one : movables) {
                one.move();
            }
	}
}

 

3. 함수형 인터페이스

함수형 인터페이스는 한 개의 추상 메소드를 가진 인터페이스를 말한다.

함수형 인터페이스는 람다식을 통해 간편하게 사용할 수 있는데,

앞서 쓴 movable 인터페이스를 람다식으로 바꿔 쓰면 아래와 같다.

// 함수형 인터페이스 사용
public class Test {
	public static void main(String[] args) {
            List<Movable> movables = Arrays.asList(new Car(), () -> System.out.println("다그닥다그닥"));
            for (Movable one : movables) {
                one.move();
            }
	}
}

 

4. 객체 타입을 확인하는 instanceof, 지양해야할까?

instanceof 를 통해 부모참조변수가 참조하는 인스턴스가 무엇인지 알 수 있다.

그런데 instanceof를 사용하는 것을 지양하라는 이야기가 있다. 왜 그런 걸까?

아래의 코드를 보면, instanceof를 사용해서 실제 참조하는 인스턴스가 무엇인지 볼 수 있다.

class Test {
    public static void main(String[] args) {
        List<Movable> movables = Arrays.asList(new Car(), new Airplane(), new Train());

        for (Movable one : movables) {
            if (one instanceof Car) {
                System.out.println("This is a Car");
                one.move();
            } else if (one instanceof Airplane) {
                System.out.println("This is an Airplane");
                one.move();
            } else if (one instanceof Train) {
                System.out.println("This is a Train");
                one.move();
            }
        }
    }
}

이렇게 실제 인스턴스를 보게 주게 되면 외부의 객체에 정보를 고스란히 노출하는 것이기 때문에, 캡슐화가 깨지게 된다.

캡슐화란, 객체가 가진 상태나 행위를 다른 이가 사용하거나 보지 못하도록 숨기는 것

[출처] https://tecoble.techcourse.co.kr/post/2021-04-26-instanceof/

따라서, 되도록 instanceof는 사용하는 것을 지양하는 것이 좋다.

 

 

[ 참고 ]

자바의 정석

스프링 입문을 위한 자바 객체 지향의 원리와 이해

instanceof의 지양, https://tecoble.techcourse.co.kr/post/2021-04-26-instanceof/

https://tecoble.techcourse.co.kr/post/2020-10-27-polymorphism/

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=next7885&logNo=130068105493 

https://life-with-coding.tistory.com/485

http://www.tcpschool.com/java/java_polymorphism_concept

https://secretroute.tistory.com/entry/140819

https://todayscoding.tistory.com/16

|  상속 (재사용 + 확장)

- 부모 - 자손의 상속 관점 보다는, 재사용과 확장으로 접근하는 것이 좋다

- 상속은, 상위 클래스의 특성을 하위 클래스에서 재사용하고, 거기에 덧붙여 확장할 수 있는 것을 말한다.

- 관계 : 상위 - 하위 / 슈퍼 - 서브

   * 상위로 갈수록 추상화, 일반화되며

   * 하위로 갈수록 구체화, 특수화된다. 

- 상속을 표현하는 문장 

구분 - 설명
포함 has a 클래스 내에서 인스턴스를 형성하여 사용
상속 is a kind of 클래스를 extends (확장하다)
구현 is able to 인터페이스를 implements (구현하다)

- 오버라이딩 : 상속 관계에 있는 상위 클래스로부터 물려 받은 메소드를 재정의한 것

 

|  상속의 장단점

장점 - 모듈화*를 통한 재사용
- 유지보수가 쉽고, 개발 시간이 단축된다.
- 코드가 통일성이 있고 간결해진다.
단점 - 상속 시 기능을 추가하고 변경할 때 버그가 발생하면 전반적인 검토가 불가피하다.
- 불필요한 기능까지 상속해야하는 경우가 있다. (특히 인터페이스의 경우)
- 상위 클래스의 구현이 하위 클래스에게 노출될 경우 캡슐화 원칙을 위반할 수 있다.
  ==> 강한 결합도 발생 

* 모듈화 : 소프트웨어를 각 기능별로 나눈 단위. 여기에서는 객체화로 보아도 무방하다.

 

|  결합도와 응집도

- 상속에 대한 이야기를 할 때 자주 나오는 이야기가 결합도와 응집도인 것 같다.

- 결합도와 응집도란 그럼 뭘까?

결합도 응집도
서로 다른 모듈 간에 상호 의존하는 정도 또는 연관된 관계 한 모듈 내부의 처리 요소들이 서로 연관되어 있는 정도
A - B 간에 결합도가 높을 경우,
- A를 변경하면,  B도 변경해야 한다.
- A를 변경한 후, B를 다른 코드에서 재사용하기 어렵다
A안에 a1, a2, a3 메소드들이 있다고 할 때,
각 메소드들이 A안에서 책임이 잘 뭉쳐져 있어
A가 독립적인 모듈로써 기능할 때 응집도가 높다고 한다.

 

상속에서는 여러 개의 하위 모듈이 하나의 상위 객체로부터 공통의 메소드를 사용하거나, 참조하기 때문에 지나치게 결합도가 높은 경우에는 차후에 수정을 할 때에 어려움이 발생할 수 있다. 

- 또한 상위 객체에서 접근제어자와 겟터(getter)/셋터(setter) 등으로 정보에 대한 접근을 제한하지 않았다면, 캡슐화(정보은닉)의 원칙도 깨어질 우려가 있다. 잘못하면 상속 후 하위 객체에서 상위 객체의 데이터에 접근을 할 수 있기 때문이다.

- 따라서, 상속 시 사전에 결합도와 응집도를 충분히 고려하는 게 필요하다.

 

|  상속시 메모리구조

- new 객체명() 를 통해 인스턴스를 생성할 때, 상속을 받은 경우, '나' 뿐만 아니라 '상위' 인스턴스도 함께 힙에 생성된다.

Static : 클래스 정보
Stack :
main() -> 하위 생성자 호출
Heap : 
하위 인스턴스, 상위 인스턴스

 

|  UML 표현 (출처 : https://gmlwjd9405.github.io)

(1) Logical

출처:https://gmlwjd9405.github.io/2018/07/04/class-diagram.html

(2) Physical

출처:https://gmlwjd9405.github.io/2018/07/04/class-diagram.html

 

|  상속을 표현한 코드

 

동물은 이름(name)과 나이(age)가 있으며, 자신을 소개(introduce)할 수 있다.

 

 '포유류'는 동물이다. (상속)

- Mammal is a kind of Animal.

 

포유류는 다른 동물처럼 이름과 나이가 있고, 자신을 소개할 수 있다.

포유류의 포유류의 평균 최대 수명(MAX_AGE)을 갖고 있다.

 

포유류는 자신을 소개할 때, 포유류의 평균 최대 수명을 함께 소개한다.

 

package inheritance;

// 동물
public class Animal {
    String name;
    int age;

    Animal () {}

    Animal (String name, int age) {
        this.name = name;
        this.age = age;
    }

    void introduce(){
        System.out.println("Hi, my name is " + name + ", and i am " + age + "years old.");
    }
}

// 포유류 
class Mammal extends Animal{
    final int MAX_AGE = 268;

    void introduce(){
        System.out.println("Hi, my name is " + name + ", and i am " + age + "years old.");
        System.out.println("I can only live MAX : " + MAX_AGE);
    }
}

// 파충류
class Reptile extends Animal{
    final int MAX_AGE = 20;

    void introduce(){
        System.out.println("Hi, my name is " + name + ", and i am " + age + "years old.");
        System.out.println("I can only live MAX : " + MAX_AGE);
    }
}

 

|  또다른 예시 코드

package inheritance;

class Vehicle {
	
	String name;
	double maxSpeed; 
	int maxCapacity;
	
	public Vehicle() {}
	
	public Vehicle(double maxSpeed, int maxCapacity) {
		super();
		this.maxSpeed = maxSpeed;
		this.maxCapacity = maxCapacity;
	}


	public void info() {
		System.out.println("== vehicle info ==");
		System.out.println("name : " + name);
		System.out.println("maxSpeed : " + maxSpeed);
		System.out.println("maxCapacity : " + maxCapacity);
	}
}

class Car extends Vehicle{
	
	final String name = "car";
	int gasDisplacement; // 배기량
	
	public Car() {}
	
	public Car(int gasDisplacement) {
		super(430,100);
		this.gasDisplacement = gasDisplacement;
	}

	public void info() {
		System.out.println("== Car info ==");
		System.out.println("name : " + name);
		System.out.println("maxSpeed : " + maxSpeed);
		System.out.println("maxCapacity : " + maxCapacity);
		System.out.println("gasDisplacement : " + gasDisplacement);
	}
}

class Train extends Vehicle{
	
	final String name = "train";
	final static String[] ALL_LINES = {"경부선", "호남선", "전라선", 
                                           "중앙선", "경의선", "경의선"}; // 나머지 생략  
	String line; // 노선
	
	public Train() {}
	
	public Train(String line) {
		super(330, 1000);
		this.line = line;
	}

	public void info() {
		System.out.println("== Train info ==");
		System.out.println("name : " + name);
		System.out.println("maxSpeed : " + maxSpeed);
		System.out.println("maxCapacity : " + maxCapacity);
		System.out.println("line : " + line);
	}
}

class Airplane extends Vehicle{
	
	final String name = "airplane";
	String departure;
	String arrival;
	
	public Airplane() {}
	
	public Airplane(String departure, String arrival) {
		super(1000, 200);
		this.departure = departure;
		this.arrival = arrival;
	}

	public void info() {
		System.out.println("== vehicle info ==");
		System.out.println("name : " + name);
		System.out.println("maxSpeed : " + maxSpeed);
		System.out.println("maxCapacity : " + maxCapacity);
		System.out.println("departure : " + departure);
		System.out.println("arrival : " + arrival);
	}
}

-- 테스트 코드

package inheritance;

public class Test {
	public static void main(String[] args) {
		Car car = new Car(1000);
		car.info();
		System.out.println();
		
		Train train = new Train(Train.ALL_LINES[1]);
		train.info();
		System.out.println();
		
		Airplane air = new Airplane("Seoul", "Reykjavik");
		air.info();
		System.out.println();
	}
}
== Car info ==
name : car
maxSpeed : 430.0
maxCapacity : 100
gasDisplacement : 1000

== Train info ==
name : train
maxSpeed : 330.0
maxCapacity : 1000
line : 호남선

== vehicle info ==
name : airplane
maxSpeed : 1000.0
maxCapacity : 200
departure : Seoul
arrival : Reykjavik

 

[참조]

스프링 입문을 위한 자바 객체 지향의 원리와 이해

https://madplay.github.io/post/coupling-and-cohesion-in-software-engineering

https://gmlwjd9405.github.io/2018/07/04/class-diagram.html

https://sujl95.tistory.com/32

 

 

|  추상화 (모델링)

- 현실 세계의 객체에서 필요한 부분만 뽑아 클래스로 만드는 작업을 말한다.

추상화란 구체적인 것을 분해하여 관심 영역에 있는 특성만 가지고 재조립하는 것(= 모델링)

[출처] 스프링 입문을 위한 객체 지향의 원리와 이해

 

|  추상화가 모델링인 이유

관심 영역(앱의 경계)에 따라 클래스 모델링이 달라진다.

- 현실 세계에서 물리적으로는 동일한 실체에 대해서도 관심 영역(여기서는 애플리케이션에 적용할 영역)이 어디냐에 따라서, 클래스 모델링이 달라진다.

Ex. 사람(객체)을 동물로 본다면 "다리 갯수", "자손 생식 방식" 등을 보겠지만, 고객으로 본다면 "연령대", "지출금액" 등을 볼 것이다.

 

|  추상화의 예시

[1] 상속을 통한 추상화와 구체화

- 상속 관계에서 상위 클래스로 올라갈 수록 추상화/일반화가 이루어지며,

- 상속 관계에서 하위 클래스로 내려갈 수록 구체화/특수화가 이루어진다.

 

[2] 인터페이스를 통한 추상화

- 인터페이스를 통해 공통된 메소드를 하나로 묶어 주는 것도 추상화이다.

- 예시 ) Map 인터페이스로부터 파생된, Hashtable과 HashMap

   * 두 객체는 기능적으로 거의 유사하지만 차이를 보인다.

   * Hashtable의 경우 key에 null을 넣을 수 없지만 HashMap은 가능하며, Hashtable은 HashMap과 달리 멀티 쓰레드 환경에 우수하다.

// Map 인터페이스로부터 구현된 클래스들

// 1. Hashtable
Hashtable<Integer, Integer> ht = new Hashtable<>();
ht.put(0, 10);
System.out.println(ht.get(0));

// 2. HashMap
HashMap<Integer, Integer> hm = new HashMap<>();
hm.put(0, 10);
System.out.println(hm.get(0));

 

|  UML 표현

- 앞에서 잠깐 언급했던 '사람'을 토대로 간단한 클래스 다이어그램을 만들면 아래와 같다.

Logical Physical

사람
이름
국가
연령
인사하다()
Person
+ name : String
+ nationality : String
+ age : int
+ sayHello() : void

 

 

[참조]

- [스프링 입문을 위한 자바 객체 지향의 원리와 이해], 김종민

- 기타 블로그

|  객체지향 프로그래밍 / 절차지향 프로그래밍

  절차 지향 프로그래밍(PP) 객체 지향 프로그래밍(OOP)
특징 일련의 동작(모듈, 함수)를 순차적으로 실행 객체(속성 + 기능) 간의 상호작용을 이용
포커스 명령어의 순서와 흐름 데이터의 속성(필드)과 기능(메서드)
장점 컴퓨터의 처리구조와 비슷해 속도가 빠르다 재사용성(상속), 유지보수 용이, 자연스러운 모델링*
단점 규모가 커질 수록, 추가 개발 및 유지보수 어려움 느린 개발 속도와 실행 속도. 높은 난이도

 

* 객체지향 프로그래밍은 하나의 방법론이지, 그것이 언어나 제품을 의미하는 것은 아니다.

* OOP나 PP 외에 CBD(Component-based development), SOA(Service-orient development)등 프로그래밍 방법론은 많다.

* OOP가 방법이라면, 자바는 언어이고, 스프링 프레임워크는 일종의 제품이다.

* OOP의 장점 중 자연스러운 모델링이라는 것은, 현실 세계와 가깝게 자연스러운 구현이 가능하다는 의미

 

[참조] 기계어 -> 어셈블리어 -> 컴파일언어(C언어) -> 자바 -> 스프링 프레임워크

더보기

- 기계어 : 0과 1의 나열이며, 컴퓨터 기종마다 쓰는 방식이 다르다.

  - 기계어는 컴퓨터를 움직이는 명령 소스 그 자체이다.

 

- 어셈블리어 : 기계어를 니모닉(ex.ADD)을 통해 사람이 이해할 수 있는 단어로 변경한 언어.

  - 슬프게도 이 또한 컴퓨터 기종마다 쓰는 단어가 다르다.

  - 다만 어셈블리어는 기계어와 1 : 1 로 대응하기 때문에 명령시 반응은 즉각적.

 

- 컴파일어 : 수학적인 기호를 통해 코딩을 하면, 그걸 컴파일하여 기계어로 번역하는 언어.

    "One Source, Multi Object, Use Anywhere"

   - 컴퓨터 기종과 상관없이 동일한 소스 코드를 사용

   - 그렇지만 운영체제에 따라 자료형 타입 크기가 달라지는 단점이 있다. 

     (ex. C언어의 int -> 어떤 OS는 int를 2바이트로 규정, 어떤 OS는 int를 4바이트로 규정)

 

- 객체지향 언어(C++, 자바...) 

  "One Source, One Object, Use Anywhere"

  - C++는 객체지향언어이지만, 객체지향이 아니게 사용될 수도 있다.

  - 자바에서 JVM을 통해 컴퓨터 기종과 상관없으며, 운영체제와도 상관없이 동일한 소스코드를 쓸 수 있게 됐다.

  * JVM은 말그래도 가상머신, 가상의 메모리 공간이다. 가상공간을 만들면 아무래도 메모리 소비는 더 많을 것.

     (JVM 사용에 따른 메모리 소비량은 그러나 하드웨어 기술이 발전함에 따라 사실상 큰 의미가 없어졌다)

 

- 스프링 프레임워크

  - POJO(Ioc/DI + PSA + AOP)를 통해 OOP를 일관성 있게 개발할 수 있는 프레임워크 제공

 

|  자바 프로그램의 구동

1. 자바와 가상 머신

- 자바 프로그램은 가상머신 안에서 구동되는 프로그램이다.

- 현실 세계의 하드웨어가 자바 가상 머신으로 옮겨졌다고 보면 된다.

- 가상 머신을 통해서 OS에 독립적인 실행이 가능해진 것이다.

- 일반 하드웨어와 JVM이 메모리를 사용하는 방식

(1) 일반 하드웨어 : 코드 실행 영역 + 데이터 저장 영역

(2) JVM : 코드 실행 영역 + [스태틱 영역 + 스택 영역 + 힙 영역]

현실  가상 동작
소프트웨어 JDK (개발 도구) 개발자는 JDK 환경에서 자바로 코드를 짠다 
운영체제 JRE (실행 환경) JRE가 JVM에서 프로그램을 돌린다
하드웨어 JVM (가상 머신) 가상의 컴퓨터다. 

 

2. 구동 순서

(1) 전처리 : 스태틱 영역에, java.lang패키지를 넣고, 모든 import 패키지와 클래스를 넣는다.

(2) 스택 : 메인 메소드(변수 x = 10) -> 다른 메소드 -> 다른 메소드...

(3) 힙 : 배열, 인스턴스 ..... (중간중간 힙 사용)

 

|  객체

- 객체란? 세상에 존재하는 모든 사물(Object)

- 클래스(분류)와 객체(실체)를 구분하는 것 : 클래스는 분류, 객체는 유일무이한 실체

  "쫀떡이, 나이가 몇살이니?" --> 고양이는 클래스, 쫀떡이는 객체 

객체 속성 (= 변수) 특성 및 상태 
행위 (= 메소드) 동작 또는 행위 

 

|  객체 지향의 4대 특성

캡슐화 정보 은닉
상속 재사용과 확장성
추상화 모델링
다형성 사용 편의

 

|  객체 지향의 5가지 원칙 - SOLID

SRP(Single Responsibility Principle) 단일 책임 원칙 작성된 클래스는 단 하나의 기능을 가지며,
모든 서비스는 하나의 책임을 수행하는데 집중돼야 한다.
OCP(Open-Closed Principle) 개방 폐쇠 원칙 소프트웨어의 구성요소(컴포넌트, 클래스, 모듈, 함수)는
확장에는 열려 있고, 변경에는 닫혀 있어야 한다.
LSP(Liskov Substitution Principle) 리스코프 치환 원칙 상위 타입의 객체를 하위 타입으로 바꾸어도
프로그램은 일관되게 동작해야 된다
ISP(Interface Segregation Principle) 인터페이스 분리 원칙 클라이언트는 이용하지 않는 메소드에 의존하지 않도록
인터페이스를 분리해야 된다
DIP(Dependency Inversion Principle) 의존 역전 법칙 클라이언트는 추상화에 의존해야 하며,
구체화에 의존해선 안된다

 

 

[참조]

- [스프링 입문을 위한 자바 객체 지향의 원리와 이해], 김종민

- https://www.nextree.co.kr/p6960/

- https://velog.io/@hanjoon_10/OOP-vs.-PP

|  개발 환경 구축

(1) 자바 환경 구축

- 자바는 JVM을 통해 모든 플랫폼(OS)에서 독립적으로 작동한다.

- 오라클사에서 JDK를 다운할 수 있다.(또는 openJDK 사용) 설치 사이트 바로가기

- 명령프롬프트(CMD)로 설치 환경 확인

- 시스템 환경 변수 설정

변수명 경로 예시
Path java.exe가 있는 경로 %JAVA_HOME%\bin
JAVA_HOME jdk폴더 경로 C:\myDev\jdk1.8.0_333

rf. 코딩 폰트 설치하기 : d2coding, jetbrains mono 폰트

rf. 맥에서 자바 설치하기 : [1] homebrew 설치 [2] 터미널에서 자바 설치

 

더보기

[ 시스템 환경 변수 - Path는 왜 넣는 걸까? ]

 

운영체제(OS)가 프로세스를 실행할 때 참조하는 값을 환경 변수라고 한다.

환경 변수에는 여러가지가 포함될 수 있는데, 이 중에서 Path는  말그대로 "경로"를 말한다.

Path를 통해서 한 번 경로를 입력해 두면, JDK 실행을 호출할 때 OS가 알아서 Path에 저장된 값를 참조한다.

 

※ 출처 : https://seol2.tistory.com/141

 

(2) 컴파일 및 실행

- 컴파일 및 실행과정

start ----------------> compiled ---------------> result
소스파일.java 컴파일러 : javac.exe 바이트코드 인터프리터 : java.exe 실행결과
class Main { //코드 } javac HelloWorld.java 000111101010001010...... java HelloWorld 안녕!

- 명령프롬프트 활용

- 에디터(IDE) 활용

에디터명 개인적인 비교 다운경로 사용자수 (*jlab 설문결과)
IntelliJ - 말끔한 UI
- 상세한 가이드 (ex. 메소드의 매개변수까지 자동 안내)
바로가기 약 40%
Eclipse - 각종 단축키를 활용하여 빠른 코딩 가능
  (ex. 만능해결사 ctrl + space)
- 워크 스페이스 구조를 상세히 안내
- 다소 올드한 UI와 복잡한 설정
바로가기 약 30%
Visual Studio - 과거에 자주 사용되던 에디터
- 다양한 플러그인을 사용할 수 있다
바로가기 그 외
이클립스를 설치하면 가장 먼저 해야할 것 : 인코딩 타입을 MS949 -> UTF-8로 바꾸어준다.
( 위치 : [Windows] - [Preference] - search : encoding, Workspace/CSS/HTML/JSP/XML 모두 UTF-8로 설정)
IntelliJ나 이클립스나 내장된 openJDK를 사용할 수 있다. * 되도록 오라클에서 JDK 설치 후 사용 권장
(단, IntelliJ는 openJDK를 선택해서 설치해야 한다. 이클립스는 기본적으로 설치된 openJDK가 있다)
이클립스 프로젝트에 JDK 경로 설정하기 (아래 '더보기' 선택)  

 

|  자바 확장자와 파일

구분 확장자 내용
자바 파일 소스파일.java, 바이트코드.class 소스코드, 이진코드
자바 아카이브 압축명.jar 패키지 포맷
: 자바코드 + 리소스 + 메타데이터 + 기타 
웹 아카이브 압축명.war 웹 패키지 포맷
: 서블릿 + JSP + 리소스(html,js,css...) + ...
엔터프라이즈 아카이브 압축명.ear application server에 배포하기 위해 jar과 war을 묶어놓은 파일

 

|  자바 구조와 문장

패키지 (선택) 첫글자 소문자로 표기, ' . '을 통해 상세 경로를 나타낸다. package java.util.Random
클래스 첫글자 대문자로 표기, 그 외는 식별자* 명명법을 따른다. class Main
    // 필드
    // 메소드
}

 

* 예약어 : 예약어는 이미 정의된 것 (ex. 기본형 변수, if/while.... )

* 식별자 : 사용자가 지정한 것 (대소문자 구분, 첫문자 숫자 불가, 공백사용 불가, 특수문자 $, _만 가능, 한글 사용 가능)

 

|  클래스

클래스 필드 변수 기본형 논리형/문자형/정수형/실수형
참조형 참조값(16진수의 주소)을 담는 변수
초기화블럭 변수 선언 및 초기화 후, 초기화 블럭을 통해 재초기화*
메서드 생성자 인스턴스 생성 시, 생성자 안에 인자를 넣어 변수 초기화 
일반 메서드 매개변수와 반환값을 갖는다
접근제어자* +, (default) all  

 

* 사용하는 접근제어자 표시 : all(모두 사용가능), +(public), #(protected) , (default), -(private) 

* 초기화 블럭 : static이 들어간 경우 클래스가 로딩될 때, 그렇지 않은 경우 인스턴스가 생성될 때마다 초기화를 한다. (일반적으로 리소스를 업로드할 때 쓴다.)

 

|  변수와 자료형

변수 구분 크기 범위 기본값
기본형 논리형 boolean 1byte false, true false
문자형 char 2byte 0 ~ 약 6만 '\u0000'
정수형 byte 1byte -128 ~ 127 0
short 2byte 약 -3만~약 3만 0
int 4byte 약 -20억 ~ 약 20억  0
long 8byte   0L
실수형 float 4byte   0.0f
double 8byte   0.0
참조형 -   JVM 크기에 따름
32bit(4byte)
/64bit(8byte)
  null

- 정수형에 대한 자세한 설명 (아래 '더보기')

더보기

(1) 진수 표현

int n10 = 10;   //10진수
int n2  = 0b10; // 2진수
int n8  = 010;  // 8진수
int n16 = 0xff; //16진수

// JDK 1.7부터는 '_'를 통해 자릿수를 표기할 수 있게 됐다.
n2 = 0b0000_0000_0000_0000_0000_0000_1010;

 

(2) 2진수의 음수 표현 (2의 보수)

//2의보수 [앞에서 마지막 전까지 반대, 마지막은 동일]
int n1 = 0b0000_0000_0000_0000_0000_0000_0000_1100; //12
int n2 = 0b1111_1111_1111_1111_1111_1111_1111_0100; //-12

//n1 + n2은 아래와 같다.
int n3 = 0b0000_0000_0000_0000_0000_0000_0000_0000; //0

(3) long과 short/byte

// short와 byte는 사칙연산 시 int로 반환된다. (내장된 연산장치가 int 단위로 반환하기 때문)
short n1 = 1;
short n2 = 2;
int res = n1 + n2;

// long의 표현
long n3 = 1L;

- 오버플로우와 언더 플로우

(1) 오버플로우 : 자료 범위를 넘어서 양수가 음수로 바뀌는 경우

(2) 언더플로우 : 자료 범위를 넘어서 음수가 양수로 바뀌는 경우

 

더보기
public class Test {
    public static void main(String[] args) {
        //int n = 30_0000_0000; --> 에러! 
        int n1 = 0b0111_1111_1111_1111_1111_1111_1111_1111; //int 최댓값 / int범위 : -2^31 ~ (2^31 - 1)
        int n2 = 0b1000_0000_0000_0000_0000_0000_0000_0000; //최댓값에 2진수 1을 더한 값
        System.out.println("n1 = " + n1); //n1 = 2147483647
        System.out.println("n2 = " + n2); //n2 = -2147483648 ---> 오버플로우

        int n3 = n2 - 1;
        System.out.println("n3 = " + n3); //n3 = 2147483647 ---> 이처럼 음수가 양수로 바뀌는 경우는 언더플로우

    }
}

- 문자형 : 유니코드에 따라 문자를 나타낸다

char c1 = '\u0000'; 
char c2 = '\uac00'; //'가'
//참조 : 한글 유니코드(UTF-8) https://jjeong.tistory.com/696

- 변수 표기법

표기법 어디에? 예시
파스칼 자바 : 클래스
c# : 변수, 클래스
class Car { }
Date, LocalDate, String, Scanner
카멜 자바 : 변수, 메소드 int speed, void speedTest(){}
스네이크 - 소문자 c, php void speed_test(){}
스네이크 - 대문자 SQL문법
자바 : 상수
SELECT * FROM member
final int MAX_SPEED
헝가리언  과거 변수 타입을 구분하기 위해 사용 int iMaxSpeed; int strUsername
케밥 html : class 구분
url 주소 표준 : '-'로 구분
<div class = "list-imgtxt">
http://www.naver.com/board-list //"_"가 아닌 "-"가 표준

 

|  연산자와 피연산자

- 단항 > 사칙연산 > 비교 > 대입, 크게 4가지의 방향으로 이해

- 아래의 어떤 연산자 보다도 가장 우선순위가 높은 것은 "(   )"

우선순위 연산자 이름 연산자 연산 방향
1 후위 단항 i++, i-- 왼쪽에서 오른쪽
2 전위 단항 ++i, --i, +val, -val, !
3 곱셈/나눗셈/나머지 *, /, %
4 덧셈 +
5 비트이동 >>, <<, >>>
6 관계 >, <, <=, >=, instanceOf
7 동등 ==, != 
8 비트 AND &
9 비트 배타적 ^
10 비트 OR |
11 논리 AND &&
12 논리 OR ||
13 조건 삼항 (조건) ? (참) : (거짓) 
14 대입 =, +=, -= .... 오른쪽에서 왼쪽

 

- 비트 연산자

연산자 이름 사용 의미
& 비트 AND op1 & op2 비트가 모두 1이면 결과는 1, 아니면 0
^ 비트 배타적 op1 | op2 비트가 적어도 하나가 1이면 결과는 1, 아니면 0
| 비트 OR op1 ^ op2 비트가 서로 다르면 결과는 1, 같으면 0
~ 보수(complement) ~op1 비트가 0이면 결과는 1, 0이면 1
// 2의 보수를 구하는 법
int i1 = i2;
int i2 = ~i1 + 1; //~ : 1의 보수, 1을 더하면 : 2의 보수

 

- 비트 이동

연산자 이름 사용 의미 비고*
>> signed left shift op1 >> op2 [기존부호] 오른쪽으로 op2비트만큼 이동 = op1 * 2^op2
<< signed right shift op1 << op2 [기존부호] 왼쪽으로 op2비트만큼 이동 = op1 / 2^op2
>>> unsigned left shift op1 >>> op2 [0으로 채움] 오른쪽으로 op2비트만큼 이동  

* 단, 변수의 Max크기까지만 가능하다. (ex. int 범위는 ~ 2^32까지)

 

- 형변환

(1) 자동 형변환 : 작은 범위에서 큰 범위의 자료형에 값을 넣을 때

(2) 명시적 형변환 : 큰 범위에서 작은 범위의 자료형에 값을 넣을 때. ex. int num = (int)(5/1.0);

 

- 연산 관련 예외

(1) ArithmeticException : (런타임 에러) 정수를 0으로 나눌 때

(2) Infinity : 실수를 0.0으로 나누면 무한대 

(3) byte와 short 산술 연산 : 모두 int로 변환되어 연산 수행

 

|  제어문과 반복문

구분 이름 코드 비고
제어문 조건문 if, if~else 자바는 if~else if~else가 없다
스위치문 switch~case default
반복문 - for, foreach
while, do while
break와 continue

 

+ Recent posts