OOP
1. Java에서 8가지의 Primitive type 제외한 모든 type은 Reference type(혹은 User-defined type)이다.
String은 Primitive type이 아니지만 유저 편의를 위해 String literal (ex."ABCD", "홍길동")을 미리 정의해두어 다음과 같은 문법이 가능하다. String s = "HAHAHA"; 엄밀히 따지면 String은 클래스이다.
2. Java는 C와 달리 boolean true가 1, false가 0을 의미하지 않는다. 심지어 boolean이 1비트가 아닐 수도 있다. (기본적으로는 1비트이지만 JVM의 종류에 따라 1비트 말고 다른 크기를 할당할 수도 있다.)
왜 그러한가?
프로그램이 메모리에서 CPU에 데이터를 전달할 때는 word 단위로 전달하는데, 이 word 단위가 거의 항상 1비트보다는 크다. (몇 바이트 이런 식) 따라서 만약 불리언 하나를 전달해야 한다면 1비트짜리 불리언를 전달하거나 1바이트짜리 불리언을 전달하거나 속도 상의 차이가 전혀 없을 수도 있다.
때문에 JVM에서 아예 불리언을 1비트말고 다른 크기로 할당하는 경우가 있다고 한다.
3. Java에서 boolean 타입은 아예 형변환이 불가능하다.
4. Java Reference는 C++처럼 메모리 주소를 직접 저장하는 게 아니라, 메모리 주소를 얻기 위한 일종의 Seed값이 저장되어 있다.
5. Java Reference는 클래스, 인터페이스, 어레이 세 가지 종류만을 가리킬 수 있다.
6. 16진수, 8진수를 사용하는 이유는 2진수로의 변환이 10진수보다 빠르기 때문이다.
7. Java 컴파일러는 String이 섞여있는 +연산을 문자열 연결로 판단한다. (컴파일러의 expression evaluation)
따라서 내부적으로 형변환이 일어난다.
단, 실제 연산 자체는 런타임에서 일어난다. 컴파일러는 어디까지나 두 타입의 연산결과가 어떤 결과를 반환할지를 미리 확인하는 것 뿐이다.
8. Java에서는 const 대신 final을 사용한다.
9. Literal이란? 상수의 일종으로 프로그램에서 직접 표현한 값이다. (ex. 1, 5, "dasd", '$')
10. 8, 16진수 정수 모두 int형으로 취급한다.
11. long 타입 리터럴은 끝에 L을 붙인다. ex. long l = 24L;
12. char은 16진수 4자리(2바이트)로도 초기화 가능하다. 단 앞에 \u를 붙여줘야 한다.
ex. char c = \u0041 -> c는 'A'
13. null 리터럴은 레퍼런스 타입에 대입 가능. (c++의 nullptr과 굉장히 유사)
14. char[] vs string?
둘은 다르다! C에서야 둘을 혼용하긴 했지만, Java에서 string은 클래스이다.물론 string 내부에 char[]이 사용되긴 한다.15. JDK7부터 숫자 내에 언더바(_) 사용이 허용됨. 가독성을 위함.ex. int i = 1234_5678 (12345678과 동일)
15. JAVA에서 static한 상수(final)은 초기화 할 때 생성자 대신 Static Initialization block에서 해주어야 한다.
16. short 와 byte를 더하면 이 값이 short로 형변환이 되는 게 아니라 int로 형변환된다.
즉, int보다 작은 단위의 정보들은 자동 형변환 시 int로 바뀐다.
17. println(10/4)는 2.5가 아니라 2를 출력한다. int끼리의 연산이기 때문.
18. (int)2.9 + 1.8 = 2 +1.8 = 3.8
(int)(2.9 + 1.8) = 4
19. Scanner은 사용자의 입력을 공백(스페이스바 혹은 \n 등 공백 문자)으로 구분된 토큰 단위로 받는다. (위치: java.util.Scanner)
ex. Hi 1 asd
-> Hi, 1, asd
20.
System.in은 키 입력을 바이트로 넘겨주기 때문에 Scanner이 직접 원하는 데이터로 변환해야 한다.
21.
Scanner.next()는 입력값을 문자열로 반환해준다. 때문에 내가 얻고 싶은 값에 맞추어 nextInt, nextBoolean 등을 사용하는 게 권장된다.
Scanner s = new Scanner(System.in);
String name = s.next(); // Scanner의 입력이 끝날 때까지(엔터) 대기.
int age = s.nextInt(); // Scanner에서 받은 값을 int화 해서 반환.
22.
Scanner.nextLint() 은 '\n'이 입력된 지점까지 읽고, '\n'을 제외한 그 앞 문자열을 반환한다.
ex. aaaa\nbbbb\n 이면 aaaa 반환
23.
Scanner.close()를 하면 scanner와 System.in이 끊기는게 아니라 System.in과 물리적 입력장치의 연결이 막혀버려 이후 다른 Scanner를 만들어도 입력을 받지 못한다!
(System.in은 자바 프로세스가 처음 실행될 때 JVM이 물리적 입력장치와 연결해 만들어놓는다)
Scanner s1 = new Scanner(System.in);
System.out.print(">>");
System.out.println(s1.next());
s1.close();
Scanner s2 = new Scanner(System.in);
System.out.print(">>");
System.out.println(s2.next()); // Exception 발생!
s2.close();
25. 일반적으로 연산자는 왼-오 순으로 처리되지만, 형변환이나 !연산, --/++ 연산자의 경우 오-왼 순으로 처리된다.
26. c++와 유사하게 condition ? opr1 : opr2 같은 연산이 된다.
27. 비트연산자
& - AND| - OR^ - XOR~ - NOT
28.
시프트 연산자에서도 자동 형변환이 일어난다!!
byte a = 5;
byte b = (byte)(a << 2);
여기서 a << 2가 실행될 때 2가 int이므로 a는 int로 자동형변환이 일어난다.
그러면 시프트 연산을 실행할 때
00000000 / 0000000/ 00000000 / 00000101을 좌로 2칸 이동하는 결과가 나오고,
이를 byte b에 대입하기 위해 다시 byte 강제 형변환을 해주는 것이다.
29. switch문의 case에 변수가 들어오면 안된다.
switch (b > 2) {
case a: // 에러.
}
30.
31.
Loop에서 Label을 활용하면 continue 할 때 자신 루프 이외의 다른 루프의 분기점으로도 이동할 수 있다.
OUTER:
for () {
for() {
continue OUTER;
}
}
일 경우 continue 시 바깥 루프로 이동한다. 물론 continue말고 break도 가능하다.
그러나 goto와 마찬가지로 권장되는 방법은 아니다.
32.
int[] arr;
int arr[];
둘다 허용되지만 전자가 권장된다.
33.
Java에서는 오브젝트가 전부 heap에 저장된다.
new 키워드는 heap 메모리를 할당한다.
34.
c/c++과 달리 java의 array는 배열"객체"이다.
즉 array는 오브젝트이고, 내부에 배열 이외의 다른 정보들도 들어있을 수 있다.
35.
heap에 무언가를 할당하고 값을 초기화를 안하면
자동으로 모든 비트가 0(null)으로 초기화된다.
때문에
intArr = new int [5]; // ex. 클래스 변수로써 선언되었다
println(intArr[2]); --> 0 출력된다
그러나
stack에 할당할 경우 0(null)으로 자동으로 초기화되지 않는다.
int[] intArr; // ex. 지역변수(함수 내)로써 선언되었다
println(intArr[2]); --> 컴파일 에러!
36.
Java에서 primitive type이 아닌, Reference type의 변수가 가리키는 것들은 전부 Heap에 있다.
(물론 Reference type 변수 자체는 stack에 있을 수 있다. 포인터가 힙 상에 존재할 수 있는 것처럼)
37.
포인터와 마찬가지로, java에서는 여러 개의 레퍼런스 변수가 같은 오브젝트를 가리킬 수 있다.
(이 레퍼런스 변수들이 힙에 있던 스택에 있던 상관 없다.)
38.
c에서 array를 포인터로 전달하면 sizeㅇ 대한 정보가 전달되지 않는 것에 비해,
java에서의 array는 클래스고, 해당 클래스 내부에 length라는 클래스 변수가 있기 때문에
array를 가리키는 레퍼런스만 전달해줘도 길이 정보가 같이 전달된다.
f(int[] a) {
a.length로 접근 가능
}
f(int* a){
a의 길이를 알 수 없다!
}
39.
함수 실행 시 스택에 함수 호출을 저장하는 단위를 stack frame이라고 부른다.
stack frame안에는 해당 함수 호출에 사용되는 변수들이 들어있다.
호출이 종료되면 stack frame 단위로 메모리를 해제한다.
40. (중요)
for-each loop (혹은 enhanced for loop)이 자바에도 있다.
for (int i : numbers) {
}
주의할 점:
foreach loop 내에서는 루프를 도는 오브젝트의 값을 바꿀 수 없다!
따라서 read - only일 경우에만 쓰는 게 권장되고,
실제로 관례적으로도 for each loop은 값을 읽을 때만 쓰는 게 원칙이다.
C++에서는
for (int &i : numbers){
}
같은 형태로 레퍼런스로 지정하면 값을 바꿀 수 있지만, 이는 권장되지 않는다.
Read only일 때만 사용하거나 아예 사용하지 않는 게 권장되며,
만약 불필요한 복사를 막기 위해 레퍼런스 타입으로 지정하고 싶다면
const reference로 설정하는 것이 권장된다.
41.
Java에서 2차원 배열을 설정하려면
int[][] intArray; 혹은 int intArray[][]; (전자 권장)
동적할당하려면
a = new int[2][3]; (크기를 지정해주어야한다)
초기화하려면
int[][] intArray = {{1,2},{2,3}};
C/C++와 유사하게, int[][]는 int[]들이 저장된 어레이와 int가 저장된 어레이들로 이루어져있는 것이다.
42.
Java 2차원 array의 length를 얻으려면
해당 어레이를 구성하는 작은 어레이들의 length를 더해줘야 한다.
43.
2차원 어레이를 동적할당할 경우
a = new int[2][]; 같은 문법도 가능하다.
왜냐? int[]형 변수 2개를 저장하는 어레이를 만들라는 말과 같기 때문.
단 사용하기 전에 a[0]와 a[1]을 할당을 별도로 해주어야 한다!!
이 때 만약 a[i]들의 길이를 다르게 설정해서 길이를 할당해주면
이를 "Ragged Array"라고 부른다.
만약 Ragged Array의 모든 요소들을 순회하고 싶다면
for loop 내에서 i < a.length; 를 사용하면 된다. (각 어레이마다 길이가 다르기 때문에)
44.
n차원 어레이는 다음과 같이 할당 가능하다.
int[][][] a = new int[2][3][2];
45.
함수 내에서 배열을 동적할당하고, 그 후 그 배열을 반환하는 구문도 가능하다.
이를 이용해 makeArray()같은 함수를 만들 수 있다.
static int[] makeArray() {
int[] tmp = new int[4];
return tmp;
}
int[] intArray;
intArray = makeArray(); // 가능!
46.
public static void main(String[] args) {
}
에서 args는 만약 컴파일 시점에서부터 인자를 전달해야한다면
(e.g. C:\> java Hello abc 3 5.7 // Hello.java를 실행)
인자들이 전달되어야 하므로 이 인자들을 받는 용도로 사용된다.
인자는 일반적으로 띄어쓰기로 구분하지만, 만약 하나의 인자에 띄어쓰기를 포함시키고 싶다면
"abc 3" 5.7 같은 형태로 전달해주면 된다.
47.
Java에서 casting은 여러 방법으로 할 수 있지만,
String을 int나 double로 변경할 때 자주 쓰이는 함수가
Integer.parseInt(), Double.parseDouble()이다.
48.
Java에서의 Exception 처리에는
try, catch, finally가 키워드로 사용된다.
그 중 finally는 에러의 발생 여부와 관계없이 항상 (심지어 중간에 return이 호출되더라도) 실행되는 블록을 의미한다.
try {
} catch (Exception e) {
} finally {
}
주의할 점)
try나 catch내에서 return이 실행되더라도 함수를 종료하기 전에 finally블록이 먼저 실행된다!
그러나 이를 하나의 기능으로써 활용하는 것은 권장되지 않는다.
코드가 불분명해지기 때문이다.
49.
에러의 전달 과정
OS -> JVM -> 앱 (catch 문에 아규먼트로 전달)
50.
인스턴스는 Heap에 위치한다.
그렇다면 이 인스턴스를 만들기 위한 클래스(틀)는 어디 위치할까?
바로 코드 영역(텍스트 영역)에 위치한다.
재미있는 점은 JVM은 필요한 대상(.class)를 그때그때 가져오는 방식이기 때문에
필요에 따라 클래스 파일(.class, 틀)을 코드 메모리 위에 올렸다가 내렸다가를 한다는 점이다.
주의)텍스트 영역에 코드가 올라가는다는 것은 그 텍스트를 바이트 코드로 바꾼 그 코드 자체가그대로 메모리에 올라간 다는 것을 의미하지,그 코드 내에 선언된 각종 변수/함수들에 해당하는 공간을 할당한다는 의미가 아니다.
51.
Primitive type은 힙에 저장되는 것 대신 생성 시 stack에 직접 저장된다.
(01001001이런 식으로)
52.
Test라는 클래스가 있을 때
Test t;
는 인스턴스를 생성하지 않는다.
(아마 포인터를 null로 초기화할 것으로 추정되지만 이는 확실하지 않음.)
53.
오버로딩: void Func(int a, int b) 와 void Func(float a, float b)가 공존하는 것.
오버라이딩: @Override (자바에서) 를 사용해 부모 클래스의 함수를 무시하고 재정의하는 것.
54.
package - 경로와 굉장히 유사, 디렉토리를 구분할 때 / 대신 .을 사용한다.
또 어떤 .java 파일이 어떤 패키지 내에 있다면 소스코드 상단에
package aaa; 같은 형식으로 코드를 추가해주어야함.
만약 aaa안에 ccc라는 패키지가 있고 그 안에 어떤 소스코드가 있으면 package aaa.ccc;로 표기해야함.
55.
접근지정자는 4가지이다.
(단 키워드는 default를 제외한 3가지이다. default는 아무 접근지정자를 안썼을 때 자동으로 지정된다.)
접근지정자는
멤버변수
멤버함수(메소드)
클래스 앞
이렇게 세 군데에 붙일 수 있으며, 여기서 클래스 앞에 붙일 때는 public과 default(공백) 두가지만 가능하다.
56.
접근지정자를 사용하는 방법:
항상 private부터 시작해서 차차 접근범위를 늘려주는 식으로 코딩해야 함.
getter, setter의 활용이 권장된다.
장점 - 디버깅 해야 할 구역이 좁아짐.
즉 public으로 해두면 public한 해당 요소를 사용한 모든 곳에 다 가서 디버깅해야하는데,
private으로 해두면 딱 그 클래스 안에서만 접근하면 되기 때문에 편리함.
*참고
자바에서는 멤버변수를 field라고도 한다
57.
static
<non-static한 멤버들의 특징>
1. 공간적 특성 - 멤버들은 인스턴스마다 독립적으로 별도 존재
2. 시간적 특성 - 필드와 메소드는 인스턴스가 생성 된 후 사용가능
3. 비공유 특성 - 멤버들은 다른 인스턴스에 의해 공유되지 않고 배타적
반면 static은
1. 공간적 특성 - 클래스 당 하나만 생성
2. 시간적 특성 - 클래스가 로딩될 때 공간 할당
> static은 Data 영역 메모리에 저장됨.
> Java에서는 어떤 .class파일이 JVM에 로딩되면 해당 클래스의 static 멤버들을 메모리에 할당함.
주의할 점은 한번 로딩되면 static 멤버들은 프로그램 종료 시 까지 계속 남아있다는 점이다.
3. 공유의 특성 - 동일한 클래스의 모든 인스턴스들에게 공유
주의: static하지 않은 함수에서 static한 변수에 접근할 수 있다. 이게 C++에서도 가능한지는 확실하지는 않음...
58.
int x = 5;
static void s1() { return x; }
>> 컴파일 에러! static한 메소드에서 nonstatic한 멤버를 접근하려하면 컴파일 에러가 발생
59.
final 클래스 - 클래스 상속 불가
final 메소드 - 오버라이딩 불가
final 멤버변수(필드) - 초기화 이후 값 변경 불가
60.
놀랍게도 메소드 뿐만 아니라 멤버변수까지 상속관계에서 오버라이딩 비슷하게
같은 이름의 여러 멤버번수가 공존할 수 있다!
즉 this.x와 super.x이 다른 값을 가질 수 있다!
그러나 당연하게도 이러한 방식은 권장되지 않는다
61.
자바는 다중 상속을 지원하지 않는다.
이유: 부모에 같은 이름의 함수 등이 존재할 경우 문제가 발생하기 때문
62.
자바의 모든 클래스의 최상위 부모는 java.lang.Object이다.
63.
C++과도 중복되는 내용으로,
자식을 생성할 때
자식 생성자 호출
부모 생성자 호출
부모 생성자 실행
자식 생성자 실행
종료
순으로 실행된다.
64.
A를 상속한 B가 있을때
A()가 정의가 안되어있으면 B b; 스타일의 구문을 실행할 때 컴파일 에러가 난다!
독특한 건 b = new B();처럼 실행하면 에러가 안난다! 명시적으로 B()를 사용한다고 표기했기 때문이다.
65.
위와 마찬가지로 A와 B가 있을 때
A()와 A(int x) 의 두가지 생성자가 있고
B()와 B(int x)의 두가지 생성자가 있을때
B(int x) {
this,num = x;
}
이렇게만 쓴 후
b = new B(5); 이렇게 하면 부모의 A(int x)가 호출되는게 아니라 A()가 호출된다!
이를 해결하려면
B(int x) {
super(x);
this,num = x;
}
이렇게 해주면 된다!
단 주의해야 할 점은 super()은 항상 생성자의 맨 앞에 적혀야 한다는 것이다.
66.
this()와 super()은 같은 생성자 내에서 같이 적힐 수 없다!
( this()는 이 클래스의 기본생성자를 의미)
67.
업캐스팅: 서브클래스 인스턴스를 슈퍼클래스 타입으로 형변환하는 것
다운캐스팅: 슈퍼 클래스 인스턴스를 서브클래스로 변환
여기서 만약 Student s = p; 하면 컴파일 에러가 난다!
그러나, 위 사진처럼 강제형변환을 한 후 대입했다고 하더라도 s.grade 등을 호출하게 되면 런타임에서도
에러가 날 수 있다.
왜냐하면 Student의 grade 멤버변수가 초기화되지 않았기 때문이다.
68.
instanceof 키워드:
69.
메소드 오버라이딩 시 접근 지정자는 더 넓은 범위의 것으로만 변경 가능하다.
(당연히 변경 안해도 된다)
ex.
class Human extends Animal 일때
Animal의 public eatFood() 를 Human에서 오버라이딩할 때
private, protected, default 로 바꾸는 게 전부 불가능하다.
Animal의 protected eatFood() 를 Human에서 오버라이딩할 때
public 으로 바꾸는 거나 그대로 protected 유지하는 것만이 가능하다.
오버라이드가 불가능한 경우
- final 메소드
- static 메소드
>> 이때 주의할 점은 Animal과 Human 모두에게 static void move()라는 함수가 존재할 수 있다.
그러나 이는 오버라이딩된게 아니라 별도의 함수 두 개가 공존하는 것이다.
고로 Animal.move() 와 Human.move()의 결과가 다르다.
만약 move() 위에 @Override annontation을 통해 컴파일러에게 오버라이드하겠다고 명시할 경우에는 에러가 난다.
- private 메소드
>> static과 유사하게, 둘은 완전히 별도의 함수로 존재한다. @Override를 붙이지 않는 이상 에러가 나지 않는다.
?.
인터페이스 - class 대신 interface 키워드 사용
interface A {
int i = 10;
void f(); (추상 메소드)
}
인터페이스는 오직 public 접근지정자를 사용가능함.
(즉 위의 예시처럼 비워둔다 하더라도 알아서 컴파일러가 public을 붙여준다.)
(마찬가지로 함수는 추상 메소드만 가능하므로 public abstract를 붙여주고
필드(멤버변수)는 오직 상수만 가능하므로 컴파일러가 public static final을 붙여준다.)
?.
클래스 상속은 extends로 표현했지만, 인터페이스는 implements라는 키워드를 사용한다.
class B implements A {
}
이 경우 B는 A 내의 모든 추상 메소드들을 구현해주어야 한다.
(참고: 이 때 만약 class C extends B 라면 굳이 B에서 구현안해주어도 된다. 대신 B객체는 new로 생성할 수 없다.)
안하면 컴파일 에러 발생!
class D extends E implements F,G,H {
}
같은 꼴도 가능하다.
인터페이스도 타입이기 때문에 위 예시에서
D obj = new D();
E obj = new D();
F obj = new D();
G obj = new D();
H obj = new D(); 다 가능하다!
그러나, 만약 G obj = new D();했다면 obj에서는 G에서 선언된 메소드만 사용할 수 있다.
인터페이스끼리도 상속이 가능하므로
만약 interface G extends J였다면 obj에서는 G, J에서 선언된 메소드들만을 사용할 수 있다.
(여기서 J도 인터페이스이고, implements 대신 상속을 나타내는 extends를 사용했다는 점에 유의.
참고: 아주 흥미롭게도 인터페이스끼리의 상속은 클래스와 다르게 다중상속을 지원한다!
즉 interface G extends J, K 가 가능하다!)
?.
추상 클래스 vs 인터페이스
요약: 외형은 같지만 용도가 다르다
인터페이스의 목적 -
1. 스펙을 주어 클래스들이 그 기능을 서로 다르게 구현할 수 있도록 하는 클래스의 규격 선언이며 클래스의 다형성을 실현하는 도구이다.
2. 일종의 컴포넌트 느낌으로 class Human implements Singable 같은 식으로 특수한 기능을 주고 싶을 때 사용 가능하다
?. <Java 8+에서의 인테페이스 내의 default 메소드 정리>
Java8부터는 인터페이스 내에서도 추상함수 대신 내부가 구현된 함수를 선언할 수 있게 되었다.
default 키워드를 사용하면 된다.
이걸 언제 사용하고 왜 사용하는가?
>> 인터페이스 자체에 부득이하게 함수를 추가해야 할때 사용한다.
멋대로 인터페이스를 변경해버리면 해당 인터페이스를 사용하는 모든 하위 오브젝트들을 다 수정해주어야 하기 때문이다.
만약 인터페이스를 구현(implements)한 클래스가 인터페이스의 default 메소드를 직접 호출하고 싶으면?
>> 클래스 상속과 유사하지만, super.키워드만을 사용하면 인터페이스에서 메소드를 찾는게 아니라 extends한 부모 클래스에서 찾기 때문에
인터페이스명.super.호출함수명();
식으로 써야 한다.
그러나 인터페이스가 extend한 또다른 인터페이스에 있는 메소드는 호출할 수 없다.
만약 구현한 인터페이스의 default 메소드와 부모 클래스의 메소드가 이름이 겹칠 경우 항상 부모클래스에 우선순위를 둔다. 즉 부모클래스의 메소드가 실행된다.
그 외 모든 경우에서는 이름이 겹치면 컴파일 에러가 발생한다. (이 경우 하위 클래스에서 @Override를 사용해 오버라이드를 해주어야 한다)
?. <Java8+에서의 인터페이스 내의 Static 메소드>
Java8+에서는 인터페이스 안에서 static 메소드의 사용이 가능해졌다.
이를 호출할 때는 인터페이스명.메소드명()으로만 실행 가능하고, 인스턴스명.메소드명()으로는 실행 불가하다.
?. <Java 9+에서의 인터페이스 내의 pritvate 메소드>
default나 static이 아니고, 그렇다고 추상메소드도 아닌 함수에 대해 인터페이스 내에서 private함수를 만들 수 있다.
사용용도는, default함수가 너무 길어질 경우 private으로 쪼개는 용도이며, 오직 이 용도이기 때문에 외부에서 호출이 불가능하다.
마찬가지로 인터페이스 내의 static 메소드가 너무 길어지면 이를 private static 함수들로 분할해 쪼개놓는 게 가능하다.
자바
1. 패키지 내의 소스의 최상단에는 package 경로; 가 필요하다.
2.
다른 경로(패키지)의 코드를 가져올때는
import 경로명.클래스명:
꼴로 가져올 수 있다.
ex. import lib.Calculator;
3.
서로 다른 위치의 같은 이름의 클래스를 동시에 import 했을 때는
부득이하게 full name을 다 써줘야 한다.
4.
모듈이란?
패키지보다 큰 개념으로, 패키지와 이미지 등의 리소스들을 담은 컨테이너를 의미함.
원래는 .jar이었지만 java 9+에서는 .jmod로 변경되었음.
5.
6.
java.lang의 Object 클래스는 모든 클래스들의 수퍼클래스이다.
모든 클래스가 강제로 상속한다.
hashCode()가 해당 객체의 id라고 보면 안된다. 객체가 달라도 hashCode가 같을 수도 있다. 애초에 반환형 int의 범위가 제한되어있다. hashCode는 해당 객체의 메모리 주소를 hash()함수에 넣은 반환값이다.
toString은 getClass와 hashCode를 내부적으로 사용해 결과값을 String으로 반환한다.
만약 별도로 원하는 값을 String화 하기를 원한다면 오버라이딩해야함.
7.
자바에서는
==보다는 equals()를 훨씬 자주 사용하며, ==은 메모리 위치를 비교하고,
equals()는 객체 자체의 내용을 비교한다.
equals()는 Object의 기본 메소드중 하나이다.
8.
primitive type들 하나하나에 대응되는 클래스들
내부에 static 함수들이 많은데, 대부분 형변환용 함수들이다.
Java 9부터 new를 사용해 Wrapper 클래스를 생성하는 게 불가능해졌다.
따라서 Integer i = new Integer(10); 같은 문법은 허용되지 않는다.
대신 Integer i = Integer.valueOf("10")과 같은 형식으로 static 메소드를 사용해 초기화한다.
9.
박싱과 언박싱 - 어차피 jdk1.5부터는 자동으로 박싱/언박싱이 된다.
10.
""를 사용해 만든 객체는 literal한 String객체이며 이는 힙 메모리에 저장된다.
또한 literal들은 스트링 리터럴 테이블이라는 것으로 JVM에 의해 별도로 추적,관리된다.
String s = "Hi";
String s2 = new String("Hi");
일때 s2는 지금 literal이 아닌, 별도의 String을 가리키는 중이다.
그런데 이를 literal을 가리키도록 할 수 있다.
s2 = s2.intern(); 하면, .intern()에 의해 해당 문자열과 똑같은 값의 literal을 리터럴 테이블에서 찾아 레퍼런스로 반환한다.
11.
즉 String을 변환하는 모든 메소드들은 원본 객체를 변경하는 게 아니라, 기존 객체를 변환한 새 객체를 만들어 return해준다.
때문에 문자열 변경 메소드들을 사용할 때는 항상 s = s.concat("Hi"); 와 같이 대입해주어야 한다.
12.
String.trim() -> 앞 뒤로 공백을 삭제해준다.
String.charAt(int i) -> char 리턴
String.replace(String a, String b) -> a를 찾는대로 다 b로 변경
13.
mutable한 String이 StringBuffer이다. 즉 StringBuffer에 조작을 하면 새로 객체를 만드는게 아니라 원본 객체를 수정한다. 때문에 값이 자주 변경되는 상황에서는 String보다 StringBuffer을 사용하고 마지막에 String으로 형변환하는 방식도 사용된다.
.capacity()는 최대 버퍼의 크기이고 .length()는 의미있는 값들의 갯수이다.
14.
StringBuilder와 StringBuffer은 거의 모든 게 동일하지만,
StringBuffer에서는 동기화가 보장되지만
StringBuilder에서는 동기화가 보장되지 않는다.
즉 멀티프로세스환경에서 StringBuffer은 한 시점에 한 스레드만이 접근 가능하다. (lock이 걸려있다)
15.
StringTokenizer: .split() 과 유사한 기능을 제공하는 클래스. java.util에 있다.
16. <컬렉션>
java.util에 들어있다.
Collection을 통해 자료구조를 사용할 수 있다. STL과 유사.
어떤 자료구조를 뜯어보면 구현은
1. 배열
2. 링크드 리스트
둘 중에 하나로 되어있다.
팁 : Vector을 새로 만든다고 할 때 Vector<Integert> v = new Vector<Integer>()꼴보다는
List<Integer> v = new Vector<Integer>()이 더 권장된다.
17. <제네릭>
primitive type은 사용이 불가능하다!!!!
따라서 Integer과 같은 Wrapper class를 사용한다.
왜 primitive을 사용 못하는가?
> generic은 컴파일러가 읽고 대입해주는 장치로,
런타임 시점에는 이미 적절한 타입들이 다 코드에 대입되어있는 상태다.
이를 구현하기 위해 Object를 사용하는데, primitive type은 Object를 부모로 갖지 않으므로 Wrapper class를 사용한다.
18. <Vector, ArrayList>
내부 구조를 array(배열)로 저장한다.
객체나 null을 넣을 수 있다.
멀티스레드 환경에서 Vector은 동기화가 되고 ArrayList는 동기화가 안된다.
19. <Linked List>
Vector와 기능은 동일하나 내부가 linked list로 구현되어 있다.
20. <HashMap>
각각의 키는 단 하나만 존재. (같은 값의 키 중복 불가)
21.
Collection에서의 enhanced for loop
i.next()를 하면 i를 증가시킴과 동시에 그 값을 반환한다.
22.
시험 출제 느낌: iterator로 전방향 순회를 하다가 도중에 갑자기 .add()를 사용해 요소가 추가되면, 추가된 요소는 무시하고 계속 순회한다.
역순회도 마찬가지.
23.
List타입에 Arrays.asList()로 만든 객체를 넣으면 리스트가 크기가 고정된다. (add/remove이 불가!)
24.
func(String... s) 같은 형태로 가변인자를 받을 수 있고, s[i] 처럼 배열처럼 접근해 쓸 수 있다. s.length도 사용가능하다.
25.
Set은 순서가 없다.
중복을 강제로 add하면 에러는 안나지만 추가가 안된다.
만약 커스텀 클래스를 Set에 넣고 싶다면 반드시 equals()와 hashCode()를 오버라이드해야한다.
이때!!!! 넣으려는 클래스 내에 커스텀 클래스형 자료가 존재한다면 그 클래스도 equals()와 hashCode()를 오버라이드 해주어야 한다!!!!!!
HashSet의 원리 -
value를 Hash(value) % size번 인덱스에 저장.
만약 해당 위치에 이미 뭐가 있으면 linked list로 연장.
이떄 linked list 내의 각 요소들과 equals() 연산 진행해서 중복 판정.
*HashMap에서 key값들의 중복을 막는 방식도 이와 동일함.
26.
TreeSet은 Tree로 구현한 Set이다.
HashSet과 마찬가지로 얘도 커스텀 클래스를 셋에 집어넣으려면 특정 함수(compareTo)를 오버라이딩해주어야 한다.
27.
Queue라는 인터페이스를 구현하는 클래스는 LinkedList이다.
insertm remove, examine에 대해
잘못된 값이 주어졌을 때 어떻게 처리하냐에 따라 두 가지 함수로 나뉘어있다.
또한 큐의 크기를 한정하려면 LinkedBlockingQueue나 ArrayBlockingQueue를 사용할 수 있다.
28.
Deque (인터페이스)
덱은 스택과 큐 모두로 사용할 수 있다.
Java에는 Stack도 있긴 한데 이것보다는 Deque를 사용하는 게 더 권장된다.
29. << 제네릭 >> (c++의 템플렛)
클래스에 제네릭을 사용할 수 있고,
클래스 내부의 메소드에도 제네릭을 사용할 수 있다.
그렇다면 둘의 이름이 같다면?
(참고: method2는 T를 사용만 할 뿐이지 제네릭 메소드는 아니다)
위 예시에서 method1은 제네릭 클래스 내부에 있는 제네릭 메소드이다.
method1에서 빨간 T와 검정 T가 겹치는데, 겹칠 경우 메소드의 제네릭이 우선시된다. (단순이 이름이 겹친다고 컴파일 에러가 발생하는 건 아니고, 위의 예시에서는 검정 T에 빨간 T를 대입하려 해서 컴파일 에러가 발생한 것)
30.
Bounded Type Parameter
<T> 대신 <T extends Number> 이라고 적으면 Number을 포함한 Number을 상속하는 객체들만을 T로 받을 수 있다.
여기에 추가적인 제약을 결려면
<T extends A & B & C> 형태로 걸 수 있다. (당연히 다중상속이 안되니까 이 중 하나 빼고는 나머지는 인터페이스다.)
31.
이건 문법적으로 안된다!
즉 제네릭은 상속 관계와 관계없이 딱 정해진 형태에 맞는 것을 입력해야 한다.
즉, Box<Number>이 가리키는 것을 Box<Integer>이 가리키면 안된다! (Vice versa)
32.
WIldcard와 Subtyping !!중요
바로 위에서 언급한 문제를 해결하는 방법이 WIldcard이다.
f(List<Number> l)과 f(List<Integer> l)을 굳이 따로 만들지 않아도 되게 해주는 방식이다.
단, 이는 매개변수나 변수 선언같이 제한적인 경우에서만 쓸 수 있다.
f(List<?> l)
List<?> l = new LIst<Integer>();
WIldcard에도 extends 같은 문법을 쓸 수 있다.
List<? extends A> 같은 사용이 가능.
여기에 더해 Wildcard에는 super을 쓸 수 있다.
이건 extends와 정반대의 개념으로,
List<? extends D>라면 D를 포함한 D의 부모 클래스들만을 타입으로 전달할 수 있다.
와일드카드를 사용할 때 만약 extends를 쓰지 않고 그냥 <?>로만 적어두었다면 Object의 메소드밖에 호출할 수 없다. 때문에 extends로 최대한 범위를 좁혀놔야 한다. (super을 사용해도 Object의 메소드밖에 사용할 수 없다)
반면 리스트에 요소를 추가하려면 무조건 super을 사용해야 한다. (즉 <?> 또는 <? extends A> 꼴은 리스트에 요소를 추가하는 연산을 할 수 없다.)
33. Nested Class
파이썬처럼 자바에서도 클래스 내에서 클래스 선언이 가능하다.
Nested에도 static을 붙이거나 안붙일 수 있다.
static Nested 일 때만 Nested 내부에 static 멤버들을 선언할 수 있다.
Nested에는 public default protected private 모두 붙일 수 있다. (원래 클래스는 public default 2개임)
Nested에서도 Outer Class의 private 필드/메소드를 사용이 가능하다.
외부에서 Static한 Nested 클래스를 언급할 때는 Outer이름.Inner이름 형태로 써야한다.
그런데 Static하지 않은 Nested 클래스를 언급할 때는
인스턴스명.Inner이름 형태로 써야한다. 즉 Outer 클래스형의 인스턴스가 반드시 미리 존재해야 한다.
static Nested 클래스는 Outer클래스가 선언되지 않았더라도 인스턴스 생성이 가능하다.
단, 이 경우 Outer클래스의 static 멤버에만 접근이 가능하다. (생각해보면 당연하다. static함수에서 static한 멤버에밖에 접근하지 못하는 것과 같은 이유)
만약 Nested 클래스 안에서 Outer 클래스의 인스턴스 멤버에 접근하고 싶으면 그냥 바로 this.를 쓰면 안되고, 바깥클래스명.this.xxxxx꼴로 접근해야 한다.
34. Local Class
어떤 함수 내에(지역 내에) 클래스 선언을 할 수 있다.
당연히 static 키워드나 접근지정자는 붙일 수 없다.
또한 해당 클래스는 해당 지역(위 예시에서는 method 함수 안)에서만 사용이 가능하다.
Local 클래스에서는 같은 지역의(같은 함수의) 변수에 접근은 가능하나 그 값을 수정할 수 없다.
35.
Anonymous class
쉽게 말해 new로 초기화를 해주면서 동시에 중괄호로 그 클래스 내부의 세부적인 사항들을 오버라이딩해주어 새로운 클래스 타입을 만들어내는 것이다.
위의 예시에서는 SuperClass의 method를 오버라이딩한 새로운 클래스 타입의 객체를 생성하고 있다.
(이 객체타입은 이름이 없으며, 완전히 유일무이한 클래스 타입이 된다.)
36.
정적 초기화 블록
생성과 동시에 초기화가 안 될 경우 사용한다.
처음 멤버에 접근하는 일이 발생하는 경우 초기화한다.
즉 위 예시에서는 우측의 arr3[cnt]가 호출되는 시점에 처음으로 초기화된다. 그전까지는 static의 공간만 할당되어 있다.
37.
인스턴스 초기화 블록
정적 초기화 블록과 유사함. non static 멤버의 초기화가 한번에 안될 경우 사용한다.
단, 초기화 시점은 처음 접근할 때가 아니라, new로 생성될 시점이다.
https://mia-dahae.tistory.com/85
38.
Unchecked Exception: 컴파일러가 미리 확인을 안해주는 Exception. 즉 런타임에서 발생한다.
checked는 컴파일러가 미리 확인해준다.
39.
함수 끝에 throws xxxx을 달아 어떤 Exception들을 던질 수도 있는지 미리 정의할 수 있다. 이렇게 적는 다는 것은 상위 함수로 해당 Exception을 던지겠다(던질 가능성이 있다)는 뜻이기도 하다.
위 예시처럼 만약 main에서 상위(JVM)으로 exception을 던지면 프로그램 실행을 중단하고 exception을 출력한다.
즉 위 상황에서는 throws를 통해 예외를 던질 수 있겠다고 경고했음에도 try catch로 싸지 않았으므로 컴파일러에서 이를 잡아내어 컴파일 에러를 낸다.
throw catch에서 exception을 받아 .getMessage()로 exception의 내용을 출력할 수 있다.
아니면 일반적인 콘솔 에러 화면을 출력하고 싶으면 .printStackTrace()를 사용하면 된다.
익셉션을 상속해 exception을 만들 수도 있다.
try-with-resources 구문:
try 문이 끝나면 close해주어야 하는 것들을 굳이 귀찮게 finally 블록 안에 넣는 대신 다음과 같이 표기할 수 있다.
sc는 자동으로 close된다.
40.
Object의 메소드인 clone을 오버라이딩해서 clone 함수를 만들 수 있다.
이때 주의할 점: 복사 메소드를 만들 클래스에 implements Cloneable을 해주어야 한다.
또한 Clone할 객체 내부에 다른 객체들이 있으면 그 객체들도 clone을 만들어주어야 한다.
'lang > java' 카테고리의 다른 글
[Java] Package란? (0) | 2021.01.09 |
---|---|
[Java] Coupling, Interface (0) | 2021.01.09 |