Preliminary
컴퓨터 구조
•
일반적으로 RAM의 메모리 주소를 참조해서 데이터를 읽어 register에 옮긴 후 CPU의 ALU가 연산 후 다시 RAM에 데이터를 write하는 방식
•
JAVA에서는 메모리 주소를 직접 참조하지는 않음
Programming Basic & Setup
고급어 vs. 저급어 & JVM
•
고급어 vs. 저급어
(CPU 접점에 전기 신호(+5v)를 넣으면 1, 그렇지 않으면 0)
◦
저급어: CPU가 인식 할 수 있는 명령어
◦
고급어: 사람이 이해하기 용이한 문자열 표기로 정의한 것이 고급어
•
디지털 회로
◦
회로의 출력 (e.g., 1 and 1 → 1 & 1 and 0 → 0)을 활용해 사칙연산 구현이 가능
◦
기계어
▪
전선에 0(0),1(+5v)의 신호를 일정 단위로 줌 → 주파수
▪
CPU에서 성능 측정을 위해 hz를 사용하는 이유
•
JVM
◦
컴퓨터에서 물리적인 CPU/RAM을 가상화해서 application program이 사용하도록 구현할 수 있음
(가상화 기술)
◦
JAVA는 가상화 기술을 기반으로 구현되는데
▪
JAVA.exe라는 s/w가 h/w를 virtual로 구현함
▪
JAVA(고급 프로그래밍언어)를 실행하면 Java byte code가 JVM 기계어로 번역
▪
JVM은 실제 h/w가 실행시킬 수 있는 기계어로 번역
◦
JAVA의 가상화 기반 구현은 아래의 장단점을 동시에 가져옴
▪
JAVA 코드는 JVM의 권한/실행 범위로 실행 범위가 제한됨 (보안성 강화, 성능 희생)
▪
JVM에만 호환시켜주면 어떤 플랫폼(OS)에서도 동작이 원활 (application 개발 원활)
컴파일러와 인터프리터
•
컴파일러
◦
고급어 소스코드를 기계어로 번역하는 프로그램
◦
전체 소스코드를 모두 기계어로 변환한 후 실행
◦
성능 최적화가 용이
•
인터프리터
◦
고급어 소스코드를 직접 실행하는 프로그램이나 환경을 의미
◦
보통 한번에 한 줄 단위 (i.e., 구문 단위)로 실행
◦
성능 (특히 속도)면에서 컴파일러 방식보다 느림
상수와 변수
•
상수
◦
소스코드를 작성하는 시점/연산식을 기술하는 시점(컴파일 타임)에 값이 정해진 수
◦
리터럴: 프로그램에서 직접 사용되는 값
int age = 25; // 25가 리터럴 상수
float pi = 3.14; // 3.14가 리터럴 상수
char grade = 'A'; // 'A'가 리터럴 상수
C
복사
◦
심볼릭: 프로그램에서 값이 변경되지 않도록 선언된 식별자
const int MAX_STUDENTS = 100; // 심볼릭 상수
#define PI 3.14159 // 매크로를 이용한 심볼릭 상수
C
복사
•
변수
◦
소스코드를 작성하는 시점에 값이 정해지지 않는 수
◦
개발자가 메모리를 사용하는 가장 일반적인 방법
JDK & JRE
•
JDK
◦
JRE를 포함
◦
컴파일러 등 프로그램 개발도구가 포함
◦
언어버전과 일치
•
JRE (Java Runtime Environment)
◦
런타임 실행 모듈 (.exe)
◦
Java 프로그램을 실행하는데 필요한 패키지
(JVM, 각종 명령, 클래스 라이브러리)
환경변수
•
환경변수 상속 구조:
◦
최상위에 시스템 환경변수 존재
◦
그 아래 사용자 환경변수(~/.zshrc에 설정한) 존재
◦
모든 프로세스는 부모 프로세스로부터 환경변수를 상속
•
IntelliJ가 환경변수를 인식하는 과정:
◦
IntelliJ를 실행하면 새로운 프로세스가 생성
◦
이 프로세스는 시스템 환경변수와 사용자 환경변수를 모두 상속
◦
따라서 JAVA_HOME과 같은 사용자 환경변수도 자동으로 인식
Java Basic
build & compile
•
build: compile
◦
.java 소스코드를 .class (jaca byte code)로 번역
◦
.class를 모듈이라고 하며 linker로 여러 모듈을 link해서 활용
•
run : link + runtime
◦
JVM의 Class loader가 절차에 따라 bytecode 로딩 후 메모리에 적재
◦
추가 클래스 파일도 로드하고 링크 (실행전에 링크 발생)
public class Main {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
Java
복사
•
Main {}: Class / scope
•
void main {} : method / 구문
JVM
•
JAVA 특징
◦
JVM 기반에서 작동하는 OOP언어
◦
Native code (os+cpu에 의존적)의 가장 큰 특징인 메모리 관리(반환의 의무)와 책임이슈를 구조적으로 제거
◦
OS에 대한 의존성 없음
◦
컴파일러, 인터프로터 특징을 모두 가짐
•
JVM
◦
class loader: .class (java byte code)를 loading
loading → linking → initialization
◦
runtime data are: memory
▪
heap: new 명령어로 생성한 객체가 생성되는 메모리 영역
▪
pc register: (현재까지 실행된) 구문 위치를 저장해주는 register
•
stack area, pc register: thread
◦
execution engine: cpu를 활용해 진정한 실행을 담당
▪
JIT compiler: Java bytecode를 실제 기계어로 번역
•
JVM이 반복되는 코드를 발견할 경우 효율을 높일 목적으로 사용
•
실행 기록을 모아 자주 사용되는 코드에 주로 적용
CPU 수준의 자료형
•
정수
◦
bit수에 따라 표현 범위 결정
◦
2의 보수를 더하는 방식으로 뺄셈 구현
•
IEEE 754 기반 (정밀도 희생)
◦
실수단정도 (float)
◦
배정도 (double, 소수점 이하 15번째 자리까지 유효)
◦
특수정도
•
사용자 정의 자료형
◦
class로 정의
▪
string
▪
boolean
실수형과 부동소수점 오차
•
실수형은 IEEE(Institute of Electrical and Electronics Engineers, 전기전자 기술자협회)가 규정한 표준을 사용
•
소수점 이하 정보를 표시할 수 형식
◦
부동 소수점 표현
▪
100.0
▪
10.0 * 10
▪
1.0 * 10^2
→ 같은 값에 대한 표현
→ 두 정수 사이에는 무수히 많은 실수가 존재하기 때문에 일정 수준이 오류(부동소수점 오차)를 인정
•
float (32bit)냐 double (64bit)에 따라서 같을 수도 다를 수도 있음
◦
float (32bit)이면 같다고할 수도 하지만 double (64bit)이면 다르다고
•
python으로 확인
import numpy as np
# 더 작은 차이를 가진 숫자로 시도
x = 1.000000001 # 1과 더 작은 차이
y = 1.0
# float32로 비교
a = np.float32(x)
b = np.float32(y)
print("float32 values:")
print(f"{x} -> {a}") # 이 경우 a는 1.0으로 표현될 것
print(f"{y} -> {b}")
print("float32 comparison:", a == b) # True가 나올 것
print("\nfloat64 values:")
c = np.float64(x)
d = np.float64(y)
print(f"{x} -> {c}") # c는 1.000000001로 유지
print(f"{y} -> {d}")
print("float64 comparison:", c == d) # False가 나올 것
Java
복사
Java의 자료형
#### 기본형 (Primitive type)
•
stack 영역에 존재
→ float은 정밀도가 떨어지기 때문에 가능하면 사용하는거 권장 X
→ java는 utf-8형태의 유니코드 인코딩을 활용
→ boolean은 숫자 X
#### 유도형 (Non-Primitive type or Derived Type)
•
Java에서 객체로 언급하는 대상
•
new 연산을 통해 Heap영역에 동적할당 GC에 의해서 관리
•
String
◦
문자열을 다루기 위한 클래스
•
Class
◦
Array, List, Queue, Stack
◦
Interface
#### 그 외 (C 기준으로)
•
무치형
◦
void
•
함수형
◦
testFunc(int a)
객체, 클래스, 인스턴스, 참조 - 용어정리
•
객체
◦
특정 목적을 가진 코드와 연산에 필요한 자료(변수), 함수(메서드)들을 한 세트로 묶어 구현할 대상
•
클래스 (객체를 구현하는 문법)
◦
java에서는 객체를 클래스로 기술
•
인스턴스
◦
특정 자료형에 대한 변수 / 클래스 인스턴스에 접근할 수 있는건 참조자
→ int a;
(a는 int 형식에 대한 인스턴스)
→ String hello = new String(”Hello”);
(hello는 새로 생성된 String 클래스 인스턴스에 대한 참조자)
문자와 문자배열 및 인코딩
•
ASCII vs UNICODE
◦
ASCII
▪
영문 알파벳을 사용하는 문자 인코딩 표준
▪
7비트를 사용하여 총 128개의 문자를 표현
▪
영문 대소문자, 숫자, 특수기호, 제어문자를 포함
▪
(1bit를 추가해서) 1바이트 체계로 매우 단순하고 가벼운 구조
◦
UNICODE
▪
전 세계의 모든 문자를 일관되게 표현하고 다룰 수 있도록 설계된 국제 표준 코드
▪
각 문자마다 고유한 코드 포인트를 부여하여 중복이나 충돌 X
▪
UTF-8, UTF-16 등 다양한 인코딩 방식을 통해 구현
▪
한글, 한자, 아랍어 등 전 세계의 문자를 포함하는 통합 문자 체계
•
문자 vs 문자열
◦
문자: 영문 혹은 한글 한 글자를 ‘문자’로 규정
→ char형은 한 문자(유니코드:2byte)를 저장하기 위한 자료형
→ char형은 정수 형식에 속함
◦
문자열: 문자를 연이어진 배열 형태로 나열하면 ‘문자(배)열’
→ char형 자료가 여럿 연이어진 배열 형태가 ‘문자열’
변수 이름 - 식별자 / 변수 종류 및 사용
•
변수
◦
연산식을 기술하는 시점에 값이 정해지지 않은 수
◦
구체화하지 않았거나 앞으로 변경될 가능성이 있는 수 (혹은 미지의 수)
◦
개발자가 메모리를 사용하는 가장 일반적인 방법
→ JVM의 메모리
•
stack: LIFO 선형구조
•
heap:
◦
구체적으로 결정되는 값에 따라 연산의 내용이 달라질 수 있는 원인으로 작용
◦
형식 이름 ; → 선언
◦
형식 이름 = 값; → 정의
•
상수
◦
연산식을 기술하는 시점에 값이 정해진 수
◦
값이 확정되어 앞으로 변할 가능성이 없는 수
◦
리터럴 (상수)
▪
‘A’, “Hello”, 3, 3.4F, 123.45
◦
심볼릭 상수
▪
final
•
변수 종류 및 사용
◦
지역변수
▪
접근성에 따른 분류
▪
static 선언이 없다면 자동변수이며 stack 사용
◦
매개변수
▪
함수 매개변수로 접근성은 지역
◦
인스턴수 변수
◦
클래스 변수 (참조자) + new → heap 영역
•
stack에서의 push/pop & class 변수의 heap 영역 존재
{
int a;
int b;
{
int c;
{
int d;
String hello = new String;
}
}
}
Java
복사
→ int a; int b; 가 stack에 가장 먼저 순차적으로 stack push되고 int d; 가 가장 마지막에 push된다.
([int a; int b; int c; int d; ])
→ } scope가 끝나면 stack에서 pop이 이루어진다.
→ new String은 heap 영역에서 클래스 변수를 생성한다.
→ hello 가 참조자로써 클래스 변수를 가르키고 있는데, }를 지나면 참조관계가 사라진다.
→ 그럼 new String은 참조관계가 사라진다. 이를 GC가 파악하고 memory에서 지워버린다.
콘솔 / 키코드 값 읽기 / Java 인코딩 규칙 / Scanner
•
CLI(Command Line Interface) 기반 HCI(Human Computer Interface)는 키보드 입력으로 구현
→ 키보드는 전선의 결합으로 되어있으며 압력이 가해졌을 시 scanning 된게 감지됨
→ 키보드 입력값을 표준값으로 변경; keycode
◦
keyboard → [I/O buffer] → CPU/RAM
◦
CPU/RAM → [I/O buffer] (flush) → monitor
•
콘솔 입력 keycode 값 읽기
◦
System.in.read()
▪
ABC 입력
▪
[A,B,C]
▪
입력된 값을 읽어서 1byte로 반환
⇒ 영문 keycode 값 읽기
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
int keycode;
keycode = System.in.read();
System.out.println(keycode);
keycode = System.in.read();
System.out.println(keycode);
keycode = System.in.read();
System.out.println(keycode);
}
}
'''
I/O = [] -> 'wait'
입력: ABCD
I/O = [65, 66, 67,68, 10]
65
66
67
'''
Java
복사
⇒ 한글 keycode 값 읽기 (unicode!)
◦
가’에 대한 UTF-8 인코딩 결과
◦
UTF-8은 유니코드 값을 1~4바이트로 가변 인코딩 (e.g., 가는 3바이트)
◦
Java에서 문자열의 끝은 NULL이 아님(※C개발자 주의)
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
int keycode;
keycode = System.in.read();
System.out.println(keycode);
keycode = System.in.read();
System.out.println(keycode);
keycode = System.in.read();
System.out.println(keycode);
}
}
'''
I/O = [] -> 'wait'
입력: 가
I/O = [234, 176, 128, \n]
234
176
128
'''
Java
복사
•
Java 인코딩 규칙
◦
Java 환경에서 문자열은 UTF-16 BE(Big Endian)로 인코딩
◦
문자열 처리 과정에서는 UTF-16 BE를Modified UTF-8로 변경해 처리
◦
문자열의 끝인 NULL(0x0000)은 본래 UTF-8 규칙으로 인코딩 시 0이지만 Modified UTF-8로 인코딩할 경우0xC080
•
Scanner
→ I/O buffer에서 연속된 byte를 int 혹은 double로 해석해야 하는 경우 사용하는 클래스
•
Scanner s = new Scanner
•
s.nextInt()
•
s.nextDouble
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner s = new Scanner(System.in); /* 키보드로 타자친걸 Scanner로 해석하겠다. */
int a = s.nextInt();
double b = s.nextDouble();
System.out.println("a: " + a);
System.out.println("b: " + b);
}
}
'''
3 3.4
a: 3
b: 3.4
'''
Java
복사
문자열 입/출력
•
10 test를 입력한다고 할때, 실질적으로 버퍼를 통해 전달되는 것은
→ ‘1’, ‘0’, ‘ ‘ ,’t’, ‘e’, ‘s’, ‘t’ ‘\n’이며 마지막 개행문자를 제외하고 인식
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String tmp = sc.nextLine(); > 개행문자가 나올때까지 읽는거
System.out.println(tmp);
}
}
Java
복사
•
위의 nextLine() 과 달리 whitespace 기준으로 끊어서 전달하는 next() 도 존재한다.
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner("Hello\r512\ntest\tTEST");
System.out.println(sc.next());
System.out.println(sc.next());
System.out.println(sc.next());
System.out.println(sc.next());
}
}
Java
복사
•
next() 를 호출하면 개행문자가 buffer에 남아서 코드가 의도된 대로 실행이 되지 않을 수 있다.
◦
input: 10
→ “1”, “0”만 next()가 처리 “\n”는 버퍼에 남음 > 아무 처리 없이 nextLine()호출하면 의도하지 않게 새로운 입력을 코드가 처리할 수 없게 됨
import java.util.Scanner;
public class Main {
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int data = sc.nextInt();
sc.nextLine();
System.out.println("data: "+data);
String tmp = sc.nextLine();
System.out.println("tmp "+tmp);
}
}
Java
복사
형식문자와 이스케이프
•
이스케이프 시퀀스
•
형식문자
◦
%d 10진수(정수형)
◦
%c 유니코드 한 문자
◦
%s 문자열
◦
%f 실수형
◦
%t 날짜시간
◦
%x 16진수
import java.util.Scanner;
public class Main {
public static void main(String[] args){
double data = 123.345;
System.out.printf("%f, %f\n", data, -data);
System.out.printf("%.1f, %.2f\n", data, data);
System.out.printf("%.3f, %.4f\n", data, data);
System.out.printf("%012.2f\n", data);
}
}
'''
123.345000, -123.345000
123.3, 123.35
123.345, 123.3450
000000123.35
'''
Java
복사
Calculator
연산자 기본 이론
•
연산자는 CPU 연산과 직결되는 문법
•
연산자 자체는 하나의 항
•
연산자의 우선순위와 결합성
◦
연산산자 결합성: 우선순위가 같은 경우 어떤 것을 먼저 연산할 것인지 나타내는 것
◦
3+4+5 연산에서 두 덧셈 연산은 우선 순위가 같고 L → R이므로 3+4를 먼저 수행
산술연산자
•
대표적인 2항 연산자
•
연산의 결과로 임시결과 발생
◦
3+4+5
▪
3+4의 임시결과인 7의 형식을 주의할 필요가 있음 (추후 연산에 활용됨)
•
정수 간 나눗셈의 결과는 반드시 정수가 되며 소수점 이하는 절사 (정보가 유실됨)
•
형승격 (Type Promotion)
◦
이형 간의 연산을 가능하게 하는 것
◦
임시결과에서 피연산자 표현범위 이상의 표현을 가능하게 함
▪
표현범위 더 넒은 걸 따라가도록
(문자열과 숫자 연산의 결과는 문자)
→ 5/2는 정수간 연산이라 정수를 임시 결과로 저장하지만
import java.util.Scanner;
public class Main {
public static void main(String[] args){
double result = 5/2;
System.out.println("Result :" + result);
}
}
'''
Result :2.0
'''
Java
복사
→ 5.0/2는 type promotion으로 2.5으로 저장
import java.util.Scanner;
public class Main {
public static void main(String[] args){
double result = 5.0/2;
System.out.println("Result :" + result);
}
}
'''
Result :2.5
'''
Java
복사
→ Integer의 최댓값에 +=1을 하면 음수로 바뀌지만 long type으로 promotion하면 더 넓은 범위 표현가능
import java.util.Scanner;
public class Main {
public static void main(String[] args){
int max = Integer.MAX_VALUE;
System.out.println("int max: " + max);
System.out.println("int max: " + (max + 1));
System.out.println("long max: " + (max + 1L));
}
}
'''
int max: 2147483647
int max: -2147483648
long max: 2147483648
'''
Java
복사
→ 문자열과 정수 연산 결과는 문자열
import java.util.Scanner;
public class Main {
public static void main(String[] args){
System.out.printf("5 +5 = %d\n", 5 +5 );
System.out.println("5 +5 = " + 5 +5);
}
}
'''
5 +5 = 10
5 +5 = 55
'''
Java
복사
•
나누기 연산
◦
5 - 2 → count+=1
3 - 2 → count+=1
1 → remainder
◦
따라서, 0으로 나누면 Endless 빼기 연산에 빠질 수 있음 (자체적으로 막지만, 실행될 경우 cpu 터짐)
대입연산자
•
대입 연산자
◦
단순 대입연산자는 두 피연산자 중 오른쪽 피연산자(r-value)의 값을 왼쪽 피연산자(l-value)에 저장하는 연산자
◦
l-value (메모리) = r-value
◦
l-value에는 overwriter가 발생하며 기존 값이 사라짐
import java.util.Scanner;
public class Main {
public static void main(String[] args){
Scanner s = new Scanner(System.in);
int a = 10;
int b;
b = a;
System.out.println(b);
}
}
Java
복사
→ 이름이 a인 변수에서 값을 read해서 이름이 b인 변수에 overwrite해라
•
final
import java.util.Scanner;
public class Main {
public static void main(String[] args){
Scanner s = new Scanner(System.in);
int a = 10;
final int b = 30; //read only -> 상수화
b = a;
System.out.println(b);
}
}
Java
복사
◦
변수이나 한 번 정한 값이 바뀌지 않도록 강제로 상수화
◦
// read only만 가능해짐 (l-value가 불가능해짐)
◦
컴파일러가 코드를 최적화 할 수 있도록 도와주며 코드의 직관성(읽기 좋은 코드) 및 유지보수성을 높여 줌
◦
심볼릭 상수를 정의하기 위한 문법
복합대입연산자
•
+= -= *= %= /= &= |= ^= ~=
◦
기능상 단순 대입 연산자와 산술 연산자, 비트 연산자가 조합된 연산자