///
Search
📔

니가 뭘 좋아하는지 몰라 다 준비했어 class, method, new, this, lambda, method reference…

태그
JAVA
class?우리가 가장 많이 만나는 class다. JVM공부를 하며 class가 어디에 할당되고 어떻게 쓰이고 언제 로딩 되고 뭐 이런 건 알게 되었다. 그럼 정의 방법은 뭘까? 사실 알고는 있는데 간략하게 정리하려고 한다.

class가 뭐임?

class란? 정말 추상적인 이야기다. ORACLE 튜토리얼에서 찾아본 결과 class의 의미는 다음과 같이 정의 하고 있다. In the real world, you'll often find many individual objects all of the same kind. There may be thousands of other bicycles in existence, all of the same make and model. Each bicycle was built from the same set of blueprints and therefore contains the same components. In object-oriented terms, we say that your bicycle is an instance of the class of objects known as bicycles. A class is the blueprint from which individual objects are created. 대충 번역하자면 많은 개별 개체를 찾을 수 있는데, 뭐 자전거를 예로 들면 동일한 제조사에 다양한 자전거가 존재할 수 있다. 각 자전거는 동일한 청사진으로 제작되었으므로 동일한 구성 요소(페달, 브레이크, 프레임…)를 포함한다. 자전거를 만드는 청사진 즉, 자전거의 기본 틀을 class라 부른다. 라고 되어있다.
내가 생각하는 class는 틀이다. 틀과 틀을 통해서 나온 결과는 다른 것이다. 물론 내가 금형을 만졌던 사람이라 이 개념이 좀 익숙한 것 같다.

대충 느낌은 알았고 class는 어케만들어?

클래스의 구성요소

field 멤버 변수로 불리며, 상태 속성을 나타낸다.
인스턴스 변수 이름에서 알 수 있듯 인스턴스를 값는 변수다. 좀 생각해야하는 것은 인스턴스가 생성될 때 만들어진다. 서로 독립적이고 heap에 할당된다(GC대상이다).
클래스 변수 static키워드가 붙은 변수이다. 해당 변수를 모든 인스턴스들은 공유한다. 그렇기 때문에 method area에 저장된다.
method 해당 객체의 행위를 나타낸다. 행위에는 행동하는 거의 모든 것이 포함된다.
인스턴스 메서드 똑같이 인스턴스를 생성해야 사용할 수 있는 method이다.
클래스 메서드 동일하게 static키워드가 붙은 메서드이다. 동일하게 method area에 할당된다.
constructor 객체 생성을 위한 초기화에 사용된다. 리턴도 없고 이름은 class명과 동일하다.
initializer 초기화 블록이다. static이 붙은 녀석과 아닌 녀석으로 구분이 된다.
인스턴스 초기화 블록
클래스 초기화 블록
public class Main { public static void main(String[] args) { new Main(); } static { System.out.println("static 초기화 블록"); } static String s = Main.staticMethod(); { System.out.println("인스턴스 초키화 블록"); } public Main() { System.out.println("생성자"); } public static String staticMethod(){ System.out.println("static 메서드"); return "할당 문자열"; } } CONSOLE RESULT static 초기화 블록 1 static 메서드 2 인스턴스 초키화 블록 3 생성자 4 // 1과 2는 순서에 따라 조금 바뀌는 것 같다.
Java
복사
추가적으로 알면 좋은 사항은 class이름을 만드는 방법이다. 클래스 및 인터페이스 - 명사를 사용해야 한다. - 대문자, 소문자가 혼합된 경우 내부 단어의 첫 글자는 대문자여야 한다. - 전체 단어를 사용하고 약어는 피해야 한다.

Inner Class

Local Class

Static Class

Anonymous Class

위의 방법처럼 4가지가 더 있다. 그런데 저걸 사용할까? 이런 의문을 나도 제일 처음에는 가졌다. 그런데 Inner class의 경우는 컴스텀 예외 객체를 만들어 사용할 때 저런 방식으로 사용을 했다. 이유는 한 클래스에서 타고 들어가면 내부에 관계있는 예외들을 쉽게 관리할 수 있기 때문이라고 알고 있다. 또한 Local class의 경우는 메서드가 실행되기 전까지는 존재하지 않고 실행 이후 빠르게 정리된다는 점이 신기했다. Static Class의 경우에는 내 주관적인 생각이지만 어디서든 많이 사용되는 공용 객체를 만들 때 좋았다. 물론 저 방식으로 싱글톤을 만들기도 한다. 아래에 더 알아본 내용에서 추가하여 설명하겠다. 마지막으로 Anonymous Class를 활용한 방식도 있다. 이 방식으로 많은 개발자를 구한 것 같다. 하지만 가독성이 떨어지는 문제도 존재하고 유지보수가 상당히 힘들다. 그래서 자바 8이후부터는 람다를 이용하여 잡음을 많이 사라지게 했다.

new 코드 이해하기

내가 정리하고 싶은 내용을 이미 정리한? 아니 내가 정리하려고 했던 것 보다 더 많이 정리하신 분이 있었다. 첨에 리터럴과 new 키워드의 차이?를 이용하여 좀 재미있게 풀어내려고 했는데 바이트코드까지 보신분이 있었다 . 그래서 이분의 블로그가 더 도움이 될 것 같다. 좋았던 내용은 바이트코드를 까본 것이다. 사람이 눈으로 보기전까진 감이 안오는데 보니깐 바로 감이 왔다. 좀 더 추가 하자면 내가 정리한 코드 중 토비의 스프링을 보고 영감을 얻을 코드가 있다. 설명하는 내용은 초기화를 어떻게 하는가에 따른 객체에 영향? 이라고 말할 수 있을 것 같다. “오브젝트와 의존 관계”를 참조하면 좋을 것 같다. 이렇게 끝내려고 했지만 아래에 정리하신 분이 빌더패턴까지 설명을 했다. 이펙티브자바를 본 것이 문득 떠올랐다. 생성패턴에 대한 이야기를… 그래서 이분 블로그가 엄청 좋은 설명이 많아 남겨본다.
내가 정리한 내용 중 초기화에 대한 방법을 조금 다른 시각에서 볼 수 있다.

Method?

일반적으로 메서드라고 하면 포함되는 것이 있다. - 접근 제어자 - 반환 타입 - 메서드 이름 - 매개변수 리스트 - 구현 이렇게 총 5가지의 기본 정의가 필요하다.
접근 제어자 및 기타 제어자 메서드에 접근할 수 있는 범위 또는 부가 사항(동기화 여부, static 여부, final 여부, 추상메서드 여부)을 명시한다.
반환 타입 말 그대로 리턴할 타입을 명시한다.
매개변수 리스트 메서드에서 사용할 매개변수들을 명시한다. 이것도 이름을 잘 만들어야 한다. 따로 규칙이 있는 것은 아니지만 보통은 class의 멤버변수 이름과 동일하게 사용하는게 관행이다. 내가 생각할 때는 API 사용자가 알아 먹을 수 있게 설계하는 것도 좋은 방식 같다. 예를 들면 난 설계할 때 X라는 변수가 무슨 의미 인지 알 수 있다. 하지만 API사용자는 단순 X라고 하면 알아 먹기 좀 힘들다. 그래서 좋은 방법은 메서드 이름이 X를 이용하여 뭘하는지 기술하고, 여의치 않다면 X가 아닌 다른 이름으로 하는 것이 좋은 것 같다.
메서드에 대해서 재미있는 것이 없는가? 에 대하여 생각하다가 똑같은 분의 재미있는 발상이 있었다. 과연 람다는 메서드 일까? 함수 일까? 라는 질문이다. 으흠… 자바의 경우 자바8 이후 함수형 프로그래밍을 지원한다. 그럼 지원하는 것이지 본질적 함수는 아니라는 것이다. 신기했다. 쪼꼼 궁금해져서 바이트 코드를 까봤다. 아래의 삽질의 결론으로는 자바의 경우 1급 함수에 해당하지 않는다. 해당되었다면 람다를 사용할때 런타임에 생성이 가능해야 하는데 그렇진 않다. 즉, 런타임에 생성이 되는 것이 아닌 컴파일 타임에 생성되어 reference를 전달하는 형태다. “자바에서는 메서드가 1급으로 처리되지 않기에, 메서드만 따로 생성하거나, 메서드 자체를 생성자나 변수나 파라미터등에 전달 할 수 없습니다.” 아까의 삽질에서도 볼 수 있듯, 람다를 사용할때 전부 이미 메서드가 생성되어 있다. 그래서 람다자체를 컴파일타임에 만들어서 사용한다는 것이다. 또한 한가지 신기한걸 알아가서 다행이다…. 자바의 람다는 익명이라 새롭게 한 줄을 더 만든다는 점이다.
public class Main { public static void main(String[] args) { Test func = new Test() { @Override public void print() { System.out.println("함수?"); } }; Test method1 = () -> System.out.println("함수?"); Test method2 = System.out::println; } } interface Test{ void print(); }
Java
복사
// handle kind 0x6 : INVOKESTATIC java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; // arguments: ()V, // handle kind 0x5 : INVOKEVIRTUAL java/io/PrintStream.println()V, // 이부분 ()V
Java
복사
Test method2 = System.out::println; 이용
// handle kind 0x6 : INVOKESTATIC java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; // arguments: ()V, // handle kind 0x6 : INVOKESTATIC queiz/Main.lambda$main$0()V, // 이부분 ()V
Java
복사
Test method1 = () -> System.out.println("함수?"); 이용
L0 LINENUMBER 6 L0 NEW queiz/Main$1 DUP INVOKESPECIAL queiz/Main$1.<init> ()V ASTORE 1 L1 LINENUMBER 14 L1 RETURN L2 LOCALVARIABLE args [Ljava/lang/String; L0 L2 0 LOCALVARIABLE func Lqueiz/Test; L1 L2 1 MAXSTACK = 2 MAXLOCALS = 2
Java
복사
익명 객체 이용
일단 람다 부분 먼저 말하겠다. 람다를 이용한 것과 메서드 참조를 이용한 것 이렇게 두가지를 사용했는데 다르다. 람다를 사용하는 경우에는 같은 클래스에 존재하는 메서드를 참조하는 것이 아니면 풀패키지명이 안뜬다. 이유는 람다는 이름을 가질 수 없기 때문이다. 요건 아래 토글에 만들어 놓겠다. 근데 분석까진 안된다.. 하지만 다르다는 건 확 와닿는다.
람다 풀패키지명 뜨게 하기
하면서 찾은 신기한 람다에 인덱스가 매겨진다고?
아까 위에서 해본 “하면서 찾은…“ 의 답변이 되는 걸 stackoverflow에서 찾은 것 같다. 메서드 참조를 사용하면 기존 메서드를 직접 참조하기 때문에 새로운 메서드를 만들 필요가 없어서 저렇게 바로 print메서드를 참조한 것이고 람다의 경우에는 javac에 의하여 새로 생성된 메서드로 변환되기 때문이다. 그래서 참조하는게 달라지게 된다. 즉, 한번의 단계를 더 거치게 된다.

this?

this자체가 가지는 의미는 Instance의 자기 자신 역할을 한다. 뭔 말 인지 이해가 안 갈 수 있다. 다음 예제를 보자.
public class Main { String 메인변수; class Inner{ String 이너변수; public void localClassMethod(){ class Local{ String 로컬변수; public Local() { // 여기서 this 키워드를 사용하면 과연 어떤 변수가 ide에서 뜰까? } }// 구분 }// 구분 }//구분 }
Java
복사
정답
정답에서 볼 수 있듯 로컬 변수만 뜬다. 즉, 현재 코드블록을 가지고 있는 바로 상단의 클래스를 지칭한다. 아무리 겹겹이 쌓인 클래스라도 this자체는 바로 현재 코드를 치는 블록을 감싸는 클래스를 지칭하게 된다. 그럼 이런 생각도 할 수 있다. 그냥 this없이 쓰면 되는거 아니냐? 하지만 지금은 변수의 이름이 다르기 때문에 어느정도 구분이 되지만 만약 이름은 동일하고 값이 다르면 어떤 상황이 벌어질까? this 키워드가 없다면 엄청 힘들어질 것이다. 또한 this의 경우에는 아까 위에서 설명된 블로그에서도 잠깐 언급된다. this(Elements...)처럼 Overloading에 사용된다.

하면서 새롭게 알게 된 내용

transient volatile keyword

Atomic변수

synchronized 키워드를 남용할 경우 lock이 걸리는데 thread가 많아지고, synchronized method 또는 로직에 대한 병목현상이 발생하기 쉬워, volatile, Atomic을 주로 사용하는 추세라고 한다.

synchronized 및 그 외의 다양한 정리 pdf

이번 주차에 업로드가 엄청 늦었다. 한 3주 걸린 것 같다. 공부할 양이 많았던 것은 아니다. 몸이 많이 안좋아서 업로드가 늦어졌다. 매일매일 공부하려니 몸에 무리가 와서.. 앞으로는 건강도 잘 챙겨야겠다.