Gs낭천
225
2018-01-17 19:14:20
6
1795

자바 문법 질문드립니다.


C# 많이해서 자바 하면서 큰 어려움은 없는데 확실히 다른 부분이 좀 있긴 하네요.

그때마다 검색하면서 하나씩 하고 있는데 검색 키워드조차 모르겠는 문법이 있어서 여쭤봅니다.

안드로이드 개발중인데 클릭 이벤트 붙일때 자주 나오는 문법입니다.

btnJapanese.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Configuration config = new Configuration ();
config.setLocale(Locale.JAPAN);
Resources resource = getActivity().getBaseContext().getResources();
resource.updateConfiguration(config, null);

getActivity().recreate();
}
});

먼저 틀린부분이 있으면 지적받고 싶습니다.

1. setOnClickListner메소드는 View의 OnClickListner인스턴스가 필요함(OnClickListner는 인터페이스던데 인터페이스를 이런식으로 인스턴스화 가능한지도 몰랐고 인터페이스부터 공부해야할 것 같습니다)

2.View.OnClickListner인터페이스는 onCLick(View v)를 갖고 있기때문에 인스턴스 화 할때 반드시 onClick(View v)메소드를 정의해 줘야함

여기까진 얼추 알겠습니다. 근데 자바에 익숙하지 않아서 그런가 이 기입 방식이 너무 싫어요..

C#할때는 그냥 button.SetEventListner += 함수명;

해주면 함수에는 처리만 기입하고 버튼의 이벤트 바인드는 한줄로 심플하게 됐는데...

싫은건 둘째치고 코딩하는 중에 문제가 번번히 발생합니다. 

첫번째. 

public void onClick(View v) { }

안에서 선언한 변수 아니면 사용을 못함(final면 사용 가능한건 검색해서 알았습니다.)

둘째 마찬가지로 저 안에서 return안됨.

막혀서 해결 못하고 있는 부분 소스를 기재하겠습니다.

public String GetWebApi(final BaseRequest oRequest)
{
String str;

new Thread(new Runnable() {
@Override
public void run() {
try {
// 大阪の天気予報XMLデータ
URL url = new URL(sUrl);
HttpURLConnection con = (HttpURLConnection)url.openConnection();
con.setRequestMethod(oRequest.GetHttpMethod());
str = InputStreamToString(con.getInputStream());

} catch(Exception ex) {
System.out.println(ex);
}
}
}).start();

return str;
}

함수의 인수를 run() 내부에서 사용하는건 인수에 final 붙여서 억지로 해결했습니다만..

결과를 리턴해 줄수가 없네요...

혹시 다른식의 기입방법이나 해결법 없을까요?

감사합니다.

0
  • 답변 6

  • LichKing
    16k
    2018-01-17 19:40:52

    1, 2는 맞습니다. 익명객체라고해서 인터페이스를 즉시 구현하는 문법이고요. 안드로이드가 자바8을 어디까지 지원하는지 잘 모르겠습니다만 글에 써주신것처럼 '추상 메서드' 가 1개인 인터페이스는 FunctionalInterface 라고해서 람다로 좀 더 간결하게 구현이 가능합니다.


    btnJapanese.setOnClickListener(v -> {
    
      // implements
    
    });


    그리고 한문으로 주석달린 저 코드는...컴파일이 되나요 저게? str에 값을 넣고 있는데 str이 지역변수라서 컴파일이 안될텐데...??

    하필 마지막 코드가 스레드 코드라서 원하시는게 '익명클래스에서 외부에 값을 넣고싶음/스레드 실행후 값을 반환하고싶음' 둘중에 뭔지 잘모르겠습니다. 전자라면 인스턴스필드를 사용하거나 Map같은 컨테이너의 레퍼런스를 보내 값을 가져오는 방법이 있고요, 후자의 경우에도 그런식으로 해도되고 Runnable 말고 Callable 인터페이스를 구현하는 방법도 있습니다.

  • Gs낭천
    225
    2018-01-17 20:00:25

    LichKing님 답변 감사합니다.

    컴파일 안됩니다. 적은 소스와 같은 방식으로 GetAPI()메소드의 처리 결과를 리턴해주고 싶은데 그게 안되서 여쭤봤습니다.

  • gorun999
    81
    2018-01-17 20:11:26

    안드로이드에서 지원하는 AsyncTask 나 return 받을수 있는 Callable 객체를 사용하시면 될거 같습니다 

  • 구구구구우
    1k
    2018-01-18 01:33:04 작성 2018-01-18 17:08:40 수정됨

    뭐라고 답변을 써야 하나...... 질문이 딱 단답으로 하기에는 오해할까봐 걱정이 되네요..

    이걸 어떻게 잘 정리해서 답변할수 있을지. 잘 답변하도록 노력해보겠습니다.


    2가지 측면에서 말씀을 드려 보겠습니다. 하나는 문법적 측면 두번쨰는 구현하려는 기능에 특성적 측면입니다. 사실 이 두가지가 앞서 달린 답변에 다 있네요.

    첫번째 문법적 측면에서 입니다. 자바의 익명클래스와 C#의 익명함수에 대해서 동일시하게 생각하시면 됩니다. 글쓴이분께서 말씀하신 C#의 문법과 자바의 익명클래스는 그 동작의 거의 비슷합니다. 하지만 한가지가 다릅니다. 다른 글에서도 소개했는데 자바의 익명클래스와 C#의 익명함수는 외부의 지역변수를 캡쳐하는데 이때 이들을(익명클래스, 익명함수) 클로저라고 부릅니다. 그리고 자바와 C#의 클로저는 동작이 서로 조금 다릅니다. 정확히는 자바는 제한적인 클로저를 제공합니다. 그 결과가 익명클래스 내에서 캡쳐한 외부 지역변수는 변경할수 없다는 거죠, C#은 변경이 가능합니다. 자바의 이 변경할수 없다는 제약은 사용자가 외부 지역변수의 final키워드를 강요합니다. 그렇지 않으면 컴파일러가 에러를 출력합니다.

    (참고로 자바 7? 8? 에서는 실질적으로 final이면 키워드를 쓰지 않아도 컴파일러가 알서 판단합니다.)

    문법 소개는 이쯤에서 마치고 자바에서 문법적으로 str을 변경하는 법에 대해서 소개를 해보겠습니다.

    외부 지역변수에 대해서 변경할수 없다고 하였는데, 이말은 반대로 하면 str을 익명클래스의 외부 지역변수를 멤버로 가진 외부클래스의 멤버 변수로 사용하는 경우 str의 값을 변경할수 있다는 겁니다.

    Class Ouster {

        public String str;

        public String method () {

             new Thread(new Runnable() {

                  public void run() {    str =  "";           // 변경   가능 }

             }).start();

            return str;

         }

    }

    아니면 str을 멤버변수로 가지는 다른 클래스를 정의하여 해당 클래스의 객체를 생성해서 이 객체를 캡처하고 해당 객체의 멤버인 str을 변경하는 방법도 있습니다.

    Class Ouster {

        public UserClass method () {

             UserClass user = new UserClass();

             new Thread(new Runnable() {

                  public void run() {    user.str =  "";           // 변경   가능 }

             }).start();

            return user;

         }

    }

    사실 첫번째 방법은 두번째 방법이 되기때문에 가능한겁니다. 자바에서 캡쳐된 변수를 변경하지는 못하는 제약이 있지만 해당 변수의 멤버를 변경하지 못하는건 아닙니다. 이것이 두번쨰 방법이고 익명클래스가 캡쳐하는 지역변수는 기본적으로 this를 포함하고 있습니다. 여기서 this는 외부지역변수를 멤버로 가진 외부클래스 객체를 가리키고요 즉 this객체의 멤버인 str은 변경이 가능한거죠(첫번쨰와 두번째가 같다는 말이 어떤 뜻인지 알겠나요?)


    두번재 구현하려는 기능적 측면에서 말씀을 드려보겠습니다. 사실 이게 제일 중요한데 제일 설명하기 어렵네요. 코드를 보니 구현하려는 기능의 특성이 비동기를 구현하려는것 같습니다. 코드를 보니 그러려고 했을거라 유추가 되는거죠. 하지만 반대로 비동기에 대한 특성을 잘 이해하고 구현하려고 하시는건지는 의문이 갑니다. 이유는 잘못된 방법으로 str을 return 하고 있기 때문인데요. 

    일단 작성하신 메소드의 내용만 보면 GetWebAPi 메소드는 호출 이후 String을 바로 return하겠죠, 그리고 코드의 실행순서는 바로 쭉 나갈겁니다. 이와 동시에 스레드가 동작하고 str에 내용을 채우겠죠.

    즉 str에 내용을 채우는 이 코드과 원래의 코드와 비동기적으로 일어난다는 겁니다. 즉 수행시간이 길어질수 있는 입출력을 수행하는 GetWebAPi 메소드를 호출할때 블락이 되지않도록(즉 해당 코드에서 긴 시간이 소요되어 다음 코드로 넘어 가지 않는) 하는 비동기의 장점을 이용하려는 것처럼 보입니다.

    이것은 UserClass result = GetWebAPi()에서 result가 언제 내가 원하는 값으로 바뀔지 알수가 없다는 의미 이기도 하지요. 즉 result.str에 내용을 일정시간마다 확인을 해야겠죠.

    비동기 구현 방법은 2가지가 핵심입니다. 수행시간이 길어질수 있는 코드를 분리해 내어 비동기적으로 수행하도록 하는것(위에 스레드를 사용한것이겠죠) 과 그렇게 비동기적으로 수행된 결과값이 있는 경우 또는 비동기적으루 수행된 코드의 완료 여부를 받는것 입니다.(또는 중간 중간 상태 또는 중간 결과를 받는것도 포함되죠). 제가 볼떄 글쓴이 분께서 원하시는건 비동기적으로 수행된 코드가 완료된 시점에 결과를 받는것이 아닐까 생각이 드네요. 그리고 해당 코드에서는 이러한 부분이 부족하고요

    또한 작성하신 코드는 비동기를 구현하기 위해 즉 분기를 주기 위해 스레드를 생성하고 있는데, 이것도 성능상 바르지 못합니다. GetWebAPi메소드가 호출되면서 스레드를 생성하는데, 이것보단 미리 생성된 스레드를 이용하는 것이 더 좋습니다.

    완료된 결과를 리턴 받는 방법도 2가지가 있는데 이벤트 방식과 동기방식(블락)입니다. 이것들을 지원하는 자바의 API는  gorun999 님이 언급하신 AsyncTask , Callable 로 구현할수 있고, 이미 잘구현된CompletableFuture 도 있습니다. CompletableFuture 나 AsyncTask 를 이용하기를 권하고 싶네요.

    C#의 경우 이러한 비동기를 구현하기 위해 문법적으로 제공합니다. async await이죠. 그리고 이것은 제가 앞서 말한것과 비슷한 방식으로 구현이 되어있습니다. 미리 생성된 스레드 풀에 분기된 작업을 수행하도록 하고 이벤트를 발생시키거나 동기(블락)를 하여 결과값을 리턴 받습니다. (C#은 한 3개월? 정도만 해봐서 정확한 내용인지는 모르겠네요. async await를 써본적도 없고, 적은 내용도 그렇게 읽었던 기억이 있어서 쓴거구요. 조금은 틀릴수도 있어요...)


    새벽에 써서 그런지 오타 많네요 ㅠ

    그리고 한가지 더 이것도 글쓸때는 쓰고 싶었는데 빼먹었네요. 위에서 익명클래스는  기본적으로 this (익명클래스의 this가 아닌 외부클래스의 this 객체)를 포함하고 있다고 하였는데, 이 얘기는 한가지 문제를 야기합니다. 익명클래스를 사용하는 경우 익명클래스의 객체 생명주기가 외부클래스의 객체 생명주기보다 길어지는 경우 익명클래스의 객체 생명주기가 끝날때까지 외부클래스의 객체가 가비지 컬렉트가 되지 않아서 메모리 누수가 됩니다.

    이것은 당연한 이야기 이지만,  익명클래스가 this객체의 참조를 갖을때 명시적으로 갖는 것이 아니고 묵시적으로 갖기 때문에 익명클래스가 this객체를 기본적으로 캡쳐한다는 사실을 모른다면 문제가 되겠죠, 그래서 이런걸 신경쓰지 않기 위해서 익명클래스를 사용할떄 static을 사용하던지 익명클래스를 구현한 새 클래스를 사용하는 방법을 사용합니다. 아니면 최근의 방식인 람다를 사용하죠 람다는 static 과 익명클래스를 구현하는 방법보다 더 세련된 방법을 제공합니다. 이것에 관련된거 여기까지 마무리 하겠습니다. 중요한건 익명클래스가 외부클래스의 this객체를 참조하고 있다는 사실을 알고 있어야 한다는 겁니다.



  • 구구구구우
    1k
    2018-01-18 01:37:02

    아 이말을 빼먹었네요 제일 걱정했던 부분인데, 2가지 측면에서 말씀을 드린다고 하였고 2가지 측면다 설명했는데 두번쨰 설명한 내용을 토대로 보면 첫번째 내용은 그리 쓸모 있는 내용이 아니라는 말을 하고 싶었습니다.

    이 두가지를 같이 설명하면 서로 섞여서 이상하게 해석이 될까봐 걱정했던 거구요

  • Gs낭천
    225
    2018-04-10 10:36:30

    구구구구우님 장문의 답변 감사합니다. 확인이 늦어서 죄송합니다 프로젝트 진행하느라 추가 답변이 달린줄도 몰랐네요. 적어주신 내용 전부 꼼꼼히 읽었습니다.

    말씀해주신 내용 프로젝트가 끝나고 나니까 대부분 이해가 됩니다 ^^.

    기능적인 부분에서 의문이였던 부분도 많이 해결됐구요.

    감사합니다^^.

  • 로그인을 하시면 답변을 등록할 수 있습니다.