generic, io 작성 x
annotation
annotation #ex01
Exam0110 -> 애노테이션
public @interface MyAnnotation {
String value();
}
// 주석의 프로퍼티를 정의한다. // interface에서 메서드를 정의하는 것과 같다고 생각하자.
// 메서드 이름은 프로퍼티(변수)명처럼 작성한다. // 자세히보면 value()라서 메서드 명이다.
@MyAnnotation(value="값") // 클래스 정의 앞에 선언할 수 있다.
public class MyClass {
@MyAnnotation(value="값") // 변수 앞에 선언할 수 있다.
int i;
@MyAnnotation(value="값") // 메서드 정의 앞에 선언할 수 있다.
public void m(// 파라미터 앞에 선언할 수 있다.
@MyAnnotation(value="값")String p) {
@MyAnnotation(value="값") int local;
// 일반 문장 앞에 선언할 수 없다!
//@MyAnnotation(value="값") if (true) System.out.println("ok"); // 컴파일 오류!
}
// bin/main/com/eomcs/annotation/ex/MyClass.class 확인 // 애노테이션이 Class에 붙어있는 것을 확인
annotation #ex02
Exam0110 -> 애노테이션 유지 정책 // @interface
// SOURCE // 소스 파일에만 남긴다. 컴파일 후 제거된다.
// CLASS // .class 파일에 남긴다. 그러나 실행 시에 추출할 순 없다.(기본)
// RUMTIME // .class 파일에 남긴다. 실행 시에 추출할 수 있다.
// SOURCE는 컴파일 시 bin/main 밑에 .class파일을 열어보면
// 컴파일 된 파일에 애노테이션이 없어 진 것을 확인할 수 있다.
// RUNTIME // 실행 중에 애노테이션을 꺼낼 수 있다. // CLASS는 불가능
@Retention(RetentionPolicy.CLASS)
public @interface MyAnnotation {
String value();
}
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation2 {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation3 {
String value();
}
@MyAnnotation(value="값") // 유지정책 => CLASS
@MyAnnotation2(value="값") // 유지정책 => SOURCE
@MyAnnotation3(value="값") // 유지정책 => RUNTIME
public class MyClass {
}
// MyClass에 3가지 애노테이션을 적용한다. // 하나에 애노테이션은 3개 다 붙힐 수 있다.
Class clazz = MyClass.class;
// => 유지정책 : CLASS
MyAnnotation obj = (MyAnnotation)clazz.getAnnotation(MyAnnotation.class);
// => 유지정책 : SOURCE
MyAnnotation2 obj2 = (MyAnnotation2)clazz.getAnnotation(MyAnnotation2.class);
// => 유지정책 : RUNTIME
MyAnnotation3 obj3 = (MyAnnotation3)clazz.getAnnotation(MyAnnotation3.class);
// MyAnnotation3 obj3 = (MyAnnotation3)clazz.getAnnotation(MyAnnotation3.class); // 애노테이션 추출
// obj는 RUNTIME이라 애노테이션을 실행중에 추출할 수 있다.
// obj3.value() // 값 // 실행시 MyClass에서 value=를 "값"이라고 저장해뒀기 때문에 "값"이 나온다 // ""는 안나옴
annotation #ex03
Exam0110 -> 애노테이션 필수 프로퍼티 // @interface
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value(); // default 값을 지정하지 않으면 필수 프로퍼티
// 즉 애노테이션을 사용할 때 반드시 값을 지정해야 한다.
}
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation2 {
String value() default "홍길동";
// default 값이 있으면,
// 애노테이션을 사용할 때 값을 지정하지 않아도 된다.
}
package com.eomcs.annotation.ex3;
//@MyAnnotation // 필수 프로퍼티 값을 지정하지 않으면 컴파일 오류!
@MyAnnotation2 // 선택 프로퍼티 값을 지정하지 않으면 default 값이 사용된다.
public class MyClass {
}
// 1번 MyAnnotation은 컴파일 오류가 발생 // default 값도 지정안하고, MyClass에서도 값을 설정하지 않아 발생
// default 값을 MyAnnotation에서 지정하던가, // MyClass에서 @MyAnnotation(value="값") 으로 설정해야함.
annotation #ex04
-> 애노테이션 생략 // Class에 @애노테이션 붙힐 때
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
//@MyAnnotation(value="홍길동") // OK!
@MyAnnotation("홍길동") // OK! value 프로퍼티는 이름 생략 가능!
public class MyClass {
}
//@MyAnnotation2(tel="222-2222") // OK!
@MyAnnotation2("222-2222") // value 속성이 아닌 경우 생략 불가!
public class MyClass2 {
}
//@MyAnnotation3(value="홍길동",tel="222-2222") // OK!
@MyAnnotation3("홍길동",tel="222-2222") // value 외 다른 프로퍼티 값도 지정할 경우,
// value 이름 생략 불가!
// value 값만 지정할 때 생략 가능!
public class MyClass3 {
}
// value라는 프로퍼티를 사용할 때에는 생략이 가능하다. // value가 아닐 경우 불가능
// value 외 다른 프로퍼티도 사용한다면 생략 불가능하다.
annotation #ex05
Exam01 -> 애노테이션 값 추출 // @interface
public static void main(String[] args) {
Class clazz = MyClass.class;
MyAnnotation obj = (MyAnnotation) clazz.getAnnotation(MyAnnotation.class);
System.out.println(obj.v1());
System.out.println(obj.v2());
System.out.println(obj.v3());
}
// MyAnnotation obj = (MyAnnotation)clazz.getAnnotation(MyAnnotation.class);
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String v1() default "가나다";
int v2() default 100;
float v3() default 3.14f;
}
// obj.v1 obj.v2 obj.v3으로 호출 가능 // 가나다 100 3.14 순으로 나온다.
Exam02 -> 애노테이션 배열 값 추출 // @interface
public static void main(String[] args) {
Class clazz = MyClass2.class;
MyAnnotation2 obj = (MyAnnotation2)clazz.getAnnotation(MyAnnotation2.class);
System.out.println(obj.v1()[0]);
System.out.println(obj.v2()[0]);
System.out.println(obj.v3()[1]);
}
// obj.v3 등 리턴 값을 빈 배열에 받아서 출력해도 상관은 없다.
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation2 {
// 배열 프로퍼티의 기본 값을 지정할 때 중괄호를 사용한다.
String[] v1() default {"가나다","라마바"};
int[] v2() default {100,200};
float[] v3() default {3.14f,5.14f};
}
// 가나다 100 5.14 순으로 나온다.
Exam03 -> 애노태이션 배열 하나 중괄호 생략 가능 // @interface
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation3 {
// 배열 값이 한 개일 경우 중괄호를 생략할 수 있다.
String[] v1() default "가나다";
int[] v2() default 100;
float[] v3() default 3.14f;
}
Exam04 -> 애노테이션 배열 값 지정 // Class에 @애노테이션 붙힐 때
@MyAnnotation3(
// 배열 값을 지정할 때 중괄호를 사용한다.
v1={"홍길동", "임꺽정", "유관순"},
v2={1000, 2000, 3000, 4000, 5000},
v3={1.12f, 2.23f, 3, 34f})
public class MyClass4 {
}
Exam05 -> 애노태이션 배열 하나 중괄호 생략 가능 // Class에 @애노테이션 붙힐 때
@MyAnnotation3(
// 배열 값이 한 개일 경우 중괄호를 생략할 수 있다.
v1="임꺽정",
v2=1111,
v3=1.11f)
public class MyClass5 {
}
annotation #ex06
-> 애노테이션 적용 범위 지정
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String v1() default "가나다";
}
@MyAnnotation // OK!
public class MyClass {
@MyAnnotation int i; // 컴파일 오류!
@MyAnnotation public void m() {} // OK!
}
// 애노테이션 적용 범위 설정을 @interface 화면에 설정 할 수 있다.
// METHOD와 TYPE이 설정되어 있는 상태이다. // METHOD는 메소드를, TYPE은 Class를 의미한다.
// int i를 사용하기 위해서는 Target에, ElementType.FIELD를 추가하여야 사용 할 수 있다.
concurrent
concurrent #ex01
Exam0110 -> java // 자바는 main() 메서드를 실행하는 한 개의 "실행 흐름"이 있다 // 실행 흐름에 따라 실행된다.
Exam0110 -> c //
Exam0120 -> java // 동시에 실행하고 싶은 코드가 있다면 thread를 상속받아 run()메서드에 그 코드를 둔다.
// thread // 코드 실행 라인을 새로 만들어 따로 실행 // 스레드는 비동기로 동작(별도로)
// 스레드에 작업을 시킨 후, 그 스레드가 작업이 끝날 때까지 기다리지 않고 즉시 리턴
// 따라서 스레드 작업과 main()의 코드가 병행(concurrent)으로 실행된다. // 멀티테스킹
// CPU Scheduling(=프로세스 스케줄링) // CPU의 실행 시간을 쪼개 실행하는 방법
// Multi-processing // Parent(프로세스)를 fork(복제)하여 Child(프로세스)를 만드는 방식
// Process를 복제할때 그에 해당하는 Heap(메모리)도 같이 복사한다. // 메모리 낭비가 심하다.
// Process별로 종료를 하여야 한다. // Parent 종료, Child 종료 별도
// Multi-threading // Process에서 thread로 실행을 분리한다. // heap(메모리)는 Process에 유지
// 작업 부분만 떼서 thread에서 처리한다. // thread는 stack(메모리)만 가져간다. // 자원 절약
// Multi-threading은 메인 프로세스의 메모리를 공유한다. // Multi-processing은 메모리를 복제한다.
// 모든 thread는 process에 종속한다. // process 종료시 thread도 종료 된다.
// JVM과 thread // JVM(process) 안에 thread가 기본적으로 들어있다. // 여러가지가 있다.
// 대표 예) main(main 메소드 호출, garbage collector, 레퍼런스 관리(인스턴스의 레퍼런스 관리)
Exam0120 -> c //
concurrent #ex02
Exam0110 -> Thread // JVM은 여러개의 스레드를 실행한다.
// Thread t = Thread.currentThread(); // 현재 실행중인 스레드 확인
// JVM이 실행될 때 main()메서드를 호출하는 thread의 이름은 main이다.
Exam0120 -> Thread 그룹 // thread는 그룹으로 묶일 수 있다.
// Thread main = Thread.currentThread(); // 현재 실행 쓰레드
// ThreadGroup group = main.getThreadGroup(); // thread main의 소속 그룹을 알 수 있다.
// 현재 상태 // main(TG) => main(T) // main이라는 쓰레드 그룹 안에 main 쓰레드가 있다.
Exam0130 -> Thread 그룹 소속 Thread 목록 확인
// Thread[] arr = new Thread[100]; // int count = mainGroup.enumerate(arr, false);
// thread 빈 배열을 준비해서 넘긴다 false는 하위 그룹에 소속된 쓰레드는 가져오지 않는다.
// 즉, 현재 그룹에 소속된 스레드 목록만 가져오라는 뜻이다.
Exam0140 -> Thread 그룹 소속 Thread 그룹 목록 확인
// ThreadGroup[] groups = new ThreadGroup[100]; // int count = mainGroup.enumerate(arr, false);
// threadgroup 빈 배열을 준비해서 넘긴다, false는 하위 그룹에 소속된 쓰레드 그룹은 가져오지 않는다.
// 즉, 현재 그룹에 소속된 스레드 그룹 목록만 가져오라는 뜻이다.
Exam0150 -> 스레드 그룹의 부모 그룹 확인
// ThreadGroup parentGroup = mainGroup.getParent(); // 최상위 그룹은 System 이다.
// System의 부모 그룹은 없다.
Exam0160 -> 시스템 그룹의 자식 그룹 확인
// ThreadGroup[] groups = new ThreadGroup[100]; int count = systemGroup.enumerate(groups, false);
// main(TG), InnocuousThreadGroup(TG)
Exam0170 -> 시스템 스레드 그룹의 자식 스레드 확인
// Thread[] arr = new Thread[100]; int count = systemGroup.enumerate(arr, false);
// Reference Handler(T), Finalizer(T), Signal Dispatcher(T), GroupAttach Listener(T)
Exam0180 -> JVM의 전체 스레드 계층도
concurrent #ex03
Exam0110 -> Thread 사용 // Thread를 상속받아 사용
// class MyThread extends Thread { // 이미 만들어져 있는 Thread를 상속받아 만들어 사용한다.
// public void run() { // 새 스레드에서 실행하고픈 코드가 있다면 오버라이딩 하여 사용한다.
// MyThread t = new MyThread(); t.start(); // Thread를 상속받은 클래스 객체를 만들어 사용
// Thread의 서브 클래스는 그냥 인스턴스를 만들어 start()를 호출
// 다중 상속이 불가능하기 때문에 다른 클래스를 상속 받을 수 없다.
Exam0120 -> Thread 익명클래스 //
// new Thread() { public void run() { ... }.start(); // run을 오버라이딩해서 사용.
Exam0210 -> Thread 사용 // Runnable 인터페이스 구현 + Thread
// class MyRunnable implements Runnable { public void run() { ... }
// Thread t = new Thread(new MyRunnable()); t.start(); // Runnable 구현체를 Thread 객체에 실어서 실행
Exam0220 -> Thread 익명클래스 // Runnable 인터페이스 구현 + Thread
// new Thread(new Runnable() { public void run() {... }).start(); // run을 오버라이딩해서 사용.
Exam0230 -> Thread lambda// new Thread(()-> {...}).start(); // 람다 문법 적용
Exam0310 -> Thread와 프로그램 종료 // 모든 스레드가 완료할 때까지 JVM은 종료되지 않는다.
// main 스레드에서 스레드 객체 생성하기 // 자식 스레드는 부모 스레드와 같은 우선 순위를 갖는다.
// main 스레드에서 만든 자식 스레드와 main 스레드가 다 끝나야 JVM은 종료 된다.
// main 스레드가 먼저 완료돼도, 자식 스레드가 끝날 때까지 JVM은 종료되지 않는다.
concurrent #ex04
Exam0110 -> Thread 생명 주기(Lifecycle) // 죽은 스레드는 살릴 수 없다.
Exam0111 -> Thread 생명 주기(Lifecycle) // 같은 쓰레드 객체를 또 실행할 수 없다.
Exam0120 -> Thread start, join
// Thread t = new Thread() { // t 스레드를 만들고
// t.start(); // 스레드 t를 시작한다.
// t.join(); // t스레드가 종료되면 t.join();을 가진 스레드가 실행된다. // 즉 t가 끝날 때까지 기다린다.
Exam0130 -> Thread sleep
// Thread.currentThread().sleep(3000); // Thread.sleep(3000); // 3초 동안 not runnable 상태로 만든다.
// currentThread()가 스태틱 메서드라 생략이 가능하다.
// sleep()을 호출하면, 그 순간 실행하는 스레드를 잠들게 한다.
// 3초 동안 CPU가 놀고 있더라도 CPU를 사용하지 않는다.
// 3초가 지나면(timeout) 다시 "main" 스레드는 CPU를 받아 실행한다.
Exam0140 -> CPU 쟁탈전(racing) // 스레드를 시작하면, running 상태로 접어든다. // CPU를 받을 수 있는 상태이다
// CPU는 OS의 관리 정책에 따라 스레드나 프로세스에 배분된다.
// OS가 CPU를 임의시간 동안 배분 후, 회수하여 현재 스레드 포함한 전체 스레드나 프로세스에 배분한다.
// 1. Round-Robin 방식 // windowOS에서 사용
// CPU 실행 시간을 일정하게 쪼개서 각 코드에 분배하는 방식
// 2. Priority 방식 // Unix, Linux에서 사용
// 우선 순위가 높은 코드에 더 많은 실행 시간을 배정하는 방식
// 단점 : 우선 순위가 낮은 경우 CPU 시간을 배정받지 못해, 몇 년 동안 실행되지 않는 경우도 있었다.
// aging 기법(에이징 기법) // 위의 단점 해결책
// CPU 시간을 배정 받지 못할 때마다 우선 순위를 높여 언젠간 실행되게 만든 기법
// 컨텍스트 스위칭(context switching) // 코드마다 어디까지 실행했는지 위치 기억이라고 생각.
// CPU 실행 시간을 쪼개 이 코드 저 코드를 실행할 때마다,
// 실행 위치 및 정보(context)를 저장하고 로딩하는 과정을 말한다.
Exam0210 -> Thread 우선 순위 조회
// Thread.MIN_PRIORITY // 최소 우선순위(1) // Thread.MAX_PRIORITY // 최대 우선순위(10)
// JVM에서는 1~10까지만 관리를 한다.
// Thread.NORM_PRIORITY // 우선 순위 기본값(5) // main스레드 우선 순위(5)
// Thread.currentThread().getPriority()); // 현재 스레드 우선 순위 확인
// 자식 스레드는 부모 스레드의 우선 순위와 같은 값을 갖는다.
// 즉 main에 다른 스레드를 만들면, main이 부모 스레드이니 우선 순위 5를 갖는다.
// 우선 순위가 높으면 CPU 사용 배분을 더 자주 받는다.
// 스레드는 JVM이 관리하는게 아니라 OS가 관리한다. // 즉 OS의 스레드를 이용하는 것이다.
// 우선 순위에 따라 실행 스케줄을 어떻게 관리할 지는 OS따라 다르다.
// windowOS는 우선 순위를 크게 고려하지 않는다.
// Windos에서 실행시 우선 순위에 영향을 적게 받는다.
// Unix, Linux 계열 OS는 우선 순위를 고려한다.
// Unix, Linux 계열 OS에서 실행시 우선 순위에 영향을 크게 받는다.
// Java는 Write Once, Run Anywhere를 캐치프라이즈로 만들어진 만큼,
// OS마다 우선순위에 따른 영향을 받는다는 것은 자바의 목적이 아니다.
// 따라서 Thread를 다룰 때 우선 순위를 고려하는 방식으로 프로그래밍 하지 말자. // 가능하면 쓰지말라
Exam0220 -> Thread 우선 순위 설정
// Thread.currentThread().setPriority(1); // Thread.setPriority(1); //로 우선순위를 설정한다.
// Math.asin(38.567); // 부동 소수점 연산은 시간을 많이 쓰기 때문에, 시간 끌기용으로 넣어둔 것이다.
concurrent #ex05
Exam0110 -> 크리티컬 섹션(임계영역; critical section) 또는 크리티컬 리전(critical region)
// 여러 스레드가 같은 메모리(balance 필드)의 값을 동시에 변경할 때 문제가 발생하는 코드
// 뮤텍스(mutex) 또는 세마포어(1)(semaphore) // 크리티컬 섹션에 동시에 접근하지 못하게 하는 기법
// 세마포어(n); semaphore // 크리티컬 섹션에 진입할 수 있는 스레드의 수를 지정한다.
// 자바에서는 세마포어를 지원하지 않는다. // 개발자가 직접 처리해야 한다.
// 뮤텍스; mutex(mutual exclusion, 상호배제) // ex) 선풍기 풍량세기, 라디오 채널, TV 채널 등
// 한 번에 오직 한 개의 스레드만이 크리티컬 섹션에 접근할 수 있다. // semaphore(1)과 같다.
// 자바는 synchronized 키워드를 통해 뮤텍스를 사용할 수 있다.
// 자바에서 뮤텍스를 구현하는 방법
// 크리티컬 섹션의 메서드나 코드 블록에 sychronized 키워드를 걸어
// 한 번에 한 스레드만 진입할 수 있도록 lock을 건다.
// synchronized // modifier // 변경자 클래스, 변수, 메서드의 선언부에 사용되어 부가적인 의미를 부여
// Exam0110 Account에는 100만원이 있다. 변경자를 주석으로 막고 실행하면,
// 5개의 ATM기에서 총합 100만원이 넘는 금액이 출금이 가능하다 // 치명적인 오류
// 동시에 여러 쓰레드가 접근이 가능하기 때문에 발생하는 문제이다.
// 변경자의 주석을 풀어 변경자를 적용하고 실행하면, 한 번에 한 스레드만 접속이 가능하다.
// ATM기별 우선순위에 따라 각 ATM기의 금액은 차이가 나지만, 총합이 100만원인 것은 변함이 없다.
// 스레드 안전(Thread safe) // synchronized, 크리티컬 섹션하지 않게 만든다.
// 여러 스레드가 동시에 실행해도 문제가 없는 코드 블록을 말한다.
// 이 Thread를 Thread safe하게 만드세요. // = 여러 스레드가 조회는 가능해도, 값을 변경하게 하지 말라.
// 실무에서는 위처럼 얘기하지 크리티컬 섹션하지 않도록 하세요 이런식으로 얘기하지 않는다.
concurrent #ex06
Exam0110 -> Thread // Dead 상태의 스레드는 다시 시작할 수 없다. // 실행중인 스레드를 다시 실행시킬 수도 없다.
Exam0120 -> Thread 재사용 // Thread가 꺼지지 않게 while (true)로 무한 반복하게 한다.
Exam0130 -> Thread 재사용 // 별도 변수 선언하여 사용하지 않으면, sleep에 들어가게 한다.
Exam0140 -> Thread 재사용 // wait(), notify() 메서드 사용 // wait()은 반드시 동기화 영역 안에서 호출해야 한다.
// 동기화 영역? // synchronized로 선언된 메서드 // synchronized로 선언된 블럭
// synchronized (this) { notify() } //
Exam0210 -> Thread 재사용 // Pooling 기법을 이용 // Thread List를 만들어 보관하여 사용한다.
// run()상태를 유지하게 하고 wait()으로 기다리게하고 notify()로 다시 사용한다.
corelib
corelib #ex01
Exam0110 -> Object 클래스 // 자바 최상위 클래스 // 모든 메서드는 Object 클래스를 자동으로 상속받는다.
// finalize() 가비지 컬렉터에 의해 메모리에서 해제되기 직전에 호출된다. // 그 외에는 밑에서 다룸.
Exam0120 -> toString() // Object 클래스의 메서드 // 클래스 이름과 해시코드를 리턴한다.
Exam0121 -> toString() 오버라이딩
Exam0122 -> toString()의 리턴 값 // 인스턴스의 주소를 알려주는 것이 아닌 해시값(해시코드)을 리턴한다.
Exam0123 -> toString() 오버라이딩
Exam0130 -> equals() // Object 클래스의 메서드 // 같은 인스턴스인지 검사한다.
Exam0131 -> equals() 오버라이딩 // 사용할때 꼭 클래스에 맞게 오버라이딩 하라.
Exam0132 -> equals() 오버라이딩 // String wrapper클래스는 오버라이딩이 자동으로 되어있다.
Exam0133 -> equals() 오버라이딩
// equals는 기본적으로 인스턴스가 같은지만 비교한다, 데이터가 같은지는 비교하지 않는다.//
Exam0134 -> equals() 오버라이딩 // StringBuffer 클래스는 오버라이딩이 자동으로 되어있지 않다.
Exam0140 -> hashCode() // Object 클래스의 메서드 // 인스턴스를 식별하는 값을 리턴한다.
Exam0141 -> hash value? // 매우 낮은 확률이지만 데이터가 다르더라도 같은 정수 값이 나올 수 있다.
// 해시 알고리즘 SHA, MD, PGP
Exam0142 -> hashCode() 오버라이딩 // Map에 값을 저장하는 key로 사용할 때 hashCode()를 오버라이딩
Exam0143 -> hashCode() // 인스턴스의 고유 값을 리턴한다. 데이터가 같더라도 다른 값을 리턴한다.
Exam0144 -> '디지털 지문' // 해시코드를 데이터를 구분하는 지문과 같다고 해서 부른다.
Exam0145 -> hashCode() 오버라이딩 // String 클래스는 오버라이딩이 자동으로 되어있다.
Exam0150 -> HashSet // 저장할 객체에 대해 hash 코드로 중복 여부를 검사
// hash 코드로 값을 저장할 인덱스를 결정하기 때문에 값을 꺼낼 때 저장한 순서대로 꺼낼 수 없다.
Exam0151 -> HashCode 오버라이딩 HashSet 저장 // 데이터가 같으면 같은 HashCode가 나오게 오버라이딩.
// HashSet에 저장하면 중복된 값은 저장되지 않는다. // equals()도 같이 오버라이딩 한다.
Exam0152 -> HashMap // 값을 저장할 때 key 객체의 해시코드를 이용하여 저장할 위치(인덱스)를 계산
Exam0153 -> HashCode오버라이딩 HashMap 저장
Exam0154 -> hashCode() 오버라이딩 HashMap 사용 // wrapper 클래스는 오버라이딩이 자동으로 되어있다.
// HashMap/Hashtable의 key로 사용하자. // 그 외에는 오버라이딩을 별도로 진행하여 키로 사용
Exam0155 -> Key로 HashMap 데이터 사용 // get(key, "데이터")으로 넣고, set(key)로 데이터를 받는다.
Exam0160 -> getClass() // Object 클래스의 메서드 // 인스턴스의 클래스 정보를 리턴한다.
Exam0161 -> getClass() 배열 타입의 정보
Exam0162 -> getClass() 배열의 클래스 정보, 배열 항목의 타입 정보
Exam0170 -> clone() // Object 클래스의 메서드 // 인스턴스를 복제한 후 그 복제 인스턴스를 리턴한다.
Exam0171 -> clone() // 오버라이딩 해야만 사용이 가능하다.
// public Score clone() throws CloneNotSupportedException { return (Score) super.clone(); }
Exam0172 -> Cloneable 인터페이스 구현 // Clone할 클래스에 표시를 해주어야 한다.
// static class Score implements Cloneable // 이 표시가 안된 클래스는 clone()을 호출할 수 없다.
Exam0173 -> shallow copy // 얕은 복제 // 인스턴스 변수가 가리키고 있는 객체는 복제하지 않는다.
Exam0174 -> deep copy // 깊은 복제 // 인스턴스 변수가 가리키는 객체도 복제하는 코드를 별도로 작성한다.
// 데이터가 간단하다면, 복제한 인스턴스 변수의 객체에 복제할 인스턴스 변수의 객체를 넣어주면 되고
// 배열이라면 반복문을 사용하는 등 변수의 객체에 맞는 방법을 사용한다.
corelib #ex02
Exam0110 -> String 인스턴스를 생성 영역에 따른 차이 // Heap vs string constant pool
// new String("Hello") // heap 영역에 생성한 Hello는 추가 생성시마다 새로운 메모리 주소를 갖는다.
// String x1 = "Hello"; // string constant pool 영역은 데이터가 같으면 추가 메모리를 생성하지않는다.
// x1과 x2는 String 인스턴스의 주소를 리턴한다. // x1과 x2는 같은 인스턴스 주소를 리턴한다.
Exam0111 -> 일반 클래스와 랩퍼 클래스의 차이 // 랩퍼 클래스는 오버라이딩이 되어 있다.
// 일반 클래스는 hashCode() , equals() , toString()을 별도로 오버라이딩 해야한다.
Exam0120 -> 랩퍼 클래스 equals() 클래스 // 오버라이딩이 되어있다.
Exam0121 -> 일반 클래스 equals() 클래스 // StringBuffer 클래스는 오버라이딩이 되어있지 않다.
Exam0130 -> immutable 객체 // 인스턴스의 데이터를 변경할 수 없다. // 원본을 못 바꾼다. // String 클래스
Exam0131 -> mutable 객체 // 인스턴스의 데이터를 변경할 수 있다. // 원본을 바꾼다. // StringBuffer 클래스
Exam0140 -> toString() // println에는 자동으로 toString 기능이 있다.
Exam0141 -> toString() String 클래스 // toString은 String클래스에서 오버라이딩 되어 사용된다.
Exam0142 -> toLowerCase() // 모든 문자를 소문자로 적용한다.
Exam0210 -> Wrapper 클래스 // primitive data type 값을 보다 다양한 방법으로 다루기 위한 클래스
Exam0220 -> auto-boxing, auto-unboxing
Exam0221 -> auto-boxing // 원시타입 데이터를 자동으로 레퍼런스에 넣는다.
// int 100(primitive data type)을 자동으로 Integer.valueOf(100)로 변경하여 Integer에 저장한다.
Exam0222 -> auto-unboxing // 레퍼런스의 값을 원시 타입 데이터에 넣는다.
// Integer.valueOf(300);의 Integer obj를 자동으로 obj.intValue()로 변경하여 int에 저장한다.
Exam0223 -> Wrapper 클래스 적용 후// 오토박싱 언박싱을 이용하여 보다 편리하게 코딩이 가능하다.
Exam0224 -> Wrapper 클래스 적용 전 // 원래라면 일일히 정의를 하고 사용을 하여야 한다.
Exam0230 -> Wrapper 클래스의 장점 // cache에 저장함.
// Integer obj3 = 100; // Integer obj5 = Integer.valueOf(100); // obj3 == obj5 // true
// obj3과 obj5는 같은 인스턴스 주소를 리턴한다. // 메모리를 아끼기 위함.
// Integer obj3 = 128; // Integer obj5 = Integer.valueOf(128); // obj3 == obj5 // false
// 단, 정수 값이 -128 ~ 127까지만 같은 인스턴스 주소를 리턴한다.
// 범위를 넘어가는 값은 임시적으로 cache에 저장한다.
Exam0231 -> Wrapper 클래스의 값 비교 // Wrapper 클래스는 equals()가 오버라이딩이 다 되어있다.
// 값을 비교할 때에는 ==가 아닌 equals를 이용하자 // cache에 저장된 값은 ==가 false로 나옴.
corelib #ex07
Exam0110 -> HashSet의 사용 // hashCode와 equals의 값이 같다면, 같은 값으로 취급하여 중복 저장하지 않는다.
// hashCode()의 값으로 순서를 결정하기 때문에, 랜덤이다. // index() 사용 불가.
Exam0120 -> ArrayList의 같은 값의 데이터 저장 // 같은 값을 가지는 데이터를 저장 할 수 있다.
Exam0121 -> ArrayList의 같은 인스턴스 저장 // ArrayList에 포함된 인스턴스도 다시 중복하여 저장할 수 있다.
Exam0210 -> HashSet에서 값을 꺼내는 방법 // iterator에게 맡긴다. // hasNext() next() 사용
Exam0220 -> LinkedList도 iterator 사용 가능.
Exam0230 -> Stack도 iterator 사용 가능.
Exam0240 -> Queue도 iterator 사용 가능.
Exam0310 -> HashSet 오버라이딩 // 오버라이딩 하지 않고서는 원하는 결과를 얻기 힘들다.
Exam0320 -> HashSet equals만 오버라이딩 // equals만 오버라이딩 해서는 원하는 결과를 얻기 힘들다.
Exam0330 -> HashSet hashCode만 오버라이딩 // hashCode만 오버라이딩 해서는 원하는 결과를 얻기 힘들다.
Exam0340 -> HashSet 오버라이딩 // HashSet은 equals와 hashCode 모두 오버라이딩 하여야 한다.
corelib #ex08
Exam0110 -> HashMap의 사용 // HashMap은 key와 value로 저장한다. // put(key, value) get(key)
// 같은 key로 다른 value를 추가하면 덮어쓴다.
// 존재하지 않는 key를 지정하면, null을 리턴한다.
Exam0111 -> Key는 Object를 상속하여, 어떠한 값도 저장이 가능하다.
Exam0120 -> 사용자 정의 데이터 타입을 key로 사용할 때 // key의 데이터가 같더라도, 저장이 된다.
// hashCode()와 equals() 오버라이딩을 하여야 한다.
Exam0130 -> HashMap equals만 오버라이딩 // equals만 오버라이딩 해서는 원하는 결과를 얻기 힘들다.
Exam0140 -> HashMap hashCode만 오버라이딩 // hashCode만 오버라이딩 해서는 원하는 결과를 얻기 힘들다.
Exam0150 -> HashMap 오버라이딩 // HashSet은 equals와 hashCode 모두 오버라이딩 하여야 한다.
// 모두 오버라이딩 하기 싫다면, 이미 되어있는 Wrapper 클래스를 이용하자. ex) String 클래스
Exam0210 -> HashMap에서 key 목록을 꺼내는 방법 // 1. Iterator 사용 2. 배열 사용 3. Collection 규칙 따를 때
// java.util.Set keys = map.keySet(); // keySet()을 통해 key의 목록을 받는다.
// key는 값이 중복되어서는 안되기 때문에 Set에 담아서 리턴한다.
Exam0220 -> HashMap에서 key와 value의 목록을 꺼내는 방법 // java.util.Set entrySet = map.entrySet(); 이용
Exam0230 -> HashMap에서 value의 목록을 꺼내는 방법 // java.util.Collection values = map.values();
// value는 값이 중복되어도 되기 때문에 Collection에 담아서 리턴한다.
Exam0310 -> HashMap과 Hashtable // null을 key나 value로 사용하여야 하면, HashMap을 사용한다.
// HashMap은 null을 key와 value로 사용이 가능하다. // Hashtable은 불가능.
// HashMap은 동기화를 지원하지 않아 속도가 빠르다. // Hashtable은 지원, 속도 느림.
Exam0320 -> HashMap에서 keySet()호출 // Set keys = map.keySet();
// keySet()의 값은 실제 호출할 때 저장이 된다.
// keySet() 객체 생성 후, 값을 변경하고 호출하면, 값을 변경한 key의 목록이 출력된다.
Exam0321 -> HashMap에서 keySet()호출과 Iterator호출 // Set keys = map.keySet();
// Iterator iterator = keys.iterator(); // Iterator는 객체 생성 시 값이 저장이 된다.
// 객체 생성 후, keySet()의 값을 변경하면 Iterator는 객체 생성 당시 값을 기억하고 있으므로,
// Iterator는 무효한 목록의 값을 가지고 있다. // 무효한 Iterator는 실행 오류가 발생한다.
// 즉, Iterator에 목록을 넘겨주고 값을 수정하면 오류가 발생한다.
Exam0330 -> Hashtable에서 keySet()호출 // Set keys = table.keySet(); // Exam0320 HashMap과 같다.
Exam0331 -> Hashtable에서 keySet()호출과 Iterator호출 // Set keys = map.keySet(); // Exam0321 HashMap과 같다
exception
exception #ex1
Exam0110 -> 원래 리턴 값으로 예외처리 문법을 확인하였다.
Exam0120 -> 리턴 값을 검사하여 오류를 판단하였다.
Exam0121 -> 리턴 값을 검사하는 것의 문제점 // 정상적인 계산 결과의 값이 검사 값과 같다면 오류로 처리한다.
Exam0130 -> Exam0121의 경우를 예로 들어 예전에는 희귀한 값을 리턴하도록 했다.
Exam0131 -> 아무리 희귀한 값이라도 그 값이 정상 값일 수 있다라는 오류가 있다.
Exam0210 -> 예외처리의 필요성 // 실행 오류를 내버리면 실행이 종료되어 버린다.
exception #ex2
Exam0110 ->예외처리 적용 전
Exam0120 -> try catch 문법 // try안에서 예외가 발생하더라도 catch에서 받아주며, 프로그램은 진행한다.
예외처리 문법이란 ? // 프로그램을 실행하던 중에 예외 상황이 발생하더라도
// 시스템을 멈추지 않고 계속 실행되게 도와주는 문법
exception #ex3
Exam0110 -> try catch 사용 // 오류가 발생해도, 진행이 계속 된다. // throws [Throwable 객체]
// Throwable 객체는 Exception 를 말한다. // try catch를 이용해 예외 발생한 이유를 출력할 수 있다.
Exam0111 -> try catch 미사용 // 오류가 발생하면 실행이 멈춰버린다.
Exam0210 -> Throwable의 종류 // 1. java.lang.Error (시스템 오류) 2. java.lang.Exception (애플리케이션 오류)
// 1. 오류가 발생하면 현재의 시스템 상태를 즉시 백업하고, 실행을 멈춰야 한다.
// 오류의 예 : 스택 오버 플로우 오류, VM 관련 오류, AWT 윈도우 관련 오류, 스레드 종료 오류 등
// 2. 적절한 조치를 취한 후 계속 시스템을 실행하게 만들 수 있다.
// 오류의 예 : 배열의 인덱스가 무효한 오류, I/O 오류, SQL 오류, Parse 오류, 데이터 포맷 오류 등
// 메서드 선언부에 반드시 어떤 오류를 던지는 지 throws [Throwable 객체]로 알려줘야 한다.
Exam0211 -> Error 계열의 예외 // JVM 관련 오류일 때 사용하는 클래스 // 가급적 예외처리를 사용하지 않는다.
// 메서드 선언부에 예외를 선언하지 않아도 된다.
Exam0212 -> Exception 계열의 예외 // 메서드 선언부에 예외를 선언해야 한다.
Exam0220 -> RuntimeException 예외 // Exception의 서브클래스 // 메서드 선언부에 예외를 선언하지 않아도 된다.
Exam0310 -> 메서드에서 발생되는 예외는 메서드 선언부에 모두 나열해야 한다.
Exam0320 -> 메서드에서 발생되는 예외들을 예외들의 수퍼클래스 선언 하나로 퉁 칠 수 있다.
// 유지보수에 도움이 되기 위해 메서드에서 발생되는 예외는 다 나열하자.
Exam0410 -> 예외처리를 한 메소드를 호출할 때, 호출한 메서드에도 예외처리가 되어있어야 한다.
Exam0420 -> main 메소드의 선언부에 예외처리를 하는 것은 가장 마지막으로 사용되어야 한다.
// main 메소드 선언부에 예외처리시 예외 발생시 실행이 멈춰버리기 때문이다.
// 사용이 불가능 한 것은 아니나 사용하지 말자.
Exam0430 -> Exam0410 방법 // 예외처리된 메소드를 호출하는 곳에서 try catch로 받자.
Exam0420 방법 // main메소드에서도 try catch로 받으면 선언부에 예외를 선언하지 않아도 된다.
Exam0440 -> 여러개의 예외를 받을 때, 슈퍼클래스의 예외처리는 가장 나중에 받자.
// 슈퍼클래스의 예외처리부터 받아버리면 오류가 발생한다.
Exam0450 -> 여러개의 예외를 받을 때, 슈퍼클래스 예외처리 하나로 예외처리가 가능하다.
// 단 슈퍼클래스 예외처리 후, 서브클래스 예외처리를 선언한다면 오류가 발생한다. // Exam0440
Exam0460 -> 다형적 변수를 활용한 예외처리 받기 // (RuntimeException | SQLException | IOException e)
Exam0470 -> Error 예외는 정상적으로 프로그램이 당장 실행 불가능할 때를 말한다 // 함부로 선언 하지마라.
Exam0471 -> 예외처리 가장 상위 클래스인 Throwable를 예외처리로 사용하지 마라.
Exam0472 -> Error 예외는 굳이 예외처리하지 않아도 된다.
Exam0510 -> finally 블록 // 정상적이던 예외가 발생하던, finally 블록은 무조건 실행한다.
Exam0520 -> finally 블록 // catch 블록이 없어도, try 블록을 나가기 전 실행해야 한다면 finally 블록을 이용하자.
Exam0610 -> close를 사용하여야 하는 이유 // close()로 연결했던 것을 풀어주는 것을 자원해제라 한다.
// 24시간 365일 유지되는 서버에서는 다른 프로그램이 해당 자원을 쓸 수 있도록 자원 해제를 해야한다.
Exam0620 -> finally 블록을 이용해, close()를 실행한다. // 예외가 발생해도 정상적으로 자원해제 되게끔 한다.
Exam0630 -> java.lang.AutoCloseable // 매번 모든 자원을 일일히 close()하지 말자
// try-with-resources 문법 // try() {} ()안에 자원을 선언하면 try문이 끝나고 자동으로 해제된다.
// try (java.lang.AutoCloseable 구현체) {...} // 단 AutoCloseable 구현체만 자동 해제가 가능하다.
Exam0640 -> AutoCloseable 구현체 // implements AutoCloseable 상속 받아 구현하자.
// static class B implements AutoCloseable { public void close() throws Exception { } }
Exam0641 -> AutoCloseable 예외발생
// try문에서 오류가 발생했다면, try문을 나가기 전에 close()가 실행된다. 그 이후 catch 실행
Exam0650 -> try() // ()안에는 변수 등 변동이 있는 값에 대한 코드를 넣을 수 없다.
Exam0710 -> 필수 예외처리 // File 패키지의 getCanonicalPath() 메서드처럼 예외처리를 필수로 해야하는 경우가 있다.
exception #ex4
Exam0110 -> 메서드 호출 // 예외처리 아예 적용 전
Exam0120 -> 예외처리 호출 // 예외처리 된 메서드를 호출하려면, 호출하는 메서드도 예외처리 되어야 한다.
// ex) m1() -> m2() -> m3() -> m4() 순으로 호출하고, m4이 예외처리된 메서드라면,
// m1(), m2(), m3() 모두 예외처리를 해주어야 한다. // throws Exception
Exam0130 -> RuntimeException // RuntimeException와 서브클래스들은 예외처리 하지 않아도 된다.
exception #ex5
Exam0110 -> 예외 처리 전 //
Exam0120 -> 예외처리 // RuntimeException의 경우에 예외처리 하지 않아도 된다.
// 하지만 개발자의 편의를 위해, RuntimeException도 예외처리를 해주자.
// } catch (RuntimeException e) { e.printStackTrace(); }
// printStackTrace()은 예외처리가 난 이유를 Println해주는 메서드이다.
Exam0130 -> Exam0120의 단점 // RuntimeException라고 선언하면 직관적이지 않다.
// public class BoardException extends RuntimeException {
// RuntimeException을 상속받고 그대로 RuntimeException을 구현하는 예외처리 클래스를 만들자.
// static Board read() throws BoardException { // BoardException class를 따로 만듦으로써 직관적이다.
generic
generic #ex01
httpcomponents
httpcomponents
Exam0110 client -> ApacheHttpClient 라이브러리 사용 //
// mvnmvnrepository.com 또는 search.maven.org 접속 - 'httpclient5' 검색
// org.springframework:spring-context // 버전 클릭 // Gradle Groovy DSL 복사
// - 라이브러리 정보를 dependencies {} 블록에 추가 - 'gradle cleanEclipse' - 'gradle eclipse'
// CloseableHttpClient httpClient = HttpClients.createDefault(); // HTTP 요청을 수행할 객체를 준비
// HttpGet get = new HttpGet("https://www.daum.net"); // HTTP GET 요청 정보를 준비
// CloseableHttpResponse response = httpClient.execute(get); // HttpClient 객체를 사용하여 GET 요청을 실행
// 리턴 값은 웹 서버의 응답 데이터를 다루는 도구
// HttpEntity entity = response.getEntity(); // 응답 도구를 이용하여 서버가 보낸 데이터를 꺼낸다.
Exam0120 client -> 도우미 클래스 사용 // HttpEntity에서 응답데이터 꺼낼 때
// EntityUtils.toString(entity)
Exam0130 client -> try-with-resources 문법 적용
Exam0140 client -> 더 간결하게 HTTP 요청하기
httpcomponents #server
Exam0110 ->
io
io #ex1
Exam0110 -> 폴더 정보 확인
// File class // 파일이나 디렉토리 정보를 관리 // 파일이나 디렉토리를 생성,삭제,변경
// 현재 폴더 정보 조회 // "." 으로 경로를 표시
// File class 메소드 //
// getName() : 이름 // getPath() : 경로
// getAbsolutePath() : 절대경로 // ex) C:\Users\heusw\git\bitcamp-study\bitcamp-java\.
// getCanonicalPath() : 계산된 절대경로 // ex) C:\Users\heusw\git\bitcamp-study\bitcamp-java
// getTotalSpace() : 총 크기 // getFreeSpace() : 남은크기 // getUsableSpace() : 가용크기
// isDirectory() : 디렉토리 여부 // isFile() : 파일 여부 // isHidden() : 감춤여부
// exists : 존재여부 // canExecute() : 실행가능 여부 //
Exam0120 -> 상위 경로는 ".."으로 표시
Exam0130 -> 존재하지 않는 폴더
// getTotalSpace, getFreeSpace, getUsableSpace는 존재하지 않은 폴더인 경우,
// 크기를 알아낼 수 없어, 0으로 표시한다.
// isDirectory, isFile, isHidden, exists, canExecute는 존재하지 않은 폴더인 경우,
// 정보를 알아낼 수 없어, false로 표시된다.
Exam0210 -> 파일 정보 확인 // Exam0110과 동일
Exam0220 -> 존재하지 않는 파일 // Exam0130과 동일
Exam0310 - > 디렉토리 생성
File dir = new File("temp");
dir.mkdir();
// File에 경로를 넘겨주고, mkdir()을 호출한다. // 경로를 지정하지 않으면 현재 폴더에 생성된다.
Exam0320 -> 하위 디렉토리 생성
File dir = new File("temp/a");
dir.mkdir();
// File에 경로&하위폴더명을 넘겨주고 mkdir()을 호출한다,
Exam0321 -> 하위 디렉토리 생성 // 없는 경로 전달
// mkdir() // mkdir()은 없는 경로를 전달하면 하위 디렉토리를 생성할 수 없다.
Exam0322 -> 하위 디렉토리 생성 // 없는 경로 전달
// mkdirs() // mkdirs는 없는 경로를 전달하면, 경로 생성 후, 하위 디렉토리를 생성한다,
Exam0330 -> 디렉토리 삭제
File dir = new File("temp");
dir.delete();
// File에 경로&하위폴더명을 넘겨주고 delete()를 호출한다,
Exam0410 -> 파일 생성
File file = new File("temp2/a/test.txt");
file.createNewFile();
// File에 경로&파일명을 넘겨주고 createNewFile()를 호출한다, // 이미 파일이 존재한다면 다시 생성할 수 없다.
Exam0411 -> 파일 생성 // 없는 경로 전달
// createNewFile() // createNewFile는 없는 경로를 전달하면, 파일을 생성할 수 없다.
Exam0420 -> 파일 삭제
File file = new File("temp2/a/test.txt");
file.delete();
// File에 경로&파일명을 넘겨주고 delete()를 호출한다,
Exam0430 -> 안봐도 됨
Exam0431 -> 없는 경로의 파일 생성법 // mkdirs 호출 경로 생성 후, createNewFile() 호출
Exam0510 -> 현재 폴더의 정보 확인
File dir = new File(".");
String[] names = dir.list();
// File dir = new File("."); // 현재 파일의 정보를 알아낸다.
// list() // 넘겨준 경로의 파일이나 하위 디렉토리의 이름 정보를 String으로 가져온다.
Exam0510 -> 현재 폴더의 정보 확인
File dir = new File(".");
File[] files = dir.listFiles();
// listFiles() // 넘겨준 경로의 파일이나 하위 디렉토리의 정보를 File 객체로 가져온다.
Exam0610 -> File 필터 적용 // Exam0611을 확인하자
Exam0611 -> Filename 필터 적용
class JavaFilter implements FilenameFilter {
@Override
public boolean accept(File dir, String name) {
File file = new File(dir, name);
if (file.isFile() && name.endsWith(".java"))
return true;
return false;
}
}
// 인터페이스 FilenameFilter 구현할 class에서 accept를 오버라이딩 한다.
// 오버라이딩 하여 원하는 필터 결과를 리턴할 수 있도록 한다.
File dir = new File(".");
JavaFilter javaFilter = new JavaFilter();
String[] names = dir.list(javaFilter);
// File을 호출할 때, javaFilter를 넘겨주어 원하는 결과만 리턴 받는다.
// list를 호출하기 때문에 FilenameFilter를 설정한다.
Exam0620 -> File 필터 적용
class JavaFilter implements FileFilter {
@Override
public boolean accept(File file) {
if (file.isFile() && file.getName().endsWith(".java"))
return true;
return false;
}
}
// 인터페이스 FileFilter 구현할 class에서 accept를 오버라이딩 한다.
File dir = new File(".");
JavaFilter javaFilter = new JavaFilter();
File[] files = dir.listFiles(javaFilter);
// File을 호출할 때, javaFilter를 넘겨주어 원하는 결과만 리턴 받는다.
// listFiles를 호출하기 때문에 FileFilter를 설정한다.
'IT Developer > Bitcamp' 카테고리의 다른 글
비트캠프 프론트엔드 및 백엔드 개발자 #Project v43 ~ v54 (0) | 2020.03.10 |
---|---|
비트캠프 프론트엔드 및 백엔드 개발자 SQL (0) | 2020.02.18 |
비트캠프 프론트엔드 및 백엔드 개발자 #Project v26 ~ v42 (0) | 2020.01.21 |
비트캠프 프론트엔드 및 백엔드 개발자 #Project v1 ~ v25 (0) | 2020.01.13 |
비트캠프 프론트엔드 및 벡엔드 개발자 디자인 패턴 & 알고리즘 (0) | 2019.12.30 |
비트캠프 프론트엔드 및 백엔드 개발자 oop Week 04~07 (Day 17 ~ Day 37) #강남학원 (19.12.24.~20.01.21) (0) | 2019.12.24 |
비트캠프 프론트엔드 및 백엔드 개발자 basic Week 03~04 (Day 11 ~ Day 16) #강남학원 (19.12.16.~12.23) (0) | 2019.12.17 |
비트캠프 프론트엔드 및 백엔드 준비단계 개발자 Week 01~02 (Day 01 ~ Day 10) #강남학원 (19.12.02~12.13) (0) | 2019.12.04 |