Notepad


  • 홈

  • 아카이브

  • 태그

[Effective Java] 종료자 사용을 피하라

작성일 2017-10-03 | In Effective Java |

종료자(finalizer)은 불필요하다

C나 C++ 사용자라면 소멸자를 많이들 사용한다. 다 사용한 자원들을 반환하기 위해서 사용하는데 java에서는 종료자(finalizer)의 사용을 피하는 것이 좋다. 종료자를 사용하면 시스템 오류, 성능 오류, 이식성 문제가 발생할 가능성이 크다. 또 종료자를 사용하면 프로그램의 성능이 급격하게 떨어진다. JVM은 모든 참조가 사라지기 전까지 종료자의 실행을 미루기 때문이다. 그래서 긴급하게 종료가 필요한 것을 종료자 안에 두면 언제 종료될지 모른다.

더 읽어보기 »

[Effective Java] 유효기간이 지난 객체는 폐기 하라

작성일 2017-10-03 | In Effective Java |

유효기간이 지난 객체는 폐기 하라

메모리를 손수 관리 해주는 C와 C++을 쓰다가 쓰레기 수집기가 있는 언어를 사용하게 되면 정말 편해진다. 편해짐에 따라 메모리 관리를 하지 않아도 쓰레기 수집기가 알아서 해준다는 생각은 하면 안된다. Stack 클래스를 예로 들어보자.

더 읽어보기 »

[Effective Java] 불필요한 객체는 만들지 말라

작성일 2017-09-27 | In Effective Java |

불필요한 객체는 만들지 말라

기능적으로 객체를 새로 만드는 것보다 재사용하는 편이 훨씬 빠르다.

String s = new String("Hello, World");

이런식으로 객체를 만들면 호출 될 때마다 항상 새로운 객체가 만들어지기 때문에 이러한 방법은 피하는 편이 좋고 아래와 같은 방법을 쓰는것이 좋다.

String s = "Hello, World";

실제로 다음과 같은 코드를 실행시키면 다음과 같은 결과를 얻는다.

public class ImmutableObject {
    public static void main(String[] args) {
        String a = new String("Hello, World");
        String b = new String("Hello, World");

        String c = "Hello, World";
        String d = "Hello, World";

        System.out.println(a == b);
        System.out.println(c == d);
    }
}

변경 불가능한 객체인 경우 정적 팩토리 메소드를 사용하면 새로운 객체를 만들지 않는다. 변경 가능한 객체도 재사용할 수 있다. 단, 변경하지 않는다면 말이다. 다음의 예를 보자

public class Person {
    private final Date birthDate;

    public boolean isBabyBoomer() {
        Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));

        gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
        Date boomStart = gmtCal.getTime();
        gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
        Date boomEnd = gmtCal.getTime();
        return birthDate.compareTo(boomStart) >= 0 && birthDate.compareTo(boomEnd) < 0;
    }
}

이렇게 만들면 해당 메소드를 호출될 때마다 Calendar 객체 하나, TimeZone 객체 하나, 그리고 Date 객체 두 개를 쓸데없이 만들어 낸다. 이렇게 비효율적인 코드는 정적 초기화 블록(static initializer)을 통해 개선하는 것이 좋다.

public class Person {
    private final Date birthDate;

    private static final Date BOOM_START;
    private static final Date BOOM_END;
    
    static {
        Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
        BOOM_START = gmtCal.getTime();
        gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
        BOOM_END = gmtCal.getTime();
    }
    
    public boolean isBabyBoomer() {
        return birthDate.compareTo(BOOM_START) >= 0 && birthDate.compareTo(BOOM_END) < 0;
    }
}

이 클래스는 클래스가 초기화 될 때 한 번만 만든다. 그리고 BOOM_START와 BOOM_END가 한번만 초기화 되는 상수라는 사실도 알수있다. 또 객체 자료형보다 기본 자료형을 사용하는 것이 좋다.

Integer i = 3;
// Integer i = new Integer(3);

auto-boxing이 되면서 불필요한 객체를 만들기 때문이다.

더 읽어보기 »

[Effective Java] 객체 생성을 막을 때는 private 생성자를 사용하라

작성일 2017-09-24 | In Effective Java |

객체 생성을 막을 때는 private 생성자를 사용하라

코딩을 하다 보면 상수나 배열 등을 모아둔 유틸리티 클래스를 만들 때도 있다. 이러한 클래스들은 객체가 생성되면 안된다. 생성자를 만들지 않으면 컴파일러가 public 생성자를 넣기 때문에 안되고 abstract로 만들면 이 클래스를 상속받는 하위 클래스들을 사용해서 객체를 만들 수 있기 때문에 안된다. 그래서 private 생성자를 두어 객체 생성을 막는다.

더 읽어보기 »

[Effective Java] private 생성자나 enum 자료형은 싱글턴 패턴을 따르도록 설계하라

작성일 2017-09-23 | In Effective Java |

private 생성자나 enum 자료형은 싱글턴 패턴을 따르도록 설계하라

싱글턴 패턴은 객체를 하나만 만들 수 있는 클래스이다. 창 관리자나 파일 시스템같은 것들이 그 예이다. 하지만 AccessibleObject.setAccessible 메소드의 도움을 받아 권한을 획득한 클라이언트는 리플렉션 기능을 통해 private 생성자를 다시 호출할 수 있다. 이러한 공격을 방어하려면 두 번째 객체를 생성하라는 요철을 받으면 예욀르 던지도록 생성자를 고쳐야 한다. 또 다른 방법은 public으로 선언된 정적 팩토리 메소드를 이용한다. 정적 팩토리 메소드를 이용해서 첫 번째 방법으로 공격하면 막지 못한다.

public class Book {
    private static final Book INSTANCE = new Book();

    private Book() {
		...
    }

    public static Book getInstance() {
        return INSTANCE;
    }
}

싱글턴 클래스를 직렬화 가능 클래스로 만들려면 클래스 선언에 implements Serializable을 추가하는 것으로는 부족하다. 모든 필드를 transient로 선언하고 readResolve 메소드를 추가해야한다. 그렇지 않으면 serialize된 객체가 역직화 될 때마다 새로운 객체가 생기게 된다.

// 싱글턴 상태를 유지하기 위한 readResolve 구현
private Object readResolve() {
	// 동일한 Book 객체가 반환되도록 하는 동시에, 가짜 Book 객체는
    // 쓰레기 수집기가 처리하도록 만든다.
	return INSTANCE;
}

jdk 1.5부터는 싱글턴을 구현할 때 새로운 방법을 사용할 수 있다. 원소가 하나뿐인 enum자료형을 정의하는 것이다.

public enum Book {
	INSTANCE;
    
    public void readBook() {...}
}

이렇게 만들면 직렬화가 자동으로 처리된다는 것이다. 여러 객체가 생길일이 없고 리플렉션을 통한 공격에도 안전하다. 원소가 하나뿐인 enum 자료형이야말로 싱글턴을 구현하는 가장 좋은 방법이다. 클래스를 싱글턴으로 만들면 클라이언트를 테스트하기가 어려워질 수가 있다. 싱글턴이 어떤 인터페이스를 구현하는 것이 아니면 가짜 구현으로 대체할 수 없기 때문이다.

더 읽어보기 »

[Android] Retrofit 사용법

작성일 2017-09-22 | In Android |

Android Retrofit 사용법

Retrofit은 Square Open Source에서 만든 REST API 통신을 위한 안드로이드 라이브러리이다. 안드로이드 서비스를 개발하려면 서버와의 통신은 꼭 필요합니다. 다른 라이브러리를 사용하면 불필요하고 귀찮은 작업이 많습니다. Retrofit은 불필요한 작업을 최소화 하고 쓰기 쉽게 만들어진 라이브러리입니다.

먼저 gradle을 추가합니다.

compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'io.reactivex.rxjava2:rxjava:2.1.3'
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.google.code.gson:gson:2.7'
compile 'com.android.support:recyclerview-v7:25.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.picasso:picasso:2.5.2'

간단하게 파싱할 https://randomuser.me/api의 데이터입니다.

{
    results: [
        {
            gender: "male",
            name: {
                title: "mr",
                first: "jimi",
                last: "polon"
            },
            location: {
                street: "9350 itsenäisyydenkatu",
                city: "kristinestad",
                state: "south karelia",
                postcode: 14156
            },
            email: "jimi.polon@example.com",
            login: {
                username: "blackdog726",
                password: "ranger",
                salt: "cFE2HMuR",
                md5: "c7caf7af79bc56b4ddeee96bb9f90d85",
                sha1: "c2a0382305915dc106e2b75a48b2f454aef4f334",
                sha256: "ff6fbdd32246bd365b3a095c91408503a1f73620526e4f24da4759f439427096"
            },
            dob: "1967-10-03 16:02:57",
            registered: "2009-07-19 00:26:27",
            phone: "02-657-116",
            cell: "042-656-81-18",
            id: {
                name: "HETU",
                value: "1167-163D"
            },
            picture: {
                large: "https://randomuser.me/api/portraits/men/76.jpg",
                medium: "https://randomuser.me/api/portraits/med/men/76.jpg",
                thumbnail: "https://randomuser.me/api/portraits/thumb/men/76.jpg"
            },
            nat: "FI"
        }
    ],
    ...
    info: {
    seed: "468e4162245cdf64",
    results: 1,
    page: 1,
    version: "1.1"
    }
}

일단 데이터를 얻어오기 위해 interface를 정의합니다.

public interface APIInterface {
    @GET("/api/")
    Call<PersonModel> getUsers(@Query("results") int results);
}

실제로 요청하는 url은 https://randomuser.me/api/?results=1 입니다. 여기 result에 따라 몇 개의 데이터를 얻어올지 결정합니다. 그리고 인터페이스에 있는 메소드의 인자들은 url에 있는 ? 뒤에 query를 설명합니다.

model도 적절하게 생성합니다.

class Name {
    @SerializedName("title")
    @Expose
    private String title;
    @SerializedName("first")
    @Expose
    private String first;
    @SerializedName("last")
    @Expose
    private String last;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getFirst() {
        return first;
    }

    public void setFirst(String first) {
        this.first = first;
    }

    public String getLast() {
        return last;
    }

    public void setLast(String last) {
        this.last = last;
    }
}

class UserProfileImage {
    @SerializedName("large")
    @Expose
    private String large;
    @SerializedName("medium")
    @Expose
    private String medium;
    @SerializedName("thumbnail")
    @Expose
    private String thumbnail;

    public String getLarge() {
        return large;
    }

    public void setLarge(String large) {
        this.large = large;
    }

    public String getMedium() {
        return medium;
    }

    public void setMedium(String medium) {
        this.medium = medium;
    }

    public String getThumbnail() {
        return thumbnail;
    }

    public void setThumbnail(String thumbnail) {
        this.thumbnail = thumbnail;
    }
}

class Item {
    @SerializedName("name")
    @Expose
    private Name name;
    @SerializedName("picture")
    @Expose
    private UserProfileImage userProfileImage;
    @SerializedName("gender")
    @Expose
    private String gender;
    @SerializedName("phone")
    @Expose
    private String phone;

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public Name getName() {
        return name;
    }

    public void setName(Name name) {
        this.name = name;
    }

    public UserProfileImage getUserProfileImage() {
        return userProfileImage;
    }

    public void setUserProfileImage(UserProfileImage userProfileImage) {
        this.userProfileImage = userProfileImage;
    }
}

public class PersonModel {
    @SerializedName("results")
    @Expose
    private List<Item> items = null;
    @SerializedName("page")
    @Expose
    private int page;

    public List<Item> getItems() {
        return items;
    }

    public void setItems(List<Item> items) {
        this.items = items;
    }

    public int getPage() {
        return page;
    }

    public void setPage(int page) {
        this.page = page;
    }
}

또 결과를 보여줄 recyclerview도 만듭니다.

interface ItemClickListener {
    void onItemClick(int position);
}

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements ItemClickListener {
    private List<Item> items;
    private Context context;

    public RecyclerAdapter(Context context, List<Item> items) {
        this.context = context;
        this.items = items;
    }

    public String getItemTitle(int position) {
        return items.get(position).getName().getFirst();
    }

    @Override
    public void onItemClick(int position) {
        Toast.makeText(context, getItemTitle(position), Toast.LENGTH_SHORT).show();
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        RecyclerView.ViewHolder holder = null;

        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false);
        holder = new UsersViewHolder(v, this);

        return holder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder == null) {
            return;
        }

        ((UsersViewHolder) holder).name.setText(items.get(position).getName().getFirst());
        Picasso.with(context).load(items.get(position).getUserProfileImage().getThumbnail()).into(((UsersViewHolder) holder).profileImg);
    }

    @Override
    public int getItemCount() {
        return this.items.size();
    }

    static class UsersViewHolder extends RecyclerView.ViewHolder {
        private TextView name;
        private ImageView profileImg;

        public UsersViewHolder(View itemView, final ItemClickListener itemClickListener) {
            super(itemView);
            this.name = (TextView) itemView.findViewById(R.id.text);
            this.profileImg = (ImageView) itemView.findViewById(R.id.userImg);

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    itemClickListener.onItemClick(getAdapterPosition());
                }
            });
        }
    }
}

마지막으로 retrofit을 만듭니다.

Retrofit client = new Retrofit.Builder().baseUrl("https://randomuser.me").addConverterFactory(GsonConverterFactory.create()).build();
APIInterface service = client.create(APIInterface.class);
Call<PersonModel> call = service.getUsers(10);
call.enqueue(new Callback<PersonModel>() {
    @Override
    public void onResponse(Call<PersonModel> call, Response<PersonModel> response) {
        if (response.isSuccessful()) {
            Log.i("Success", response.toString());
            PersonModel person = response.body();
            recyclerView.setAdapter(new RecyclerAdapter(getApplicationContext(), response.body().getItems()));
        }
    }

    @Override
    public void onFailure(Call<PersonModel> call, Throwable t) {
        Log.i("Failed", "Failed Connection");
    }
});
더 읽어보기 »

[Effective Java] Builder Pattern

작성일 2017-09-13 | In Effective Java |

Builder Pattern

정적 팩토리 메소드나 생성자는 같은 문제를 가지고 있다. 선택적 인자가 많은 상황에 잘 적응하지 못한다는 것이다. 이럴때에 점층적 생성자 패턴을 적용하기는 하지만 필요없는 인자를 넣어 줘야 할 때도 있고 클라이언트 코드를 작성하기도 어려워진다. 그리고 가독성도 많이 떨어진다. 다른 방법으로 자바빈 패턴이있다. 일단 객체를 생성한 뒤에 세터를 사용해 객체에 값을 넣어주는 방법이다. 하지만 객체를 만들면서 초기화 작업을 하지 못하므로 객체의 일관성이 일시적으로 깨질 수 있다는 것이다. 빌더 패턴을 사용하면 이 문제를 해결 할 수 있다.

Code

class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // 필수인자
        private final int servingSize;
        private final int servings;
        // 선택적 인자 - 기본값으로 초기화
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbohydrate = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder calories(int val) {
            calories = val;
            return this;
        }

        public Builder fat(int val) {
            fat = val;
            return this;
        }

        public Builder sodium(int val) {
            sodium = val;
            return this;
        }

        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

public class BuilderPattern {
    public static void main(String[] args) {
        NutritionFacts n = new NutritionFacts.Builder(0, 1).calories(1).build();
    }
}

NutritionFacts 객체는 변경이 불가능하고 모든 인자의 기본값이 한곳에 모여 있다. 그리고 빌더에 정의된 설정 매소드는 빌더 객체 자신을 반환하므로, 설정 매소드를 호출하는 코드는 쭉 이어서 쓸 수 있다. 또 작성하기도 쉽고 가독성도 뛰어나다.

빌더 패턴 단점

객체를 만들려면 빌더 객체를 생성해야 한다는 것이다. 빌더 객체를 만드는 오버헤드가 문제가 안될 수 도있지만 성능이 중요한 상황에서는 고려해야한다.

더 읽어보기 »

[Effective Java] 정적 팩토리 메소드

작성일 2017-09-12 | In Effective Java |

정적 팩토리 메소드

클래스를 통해 객체를 만드는 방법은 public으로 선언된 생성자를 사용하는 것이다. 또 다른 방법은 클래스의 public으로 선언된 정적 팩토리 메소드(Static Factory Method)를 사용하는 것이다. 이 메소드는 static로 선언되어 있어서 외부에서 바로 접근할 수 있다.

정적 팩토리 메소드의 장점

  1. 생성자와는 달리 이름이 있다. 생정자의 전달되는 인자들은 어떤 객체가 생성되는지를 설명하지 못하지만, 정적 팩토리 메소드는 이름만 잘 짓는다면 어떤 객체가 만들어 지는지 설명할 수 있다.
  2. 생성자와는 달리 호출할 때마다 새로운 객체를 생성할 필요가 없다. 변경 불가능 클래스라면 이미 만들어둔 객체를 활용할 수도 있고, 만든 객체를 캐시 해놓고 재사용하여 같은 객체가 불필요하게 거듭 생성되는 일을 피할 수도 있다. 동일한 객체가 요청되는 일이 잦고, 특히 객체를 만드는 비용이 클 때 적용하면 성능을 크게 개선할 수 있다. 다음과 같은 코드가 정적 팩토리 메소드를 적용한 것이다.
     public static Boolean valueOf(boolean b) {
             return b ? Boolean.TRUE : Boolean.FALSE;
         }
    

    정적 팩토리 메소드를 사용하면 같은 객체를 반복해서 반환할 수 있으므로 어떤 시점에 어떤 객체가 얼마나 존재할지를 정밀하게 제어할 수 있다. 이런 기능을 갖춘 클래스를 개체 통제 클래스(instance-controlled class)라고 부른다. 개체 수를 제어하면 싱글톤 패턴을 따를 수 있고, 객체 생성이 불가능한 클래스를 만들 수도 있다. 변경 불가능의 클래스의 경우 두 개의 같은 객체가 존재하지 못하도록 할 수도 있다.

  3. 생성자와는 달리 반환값 자료형의 하위 자료형 객체를 반환할 수 있다는 것이다. public으로 선언되지 않은 클래스의 객체를 반환하는 API를 만들 수 있다. 그러면 구현 세부사항을 감출 수 있으므로 아주 간결한 API가 가능하다. 인터페이스 기반 프레인워크(interface-based framework)구현에 적합한데, 이 프레임워크에서는 정적 팩토리 메소드의 반환값 자료형으로 이용된다. 인터페이스는 정적 메소드를 가질 수 없으므로, 관습상 반환값 자료형이 Type이라는 이름의 인터페이스인 정적 팩토리 메소드는 Type라는 이름의 객체 생성 불가능 클래스안에 둔다.
     // 서비스 인터페이스
     public interface ServiceInterface {
         int add(int x, int y);
         int mul(int x, int y);
         ... // 서비스에 고유한 메소드들이 이 자리에 온다
     }
    
     public class ServiceImplement implements ServiceInterface {
         @Override
         public int add(int x, int y) {
             return x + y;
         }
    
         @Override
         public int mul(int x, int y) {
             return x * y;
         }
     }
    
     //서비스 제공자 인터페이스
     public interface ProviderInterface {
         ServiceInterface newService();
     }
    
     public class ProviderImplement implements ProviderInterface {
         @Override
         public ServiceInterface newService() {
             return new ServiceImplement();
         }
     }
    
     // 서비스 등록과 접근에 사용되는 객체 생성 불가능 클래스
     public class ServiceProvider {
         private ServiceProvider() {} // 인스턴스 생성 X
    	
         private static final Map<String, ProviderInterface> providers =
             new ConcurrentHashMap<String, ProviderInterface>();
         public static final String DEFAULT_PROVIDER_NAME = "<def>";
    	
         // 제공자 등록 API
         public static void registerDefaultProvider(ProviderInterface p) {
             registerProvider(DEFAULT_PROVIDER_NAME, p);
         }
    	
         public static void registerProvider(String name, ProviderInterface p) {
             providers.put(name, p);
         }
    	
         // 서비스 접근 API
         public static ServiceInterface newInstance() {
             return newInstance(DEFAULT_PROVIDER_NAME);
         }
    	
         public static ServiceInterface newInstance(String name) {
             ProviderInterface p = providers.get(name);
             if (p == null) {
                 throw new IllegalArgumentException(
                     "No provider registered with name: " + name);
             }
             return p.newService();
         }
     }
    
     public class ServiceMain {
         public static void main(String[] args) {
             ServiceProvider.registerDefaultProvider(new ProviderImplement());
             ServiceProvider.registerProvider("BoWoon", new ProviderImplement());
    	
             System.out.println(ServiceProvider.newInstance().add(1, 4));
             System.out.println(ServiceProvider.newInstance().mul(2, 2));
             System.out.println(ServiceProvider.newInstance("BoWoon").add(5, 5));
         }
     }
    
  4. 형인자 자료형(parameterized type) 객체를 만들 때 편하다
     Map<String, List<String>> m = new HashMap<String, List<String>>();
    

    자료형 명세를 중복하면 형인자가 늘어남에 따라 길고 복잡한 코드가 만들어진다. 하지만 정적 팩토리 메소드를 사용하면 컴파일러가 형인자를 스스로 알아내도록 할 수 있다. 이런 기법을 자료형 유추(type inference)라고 부른다.

     public static <K, V> HashMap<K, V> newInstance() {
         return new HashMap<K, V>();
     }
    

    이런 메소드가 있으면 아까와 같은 코드를 간결하게 작성할 수 있다.

     Map <String, List<String>> m = HashMap.newInstance();
    

정적 팩토리 메소드의 단점

  1. public이나 protected로 선언된 생성자가 없으므로 하위 클래스를 만들 수 없다
  2. 정적 팩토리 메소드가 다른 정적 메소드와 확연히 구분되지 않는다는 것
더 읽어보기 »
1 … 3 4
Kim BoWoon

Kim BoWoon

https://kimbowoon.github.io/

38 포스트
3 카테고리
RSS
© 2020 Kim BoWoon
Powered by Jekyll
Theme - NexT.Muse