2021. 5. 26.

유니티(Unity) 엔진 최적화 문제점

타르코프 최적화로 화날 때마다 와서 조사하고 작성하는 글

타르코프란? (Escape from Tarkov)
2016년 8월 4일 알파 테스트를 시작으로 아직 개발(정식 출시 X)하고 있는 유니티 엔진 FPS 게임. 유니티에서 '우리 엔진을 사용하시면 이런 게임도 제작이 가능합니다.'고 홍보하는 게임 중 하나.

2016년부터 지금까지도 공식 포럼에 엔진 변경을 고려해야 하지 않냐고 종종 글이 올라오는 게임이다.
공식 포럼에 운영자가 남긴 엔진 교체 관련 답변

최적화와 각종 버그 및 프리징(멈추는 현상)으로 고통받는 건 기본사양이고 어느 정도 해결돼도 다음 패치에서 어떤 식으로 다시 버그와 멈춤 현상이 발생할지 아무도 모른다. (실제로 몇 번 재발생)


출처: https://youtu.be/h2hyZkzgHPc?t=415

이런 문제를 인지하고 유니티에서 제시한 방법은 점진적 GC(Garbage Collection) 도입인데 근본적인 해결방안이라기보단 임시방편.

타르코프는 유니티 5를 시작으로 유니티 2018까지 순차적으로 업그레이드 작업을 진행했으며 2021년 여름 유니티 2019 업그레이드를 목표로 하고 있다.


아래는 유니티 엔진의 문제점을 알아보면서 발견한 자료들


유니티는 2021년 4월 포럼에 '2021.1 릴리스부터는 이전 시스템(엔티티 기반)에 대한 호환성이 없어져서 2020 LTS 버전에 머물러 있어야 한다. 최대한 빠르게 정보를 제공하겠다.'라고 공지를 올렸다.

링크: https://forum.unity.com/threads/notice-on-dots-compatibility-with-unity-2021-1.1091800/

'크게 우려할 상황은 아니다.', '자세한 설명이 없는 일방적인 소통방식이다.'는 것과 같은 다양한 반응들이 쏟아지고 있는데 이제 발표하고 사용한 지 겨우 1~2년 된 기술 스택을 다시 크게 바꾸는 정책이나 지금도 이 순간에 개발되고 있는 다양한 프로젝트에 대한 고려가 충분히 논의되고 있는지 의심스럽다.



유니티는 2018년 11월 ESC 시스템을 발표했다. 멀티스레딩을 위한 잡 시스템(Job System) 등등 최적화와 관련된 기술들을 발표했다.

발표영상 - https://www.youtube.com/watch?v=j4rWfPyf-hk

아래는 이를 적용한 사례 발표 영상


유니티 엔진 게임 리스트
https://en.wikipedia.org/wiki/List_of_Unity_games

언리얼 엔진 게임 리스트
https://en.wikipedia.org/wiki/List_of_Unreal_Engine_games


유니티와 OpenGL을 사용해 간단한 게임을 만든 뒤 비교한 영상


총알 오브젝트 개수당 100개의 프레임을 그려내는 데 사용한 시간 그래프


총알 오브젝트 개수당 CPU 사용량


총알 오브젝트 개수당 GPU 사용량


총알 오브젝트 개수당 RAM 사용량

유니티 코드 라인수: 542

OpenGL 코드 라인수: 2619


유니티 개발자가 2019년 언리얼 개발에 들어가면서 객관적으로 비교하기 위해 작성하고 있는 글

https://gametorrahod.com/objectively-comparing-unity-and-unreal-engine/

https://www.reddit.com/r/unrealengine/comments/aezhdv/it_seems_people_at_epic_are_considering_adding/edxha25/

에픽 CEO 팀 스위니는 C#의 단순함을 좋아하지만 데이터가 실제 C++ 구현에 연결되어야 하는 것은 마음에 들지 않는다고 말했습니다. 실제로 유니티가 C++ 데이터베이스 기반의 MonoBehaviour를 사용하고 있기에 빠져있는 함정입니다. 유니티 CTO 또한 MonoBehaviour가 나빴다는 점을 인정했습니다. 이제 유니티는 모든 객체(Entity)에 C# 기반의 데이터베이스를 가지고 있는 ECS를 이용해 이를 제거하고 있습니다.

https://forum.unity.com/threads/why-can-the-subscene-of-the-ecs-improve-load-times.686893/#post-4595524

Joachim Ante(유니티 CTO): 게임 객체 기반은 OO(object oriented)가 유행하던 시절부터 14년 동안 만들어졌으며 궁극적으로 그 비용이 얼마인지에 대한 명확한 시각이 없었습니다. 또한 우리는 14년 전에 지금과 같이 성능을 우선시하지 않았습니다.


- 끔찍한 어셈블리/도메인 리로드 타임

유니티를 멀리해야할 이유가 있다면 바로 이것

- 확장 문제

추가된 많은 스크립트로 인해 도메인 리로드가 길어지고 플레이 모드로 들어가는 시간이 길어진다. 스크립트를 자유롭게 추가하는 것을 두려워 하게 된다.

- TypeCache API

2019.2 유니티에서 이 문제를 해결하기 위해 UnityEditor.TypeCache API를 도입
https://forum.unity.com/threads/unityeditor-typecache-api-for-fast-extraction-of-type-attributes-in-the-editor-tooling.687682/

- 그렇다면 언리얼은?

많은 사람이 블루프린트를 유니티의 Prefabs와 비교하지만, 그 이상이다. 디자인한 그래프를 개별적으로 C++ 코드로 컴파일 할 수 있다. 개별적으로 컴파일되기 때문에 유니티처럼 컴파일 시간이 기하급수적으로 확장되지 않음을 의미한다. 좋아 컴파일 시간은 유니티의 스피너와 같지만 그 이후에 대해 말해보자. 언리얼 엔진에서 리로드할 도메인이 없다고 생각한다. Play 모드로 들어가는 것이 너무나 빠르다. 2019.3에 추가된 도메인 재로드 비활성화는 이것에 비하면 기껏해야 반창고 붙이기다.

- 버그 보고서 절망의 QA 벽돌 벽

나는 유니티가 축적한 나쁜 평판에는 QA 직원에서 비롯된 것이라고 굳게 믿고 있다. 발견한 버그를 고치게 하는 것은 매우 어렵다. 나는 아직도 그들에게 도달하지 못한 버그 묘지가 너무 많지만 계속 버그와 같이 살아가야 한다. 언리얼에서는 모든 엔진의 소스를 볼 수 있기 때문에(매우 어렵지만) 궁극적인 해결책을 가지고 있다. 유니티에는 엔진 코드에 대한 접근 권한이 없어서 개발자 측면에서 블랙박스를 만들고 사용자 측면에서 에러를 만드는 트리거를 생성한다.

- 프로젝트 재작업의 광기(Project-breaking, reworking madness)

유니티는 너무 많은 구조 변경이 이뤄지고 있다. 유니티를 고수하려면 지속적으로 앞으로 나가야 한다.(LTS 버전을 사용하지만 프로젝트 수명 내내 불안전하다. 내 프로젝트는 단단한 구조에서 변경에 준비된 구조(agile)로 변경됐다. 하지만 결과적으로 너무 많은 시간을 잃었다.)

다음 블로그 글은 2019.3 릴리스 후보가 어떤 소란을 만들어 내는지 볼 수 있다. (유니티 공식 블로그로 댓글에 개발자들이 남긴 장문의 불만 글을 볼 수 있습니다. 수정-현재는 없어졌습니다.)
https://blog.unity.com/technology/2019-3-is-now-in-the-final-stages-of-beta-testing


마이크로소프트가 마인크래프트 회사를 인수한 후 자바로 만들어진 마인드 크래프트를 C++로 재개발한 '베드락 에디션' 벤치마크 영상 (Optifine은 최적화 모드)

2021. 4. 29.

동료를 존중하는 것이 비즈니스에 좋은 이유

동료를 존중하는 것이 업무에 좋은 이유 (Why being respectful to your coworkers is good for business)

사소하더라도 무례한 행동은 공격성과 폭력성 같은 더 큰 문제로 이어집니다.
우리는 무례함이 업무 수행과 결과에 영향을 준다고 믿었습니다. 그래서 우리는 연구를 시작했고 놀랄만한 결과를 얻었습니다.

66%는 업무에 들이는 노력이 줄었고

80%는 자신이 당한 일로 근심하며 근무 시간을 보냈으며

12%는 직장을 떠났습니다.

시스코는 이 수치를 보고 몇 가지 항목만 놓고 보더라도 무례함으로 연간 최소한 1,200만 달러(약 132억 8천만원)의 비용이 발생한다고 추정했습니다.

동료가 어느 그룹원에게 모욕을 줄 때 어떤 영향을 미치는지 시험했습니다. 그 결과는 매우 흥미로웠습니다. 왜냐하면 목격자들의 업무 능률도 감소했는데 조금이 아니라 꽤 상당히 줄어들었기 때문이죠.

25% 능률 감소 & 45% 아이디어 감소

무례함은 유행성 질병입니다. 전염성이 있죠. 우리는 그 주변에 있는 것만으로도 무례함의 매개체가 됩니다. 그리고 이것은 일터에서만 국한되는 것이 아닙니다. 어디서나 이 바이러스를 발견할 수 있죠. 가정, 인터넷, 학교 그리고 우리 사회 안에서요. 우리의 감정과 의욕, 능률에 영향을 미칩니다. 타인을 대하는 태도에도 영향을 미치죠. 심지어 집중력에도 영향을 미치고 지적 능력도 감소합니다. 그리고 이것은 무례함을 당할 때뿐만 아니라 목격할 때도 일어납니다. 심지어 단지 무례함이 내포된 단어를 보거나 읽을 때도 일어납니다. 예를 들어서 자세히 말씀드리죠. 실험을 위해서 우리는 참가자들에게 문장을 만들 때 사용할 단어의 조합을 줬습니다. 하지만 우리는 매우 교활했죠. 참가자의 절반에게는 무례함을 유발하는 15개 단어의 목록을 주었습니다. 버릇없음, 방해, 불쾌함, 그리고 괴롭힘 같은 단어였죠. 나머지 절반은 그런 무례함이 없는 단어 목록을 받았습니다. 그리고 알게 된 사실은 정말 놀라웠습니다. 왜냐하면 무례한 단어를 가진 사람들은

바로 앞의 컴퓨터 화면에 있는 정보를 놓칠 가능성이 5배나 높았거든요.

그리고 연구를 계속 진행하면서 무례한 단어들을 읽은 사람들이 의사결정에 더 오랜 시간이 걸린다는 걸 알았습니다. 자신의 의견을 기록할 때도 마찬가지였죠. 그리고 실수도 훨씬 많았습니다. 이것은 큰 문제가 될 수도 있습니다. 특히 생사가 달린 상황이라면요. 의사인 스티브는 제게 동료 의사에 대해 말했습니다. 절대로 누굴 존중하는 사람이 아니라고 했죠. 특히 신입 직원과 간호사에게요. 하지만 스티브는 그 의사가 의료진에게 소리를 질렀을 때 어떤 특정한 상호작용이 일어난다고 말했습니다. 이 상호작용 직후에 그 의료진은 환자에게 약을 잘못 투여했습니다. 스티브 말에 따르면, 차트에 정확한 정보가 있어도 팀원 모두가 그것을 놓쳤다는 거예요. 그는 의료진이 집중력이 떨어져 있었고 고려 사항을 인지하지 못했다고 했습니다. 간단한 실수죠? 글쎄요, 그 환자는 죽었습니다. 이스라엘에 있는 연구자들이 실제로 증명한 바에 따르면 무례함에 노출된 의료진의 업무 능력이 저하되었습니다. 진단뿐만 아니라 모든 업무 절차상에서요. 그 주요 원인은 무례함에 노출된 팀이 정보를 기꺼이 공유하려 하지 않았고 동료에게 도움을 구하지 않았기 때문이었습니다. 이런 현상은 의학계뿐만 아니라 모든 산업계에서 나타납니다.

무례함으로 그렇게 큰 대가를 치러야 함에도 왜 여전히 무례한 행동을 하게 될까요? 저는 궁금해서 이에 대한 설문조사도 해봤습니다. 첫 번째 이유는 스트레스입니다. 압도당하는 기분이 드는 거죠. 사람들이 더 정중하지 않은 또 다른 이유는 사람들이 정중하거나 친절해 보이는 것에 회의적으로 생각하고 신경을 씁니다. 그렇게 하면 리더처럼 보이지 않는다고 생각하죠. 사람들은 이렇게 생각합니다. "착하면 뒤처지지 않을까?" 바꿔 말하면 이거죠. "나쁜 놈이 앞서가지 않을까?" 그렇게 생각하기 쉽습니다. 특히 몇몇 영향력을 발휘한 것으로 잘 알려진 사례들을 볼 때 말이죠. 그렇지만 길게 보면 결국에는 그렇게 되지 않습니다. 창조적 리더십 센터의 모건 맥콜과 마이클 롬바르도는 이에 대한 다양한 연구를 수행했습니다.

그들은 경영 실패의 가장 첫 번째 원인은 몰상식하고, 거칠고, 괴롭히는 태도에 있음을 알았죠.

하지만 착한 사람들의 경우에는요? 예의 바르면 도움이 될까요? 네, 그렇습니다. 예의 바른 사람이라고 해서 그게 곧 나쁜 사람이 아님을 뜻하진 않습니다. 다른 사람을 낮추지 않는다고 해서 그들을 높이는 것은 아니죠.

진정으로 예의 바르다는 것은 사소한 행동을 의미합니다. 복도에서 웃으며 인사하거나 누군가의 말에 주의 깊게 귀 기울이는 것처럼 말이죠.

이제 여러분은 강하게 자기 주장을 할 수 있고 반대하거나, 갈등을 빚기도 하고 반대 의견을 줄 수 있습니다. 정중하게 상대를 존중하며 말이죠. 이를 '과격한 솔직함'이라고 하는 사람들도 있습니다. 개인적으로 신경쓰이지만 직접적으로 도전하는 것이죠. 네, 예의는 도움이 됩니다. 제 동료와 저는 생명공학 회사에 대한 연구에서

정중하게 보이는 사람들은 두 배는 더 리더처럼 보이고 (2X More likely to be viewed as leaders)

업무 수행도 훨씬 더 잘한다는 것을 알았습니다. (13% Higher performance)

예의 바른 것이 왜 도움이 될까요? 왜냐하면 사람들이 여러분들을 중요하고 강력한 사람으로 보고 두 가지 주요 특징의 독특한 조합을 보기 때문입니다. 따듯함과 완전함(Warm & Competent), 친절함과 똑똑함(Friendly & Smart)입니다. 바꿔 말하면, 정중한 것은 그저 타인에게 동기를 부여하는 게 아닙니다. 그것은 여러분에 대한 것입니다. 여러분이 예의 바르다면, 더욱 리더로 보일 것입니다. 일을 더욱 잘 수행할 것이고, 따듯하고 유능하다고 보일 것입니다.

하지만 어떻게 정중함이 도움을 주는지에 대한 더 중요한 부분이 있고 그것은 리더십에 관해 가장 중요한 질문 중 하나와 연결되어 있습니다. 사람들은 자신의 지도자에게서 가장 바라는 것이 무엇일까요? 우리는 전 세계 2만명 이상의 직원들의 자료를 모았고 그 대답이 단순하다는 것을 알았습니다.

존중입니다.

존중받는 것이 무엇보다 중요했어요. 인정과 감사, 유용한 피드백, 심지어 학습 기회보다 더 중요했죠. 존중받는다고 느끼는 사람은

더욱 건강하고 (56% Healthier)

더 집중하고 (92% More focused)

조직에 더 오래 머무르려는 경향이 있었습니다. (1.1x Greater retention)

그리고 일도 훨씬 더 열심히 하죠. (55% More engaged)

그럼 여러분은 어떻게 해야 할까요? 어떻게 사람들을 북돋아주고 존중받고 있다는 느낌을 줄 수 있을까요? 다행인 것은 큰 변화가 필요치 않다는 것입니다. 사소한 것으로 큰 변화를 만들 수 있습니다. 제가 알게 된 것은 사람들에게 감사하는 것과 신뢰를 주는 것, 주의 깊게 듣는 것, 겸손하게 질문하는 것, 다른 사람을 인정하고 미소짓는 것이 영향을 미친다는 사실입니다.

2021. 4. 26.

내성적인 사람은 성공에 불리할까

[윤대현의 마음속 세상 풍경] [52] 내성적인 사람은 성공에 불리할까

특정 식음료 회사의 130개 프랜차이즈 지점을 대상으로 리더와 구성원의 성향을 조사한 연구가 있다. 그 결과 소통에 다소 소극적인, 즉 내성적인 구성원이 외향적인 리더를 만난 곳은 평균치보다 수익률이 높았다. 반면 의견을 적극 제시하는 구성원이 외향적인 리더를 만났을 때는 최고의 조합일 듯한데, 오히려 수익률이 평균치보다 낮았다는 것이다. 다른 유사한 연구에서는 내향적인 리더가 자기 의견이 강한 구성원을 만났을 때 오히려 업무 효율이 증가했다. 리더의 외향적 성향이 내향적 성향에 비해 꼭 우월한 것은 아니라는 것이다.

출처:

https://www.chosun.com/opinion/specialist_column/2021/04/27/R5KGBUZHRRCKNGKVEHFSSTINVA/

https://news.naver.com/main/read.nhn?mode=LSD&mid=sec&sid1=103&oid=023&aid=0003610440

2021. 4. 13.

mappedBy reference an unknown target entity property:

스프링 부트 + JPA (@OneToMany, @ManyToMany) 작업 도중 만나게 된 에러 내용

Caused by: org.hibernate.AnnotationException: 
mappedBy reference an unknown target entity property:

이 에러의 주된 원인은 연관관계를 맺고 있는 변수를 잘못 적거나 착각하거나 오타를 냈을 때 일어난다.

@Entity
public class Member {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    
    @ManyToMany
    @JoinTable(name = "MEMBER_PRODUCT",
    	joinColumns = @JoinColumn(name = "MEMBER_ID"),
        inversJoinColumns = @JoinColumn(name = "PRODUCT_ID"))
    private List<Product> products = new Arraylist<Product>();
}
@Entity
public class Product {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToMany(mappedBy = "products")
    private List<Member> members;
}

List의 변수명 'products'와 mappedBy 텍스트가 정확하게 일치해야 한다. 자동완성으로 'productList'란 변수명을 만들고 mappedBy에는 'product'를 작성하면 타겟 엔티티를 못찾겠다는 에러메세지가 출력된다.

2021. 3. 5.

15주차 과제: 람다식

https://github.com/whiteship/live-study/issues/15

목표

자바의 람다식에 대해 학습하세요.

학습할 것 (필수)

  • 람다식 사용법
  • 함수형 인터페이스
  • Variable Capture
  • 메소드, 생성자 레퍼런스


람다식 사용법

람다 표현식은 자바 8에서 추가됐습니다.

람다식은 기본적으로 함수형 인터페이스(functional interface: 단일 추상 메소드를 가진 인터페이스. 예-java.lang.Runnable)의 인스턴스 표현입니다. 추상 메소드가 하나만 포함되어 있어서 구현 시 메소드의 이름을 생략 가능합니다.

파라미터(매개변수)를 받아 값을 반환하는 짧은 코드 블록입니다.

// 하나의 파라미터
parameter -> expression

// 둘 이상의 파라미터
(parameter1, parameter2) -> expression

// 코드 블록 사용 시
(parameter1, parameter2) -> { code block }

표현식에는 제한이 있습니다. 즉시 값을 반환해야 하며 변수, 값 할당 if나 for 같은 구문을 포함할 수 없습니다. 더 복잡한 작업을 수행하려면 중괄호({ })를 사용해야 합니다. 람다식이 값을 반환해야 하는 경우에는 코드 블록에 return문이 있어야 합니다.

GUI 애플리케이션의 Lambda 표현식

키보드 동작, 마우스 동작, 스크롤 동작과 같은 graphical user interface(GUI) 응용 프로그램에서 이벤트를 처리하려면 일반적으로 특정 인터페이스 구현과 관련된 이벤트 처리기를 만듭니다. 종종 이벤트 핸들러 인터페이스는 함수형 인터페이스(functional interface)입니다.

btn.setOnAction(new EventHandler<ActionEvent>() {

    @Override
    public void handle(ActionEvent event) {
        System.out.println("Hello World!");
    }
});

'btn.setOnAction' 메소드 호출은 btn 객체가 나타내는 단추를 선택할 때 발생하는 작업을 지정합니다. 이 메소드에는 EventHandler<ActionEvent> 타입의 객체가 필요합니다. EventHandler<ActionEvent> 인터페이스에는 void handle(T event) 메소드가 하나만 포함되어 있습니다. 이 인터페이스는 함수형 인터페이스(functional interface)이므로 다음과 같이 람다식을 사용하여 대체할 수 있습니다.

btn.setOnAction(
    event -> System.out.println("Hello World!")
);

변수 할당

Callable c = () -> process();

메소드 파라미터

new Thread(() -> process()).start();

출처:
https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
https://www.w3schools.com/java/java_lambda.asp


함수형 인터페이스

함수형 인터페이스를 나타내는 '@FunctionalInterface' 문서 설명입니다.

Java 언어 사양에 정의된 함수형 인터페이스임을 나타내는데 사용하는 정보 주석 타입입니다. 개념적으로 함수형 인터페이스에는 정확히 하나의 추상 메소드가 있습니다. 기본 메소드(default method)는 구현이 있으므로 추상이 아닙니다. 인터페이스가 java.lang.Object의 공용 메소드 중 하나를 재정의하는 추상 메소드를 선언하면 java.lang.Object 또는 다른 곳에서 구현되므로 추상 메소드 수에 포함되지 않습니다.

함수형 인터페이스의 인스턴스는 람다식, 메소드 참조(reference), 생성자 참조를 사용해 만들 수 있습니다.

이 애노테이션 타입이 적혀있는 경우 컴파일러는 다음 조건이 아닌 경우 오류 메시지를 생성합니다.

  • 인터페이스 타입이며 애노테이션 타입, enum 타입, 클래스가 아닙니다.
  • 애노테이션 타입은 함수형 인터페이스의 요구 사항을 충족합니다.

하지만 컴파일러는 @FunctionalInterface 선언 여부와 관계없이 함수형 인터페이스의 정의를 충족하는 모든 인터페이스를 함수형 인터페이스로 취급합니다.

// 예시
interface FileFilter { boolean accept(File x); }
interface ActionListener { void actionPerformed(…); }
interface Callable<T> { T call(); }

@FunctionalInterface
public interface Runnable {
    // 추상 메소드가 1개
    public abstract void run();
}

@FunctionalInterface
public interface Predicate<T> {
    default Predicate<T> and(Predicate<? super T> p) {…};
    default Predicate<T> negate() {…};
    default Predicate<T> or(Predicate<? super T> p) {…};
    static <T> Predicate<T> isEqual(Object target) {…};
    // 추상 메소드가 1개이기 때문에 함수형 인터페이스
    boolean test(T t);
}

@FunctionalInterface
public interface Comparator {
    // Static, default 메소드 생략
    
    // 오직 1개인 추상 메소드
    int compare(T o1, T o2);
    // equals(객체) 메소드는 Object class에서 온 메소드로 추상 메소드 수에 포함하지 않습니다.
    boolean equals(Object obj);
}

출처:
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/FunctionalInterface.html
https://www.oracle.com/webfolder/technetwork/tutorials/moocjdk8/documents/week1/lesson-1-3.pdf


Variable Capture

로컬, 익명 클래스처럼 람다식도 변수를 캡처할 수 있습니다. 둘러싼 범위(enclosing scope) 내에서 로컬 변수에 대해 동일한 액세스 권한을 가집니다. 그러나 로컬 및 익명 클래스와 달리 람다식에는 shadowing 문제가 없습니다. 람다 표현식은 문법적으로 범위가 지정됩니다. 상위타입(supertype)에서 이름을 상속하지 않고 새로운 범위 지정을 하지 않습니다. 람다식의 선언은 둘러싼 환경에 따라 해석됩니다. 다음 예제 LambdaScopeTest는 이를 보여줍니다.

package com.test.mytest;

import java.util.function.Consumer;

public class LambdaScopeTest {

    public int x = 0;

    class FirstLevel {
        public int x = 1;

        void methodInFirstLevel (int x) {
            Consumer<Integer> myConsumer = (y) ->
            {
                System.out.println("x = " + x);
                System.out.println("y = " + y);
                System.out.println("this.x = " + this.x);
                System.out.println("LambdaScopeTest.this.x = " + LambdaScopeTest.this.x);
            };
            myConsumer.accept(x);
        }
    }

    public static void main(String[] args) {
        LambdaScopeTest st = new LambdaScopeTest();
        LambdaScopeTest.FirstLevel f1 = st.new FirstLevel();
        f1.methodInFirstLevel(23);
    }
}
x = 23
y = 23
this.x = 1
LambdaScopeTest.this.x = 0

Process finished with exit code 0

Interface Consumer<T>: 입력 인수(argument)를 받아서 결과값을 반환하지 않는 작업을 합니다. 대부분 다른 함수형 인터페이스와 달리 Consumer는 부작용을 예상하고 작동합니다.

만약 람다식 myConsumer 매개변수(parameter) y를 x로 교체하면 컴파일 오류를 생성합니다.

람다식이 새로운 범위를 지정하지 않았기 때문에 컴파일러에서 "변수 x가 methodInFirstLevel(int)에 이미 정의되어 있습니다."라는 오류를 생성합니다. 결과적으로 둘러싼 범위(enclosing scope)의 필드, 메소드, 로컬 변수에 직접 액세스 할 수 있습니다. 예를 들어 람다식은 methodInFirstLevel 메소드의 매개변수(parameter)에 직접 접근합니다. 근접 클래스(enclosing class) 변수에 접근하려면 this 키워드를 사용합니다. 예시에서 this.x는 맴버 변수 FirstLevel.x를 참조합니다.

하지만 로컬 및 익명 클래스처럼 람다식은 근접 블록 내의 final 또는 사실상 final인 로컬 변수와 매개변수(parameter)에만 접근할 수 있습니다. 예를 들어 methodInFirst 바로 뒤에 'x = 99;' 할당문을 추가한다고 가정해 봅시다.

이 할당문 때문에 FirstLevel.x 변수는 사실상 final이 아닙니다. 결과적으로 "람다식에서 참조된 로컬 변수가 final 혹은 사실상 final이어야 합니다."와 유사한 오류 메세지를 생성합니다.

출처: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#accessing-local-variables


메소드, 생성자 레퍼런스

메소드 레퍼런스

람다식을 사용해 익명 메소드를 만듭니다. 하지만 람다식은 종종 기존 메소드를 호출하는 것 외에는 아무것도 하지 않습니다. 이런 경우 기존의 메소드 이름을 참조하는 것이 더 명확합니다. 메소드 참조(reference)를 사용해 이미 이름이 있는 메소드를 간결하고 읽기 쉬운 람다식으로 사용합니다.

public class Person {

    public enum Sex {
        MALE, FEMALE
    }

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    public int getAge() {
        // ...
    }
    
    public Calendar getBirthday() {
        return birthday;
    }    

    public static int compareByAge(Person a, Person b) {
        return a.birthday.compareTo(b.birthday);
    }}

    // ...

애플리케이션의 멤버가 배열에 포함되어 있고 배열을 연령별로 정렬한다고 가정합니다.

Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);

class PersonAgeComparator implements Comparator<Person> {
    public int compare(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
        
Arrays.sort(rosterAsArray, new PersonAgeComparator());

인터페이스 Comparator는 함수형 인터페이스입니다. 따라서 Comparator를 구현(implement)하는 클래스의 새 인스턴스를 정의하고 만드는 대신 람다식을 사용할 수 있습니다.

Arrays.sort(rosterAsArray,
    (Person a, Person b) -> {
        return a.getBirthday().compareTo(b.getBirthday());
    }
);

하지만 두 Person 인스턴스의 생년월일을 비교하는 메소드는 이미 Person.compareByAge로 존재합니다. 람다식의 본문에서 이 메소드를 호출할 수 있습니다.

Arrays.sort(rosterAsArray,
    (a, b) -> Person.compareByAge(a, b)
);

람다식은 기존의 메소드를 호출하므로 람다식 대신에 메소드 레퍼런스를 사용할 수 있습니다.

Arrays.sort(rosterAsArray, Person::compareByAge);

메소드 레퍼런스 'Person::compareByAge'는 람다식 '(a, b) -> Person.compareByAge(a, b)'와 의미상 동일합니다. 다음과 같은 특징이 있습니다.

  • 형식 인자(formal parameter) 리스트 (Person, Person)는 Comparator<Person>.compare에서 복사됩니다.
  • 본문은 Person.compareByAge 메소드를 호출합니다.


생성자 레퍼런스

new 이름을 사용해 static 메소드와 동일한 방법으로 생성자를 참조할 수 있습니다. 다음 메소드는 컬렉션에서 다른 컬렉션으로 요소(elements)를 복사합니다.

public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
    DEST transferElements(
        SOURCE sourceCollection,
        Supplier<DEST> collectionFactory) {
        
        DEST result = collectionFactory.get();
        for (T t : sourceCollection) {
            result.add(t);
        }
        return result;
}

함수형 인터페이스 Supplier에는 인수(argument)를 받지 않고 객체를 반환하는 메소드가 있습니다. 따라서 다음과 같이 람다식을 사용해 'transferElements' 메소드를 호출할 수 있습니다.

Set<Person> rosterSetLambda =
    transferElements(roster, () -> { return new HashSet<>(); });

다음과 같이 람다식 대신 생성자 레퍼런스를 사용할 수 있습니다.

Set<Person> rosterSet = transferElements(roster, HashSet::new);

자바 컴파일러는 당신이 Person 타입 요소를 포함하는 HashSet collection을 만들고 싶어한다고 추론합니다. 대안으로 다음과 같이 작성할 수 있습니다.

Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);

출처: https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

2021. 2. 26.

14주차 과제: 제네릭

https://github.com/whiteship/live-study/issues/14

목표

자바의 제네릭에 대해 학습하세요.

학습할 것 (필수)

  • 제네릭 사용법
  • 제네릭 주요 개념 (바운디드 타입, 와일드 카드)
  • 제네릭 메소드 만들기
  • Erasure


제네릭 사용법

2004년 J2SE(Java 2 Platform, Standard Edition. 6.0 버전 이후 'Java SE'로 변경) 5.0 버전에 추가된 기능입니다. 자바의 타입 시스템을 확장해 "컴파일 시 타입 안정성을 제공하면서 다양한 타입의 객체에서 작동하는 타입 또는 메소드"를 제공하도록 설계되었습니다.

Java 컬렉션 프레임 워크는 컬렉션 인스턴스에 저장된 오브젝트 타입을 지정하는 제네릭을 지원합니다.

다음 Java 코드는 제네릭을 사용하지 않을 때 존재하는 문제를 보여줍니다.

import java.util.List;
import java.util.ArrayList;

public class Main {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("hello"); // 문자열은 정수 타입으로 형 변환이 불가능합니다.
        Integer i = (Integer) list.get(0); // 런타임 오류
    }
}
Exception in thread "main" java.lang.ClassCastException: 
    class java.lang.String cannot be cast to class java.lang.Integer 
    (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
	at com.testcompany.Main.main(Main.java:11)

코드는 오류 없이 컴파일되지만, 메인 코드의 세 번째 줄을 실행할 때 런타임 예외(java.lang.ClassCastException)가 발생합니다. 이러한 유형의 논리 오류를 제네릭을 사용하여 컴파일 시간에 감지할 수 있으며 이를 사용하는 주요 동기입니다.

import java.util.List;
import java.util.ArrayList;

public class Main {

    public static void main(String[] args) {
        List<String> v = new ArrayList<String>();
        v.add("test");
        Integer i = (Integer) v.get(0); // (타입 오류)  컴파일 오류
    }
}

cast(형 변환) 제거

다음 코드는 제네릭을 사용함으로써 캐스팅이 불필요해짐을 보여줍니다.

import java.util.List;
import java.util.ArrayList;

public class Main {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("hello");
        Strign s = (String) list.get(0);
    }
}
import java.util.List;
import java.util.ArrayList;

public class Main {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("hello");
        String s = list.get(0); // 형 변환 불필요
    }
}


- 간단한 상자 클래스

public class Box {
    private Object object;

    public void set(Object object) { this.object = object; }
    public Object get() { return object; }
}

컴파일 시간에는 클래스가 어떻게 사용됐는지 확인할 방법이 없습니다. Integer 타입이 통과하고 다른 파트에서 실수로 String 값을 통과 시켜 런타임 에러를 발생할 수 있습니다.

- 제네릭 버전 상자 클래스

제네릭 클래스는 다음과 같은 형식으로 정의합니다.

class name<T1, T2, ..., Tn> { /* ... */ }

클래스 이름 다음으로 홑화살괄호(부등호 기호)로 구분된 타입 매개변수(parameter)가 있습니다. 타입 매개변수(타입 변수라고도 합니다.)는 'T1, T2, ..., Tn'처럼 열거합니다.

public class Box<T> {
    private T t;

    public void set(T t) { this.t = t; }
    public T get() { return t; }
}

Object는 모두 T로 대체됩니다. 똑같은 테크닉으로 제네릭 인터페이스도 만들 수 있습니다.

- 타입 파라미터 명명 규칙

규칙에 따라 타입 파라미터(유형 매개변수) 이름은 단일 대문자입니다. 이미 알고 있는 변수 명명 규칙과 다르지만 그럴만한 이유가 있습니다. 이 규칙이 없으면 타입 변수와 일반 클래스 또는 인터페이스 이름의 차이를 구분하기 어려울 겁니다.

가장 일반적으로 사용되는 타입 파라미터 이름은 다음과 같습니다.

  • E - Element (자바 컬렉션 프레임워크에서 광범위하게 사용)
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S, U, V etc. - 2nd, 3rd, 4th types

- 제네릭 유형 호출 및 인스턴스화

코드 내에서 제네릭 박스 클래스를 참조하려면 T를 Integer와 같은 구체적인 값으로 대체해 호출해야 합니다.

Box<Integer> integerBox;

클래스를 인스턴스화 하려면 평소처럼 new 키워드를 사용하되 클래스 이름과 괄호 사이에 <Integer>를 넣습니다.

Box<Integer> integerBox = new Box<Integer>

- 다이아몬드

Java SE 8 이상부터 컴파일러가 컨텍스트에서 타입 인수(arguments)를 결정하거나 추론할 수 있으면 제네릭 클래스에서 생성자를 호출하는데 필요한 타입 인수를 (<>) 세트로 바꿀 수 있습니다. 비공식적으로 다이아몬드라고 합니다.

Box<Integer> integerBox = new Box<>();

출처:
https://en.wikipedia.org/wiki/Generics_in_Java
https://docs.oracle.com/javase/tutorial/java/generics/why.html


제네릭 주요 개념 (바운디드 타입, 와일드 카드)

- 바운디드 타입 파라미터

타입 인수(type arguments)를 제한하려는 경우에 사용합니다. 예를 들어 숫자에 작동하는 메소드는 Number 또는 subclass 인스턴스만 허용하려고 할 수 있습니다. 이것이 바운디드 타입 파라미터(경계 유형 매개변수)의 용도입니다.

바운디드 타입 파라미터를 선언하려면 타입 파라미터의 이름, extends 키워드, 상위 경계(upper bound)(예시에서는 Number)를 나열합니다.

이 컨텍스트에서 extends는 확장(extends;클래스에서 사용되는) 또는 구현(implements;인터페이스에서 사용되는)의 일반적인 의미로 사용됩니다.

public <U extends Number> void inspect(U u){
    System.out.println("T: " + t.getClass().getName());
    System.out.println("U: " + u.getClass().getName());
}

바운드에 정의된 메소드 실행도 가능합니다.

public class NaturalNumber<T extends Integer> {

    private T n;

    public NaturalNumber(T n)  { this.n = n; }

    public boolean isEven() {
        return n.intValue() % 2 == 0;
    }

    // ...
}

isEven 메소드는 n을 사용해 Integer 클래스에 정의된 intValue 메소드를 호출합니다.

- 와일드 카드

제네릭 코드에서 와일드카드라고 하는 물음표(?)는 알 수 없는 타입을 나타냅니다. 와일드카드는 파라미터, 필드, 지역 변수의 타입 등 다양한 상황에서 사용할 수 있습니다.

public static void process (List <? extends Number> list) {/ * ... * /}

상한 제한 와일드카드(Upper Bounded Wildcards)는 예시처럼 Number 타입 또는 상속받는 subclass를 사용합니다.

public static void printList (List <?> list) {/ * ... * /}

무제한 와일드카드(Unbounded Wildcards)는 <?>를 사용합니다.

public static void addNumbers (List <? super Integer> list) {/ * ... * /}

하위 제한 와일드카드(Lower Bounded Wildcards)는 해당 타입과 superclass로 제한합니다.


제네릭 메소드 만들기

제네릭 메소드는 자체 타입 파라미터(형식 매개변수)를 사용합니다. 제네릭 타입을 선언하는 것과 비슷하지만 타입 파라미터 제한은 메소드 범위까지만 제한됩니다. 제네릭 클래스 생성자뿐만 아니라 static, non-static 제네릭 메소드도 사용 가능합니다.

public class Util {
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
    // ...
}

메소드를 호출하는 구문은 다음과 같습니다.

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);

// 타입을 생략 가능하며 컴파일러가 필요한 타입을 추론합니다.
boolean same = Util.compare(p1, p2);


Erasure

제네릭을 구현하기 위해 Java 컴파일러는 다음과 같은 상황에서 타입 제거(type erasure)를 적용합니다.

  • 제네릭 타입의 모든 타입 파라미터를 해당 바운드나 Object(unbounded 경우)로 교체합니다. 그러므로 생성된 바이트 코드에는 일반 클래스, 인터페이스 및 메소드만 포함됩니다.
  • 타입 안전을 유지하기 위해 필요한 경우 타입 캐스트를 삽입합니다.
  • 확장된 제네릭 타입의 다형성(polymorphism)을 보존하는 브릿지(bridge) 메소드를 생성합니다.

타입 제거(type erasure)는 parameterized(매개변수화) 타입에 새 클래스가 생성되지 않도록 합니다. 결과적으로 런타임 오버헤드를 발생시키지 않습니다.

public class Node<T> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
    // ...

타입 파라미터 T가 unbounded이기 때문에 컴파일러는 Object로 교체합니다.

public class Node {

    private Object data;
    private Node next;

    public Node(Object data, Node next) {
    // ...

다음 예제는 제한된(bounded) 타입 파라미터를 사용합니다.

public class Node<T extends Comparable<T>> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
    // ...

컴파일러는 bounded 타입 파라미터 T를 첫번째 바운드 클래스 Comparable로 교체합니다.

public class Node {

    private Comparable data;
    private Node next;

    public Node(Comparable data, Node next) {
    // ...

타입 제거 프로세스 일부로 브리지 메소드를 만들어야 할 수 있습니다. 일반적으로 브리지 메소드에 대해 걱정할 필요는 없지만, 스택 추적에 나타나는 경우 당황할 수 있습니다.

public class Node {

    public Object data;

    public Node(Object data) { this.data = data; }

    public void setData(Object data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node {

    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

타입 삭제 후 메소드의 시그니쳐가 일치하지 않습니다. Node 메소드는 setData(Object)가 되고 MyNode 메소드는 setData(Integer)가 됩니다. 따라서 MyNode setData 메소드는 Node setData 메소드를 재정의하지 않습니다.

이 문제를 해결하고 다형성을 보존하기 위해 컴파일러는 subtype이 예상대로 작동하는지 확인하는 브릿지 메소드를 생성합니다.

class MyNode extends Node {

    // Bridge method generated by the compiler
    //
    public void setData(Object data) {
        setData((Integer) data);
    }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }

    // ...
}

출처: https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

2021. 2. 19.

13주차 과제: I/O

https://github.com/whiteship/live-study/issues/13

목표

자바의 Input과 Ontput에 대해 학습하세요.

학습할 것 (필수)

  • 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
  • InputStream과 OutputStream
  • Byte와 Character 스트림
  • 표준 스트림 (System.in, System.out, System.err)
  • 파일 읽고 쓰기


스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O

스트림

  • 입력에서 출력으로 흐르는 흐름 (단방향)
  • FIFO(First In First Out)
  • 블록킹 방식만 지원(동기)
  • 입력 스트림, 출력 스트림 필요

버퍼

  • 기본 데이터 타입을 저장할 수 있는 저장소
  • 일정한 양이 채워지면 데이터를 전송

채널

  • 쌍방향 통로
  • 데이터를 주고받을 때 버퍼를 사용
  • 입력과 출력을 위한 별도의 채널 불필요

NIO (New I/O, Non-blocking I/O)

  • 자바 1.4 버전부터 추가된 API
  • Buffer(버퍼)를 사용
  • 비동기 방식 지원
  • 스트림이 아닌 채널(Channel)을 사용


InputStream과 OutputStream

InputStream

  • 바이트 단위 입출력을 위한 최상위 입력 스트림 클래스
  • FileInputStream, BufferedInputStream, DataInputStream

표 출처: https://coding-factory.tistory.com/281


OutputStream

  • 바이트 단위 입출력을 위한 최상위 입력 스트림 클래스
  • FileOutputStream, PrintStream, BufferedOutputStream, DataOutputStream

표 출처: https://coding-factory.tistory.com/281


Byte와 Character 스트림

Byte Stream

  • 바이트 기반 입출력 스트림
  • 입출력 단위가 1byte
  • FileInputStream/FileOutputStream: 파일
  • ByteArrayInputStream/ByteArrayOutputStream: 메모리(byte배열)
  • PipedInputStream/PipedOutputStream: 프로세스(프로세스간 통신)
  • AudioInputStream/AudioOutputStream: 오디오 장치

Character Stream

  • 문자(2 byte)를 위한 스트림
  • 모든 문자 스트림 클래스는 Reader / Writer의 자손
  • 스트림 클래스로 수행 된 입력 및 출력은 로컬 문자 집합과 자동으로 변환

https://docs.oracle.com/javase/tutorial/essential/io/charstreams.html


표준 스트림 (System.in, System.out, System.err)

콘솔을 통한 데이터 입력과 출력을 위한 스트림

  • System.in - 콘솔로부터 데이터 입력받는데 사용
  • System.out - 콘솔 화면에 문자열 출력을 위한 용도
  • System.err - System.out과 같이 콘솔 문자열 출력이지만 버퍼링을 지원하지 않고 빨간색으로 출력된다.


파일 읽고 쓰기

채널 I/O를 사용한 파일 읽고 쓰는 방법

스트림 I/O가 한 번에 문자를 읽는 동안 채널 I/O는 한 번에 버퍼를 읽습니다. ByteChannel 인터페이스는 기본으로 read write 기능을 제공합니다. SeekableByteChannel은 ByteChannel의 위치를 유지하거나 변경할 수 있는 기능이 있습니다. 또한 채널과 관련된 파일 자르기 및 파일 크기 쿼리를 지원합니다.

채널 I/O를 읽고 쓰는 방법에는 두 가지가 있습니다.

  • newByteChannel(Path, OpenOption...)
  • newByteChannel(Path, <Set? extends OpenOption>, FileAttribute<?>...)

다음 코드 조각은 파일을 읽고 표준 출력으로 인쇄합니다.

// Defaults to READ
try (SeekableByteChannel sbc = Files.newByteChannel(file)) {
    ByteBuffer buf = ByteBuffer.allocate(10);

    // Read the bytes with the proper encoding for this platform.  If
    // you skip this step, you might see something that looks like
    // Chinese characters when you expect Latin-style characters.
    String encoding = System.getProperty("file.encoding");
    while (sbc.read(buf) > 0) {
        buf.rewind();
        System.out.print(Charset.forName(encoding).decode(buf));
        buf.flip();
    }
} catch (IOException x) {
    System.out.println("caught exception: " + x);

UNIX 및 기타 POSIX 파일 시스템 용으로 작성된 다음 예제는 특정 파일 권한 집합을 사용하여 로그 파일을 만듭니다. 이 코드는 로그 파일을 만들거나 이미있는 경우 로그 파일에 추가합니다. 로그 파일은 소유자에 대한 읽기 / 쓰기 권한과 그룹에 대한 읽기 전용 권한으로 생성됩니다.

import static java.nio.file.StandardOpenOption. *; 
import java.nio. *; 
import java.nio.channels. *; 
import java.nio.file. *; 
import java.nio.file.attribute. *; 
import java.io. *; 
import java.util. *; 

public class LogFilePermissionsTest { 

  public static void main (String [] args) { 
  
    // 파일에 추가하기위한 옵션 세트를 만듭니다. 
    Set <OpenOption> options = new HashSet<OpenOption>(); 
    options.add(APPEND); 
    options.add(CREATE); 

    // 사용자 지정 권한 속성을 만듭니다. 
    Set <PosixFilePermission> perms = 
      PosixFilePermissions.fromString ("rw-r-----"); 
    FileAttribute <Set<PosixFilePermission>>
      PosixFilePermissions.asFileAttribute(perms); 

    // 문자열을 ByteBuffer로 변환합니다. 
    String s = "Hello World!"; 
    byte data[] = s.getBytes (); 
    ByteBuffer bb = ByteBuffer.wrap(data); 
    
    경로 파일 = Paths.get ("./permissions.log"); 

    try (SeekableByteChannel sbc = 
      Files.newByteChannel (file, options, attr)) { 
      sbc.write (bb); 
    } catch (IOException x) { 
      System.out.println ("예외 발생: "+ x); 
    } 
  } 
}

출처: https://docs.oracle.com/javase/tutorial/essential/io/file.html

2021. 2. 5.

12주차 과제: 애노테이션

https://github.com/whiteship/live-study/issues/12

목표

자바의 애노테이션에 대해 학습하세요.

학습할 것 (필수)

  • 애노테이션 정의하는 방법
  • @retention
  • @target
  • @documented
  • 애노테이션 프로세서


애노테이션 정의하는 방법

// 골뱅이(@, at sign) 기호는 컴파일러에게 주석임을 알려줍니다.
// 예) @재정의
@Override
void mySuperMethod() { ... }

자바 애노테이션(Annotations)이란?

메타 데이터(metadata: 다른 데이터를 설명해 주는 데이터)의 한 형태인 주석(Annotations)은 프로그램의 일부는 아니지만, 프로그램에 대한 데이터를 제공합니다.

주석은 코드 작동에 직접적인 영향을 주지 않습니다.

주석은 다음과 같은 여러 용도가 있습니다.

  • 컴파일러에 대한 정보 - 컴파일러에서 주석을 사용하여 오류를 감지하거나 경고를 억제할 수 있습니다.
  • 컴파일 시간, 배포 시간 처리 - 주석 정보를 처리해 코드, XML 파일 등을 생성할 수 있습니다.
  • 런타임 처리 - 일부 주석은 런타임에 검사 가능
@SuppressWarnings (value = "unchecked") 
void myMethod () {...}

값이 하나만 있으면 다음과 같이 생략 가능합니다.

@SuppressWarnings ("unchecked")

주석 유형이 동일한 경우 반복 주석이라고 합니다.

@Author (name = "Kim") 
@Author (name = "Park") 
class MyClass {...}

반복 주석은 Java SE 8 릴리스부터 지원됩니다.

클래스, 필드, 메소드 및 기타 프로그램 요소 선언에도 주석을 사용할 수 있습니다.

자바 SE 8 릴리스부터 주석을 타입으로 적용할 수도 있습니다.

// 클래스 인스턴스 생성
new @Interned MyObject();

// 타입 캐스트
myString = (@NonNull String) str;

// 구현 절(implements clause)
class UnmodifiableList<T> implements
        @Readonly List<@Readonly T> { ... }

// 예외 처리
void monitorTemperature() throws
        @Critical TemperatureException { ... }

많은 애노테이션이 코드의 주석(코멘트)을 대체합니다.

소프트웨어 개발 그룹이 전통적으로 클래스 시작을 다음과 같이 중요한 정보를 제공하는 주석으로 시작한다고 가정해 봅시다.

public class MyClass extends MySuperClass {

   // Author: Tester1
   // Date: 2/01/2021
   // Current revision: 3
   // Last modified: 1/31/2021
   // By: Kim
   // Reviewers: Alice, Bill, Cindy

}

애노테이션을 사용해 똑같은 메타데이터를 추가합니다. 먼저 애노테이션 타입을 정의합니다.

@interface ClassPreamble {
   String author();
   String date();
   int currentRevision() default 1;
   String lastModified() default "N/A";
   String lastModifiedBy() default "N/A";
   // Note use of array
   String[] reviewers();
}

애노테이션 타입 정의는 인터페이스 정의와 비슷하게 interface 키워드 앞에 @를 붙입니다. 애노테이션 타입은 인터페이스의 한 형태입니다.

애노테이션 타입을 정의한 후 다음과 같이 값을 채워 타입으로 사용할 수 있습니다.

@ClassPreamble (
   author = "Tester1",
   date = "2/01/2021",
   currentRevision = 3,
   lastModified = "1/31/2021",
   lastModifiedBy = "Kim",
   reviewers = {"Alice", "Bob", "Cindy"}
)
public class MyClass extends MySuperClass { ... }

참고: @ClassPreamble의 정보가 Javadoc 문서에 나타나도록 하려면 @ClassPreamble 정의에 @Documented 주석을 추가해야 합니다.

// @Documented 사용하기 위해 import
import java.lang.annotation.*;

@Documented
@interface ClassPreamble {
   // 애노테이션 요소 정의
}

출처:
https://docs.oracle.com/javase/tutorial/java/annotations/


@Retention

애노테이션 타입이 있는 애노테이션이 얼마나 보존될지를 나타냅니다. 타입 선언에 Retention 애노테이션이 없는 경우 retention 정책 기본값은 RetentionPolicy.CLASS 입니다.

  • RetentionPolicy.SOURCE - 소스 수준에서만 유지되며 컴파일러에서 무시됩니다.
  • RetentionPolicy.CLASS - 컴파일 타임에 컴파일러에 의해 유지되지만 JVM에서는 무시됩니다.
  • RetentionPolicy.RUNTIME - JVM에 의해 유지되므로 런타임 환경에서 사용할 수 있습니다.

Retention.java

package java.lang.annotation;

/**
 * Indicates how long annotations with the annotated type are to
 * be retained.  If no Retention annotation is present on
 * an annotation type declaration, the retention policy defaults to
 * {@code RetentionPolicy.CLASS}.
 *
 * A Retention meta-annotation has effect only if the
 * meta-annotated type is used directly for annotation.  It has no
 * effect if the meta-annotated type is used as a member type in
 * another annotation type.
 *
 * @author  Joshua Bloch
 * @since 1.5
 * @jls 9.6.4.2 @Retention
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class MyTest {
    public static void main(String[] args) {
    
    @Retention(RetentionPolicy.RUNTIME)
//    @Retention(RetentionPolicy.CLASS)
//    @Retention(RetentionPolicy.SOURCE)
    @Target(ElementType.METHOD)
    /* @Target({
            ElementType.PACKAGE, // 패키지 선언시
            ElementType.TYPE, // 타입 선언시
            ElementType.CONSTRUCTOR, // 생성자 선언시
            ElementType.FIELD, // 멤버 변수 선언시
            ElementType.METHOD, // 메소드 선언시
            ElementType.ANNOTATION_TYPE, // 어노테이션 타입 선언시
            ElementType.LOCAL_VARIABLE, // 지역 변수 선언시
            ElementType.PARAMETER, // 매개 변수 선언시
            ElementType.TYPE_PARAMETER, // 매개 변수 타입 선언시
            ElementType.TYPE_USE // 타입 사용시
    }) */
    public @interface MyAnnotation {
        /* enum 타입을 선언할 수 있습니다. */
        public enum Quality {BAD, GOOD, VERYGOOD}
        /* String은 기본 자료형은 아니지만 사용 가능합니다. */
        String value();
        /* 배열 형태로도 사용할 수 있습니다. */
        int[] values();
        /* enum 형태를 사용하는 방법입니다. */
        Quality quality() default Quality.GOOD;
    }
}

코드 출처: https://jdm.kr/blog/216

출처:
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/Retention.html

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/Target.html

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/ElementType.html


@Target

애노테이션을 어디에 적용할 수 있는지 나타내는 컨텍스트입니다.

@Target 메타-애노테이션은 선언된 유형 자체가 메타 주석 유형임을 나타냅니다. 주석 유형 선언에서만 사용할 수 있습니다.

    @Target(ElementType.ANNOTATION_TYPE)
    public @interface MetaAnnotationType {
        ...
    }

이 @Target 메타 주석은 복잡한 주석 유형 선언에서 맴버 유형으로 사용하기위한 것입니다. 직접 주석을 다는데는 사용할 수 없습니다.

    @Target({})
    public @interface MemberType {
        ...
    }

ElementType 상수가 @Target 애노테이션에 두 번 이상 나타나는 것은 컴파일 타임 오류입니다.

    @Target({ElementType.FIELD, ElementType.METHOD, ElementType.FIELD})
    public @interface Bogus {
        ...
    }

출처: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/Target.html


@Documented

'Documented 주석 처리된 경우 javadoc과 같은 기본 도구는 출력시 해당 유형의 주석을 표시하지만 Documented가 없는 주석은 표시되지 않습니다.'

javadoc(소스 코드의 문서 주석을 HTML 형식으로 API 문서를 생성하는 도구)으로 api 문서를 만들 때 어노테이션에 대한 설명도 포함하도록 지정하는 메타테그

주석 포맷 설명: https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html#format

출처: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/Documented.html


애노테이션 프로세서

애노테이션 프로세서는 컴파일 타임에 주석을 스캔하고 처리하기 위해 javac로 빌드된 도구입니다.

주석을 만들고 처리하는 단계

  • 주석 선언
  • 주석 프로세서 구현
  • 코드에서 주석 사용

AbstractProcessor

package com.example;

public class MyProcessor extends AbstractProcessor {

	@Override
	public synchronized void init(ProcessingEnvironment env){ }

	@Override
	public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }

	@Override
	public Set<String> getSupportedAnnotationTypes() { }

	@Override
	public SourceVersion getSupportedSourceVersion() { }

}
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;

@SupportedAnnotationTypes("Todo")   // (1)
@SupportedSourceVersion(SourceVersion.RELEASE_8)   // (2)
public class TodoProcessor extends AbstractProcessor{   // (3)
    // (4)
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        
    }
}

(1) SupportedAnnotationTypes는 클래스가 처리할 주석의 유형을 알려주는 메타 주석입니다. 이 경우 클래스는 Todo 주석을 처리합니다.

(2) SupportedSourceVersion 이 주석 프로세서가 지원하는 최신 Java 버전입니다.

(3) 주석 프로세서는 Processor 인터페이스를 구현 하거나 AbstractProcessor 클래스를 확장해야 합니다.

(4) AbstractProcessor 클래스의 프로세스 메서드를 재정의 합니다 . 컴파일러는 응용 프로그램의 모든 클래스에서이 메서드를 호출합니다.

출처:
https://docs.oracle.com/en/java/javase/11/docs/api/java.compiler/javax/annotation/processing/AbstractProcessor.html

https://thetechstack.net/how-to-process-annotations-in-java/

https://www.baeldung.com/java-annotation-processing-builder

https://iammert.medium.com/annotation-processing-dont-repeat-yourself-generate-your-code-8425e60c6657

http://hannesdorfmann.com/annotation-processing/annotationprocessing101/

https://medium.com/@jason_kim/annotation-processing-101-%EB%B2%88%EC%97%AD-be333c7b913

2021. 1. 29.

11주차 과제: Enum

https://github.com/whiteship/live-study/issues/11

목표

자바의 열거형에 대해 학습하세요.

학습할 것 (필수)

  • enum 정의하는 방법
  • enum이 제공하는 메소드 ( values()와 valueOf() )
  • java.lang.Enum
  • EnumSet


enum 정의하는 방법

public enum Season {
    SPRING(3, 4, 5),
    SUMMER(6, 7, 8),
    AUTUMN(9, 10, 11),
    WINTER(12, 1, 2);

    Season(int i, int i1, int i2) {
        this.start = i;
        this.middle = i1;
        this.end = i2;
    }
    private final int start;
    private final int middle;
    private final int end;

    public int getStart() {
        return start;
    }

    public int getMiddle() {
        return middle;
    }

    public int getEnd() {
        return end;
    }
}
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class MySeasonTest {
    Season season;

    public MySeasonTest(Season season) {
        this.season = season;
    }

    public void showMeTheSeason() {
        System.out.println(season.getStart());
        System.out.println(season.getMiddle());
        System.out.println(season.getEnd());
    }

    @Test
    @DisplayName("main")
    public static void main(String[] args) {
        MySeasonTest mySeasonTest
                = new MySeasonTest(Season.WINTER);
        mySeasonTest.showMeTheSeason();
    }
}
> Task :MySeasonTest.main()
12
1
2

BUILD SUCCESSFUL in 415ms
3 actionable tasks: 2 executed, 1 up-to-date
  • Java의 enum 타입은 클래스
  • 상수이기에 변수명은 대문자
  • 암시적으로 java.lang.Enum을 상속(extends)
  • enum 생성자는 반드시 package-private, private 접근
  • enum 시작 부분에 정의된 상수를 자동으로 생성
  • enum 생성자를 직접 호출 불가능
  • 암시적으로 public static final

출처, 참고:
https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html

https://docs.oracle.com/javase/specs/jls/se13/html/jls-8.html#jls-8.9.2

https://woowabros.github.io/tools/2017/07/10/java-enum-uses.html


enum이 제공하는 메소드 ( values()와 valueOf() )

    @Test
    @DisplayName("main")
    public static void main(String[] args) {
        MySeasonTest mySeasonTest
                = new MySeasonTest(Season.WINTER);

        // valueOf()
        Season tempSeason 
                = mySeasonTest.season.valueOf("SUMMER");
        System.out.println("tempSeason.getStart(): " 
                + tempSeason.getStart());

        // values()
        for (Season s :
                Season.values()) {
            System.out.println(s + ", " 
                    + s.getStart() + ", " 
                    + s.getMiddle() + ", " 
                    + s.getEnd());
        }
        
        // ordinal()
        for (Season s :
                Season.values()) {
            System.out.println(
                    s + " at index " + s.ordinal());
        }
    }
> Task :MySeasonTest.main()

// valueOf()
tempSeason.getStart(): 6

// values()
SPRING, 3, 4, 5
SUMMER, 6, 7, 8
AUTUMN, 9, 10, 11
WINTER, 12, 1, 2

// ordinal()
SPRING at index 0
SUMMER at index 1
AUTUMN at index 2
WINTER at index 3

BUILD SUCCESSFUL in 411ms
3 actionable tasks: 2 executed, 1 up-to-date
  • valueOf(): 지정한 이름의 enum 상수를 반환
  • values(): 해당 enum의 모든 상수를 저장한 배열을 생성하여 반환
  • ordinal(): enum 상수의 순서값 반환
  • toString(): enum 상수의 이름을 반환
  • name(): enum 상수의 이름을 반환

toString() 메소드가 사용자에게 더 친숙한 이름을 반환하므로 name() 메소드보다 우선적으로 사용해야 합니다. (Most programmers should use the toString() method in preference to this one, as the toString method may return a more user-friendly name.)

출처: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Enum.html#name()


Enum 바이트코드(ByteCode)

public enum Day {
    SUNDAY, MONDAY, TUESDAY
}
javap -c Day.class

Compiled from "Day.java"
public final class com.testcompany.Day extends java.lang.Enum<com.testcompany.Day> {
  public static final com.testcompany.Day SUNDAY;

  public static final com.testcompany.Day MONDAY;

  public static final com.testcompany.Day TUESDAY;

  public static com.testcompany.Day[] values();
    Code:
       0: getstatic       #13  // Field $VALUES:[Lcom/testcompany/Day;
       3: invokevirtual   #17  // Method "[Lcom/testcompany/Day;".clone:()Ljava/lang/Object;
       6: checkcast       #18  // class "[Lcom/testcompany/Day;"
       9: areturn

  public static com.testcompany.Day valueOf(java.lang.String);
    Code:
       0: ldc             #1  // class com/testcompany/Day
       2: aload_0
       3: invokestatic    #22 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
       6: checkcast       #1  // class com/testcompany/Day
       9: areturn

  static {};
    Code:
       0: new             #1  // class com/testcompany/Day
       3: dup
       4: ldc             #32 // String SUNDAY
       6: iconst_0
       7: invokespecial   #33 // Method "<init>":(Ljava/lang/String;I)V
      10: putstatic       #3  // Field SUNDAY:Lcom/testcompany/Day;
      13: new             #1  // class com/testcompany/Day
      16: dup
      17: ldc             #34 // String MONDAY
      19: iconst_1
      20: invokespecial   #33 // Method "<init>":(Ljava/lang/String;I)V
      23: putstatic       #7  // Field MONDAY:Lcom/testcompany/Day;
      26: new             #1  // class com/testcompany/Day
      29: dup
      30: ldc             #35 // String TUESDAY
      32: iconst_2
      33: invokespecial   #33 // Method "<init>":(Ljava/lang/String;I)V
      36: putstatic       #10 // Field TUESDAY:Lcom/testcompany/Day;
      39: invokestatic    #36 // Method $values:()[Lcom/testcompany/Day;
      42: putstatic       #13 // Field $VALUES:[Lcom/testcompany/Day;
      45: return
}
  • 놀랍게도 바이트코드 내부에 'public static final' 선언
  • values(), valueOf()도 미리 준비
  • 메소드의 형태로 static 필드에 선언

바이트 코드 설명

  • getstatic: 클래스의 static 필드값 가져오기
  • invokevirtual: 인스턴스 메서드 호출
  • checkcast: 객체의 타입 확인
  • areturn: 메소드 참조 반환 (Return reference from method)
  • ldc: 런타임 상수 풀에 아이템 푸시
  • aload_<n>: 지역변수 레퍼런스 로드
  • invokestatic: static 메서드 호출
  • new: 새로운 객체 생성
  • dup: 연산자 스택의 맨 위의 값 복사
  • iconst_<i>: int 상수 푸시
  • invokespecial: 생성자, private 메소드, 슈퍼 클래스의 메소드 호출
  • putstatic: static 필드 선언
  • return: Return void from method
여기서 관심을 가질 부분은 "Ljava/lang/String;"과 마지막의 "V"이다. 자바 바이트코드의 표현에서 "L;"은 클래스 인스턴스이다. 즉, addUser() 메서드는 java/lang/String 객체 하나를 파라미터로 받는 메서드이다. 이 사례의 라이브러리에서는 파라미터가 변경되지 않았으므로 이 파라미터는 정상이다. 위 메시지 마지막의 "V"는 메서드의 반환값을 나타낸다. 자바 바이트코드 표현에서 "V"는 반환값이 없음을 의미한다.

참고, 출처:
https://d2.naver.com/helloworld/1230

https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings

https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-6.html


java.lang.Enum

public abstract class Enum<E extends Enum<E>>
        implements Constable, Comparable<E>, Serializable {
    private final String name;
    
    @NotNull
    public final String name() {
        return name;
    }
    
    private final int ordinal;
    
    @Range(from = 0, to = java.lang.Integer.MAX_VALUE)
    public final int ordinal() {
        return ordinal;
    }
    
    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }
    
    public String toString() {
        return name;
    }
    
    ...
}
  • 모든 자바 열거형의 기본 클래스
  • Enum은 추상 클래스. Enum 클래스를 생성할 수 없다.
  • final 메소드로 선언되므로 수정이 불가능


EnumSet

// Creates an enum set containing all of the elements 
// in the specified element type.
// 명시한 요소 타입이 모두 들어간 enum 세트 생성
EnumSet<Season> myEnumSet = EnumSet.allOf(Season.class);
System.out.println("allOf(Season.class): " + myEnumSet);

// Creates an empty enum set with the specified element type.
// 명시한 요소 타입을 사용해 빈 enum 세트 생성
myEnumSet = EnumSet.noneOf(Season.class);
System.out.println("noneOf(Season.class): " + myEnumSet);

// Creates an enum set initially containing the specified element.
// 생성시 명시한 요소가 들어간 enum 세트 생성
myEnumSet = EnumSet.of(Season.SPRING, Season.AUTUMN);
System.out.println("of(Season.SPRING, Season.AUTUMN): " + myEnumSet);

// Creates an enum set with the same element type 
// as the specified enum set, initially containing 
// all the elements of this type that are not contained 
// in the specified set.
// 명시한 세트가 포함되지 않은 enum 세트 생성
myEnumSet = EnumSet.complementOf(myEnumSet);
System.out.println("complementOf(myEnumSet): " + myEnumSet);

// Creates an enum set initially containing all of the elements 
// in the range defined by the two specified endpoints.
// 지정된 두 요소 범위안의 enum 세트를 생성
myEnumSet = EnumSet.range(Season.SUMMER, Season.AUTUMN);
System.out.println("range(Season.SPRING, Season.AUTUMN): " + myEnumSet);
allOf(Season.class): [SPRING, SUMMER, AUTUMN, WINTER]
noneOf(Season.class): []
of(Season.SPRING, Season.AUTUMN): [SPRING, AUTUMN]
complementOf(myEnumSet): [SUMMER, WINTER]
range(Season.SPRING, Season.AUTUMN): [SUMMER, AUTUMN]

Process finished with exit code 0
  • enum 타입과 함께 사용하기 위한 특수 Set의 구현
  • 모든 요소는 enum 셋을 만들 때 명시적으로 또는 암시적으로 지정된 단일 enum 타입을 가져와야 함
  • 내부적으로 bit vector로 표시됨 (매우 간결하고 효율적)
  • 이 클래스의 공간 및 시간 성능은 기존의 int 기반 "비트 플래그"의 대안으로 고품질에 타입 안전으로 사용할 수 있을 만큼 충분히 우수해야 함

출처: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/EnumSet.html


2021. 1. 22.

10주차 과제: 멀티쓰레드 프로그래밍

https://github.com/whiteship/live-study/issues/10

목표

자바의 멀티쓰레드 프로그래밍에 대해 학습하세요.

학습할 것 (필수)

  • Thread 클래스와 Runnable 인터페이스
  • 쓰레드의 상태
  • 쓰레드의 우선순위
  • Main 쓰레드
  • 동기화
  • 데드락


Thread 클래스와 Runnable 인터페이스

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println("Runnable thread");
    }

    public static void main(String args[]) {
        (new Thread(new MyRunnable())).start();
    }

}
public class MyThread extends Thread {

    public void run() {
        System.out.println("extends thread");
    }

    public static void main(String args[]) {
        (new MyThread()).start();
    }

}

출처: https://docs.oracle.com/javase/tutorial/essential/concurrency/runthread.html


쓰레드의 상태

NEW - 아직 시작되지 않은 스레드 상태

RUNNABLE - Java 가상 머신에서 실행중인 스레드 상태

BLOCKED - 모니터 잠금을 기다리면서 차단 된 스레드 상태

WAITING - 다른 스레드가 특정 작업을 수행 할 때까지 무기한 대기중인 스레드 상태

TIMED_WAITING - 다른 스레드가 지정된 대기 시간까지 작업을 수행하기를 기다리는 상태

TERMINATED - 종료 된 스레드 상태

public class MyThread implements Runnable {
    @Override
    public void run() {
        // moving thread2 to timed waiting state
        try {
            Thread.sleep(1500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("State of thread1 while it 
        called join() method on thread2 -"
        + MyTest.thread1.getState());
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class MyTest implements Runnable {
    public static Thread thread1;
    public static MyTest obj;

    public static void main(String[] args) {
        obj = new MyTest();
        thread1 = new Thread(obj);

        // thread1 created and is currently 
        // in the NEW state.
        System.out.println("State of thread1 after 
        creating it - " + thread1.getState());
        thread1.start();

        // thread1 moved to Runnable state
        System.out.println("State of thread1 after calling 
        .start() - " 
        + thread1.getState());
    }

    @Override
    public void run() {
        MyThread myThread = new MyThread();
        Thread thread2 = new Thread(myThread);

        // thread1 created and is currently in the NEW
        // state.
        System.out.println("State of thread2 after
        creating it - " + thread2.getState());
        
        thread2.start();

        // thread2 moved to Runnable state
        System.out.println("State of thread2 after calling
        .start() - " + thread2.getState());

        // moving thread1 to timed waiting state
        try {
            //moving thread1 to timed waiting state
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("State of thread2 after calling
        .sleep() - " + thread2.getState());

        try {
            // waiting for thread2 to die
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("State of thread2 when it has
        finished - " + thread2.getState());
    }
}
State of thread1 after creating it - NEW
State of thread1 after calling .start() - RUNNABLE
State of thread2 after creating it - NEW
State of thread2 after calling .start() - RUNNABLE
State of thread2 after calling .sleep() - TIMED_WAITING
State of thread1 while it called join() - WAITING
State of thread2 when it has finished - TERMINATED

Process finished with exit code 0

코드 출처: https://www.geeksforgeeks.org/lifecycle-and-states-of-a-thread-in-java/


쓰레드의 우선순위

public static int MIN_PRIORITY: 최소 우선 순위. 값: 1

public static int NORM_PRIORITY: 기본 우선 순위. 값: 5. 명시하지 않을 경우 기본값.

public static int MAX_PRIORITY: 스레드의 최대 우선 순위. 값: 10


public final int getPriority(): java.lang.Thread.getPriority() 주어진 스레드의 우선 순위를 리턴

public final void setPriority(int newPriority): java.lang.Thread.setPriority() 스레드의 우선 순위를 newPriority 값으로 변경. newPriority의 값이 minimum(1) maximum(10) 한계를 초과하면이 IllegalArgumentException 발생

public class ThreadDemo extends Thread {

    public void run() {
        System.out.println("Inside run method");
    }

    public static void main(String[] args) {
        ThreadDemo t1 = new ThreadDemo();
        ThreadDemo t2 = new ThreadDemo();
        ThreadDemo t3 = new ThreadDemo();

        // Default 5
        System.out.println("t1 thread priority : "
                + t1.getPriority());

        // Default 5
        System.out.println("t2 thread priority : "
                + t2.getPriority());

        // Default 5
        System.out.println("t3 thread priority : "
                + t3.getPriority());

        t1.setPriority(2);
        t2.setPriority(5);
        t3.setPriority(8);

        // t3.setPriority(21); will throw
        // IllegalArgumentException

        // 2
        System.out.println("t1 thread priority : "
                + t1.getPriority());

        // 5
        System.out.println("t2 thread priority : "
                + t2.getPriority());

        // 8
        System.out.println("t3 thread priority : "
                + t3.getPriority());

        // Main thread

        // Displays the name of
        // currently executing Thread
        System.out.println(
                "Currently Executing Thread : "
                + Thread.currentThread().getName());

        System.out.println(
                "Main thread priority : "
                + Thread.currentThread().getPriority());

        // Main thread priority is set to 10
        Thread.currentThread().setPriority(10);
        System.out.println(
                "Main thread priority : "
                + Thread.currentThread().getPriority());
    }
}
t1 thread priority : 5
t2 thread priority : 5
t3 thread priority : 5
t1 thread priority : 2
t2 thread priority : 5
t3 thread priority : 8
Currently Executing Thread : main
Main thread priority : 5
Main thread priority : 10

Process finished with exit code 0

코드 출처: https://www.geeksforgeeks.org/java-thread-priority-multithreading/


Main 쓰레드

Java 프로그램이 시작되면 하나의 스레드가 즉시 실행. 프로그램이 시작될 때 실행되는 스레드이기 때문에 일반적으로 프로그램의 메인 스레드라고 한다.

"자식"스레드가 생성되는 스레드

public class MyTestThread extends Thread {

    public static void main(String[] args) {
        // getting reference to Main thread
        Thread t = Thread.currentThread();
        // getting name of Main thread
        System.out.println(
                "Current thread: "
                        + t.getName());
        // changing the name of Main thread
        t.setName("Geeks");
        System.out.println(
                "After name change: "
                        + t.getName());
        // getting priority of Main thread
        System.out.println(
                "Main thread priority: "
                        + t.getPriority());
        // setting priority of Main thread to MAX(10)
        t.setPriority(MAX_PRIORITY);
        System.out.println(
                "Main thread new priority: "
                        + t.getPriority());
    }
}
Current thread: main
After name change: Geeks
Main thread priority: 5
Main thread new priority: 10

Process finished with exit code 0

코드 출처: https://www.geeksforgeeks.org/main-thread-java/


동기화

public class SynchronizedCounter {
    private int c = 0;

    public synchronized void increment() {
        c++;
    }

    public synchronized void decrement() {
        c--;
    }

    public synchronized int value() {
        return c;
    }
}

1. 동기화(synchronized) 메소드를 동일한 객체에서 두 번 호출하는 인터리빙(interleave - 스레드가 수행되는 순서)이 불가능해진다. 첫 스레드가 동기화 메소드를 실행 완료할 때까지 같은 블록의 동기화 메소드를 실행한 모든 스레드는 실행을 일시 중단하고 기다린다.

2. 동기화 메소드가 종료되면 같은 객체의 동기화된 메소드의 다음 호출 관계를 자동으로 설정. 모든 스레드에 객체의 상태 변경 사항을 보여준다.

생성자는 동기화가 불가능. 생성자를 동기화하는 것은 의미가 없습니다. 객체를 생성하는 스레드만이 접근할 수 있어야 하기 때문.

출처: https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html


데드락

public class Deadlock {
    static class Friend {
        private final String name;
        public Friend(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
        public synchronized void bow(Friend bower) {
            System.out.format("%s: %s"
                + "  has bowed to me!%n", 
                this.name, bower.getName());
            
            // 교착상태를 만드는 부분
            bower.bowBack(this);
        }
        public synchronized void bowBack(Friend bower) {
            System.out.format("%s: %s"
                + " has bowed back to me!%n",
                this.name, bower.getName());
        }
    }

    public static void main(String[] args) {
        final Friend alphonse = new Friend("Alphonse");
        final Friend gaston = new Friend("Gaston");
        
        new Thread(new Runnable() {
            public void run() { alphonse.bow(gaston); }
        }).start();
        
        new Thread(new Runnable() {
            public void run() { gaston.bow(alphonse); }
        }).start();
    }
}

코드 출처: https://docs.oracle.com/javase/tutorial/essential/concurrency/deadlock.html

동기화된 bowBack 메소드를 호출하지만 동기화된 bow 메소드 종료를 기다리기에 교착상태(Deadlock) 발생


2021. 1. 15.

9주차 과제: 예외 처리

https://github.com/whiteship/live-study/issues/9

목표

자바의 예외 처리에 대해 학습하세요.

학습할 것 (필수)

  • 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
  • 자바가 제공하는 예외 계층 구조
  • Exception과 Error의 차이는?
  • RuntimeException과 RE가 아닌 것의 차이는?
  • 커스텀한 예외 만드는 방법


자바에서 예외 처리 방법 (try, catch, throw, throws, finally)

public class MyClass {
    public static void main(String[] args) {
        try {
        
            // 예외를 발생시킬 수 있는 코드
            int[] myArray = {1, 2, 3};
            System.out.println(myArray[3]);
            
        } catch (Exception e) {
        
            // 오류 발생 시 실행할 코드
            System.err.println(e.getMessage());
            
        } finally {
        
            // try 블록 종료 시 항상 실행되는 코드
            System.out.println("try catch block end");
        }
    }
}
Index 3 out of bounds for length 3
try catch block end

Process finished with exit code 0
public class MyClass {

    private static void checkAge(int age) 
            throws ArithmeticException {

        if (age < 18) {
            // throw를 사용해 특정 Exception으로 예외를 던진다.
            throw new ArithmeticException("Access denied");
        } else {
            System.out.println("Access granted");
        }
    }
    public static void main(String[] args) {
        checkAge(20);
    }
}
Exception in thread "main"
java.lang.ArithmeticException: Access denied
	at com.company.MyClass.checkAge(MyClass.java:7)
	at com.company.MyClass.main(MyClass.java:13)

출처: https://www.w3schools.com/java/java_try_catch.asp

throw / throws 비교

  • 메소드 안에서 예외를 사용 / 메소드의 블럭에서 예외를 받아 처리
  • 1개의 예외 선언 가능 / 여러개의 예외 선언 가능

https://www.w3schools.com/java/ref_keyword_throws.asp

https://docs.oracle.com/javase/tutorial/essential/exceptions/try.html

https://docs.oracle.com/javase/tutorial/essential/exceptions/catch.html

https://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html


자바가 제공하는 예외 계층 구조

출처: https://www.manishsanger.com/java-exception-hierarchy/

Throwable

  • 예외처리의 최상위 클래스
  • Serializable 인터페이스의 구현
  • throwable에는 생성 당시 스레드의 실행 스택 스냅샷이 포함

https://www.geeksforgeeks.org/throwable-class-in-java-with-examples/

Error

  • 예상치 못한 심각한 문제
  • 처리 기술로 복구할 수 없는 조건 (비정상적인 프로그램 종료의 원인)
  • 런타임에 발생 (ex 메모리 부족, 시스템 충돌 오류)

Exception

  • 포착하려는 조건의 문제
  • 런타임시 발생하는 조건으로 프로그램이 종료 될 수 있지만 try catch, throw 키워드로 복구 가능
  • checked / unchecked 예외 두 가지 범주

https://docs.oracle.com/javase/tutorial/essential/exceptions/throwing.html


Exception과 Error의 차이는?

출처: https://www.geeksforgeeks.org/errors-v-s-exceptions-in-java/


RuntimeException과 RE가 아닌 것의 차이는?

Checked Exception

  • 반드시 예외를 처리해야 함
  • 컴파일 단계에서 확인

Unchecked Exception

  • 예외 처리를 강제하지 않음
  • 실행 단계에서 확인
  • RuntimeException 하위 예외


커스텀한 예외 만드는 방법

class MyException extends Exception {
    public MyException(String errorMessage) {
    	// 부모의 생성자 호출
        super(errorMessage); 
    } 
}

public class Main { 
    public static void main(String args[]) { 
        try
            throw new MyException("메세지"); 
        } 
        catch (MyException ex) { 
            System.out.println("Caught");
            System.out.println(ex.getMessage()); 
        }
    }
}


2021. 1. 8.

8주자 과제: 인터페이스

https://github.com/whiteship/live-study/issues/8

목표

자바의 인터페이스에 대해 학습하세요.

학습할 것 (필수)

  • 인터페이스 정의하는 방법
  • 인터페이스 구현하는 방법
  • 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
  • 인터페이스 상속
  • 인터페이스의 기본 메소드 (Default Method), 자바 8
  • 인터페이스의 static 메소드, 자바 8
  • 인터페이스의 private 메소드, 자바 9


인터페이스 정의하는 방법

public interface MyInterface extends MyInterface1, 
MyInterface2, MyInterface3 {
    void doExample(int i, double x);
    int doExampleSomething(String s);
}

출처: https://docs.oracle.com/javase/tutorial/java/IandI/interfaceDef.html

인터페이스 선언은 접근 지시자, interface 키워드, 인터페이스 이름, 쉼표로 구분된 상위 인터페이스 목록(있는 경우) 및 인터페이스 본문으로 구성

서브 클래스처럼 다른 인터페이스를 확장하거나 다른 클래스를 확장 가능

클래스는 하나의 다른 클래스만 확장할 수 있지만 인터페이스는 여러 인터페이스를 확장 가능

Interface Body

인터페이스 본문에는 추상 메소드, 기본 메소드, static 메소드가 포함될 수 있다.

인터페이스의 모든 추상, 기본, static 메소드는 암시적으로 공용이므로 public을 생략 가능

인터페이스에 정의된 모든 상숫값은 암시적으로 public, static 및 final


인터페이스 구현하는 방법

샘플 인터페이스, MyInterface

public interface MyInterface {
    public int isCompareTo(MyInterface other);
}

MyInterface 인터페이스 구현

public class MyRectangle implements MyInterface {

    // 가로 * 세로
    public int getArea() {
        return width * height;
    }

    // MyInterface 인터페이스 구현을 위한 메소드
    @Override
    public int isCompareTo(MyInterface other) {
        MyRectangle otherRect 
            = (MyRectangle)other;
        if (this.getArea() < otherRect.getArea())
            return -1;
        else if (this.getArea() > otherRect.getArea())
            return 1;
        else
            return 0;               
    }
}

출처: https://docs.oracle.com/javase/tutorial/java/IandI/usinginterface.html

MyInterface 인터페이스를 구현(implements)해 MyRectangle 클래스 안에서 사각형의 크기를 계산하는 메소드 isCompareTo()를 오버라이딩(overriding;재정의)

MyRectangle는 MyInterface을 구현(implements)하기 때문에 두 MyRectangle 객체의 크기를 비교 가능

Note: isCompareTo 메소드는 MyInterface 타입의 객체를 사용합니다. MyRectangle 타입 캐스팅은 컴파일러에 실제 객체가 무엇인지 알려줍니다. 다른 인스턴스(other.getArea())에서 직접 getArea를 호출하면 다른 인스턴스가 실제로 MyRectangle 인스턴스임을 이해하지 못하기 때문에 컴파일에 실패합니다.


인터페이스 레퍼런스를 통해 구현체를 사용하는 방법

새로운 인터페이스를 정의할 때 새로운 레퍼런스 데이터 타입으로 정의할 수 있다. 데이터 타입 이름을 사용할 수 있는 모든 곳에서 인터페이스 이름을 사용할 수 있다.

public Object findLargest(Object object1, Object object2) {
    MyInterface obj1 = (MyInterface) object1;
    MyInterface obj2 = (MyInterface) object2;
    if ((obj1).isLargerThan(obj2) > 0)
        return object1;
    else
        return object2;
}

object1을 MyInterface 타입으로 캐스팅하여 isLargerThan 메소드를 호출할 수 있다.

다양한 클래스에서 MyInterface을 구현(implementing)하려는 경우 두 객체가 동일한 클래스인 경우 클래스 중 하나에서 인스턴스화 된 객체를 findLargest()로 비교가 가능하다. 비슷하게 다음과 같이 비교할 수 있다.

public Object findSmallest(Object object1, Object object2) {
    MyInterface obj1 = (MyInterface) object1;
    MyInterface obj2 = (MyInterface) object2;
    if ((obj1).isLargerThan(obj2) < 0)
        return object1;
    else
        return object2;
}

public boolean isEqual(Object object1, Object object2) {
    MyInterface obj1 = (MyInterface) object1;
    MyInterface obj2 = (MyInterface) object2;
    if ((obj1).isLargerThan(obj2) == 0)
        return true;
    else
        return false;
}


인터페이스 상속

클래스와 인터페이스의 중요한 차이점은 클래스는 필드를 가질 수 있지만 인터페이스는 가질 수 없다. 추가로 클래스의 객체 생성이 가능하지만 인터페이스는 불가능 하다.

객체는 클래스에 정의된 필드의 상태를 저장한다. 자바 언어가 둘 이상의 클래스를 확장할 수 없는 이유는 여러 클래스에서 필드를 상속하는 기능인 다중 상속 문제를 방지하기 위한 것.

인터페이스에는 필드가 포함되지 않기 때문에 다중 상속으로 인해 발생하는 문제에 대해 걱정할 필요가 없다.

구현(implementation)의 다중상속은 여러 클래스에서 메소드의 정의를 상속하는 기능

다중상속에서 이름 충돌 및 모호성과 같은 문제가 발생하는데 컴파일러가 슈퍼 클래스의 같은 이름의 메소드를 만나면 어떤 멤버나 메소드를 실행하거나 접근해야 하는지를 결정할 수 없는 경우가 있다. 또한 슈퍼 클래스에 새 메소드를 추가하여 무의식적으로 이름 충돌을 일으킬 수 있다. 기본 메소드(Default methods)는 다중 상속의 한 형태를 소개한다. 클래스는 동일한 이름을 가진 기본 메소드를 포함한 인터페이스를 2개 이상 구현할 수 있다. 컴파일러는 특정 클래스가 사용하는 기본 메소드를 결정하는 규칙을 제공한다.

출처: https://docs.oracle.com/javase/tutorial/java/IandI/multipleinheritance.html


인터페이스의 기본 메소드 (Default Method), 자바 8

산업 표준 인터페이스를 발표하는 컴퓨터 제어 자동차 제조업체의 예를 들어 설명해 봅시다. 컴퓨터로 제어되는 자동차 제조업체가 비행과 같은 새로운 기능을 자동차에 추가하면 어떻게 될까요? 제조업체는 다른 회사(예: 전자 유도 기기 제조업체)가 소프트웨어를 적용하도록 특별히 새로운 메소드가 필요할 겁니다. 이러한 새로운 비행 관련 메소드를 어디에 선언해야 할까요? 만약 오리지널 인터페이스에 추가하면 해당 인터페이스를 구현한 프로그래머는 구현을 다시 작성해야 합니다. 만약 static 메소드로 추가하면 프로그래머는 필수 핵심 메소드가 아닌 유틸리티 메소드로 간주할 것입니다.

기본 메소드(default methods)를 사용하면 라이브러리의 인터페이스에 새 기능을 추가하고 이전 버전용으로 작성된 코드와 바이너리 호환성을 보장할 수 있습니다.

출처: https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html

public interface MyInterface {
    default int getData(int i) {
        return i;
    }
}


인터페이스의 static 메소드, 자바 8

public interface MyInterface {
    static public int getData(String myString) {
        try {
            return 1;
        } catch (Exception e) {
            System.err.println("error");
            return -1;
        }
    }
}

클래스의 모든 인스턴스는 static 메소드를 공유

라이브러리에서 helper 메소드를 쉽게 구성할 수 있다.


인터페이스의 private 메소드, 자바 9

Java 9부터 인터페이스에 private 메소드와 private static 메소드 추가 가능

인터페이스 내부의 코드 재사용성을 향상

private 메소드는 인터페이스 내에서만 사용 가능

private static 메소드는 다른 static or non-static 인터페이스 메소드 안에서 사용 가능

private non-static 메소드는 private static 메소드 내부에서 사용 불가능

public interface MyInterface {
    private int add(int nums) {
        return nums + 1;
    }
}

출처: https://howtodoinjava.com/java9/java9-private-interface-methods/

링크1: 인터페이스의 장점에 대해 모르겠습니다. 납득좀 시켜주세요

링크2: 객체지향 개발 5대 원리: SOLID

2021. 1. 1.

7주차 과제: 패키지

https://github.com/whiteship/live-study/issues/7

목표

자바의 패키지에 대해 학습하세요.

학습할 것 (필수)

  • package 키워드
  • import 키워드
  • 클래스패스
  • CLASSPATH 환경변수
  • -classpath 옵션
  • 접근지시자


package 키워드

//in the Draggable.java file
package graphics;
public interface Draggable {
    . . .
}

//in the Graphic.java file
package graphics;
public abstract class Graphic {
    . . .
}

//in the Circle.java file
package graphics;
public class Circle extends Graphic
    implements Draggable {
    . . .
}

//in the Rectangle.java file
package graphics;
public class Rectangle extends Graphic
    implements Draggable {
    . . .
}

//in the Point.java file
package graphics;
public class Point extends Graphic
    implements Draggable {
    . . .
}

//in the Line.java file
package graphics;
public class Line extends Graphic
    implements Draggable {
    . . .
}
  • package 선언은 소스 파일 최상단에
  • 1개의 소스 파일은 1개의 패키지만 선언 가능
  • 1개의 소스 파일에 여러 타입을 넣는 경우 한 개의 타입은 'public'이며 소스 파일 이름과 동일해야 함
    ex) 'public class Circle'의 소스 파일 이름 'Circle.java', 'public interface Draggable'의 소스 파일 이름 'Draggable.java', 'public enum Day'의 소스 파일 이름 'Day.java'
  • 'public' 타입과 동일한 파일에 'private' 타입을 포함할 수 있지만('private' 타입이 작고 'public' 타입과 밀접한 관련이 있는 경우를 제외하고는 강력히 권장하지 않음) 'public' 타입만 외부에서 접근 가능

전 세계 프로그래머가 Java 언어를 사용하여 클래스와 인터페이스에 같은 이름을 사용할 가능성이 높다. 예) java.awt 패키지에 이미 Rectangle 클래스가 있을 때 Rectangle 클래스를 정의. 그래도 컴파일러는 두 클래스가 다른 패키지에 있을 경우 같은 이름을 허락한다. Rectangle 클래스의 정규화 이름(fully qualified name)에 패키지 이름이 포함된다. graphics 패키지의 Retangle 클래스의 정규화 이름은 'graphics.Rectangle', java.awt 패키지의 Retangle 클래스의 정규화 이름은 'java.awt.Rectangle'.

두명의 프로그래머가 동일한 패키지 이름을 사용하지 않는한 잘 작동합니다. 이 문제를 방지하는 방법은? 규칙 (Convention)

패키지 명명 규칙

  • 패키지 이름은 클래스 or 인터페이스 이름과 충돌하지 않도록 모두 소문자로 작성
  • 역방향 인터넷 도메인 이름을 사용 ex) example.com에서 만든 mypackage 패키지 → com.example.mypackage
  • 회사 내에서 발생하는 이름 충돌은 회사 이름 뒤에 지역 or 프로젝트 이름 (ex: com.example.region.mypackage)을 포함하여 사내 규칙에 따라 처리)
  • Java 언어 안의 패키지는 java or javax로 시작
  • 도메인 이름이 패키지 이름으로 적절하지 않거나 하이픈, 특수문자, Java 키워드, 이름규칙에 안맞는 숫자 문자가 포함될 경우 다음 예시와 같이 처리

출처: https://docs.oracle.com/javase/tutorial/java/package/createpkgs.html


import 키워드

// 정규화 이름 사용 예
graphics.Rectangle myRect = new graphics.Rectangle();
import graphics.Rectangle;
. . .
// import 사용 예
Rectangle myRectangle = new Rectangle();
. . .
import graphics.*;
. . .
// 패키지 내 모든 내용 import 사용 예
Circle myCircle = new Circle();
Rectangle myRectangle = new Rectangle();
. . .
// 패키지가 계층적으로 보이지만 사실 그렇지 않음
// 예) java.awt.* 이 java.awt 패키지의 모든 내용을 가져올 것 같지만
// java.awt.color, java.awt.font 같은 패키지 내용을 가져오지 않음
import java.awt.*;
import java.awt.color.*;

가져온 두 패키지의 맴버 이름이 동일하면 정규화 이름을 사용

java.awt.Rectangle rect1;
graphics.Rectangle rect2;

Static Import

하나 or 두 개의 클래스에서 static final fields (constants 상수)와 static 메소드에 자주 접근해야 하는 상황이 있습니다. 이러한 클래스 이름을 반복해서 붙이면 코드가 복잡해질 수 있습니다. static import 선언을 사용하면 상수와 static 메소드를 가져오는 방법을 제공하므로 해당 클래스의 접두사를 지정할 필요가 없습니다.

java.lang.Math 클래스는 PI 상수와 많은 static 메소드, 사인, 코사인, 탄젠트 등등을 포함합니다. 예를 들어

public static final double PI 
    = 3.141592653589793;
public static double cos(double a)
{
    ...
}

일반적으로 다른 클래스의 객체를 사용하려면 다음과 같이 클래스 이름을 접두사로 지정

double r = Math.cos(Math.PI * theta);

static import를 사용하면 'java.lang.Math'의 static members를 가져올 수 있으므로 Math 접두사를 붙일 필요가 없습니다. Math의 static members는 개별적으로 불러올 수 있습니다.

import static java.lang.Math.PI;
import static java.lang.Math.*;

// 불러온 static members는 제한없이 사용할 수 있다.
double r = cos(PI * theta);

// 자주 사용하는 상수와 static 메소드를 포함하는 클래스를 작성한 다음
// import 문을 사용할 수 있습니다.
import static mypackage.MyConstants.*;

'static import'는 매우 절약해서 사용하세요. 과도하게 사용하면 코드를 읽는 사람이 특정 static 객체가 어느 클래스에서 정의됐는지 알 수 없기 때문에 코드를 읽고 유지 관리하기 어려울 수 있습니다. 올바르게 사용하면 static import 사용해 클래스 이름의 반복을 제거해 코드를 더 쉽게 읽을 수 있습니다.

출처: https://docs.oracle.com/javase/tutorial/java/package/usepkgs.html


클래스패스

컴파일러와 JVM이 .class 파일을 찾을 수 있도록 설정하는 경로

<출력 파일의 상위 디렉터리 경로>\com\example\graphics\Rectangle.class
<출력 파일의 상위 디렉터리 경로>\com\example\graphics\Helper.class
<path_one>\sources\com\example\graphics\Rectangle.java
<path_two>\classes\com\example\graphics\Rectangle.class

이렇게 하면 소스를 공개하지 않고 다른 프로그래머에게 클래스 디렉토리를 제공할 수 있다. 또한 컴파일러와 JVM이 프로그램에서 사용하는 모든 타입을 찾을 수 있도록 이러한 방식으로 소스 및 클래스 파일을 관리해야 한다.

현재 CLASSPATH 값 확인
In Windows:   C:\> set CLASSPATH
In UNIX:      % echo $CLASSPATH

현재 CLASSPATH 값 삭제
In Windows:   C:\> set CLASSPATH=
In UNIX:      % unset CLASSPATH; export CLASSPATH

CLASSPATH 설정 예시
In Windows:
C:\> set CLASSPATH=C:\users\george\java\classes
In UNIX:
% CLASSPATH=/home/george/java/classes; export CLASSPATH

출처: https://docs.oracle.com/javase/tutorial/java/package/managingfiles.html


CLASSPATH 환경변수

읽어보기: https://hyoje420.tistory.com/7

https://effectivesquid.tistory.com/entry/자바-클래스패스classpath란


-classpath 옵션

클래스 경로를 지정하는 기본 방법은 -cp 명령어를 사용. 이를 통해 다른 애플리케이션에 영향을 주지 않고 각 애플리케이션에 개별적으로 CLASSPATH를 설정할 수 있다.

javac -cp C:\Java\jdk1.7.0\bin;C:\Windows\System32\
java -cp C:\Java\jdk1.7.0\bin;C:\Windows\System32\

출처: https://docs.oracle.com/javase/tutorial/essential/environment/paths.html


접근지시자

패키지 레벨과 관련있는 접근지시자

출처: https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html

public: 제한없이 접근 가능

protected: 자신의 패키지 내에서만 접근 가능

default(접근 지시자가 없는 경우): 자신의 패키지 내에서만 접근 가능

private: 클래스 내에서만 접근 가능