나의풀이

class Solution {
    public boolean solution(String s) {
        boolean answer = true;
        if(s.length()==4||s.length()==6)
        {
            for(int i=0;i<s.length();i++)
            {
               if(s.charAt(i)<48||s.charAt(i)>57)
               {
                     answer=false;
               }
            }
            
        }
        else
        {
            answer=false;
        }
        
        return answer;
    }
}

참고할만한풀이-정규식사용

import java.util.*;

class Solution {
  public boolean solution(String s) {
        if (s.length() == 4 || s.length() == 6) return s.matches("(^[0-9]*$)");
        return false;
  }
}

참고할만한풀이-한줄풀이

class Solution {
  public boolean solution(String s) {
    return (s.length() != 4 && s.length() != 6) || (s.split("[0-9]").length > 0) ? false:true;
  }
}

 

 

주요 Issue : 부동소수점 연산문제

double - 1x 자리 이상인경우 오차 ->해결책 BigDecimal사용

class Solution {//시작시칸 20:40경
    public long solution(long n) {
        long first=0;//3진법저장
        long second=0;//앞뒤반전저장
        long answer = 0;//10진법 표현값
        long temp=1; long temp2=0;  long size=0;
        while(n>=1)//3진법으로
        {
            first+=n%3*temp;
            temp=temp*10;
            n=n/3; 
            ++size;//자릿수
        }
        
        System.out.println(first+" "+size);   
             while(size>=0)//반대로돌리기
        {
             System.out.println(first+ " "+second+" "+size);  
             second+=(first%10)*(long)Math.pow(10,size-1);//Math.pow(10,size);
             //System.out.println(first+ " "+second+" "+size);  
             first/=10;        
            --size;
        }      
   // second+=Math.pow(10,0);
        while(second>0)//3진법 10진법으로 표현
        {
            answer+=(second%10)*Math.pow(3,temp2);
            second/=10;
        //    second=Math.floor(second);
            temp2++;
            System.out.println(answer+"임");
        }

        return answer;

    }
}

Math.pow(10,size-1)의 반환값이 실수형이기때문에 10번째 예제가 오류가 떳다. 형변환을통해 정수로 바꾸어서 해결하였다.

부동소수점예시

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.StringTokenizer;


public class aa {

    public static void main(String[] args) throws IOException{
long a = 2;
double b =1.0;
long z=1;
long c=200000;
long test=0;
for(int i=0;i<19;i++)
{
    a*=10;
}
a+=1;
    System.out.println(a+"이다~! "+(a+b)+"이다~!  "+a%10+" "+(a+b)%10+"이다 "+(a+z)%10);
    System.out.println(a+b);
    System.out.println(c+b+ " "+(c+b)%10);
    }

}

정수형에 실수형을 더하면 1.xx*10e형식으로 변하고 소수 16자리?까지 값 보장이 안되므로 수의 길이가 길면 값이 제대로 더해지지 않는다.

다른사람풀이

나와는 다르게 문자열로 보고 문제를 해결함

class Solution {
    public int solution(int n) {
        String a = "";

        while(n > 0){
            a = (n % 3) + a;
            n /= 3;
        }
        a = new StringBuilder(a).reverse().toString();


        return Integer.parseInt(a,3);
    }
}

 

필요한부분

->부동소수점에대해 한번 살펴보기

소요시간:13분

문제 설명

단어 s의 가운데 글자를 반환하는 함수, solution을 만들어 보세요. 단어의 길이가 짝수라면 가운데 두글자를 반환하면 됩니다.

재한사항

s는 길이가 1 이상, 100이하인 스트링입니다.

 

class Solution {
    public String solution(String s) {
        String answer = "";
        int len = s.length();
        int len2=0;
        len2=len/2;
        if(len%2==0)
        {
            answer=s.substring(len2-1,len2+1);//len2-1부터 len2까지, substring의 맨앞은(0, )임
        }
        else{
              answer=s.substring(len2,len2+1);
        }
        
        return answer;
    }
}

문제 설명

두 정수 left와 right가 매개변수로 주어집니다. left부터 right까지의 모든 수들 중에서, 약수의 개수가 짝수인 수는 더하고, 약수의 개수가 홀수인 수는 뺀 수를 return 하도록 solution 함수를 완성해주세요.

내가짠 코드

class Solution {
    public int solution(int left, int right) {
        int answer = 0;
        
        for(int k=left;k<=right;k++){
        if(sum(k)%2==0)
        {
            answer+=k;
        }
        else
        {
            answer-=k;
        }
        }        
        return answer;
    }
    
    public int sum(int a)
    {
        int count=0;
        for(int i=1;i<=a;i++)
        {
            if(a%i==0)
            {++count;}
        }
        
        return count;
    }
    
}

참고할만한 다른사람 코드

import java.util.*;
class Solution {
    int yaksu(int x) {
        if(x == 1) return 1;
        Set<Integer> set = new HashSet<>();
        for(int i = 1; i <= x / 2; i++) {
            if(x % i == 0) {
                set.add(i);
                set.add(x / i);
            }
        }
        return set.size();
    }
    public int solution(int left, int right) {
        int answer = 0;
        for(; left <= right; left++) answer += left * (yaksu(left) % 2 == 0 ? 1 : -1);
        return answer;
    }
}

'코딩(알고리즘)' 카테고리의 다른 글

[LEVEL1] 행렬의 덧셈  (0) 2022.03.23
[LEVEL1] 문자열을 정수로 바꾸기  (0) 2022.03.23
[LEVEL1] 문자열다루기 기본  (0) 2022.03.23
[LEVEL1] 3진법 뒤집기  (0) 2021.11.11
[LEVEL1] 가운데 글자 가져오기  (0) 2021.11.03

함수형인터페이스는 추상메서드가 단 하나만 존재하는 인터페이스.

람다식은 함수형 인터페이스 기반으로 작성

@FunctionalInterface
interface Calculate{
int cal(int a, int b);
}

@FunctionalInterface 어노테이션을 붙여서 함수형 인터페이스에 부합하는지확인할 수 있다.

(컴파일타임에검사)

*static이나 default선언이 붙은 메서드의 경우 함수형 인터페이스에 영향을 끼치지는 않는다.

@FunctionalInterface
public interface Calculate{
int cal(int a, int b);

default int plus(int a, int b) { return a+b;}
static int sub(int a, int b){return a-b;}
}

인터페이스는 제네릭으로 사용 가능

 

함수형 인터페이스 종류(반환하는지, 매개변수 등에 따라 분류)

Function<T,R> T타입 인자 받고 R타입 객체 리턴

Consumer<T> T타입 인자 받고 리턴 없음

Runnable 인자도 안받고 리턴값도 없음

Predicate<T> T타입 인자 받고 결과로 boolean 리턴

Supplier<T> 인자를 받지않고 T타입 객체 리턴

[ 메소드 참조(Method Reference) ]

메소드 참조란 함수형 인터페이스를 람다식이 아닌 일반 메소드를 참조시켜 선언하는 방법이다. 일반 메소드를 참조하기 위해서는 다음의 3가지 조건을 만족해야 한다.

  • 함수형 인터페이스의 매개변수 타입 = 메소드의 매개변수 타입
  • 함수형 인터페이스의 매개변수 개수 = 메소드의 매개변수 개수
  • 함수형 인터페이스의 반환형 = 메소드의 반환형

참조가능한 메소드는 일반 메소드, Static 메소드, 생성자가 있으며 클래스이름::메소드이름 으로 참조할 수 있다. 이렇게 참조를 하면 함수형 엔터페이스로 반환이 된다. 3가지의 메소드에 대해 메소드 참조 예시를 자세히 살펴보도록 하자.

 

 

1. 일반 메소드 참조

예를 들어 위에서 보여준 Function에 메소드 참조를 적용한다고 하자. 우선 해당 메소드(length)가 위의 3가지 조건을 만족하는지 살펴보아야 한다.

  • 매개변수 없음
  • 매개변수 개수 = 0개
  • 반환형 = int

우리가 선언한 function과 String의 length 함수는 모두 매개변수가 없으며, 반환형이 int로 동일하기 때문에 String::length로 다음과 같이 메소드 참조를 적용할 수 있다.

// 기존의 람다식
Function<String, Integer> function = (str) -> str.length(); function.apply("Hello World");
// 메소드 참조로 변경 Function<String, Integer> function = String::length; function.apply
("Hello World");

또한 추가로 예시를 살펴보자. System.out.println() 메소드는 반환형이 void이며, 파라미터로 String을 받는 메소드이다. 그렇기 때문에 우리는 Consumer에 System.out.println() 메소드를 참조시킬 수 있다.

// 일반 메소드를 참조하여 Consumer를 선언한다. 
Consumer<String> consumer = System.out::println;
consumer.accept("Hello World!!");
// 메소드 참조를 통해 Consumer를 매개변수로 받는 forEach를 쉽게 사용할 수 있다.
List<String> list = Arrays.asList("red", "orange", "yellow", "green", "blue");
list.forEach(System.out::println); 
//interface Iterable<T> default void forEach(Consumer<? super T> action) 
{ Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }

2. Static 메소드 참조

Static 메소드 역시 메소드 참조가 가능하다. 예를 들어 Objects의 isNull은 반환값이 Boolean이며, 매개변수 값은 1개이고, 매개 변수가 Object이므로 Predicate로 다음과 같이 메소드 참조가 가능하다.

Predicate<Boolean> predicate = Objects::isNull; 
// isNull 함수
public static boolean isNull(Object obj) { return obj == null; }


3. 생성자 참조

생성자도 메소드 참조를 할 수 있다. 생성자는 new로 생성해주므로 클래스이름::new로 참조할 수 있다. Supplier는 매개변수가 없이 반환값만을 갖는 인터페이스이기 때문에, 매개변수 없이 String 객체를 새롭게 생성하는 String의 생성자를 참조하여 Supplier로 선언할 수 있다.

Supplier<String> supplier = String::new;

'자바' 카테고리의 다른 글

업캐스팅, 다운캐스팅  (0) 2022.07.02
불변객체,final  (0) 2022.06.28
자바프로그램의 동작,  (1) 2021.10.17
[Java]List Interface  (0) 2021.09.18
java 환경변수 3가지 셋팅  (0) 2017.05.05

1. 타입 안전 이종 컨테이너 패턴

매개변수화 되는 대상은 원소가 아닌 컨테이너 자신이다
Set<Integer>가 있을 때 매개변수화 되는 것은 Integer가 아니라 List<Integer>이다.

하나의 컨테이너에서 매개변수화 할 수 있는 타입의 수가 제한된다.
이보다 유연한 수단 : 타입 안전 이종 컨테이너 패턴

타입 안전 이종 컨테이너 패턴 (type safe heterogeneous container pattern)
= 컨테이너 대신 키를 매개변수화 한 다음, 컨테이너에 값을 넣거나 뺄대 매개변수화 한 키를 함께 제공한다.
각 타입의 Class 객체를 매개변수화한 키 역할로 사용한다 : 이때 class 리터럴의 타입은 Class<T>이다.

public class Favorites{ // 타입 이종 컨테이너 추상화
  public <T> void putFavorite(Class<T> type, T instance);
  public <T> T getFavorite(Class<T> type)
}

타입토큰 : 컴파일 타임 정보와 런타임 타입 정보를 알아내기 위해 메서드들이 주고받는 class 리터럴

public class Favorites { // 타입 이종 컨테이너 구현
  private Map<Class<?>, Object> favorites = new HashMap<>();
  public <T> void putFavorite(Class<T> type, T instance) {
    favorites.put(Objects.requireNonNull(type), type.cast(instance));
  }
  public <T> T getFavorite(Class<T> type) {
    return type.cast(favorites.get(type));
  }
}

여기서 와일드 카드 타입으로 put 할수 없다고 생각할 수 있지만, 이때 키가 와일드 카드 타입이기 때문에 넣을 수 있다.

Map의 값이 Object를이기 대문에 Class의 cast 메서드를 사용해 동적 형변환한다.
이때 cast 메서드에서 제네릭의 이점을 완벽히 사용한다 : 비검사 형변환 없이도 Favorites를 타입 안전하게 한다.

public class Class<T>{
  T cast(Object obj);
}

2. 타입 안전 이종 컨테이너의 제약

a. 악의적인 클라이언트가 Class 객체를 로타입으로 넘기면 Favorites 인스턴스의 타입 안정성이 쉽게 깨진다.

f.putFavorite((Class) Integer.class, "Integer의 인스턴스가 아니다.");
int favoriteInteger = f.getFavorite(Integer.class)

따라서 위에 구현한대로, put을 해줄 당시에 type.cast(instacne)와 같은 동적 형변환을 넣어주자

b. 실체화 불가 타입에는 사용할 수 없다.

  • List<String> 용 Class 객체를 얻을 수 없기 때문이다.
  • List.class 를 사용해야하지만 이렇게 했을 때 List<String>.class, List<Integer>.class 모두를 허용하여 객체 참조를 한다면 오류가 많아질 것이다.

 

3. 한정적 타입 토큰

한정적 타입 매개변수나 한정적 와일드카드를 사용하여 표현가능한 타입을 제한하는 타입토큰

애너테이션 API는 한정적 타입 토큰을 적극적으로 사용한다.

public <T extends Annotation> T getAnnotation(Class<T> annotationType)

annotationType : 애너테이션 타입을 뜻하는 한정적 타입 토큰
대상 요소에 달려있는 애너테이션을 런타임에 읽어오는 기능을 한다.
이 메서드는 토큰으로 명시한 타입의 애너테이션이 대상 요소에 달려있으면 그 애너테이션을 반환하고, 없다면 null을 반환

즉, 애너테이션된 요소는 그 키가 애너테이션 타입인 타입 안전 이종 컨테이너인 것이다.

Class<?> 타입의 객체를 한정적 타입 토큰을 받는 메서드에 넘기고 싶을 때

asSubclass 메서드 : 호출된 인스턴스 자신의 Class 객체를 인수가 명시한 클래스로 형변환 한다.

if 성공 : 인수로 받은 클래스 객체를 반환
else : ClassCastException

static Annotation getAnnotation(AnnotationElement element, String annotationTypeName){
  Class<?> annotationType = null; //바한정적 타입 토큰
  try{
    annotationType = Class.forName(annotationTypeName);
  }catch (Exception ex){
    throw new IllegalArgumentException(ex);
  }
  return element.getAnnotation(annotationType.asSubClass(Annotation.class))
}

가변인수 메서드를 호출하면 가변인수를 담기 위한 배열이 자동으로 하나 만들어진다. 그런데 내부로 감춰야 했을 이 배열을 그만 클라이언트에 노출하는 문제가 생겼다. 그 결과 varargs 매개변수에 제네릭이나 매개변수화 타입이 포함되면 알기 어려운 컴파일 경고가 발생한다.

warning: [unchecked] Possible heap pollution from     parameterized vararg type List<String>

매개변수화 타입의 변수가 타입이 다른 객체를 참조하면 힙 오염이 발생한다. 다음 메서드를 통해 확인해보자.

static void danferous(List<String>... stringLists)
{    List<Integer> intList = List.of(42);    Object[] objects = stringLists;
objects[0] = intList; // 힙 오염 발생    String s = stringLists[0].get(0); // ClassCastException}

이 메서드 에서는 형변환하는 곳이 보이지 않는데도 인수를 건네 호출하면 ClassCastException을 던진다. 마지막 줄에 컴파일러가 생성한 (보이지 않는)형변환이 숨어 있기 때문이다.

제네릭 배열을 프로그래머가 직접 생성하는 건 허용하지 않으면서 제네릭 varargs 매개변수를 받는 메서드를 선언할 수 있게 한 이유는 무엇일까? 그 답은 제네릭이나 매개변수화 타입의 varargs 매개변수를 받는 메서드가 실무에서 매우 유용하기 때문이다. 사실 자바 라이브러리도 이런 메서드를 여럿 제공하는데, Arrays.asList(T... a), Collections.addAll(Collection<? super T> c, T... elements), EnumSet.of(E first, E...rest)가 대표적이다.

그래서 사용자는 이 경고 들을 그냥 두거나 (더 흔하게는) 호출하는 곳마다 @SuppressWarnings("unchkecked") 에너테이션을 달아 경고를 숨겨야 했다. 그래서 자바 7에서는 @SafeVarargs 애너테이션이 추가되어 제네릭 가변인수 메서드 작성자가 클라이언트 측에서 발생하는 경고를 숨길 수 있게 되었다. @SafeVarargs 애너테이션은 메서드 작성자가 그 메서드가 타입 안전함을 보장하는 장치다. 컴파일러는 이 약속을 믿고 그 메서드가 안전하지 않을 수 있다는 경고를 더 이상 하지 않는다.

그렇다면 메서드가 안전한지는 어떻게 확신할 수 있을까?

메서드가 이 배열에 아무것도 저장하지 않고(그 매개변수들을 덮어쓰지 않고) 그 배열의 참조가 밖으로 노출되지 않는다면(신뢰할 수 없는 코드가 배열에 접근할 수 없다면)타입 안전하다.

static <T> T[] toArray(T... args) {    return args;}

이 메서드가 반환하는 배열의 타입은 이 메서드에 인수를 넘기는 컴파일 타임에 결정되는데, 그 시점에는 컴파일러에게 충분한 정보가 주어지지 않아 타입을 잘못 판단할 수 있다.

static <T> T[] pickTwo(T a, T b, T c) {    switch(ThreadLocalRandom.current().nextInt(3)) {    case 0: return toArray(a, b);    case 0: return toArray(a, b);    case 0: return toArray(a, b);    }    throw new AssertionError(); // 도달할 수 없다. }

아무런 문제가 없는 메서드이니 별다른 경고 없이 컴파일된다. 하지만 실행하려 들면 ClassCastException을 던진다. Object[]는 String[]의 하위 타입이 아니므로 이 형변환은 실패하기 때문이다.

즉, 다음 두 조건을 모두 만족하는 제네릭 varargs 메서드는 안전하다. 둘 중 하나라도 어겼다면 수정하라

다음은 제네릭 varargs 매개변수를 안전하게 사용하는 전형적인 예다. 다음의 flattern 메서드는 임의 개수의 리스트를 인수로 받아, 받은 순서대로 그 안의 모든 원소를 하나의 리스트로 옮겨 담아 반환한다.

@SafeVarargsstatic <T> List<T> flatten(List<? extends T>... lists)
{    List<T> result = new ArrayList<>();    for (List<? extends T> list : lists) 
result.addAll(list);    return result;}

정리

가변인수제네릭은 궁합이 좋지 않다. 가변인수 기능은 배열을 노출하여 추상화가 완벽하지 못하고, 배열제네릭의 타입 규칙이 서로 다르기 때문이다. 메서드에 제네릭 (혹은 매개변수화된) varags 매개변수를 사용하고자 한다면, 먼저 그 메서드가 타입 안전한지 확인한 다음 @SafeVarargs 애너테이션을 달아 사용하는 데 불편함이 없게끔 하자

소스코드 파일 * .java     ->  바이트코드 파일  *.class

 

*.class는 JVM에서 읽음

JVM에서의 실행과정

  1. Class Loader를 통해 .class 파일들을 JVM에 올린다
  2. JVM에 있는 .class 파일들을 Execution Engine의 Interpreter와 JIT Complier를 통해 해석된다
  3. 해석된 바이트 코드는 Runtime Data Areas 에 배치되어 실질적인 수행이 이루어진다

 

       

Class Loader(Loading,linking,initializing)

자바프로그램을 실행하면 .java파일은 컴파일러를 통해 .class(자바 바이트코드)파일로 변환된다. Class Loader는 해당 파일을 Runtime(실행)하는 시점에 Runtime Data Areas에 로딩시킨다. 즉, 컴파일하는 시점이 아니라 클래스를 처음으로 참조하는 시점(클래스의 인스턴스를 만들 때) Class Loader를 통해 메모리(Runtime Data Areas)에 로드한다.

Runtime Data Areas

런타임 데이터 영역은 JVM이 OS 위에서 실행될 때 할당받는 메모리 영역이다.

- PC Register

현재 수행중인 JVM 명령의 주소가 저장되어있다. CPU의 Register와는 다름, 연산을 위해 필요한 피연산자를 임시로 저장하기 위한 용도로 사용한다.

- JVM Stack(메서드들 영역)..T메모리

Stack Frame을 저장하는 스택. JVM은 오직 Stack Frame을 push하고 pop하는 작업만 함. 예외 발생 시 printStackTrace() 등의 메서드로 보여주는 Stack Trace의 각 라인은 하나의 스택 프레임을 표현

  • Stack Frame(스택프레임) : 메서드가 수행될 때마다 하나의 스택 프레임이 생성되어 해당 스레드의 JVM 스택에 추가되고 메서드가 종료되면 스택 프레임이 제거된다. 각 스택 프레임은 Local Variable Array, Operand Stack, 현재 실행 중인 메서드가 속한 클래스의 Runtime Constant Pool 에 대한 레퍼런스를 갖는다. 지역 변수 배열, 피연산자 스택의 크기는 컴파일 시에 결정되기 때문에 스택 프레임의 크기도 메서드에 따라 크기가 고정된다.
  • Local Variable Array(지역변수 배열) : 0부터 시작하는 인덱스를 가진 배열. 0은 메서드가 속한 클래스 인스턴스의 this 레퍼런스이고, 1부터는 메서드에 전달된 파라미터들이 저장되며, 메서드 파라미터 이후에는 메서드의 지역 변수들이 저장된다.
  • Operand Stack(피연산자 스택) : 메서드의 실제 작업 공간. 각 메서드는 피연산자 스택 지역 변수 배열 사이에서 데이터를 교환하고, 다른 메서드 호출 결과를 push하거나 pop한다. 피연산자 스택 공간이 얼마나 필요한지는 컴파일할 때 결정할 수 있으므로, 피연산자 스택의 크기도 컴파일 시에 결정된다.

- Native Method Stack

자바 외의 언어로 작성된 네이티브 코드를 위한 스택이다. 즉, JNI(Java Native Interface)를 통해 호출하는 C/C++ 등의 코드를 수행하기 위한 스택으로, 언어에 맞게 C 스택이나 C++ 스택이 생성된다.

- Heap(객체들이 있는곳)..T메모리

인스턴스(객체)를 저장하는 공간. GC(Garbage Collection)의 대상이다. 성능 이슈를 일으키는 공간

- Method Area(스테틱영역 클래스들)

JVM이 읽어 들인 각각의 클래스와 인터페이스에 대한 Runtime Constant Pool, 필드와 메서드 정보, Static 변수, 메서드의 바이트코드 등을 보관한다. 이 영역에 있는 클래스 정보로 Heap 영역에 객체를 생성한다. 즉, 매우 중요한 영역!

메서드 영역은 JVM 벤더마다 다양한 형태로 구현할 수 있으며, 오라클 핫스팟 JVM(HotSpot JVM)에서는 흔히 Permanent Area, 혹은 Permanent Generation(PermGen)이라고 불린다. 메서드 영역에 대한 가비지 컬렉션은 JVM 벤더의 선택 사항이다.

  • Runtime Constant Pool(런타임 상수 풀) : 클래스 파일 포맷에서 constant_pool 테이블에 해당하는 영역이다. Method Area 에 포함되는 영역이긴 하지만, JVM 동작에서 가장 핵심적인 역할을 수행하는 곳이기 때문에 JVM 명세에서도 따로 중요하게 기술한다. 각 클래스와 인터페이스의 상수뿐만 아니라, 메서드와 필드에 대한 모든 레퍼런스까지 담고 있는 테이블이다. 즉, 어떤 메서드나 필드를 참조할 때 JVM은 런타임 상수 풀을 통해 해당 메서드나 필드의 실제 메모리상 주소를 찾아서 참조한다.

Execution Engine

Execution Engine  Class Loader를 통해 JVM 내의 Runtime Data Areas에 배치된 바이트코드를 실행한다. Execution Engine은 자바 바이트코드를 명령어 단위로 읽어서 실행한다

그런데 자바 바이트코드는 기계가 바로 수행할 수 있는 언어보다는 비교적 인간이 보기 편한 형태로 기술된 것이다. 그래서 실행 엔진은 이와 같은 바이트코드를 실제로 JVM 내부에서 기계가 실행할 수 있는 형태로 변경하며, 그 방식은 다음 두 가지가 있다.

  • Interpreter : 바이트코드 명령어를 하나씩 읽어서 해석하고 실행한다. 하나씩 해석하고 실행하기 때문에 바이트코드 하나하나의 해석은 빠른 대신 인터프리팅 결과의 실행은 느리다는 단점을 가지고 있다. 흔히 얘기하는 인터프리터 언어의 단점을 그대로 가지는 것이다. 즉, 바이트코드라는 ‘언어’는 기본적으로 인터프리터 방식으로 동작한다.
  • JIT(Just-In-Time) Compiler : 인터프리터의 단점을 보완하기 위해 도입된 것이 JIT 컴파일러이다. 인터프리터 방식으로 실행하다가 적절한 시점에 바이트코드 전체를 컴파일하여 네이티브 코드로 변경하고, 이후에는 해당 메서드를 더 이상 인터프리팅하지 않고 네이티브 코드로 직접 실행하는 방식이다. 네이티브 코드를 실행하는 것이 하나씩 인터프리팅하는 것보다 빠르고, 네이티브 코드는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 계속 빠르게 수행되게 된다.

JIT 컴파일러 가 컴파일하는 과정은 바이트코드를 하나씩 인터프리팅하는 것보다 훨씬 오래 걸리므로, 만약 한 번만 실행되는 코드라면 컴파일하지 않고 인터프리팅하는 것이 훨씬 유리하다. 따라서, JIT 컴파일러를 사용하는 JVM들은 내부적으로 해당 메서드가 얼마나 자주 수행되는지 체크하고, 일정 정도를 넘을 때에만 컴파일을 수행한다.

GC

Garbage Collection은 메모리 관리를 위한 방법 중의 하나로, 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역[Heap]을 해제하는 기능. (new 연산자를 이용한 객체)

Java GC는 객체가 garbage인지 판단하기 위해 reachability 개념을 사용하여 객체의 유효한 참조가 있다면 ‘reachable’ 로 없으면 ‘unreachable’ 로 구별하고, unreachable 객체를 Garbage로 간주하여 GC를 실행한다. 한 객체가 다른 객체를 참조하며, 다른 객체는 또다른 객체를 참조할 경우에는 유효한 최초의 참조가 무엇인기 파악해야 되는데, 이를 객체 참조의 root set이라고 한다.

Heap 영역에 있는 객체들에 대한 참조의 종류는 4가지이다.

  1. Heap 내의 다른 객체에 의한 참조
  2. JVM Stack, Java 메서드 실행 시에 사용하는 지역 변수와 파라미터들에 의한 참조
  3. Native Stack, JNI(Java Native Interface)에 의해 생성된 객체에 대한 참조
  4. Method Area 의 static 변수에 의한 참조

이들 중 Heap 내의 다른 객체에 의한 참조를 제외한 나머지 3개(JVM Stack, Native Stack, Method Area)가 root set으로, reachability를 판가름하는 기준이 된다. 즉 root set으로부터 시작한 객체들은 reachable이며, root set과 무관한 객체들이 unreachable 객체로 GC의 대상이 된다.

 

------------------------

Jit Compiler

자주쓰이는 코드는 JVM이 machine code로 컴파일해서 속도를 높임.

바이트코드를 native code로 변환하는 작업은 JVM내에 별도의 쓰레드에서 이루어진다.

컴파일 작업이 끝나면 jvm은 바이트코드대신 컴파일된 버전을 사용함

Garbage Collector

실행되면 해당스레드외 다른스레들은 잠시 멈춘다.

가비지 컬렉션 과정 - Generational Garbage Collection

GC에 대해서 알아보기 전에 알아야 할 용어가 있다. 바로 'stop-the-world'이다. stop-the-world란, GC을 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것이다. stop-the-world가 발생하면 GC를 실행하는 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 멈춘다. GC 작업을 완료한 이후에야 중단했던 작업을 다시 시작한다. 어떤 GC 알고리즘을 사용하더라도 stop-the-world는 발생한다. 대개의 경우 GC 튜닝이란 이 stop-the-world 시간을 줄이는 것이다.

Java는 프로그램 코드에서 메모리를 명시적으로 지정하여 해제하지 않는다. 가끔 명시적으로 해제하려고 해당 객체를 null로 지정하거나 System.gc() 메서드를 호출하는 개발자가 있다. null로 지정하는 것은 큰 문제가 안 되지만, System.gc() 메서드를 호출하는 것은 시스템의 성능에 매우 큰 영향을 끼치므로 System.gc() 메서드는 절대로 사용하면 안 된다(다행히도 NHN에서 System.gc() 메서드를 호출하는 개발자를 보진 못했다).

(https://d2.naver.com/helloworld/1329..)

'자바' 카테고리의 다른 글

업캐스팅, 다운캐스팅  (0) 2022.07.02
불변객체,final  (0) 2022.06.28
람다와 함수형 인터페이스  (0) 2021.10.23
[Java]List Interface  (0) 2021.09.18
java 환경변수 3가지 셋팅  (0) 2017.05.05

1. 제네릭클래스로 만드는 방법

a. 클래스 선언에 타입매개변수를 추가한다.

  • 보통 E를 많이 사용한다.
  • 제네릭 필드를 쓴다는 것을 명시하는 것이다.

b. 실체화 불가 타입으로는 배열을 만들 수 없으니 해결한다.

E[] element = new E[DEFAULT_INITAL_CAPACITY]

b-1. 제네릭 배열을 금지하는 제약을 대놓고 우회한다.

E[] elemet = (E[]) new Object[DEFAULT_INITAL_CAPACITY]

비검사 형변환이 프로그램의 타입 안정성을 해치지 않는지를 확인한다. > 이후 @SuppressWarnings 애너테이션으로 해당 경고를 숨긴다.

  • 배열이 private 필드에 저장된다.
  • 클라이언트로 반환되거나 다른 메서드에 전달되는 일이 없다.
  • 배열에 저장되는 원소의 타입은 항상 E 이다.

특징

  • 가독성이 더 좋다. : E 타입만을 받는다는 점을 어필한다.
  • 형변환을 배열 생성시 단한번만 해주면된다.
  • 배열의 런타임 타입이 컴파일타임 타입과 달라 힙오염이 일어날 수 있다.

b-2. 배열 필드의 타입을 Object[]로 바꾼다.

E result = (E) elements[--size];

E가 실체화 불가 타입이므로 컴파일러는 런타임에 이뤄지는 형변환이 안전한지 증명할 방법이 없다. > 이후 비검사 형변환을 수행하는 할당문에서만 경고를 숨긴다.

특징

  • 배열에서 원소를 읽을 떄마다 형변환 해주어야 한다.
  • 힙오염이 해가되지 않는다.

 

2. 제네릭 배열을 사용하는 이유

  • 자바가 리스트를 사용하는게 항상 가능하지도, 꼭 더 좋은 것도아니다. 리스트도 결국은 기본타입인 배열을 사용해 구현해야하기 때문이다.
  • 성능향상의 이슈로 배열을 사용할 수 있다.
  • 제네릭타입의 타입매개변수에는 primitive 타입을 사용할 수 없다. 박싱된 기본타입으로 우회할 수 있긴하다.

 

3. 한정적 타입 매개변수

class DelayQueue<E extends Delayed> implements BlockingQueue<E>

Delayed.class의 하위타입만 받는다는 의미이다. (모든 타입은 자기 자신의 하위 타입)

ClassCastException을 걱정할 필요가 없다.

1. 배열과 제네릭의 차이

배열

  • 공변 (convariant) - Sub가 Super의 하위타입이라면 배열 Sub[]는 배열 Super[]의 하위타입이다. (함께 변한다)
  • 배열에서는 실수를 런타임에 타입 오류를 알 수 있다.
  •  Object[] objectArray = new Long[1];
      objectArray[0] = "타입이 달라 넣을 수 없다" // ArrayStoreException

실체화(reify)된다 : 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다.

제네릭

  • 불공변 (invariant) - 서로 다른 타입 Type1,Type2가 있을 때 List<Type1>  List<Type2> 의 하위타입도 아니고 상위타입도 아니다
  • 리스트에서는 컴파일할 때 타입 오류를 바로 알 수 있다
  List<Object> ol = new ArrayList<Long>();    // 호환되지 않는 타입이다.
  ol.add("타입이 달라 넣을 수 없다.")

소거(erasure)된다 : 원소 타입을 컴파일 타임에만 검사하며 런타임에는 알 수 없다.
제네릭 지원 전과 제네릭 타입을 함게 사용할 수 있게 해주는 메커니즘이다.

 

2. 제네릭 배열은 사용불가하다.

배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없다.
new List<E>[], new List<String>[], new E[] : 컴파일시 제네릭 생성오류를 일으킨다.

이유 : 타입안전하지 않기 때문이다
컴파일러가 자동 생성한 형변환 코드에서 런타임에 ClassCastException이 발생할 수 있다 : 제네릭 타입의 취지와 맞지 않다.

 

3. 실체화 불가 타입

E, List<E>, List<String> : 실체화 불가 타입

실체화 되지 않아 런타임에는 컴파일 타임보다 타입점보를 적게 가지는 타입

List<?>, Map<?,?> : 소거 메커니즘 때문에 매개변수화 타입 가운데 실체화 가능한 타입 - 비한정적 와일드카드 타입

 

4. 배열의 불편함

a. 제네릭 컬렉션에서는 자신의 원소 타입을 담은 배열을 반환하는게 불가능하다.

아이템 33의 타입 안전 이종 컨테이너를 이용하여 자신의 원소타입을 추론할 수있다 (우회)

b. 제네릭 타입과 가변인수 메서드를 함께 쓰면 해석하기 어려운 경고 메세지를 받게된다.

가변인수 메서드를 호출할 때마다. 가변인수를 담는 배열이 만들어지는데, 이때 그 배열의 원소가 실체화 불가 타입이면 경고가 뜬다. @SafeVarargs로 해결한다.

 

5. 배열대신 리스트를 사용하자

  • 장점 : 타입 안정성과 상호 운용성이 좋아진다.
  • 단점 : 코드가 조금 복잡해지고 성능이 살짝 나빠질 수도 있다.
public class Chooser<T>{
  private final List<T> choiceList;

  public Chooser(Collection<T> choices){
    choiceList = new ArrayList<>(choices);
  }

  public T choose(){
    Random rnd = ThreadLocalRandom.current();
    return choiceList.get(rnd.netInt(choiceList.size()));
  }
}

List를 사용하면 런타임에 ClassCastException을 만날 일은 없다.

public class Chooser<T>{
  private final T[] choiceArray;

  public Chooser(Collection<T> choices){
    choicesArra = (T[]) choices.toArray; 
  }

    public Object choose(){
    Random rnd = ThreadLocalRandom.current();
    return choiceArray[rnd.nextInt(choiceArray.length)];
  }
}

T[] 로의 타입캐스팅 과정에서 경고가 뜬다. 제네릭에서는 원소의 타입정보가 소거되어 런타임에는 타임 정보를 알 수 없기 때문이다. item 27 비검사 경고를 제거하라는 말에 따라 위험요소를 제거할 수 있는 최선의 방법은 List로 구현하는 것이었다.

+ Recent posts