Primitive vs Reference
자바는 객체지향 언어임. 모든게 객체임. 스레드도 객체고, 함수도 객체에 구현되어 있고, 변수에 저장하는 타입도 객체임.
그런 객체는 class 소스파일이 따로 있음. java.lang 에 구현되어 있다던가 하는거임.
하지만 이런 객체 설계도를 가지고 인스턴스를 만들어 사용하는게 아니라 다이렉트로 바로 정의 없이 사용가능한 특수한 애들이 있음. 그게 Primitive Data Type 원시 데이터 타입임.
그럼 얘네는 뭘 알고 뭘 믿고 누가 어떻게 사용하는건데? JVM이 해석하고 JVM이 알아서 씀.
main() 함수를 시작하라! 라는 매서드를 따로 만든것도 아닌데 JVM이 프로세스를 돌리자마자 첫빠따 메인스레드가 만들어져 돌아가고 바로 main()함수를 찾아 실행시키는것 처럼.
int, char, long ... 이런 단순 값 형태를 해석하는 데이터 전용 타입을 JVM이 따로 원시타입이라고 관리함.
자바의 모든 클래스는 Object를 상속받는다 -> O
그럼 프리미티브 타입은 Object 기능을 사용할 수 있다? -> X
그러한 클래스적인 기능들을 추가하면서 단순 값의 형태로 사용하기위한 전용 클래스가 있다? -> 그게 Wrapper Class인 Integer, Character, String 이런 애들임.
부동 소수점 연산시 오차를 주의하라.
개념상 같다고 생각하여 식을 짜다보면 오류가 날 수 있어. 왜냐하면 근사값이거든. 그걸 미리 정의해놓고 비교할때 그 안에 들어오는 녀석은 그냥 같다고 처리하도록 구현하는 것도 방법
double machine_epsilon_mount = 1E-5;
if( Math.abs(a-b) < machine_epsilon_mount ) //같은 경우다
else{ //다른 경우다 }
컬렉션
자바에서 제공하는 자료구조들을 컬렉션이라고 하고 대부분 java.util.* 에 존재한다.
배열
import java.util.Arrays; //임포트
int[] array = {1,2,3}; //선언1
int[] array2 = new int[] {1,2,3,}; //선언2
int[] array3 = new int[3]; //선언3
//배열의 선언시 특징은 선언과 동시에 크기와 안에 들어갈 내용까지
//스태틱하게 정해줘야만 오류가 아니라는 것
//이는 복사를 할때도 마찬가지이다. 안에 내용물의 사이즈와 동일하게 생성복사
array3[0] = 1; //배열의 사용 - 할당
array3[1] = 2;
array3[2] = 3;
System.out.println(Arrays.toString(array)); //배열의 사용 - 값참조
System.out.println(array3[0]);
리스트
배열의 상위호환인 리스트이다. 가변 길이 배열임. 그만큼 메모리도 속도도 조금은 느림.
맨뒤에 하나 추가할때는 O(1)
중간에 하나 추가할때는 O(n)
데이터 삭제 즉 길이 하나 줄어듬 O(n)
ArrayList<Integer> list = new ArrayList<>();
list.add(1);list.add(2);list.add(3);
System.out.println(list.get(2)); //인덱스2번 값 가져옴
해시맵
키-값 쌍을 저장하는 테이블
HashMap<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("cocoa", 3);
System.out.println(map);
if(map.containsKey("apple")){
System.out.println(map.get("apple"));
}else{
System.out.println("해당 키로 정의된 값은 없어요");
}
//이미 있는 키면 값을 덮어씌움
map.put("banana", 4);
//키로 키-값쌍 삭제
map.remove("cocoa");
문자열 : String vs StringBuilder | Mutable vs Imutable
핵심은 변수가 가리키는 메모리에 접근해서 값을 직접 바꿀 수 있냐 없냐의 차이라고 생각하는게 내 머리에 잘 들어 왔음.
Imutable == 바꿀 수 없는.
무엇을? = 안의 값을
값을 바꿀수 없는데 할당하면 무슨일이 일어나냐? = 할당하고자하는 값을 저장할 새로운 공간을 창출한채 가리켜야할 주소를 바꾸는 과정을 하고 앉아있음.
mutable == 바꿀 수 있는
무엇을? = 안의 값을
어떻게? = 처음 변수가 할당받은 주소는 고정이고, 그 메모리 주소에 접근하여 값을 요리 조리 바꿈.
String, Integer, Long 이런애들은 실상 단순 리터럴 값을 저장하는 객체이기에 Immutable 하게 구현된거 같음.
ArrayList, HashMap 얘네들은 얘네들 자체 주소는 진입점 같은거고, 내부적으로는 또 여러 값을 저장하기에 Mutable 하게 구현된거 같음.
Java 설계자가 이렇게 만들었음.
장단점 및 특이사항.
Immutable 한게 해당 주소에 접근하면 절대 변하지 않는 리터럴값을 참조 가능하니까 예외 고려할게 적다는거. 값을 교체하려고 하지 않는이상 이놈들의 값을 가지고 요리조리 사용할때 부담이 적음.
Mutable 한건 일단 접근은 하는데 이 타이밍 저 타이밍에 따라 내부 상태는 바뀔수 있기에 고려하여 주의해야함. 그리고 리터럴을 추가로 만들고 해시값 교체하고 하는 1차과정을 아예 안하기에 복잡한 자료형을 일일히 메모리 주소 바꿔주고 링크드 주소 바꿔주고 하는 과정 자체가 필요없으니 더욱 빠른 속도로 사용가능하게 해줌.
자바가 이러한 특성들을 고려하여 구현한거라 생각함.
다시 문자열부터
String str = "Hello"; //리터럴 고유키로 저장
str += " World"; //리터럴 고유키 교체해버림
str = str.replace("l", ""); //모든 l을 공백으로 교체 따라서 Heo Word
str = "Hello";
String str2 = "Hello";
if(System.identityHashCode(str) == System.identityHashCode(str2) ){
System.out.println("같아요"); //이게 출력
}
else{
System.out.println("달라요");
}
str += " "; //뒤에 그저 공백 추가
String str2 = "Hello";
if(System.identityHashCode(str) == System.identityHashCode(str2) ){
System.out.println("같아요");
}
else{
System.out.println("달라요"); //이게 출력.. 기존녀석에 공백하나 추가했다고 해시코드주소까지 달라진샘
}
//Immutable String을 교체할때 시간 복잡도
String s = "123";
System.out.println(System.identityHashCode(s)); //얘랑
s += "456";
System.out.println(System.identityHashCode(s)); //얘는 다름. 즉 객체가 새로 생성된거나 마찬가지 뒤에 3개만 추가되는게 아니란 소리
Immutable한 녀석을 가지고 Mutable한 조작을 할때 총 연산횟수는 문자열의 길이만큼 늘어남. 이게 뭔소리고 하니
먼저 이름은 똑같은 s이지만 실상 빈 메모리를 하나 확보함.
기존 s를 문자열만큼 탐색하며 하나씩 1복사 2복사 3복사를 함.
다시 4복사 5복사 6복사를 하여 리터럴 문자열 메모리를 하나 만들고, s에 메모리 주소를 딱 끼워뿌면 이게 String 문자열 추가 연산임.
추가할 만큼만 소비를 하는게 아니라 그때마다 교체해야되는 숙명에 따라 전체 문자열만큼 연산을 하는거.
StringBuilder를 사용하삼.
StringBuilder s = new StringBuilder();
for(int i = 1; i<=10000; i++){
s.append(i);
}
//그냥 append 해버리면 되는데 일반 String과 속도차이가
//무려 O(1)과 0(n) 차이임...
StringBuilder sb = new StringBuilder();
sb.append(103); //문자열로 알아들어감.
sb.deleteCharAt(1);//숫자 인덱싱 그냥삭제 여기선 "0"이 제거됨
sb.insert(1, 2); //1번째 인덱스에 2를 추가 -> 알아서 밀려남 즉 13에서 123이 됨.
이상한 메서드
private static class Node {
int dest, cost;
public Node(int dest, int cost) {
this.dest = dest;
this.cost = cost;
}
}
public static void main(String[] args) {
Node[] nodes = new Node[5];
nodes[0] = new Node(1, 10);
nodes[1] = new Node(2, 20);
nodes[2] = new Node(3, 15);
nodes[3] = new Node(4, 5);
nodes[4] = new Node(1, 25);
Arrays.sort(nodes, (o1, 02) -> Integer.compare(o1.cost, o2.cost)); // 얘를 A라하고
Arrays.sort(nodes, new Comparator<Node>() {
@Override
public int compare(Node o1, Node 02) {
return Integer.compare(o1.cost, o2.cost);
}
}); // 얘를 B라 할때 2개는 완전 같은 동작
}
일명 람다식이라고 하는 개잡기술인데.. 익숙하지 않은 사람은 가독성 제로이고, 익숙한 사람들은 가독성 향상이란다.
근데 나빼고 다 익숙하니까 다 람다식 쓰니까 내가 익숙해질 수 밖에 없다.
원리를 쪼개보자.
Arrays가 오브젝트 배열을 정렬하는 함수로 사용될때 원형은 다음과 같다.
static <T> void sort(T[] a, Comparator<? super T> c)
지정된 비교자에 의해 유도된 순서에 따라 지정된 객체 배열을 정렬합니다.
첫번째 인자에 오브젝트 배열이 있고, 두번째 인자에 비교자를 정의하면 되는데
비교자 Comparator의 정의는 다음과 같다.
int compare(T o1, T o2)
두 가지 인수를 비교하여 순서를 정합니다. o1이 작으면 - 같으면 0 크면 + 를 반환하는 컴패어 함수를 정의한 객체를 넣어주면 컴패어 함수를 이용하여 맞게 정렬한다.
즉 결국 Comparator<Node>() 객체를 정의해줘야하고, 거기 안에는 compare 함수를 구현해줘야되는데 Integer.compare로 cost를 비교하는 형태로 구현할거다.
라는걸 한줄로 표현한게 저 람다식...
어차피 이미 Object를 대상으로한 Arrays.sort()의 2번째 인자는 Comparator 니까 생략가능.
그 Comparator는 compare 함수를 구현해야되니까 생략가능 그래서 생략하고
(매개변수) => {구현로직} ;
형태로 다 생략한거... 이게 되려면 Comparator 처럼 명확하게 매개변수와 리턴값 만으로 어떤 함수를 또 어떤 객체를 지금 오버라이딩 하고 구현하고 있는지 명확해야함. 그게 조건임.
'Learn > 코딩 테스트 합격자 되기: 자바 편' 카테고리의 다른 글
[chapter 05 배열][저자추천문제] 배열의 평균값 | 배열 뒤집기 | n^2 배열 자르기 | 나누어 떨어지는 숫자 배열 (2) | 2024.12.19 |
---|---|
배열: 실패율 | 방문 길이 (0) | 2024.12.02 |
배열: 두 개 뽑아서 더하기 / 모의고사 / 행렬의 곱셈 (1) | 2024.12.01 |
자바로 코딩테스트 준비 (1) : 학습법, 효율적인 준비법, 알고리즘 효율 분석 (0) | 2024.12.01 |