이름을 가질 수 있다
: 생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 제대로 설명하지 못한다. public static Order primeOrder(Product product) {
Order order = new Order();
order.prime = true;
order.product = product;
return order;
}
public static Order urgentOrder(Product product) {
Order order = new Order();
order.urgent = true;
order.product = product;
return order;
}
호출될 때마다 인스턴스를 생성하지 않아도 된다
: 자바의 생성자는 호출될 때마다 새로운 객체를 만드는데, 정적-는 반복되는 요청에 같은 객체를 반환한다. 싱글톤으로 만들 수도, 인스턴스화 불가로 만들 수도 있다.private Settings() {}
private static final Settings SETTINGS = new Settings();
public static Settings getInstance() {
return SETTINGS;
}
반환타입의 하위타입객체를 반환할 수 있다
따라서 하위타입이기만 하면 따라 다른 클래스의 객체를 반환할 수 있다
즉, 반환타입은 인터페이스나 클래스로 하고 실제 반환은 인터페이스나 상위클래스의 하위 객체를 반환활 수 있으므로 굉장히 유연하다. static HelloService of(String lang){
// 이 메서드를 인터페이스에 선언할 수 있고
if (lang.equals("ko")){
return new KoreanHelloService();
// 다른 객체를 반환할 수 있다.
}else{
return new EnglishHelloService();
}
}
정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
인터페이스만 존재하고 인터페이스의 구현체가 존재하지 않아도 된다. 아직 어려운 부분이므로 나중에 필요할 때 찾아보자ServiceLoader<HelloService> loader = ServiceLoader.load(HelloService.class);
Optional<HelloService> helloServiceOptional = loader.findFirst();
helloServiceOptional.ifPresent(h -> {
System.out.println(h.hello());
});
==
를 사용할 수 있으며 equals()보다 권장된다. equals()는 nullPointException이 발생할 수 있기 때문이다.enum클래스.values(); 모든 객체를 배열로 만들어서 리턴
valueOf() : 주어지는 문자열과 동일한 문자열을 가지는 열거 객체를 리턴한다.
이 메소드는 외부로부터 문자열을 입력받아 열거 객체로 변환할 때 유용하다.
Map<Role, String> map = new EnumMap<Role, String>(Role.class);
EnumMap
은 내부에 데이터를 Array에 저장하기 때문에 map과 달리 순서가 보장되며 해시를 만들고 해시충돌을 대비하는 작업이 필요 없어지게 된다. 그리고 HashMap는 일정 이상의 자료가 저장되면 자동으로 resizing하지만 EnumMap는 처음부터 사이즈가 enum으로 제한되기 떄문에 문제가 발생할 일이 없다생성자 체이닝 : this()
를 이용하여 같은 클래스 내의 생성자를 호출
자바빈즈 패턴 : setter
메서드를 사용, 필수로 설정되어야 하는 값이 설정되지 않아 불완전한 객체가 생성될 수 있다.
그리고 set
을 사용하다보니 불변객체를 만들기 어렵다
빌더패턴 : @Builder을 사용하면 자동으로 모든 파라미터를 받는 생성자가 생성된다. 외부에 생성자를 노출하지 않고 빌더만을 사용하여 객체를 생성하고 싶으면 @AllArgsConstructor(access=) 레벨을 설정하면 된다.
@Builder을 사용하는 경우 객체 생성시 반드시 들어가야하는 필수값을 설정하는 방법이 없다. 필수값을 설정해야하는 경우 직접 빌더패턴 코드를 작성하는 것이 더 나을 수 있다.
record 클래스
도 추가로 공부하자public class Main {
public static void main(String[] args) {
Person person = new Person("Alice", 30);
// 필드는 직접 접근할 수 없지만, 접근자 메소드를 통해 값을 얻을 수 있습니다.
String name = person.name(); // "Alice"
int age = person.age(); // 30
// 필드에 대한 직접적인 접근은 불가능합니다.
// String name = person.name; // 컴파일 에러
}
}
unchecked Exception
배열
이며 하나의 메서드에 2개 이상의 가변인자가 들어갈 수 없다.public void variable(String... s) {
System.out.println(s);
}
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
boolean created = false;
private Elvis() {
if(created) {
throw new RuntimeException("Can't create Constructor");
} // 처음 인스턴스가 생성될 때는 그냥 지나간다.
created = true;
}
두번째는 역직렬화를 할 때 싱글톤에 문제가 생긴다. 직렬화
로 객체의 정보를 어딘가에 저장하고 역직렬화
로 저장된 객체정보를 가지고 오는데 역직렬화를 할 때 새로운 객체가 생성이 되어 싱글톤이 깨지게 된다. 이 문제를 막기 위해 readResolve 메서드를 제공해야한다.
private Object readResolve() {
// 역직렬화가 되어 새로운 인스턴스가 생성되더라도 INSTANCE를 반환하여 싱글턴 보장
// 새로운 인스턴스는 GC에 의해 UnReachable 형태로 판별되어 제거
return INSTANCE;
}
public enum Elvis {
INSTANCE;
싱글톤으로 만들 Elvis가 사용할 메서드들을 여기에 작성
}
class::methodName
구문을 사용하여 클래스 또는 객체에서 메서드를 참조public class MathUtils {
public static int AddElement(int x, int y) {
return x + y;
}
}
IAdd addLambda = (x, y) -> MathUtils.AddElement(x, y);
IAdd addMethodRef = MathUtils::AddElement;
public class MathUtils {
public int AddElement(int x, int y) {
return x + y;
}
}
MathUtils mu = new MathUtils();
IAdd addLambda = (x, y) -> mu.AddElement(x, y);
IAdd addMethodRef = mu::AddElement;
String[] strArr = {"a", "B", "e", "c", "D"};
Arrays.sort(strArr, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareToIgnoreCase(s2);
}
});
Comparator 객체는 함수형 인터페이스를 구현한 클래스와 유사합니다. 그러므로 람다식으로 변경할 수 있습니다.
Arrays.sort(strArr, (s1, s2) -> s1.compareToIgnoreCase(s2));
Arrays.sort(strArr, String::compareToIgnoreCase);
dates.stream().map(
d -> return new Person(d); // 이 경우에도 가능
).collect(Collectors.toList());
dates.stream().map(
Person::new // stream에서 전달받은 값을 사용해서 객체 생성
).collect(Collectors.toList());
생성자가 여러 개일 때 어떤 생성자를 메서드 참조하는지 어떻게 알 수 있을까?
Function<Input, ouput>
: 파라미터가 있고 결과값을 리턴한다
ex) (i) -> "aaa";
객체를 생성하는데 생성자에 파라미터가 있다면 Function<LocalDate, Person> = Person::new
Supplier<output>
: 파라미터는 없지만 return이 있음
ex) Person::new
Consumer<input>
: 파라미터를 받지만 리턴값이 없다
Predicate<input>
: 파라미터를 받고 boolean을 리턴한다
throw new AssertionError();
코드를 만들어 두고 주석으로 설명을 쓰는 것이 좋다private ImageUtility() {
throw new AssertionError();
}
new
같은 경우를 말한다.String.matches
메서드 내부에서 만드는 정규표현식용 Pattern
인스턴스는 한 번 쓰고 버려져 곧 바로 가비지 컬렉션 대상이 된다. Pattern은 입력받은 정규표현식에 해당하는 유한 상태 머신(finite state machine)을 만들어 인스턴스 생성 비용이 높다. 이렇게 생성 비용이 많이 드는 객체가 반복해서 필요하다면, 캐싱하여 재사용하는 것을 권장한다.static boolean isRomanNumeral(String s){
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
//string.matches 메서드는 내부에서 정규표현식용 Pattern 인스턴스를 만들고 한 번 쓰고 버려져 곧 바로 가비지 컬렉션 대상이 된다.
}
// string.matches 메서드로 아래의 Pattern 객체가 만들어진다.
public boolean matches(String regex) {
return Pattern.matches(regex, this);
이렇게 정규표현식 인증에 사용하는 pattern 객체는 생성에 많은 비용이 든다.
}
static final
로 Pattern객체를 직접 생성해 캐싱해두고, 나중에 isRomanNumeral 메서드 호출을 통해 이 인스턴스를 재사용하여 성능을 개선할 수 있다. private static final Pattern ROMAN
= Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
static boolean isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
split
: 문자 하나(ex ",")를 기준으로 split을 할 때는 빠르다. 따라서 미리 컴파일해서 쓸 필요가 없지 않고 그냥 split메서드를 사용하는 것이 좋을 수 있다. 한 글자 이상일 경우 미리 컴파일해서 재사용하는 것이 좋다.replace
a.replace(".", "/");
.을 /로 바꾸어준다
replaceFirst
a.replace(".", "/");
처음 나오는 .을 /로 바꾸어준다
Pattern.compile(regex).matcher(str).replaceFirst(repl)
4. replaceAll
Pattern.compile(regex).matcher(str).replaceAll(repl)
5. match
Eden, S0, S1
를 사용해서 사용하지 않는 인스턴스를 계속 정리하다가 오래 살아남는 인스턴스를 Old Generation로 옮긴다. (=> Minor GC)
(Full GC)
// 공간이 부족할 때 스택을 확장한다
private void ensureCapacity() {
if (elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
public void push(Object e) {
ensureCapacity();
elements[size++] = 0;
}
// 잘못된 코드
// pop으로 객체가 stack에서 나와도 스택이 객체를 참조하고 있다.
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
return elements[--size];
}
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // 다 쓴 참조 해제
return result;
}
메모리 누수의 문제가 되는 경우는 List, 배열, Map 등의 컬렉션
, 캐시
, 리스너
인데 모두 객체를 쌓아 놓는 경우이다.
캐시의 경우 WeakHashMap
을 사용해 볼 수 있다. HashMap의 경우 해당 객체가 사라지더라도 GC대상으로 잡지 못하여 컬렉션에 쌓여, 메모리 누수의 원인이 된다.
반면에 WeakHashMap
은 WeakHashMap에 있는 Key값이 더이상 사용되지 않는다고 판단되면 다음 GC때 해당 Key, Value 쌍을 제거한다. 임의로 제거되어도 상관없는 데이터들을 위해 주로 사용된다.
또 주기적으로 캐시를 정리하는 백그라운드 쓰레드를 활용하는 방법이 있다.
value가 더 중요한 가치를 지닌 경우로, value가 유효한동안은 key도 유효해야하는 경우에는 WeakHashMap을 사용하지 않는다. value가 더 중요한 경우가 대부분이다. 반대로 key가 가치가 없어지면 value의 가치가 없어지는 경우에 사용한다
key를 Integer 등의 타입으로는 사용하지 말고 인스턴스로 한 번 감싸야 한다. 그냥 사용하면 key를 null만들더라도 어딘가에 값이 남아있어 참조가 되기 때문이다.
weakReference도 찾아보자.. 사용할 일 거의 없다.
new Thead() 로 다수의 쓰레드를 만드는 방식은 리소스를 많이 사용하기 때문에 비효율적이다
Executor는 쓰레드 풀을 만들어서 가져다가 쓰는 방식이다. 총 4가지의 방식이 있는데 쓰레드 풀을 만드는 방식은 찾아서 적절한 것을 사용하자
쓰레드풀의 갯수를 정할 때는 cpu에 집중적인 작업인지, 입출력 중심 작업인지 고려해야한다.
cpu중심의 작업이라면 cpu개수 이상의 쓰레드 풀을 만들어봤자 의미가 없다. 입출력 중심 작업이라면 성능에 따라 적절한 개수를 정해야한다.
runtime.getruntime().availableprocessors()
cpu의 개수를 구하는 코드
Future<>
이다Future<String> submit = ExcutorService.submit(new Task());
submit.get() // 여기서 리턴값을 가지고 온다.(" world)
static class Task implement Callable<String> {
@Override
public String call() {
return " world"
static void copy(String src, String dst) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally {
out.close(); // 안에 있는걸 먼저 실행하고
}
} finally {
in.close(); // 밖의 것을 실행하고 차례대로 해야한다
}
}
// 그렇다고 try-finally 하나로 묶어버리면 finally안의 앞부분 코드를
실행했을 때 에러가 나면 finally 뒤의 코드가 실행이 안된다.
또는 첫번째와 두번째 모두에서 예외가 발생하면, 두번째 예외가 첫번째 예외를 삼켜,
스택 추적 내역에 첫번째 예외에 대한 정보는 남지 않게 된다.
즉 가장 나중에 발생한 예외만 보인다.
이러한 문제를 해결하기 위해 나온 것이 try-with-resources
이다. 첫번째 에외뿐만아니라 후속으로 발생한 예외도 Suppressed
로 보여준다. 심지어 close를 호출하지 않아도 자동으로 해준다.
. try-with-resources 구조를 사용하려면 해당 자원이 AutoCloseable
인터페이스를 구현해야한다. (상위 인터페이스로 올라가서 확인해보자)
static void copy(String src, String dst) throws IOException{
try(InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst))
{
byte[] buf = new byte[BUFFER_SIZE];
int n;
while((n = in.read(buf))>= 0)
out.write(buf, 0, n);
}
}
try에 자원 객체를 전달하면, try 코드 블록이 끝나면 자동으로 자원을 종료해주는 기능이 구현되어 있다.