오늘 포스팅 할 내용은 Builder 패턴이다.

Class 를 설계하다 보면,

필수로 받아야 할 인자들이 있고,

선택적으로 받아야 할 인자들이 있는 경우가 있다.

필수적으로 받아야 할 인자를 받기 위해

생성자의 매개변수를 넣는다.

또한 선택적 인자를 받기 위해 추가적인

생성자를 만든다.


public class PersonInfo{

  private String name;         //필수적으로 받야할 정보

  private int age;                //선택적으로 받아도 되는 정보

  private int phonNumber;   //선택적으로 받아도 되는 정보

  public PersonInfo(String name){

     this.name = name;

  }

  public PersonInfo(String name, int age){

    this.name = name;

    this.age = age;

  }

  public PersonInfo(String name, int age, int phonNumber){

    this.name = name;

    this.age = age;

    this.phonNumber = phonNumber;

  }

}

이러한 상황을 Effective Java 2/E 에선 점층적 생성자 패턴이라 말한다.

하지만, 이러한 패턴은 잘 동작하지만, 몇가지 단점들이 존재한다.

첫번째로, 인자들이 많아 질 수록 생성자의 숫자 역시 많아진다.

두번째로, 매개변수의 이름으로 원하는 정보를 설명하지만,

같은 자료형은 실수로 바꿔 넣는 실수가 생길 수 있다.

예를 들어, 위의 예시에서 age와 phonNumber를 서로 바꿔 넣는 경우이다.

세번째로, 읽기 어려운 코드가 된다.

PersonInfo personInfo = new PersonInfo("Mommoo", 12, 119);

위의 코드는 매개변수의 정보를 설명할 수 없으므로,

personInfo 객체가 어떤 인자들이 들어갔는지 알기 위해선

PersonInfo 클래스의 매개변수 이름이나, 주석을 참고해야 한다.


이러한 단점들을 보완하기 위해, 자바 빈즈(beans) 패턴이 대안점으로 나온다.

해당 패턴은 웹프로그래밍을 할 때, 자주 쓰는 패턴이다.

public class PersonInfo{

  private String name;         //필수적으로 받야할 정보

  private int age;                //선택적으로 받아도 되는 정보

  private int phonNumber;   //선택적으로 받아도 되는 정보

  public PersonInfo(){

  }

  public void setName(String name){

    this.name = name;

  }

  public void setAge(int age){

    this.age = age;

  }

  public void setPhonNumber(int phonNumber){

    this.phonNumber = phonNumber;

  }

}

생성자의 단점으로 꼽혔던 가독성이 해당 패턴을 사용하는 경우, 어느정도 해결이 된다.

PersonInfo personInfo = new PersonInfo( );

personInfo.setName("Mommoo");         // 이름을 넣는 메서드

personInfo.setAge(12);                       // 나이를 넣는 메서드

personInfo.setPhonNumber(119);          // 전화번호를 넣는 메서드

자바빈즈 패턴은 코드량이 늘어나는 단점이 존재한다. 

하지만 그것보다 더욱 문제가 되는 것은 객체 일관성이 깨진다.

객체 일관성이 깨진다는 것이란, 한번 객체를 생성할때, 그 객체가 변할 여지가 존재한다는 뜻이다.

즉, set메서드는 언제 어디서든 사용 할 수 있다는 장점이 있지만, 객체의 불변성이 깨진다.

스레드 작업에 큰 단점이 될 수 있을 뿐더러, 컴파일러 오류는 아니지만, 우리가 원하지 않는

결과물이 만들어 질 수 있는것이다.


점층적 생성자패턴과 자바빈즈 패턴의 장점을 섞은것이 빌더패턴이다.

정보들은 자바 빈즈 패턴처럼 받되, 데이터 일관성을 위해 정보들을 다 받은후에 객체생성을 해준다.


public class PersonInfo{

  private String name;         //필수적으로 받야할 정보

  private int age;                //선택적으로 받아도 되는 정보

  private int phonNumber;   //선택적으로 받아도 되는 정보

  private PersonInfo(){

  }

  public static class Builder{

    private String name;

    private int age;

    private int phonNumber;

   

   public Builder(String name){

      this.name = name;

    }

   public Builder setAge(int age){

 this.age = age;

 return this;

   }

   public Builder setPhonNumber(int phonNumber){

 this.phonNumber = phonNumber;

 return this;

   }

   public PersonInfo build(){

   PersonInfo personInfo = new PersonInfo( );

   personInfo.name = name;

   personInfo.age = age;

   personInfo.phonNumber = phonNumber;

   return personInfo;

 }

}

이러한 빌더 패턴은 아래와 같이 사용한다.

PersonInfo personInfo = new Builder("Mommoo").setAge(12).setPhonNumber(119).build( );

setter에 리턴자료형을 Builder 객체를 지정함으로써, 메서드 체이닝 기법을 적용했고,

정보를 다 넣은 경우 build( ) 메서드로 객체생성을 한다.

build( ) 메서드를 쓴 이후 에는 PersonInfo 클래스의 멤버변수를 변경 할 수 있는 방법은

리플렉션 기법빼곤 존재하지 않는다. 따라서, 데이터 일관성, 객체불변성 등을 만족시킨다.

또한 코드 가독성 역시 올라간다.


하지만 이러한 빌드 패턴의 단점은 많은 코드량이 필요할 뿐더러,

Builder라는 객체를 하나더 만드는 것이므로, 때에 따라선 성능이 낮아질 수 있다.


Class 설계할때, 받아야할 인자들이 많거나, 선택적 인자들이 많거나 또한, 추가될 인자들이 많은 경우

에 Builder 패턴을 고려한다면, 좀 더 나은 설계를 할 수 있을 것이다.


포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

오늘 포스팅 할 내용은 정적 팩토리 메서드에 관한 내용이다.


내용 의 상당수는 (Effective Javaa 2/E - 2판)의 도움을 받았고,


개인적인 의견을 덧 붙여 해당 내용을 풀어 설명할 것 이다.


인스턴스를 생성하기 위해서는 Class 원칙에 따라, 생성자를 호출해야 한다.


Person p = new Person( );


Class의 이름은 캡슐화를 위해 Class의 내용을 아우를 수 있는 이름을 짓기 마련이다.


따라서, 생성자의 이름만을 봐서는 감이 안올때가 있다.


또한, 여러가지 매개변수를 받는 생성자를 만들 때도, 생성자의 매개변수를


설명할 방법이 딱히 없다. 하지만, 정적 팩토리 메서드의 활용 여부에 따라 


코드의 가독성을 높일 수 있다. (여기서 정적은 static을 가리킨다.)


그로인해 아래와 같은 장점들이 존재한다.


정적 팩토리 메서드는 해당 클래스 인스턴스에 이름을 지어줄 수 있다.


public class Person{

  private String name;

  private Person( ){

  }

  

  public static Person newInstance(){

    return new Person( );

  }

  public static Person newInstance(String name){

    this.name = name;

    return new Person( );

  }


  public static void main(String[] args){

    Person p  = Person.newInstance(); // 평범한 인스턴스 생성

    Person p2 = Person.newInstanceByName("Mommoo"); // name속성을 가지는 인스턴스 생성 

  }

}


인스턴스의 갯수를 제어 할 수 있다. 이를 응용한 것이 싱글톤 패턴 이다.


public class Person{

  private final Person p = new Person( );


  private Person( ){

  }


  public static Person getInstance(){

    return p;

  }


  public static void main(String[] args){

    Person p = Person.getInstance( ); // 이미 존재하고 있는 객체 리턴

  }

}


생성자엔 반환값이 없지만, 필요시에 적절한 반환값을 만들어 줄 수 있다.


public class Person{

  private Person( ){ 

  }


  public static class Doctor{

    private Doctor(){

    }

  }


  public static Doctor getDoctor( ){

    return new Doctor( );

  }


  public static void main(String[] args){

    Doctor doctor = Person.getDoctor( );

  }

}


위의 예시는 반환값을 특정 Class로 했지만, 설계에 따라 어떠한 반환값이든 가능하다.


생성자를 감싸는 구조이므로, 유지보수와 성능 개선에 좋다.


위의 예시중 getDoctor 메서드가 new Doctor( )란 새로운 인스턴스를 리턴하지만,


개발자가 싱글톤으로 리턴하는 코드로 변경한다고 가정해보자.


public class Client{


  public static void main(String[] args){

    /** 클라이언트는 변경사항이 없다. 

      *  getDoctor( ) 메서드가 내부적으로 어떻게 변경되는지, 어떻게 쓰이는지 알 필요조차 없다.

      *  클라이언트는 단순히 Doctor라는 객체를 쓰는데만 집중한다. 

      */

    Doctor doctor = Person.getDoctor( );  

  }

}


public class Person{

  private Person( ){ 

  }


  public static class Doctor{

    

    private final Doctor d = new Doctor( );


    private Doctor(){

    }

  }


  public static Doctor getDoctor( ){

    return Doctor.d;   //new Doctor( ) 에서 Doctor.d 로 변경되었다.

  }


  public static void main(String[] args){

    Doctor doctor = Person.getDoctor( );

  }

}


이러한 장점들이 있는 반면, 단점들은 무엇이 있을까? 


먼저, 정적 팩토리 메서드의 사용을 권하기 위해서 private 제어자를 사용해 생성자를 막는다.


그로인해, 해당 클래스를 상속 할 수 없다. 왜냐하면 상속받을 생성자가 없기 때문이다.


또한, 다른 정적 팩토리 메서드는 다른 메서드와 크게 구분이 가지 않는다. 


만약 생성자를 이용한다고 가정해보자.


클래스를 설계 할때는 클래스 이름과 동일하고 반환값 자리가 아예 존재 하지 않는


메서드라는 특정 표식이 있고, 클래스를 쓸때도, new 생성자( ); 처럼


new 연산자를 통해 생성하므로, 생성자의 느낌을 물씬 주지만


정적 팩토리 메서드는 다른 정적 메서들과 차이점이 존재하지 않는다.


Class를 설계한 개발자 조차도 주석을 달아 놓지 않으면, 후에 다시 자신이 설계한 클래스를


봤을 때, 잊어먹 을 수 있다. 


결국 정적 팩토리 메서드를 활용한 클래스를 사용할때는, API나 사용법을 들어야한다.


(물론 생성자를 사용할 때도 어느정도 API나 사용법등을 참고해야할 때가 있다.)




'Java [Effective Java 2_E]' 카테고리의 다른 글

JAVA - 열거자료형 Enum 사용하기(1)  (0) 2016.11.11
Java - Builder 패턴 적용하기.  (3) 2016.10.08

포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

오늘 포스팅할 내용은,


안드로이드 디바이스가 현재


인터넷을 사용할 수 있는 상태인지


또한 사용할 수 있는 상태라면 


어떤 인터넷 환경인지 (Wifi or 3g ,4g ) 알아보는 방법에 대해 기술한다.



인터넷을 사용할 수 있는 상태인지를 체크하기 앞서,


인터넷이 연결되 있는 경우


어떤 인터넷 환경인지 가져오는 것부터 할 것이다.



먼저 AndoridManifest.xml 에 


해당 퍼미션을 등록해준다.


<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>

인터넷 환경을 탐색하기 위한 멤버변수를 선언 해준다.


public static final String WIFI_STATE = "WIFE";
public static final String MOBILE_STATE = "MOBILE";
public static final String NONE_STATE = "NONE";

그 후 아래의 메서드를 작성한다.


public static String getWhatKindOfNetwork(Context context){
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
if (activeNetwork != null) {
if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) {
return WIFI_STATE;
} else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
return MOBILE_STATE;
}
}
return NONE_STATE;
}


핵심코드는 getActiveNetworkInfo( ) 메서드이다.


해당 메서드는 인터넷이 연결되있는 경우 인터넷 환경에 대한 여러가지 정보를 담은 객체를 리턴한다.


만약 인터넷 환경이 연결되있는 상태가 아니라면 null을 리턴한다.


위의 메서드를 사용할 시, 인터넷 환경이 wifi 상태인지 3g,4g 상태인지 알 수 있다.



이 메서드를 통해서 인터넷이 연결됬는지 확인 할 수도 있지만,


이 메서드만 사용한다면 캐치 못하는 경우의 수가 있다. 



Wifi 연결은 되었지만 로그인, 인증서 등 권한을 획득하지 못하여 인터넷을 쓰지 못하는 경우.



위의 경우의 수에서는 사용자가 인터넷을 사용하진 못하지만 디바이스에서는 wifi가 연결된 걸로 인지한다.


이러한 경우의 수까지 캐치 하고 싶다면 아래의 코딩을 추가 해야한다.



1. Ping 보내기.


 ping이란, 원하는 네트워크 주소에 신호를 보내, 응답이 도착하는가 확인하는 것이다.

 

 만약 인터넷을 사용하지 못한다면, ping 신호는 도달 하지 못할 것이다.


 안드로이드도 리눅스 기반 이므로, 리눅스 명령어를 통해 ping 을 보낼 수 있는데,


 java 에서는 제공해주는 리눅스 api가 없기 때문에,


 밑단까지 내려가 명령을 줘야한다.  아래의 코드를 보자.



 private static class PingTask extends Thread {

private boolean success;
private String host;

public PingTask(String host) {
this.host = host;
}

@Override
public void run() {
super.run();
try {
Runtime runtime = Runtime.getRuntime();
Process proc = runtime.exec("ping -c 1 -W 100" + host);
proc.waitFor();
int exitCode = proc.exitValue();
if (exitCode == 0) {
success = true;
} else {
success = false;
}
} catch (IOException e) {
e.printStackTrace();
success = false;
} catch (InterruptedException e) {
e.printStackTrace();
success = false;
}
}

public boolean isSuccess() {
return success;
}
}

윗 코드는 리눅스 터미널까지 내려가 리눅스 명령어를 내리는 코드이다.


해당 코드를 쓰면 인터넷의 사용 가능 상태를 체크 할 수 있다.


하지만, 문제점은 생각보다 느렸다.


백그라운드 스레드로 사용한다면 상관없겠지만,


만약 인터넷 사용 가능 상태를 미리 조사를 해야 하는 상황이라면, 


사용자가 느끼기엔 느릴 것이다.


느린 원인은 잘 모르겠다. 추측성으론, 


안드로이드 명령어가 아닌 시스템 명령어를 내리는 것이므로 오버헤드가 발생한것이 아닌가 생각한다.


대안책으로 아래의 코드를 보자.



2. HttpURLConnection 객체 이용하기.


 HttpURLConnection객체를 응용해서 인터넷 사용 가능 상태를 검사할 것이다.


 문제점은 HttpURLConnection이 연결되는지 안되는지 기준은 HTTP_OK 상태 이므로,


 만약 인증받지 않은 와이파이 일지라도 HttpURLConnection은 연결상태로 뜰 것이다.


 따라서 아래의 방법으로 해결한다.



HTTP_OK 상태로만 연결 상태를 체크하지 말고, HTTP_OK 상태 체크 후,


웹페이지의 원하는 내용을 긁어올 수 는지 여부로 체크하기.



 무슨 말 이냐면, 인터넷 사용 가능한 권한을 받지 못하는 경우도 connect 는 되지만 원하는 웹페에지의 내용은 안긁어와진다.


 하지만 인터넷 사용 가능한 환경이 주어진다면, connect는 당연히 되고, 원하는 웹페이지의 내용도 긁어올수 있을것이다.


아래의 URL은 위의 논리를 구현하기 위해 구글이 제공하는 URL이다.


http://clients3.google.com/generate_204


해당 URL은 아무내용도 없는 즉, 인터넷이 연결된 상태에서 긁어올 내용을 넣지 않았다.


해당 URL은 HTTP_OK 를 넘어서, HTTP_NO_CONTENT ( 204번 ) 을 리턴한다.


이 URL을 사용 할 시, wifi 가 연결되었지만 권한이 없는경우는 200번을 리턴하고,


권한이 있는경우는 204번을 리턴한다.



아래의 코드를 보자.



멤버변수로 아래의 코드를 추가한다.


public static final String CONNECTION_CONFIRM_CLIENT_URL = "http://clients3.google.com/generate_204";

HttpURLConnection 객체를 호출하는 코드도 추가한다.


private static class CheckConnect extends Thread{
private boolean success;
private String host;

public CheckConnect(String host){
this.host = host;
}

@Override
public void run() {

HttpURLConnection conn = null;
try {
conn = (HttpURLConnection)new URL(host).openConnection();
conn.setRequestProperty("User-Agent","Android");
conn.setConnectTimeout(1000);
conn.connect();
int responseCode = conn.getResponseCode();
if(responseCode == 204) success = true;
else success = false;
}
catch (Exception e) {
e.printStackTrace();
success = false;
}
if(conn != null){
conn.disconnect();
}
}

public boolean isSuccess(){
return success;
}

}

이제 인터넷 상태를 체크해주는 메서드도 만들어본다.


public static boolean isOnline() {
CheckConnect cc = new CheckConnect(CONNECTION_CONFIRM_CLIENT_URL);
cc.start();
try{
cc.join();
return cc.isSuccess();
}catch (Exception e){
e.printStackTrace();
}
return false;
}

필자는 인터넷 환경체크를 우선시 하므로 


스레드에 join을 걸어 mainThread의 작업이랑 동기화 하였다.


만약 서브스레드로만 돌려도 되는 상황이라면 join을 지우면 되겠다.




이제 isOnline( ) 메서드와 getWhatKindOfNetwork( ) 메서드만 사용한다면,


인터넷 상태를 완벽하게 체크 할 수 있다.





 


포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

이번 포스팅은 저번에 이어


마지막 차례인 DownCating(다운 캐스팅)이다.


아래의 URL은 현재 기술할 내용을 이해하기 위해


필요한 내용을 기술한 포스팅이다.


http://mommoo.tistory.com/40 - 캐스팅

http://mommoo.tistory.com/41 - 업캐스팅


이전 업캐스팅 내용을 토데로 복습하자면


자바에서는 관련있는 데이터 끼리 형변환이 가능 했었다.


ex) (Child 클래스가 Parent 클래스를 상속받은 경우)


Parent parent = new Child();


윗 경우는 업캐스팅이라 했었고, 형변환 기호( (Parent) )를 붙여주지 않더라도


생략이 가능하다.


그렇다면 반대인 경우는 어떨까?


Child child = new Parent();


앞서 포스팅에서 기술 한 것처럼, 관련있는 데이터 끼리는


캐스팅이 가능하다고 했다. 하지만 , 위의 경우는 성립하지 않는다.


왜냐하면 앞서 포스팅에서 덧붙여 설명했던 개념인,


변수가 원하는 정보를 다 채워줘야하는 원칙에 어긋난다.


Child 클래스는 Parent 클래스를 상속받았기 때문에 Parent 클래스보다는 


Child 클래스가 더욱 많은 데이터를 가졌을 것이다.


즉,


child 변수가 원하는 정보는 Child 클래스의 데이터 전부를 원하는데,


Parent 인스턴스 ( new Parent(); ) 는 Parent 데이터만 가지고 있을 뿐,


Child의 데이터를 가지지 않는다. 그러므로 빨간줄이 그어지면서


컴파일 오류를 발생시킨다.



그렇다면, 위의 경우에서 형변환을 시켜준다면 어떨까?


Child child = (Child)new Parent();


개발툴에서 확인해보면, 컴파일 오류에서 벗어나면서 빨간줄이 사라질 것이다.


하지만, 저 코드는 런타임 오류가 발생한다.


이유는 아래와 같다.



"컴파일러에게 프로그래머가 형변환을 함으로써, 일단 데이터를 맞게 넣어준것 처럼 보여준다.


컴파일러는 문법이 맞다고 생각하여 넘어간다.


하지만, 프로그램이 실제로 동작할때, new Parent(); 인스턴스는 Child 형 데이터로 바꾸지 못한다는 것을


깨닫고, 런타임 오류를 뿜으며 프로그램이 종료된다. "



그렇다면, 왜 런타임 오류를 발생할까? 


그 이유는 JVM은 new Parent(); 인스턴스를 Child 데이터로 형변환 하려 했지만,


Child 데이터가 무엇인지 모르기 때문이다. 조금더 구체적인 이유는


Child 데이터는 만드는 프로그래머 마다 성질이 다를 것인데, 그것을 JVM 추리하지 못하기 때문이다.


이전 캐스팅 포스팅에서 나오길, 기본자료형 끼리는 추리가 가능하기 때문에,


알아서 알맞은 데이터 크기로 변환하여 넣어준다고 설명했다.


하지만, 위와 같이 참조형 데이터를 캐스팅 할때는, 


속성,성질이 정해져 있지 않은 참조형 데이터는 JVM이 알길 이 없다.


Child 데이터에 Parent 데이터를 넣는 경우는 화살표가 아래로 향하므로,


다운캐스팅이라 한다. 


위와 같은 예시에서 봤드시,


다운캐스팅은 일반적으로 성립하지 않는다. 


하지만, 다운캐스팅이 성립되는 경우가 존재한다.


아래의 예시를 보자.


Parent parent = new Child(); 


Child child = (Child)parent;


위의 예시는 다운 캐스팅이 성립되는 경우의 수이다.


왜 성립이 되는것일까?

 

parent 변수는 Parent클래스 형태의 변수지만, 


태생이 Child 인스턴스 인 데이터를 넣어주었다.


그러한 정보를 가지고 있는 parent 변수를 다시 Child 클래스형태로


다운캐스팅을 하였다.  parent변수 상태는 Parent 클래스형 상태이지만,


다운캐스팅을 해주는 경우 ( (Child)parent ) 태생이 Child 클래스 형이므로,


JVM이 parent 변수를 태생 정보인 Child 클래스 데이터 형으로 


다운캐스팅을 해줄 수 있는 것이다.




결과적으로, 다운캐스팅은 보통 성립하지 않는 문법이지만,


위와같이 업캐스팅이 선행된 경우, 다운캐스팅이 성립되는 경우가 존재한다.









'Java' 카테고리의 다른 글

JAVA - JNI 사용하기  (2) 2017.09.02
JAVA - [SWING] LinearLayout 사용하기.  (0) 2017.08.25
JAVA - HashMap key 구하기.  (1) 2016.08.25
JAVA - 변수 선언할때 m을 왜 붙일까?  (2) 2016.07.05
JAVA - UpCasting(업캐스팅)  (9) 2016.06.07

포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee


HashMap은 key 와 value 쌍으로 존재하는 


자료구조이다.


프로그래머가 key값을 알고 있는 상태에서 쓰는 경우도 있지만,


프로그래머가 동적으로 구한 데이터를 key, value 쌍으로 


HashMap을 쓴 경우는 프로그래머도 key 값이 무엇이 들어갔는지 알 수가 없다.


arrayList 처럼 index 가 정해져 있는것도 아니라


순차적으로 Log 에 찍어보기도 애매하다.


오늘 포스팅 내용은 이러한 상황에서 HashMap key값이 무엇이 있는지


구하는것이다.


HashMap 클래스 내부구조로 Set 자료구조에 key를 보관한 객체가 존재한다.


Set 객체를 통해 key를 가져오는 것이다. 


Set 객체에 들어있는 값(key)를 iterator 인터페이스를 통해, 순차적으로 탐색할 준비를한다.



HashMap<String,String> hashMap = new HashMap<>();

hashMap.put("key","value");


Set set = hashMap.keySet();

Iterator iterator = set.iterator();



가져온 후, iterator를 통해 순차탐색한다.


while(iterator.hasNext()){

  String key = (String)iterator.next();

  System.out.println("hashMap Key : " + key);

}


Entry 객체를 이용한 방법도 있다.


Entry 객체를 이용하면 key 와 value를 동시에 구할 수 있다.


물론 위에 코드로 key값을 구한 후, hashMap.get(key) 로 value를 구해도 상관없다.


Set set = hashMap.entrySet();

Iterator iterator = set.iterator();


while(iterator.hasNext()){

  Map.Entry entry = (Map.Entry)iterator.next();

  String key = (String)entry.getKey();

  String value = (String)entry.getValue();

  System.out.println("hashMap Key : " + key);

  System.out.println("hashMap Value : " + value);

}


주의할 점은 Set 자료형에 keySet() 을 넣은 경우와 entrySet() 경우를 잘 구별해서 이용 해야한다.




'Java' 카테고리의 다른 글

JAVA - [SWING] LinearLayout 사용하기.  (0) 2017.08.25
JAVA - DownCasting(다운캐스팅)  (25) 2016.08.27
JAVA - 변수 선언할때 m을 왜 붙일까?  (2) 2016.07.05
JAVA - UpCasting(업캐스팅)  (9) 2016.06.07
JAVA - Casting(캐스팅)  (0) 2016.05.26

포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

오늘 포스팅은 안드로이드 퍼미션에 관한 내용이다.


안드로이드 마시멜로우 OS가 등장함에 따라,


기존 환경에서 바뀐점들이 있다. 

(https://developer.android.com/about/versions/marshmallow/android-6.0-changes.html)


그 중, 제일 골치썩는 문제는 Runtime Permission이 아닐까 싶다.


아래의 글은 위 URL 에서 발췌한 내용중 하나이다.

Runtime Permissions


This release introduces a new permissions model, where users can now directly manage app permissions at runtime. This model gives users improved visibility and control over permissions, while streamlining the installation and auto-update processes for app developers. Users can grant or revoke permissions individually for installed apps.

On your apps that target Android 6.0 (API level 23) or higher, make sure to check for and request permissions at runtime. To determine if your app has been granted a permission, call the newcheckSelfPermission() method. To request a permission, call the new requestPermissions() method. Even if your app is not targeting Android 6.0 (API level 23), you should test your app under the new permissions model.

For details on supporting the new permissions model in your app, see Working with System Permissions. For tips on how to assess the impact on your app, see Permissions Best Practices.



간단하게 해석해보면, 


사용자가 접근 권한이 필요한 기능( 예를 들자면, 전화 바로 걸기)을 수행할때,


사용자로 하여금 해당 권한을 앱에 허락 할 것인지 묻고, 개발자가 아닌 사용자가 


자신의 디바이스의 접근 권한을 결정하는 방식이다.



이전 버전에서(API 22 이하 버전)의 접근 권한 수용방식은 앱을 설치할때,


사용자로 하여금 해당 권한을 사용한다는 것만 보여준다. 


즉, 앱을 설치하면 해당 권한등을 동의하는 것으로 간주된다.



API 22 아래 버전.


위 경우에서 개발자는 단순히 Manifest.xml에 권한 추가후 코딩하면 되겠지만,


아래와 같이 마시멜로우 버전에서의 런타임 퍼미션 체크방식 일 시, 코딩하기 녹록지 않다.


API 23 이상 버전.




하지만 모든 권한을 사용자로 하여금 확인받아야 하는 것은 아니다.


개인적인 생각이지만, 개인정보와 관련있는 권한은 확인 받지만


관련이 없는 권한은 확인을 안받아도 되는것 같다.


아래는 확인 받아야 할 권한 목록이다.



출처 : https://developer.android.com/guide/topics/security/permissions.html?hl=ko#normal-dangerous

Permission GroupPermissions
CALENDAR
CAMERA
CONTACTS
LOCATION
MICROPHONE
PHONE
SENSORS
SMS
STORAGE


지금부터 메시멜로우 os의 런타임 퍼미션을 위해 필요한 코딩등을 소개한다.

총 4가지의 메서드를 사용 할 것이다.

  • ContextCompat.checkSelfPermission()
  • ActivityCompat.shouldShowRequestPermissionRationale()
  • ActivityCompat.requestPermissions()
  • onRequestPermissionsResult()



첫번째 부터 세번째 까지의 메서드는 매개변수로 Context만 넣어줄 수 있다면, 어디서든 사용가능하다.


네번째 메서드는 Activity 안에서의 오버라이드 메서드 이다.


이름에서 볼 수 있다시피, 퍼미션 여부를 수행한 후 결과가 들어오는 곳이다.


아래의 예제를 보자.


1
2
3
4
5
6
7
8
/**
  해당 권한이 승낙 상태인지 거절 상태인지 확인한다.
*/
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED){
   //Manifest.permission.READ_CALENDAR이 접근 승낙 상태 일때
} else{
   //Manifest.permission.READ_CALENDAR이 접근 거절 상태 일때
}
cs


최초 앱 실행시, 당연히 접근 권한은 없으므로 else 블록을 타게 된다.


위 이미지에서 보는 것 처럼 권한 설정 요구 다이얼로그를 뜨게 하려면


아래 예시처럼 requestPermission() 메서드를 추가해야 한다.


1
2
3
4
5
6
7
8
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED){
   //Manifest.permission.READ_CALENDAR이 접근 승낙 상태 일때
} else{
   //Manifest.permission.READ_CALENDAR이 접근 거절 상태 일때
  
   //사용자에게 접근권한 설정을 요구하는 다이얼로그를 띄운다.
   ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CALENDAR},0);
}
cs


requestPermission 메서드는 매개변수로 Context, String 배열, int형 변수 를 원한다.


int형 변수는 후에 onRequestPermissionsResult() 메서드에 결과물이 전돨될 시,


결과물들을 구분 짓는 index 번호이다.


onActivityResult 메서드랑 똑같이 보면 될 것이다.




만약 사용자가 다이얼로그를 보고 승낙을 누른다면 좋겠지만,


만약 거절을 누른다면,


더 나아가 다시 보지 않기 체크 박스에 체크후 거절 버튼을 누른다면


상황은 복잡해진다.


그러한 상황을 캐치 하기 위해 ActivityCompat.shouldShowRequestPermissionRationale() 메서드를 써야한다.


해당 메서드를 쓰기 앞서 


사용자가 다이얼로그를 띄웠을 시, 할 수 있는 경우의 수를 계산 해보자.


1. 승낙하는 경우.


2. 거절하는 경우.


3. 다시 보기 않기 체크 후 거절 하는 경우.


이렇게 3가지 이다.


2번의 경우의 수인 그냥 거절 하는 경우, 후에 다시 앱을 킬때 


ActivityCompat.shouldShowRequestPermissionRationale() 메서드를 사용하면 true 가 리턴이 된다.


즉, 사용자가 거절한 이력이 있나 없나를 알 수 있는 메서드이다.  


이 메서드는 조심해서 써야한다. 


사용자가 최초 앱 접속시 뜨는 다이얼로그는 거절 이력이 없기때문에


ActivityCompat.shouldShowRequestPermissionRationale() 메서드를 사용시 false 가 리턴된다.


또한, 사용자가 3번 경우의 수를 선택 했을 경우도 마찬가지로  false가 리턴된다.


사용자가 3번 경우의 수를 선택 했을 경우는 후에 앱이 다시 켜질때,


requestPermission() 메서드를 쓰더라도 구글이 만든 권한 설정 요구 다이얼로그가 뜨질 않는다.


그 후, 곧바로 OnRequestPermissionResult 메서드가 실행된다.


따라서, 개발자 자체적으로 다이얼로그를 띄어 사용자가 직접 설정에 들어가 권한을 설정하게끔 유도를 해야한다.



아래는 ActivityCompat.shouldShowRequestPermissionRationale() 메서드를 추가한 예시이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED){
   //Manifest.permission.READ_CALENDAR이 접근 승낙 상태 일때
else{
   //Manifest.permission.READ_CALENDAR이 접근 거절 상태 일때
   if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.READ_CALENDAR)){
      //사용자가 다시 보지 않기에 체크를 하지 않고, 권한 설정을 거절한 이력이 있는 경우
   } else{
      //사용자가 다시 보지 않기에 체크하고, 권한 설정을 거절한 이력이 있는 경우
   }
 
   //사용자에게 접근권한 설정을 요구하는 다이얼로그를 띄운다.
   //만약 사용자가 다시 보지 않기에 체크를 했을 경우엔 권한 설정 다이얼로그가 뜨지 않고,
   //곧바로 OnRequestPermissionResult가 실행된다.
   ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CALENDAR},0);
   
}
cs


최종 결과값은 OnRequestPermissionResult() 메서드로 받아야 한다.


아래의 코드는 예시이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResult){
  super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  //위 예시에서 requestPermission 메서드를 썼을시 , 마지막 매개변수에 0을 넣어 줬으므로, 매칭
  if(requestCode == 0){
     // requestPermission의 두번째 매개변수는 배열이므로 아이템이 여러개 있을 수 있기 때문에 결과를 배열로 받는다.
     // 해당 예시는 요청 퍼미션이 한개 이므로 i=0 만 호출한다.
     if(grantResult[0== 0){
        //해당 권한이 승낙된 경우.
     }else{
        //해당 권한이 거절된 경우.
     }
  }
}
cs


onRequestPermissionResult 메서드는 사용자가 무얼 선택하던, 무조건 타는 메서드 이다.


따라서 이 메서드를 통해 사용자가 권한을 거절했을 경우 대처해야한다.


필자는 복잡한 권한체크를 해야한다는 부담감과 설령 구현하더라도 해당 코드들이 난잡하게 섞이는게 싫어서, 


자체적으로 라이브러리를 만들었다.


손쉽게 권한체크를 하고싶은 독자들은 아래의 Github 주소를 참고 하길 바란다.


 

Github에서 Star를 눌러준다면, 더 좋은 오픈소스를 만들 수 있다.



  https://github.com/Mommoo/MommooPermission




포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

.aspx.cs 파일에 서버 컨트롤을 작성하다 보면,


페이지가 다시 로딩 될 시(버튼 클릭, 새로고침 등) 


변수의 데이터가 날라가고 처음 값으로 셋팅된다.


만약 페이지가 재로딩 되더라도 상태 값을 저장하고 싶다면,


Page객체가 제공하는 ViewState 컬랙션을 사용하면 좋다.


Key 와 Value로 상태를 저장하면 된다.


주의할 점은 동일 페이지 내에서만 적용된다.


다른 페이지간 이동시, ViewState는 유지되지 않는다.


아래의 예시코드를 보자.


public partial class Test : System.Web.UI.Page{


  protected int test = 1;


  protected void Page_Load(Object sender, EventArgs e){


    if(IsPostBack) Response.write((int)ViewState["Test"]);

    Response.write("test : "+test);

  }


  protected void Page_PreRender(Object sender, EventArgs e){

    ViewState["Test"] = test++;

  }


}


Page가 재로딩 될때 ViewState를 접근해야 null pointer오류가 나지 않는다.


따라서 Page가 제공해주는 IsPostBack 변수를 사용했다.


실행한 후, 결과물을 보면 test 는 계속 1이지만, ViewState를 통해 가져온 상태값은 하나씩 증가하는걸 볼 수 있다.


중요한 메서드는 Page_PreRender 이다. 페이지 생명주기에서 Page_PreRender 의 역할은


Page가 재로딩 되기전의 상태를 제공해준다.


따라서 test 의 변수 정보를 ViewState["Test"]에 넣어준다. 


ViewState는 아래와 같이 키 와 벨류가 존재한다.


ViewState[키값] = 벨류; 이렇게 사용하는데 키값이 존재하지 않을경우엔 새로 생성되고


존재하는 경우엔 벨류값이 바뀐다.





포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

오늘 포스팅할 내용은 안드로이드 statusbar에


색상을 넣는 방법이다.


statusbar는 안드로이드 화면 최상단에 존재하며,


앱의 푸쉬나, 배터리 상태, 시간등을 알려주는 역할을 한다.


롤리팝 버전부터 toolbar ( 또는 actionBar )의 색상과 통일감을 주기 위해


statusBar의 색상을 바꿀 수 있도록 API 개편했다.


따라서 머터리얼 디자인을 준수한다면, statusbar 색상을 고려하지 않을 수 없다.


하지만, 5.0 이상 API 에선 바꾸기 수훨하지만, 5.0 미만 API 에서는


statusbar 색상을 바꾸기가 쉽지 않다.


본 포스팅은 롤리팝 이후 API를 이용한 statusbar 색상 넣기와


롤리팝 이전 API를 이용한 statusbar 색상 넣기


두가지를 다룰 예정이다.



아래는 롤리팝 버전 이후 API를 이용한 statusbar 색상 넣기이다. 


롤리팝 보다 낮은 버전은 동작하지 않으니 주의하자.




왼쪽의 이미지를 보면,


각 view 영역에서의 색상태그를 알려준다.


statusbar 색상을 바꾸기 위해


res 디렉토리 안에 있는 


value 디렉토리에 접근후 color.xml에


왼쪽과 같이 태그를 작성한다.


<color name= "colorPrimaryDark">원하는색상</color>


왼쪽에서 보이는 view 영역중 바꾸고 싶은 색상이 있다면,


위와 같이 태그이름과 벨류를 정해주면 된다.
















위의 방법은 xml셋팅으로 statusbar를 바꾸는 방법이다.


아래의 방법은 코드적으로 바꿀 수 있는 방법이다.


액티비티 안이라면 아래와 같이 똑같이 작성하면 되지만, ( window = getWindow() )


액티비티가 아니라면 액티비티 참조값에서 메서드를 호출해야한다. ( window = activity.getWindow() )


Window window = getWindow();


window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUENT_STATUS);


window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUND);


window.setStatusBarColor(자신이 바꾸고 싶은 컬러);


롤리팝 API부터 statusbar는 TRANSLUENT를 적용하면 투명 상태가 아니라, 반투명 상태의 색상이 입혀진다.


따라서 TRANSLUENT 속성을 지우고, statusbar 영역의 view를 칠할 수 있게 셋팅을 한다.


셋팅이후 자신의 원하는 색상을 바꿀 수 있다.



다음 포스팅엔 롤리팝 하위 버전에서 상태바 색상을 바꾸는 방법을 포스팅 하겠다.

 





포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

+ Recent posts