java 문법관련 질문입니다. Android programming시 변수에 final 선언

lmk378의 이미지

예로 들겠습니다.

public class Test extends Activity
{
int a = 1;
public void onCreate(...){
super.onCreate(...);
...
Button btn = new Button(this);
...
btn.setInClickListener(new View.OnClickListener(){
public void onClick(){
a = 0;
}
});
}

버튼 클릭시 a라는 클래스 변수에 0을 대입합니다.
헌데 다음 코드의 int a = 1; 부분을 다음과 같이 함수내부에 선언하면

...
super.onCreate(...);
...
int a = 1;
Button btn = new Button(this);
...
btn.setInClickListener(new View.OnClickListener(){
a = 0;
...

오류로 인해 컴파일이 되지 않지만 final int a = 1; 로 수정하면 컴파일이 됩니다.
어떤 차이가 있나요??

cleol의 이미지

inner class 에서 접근하는 외부 로컬 변수는 반드시 final 이어야합니다.
즉, 그 변수가 가리키는 객체가 바뀌지 않는다는 보장이 있어야합니다.
그렇지 않다면

public void func() {
    int a = 1;
    ...
    btn.setInClickListener(new View.OnClickListener(){
        public void onClick(){
            System.out.println(a);
        }
    });
    ...
    a = 2;
}

같은 상황에서 혼란스럽겠지요.
이런 상황에서 a == 1 이 자연스럽거나 유용한지, a == 2 가 자연스럽거나 유용한지에 대한 문제입니다.
정하기 나름이지만, 소스 코드에서 나타나는 정적인 순서에 따라 a == 1 인 것이 보장되는 것이 자연스럽습니다.
고로 a는 반드시 final 이도록 정해져있습니다.
a 를 변경하고 싶으시면 어떤 클래스의 멤버 변수로 만드시면 됩니다.
물론 현재 클래스의 멤버이어도 되고, 그게 자연스럽지 않다면
   final int[] a = new int[] { 1 };
   ...
   btn.setInClickListener(new View.OnClickListener(){
        public void onClick(){
            a[0] = 0;
        }
    });

같은 꽁수를 쓸 수도 있습니다.
하지만 어디까지나 꽁수일 뿐, 이런 경우에는 디자인을 점검해보시는 것이 좋을 겁니다.

lmk378의 이미지

답변 감사합니다. 추가적인 질문이 생겼네요
이너클래스에게 final로 변수의 불변성을 명시해 주어야 하기 때문이라 하셨는데
클래스 변수로 선언하면 final 속성을 붙여주지 않아도 오류가 발생하지 않는 이유가 궁금해졌네요.

cleol의 이미지

class A {
	...
	public void foo() {
		String x = "hello"; // 원래는 반드시 final 이어야합니다.
		aButton.addActionListener(new ActionListener{
			public void actionPerformed() {
				System.out.println(x);
				System.out.println(y);
			}
		});
		x = "world"; // final 이면 이렇게 할 수 없지요.
	}
	...
	private String y;
}
 
...
 
class Main {
	public static void main(String[] args) {
		A a = new A();
		a.foo();
		...
		//여기
	}
}

말하자면, 내부 클래스는 외부 환경에 대한 레퍼런스를 가지고 있습니다. (closure 에 대해 검색해보세요.)
main 에서 호출된 a.foo가 리턴한 다음을 생각해보시면 됩니다.
버튼에 전달된 ActionListener 객체의 actionPerformed 는 이 상황에서 사용자가 버튼을 클릭했을 때에 호출되겠지요.
a의 필드인 a.y 는 이때에도 유효한 객체입니다.
고로 actionPerformed 가 호출되었을 때에 a.y 를 출력하는 것은 아무 문제 없습니다.
하지만 foo 안에서 정의된 로컬 변수인 x 는 foo 가 리턴한 후에는 유효하지 않습니다.
따라서 actionPerformed 가 호출되었을 때에 x 를 출력하려면 내부 ActionListener 객체가 x 의 복사본을 가지고 있어야합니다.
(주의할 것은 x 가 가리키는 객체의 복사본이 아니고 레퍼런스 변수 x 자체의 복사본입니다.)
그렇다면 이것을 "언제" 복사할 것인가가 문제이지요.
x 가 "hello" 를 가리킬 때에 복사할 것인지, "world" 를 가리킬 때에 복사할 것인지가 문제입니다.
이 경우에 "hello" 일때에 복사하고, 코드의 정적인 의미가 일관성이 있도록 반드시 final로 정한 것입니다.
설명이 깔끔하지 못한 것 같습니다만 이런 저런 시나리오를 만들어서 생각해보시면 아마 이해되실 겁니다.

nice7285의 이미지

설명도 좋고 예시도 좋아서 그냥 지나칠수가 없어서 글을 남겼습니다.
좋은글 감사합니다.

hello

lmk378의 이미지

죄송합니다. 답변이 좀 늦었네요.
답변 감사합니다. 잘 이해되었습니다.

익명 사용자의 이미지

cleol 님 답변 주셔서 감사합니다. 저도 궁금했던 부분이었는데 속시원히 해소됐네요.

joybug의 이미지

같은 맥락 이겠습니다만.. 함수(메소드) 호출 스택과 관련있다고 저는 이해하고 있습니다.
즉, 메소드 호출시 스택에 메소드에 넘겨주는 인자들, 그리고 메소드 안에서 생성하는 지역변수 들을 차례 차례 쌓아 놓다가,
메소드가 리턴 되면 스택은 지워져 버립니다. 그런데 익명클래스가 참조되는 타이밍은 메소드가 리턴된 후 이기 때문에(생성은 메소드 안에서 런타임시 되지만..) 만일에 익명 클래스에서 메소드의 인자나 지역변수를 참조하고 있다면.. null 포인터가 되버릴 것입니다.
그래서 그런 변수들은.. final 이라는 키워드를 이용해서 함수스택이 아닌 정적 메모리 공간에 할당 될 수 있게 하는 것입니다.

그러면 여기서 한가지 의문이 생김니다.. 예를 들어

...
public String foo(){

Stirng rtnStr = "리턴값";

return rtnStr;
}
...

이면.. rtnStr 도 final로 선언되야 할까요? 그렇치 않습니다. rtnStr 는 스택변수(지역변수)가 맞지만..
메소드가 종료되기 전에 호출측에 실제값 "리턴값" 의 번지(포인터)를 넘겨주고 메소드를 끝내고 스택이 지워 지기 때문에 아무 이상 없습니다.
반면 메소드안의 익명클래스에서는.. 지역 변수가 그대로 박혀 쓰이기 때문에 .. 그런식으로 쓰이는 것은.. 메소드 호출이 끝난뒤 지워지는 스택변수로 만들면 않되기 때문에 final을 붙여 줍니다.

p.s. - 저도 오늘 이대목이 참 궁금해서 여기저기 구글링 하다 제생각을 정리한 것입니다. 어디까지나 사견입니다. ^^

익명 사용자의 이미지

final 변수와 정적 메모리 공간은 아무 관련 없습니다.
java spec이 이에 대해 아무런 언급도 하지 않습니다.
final 키워드는 단지 그 변수에 다른 값을 대입할 수 없는 것을 보장할 뿐입니다.
semantic 제한이지 memory layout과는 아무 관련 없습니다.

final이 아닌 local 변수를 inner class가 참조하는 것은 기술적으로 얼마든지 구현할 수 있습니다.
문제는 그 참조가 어떤 행동을 하는 것이 적합한지 정하는 것입니다.
함수가 리턴한 후에 참조하는 것은 그냥 정의되지 않은 행동이 되도록 정할지,
예외를 낼지, 아무 문제 없도록 closure개념으로 가져갈 지 등등을 정하면 되는 겁니다.

그도 아니면 지금 자바에서 채택한 것처럼 final이 되도록하고 어딘가에
복사본을 남겨서 참조하도록하면 시나리오가 단순해지니까 좋겠지요.

익명 사용자의 이미지

내부 class의 지역변수 참조를 위해 정적 메모리 공간에 할당을 위한 것이다가 정확한 설명인 것 같습니다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.