안드로이드 개인 프로젝트를 진행하는 중에 옆에 있던 동기 누나가 생긴 오류로 코드를 들여다보았다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
| protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); fragmentManager=getFragmentManager(); fragmentTransaction=fragmentManager.beginTransaction(); ContentsViewFragment contentsViewFragment= new ContentsViewFragment(); fragmentTransaction.add(R.id.fragment_container,contentsViewFragment); fragmentTransaction.commit(); // 1. Action bar에서 navigation drawer toggle버튼을 클릭하면 navigation list가 나옴 // -> navigation list에 eventlistener 설정 // -> selectCategory() upperToolbar=(Toolbar)findViewById(R.id.upper_toolbar); setSupportActionBar(upperToolbar); // 2. 하단에 Action bar로 home, search, write, mypage 버튼 구성 및 eventㅣistener로 각 fragment로 연결 // -> selectMenu() // -> home : ContentsViewFragment // -> search: before search; BeforeSearchFragment, after search; ContentsViewFragment, action bar 검색어 입력 모드로 연결 // -> write: WriteCategoryFragment // -> mypage: MyPageFragment //bottomToolbar는 standalone으로 구현 bottomToolbar=(Toolbar)findViewById(R.id.bottom_toolbar); bottomToolbar.inflateMenu(R.menu.menu_bottom_bar); bottomToolbar.setOnMenuItemClickListener( new Toolbar.OnMenuItemClickListener(){ @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.home: ContentsViewFragment contentsViewFragment= new ContentsViewFragment(); fragmentTransaction.replace(R.id.fragment_container,contentsViewFragment); fragmentTransaction.addToBackStack( null ); fragmentTransaction.commit(); break ; case R.id.search: BeforeSearchFragment beforeSearchFragment= new BeforeSearchFragment(); fragmentTransaction.replace(R.id.fragment_container,beforeSearchFragment); fragmentTransaction.addToBackStack( null ); fragmentTransaction.commit(); break ; case R.id.write: WriteCategoryFragment writeCategoryFragment= new WriteCategoryFragment(); fragmentTransaction.replace(R.id.fragment_container,writeCategoryFragment); fragmentTransaction.addToBackStack( null ); fragmentTransaction.commit(); break ; case R.id.mypage: MyPageFragment myPageFragment= new MyPageFragment(); fragmentTransaction.replace(R.id.fragment_container,myPageFragment); fragmentTransaction.addToBackStack( null ); fragmentTransaction.commit(); break ; } return true ; } }); } |
});
activity_main이 fragment로 화면을 바꾸어 주는데, 아래에 Toolbar로 변경할 수 있도록 구성했다. onMenuItemClick()에 switch문에서 id값에 따라 fragment를 바꿔준다. 코드를 보면 setOnMenuItemClickListener() 안에 new Toolbar.OnMenuItemClickListener() 로 Anonymous class가 선언되고 그 클래스의 onMenuItemClick() method 내에서 switch 구문을 구현한 것이다.
코드를 실행하니 Inner class에서 local variable에 선언 된 변수에 접근하려니 에러가 발생했는데 그 내용이 'Inner class가 local variable에 접근하고 있다. 해결하려면 local variable을 final로 선언해야 한다' 라는 내용이었다.
onMenuItemClick()의 switch 구문에서 onCreate()에서 선언된 contentsViewFragment 변수에 접근하면서 생긴 오류였다.
그 이유가 무엇일까 찾아보다가 깊이 있는 분석이 필요함을 느꼈다.
먼저 Inner Class의 개념에 대해 정확히 파악할 필요가 있었다. Java에는 Nested Class 가 있는데, 이 안에 Inner Class와 Lamda Expression이 포함된다. Inner Class는 다시 Local Class와 Anonymous Class가 있다. Java에서는 왜 Nested Class가 필요해졌으며, Inner Class에는 Local Class와 Anonymous Class가 분류되었을까? 이것에 대한 이해가 없이는 그 아래에 대한 이해가 힘들 것 같았다.
Nested Class가 왜 등장하게 되었는지 살펴보니
1. It is a way of logically grouping classes that are only used in one place
개념적으로 하나의 클래스 B가 오직 다른 클래스 A에서만 유용하다면, B 클래스는 A 클래스 안에서 사용하는 것이 좋다는 것이다. 굳이 다른 곳에서 쓰는 일이 없는데 클래스로 빼낼 필요가 있을까라는 말인 듯 하다. 클래스로 만든 다는 것은 재사용성을 높이기 위함이 있으니까..
2. It increases encapsulation
B 클래스가 A 클래스의 멤버에 접근해야 한다. 그러려면 public으로 선언하거나 protected로 선언해서 상속을 받아야 할 것이다. 하지만 inner class로 선언해두면 private으로 선언할 수 있다. 그리고 B class는 안에 있기 때문에 외부(outside world)로부터 숨을 수 있다.
3. It can lead to more readable and maintainable code
좀 더 가독성이 좋고 유지보수하기 쉬운 코드가 된다고 한다.
Nested Class는 2개의 카테고리로 나뉘는데 static nested class와 non-static nested class(inner class)로 나뉜다. Inner class는 enclosing class의 멤버가 private이더라도 접근할 수 있다.
Inner class의 경우 추가적으로 2가지 타입이 더 있다. Inner class는 method 안에서 선언할 수도 있는데, method 안에 선언하면 local class라고 부르고, class name 없이 method body 안에 선언하면 anonymous class라고 부른다.
Local class는 enclosing class의 멤버에 접근할 수 있다. method 내에 선언하는 class를 local class라고 하니 정확히는 method 내에 선언 된 변수를 의미하는 듯 하다. 그런데 Local class는 오직 final로 선언된 local variable에만 접근할 수 있다.
"When a local class accesses a local variable or parameter of the enclosing block, it captures that variable or parameter."
Local Class가 local variable 또는 parameter에 접근(Java 8부터는 parameter에도 접근할 수 있다)하려고 하면 그 변수를 captured 한다는데 정확히 무슨 뜻인지 모르겠다. 복사한다는 뜻일까?
Anonymous Class도 Local Class와 마찬가지로 enclosing class의 member에 접근을 하려면 final로 선언이 되 있어야 한다고 했다.
[출처 : https://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html,https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html]
이제 JVM 내부에서 어떻게 동작이 되길래 final에 접근하는지에 대한 고민이 필요했다.
http://www.slipp.net/questions/278 에서도 같은 고민의 글이 있어서 참고를 했다.
위에 읽은 글과 블로그의 내용을 바탕으로 이해한 것은 Anonymous Class나 Local Class에서 local variable에 접근을 하면 그 변수 값을 복사(captured)해서 사용하기 때문에 2개의 변수가 생겨서 값이 불일치 하는 상황이 생길 수 있다. 따라서 final로 선언해서 그런 일을 방지하려고 하는 것이다.
위의 블로그에는 thread safe에 관해서도 얘기를 하고 있는데 거기까지는 이해가 되지 않고 있다. 좀 더 자세히 생각해봐야 할 것 같다.
[출처]http://ybin.tistory.com/8