Open skarltjr opened 3 years ago
Just In Time. 프로그램을 실제 실행하는 시점에 번역하는 컴파일 기법이다. javac의 컴파일과 JIT의 컴파일은 다르다. 자바컴파일러(javac)는 자바 소스코드를 바이트코드로 변환하고 JIT는 변환된 바이트코드를 해석하고 실행한다.
같은 코드를 매번 해석하지 않고 실행할 때 컴파일 하면서 코드를 캐싱 한다. 사전에 정의된 임계치에서 시작하여 호출될 때마다 감소시키는 방식으로 자주 사용되는 메서드를 찾는다. 자주 사용되는 메서드는 저장해서 해석하지 않고 바로 실행시키는 방식으로 성능을 향상시킨다.
그림을 보면 jit컴파일러는 jvm의 일부.
java는 jvm으로 인해 os에 독립적인데 그 중 바이트코드를 해석하는 jit 컴파일러가 jvm에 속한다.
즉 바이트코드를 os에 따라 달리 해석 - jit compiler
-JVM JRE JDK java는 os에 독립적 그러나 jvm에게 종속적
jit 컴파일러
-JVM 의 구성요소는 크게 3가지로 구성 되어있다. 클래스 로더 시스템 (Class Loader) 메모리 (Jvm Memory) 엔진 (Execution Engine)
클래스 로더 시스템 (Class Loader)
메모리 (Jvm Memory)
엔진 (Execution Engine) 실행엔진은 말 그대로 클래스 로딩 과정을 통해 런타임 데이터 영역에 배치된 바이트 코드를 명령어 단위로 읽어서 실행. 이제 이 과정을 수행하고 기계가 읽을수 있는 코드로 변경을 해주는데, 이때 사용되는게 인터프리터와 JIT컴파일러 이다.
-JDK / JRE
-JDK JDK는 자바 프로그램을 작성하고, JRE를 실행하는데 필요한 툴들을 가지고 있다. 그리고 컴파일러 (javaC) 와 자바 어플리케이션 런처, Appletiviewer 등을 포함하고 있다. 컴파일러는 자바 코드르를 바이트 코드로 변경을 해주는데 이는 JVM이 읽을 수 있는 언어로 변경해주는 것이다. 자바 어플리케이션 런처는 JRE를 실행시키는데, 필요한 클래스나, 메인 메서드를 로딩한다. JDK > JRE > JVM 이런식으로 가지고 있는 것이다. 그래서JRE와 개발에 필요한 툴을 JDK가 제공해 주는 것이다.
-JRE JRE는 클래스 라이브러리, JVM, 여러 Supporting 파일들을 가지고 있다. Debuger와 Compiler과 같은 개발 도구는 포함되어있지 않는다. 즉, JRE는 소스코드를 읽기 위해, JDK는 소스코드를 작성하기 위한
자바의 PRIMITIVE TYPE(기본 타입/ int long ....) vs REFERENCE TYPE(Integer...)
-자바의 타입추론 var 타입추론은 말그대로 개발자가 변수의 타입을 명시적으로 적어주지 않고도, 컴파일러가 알아서 이 변수의 타입을 대입된 리터럴로 추론하는 것이다. Var는 초기화값이 있는 지역변수 (Local Vairable)로만 선언이 가능하다.
잘못된 var 사용
클래스
객체 (객체란 속성(상태)과 기능(동작)을 가진 덩어리)
인스턴스 (생성된 객체, 어떤 클래스에 속하는 각각의 객체)
-자바의 배열 배열 타입은 클래스가 아니다. 하지만, 배열의 인스턴스들은 Obejcts(객체)이다. 즉 배열은 ‘java.lang.Objects’ 에 상속 받는다. 그래서 배열은 ‘Cloneable interface’ (객체 자신의 메모리를 복사하는 Object의 인터페이스)를 implement 받는다. 그리고 CloneNotSupportedException 을 따로 throw로 예외처리를 해주지않고도 완벽하게 보장하는 clone() 메소드를 오버라이딩 한다.
-배열의 직렬화 배열은 또한 ‘Serializable (자바의 직렬화, 자바 시스템 내부에서 사용되는 Object , Data를 외부에서 사용할 수 있게 바이트 형태로 변환하는 기술, 즉 JVM에서 데이터를 저장하는 스택, 힙에 쌓여있는 데이터를 바이트 형태로 변환한다고 생각하면 된다.)’ 인터페이스를 implements 한다. 그러므로 배열을 직렬화 시킨다면, 어떤 배열이든 직렬화가 가능하다. Array type widening conversions Array (배열)은 기본적으로 ‘Object’를 상속 받고, ‘Cloneable’ 과 ’ Serializable’ 인터페이스를 implements 하기 때문에, 모든 배열 타입은 다음 세가지 유형중 하나로 변경이 확장 할 수 있다. 하지만 특정 배열 타입은 다른 배열 타입으로 확장할 수 있다. 만약, 어떠한 배열이 T 라는 레퍼런스 타입을 타입으로 지정하고 있고, T타입에서 S타입으로 할당 할 수 있다면,T[ ] 배열은 S[ ] 배열로 할당 할 수 있다.
-자바 연산자 중 equals - 동등성 / 동일성 - (== 연산자 (EqualsOperators))
‘==’ 연산자는 기본적으로 프리미티브 타입에 한해서 두 피연산자의 값이 같으면 true, 아니면 false를 리턴한다고 생각하면 된다. 이는 프리미티브 타입에 한해서 ‘Value(값)’이 서로 같은지 비교를 한다. 만약 프리미티브 타입이 아닌 ‘레퍼런스 타입’은 각 객체의 참조 주소를 비교하게 된. 결국 두개의 값이 같은지 판단하는게 아니라, 두개의 주소가 같은지 판단하는 것이다.또한 String 문자열은 서로를 비교할 수 없다.
String st1 = new String("aaa");
String st2 = new String("aaa");
System.out.println(st1==st2); // false 객체간 == 은 주소값을 비교
System.out.println(st1.equals(st2)); // true equals는 객체의 값을 비교
‘==’연산자가 같은 타입이지 않을 때, 예를들어보자 만약 short 타입과 long 타입이 있다고 가정해보자 어찌 됐든 이 둘을 비교하기 위해서는 서로 타입이 같은 상황에서 비교가 가능하다. 그러므로 8바이트 짜리를 4바이트로 변경하는 건 위험
-Instanceof 연산자(instanceof operator) 프리미티브타입 x / 레퍼런스타입만 가능
System.out.println("str" instanceof String);//true
System.out.println("" instanceof Object);//true
System.out.println(null instanceof Integer); // null은 instanceof 어디에도 false
Object o = new int[]{1, 2, 3};
System.out.println(o instanceof Object);//true
System.out.println(o instanceof int[]);//true
정리 자바에서 정의하고있는 hashCode규약 equals() 메소드가 true 이면 , 두 객체의 hashCode값은 같아야한다. equals() 메소드가 false 이면 , 두 객체의 hashCode값이 꼭 다를 필요는 없다.
위에서 equals 매서드가 값에 따라 두 객체의 동등성을 비교한다고했다. 그러나 기본 equals매서드는
public boolean equalse(Object obj) { return this == obj; }
이 경우 당연히 객체 간 ==비교 -> 주소값비교 -> 여기선 두 객체를 생성했으니 서로다른 주소 ->false
public class main {
public static void main(String[] args) {
A a = new A(1);
A b = new A(1);
System.out.println(a.equals(b)); // false
}
}
즉 객체간 == 비교 -> 주소값비교다.
올바른 equals를 위해 오버라이딩이 필요하다.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
A a = (A) o;
return value == a.value;
}
@Override
public int hashCode() {
return Objects.hash(value);
}
객체 해시코드란 객체를 식별하는 하나의 정수값을 말한다. Object의 hashCode() 메소드는 객체의 메모리 번지를 이용해서 해시코드를 만들어 리턴하기 때문에 객체 마다 다른 값을 가지고있다. 객체의 값을 동등성 비교시 hashCode()를 오버라이딩 할 필요성이 있는데, 컬렉션 프레임워크에서 HashSet, HashMap, HashTable은 다음과 같은 방법으로 두 객체가 동등한지 비교한다.
우선 hashCode() 메소드를 실행해서 리턴된 해시코드 값이 같은지를 본다. 해시 코드값이 같으면 equals() 메소드로 다시 비교한다. 이 두개가 모두 맞아야 동등 객체로 판단한다.
롬복
@EqualsAndHashCode
equals와 hashcode를 만들어 주는 것
equals: 두 객체의 내용이 같은 지 확인
hashcode: 두 객체가 같은 객체인지 확인
Tip.
@EqualsAndHashCode(of="id"): 연관 관계가 복잡해 질 때, @EqualsAndHashCode에서 서로 다른 연관 관계를 순환 참조하느라 무한 루프가 발생하고, 결국 stack overflow가 발생할 수 있기 때문에 id 값만 주로 사용
-자바의 클래스
클래스란?
-익명클래스
public class Pet{
String name = "돼지";
public String getName(){
return name;
}
}
public static void main(String[] args){
Pet pet = new Pet(){
String name = "익명 돼지";
@Override
public String getName(){
return name;
}
};
System.out.println(pet.getName()); // 결과 : 익명 돼지
}
-그러나 자바에서 기본 클래스와 익명클래스는 동일하지 않다. 같은 놈이라고 생각하지않는다.
-NEW 키워드
ex) Study라는 클래스가 있을 때
Study study; --> 는 선언이 된건가? x
Study study = new Study(); 로 new / 생성자로 초기화 해줘야한다.
-그런데 String 같은 레퍼런스 타입도 클래스로 정의 되었는데 왜 new를 사용하지 않는가? new 생성은 Heap 메모리 영역에, ' = " "' 은 상수 풀 (Runtime constant pool)에 저장
String asd = "qwe";
String qwe = new String("qwe");
System.out.println(asd == qwe); // false
System.out.println(asd.equals(qwe)); //true
-자바의 생성자 생성자는 이전에 설명한 new 연산자와 사용하고, 객체를 생성하는 역할과 객체 초기화 역할을 한다. 생성자가 제대로 실행도지 않는다면, 객체의 주소값이 리턴도지 않을 뿐더라, 객체가 heap에 올라가지도 않을 것이다
-상속 extends 이미 존재하는 클래스를 기반으로 새 클래스를 만드는 방법.
-자식 클래스가 부모 클래스의 한 종류(is-a) is-a 관계 : 상속관계, 부분집합 관계
-부모와 자식이 있을 경우 부모부터 초기화된다
-다중 상속 자바는 다중 상속을 지원하지 않는다.
부모의 멤버변수,함수 접근- private protected public
★부모에서 자식으로 타입변환은 안전하지만
자식에서 부모는 컴파일러가 오류를 발생시킨다. +부모가 동일해도 형제끼리는 캐스팅 할 수 없다.
-상속의 장단점
-장점 재사용성 코드 중복 줄어든다 관련 코드를 한 파일로 관리할 수 있다
-단점
상속 단계가 증가하면 추상화하기 힘들다 잘못된 상속
-super 키워드 super는 현 객체의 부모. super()는 부모 생성자 호출
-다이나믹 메소드 디스패치 (Dynamic Method Dispatch) =동적함수
정적 디스패치 컴파일 시점에 어떤 메서드가 실행될지 알 수 있다.
동적 디스패치 method가 ★오버라이드 되어있는 경우 컴파일 시점이 아닌 실행시점에서 어떤 메소드를 실행할 지 결정된다.
-추상클래스 인스턴스를 만들 수 없는 클래스 (인스턴스를 만들 수 있는 클레스는 구체 클래스) 다른 클래스의 부모 클래스가 될 수 있다 반드시 추상 메서드가 있을 필요는 없다
-Final 키워드 클래스 앞에 붙는 final 더 이상 상속하지 못함
-Static 1.클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통적으로 사용해야하는 것에 static을 붙인다.
static이 붙은 멤버변수는 인스턴스를 생성하지 않아도 사용할 수 있다.
public class main {
public static void main(String[] args) {
String name = new pet().name;
new per.xxxxxxxxxxxxxx; 사용불가 // static이 붙은 메서드(함수)에서는 인스턴스 변수를 사용할 수 없다.
System.out.println(name);
}
public static class pet{
String name = "qwe";
}
public class per{
String name2 = "qweasd";
}
}
3. static이 붙은 메서드(함수)에서는 인스턴스 변수를 사용할 수 없다.
- static이 메서드는 인스턴스 생성 없이 호출가능한 반면, 인스턴스 변수는 인스턴스를 생성해야만 존재하기 때문에... static이 붙은 메서드(클래스메서드)를 호출할 때 인스턴스가 생성되어있을수도 그렇지 않을 수도 있어서 static이 붙은 메서드에서 인스턴스변수의 사용을 허용하지 않는다. (반대로, 인스턴스변수나 인스턴스메서드에서는 static이 붙은 멤버들을 사용하는 것이 언제나 가능하다. 인스턴스변수가 존재한다는 것은 static이 붙은 변수가 이미 메모리에 존재한다는 것을 의미하기 때문이다.)
4. 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다.
- 메서드의 작업내용중에서 인스턴스 변수를 필요로 한다면, static을 붙일 수 없다. 반대로 인스턴스변수를 필요로 하지 않는다면, 가능하면 static을 붙이는 것이 좋다. 메서드 호출시간이 짧아지기 때문에 효율이 높아진다. (static을 안붙인 메서드는 실행시 호출되어야할 메서드를 찾는 과정이 추가적으로 필요하기 때문에 시간이 더 걸린다.)
5. 클래스 설계시 static의 사용지침
- 먼저 클래스의 멤버변수중 모든 인스턴스에 공통된 값을 유지해야하는 것이 있는지
살펴보고 있으면, static을 붙여준다.
- 작성한 메서드 중에서 인스턴스 변수를 사용하지 않는 메서드에 대해서 static을
붙일 것을 고려한다.
예제
class MemberCall {
int iv = 10;
static int cv = 20;
int iv2 = cv;
// static int cv2 = iv; 에러. 클래스변수는 인스턴스 변수를 사용할 수 없음.
static int cv2 = new MemberCall().iv; // 굳이 사용하려면 이처럼 객체를 생성해야함.
static void classMethod1() {
System.out.println(cv);
// System.out.println(iv); 에러. 클래스메서드에서 인스턴스변수를 바로 사용할 수 없음.
MemberCall c = new MemberCall();
System.out.println(c.iv); // 객체를 생성한 후에야 인스턴스변수의 참조가 가능함.
}
void instanceMethod1() {
System.out.println(cv);
System.out.println(iv); // 인스턴스메서드에서는 인스턴스변수를 바로 사용가능.
}
static void classMethod2() {
classMethod1();
// instanceMethod1(); 에러. 클래스메서드에서는 인스턴스메서드를 바로 호출할 수 없음.
MemberCall c = new MemberCall();
c.instanceMethod1(); // 인스턴스를 생성한 후에야 인스턴스메서드를 호출할 수 있음.
}
void instanceMethod2() { // 인스턴스메서드에서는 인스턴스메서드와 클래스메서드
classMethod1(); // 모두 인스턴스생성없이 바로 호출이 가능하다.
instanceMethod1();
}
}
패키지는 연관된 클래스를 담는 컨테이너 역할을 한다. 패키지 내에는 외부로부터 접근 가능한 클래스들과 특정 목적을 위해 접근이 제한된 클래스가 존재한다.
livestudy.week7.Test , livestudy.week6.Test
protected : 같은 패키지에 존재하는 클래스와 자식 클래스에서 접근 가능 default : 같은 패키지에 존재하는 클래스에서 접근 가능
// util 패키지의 모든 클래스 가져오기
import java.util.*;
// 일반적으로 클래스명은 2개의 패키지에 동일한 이름의 클래스가 존재할 때 구별하기 위해 쓰인다.
import java.util.Date;
import my.package.Date;
// 패키지 이름은 파일이 저장된 디렉토리의 이름과 반드시 같아야 함
package myPackage;
public class MyClass{ public void getNames(String s){ System.out.println(s); } } 다른 패키지에서 import 해서 사용할 수 있다.
// myPackage에 존재하는 MyClass
클래스를 import해서 사용할 수 있다.
import myPackage.MyClass;
public class PrintName{ public static void main(String args[]){ String name = "Pikachu"; MyClass mc = new MyClass(); mc.getNames(name); } }
#### 5. static import 사용하기
- 임의의 패키지의 클래스에서 public static으로 정의된 멤버(필드나 메서드)를 사용할 때, 클래스명을 언급하지 않고도 사용할 수 있다.
#### 6. 디렉토리 구조
패키지명은 클래스들을 저장하기 위해 사용하는 디렉토리의 구조와 연관되어 있다.
- 실제 디렉토리 구조는 패키지명과 동일하게(패키지\하위 패키지) 저장되어 있다.
com.zzz.project1.subproject2패키지의 MyClass 클래스가 존재한다면, 실제 클래스 파일은 다음과 같이 저장되어 있다.
$BASE_DIR/com/zzz/project1/subproject2/MyClass.class
$BASE_DIR은 패키지의 기본 디렉토리가 된다.
- 기본 디렉토리($BASE_DIR)은 파일 시스템의 어디에나 위치할 수 있다.
따라서 자바 컴파일러와 JVM은 기본 디렉토리의 위치를 알고 있어야 한다.
이를 위해서 필요한 환경 변수(environment variable)을 CLASSPATH라 부른다.
- CLASSPATH는 커맨드 쉘이 실행할 프로그램을 찾을 수 있게끔 PATH를 알려주는데 사용된다.
#### CLASSPATH란?
`import org.company.Menu;`
해당 명령어의 의미는 org.company 패키지에 있는 Menu라는 클래스를 현재 클래스에서 사용할 수 있게 하는 것이다.
JVM은 Menu클래스의 위치를 찾아서 해당 클래스의 인스턴스를 생성한다.
`Menu menu = new Menu();`
그럼 JVM은 어떻게 해당 클래스를 찾는 것일까?
만약 JVM이 Menu 클래스를 찾기 위해 존재하는 모든 클래스를 검사해야 한다면 매우 비효율적일 것이다. 그러므로 우리는 CLASSPATH 변수를 사용하여 해당 클래스가 위치한 곳을 JVM에게 알려준다.
만약 Menu 클래스가 dir이라는 디렉토리에 존재한다면, Menu 클래스의 전체 경로는 dir/org/company/Menu가 된다.
이 때 dir이라는 디렉토리를 classpath 변수로 등록해 놓고, 나머지 정보(org/company/Meny)는 import 명령어를 통해 제공해주므로서 외부 패키지의 클래스를 가져와 사용할 수 있게 된다.
#### 접근제어자
![화면 캡처 2021-01-03 013702](https://user-images.githubusercontent.com/62214428/103461774-379c7180-4d64-11eb-87e3-247555525a01.png)
![화면 캡처 2021-01-03 014159](https://user-images.githubusercontent.com/62214428/103461888-efca1a00-4d64-11eb-8ec8-851440fd7ddc.png)
####
- 모든 클래스는 패키지에 속해있다.
- 만약 파일 내에 패키지가 명시되어 있지 않다면, 해당 클래스 파일은 특별한 unnamed package(defualt)로 이동한다.
인터페이스는 일종의 추상클래스이다. 인터페이스는 추상클래스처럼 추상메소드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 일반 메소드 또는 멤버변수를 구성원으로 가질 수 없다. 오직 추상메소드와 상수만을 멤버로 가질 수 있으며, 그 외의 다른 어떠한 요소도 허용하지 않는다. (자바 8버전부터 default 예약어를 통해 일반 메소드구현이 가능하다.
⭐️ 그렇다면 추상클래스와 인터페이스는 왜 분리했을까?
- 사용용도!!! 를 생각해라
- 다중상속은 불가하지만 다중impl은 가능하다
Dog라는 클래스가 있다고 생각해보자
모든 Dog들이 공통적으로 가져야할 특성이 있을것이다. kind, age, height, weight 등..
이렇게 공통적으로 가져가는 부분을 추상클래스로 정의한다면 모든 Dog는 공통부분을 지닐 수 있다.
그러나 모든 개가 동일한 행동을하진 않을것이다 어떤개는 짖을수도, 물수도, 수영을 할 수도 있을것이다.
따라서 인터페이스를 통해 여러 기능을 특정 개에 맞게 구현해낼 수 있다.
⭐️
즉 추상클래스는 ~이다
즉 인터페이스는 ~를 할 수 있다.
interface 인터페이스 {
public static final 타입 상수이름 = 값;
public abstract 메소드이름(매개변수);
}
public interface Animal {
void sound();
}
public class Dog implements Animal {
@Override
public void sound() {
System.out.println("멍멍");
}
public void sleep() {
System.out.println("새근새근 잡니다.");
}
}
public class Lion implements Animal {
@Override
public void sound() {
System.out.println("크아앙");
}
public void hunting() {
System.out.println("사냥을 합니다.");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
Animal lion = new Lion();
dog.sound();
lion.sound();
// dog.sleep(); X
// lion.hunting(); X
((Dog)dog).sleep(); // O
((Lion)lion).hunting(); // O
}
}
public interface EchoMode {
void EchoMode();
}
public interface NormalMode {
void NormalMode();
}
public interface SportsMode {
void SportsMode();
}
public class Car implements EchoMode, SportsMode, NormalMode{
@Override
public void EchoMode() {
System.out.println("에코 모드로 주행 합니다.");
}
@Override
public void NormalMode() {
System.out.println("기본 모드로 주행 합니다.");
}
@Override
public void SportsMode() {
System.out.println("스포츠 모드로 주행 합니다.");
}
}
접근제어자에서 사용하는 default와 같은 키워드이지만, 접근제어자는 아무것도 명시하지 않은 접근제어자를 default라 하며
인터페이스의 default method는 'default'라는 키워드를 명시해야 한다.
interface MyInterface {
default void printHello() {
System.out.println("Hello World!");
}
}
-★implements한 클래스에서 재정의가 가능하다.
public interface ICalculator {
int add(int x, int y);
int sub(int x, int y);
default int mul(int x, int y) {
return x * y;
}
static void print(int value) {
System.out.println(value);
}
}
public class CalcTest {
public static void main(String[] args) {
ICalculator cal = new Calculator();
// cal.print(100); error
ICalculator.print(100);
// interface의 static 메소드는 반드시 interface명.메소드 형식으로 호출
}
}
-★interface이름.메소드로 호출해야 한다.
java9 에서는 private method와 private static method가 추가 되었다.
java8의 default method와 static method는 여전히 불편하게 만든다.
단지 특정 기능을 처리하는 내부 method일 뿐인데도, 외부에 공개되는public method로 만들어야하기 때문이다.
interface를 구현하는 다른 interface 혹은 class가 해당 method에 엑세스 하거나 상속할 수 있는 것을 원하지 않아도,
그렇게 될 수 있는 것이다.
java9 에서는 위와 같은 사항으로 인해 private method와 private static method라는 새로운 기능을 제공해준다.
→코드의 중복을 피하고 interface에 대한 캡슐화를 유지 할 수 있게 되었다.
public interface Car {
void carMethod();
default void defaultCarMethod() {
System.out.println("Default Car Method");
privateCarMethod();
privateStaticCarMethod();
}
private void privateCarMethod() {
System.out.println("private car method");
}
private static void privateStaticCarMethod() {
System.out.println("private static car method");
}
}
DefaultCar.java 클래스 - Car 인터페이스 구현체
public class DefaultCar implements Car{
@Override
public void carMethod() {
System.out.println("car method by DefaultCar");
}
}
public class Main {
public static void main(String[] args) {
DefaultCar car = new DefaultCar();
car.carMethod();
car.defaultCarMethod();
}
}
car method by DefaultCar Default Car Method private car method private static car method
Process finished with exit code 0
일반예외 (Exception) : 일반 예외와 실행 예외 클래스를 구별하는 방법은 예외 Exception을 상속받지만, RuntimeException은 상속받지 않이함.
실행예외 (Runtime Exception) : 실행 예외는 RuntimeException을 상속 받는다. 물론, 표에서 보다시피 Rumtime Exception 이 java.lang.Exception을 상속받지만, jvm에서는 runtimeException 상속 여부를 보고 판단하게 된다.
프로그램에서 예외든 에러든 발생하는 경우 프로그램이 종료되는데 이렇게 갑작스러운 종료를 막기위해 필요한 것이 예외처리
+++ mvc 예외처리 https://www.notion.so/cce3fc21976f4400aa4ed8d3fb26497b
쓰레드를 생성하는 방법
Thread클래스가 다른 클래스를 확장할 필요가있다면 Runnable 그게 아니라면 Thread클래스를 사용하자
쓰레드는 순서대로 작동하지않는다 ! - > start가 종료될 떄 까지 기다린후 다음을 실행하는것이아니다
컴퓨터의 성능에 따라 달라질 수 있으며 결과는 매번 다르다.
run메서드가 끝날떄까지 애플리케이션은 종료되지않는다.
쓰레드 대기를 위한 sleep메서드
주의할점은 Thread.sleep() 사용시 항상 try-catch로 묶어줘야한다. / InterruptedException
자바는 JVM을 통해 돌아가는데 이것이 하나의 프로세스고 PSVM-> main()메서드가 곧 메인쓰레드다. / 따로 쓰레드를 실행하지않고 main만 실행하는 것이 싱글쓰레드애플리케이션
싱글쓰레드 애플리케이션
멀티쓰레드 애플리케이션
참고 : https://sujl95.tistory.com/63
자바는 크게 3가지 영역의 메모리 영역이 존재 클래스파일은 크게 필드 , 생성자, 메서드로 구성
public int counter = 0;
같은 변수가 존재한다고 했을 때 public enum EventType {
FCFS,CONFIRMATIVE;
}
public enum EventType {
CONFIRMATIVE,FCFS;
}
1.System.out.println(EventType .CONFIRMATIVE);
2.System.out.println(Language.valueOf("CONFIRMATIVE"));
.. 등
혹은 enum을 정의할 때
enum Fruit { APPLE, PEACH, BANANA }
처럼 사용할 수 있다.
enum Fruit {
APPLE, PEACH, BANANA;
Fruit() {
System.out.println("생성자 호출 " + this.name());
}
}
public abstract class Enum<E extends Enum<E>>
implements Constable, Comparable<E>, Serializable {
private final String name;
private final String name() {
return name;
}
}
enum Color {
RED, YELLOW, GREEN, BLUE, BLACK, WHITE
}
public class EnumDemo {
public static void main(String[] args) {
EnumSet<Color> set1, set2, set3, set4, set5;
set1 = EnumSet.allOf(Color.class);
set2 = EnumSet.of(Color.RED, Color.GREEN, Color.BLUE);
set3 = EnumSet.complementOf(set2);
set4 = EnumSet.range(Color.YELLOW, Color.BLACK);
set5 = EnumSet.noneOf(Color.class);
set5.add(Color.BLACK);
set5.add(Color.BLUE);
set5.remove(Color.BLUE);
System.out.println("set1 = " + set1);
System.out.println("set2 = " + set2);
System.out.println("set3 = " + set3);
System.out.println("set4 = " + set4);
System.out.println("set5 = " + set5);
System.out.println(set5.contains(Color.BLACK));
}
}
@Override
@Override
: 현재 매서드가 부모 클래스의 매서드를 오버라이드한것을 컴파일러에게 알려준다@Deprecated
: 마커어노테이션으로 다음 버전에서 지원되지 않을 수 있기때문에 사용을 자제하라고 경고해준다.@SuppressWarning
: 이름 그대로 경고를 띄우지 않기 위해 사용하는 어노테이션 @FunctionalInterface
: 함수형인터페이스라는 것을 컴파일러에게 알리는 어노테이션 // 함수형 인터페이스 = 1개의 추상 메서드만을 갖고 있는 인터페이스어노테이션에 사용되는 어노테이션. 어노테이션을 정의하기 위한 어노테이션
@Retention
: 어노테이션이 유지되는 기간을 설정
-SOURCE : 소스파일에만 존재하고, 클래스파일에는 존재x, 컴파일러에 의해 버려진다.
-CLASS : 클래스파일에는 존재하지만 런타임 시에 유지할 필요 없다는 것을 알리고 이 값이 default이다.
-RUNTIME : 클래스파일에도 존재하고 런타임애 VM에 의해 유지되어 리플랙션을 통해 클래스 파일의 정보를 읽어 처리 가능하다.
@Target
: 어노테이션을 적용할 대상을 설정
파라미터 / 매서드 / 필드 등..
@Inherited
: 하위 클래스에도 어노테이션이 상속이 되도록 설정
@Repeatable
: 어노테이션을 반복적으로 선언할 수 있다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account")
public @interface CurrentUser {
}
종류
매서드
매서드를 보면 abstract 즉 상황에 맞게 구현하여 사용
// 먼저 기반 스트림을 생성한다.
FileInputStream fileInputStream = new FileInputStream("test.txt");
// 기반 스트림을 이용해 보조 스트림을 생성한다. BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
// Buffered**Stream 생성 시 사이즈도 정의하여 생성할 수 있다. (2번째 파라미터) // default : 8192 BufferedInputStream bis = new BufferedInputStream(fileInputStream, 8192);
// 보조스트림을 이용해 데이터를 읽는다. bufferedInputStream.read();
- ★입출력 자체의 기능은 당연히 보조스트림이 아닌 InputStream/outputstream 이 수행한다.
#### 문자기반 스트림 - Reader,Writer
- 바이트기반(1byte)이 아닌 문자기반(2byte)
- InputStream =>Reader , OutpuStream=>writer
### 채널기반의 스트림 NIO (new input/output)
- 새로운 입출력 패키지
![화면 캡처 2021-02-27 132641](https://user-images.githubusercontent.com/62214428/109375349-73385f80-78ff-11eb-8cb1-d9c851f30054.png)
- I/O는 스트림기반 vs NIO는 채널기반
![화면 캡처 2021-02-27 133051](https://user-images.githubusercontent.com/62214428/109375427-06719500-7900-11eb-9a27-b98b2f228dad.png)
- 스트림 VS 채널
: IO는 스트림기반으로 입 / 출력을 구분하여 단방향 스트림을 생성
: NIO는 채널기반으로 채널은 양방향 - 입출력을 위한 별도의 채널생성X
- NON버퍼 VS 버퍼
: IO는 버퍼를 사용하지않는다. - 읽은 데이터를 즉시처리
: NIO는 기본적으로 무조건 버퍼에 저장한다 - IO보다 성능면에서 우수
- 블로킹 : 데이터를 읽어 올 때 데이터를 기다리기 위해 멈춰있는 것을 뜻한다. 예를 들어 사용자가 데이터를 입력하기 전까지 기다리고 있을 때 블락킹 상태에 있다고 한다.
- 블로킹 VS NON블로킹
: IO는 블로킹 : 입력 스트림의 read() 메소드를 호출하면 데이터가 입력되기 전까지 Thread는 블로킹(대기상태) - 빠져나오기 위해선 스트림을 종료하는 방법 뿐
: NIO는 블로킹 , 넌 블로킹 둘 다 - NIO는 interrupt를 통해 블로킹을 빠져나올 수 있다.
------------------------
#### 바이트기반 스트림 InputStream / OutputStream
- 매서드
![화면 캡처 2021-02-23 164145](https://user-images.githubusercontent.com/62214428/108814392-10d91980-75f6-11eb-895d-b2a59ff58a0c.png)
![화면 캡처 2021-02-23 164200](https://user-images.githubusercontent.com/62214428/108814395-120a4680-75f6-11eb-82b8-009931efb715.png)
----------------------------
#### print스트림
- print, println, printf등으로 데이터를 문자로 출력하는 문자기반 스트림역할을 수행
- 흔히 사용하는 sout도 print스트림
- 문자기반 스트림 reader writer : 문자를 다루기 위해 byte대신 char타입을 다루는 것 뿐만 아니라 문자를 다루기 위한 encoding을 자동제공
---------------------------
#### InputStreamReader와 OutputStreamWriter
- 바이트 기반 스트림을 문자 기반 스트림으로 연결시켜주는 역할을 수행
- 추가적으로 바이트기반 스트림의 데이터를 지정된 인코딩의 문자데이터로 변환하는 작업을 수행
InputStream resourceAsStream = getClass().getResourceAsStream("/zones_kr.csv");
BufferedReader reader = new BufferedReader(new InputStreamReader(resourceAsStream));
List
-----------------------------
## 표준 입출력-System.in, System.out, System.err
public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
- 실제로는 `return new PrintStream(new BufferedOutputStream(fos, 128), true, enc);`처럼 BufferedInputStream과 BufferedOutputStream의 인스턴스를 사용
#### 표준입출력의 대상을 변경하는 setOut(), setErr(), setIn()
try (FileOutputStream fos = new FileOutputStream("test.txt"); PrintStream ps = new PrintStream(fos)) {
// System.out 의 출력 대상을 test.txt 파일로 변경
System.setOut(ps);
System.out.println("남기석");
}
catch (IOException e) {
e.printStackTrace();
}
- sout의 대상이 test파일로 변경 -> 남기석이 파일에 작성
----------------------------
#### 파일과 객체 ㅣ 직렬화
- serialization : 객체를 데이터스트림으로 (ex) json으로)
- deserialization : 반대
![화면 캡처 2021-02-27 142244](https://user-images.githubusercontent.com/62214428/109376359-45efaf80-7907-11eb-8729-33a002e214fd.png)
- ★ A라는 객체! 객체란 결국 다양한 인스턴스 변수의 집합체
A객체 아래
인스턴스변수1 인스턴스변수2 .... 이 존재
- 이것을 말 그래도 쭉 펴주는것이 직렬화 - 이를통해 객체의 모든 인스턴스 변수값을 스트림에 write 할 수 있도록
------------------------
#### ObjectInputStream, ObjectOutputStream
- 직렬화(스트림에 객체를 출력)에는 ObjectOutputStream을 사용
- 역직렬화(스트림으로부터 객체를 입력)에는 ObjectInputStream을 사용
- 파일에 객체저장하기
FileOutputStream fos = new FileOutputStream("objectfile.ser"); ObjectOutputStream out = new ObjectOutputStream(fos);
out.writeObject(new UserInfo());
- ★그런데 여기서 객체가 직렬화가 불가능하다면? : Serializable
public class UserInfo implements java.io.Serializable{ String name; String password; int age; }
- ★ 1. 부모가 Serializable이라면 자식은 자동으로 Serializable
- ★ 2. 자식이 Serializable이라면 부모는 Serializable (x ) - 해당사항 x
- transient 키워드를 통해 인스턴스 변수를 직렬화에서 제외시킬 수 있다.
-----------------------------------
#### 파일입출력
- File클래스
- exist를 통해 파일이 존재하는지 확인 후 사용
- ★주의 파일클래스는 입출력을 지원하는것이 아니라 파일 자체에 대한 내용(크기,속성 등)을 위한 클래스로 입출력은 스트림을 통해서
- `FileInputStream fis = new FileInputStream("C:/Temp/image.gif");`
예시
public static void main(String[] args) { System.out.println("hi");
try(FileInputStream fis = new FileInputStream("경로")){
int data;
while((data = fis.read()) != -1){
System.out.write(data);
}
}catch(Exception e){
e.printStackTrace();
}
}
- FileOutputStream
- 기본적으로 동일한 경로의 파일이 존재하면 지우고 다시 만든다.
//만약 기존내용에 추가적으로 작성을 하고싶다면 파라미터 true를 추가 FileOutputStream fos = new FileOutputStream("C:/Temp/image.gif", true);
FileOutputStream fos = new FileOutputStream(file, true);
- write() 메소드를 호출한 이후 "flush()" 메소드로 출력 버퍼에 잔류하는 데이터를 완전히 출력하도록 하며, 모든 작업이 끝난 후 close() 로 파일을 닫아줘야 한다.
- 문자기반 (텍스트 파일을 위한) FileReader
- 문자기반이기 때문에 텍스트가 아닌 그림 오디오등은 x
Resource resource = new ClassPathResource("zones_kr.csv");
List
class GenericSample<T>{
T element;
void setElement(T element){
this.element = element;
}
}
GenericSample<Integer> inte = newGenericSample<>();
public class BoundType<T extends Number> {
T element;
public void setElement(T element) {
this.element = element;
}
public T getElement() {
return element;
}
}
public static void main(String[] args) {
BoundType<Integer> inte = new BoundType<>(); //number하위타입integer
inte.setElement(3); // set
System.out.println(inte.getElement());//get이 가능
}
만약 여기서 string을 넣어본다면?
Number의 하위타입이 아니기때문에 string을 컴파일시점에서 걸러냈다.
Upper Bounded WildCard :List<? extends AnyClass>처럼 AnyClass를 상속받은 어떠한 하위 클래스가 와도 사용가능
Lower Bounded WildCard :반대로 List<? super AnyClass> AnyClass의 부모 클래스만
public class GenericSample{
public <T> void sample(T ele) {
T value = ele;
System.out.println(value);
}
}
public static void main(String[] args) {
GenericSample genericSample = new GenericSample();
String data = " hello";
genericSample.sample(data);
}
public static void main(String[] args) {
List<String> qwe = new ArrayList<>();
qwe.add(1); // 먼저 타입을 검사한 후 타입에 대해 euqlas가 false이면 컴파일 에러
}
ex) 스택 구현예시와 같은 클래스가 존재할 때
public class Stack<T> {
public T[] elements;
public Stack(int capacity) {
elements = (T[])new Object[capacity];
}
public void push(T data) {
// ~~~
}
}
1. 만약 파라미터가 바인딩되지않은경우 T->object
2. 바인딩된다면 그대로 진행 후 첫 번째로 들어온(바인딩된) 파라미터를 Comparable로 대체
int[] arr = new int[5];
Arrays.setAll(arr, (i) -> (int)(Math.random() * 5) + 1);
여기서 람다식 (i) -> (int)(Math.random() * 5) + 1) 는 아래 함수를 대체한다.
int method() {
return (int)(Math.random() * 5) + 1;
}
반환타입 메서드이름(매개변수 선언) {
문장들
}
⇩
반환타입 메서드이름 (매개변수 선언) -> {
문장들
}
ex) 람다식 변환 예시
int max(int first ,int seond)
{
return first > second ? first : second;
}
이 매서드는 람다식으로 표현하면
(int first, int second) -> {
return first > second ? first : second;
}
(first, second) -> {
return first > second ? first : second;
}
a -> a * a // o
int a -> a * a // x
아래의 인터페이스가 존재한다고 했을 때
interface MyFunction {
public abstract int max(int a, int b);
}
인터페이스를 구현한 익명 객체 생성은
MyFunction f = new MyFunction() {
public int max(int a, int b) {
return a > b ? a : b;
}
};
int big = f.max(5, 3);
★ 앞서 람다식은 익명객체와 동등하다고 했다. - 람다식으로 변환하면
MyFunction f = (int a, int b) -> {
return a>b ? a : b;
}
★ 주의할 점은 이러한 기능 하나의 매서드를 포함한 인터페이스일 때만 해당된다.
@FunctionalInterface를 통해 컴파일러가 검증을 해준다.
@FunctionalInterface
interface MyFunction {
public abstract int max(int a, int b);
}
MyFunction f = (MyFunction)( () -> { } );
: 컴파일시점에서 멤버 매서드 내부에서 생성된 개게가 멤버 매서드 내부에서 사용되는 지역변수를 사용할 경우 객체 내부로 값을 복사해온다. 주의해야할 점은 final 키워드 or final 성격을 가진 변수만 가능하다.
++ 매서드 생성자에 대한 내용은 아래 참조 https://yadon079.github.io/2021/java%20study%20halle/week-15 참고
HorizontalScrollBarDecorator result = new HorizontalScrollBarDecorator(new VerticalScrollBarDecorator(new SimpleWindow()));
처럼 절차적인 문장을 피하고 선언형★으로 유지보수성 상승
main
public class Main {
public static void main(String[] args) {
HorizantalScrollBarDecorator result =
new HorizantalScrollBarDecorator(new VerticalScrollBarDecorator(new SimpleWindow()));
result.draw();
}
}
1.
public interface Window {
public void draw();
}
2.
public class SimpleWindow implements Window {
@Override
public void draw() {
System.out.println("draw");
}
}
3.
abstract class WindowDecorator implements Window {
protected Window decoratedWindow;
public WindowDecorator(Window decoratedWindow) {
this.decoratedWindow = decoratedWindow;
}
}
4.
public class VerticalScrollBarDecorator extends WindowDecorator {
public VerticalScrollBarDecorator(Window decoratedWindow) {
super(decoratedWindow);
}
@Override
public void draw() {
System.out.println("draw vertical");
}
}
5.
public class HorizontalScrollBarDecorator extends WindowDecorator {
public HorizontalScrollBarDecorator(Window decoratedWindow) {
super(decoratedWindow);
}
@Override
public void draw() {
System.out.println("horizon");
}
}
class Main {
public static void main(String[] args) {
Integer a = 127; // == > Integer a = Integer.valueOf(127);
Integer b = 127;
Integer c = 128;
Integer d = 128;
Integer e = 1;
Integer f = 1;
System.out.println(a==b);
System.out.println(c==d);
System.out.println(e==f);
}
}
아래 코드를 보고 예상해보기
class Main {
public static void main(String[] args) {
String str1 = "hello";
System.out.println(str1.hashCode());
str1 = str1+"plus";
System.out.println(str1.hashCode());
StringBuffer str3 = new StringBuffer();
str3.append("test");
System.out.println(str3.hashCode());
str3.append("plus");
System.out.println(str3.hashCode());
}
}
99162322
1197534572
989110044
989110044
StringBuilder와 StringBuffer의 큰 차이점은 바로 StringBuilder는 변경가능한 문자열이지만 synchronization이 적용되지 않는다는 것
반면 StringBuffer는 synchronization이 적용되어 / multi thread에서 스레드 세이프하다!
StringBuffer에서 동기화부분이 빠진것이 빌더
스트링 빌더와 버퍼의 결과 값 차이를 확인해보자
import java.util.*;
class Main {
public static void main(String[] args) { StringBuffer stringBuffer = new StringBuffer(); StringBuilder stringBuilder = new StringBuilder();
new Thread(() -> {
for(int i=0; i<10000; i++) {
stringBuffer.append(i);
stringBuilder.append(i);
}
}).start();
new Thread(() -> {
for(int i=0; i<10000; i++) {
stringBuffer.append(i);
stringBuilder.append(i);
}
}).start();
new Thread(() -> {
try {
Thread.sleep(5000);
System.out.println("StringBuffer.length: "+ stringBuffer.length());
System.out.println("StringBuilder.length: "+ stringBuilder.length());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
} }
#### 빌더의 값이 더 작은 것을 확인할 수 있다.
결과 : 스트링버퍼는 멀티스레드 환경에서 동시에 건드리지 못하도록 막는다. // 반면 빌더는 이런 동기화 부분이 제외됐고 결과가 다르다.
따라서 : 멀티스레드 환경에서 스레드 세이프한 스트링버퍼 사용
StringBuffer.length: 77780 StringBuilder.length: 46982
- 그럼 마냥 StringBuffer가 좋느냐? -> 동기화는 그만큼 오버헤드발생. 성능에 따라 빌더를 사용
참고 : https://novemberde.github.io/2017/04/15/String_0.html
--- 직접 작성 쓰레드 코드
class Main { public static void main(String[] args) { StringBuilder bl= new StringBuilder(); StringBuffer bf = new StringBuffer();
class Mythread extends Thread{
@Override
public void run(){
for(int i=0;i<10000;i++){
bf.append(i);
bl.append(i);
}
}
public void check(){
System.out.println("buffer = "+bf.length());
System.out.println("builder = "+bl.length());
}
}
Mythread mt1 = new Mythread();
Mythread mt2 = new Mythread();
mt1.start();
mt2.start();
Mythread mt3 = new Mythread();
try{
mt3.sleep(10000);
mt3.check();
}catch (InterruptedException e) {
e.printStackTrace();
}
} }
결과
buffer = 77780 builder = 51572
- 예외로 당연히 조인을 추가한다면 mythread1이 끝날 때 까지 기다리기 때문에 둘 다 같은 값. 정상적으로 나온다
mythread1.start(); try { mythread1.join(); } catch (InterruptedException e) { e.printStackTrace(); } mythread2.start();
simple ex)
class Main {
public static void main(String[] args) {
Myclass ss = new Myclass(3);
ss.getMadeId();
}
}
public class Myclass{
private int count = 100;
private int id = 0;
Myclass(){
}
Myclass(int count){
this.count = count;
System.out.println("turn = a");
}
void getMadeId(){
System.out.println("turn = b");
this.id = new Idmaker(0).getId();
System.out.println("turn = d");
System.out.println(this.id);
}
private static class Idmaker{
private int id = 0;
Idmaker(int count){
this.id = count + 123;
System.out.println("turn = c");
}
private int getId(){
return this.id;
}
}
}
turn a / turn b / turn c / turn d 순서 봐보기
공변이란? : 함께 변한다.
class Main {
public static void main(String[] args) {
Object[] ob = new Long[1];
ob[0] = "hello";
List<Object> ob = new ArrayList<Long>();
}
}
- 위 코드에서 배열과 리스트는 어떤 차이가 있을까?
- 에러 발생의 시점에 차이가 있다.
✔︎ 위 상황에서 배열은 런타임 시점에 ArrayStoreException가 나온다
✔︎ 리스트는 런타임 x, 컴파일 시점에 이미 에러가 잡힌다.
- 즉 제네릭 - 배열 / 리스트에서 배열은 프로그램이 이미 실행된 후 에러가 발생하지만 리스트는 실행하기 전 미리 에러를 파악할 수 있다는 장점이 있다.
@Controller
@RequestMapping("/articles")
public class ArticleController {
@Autowired
private ArticleService articleService;
@PostMapping
public String write(UserSession userSession, ArticleDto.Request articleDto){
}
@GetMapping("/{id}")
public String show(@PathVariable int id, Model model) {
}
}
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoWired {
}
인스턴스 생성 및 DI역할의 container service
public class ContainerService {
public static <T> T getObject(Class<T> classType) {
// 기본생성자를 통해서 인스턴스를 만든다.
T instance = createInstance(classType);
// 클래스의 모든 필드를 불러온다.
Stream.of(classType.getDeclaredFields())
.filter(field -> field.isAnnotationPresent(AutoWired.class)) // 어노테이션에 AutoWired를 갖는 필드만 필터
.forEach(field -> {
try {
// 필드의 인스턴스 생성
Object fieldInstance = createInstance(field.getType());
// 필드의 접근제어자가 private인 경우 수정가능하게 설정
field.setAccessible(true);
// 인스턴스에 생성된 필드 주입
field.set(instance, fieldInstance);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
});
return instance;
}
private static <T> T createInstance(final Class<T> classType) {
try {
// 해당 클래스 타입의 기본생성자로 인스턴스 생성
return classType.getConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
내가 개발할 때 작성하는 관점으로
public class ArticleController {
@AutoWired
private ArticleService articleService;
public void foo(){
articleService.foo();
}
}
public class ArticleService {
public void foo() { System.out.println("call foo"); } }
public static void main(String[] args){ ContainerService containerService = new ContainerService();
ArticleController articleController = containerService.getObject(ArticleController.class);
articleController.foo();
}
-------
#### 전체적인 동작을 살펴보자
문제 상황 : articleController의 foo는 articleService의 foo를 실행한다. 그러나 컨트롤러가 articleService가 뭔지알고 가져오는가?
public class ArticleController { @AutoWired private ArticleService articleService;
public void foo(){ articleService.foo(); } }
1. main에서 `containerService.getObject(ArticleController.class);`
2. `ContainerService`의 `createInstance`매서드가 실행 -> `ArticleController`인스턴스 생성
3. `ArticleController`의 필드 중 `Autowired` 어노테이션을 가진 필드만 선별
4. `ArticleController`인스턴스의 필드 중 `Autowired`어노테이션을 가진 `ArticleService`필드 주입
이렇게 DI가 리플렉션을 통해 어떻게 주입되는지 살펴볼 수 있다.
결과를 예측해봐라
int[] a = {1, 2, 3};
int[] b = a;
a[0] = 3;
for (int i : b) {
System.out.println(i);
}
3 2 3
int[] a = {1, 2, 3};
int[] b = a;
a[0] = 3;
for (int i : b) {
System.out.println(i);
}
System.out.println(a.hashCode());
System.out.println(b.hashCode());
// 2083562754
// 2083562754
스레드를 활용하여 싱글톤 패턴 동시에 접근테스트
싱글톤
public class Settings {
private static Settings instance;
public Settings() {
}
public static synchronized Settings getInstance() {
if (instance == null) {
instance = new Settings();
}
return instance;
}
}
public class Main { public static void main(String[] args) { MyThread myThread1 = new MyThread(1); MyThread myThread2 = new MyThread(2);
myThread1.start();
myThread2.start();
}
}
class MyThread extends Thread { public Settings settings; private int num; public MyThread(int num) { this.num = num; }
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
settings = Settings.getInstance();
System.out.println(settings + " thread " + num);
}
}
}
2. 결과는
algo.Settings@74719816 thread 2 algo.Settings@74719816 thread 1 algo.Settings@74719816 thread 2 algo.Settings@74719816 thread 1 .....
소숫점을 제어해보자
상황 : 123.456에서 나는 소숫점 아래 2자리까지만 반영하고 싶었다
구현
double originPercentage = 123.456;
DecimalFormat form = new DecimalFormat("#.##");
String format = form.format(originPercentage);
float percentage = Float.parseFloat(format);
L value : 단일 표현식이후에도 사라지지 않는 객체
R value : 단일 표현식이후에 사라지는 임시값
ex)
private final Myclass a = Myclass("hello");
a의 값을 변경할 수 있는가? 기본적으로 final은 l value의 r value값을 고정한다. 그 말은 즉 a의 값은 Myclass("hello")의 주소값으로 고정되어있다. a의 객체의 값을 바꿀 수 있느냐? Myclass("hello") 주소를 따라가서 나오는 값을 변경한다.
자바의 'this' keyword는 이 클래스를 기반으로 생성된 인스턴스를 가리키는 참조
잘 생각해보자.
this는 인스턴스를 가리키는! 참조
인스턴스 자체가 아니다!
public class Temp {
public Temp getTempClass() {
return this;
}
}
이와같은 클래스가 존재한다고 해보자
this는 해당 클래스로 생성된 인스턴스를 가리키는 참조
그렇다면 아래의 결과는 어떨까?
public class Main {
public static void main(String[] args) throws IOException {
Temp temp = new Temp();
System.out.println(temp.hashCode());
System.out.println(temp.getTempClass().hashCode());
System.out.println(temp == temp.getTempClass());
System.out.println(temp.equals(temp.getTempClass()));
}
}
747464370
747464370
true
true
결국 this 키워드는 인스턴스를 가리키는 참조로 temp 인스턴스를 가리키는 참조를 따라가보면 temp가 나온다
직렬화란??
직렬화란
자바 시스템 내부에서 사용되는 object 또는 Data를 외부의 자바 시스템에서도 사용할 수 있도록 바이트 형태로 데이터를 변환하는 기술
- jvm의 메모리에 상주되어 있는 객체 데이터를 바이트 형태로 변환
- JVM의 메모리에만 상주되어 있는 객체 데이터를 그대로 영속화(persist)가 필요할 때 사용
장점 :
- 시스템이 종료되더라도 없어지지 않는 장점을 가지며 영속화된 데이터이기 때문에 네트워크로 전송도 가능.
- 필요할 때 직렬화 된 객체 데이터를 가져와서 역직렬화하여 객체를 바로 사용
⭐️단점 :
- 직렬화를 통해 변환된 바이트 형태의 데이터를 역직렬화 할 때 이전 객체와 동일한 객체여야한다
- 즉 자주 변경되는 비즈니스적인 데이터를 Java 직렬화을 사용하면 문제가 발생
- 따라서 serialVersionUID를 통해 관리가 필요
정리하자면 직렬화는 장점만큼 단점이 뚜렷하다
jvm 메모리에 상주하는 객체 데이터를 영속화하여 편리하게 사용할 수 있지만 변경에 굉장히 취약
개발자가 직접 컨트롤이 가능한 클래스의 객체가 아니라면 직렬화를 지양해야한다.
todo