달력

1

« 2025/1 »

  • 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
2007. 8. 20. 17:10

Java Anti-Pattern 그거/Java2007. 8. 20. 17:10

Java Anti-Patterns

좋아보이지만 좋아보이지 않는 코드들을 모아봤다.

# String concatenation

- very bad code
String s = "";
for (Person p : persons) {
    s += ", " + p.getName();
}
s = s.substring(2); //remove first comma

이건 정말 멍청한 짓이다. loop 안에서 String의 concatenation을 반복하는 것은 쓰잘데기 없는 array copy와 garbage를 남발하는 것이다.
게다가, 마지막에 콤마를 제거하는 연산을 한 번 더 해줘야 한다.

- better code
StringBuilder sb = new StringBuilder(persons.size() * 16); // well estimated buffer
for (Person p : persons) {
    if (sb.length() > 0) sb.append(", ");
    sb.append(p.getName);
}


# Lost StringBuffer performance

- not so good code
StringBuffer sb = new StringBuffer();
sb.append("Name: ");
sb.append(name + '\n');
sb.append("!");
...
String s = sb.toString();

좋아 보임에도 불구하고 위 코드는 문제가 있다. 가장 명백하게 눈에 띄는 실수는 3번째 라인의 String concatenation 이다.
4번째 라인의 char를 append 하는 것은 String을 append 하는 것 보다 빠르다.
또한 하나 간과한 것이 buffer의 사이즈를 초기화하지 않음으로해서, 불필요한 resizing(array copy)이 일어날 수 있다는 것이다.
JDK 1.5에서는 synchronization이 필요없는 로컬 변수에는 StringBuilder가 StringBuffer 대신에 사용된다.

- good code
StringBuilder sb = new StringBuilder(100);
sb.append("Name: ");
sb.append(name);
sb.append('\n');
sb.append('!');
String s = sb.toString();


# Testing for string equality

- not perfect
if (name.compareTo("John") == 0) ...
if (name == "John") ...
if (name.equals("John")) ...
if ("".equals(name)) ...

위 비교는 잘못된 결과를 초래할 수 있다.
compareTo() method는 좀 지나치고, == 연산자는 객체 동치에 대한 연산을 수행하는데, 이는 아마도 원치 않는 결과를 초래할 것이다.
equals() method를 사용해야 할 것인데, 변수와 상수의 위치를 바꾸면 NullPointerException을 피할 수 있는 부가적인 안정성도 얻을 수 있고,
loop 안에서 equals() method가 호출될 때 equals() method가 같은 object로부터 호출이 되기 때문에 수행 속도의 향상도 가져올 수 있다.
빈 문자열을 체크할 때는 문자열의 길이를 체크하는 것이 가장 빠른데, 이는 equals() method가 hash code를 먼저 계산하기 때문이다.

if ("John".equals(name)) ...
if (name.length() == 0) ...


# Converting numbers to Strings

"" + set.size()
new Integer(set.size()).toString()

Set.size()의 반환값은 int 이다. 결과를 String 으로 바꾸려고 하는데, 위의 두 줄의 코드를 이용하여 String으로의 변환을 수행할 수 있으나,
첫번째 라인에서 String concatenation의 문제가 있고, 두번째 라인에서는 toString() method를 수행하기 위한 Integer class를 생성하기까지 한다.
이는 아래와 같이 간단하게 수정할 수 있다.

String.valueOf(set.size())

# Not taking adavantage of immutable objects

- code wastes resources
zero = new Integer(0);
return Boolean.valueOf("true");

Integer class와 Boolean class는 immutable이다. 즉, 불변객체이다. 쓸데없이 객체를 생성할 필요가 없다.
위 코드에서 사용된 Integer와 Boolean class 들은 자주 사용하는 instance들에 대한 built-in cache가 존재한다.
Boolean class의 경우는 값이 오로지 true/false 두가지 뿐이다.

- conservative code
zero = Integer.valueOf(0);
return Boolean.TRUE;


# XML parsers are for sissies

- native code
int start = xml.indexOf("<name>");
int end = xml.indexOf("</name>");
String name = xml.substring(start, end);

위에서 사용한 XML parsing은 오로지 정말 단순한 XML 문서에서만 동작을 한다.
다음과 같은 경우의 XML 문서에서는 오동작을 할 것이다.
a) 문서상에서 name element가 유일하지 않을 때
b) name의 내용이 문자데이터로만 이루어지지 않았을 때
c) 문서에서 XML namespace를 사용할 경우

XML은 문자열 연산을 하기에 너무 복잡하다. Xereces 같은 XML parser의 경우 jar 파일이 1 MB 가 넘는데는 다 그만한 이유가 있는 것이다.

- more professional code
SAXBuilder builder = new SAXBuilder(false);
Document doc = doc = builder.build(new StringReader(xml));
String name = doc.getRootElement().getChild("name").getText();


# The XML encoding trap

- very bad code
String xml = FileUtils.readTextFile("my.xml");

XML 파일의 내용을 읽어서 String class에 저장하는 것은 멍청한 짓이다.
XML 파일은 XML header에 해당 내용에 대한 encoding을 지정하고 있다.
XML 파일을 읽기 전에 해당 파일에 대한 encoding 정보를 알아야 한다.
또한, XML 파일의 내용을 String class에 저장하는 것은 메모리 낭비다.
XML parser들은 XML 파일을 parsing하는데 InputStream을 이용하여 내용을 읽어 들이고, encoding이 올바로 되었는지 확인한다.


# Undefined encoding

- not portable code
Reader r = new FileReader(file);
Writer w = new FileWriter(file);
Reader r = new InputStreamReader(inputStream);
Writer w = new OutputStreamWriter(outputStream);
String s = new String(byteArray); // byteArray is a byte[]
byte[] a = string.getBytes();

위 코드들은 각각 기본 platform encoding 정보를 사용하여 byte와 char를 변환하고 있다.
위 코드들은 어떤 platform에서 실행되느냐에 따라서 다른 결과를 초래한다.
이는 다른 platform 간의 데이터 전송에 있어서 문제를 유발할 수 있다.
platform의 기본 encoding 정보를 이용하는 것은 나쁜 습관이다.
변환은 encoding 정보를 지정하여 수행되어야 한다.

- portable code
Reader r = new InputStreamReader(new FileInputStream(file), "ISO-8859-1");
Writer w = new OutputStreamWriter(new FileOutputStream(file), "ISO-8859-1");
Reader r = new InputStreamReader(inputStream, "UTF-8");
Writer w = new OutputStreamWriter(outputStream, "UTF-8");
String s = new String(byteArray, "ASCII");
byte[] a = string.getBytes("ASCII");


# Unbuffered streams

- performance sinking
InputStream in = new FileInputStream(file);
int b;
while ((b = in.read()) != -1) {
   ...
}

위 코드는 파일을 byte 단위로 읽는다.
read() method가 호출 될때마다 매번 JNI call이 일어난다.
JNI call은 비용이 비싸다. native call의 횟수를 stream을 warpping한 BufferedInputStream을 사용하여 획기적으로 줄일 수 있다.
노트북에서 /dev/zero 로 부터 1MB의 데이터를 읽어들이는데 위 코드는 1초가 걸린 반면 BufferedInputStream을 사용한 경우는
60 ms로 줄었음을 확인할 수 있다. 자그만치 94%의 속도 증가이다. 이 stream wrapping 은 output stream에 대해서도 적용이 가능하며,
file system 에서 뿐만 아니라 socket 에 대해서도 적용가능하다.

- reasonable performance
InputStream in = new BufferedInputStream(new FileInputStream(file));

# Infinite heap

- resource waster
byte[] pdf = toPdf(file);

파일의 내용을 읽어서 byte array로 반환하여 PDF 파일을 만드는 method가 있다.
이 코드는 파일의 내용이 적을 경우에만 유효하다. Heap에 파일 크기만한 여유가 있을 경우에만 가능하다는 얘기다.
그렇지 않을 경우 OOE(OutOfMemoryException)에 직면하게 된다.
Bulk 데이터는 절대로 byte array로 취급되면 안된다. Stream을 이용해야 하고 데이터는 DB나 disk에 저장되어야 한다.

- conservative code
File pdf = toPdf(file);


# Catch all: I don't know the right runtime exception

- wrong code
Query q = ...
Person p;
try {
    p = (Person) q.getSingleResult();
} catch(Exception e) {
    p = null;
}

이는 J2EE EJB3의 query 이다. getSingleResult() method는 아래와 같은 경우에 RuntimeException을 야기한다.
a) 결과가 유일하지 않을 때
b) 결과가 없을 때
c) query가 DB 오류등으로 인해 실행되지 못했을 때

위 코드는 모든 exception을 잡을 수 있다. 전형적인 catch-all 블럭이다.
null 이라는 결과를 사용하는 것은 b) 의 경우에는 유효할 지 모르나, a)나 c)의 경우에는 그렇지 않다.
일반적으로 필요한 exception 이외에는 더 잡을 필요도 없다.

- correct code
Query q = ...
Person p;
try {
    p = (Person) q.getSingleResult();
} catch(NoResultException e) {
    p = null;
}

# Exceptions are annoying

- hard to debug code
try {
    doStuff();
} catch(Exception e) {
    log.fatal("Could not do stuff");
}
doMoreStuff();

위 코드에는 두가지 문제점이 있다.
첫째, 이 코드에서 정말로 치명적인 오류가 발생한다면, 이를 호출한 쪽에 알리고, 적당한 exception을 발생시켜야 한다.
그렇지 않으면, 치명적인 오류 이후에도 동작을 계속하게 된다.
둘째, 문제가 생겼을 경우에 exception에 대한 정보가 사라지기 때문에 debug 하기가 힘들다.
exception 객체는 문제가 생기면 어디서 어떤 문제가 생겼는지 상세한 정보들을 담고 있다.
exception이 발생한다면 적어도 오류 메세지와 stack trace 정도는 log에 남겨야 한다.

- better code
try {
    doStuff();
} catch(Exception e) {
    throw new MyRuntimeException("Could not do stuff because: "+ e.getMessage, e);
}


# Catching to log

- stupid code
try {
    ...
} catch(ExceptionA e) {
    log.error(e.getMessage(), e);
    throw e;
} catch(ExceptionB e) {
    log.error(e.getMessage(), e);
    throw e;
}

위 코드를 보면 catch 절에서 단지 log만 남기고 원래 exception을 throw 하는 것을 볼 수 있다.
이는 멍청한 짓이다. 이 코드가 속한 method를 호출한 곳에서 해당 exception에 대해서 처리하도록(log를 남기던지 등)
try/cat 전체를 옮기는 것이 낫겠다.


# Incomplete exception handling

- this instable code can leak file handles
try {
    is = new FileInputStream(inFile);
    os = new FileOutputStream(outFile);
} finally {
    try {
        is.close();
        os.close();
    } catch(IOException e) {
        /* we can't do anything */
    }
}

stream이 close 되지 않으면, 해당 OS 에서는 사용했던 resource를 해제할 수 없게 된다.
이 코드에서는 stream 두개를 닫기 위해서 finally 절에 close 를 넣었는데,
만약에 is.close()에서 IOException이 발생하면 os.close()는 수행되지 않게 된다.
두개의 close 는 각각 try/cat 절로 쌓여야 한다.
게다가, input stream을 생성하다가 exception이 발생하면 os 가 null이 되므로 os.close() 에서는 NullPointerException이 발생할 것이다.

- stable codes that frees all resources
try {
    is = new FileInputStream(inFile);
    os = new FileOutputStream(outFile);
} finally {
    try { if (is != null) is.close(); } catch(IOException e) {/* we can't do anything */}
    try { if (os != null) os.close(); } catch(IOException e) {/* we can't do anything */}
}


# The exception that never happens

- bad code
try {
  ... do risky stuff ...
} catch(SomeException e) {
  // never happens
}
... do some more ...

위 코드에서 개발자는 귀차니즘으로 인해 이 코드를 호출한 곳으로 어떤 exception을 다시 던지기를 원치 않는다.
게다가 exception이 절대 발생하지 않을 거라는 자만심에 빈 catch 절에 주석까지 달아놓았다.
하지만, exception이 절대 발생하지 않는다고 어떻게 장담할 수 있을까?
호출하는 method의 내용이 변경되기라도 한다면, 특정 경우에 exception이 발생하는데도 그런 경우에 대해서 생각을 못하고 있다면 어떨까?
만약 문제가 생기면 try/catch 이후의 코드들이 수행이 되면서 잘못된 결과를 초래할 것이다.  exception은 절대로 잡을 수 없게 될 것이다.
위 코드는 runtime exception을 던짐으로해서 보다 안정적이 될 필요가 있다.
그럼으로써 assertion의 역할도 하고 "crash early" 원칙을 따르는 것이기도 하다.
개발자는 모든 경우에 대해서 생각해야 하고, 작성한 코드가 완벽하다는 가정은 애시당초 틀렸다는 것을 가정해야 하며,
오류가 발생한 지점의 try/catch 이후의 코드는 실행되지 않도록 해야 한다.
이렇게 처리한 후에 exception이 나지 않으면 그만이고, exeption이 발생하면 다행인 것이다.

- more reliable code
try {
  ... do risky stuff ...
} catch(SomeException e) {
  // never happens hopefully
  throw new IllegalStateException(e.getMessage(), e); // crash early, passing all information
}
... do some more ...


# The transient trap

- wrong code
public class A implements Serializable {
    private String someState;
    private transient Log log = LogFactory.getLog(getClass());
   
    public void f() {
        log.debug("enter f");
        ...
    }
}

Log 객체는 직렬화되지 않았다. 개발자도 이를 알고 직렬화되지 않도록 log 필드를 transient로 정의했다.
그러나, 이 변수의 초기화는 class의 initializer에서 이루어진다.
역직렬화시에는 initializers와 constructors는 수행이 되지 않는다.
역직렬화된 객체에는 log 변수가 null로 되어 있고, f() method에서는 NullPointerException을 야기할 것이다.
transient 변수는 절대 class 초기화에 사용하지 말아야 하며, 이 문제는 static 변수나 로컬 변수로 사용함으로써 해결할 수 있다.

- correct code
public class A implements Serializable {
    private String someState;
    private static Log log = LogFactory.getLog(getClass());
   
    public void f() {
        log.debug("enter f");
        ...
    }
}

public class A implements Serializable {
    private String someState;
   
    public void f() {
        Log log = LogFactory.getLog(getClass());
        log.debug("enter f");
        ...
    }
}


# Overkill initialization

- overkill
public class B {
    private int count = 0;
    private String name = null;
    private boolean important = false;
}

위 코드를 작성한 사람은 C로 프로그래밍하는데 익숙한 사람이어서, 모든 변수가 적절한 값으로 초기화 되기를 바란다.
Java 에서는 member 변수의 경우에 0, null, false 등으로 자동으로 초기화가 된다.
따라서 위 코드는 아래와 깉이 고치는 것이 좋다.

- faster and slicker and still the same
public class B {
    private int count;
    private String name;
    private boolean important;
}


# Too much static

- prevents class collection
private static Log LOG = LogFactory.getLog(MyClass.class);
많은 개발자들은 Log instance를 static 멤버 변수로 사용하는데 이는 디자인 관점에서 보면 맞는다.
하지만, 이는 절대 그럴 필요가 없다.
LogFactory class는 이미 Log 객체에 대한 static 참조를 갖고 있어서 getLog() 를 호출하면 hash table에서 찾게 되고, 비용도 비싸지 않다.
하지만, 모든 class 들이 static 멤버 변수를 갖도록 하는 것은 class들에 대한 gc를 방해하기 때문에 몹쓸 짓이다.
단지, 드물게 위에 위에 설명했던 직렬화 class 같은 경우에는 static 변수를 사용하는 것이 방법이다.
이는 클래스명을 찾기 위해 getClass()를 사용할 수 없게 만든다.
대신에 클래스명을 하드코딩 해야만 하고, 로그 개체를 상속 클래스와 공유할 수 없어서 이를 private으로 선언해야만 한다.

- lets the class unload when not used
protected Log log = LogFactory.getLog(getClass());


# Chosing the wrong class loader

- rarely uses the right class loader
Class clazz = Class.forName(name);

위 코드는 현재 class를 load하는 class loader를 사용한다.
이것은 다른 class를 동적으로 load 하는데 맘대로 되지 않는다.
Servlet engine, Java Webstart 또는 Application server 같은 환경에서는 확실히 잘못된 것이다.
이 코드는 실행되는 환경에 따라 다르게 동작한다.
컨텍스트 클래스 로더를 사용하는 환경에서는 어플리케이션이 자신의 클래스를 로드하기 위해 사용해야 하는 클래스 로더를 제공한다.

- mostly uses the right class loader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) cl = getClass().getClassLoader(); // fallback
Class clazz = cl.loadClass(name);


# Poor use of reflection

- dangerous
Class beanClass = ...
if (beanClass.newInstance() instanceof TestBean) ...

이 개발자는 reflection API와 싸움중이다.
상속 여부에 대해서 체크하는 방법을 알아야 되는데 아직 방법을 찾지 못했다.
그래서 새로운 instance를 하나 생성하고 instanceof 연산자를 사용하곤 했다.
class의 instance를 생성하는 것이 얼마나 위험한지 모른다.
이 class가 뭐하는 class 인지도 모른다. 객체 생성 비용이 비쌀 수도 있다. 기본 생성자가 없을 수도 있다.
exception을 던지기라도 한다면.
이를 해결하는 방법은 Class.isAssignableFro(Class) method를 사용하는 것이다.
이것은 instanceof의 역방향이다.

- correct
Class beanClass = ...
if (TestBean.class.isAssignableFrom(beanClass)) ...


# Synchronization overkill

- synchronization overkill
Collection l = new Vector();
for (...) {
   l.add(object);
}

Vector는 동기화된 ArrayList이고, Hashtable도 동기화된 HashMap 이다.
두 class들은 객체를 동시에 접근하는 일이 있을 경우에만 사용 되어져야 한다.
동기화가 필요없는 지역 임시 변수에 이것들을 사용하는 것은 오버이며, 성능을 꽤 떨어뜨릴 수도 있다.

- no synchronization
Collection l = new ArrayList();
for (...) {
   l.add(object);
}


# Wrong list type

- 초보 개발자들은 적절한 list 유형을 결정하는데 어려움을 겪는다.
그들은 보통 Vector, ArrayList, LinkedList 중에 아무거나 골라 사용한다.
하지만, 성능 향상에 대한 문제를 고민해 봐야 한다.
하지만, 각 classe들의 구현은 adding, iterating, accessing 에 따라서 상당히 다르다.
아래 간략하게 표로 정리했는데, 이 표에서 Vector는 동기화 때문에 약간 느린거 말고는 ArrayList와 비슷하기때문에 생략했다.

                                   ArrayList                LinkedList
add (append)        O(1) or ~O(log(n))             O(1)
insert (middle)       O(n) or ~O(n*log(n))         O(1)
iterate                 O(n)                                 O(n)
get by index         O(1)                                 O(n)

linked list가 insert 작업에 대한 성능이 지속적인 반면, list entry마다 보다 많은 메모리를 사용하고 있다.
ArrayList의 insert 성능은 초기화 사이즈가 적당한지, insert 도중에 size가 늘어나는지에 따라 좌우된다.
확장은 2의 지수 형식으로 늘어나며, O(log(n))의 비용이 든다.
지수 형태의 확장은 정말 필요한 양보다 많은 양의 메모리를 사용하게 된다.
리스트 크기의 재설정은 또한 응답시간을 느리게 만들고 리스트가 크다면 major 가비지 컬렉션을 일으킬 수도 있다.
list에서 Iterator를 사용하는 것은 그리 큰 비용이 들지 않지만, index를 사용하는 것은 아주 느리다.


# I love arrays

/**
 * @returns [1]: Location, [2]: Customer, [3]: Incident
 */
Object[] getDetails(int id) {...

문서화되었다 하더라도, 이런 종류의 메소드의 값 반환은 다루기 난처하고 에러를 쉽게 유발한다.
반드시 이 값들을 담는 작은 클래스를 선언해서 사용해야 한다. 이는 C의 struct와 비슷하다.

Details getDetails(int id) {...}

private class Details {
    public Location location;
    public Customer customer;
    public Incident incident;
}


# Premature object decomposition

public void notify(Person p) {
    ...
    sendMail(p.getName(), p.getFirstName(), p.getEmail());
    ...
}
class PhoneBook {
    String lookup(String employeeId) {
        Employee emp = ...
        return emp.getPhone();
    }
}

첫번째 예제에서 단지 method에 상태를 전달하기 위해 객체를 분해하는 건 참 쓰잘데기 없는 일이다.
두번째 에제에서 이 method의 사용은 매우 제한적이다.
object 자체를 넘기도록 디자인되어야 한다.

public void notify(Person p) {
    ...
    sendMail(p);
    ...
}
class EmployeeDirectory {
    Employee lookup(String employeeId) {
        Employee emp = ...
        return emp;
    }
}


# Modifying setters

private String name;

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

public void String getName() {
    return this.name;
}

이 개발자는 사용자가 입력한 이름 값의 앞/뒤 빈 공백에 대해서 처리를 하기 위해서
bean 의 setter method에서 공백들을 없애는 처리를 했다.
하지만, bean은 단지 값을 저장하기 위한 공간이지 Business Logic이 아니다.
bean 에서는 데이터를 변경하면 안된다. 값을 변경하려면 값이 입력되는 곳에서 차단하거나, 빈 공백을 원하지 않는 곳에서 처리해야 한다.

person.setName(textInput.getText().trim());


# Unnecessary Calendar

Calendar cal = new GregorianCalender(TimeZone.getTimeZone("Europe/Zurich"));
cal.setTime(date);
cal.add(Calendar.HOUR_OF_DAY, 8);
date = cal.getTime();

date, time, calendar, time zone에 대한 혼동이 있는 개발자들의 의한 전형적인 실수이다.
Date에 8시간을 더하기 위해 Calendar는 필요 없다. time zone도 상관이 없다.
(Think about is if you don't understand this!) However if we wanted to add days (not hours) we would need a Calendar,
하지만, 시간이 아니라 날짜를 더하기 위해서는 Calendar가 필요하다.
because we don't know the length of a day for sure (on DST change days may have 23 or 25 hours).
왜냐하면, 우리는 정확히 하루의 길이가 어떻게 되는지 모르기 때문이다.(DST- Daylight-Saving Time- 상에서는 23시간이 되기도 하고 25시간이 되기도 한다)

date = new Date(date.getTime() + 8L * 3600L * 1000L); // add 8 hrs

# Relying on the default TimeZone

Calendar cal = new GregorianCalendar();
cal.setTime(date);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
Date startOfDay = cal.getTime();

개발자는 하루의 시작( 0시 0분)을 계산하고 싶어한다. 이 개발자는 Calendar의 밀리초를 놓쳤다.
하지만, 진짜 큰 실수는 Calendar 객체에 TimeZone을 설정하지 않은 것이다.
TimeZone을 설정하지 않으면 Calendar는 기본 time zone을 사용한다.
이는 Desktop 프로그램에서는 괜찮을지 모르나, server-side 프로그램에서는 원하는 결과가 나오지 않을 수도 있다.
상하이에서의 0시 0분은 런던에서의 0시 0분과 전혀 다른 시간이다.
개발자는 날짜 계산을위해 타당한 time zone을 사용하는지 확인해볼 필요가 있다.

Calendar cal = new GregorianCalendar(user.getTimeZone());
cal.setTime(date);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
Date startOfDay = cal.getTime();


# Time zone "conversion"

- stupid
public static Date convertTz(Date date, TimeZone tz) {
  Calendar cal = Calendar.getInstance();
  cal.setTimeZone(TimeZone.getTimeZone("UTC"));
  cal.setTime(date);
  cal.setTimeZone(tz);
  return cal.getTime();
}

만약 위 코드가 뭔가 쓸모있다고 생각한다면 가서 article about time(http://www.odi.ch/prog/design/datetime.php)을 다시 읽어보라.
이 개발자는 위 기사를 읽지 않았고, 그의 날짜의 time zone을 수정하려고 노력하고 있다.
사실 그 method는 아무 일도 안한다. 반환된 Date는 parameter로 넘겼단 값과 다르지 않을 것이다.
왜냐하면, Date는 time zone에 대한 정보를 갖고 있지 않기 때문이다.
항상 UTC를 따른다. 그리고, Calendar의 getTime()/setTime() method는 항상 UTC와 실제 time zone 사이의 변환을 한다.

# Calling Date.setTime()

- not recommended
account.changePassword(oldPass, newPass);
Date lastmod = account.getLastModified();
lastmod.setTime(System.currentTimeMillis());

위 코드는 account entity의 최근 수정 날짜를 변경하고 있다.
프로그래머는 보수적이고, 새로운 Date 객체를 생성하는 것을 피하고 있다.
대신에 존재하는 Date instance를 수정하기 위해 setTime() method를 사용한다.

사실 위 코드에 잘못된 것은 없다. 하지만, 추천하고 싶지는 않다.
Date 객체는 일반적으로 조심스럽게 전달된다. 같은 Date instance가 setter에서 copy를 하지 않는 여러개의 객체들에 전달이 될 수 있다.
Date는 종종 원시형 처럼 사용된다. 그래서, Date instance를 수정하면, 이 instance를 사용하는 다른 객체에서 기대하지 않은 일이 발생할 수 있다.
물론, 프로그래머가 OO 원칙에 입각한 코드를 작성한다면, 객체가 그 자신의 본질적인 Date instance를 바깥 세상에 노출시키는 것이 깨끗한 디자인은 아니다.
대부분의 Java 코드에서 그들의 setter 에서 복제하지 않고, 단지 Date 참조를 복사하고 있다.
모든 프로그래머는 Date 를  불변(immutable) 로 취급해서, 존재하는 instance에 수정을 가하면 안된다.
이는 오직 성능 향상의 이유로만 이루어져야 한다. 한편으로는 간단한 long의 사용이 좋을 수도 있다.


- real world code
account.changePassword(oldPass, newPass);
account.setLastModified(new Date());

# Not noticing overflows

- overflow
public int getFileSize(File f) {
  long l = f.length();
  return (int) l;
}

이 개발자는 무슨 이유에서인지 파일의 크기를 반환하는 method에서 반환값을 long 대신에 int를 사용했다.
이 코드는 파일의 크기가 2GB를 넘을 경우 제대로 작동하지 않는다.
작은 값으로의 cast일 경우에는 반드시 overflow에 대해서 체크하고 exception을 던지도록 해야 한다.

- no overflow
public int getFileSize(File f) {
  long l = f.length();
  if (l > Integer.MAX_VALUE) throw new IllegalStateException("int overflow");
  return (int) l;
}

다른 버전의 overflow 버그는 아래와 같다.

long a = System.currentTimeMillis();
long b = a + 100;
System.out.println((int) b-a);
System.out.println((int) (b-a));


# Storing money in floating point variables

- broken sum
float total = 0.0f;
for (OrderLine line : lines) {
  total += line.price * line.count;
}

많은 개발자들이 위와 같이 loop을 코딩한다.
하나에 0.3$ 짜리 물건을 100개 계산한다고 하면, total 에 계산되어지는 값은 정확히 29.999971 일 것이다.
개발자들은 이 이상한 동작에 대해서 알아채고, 보다 정확한 double로 변경하여 30.000001192092896 값을 얻는다.
이런 결과는 사람과 컴퓨터가 숫자에 대해서 인식하는 방법의 차이 때문이다.
이는 소숫점자리의 돈 계산시에 가장 귀찮은 형태로 항상 발생한다.
따라서, 돈 같은 경우는 계산이 필요 없는 경우라면 text 로 저장되어야 한다.
계산시에 text 표현을 integer로 안전하게 변환해야 한다.
Double.parseDouble(amount) * 100 사용에 주의하라.
차라리 String의 소숫점 자리에 0을 붙이거나, 소숫점을 없애고, Integer.parseInt를 이용해서 integer로 변환하라.
덧붙일 길이는 정밀도 요구에 따라 달라진다.


- correct sum
int totalcents = 0;
MoneyUtils money = new MoneyUtils(2);
for (OrderLine line : lines) {
  totalcents += money.toCents(line.priceString) * line.count;
}
String total = money.fromCents(totalcents);


public class MoneyUtils {
  private int precision;
  private String zero;
 
  public MoneyUtils(int precision) {
      this.precision = precision;
      char[] z = new char[precision];
      for (int i=0; i<z.length; i++) z[i] = '0';
      zero = new String(z);
  }
 
  public int toCents(String amount) {
      String[] parts = amount.split("\\.");
      String padded;
      switch (parts.length) {
          case 1:
              padded = parts[0] + zero.substring(0, precision);
              break;
        
          case 2:
              int end = precision - parts[1].length();
              if (end < 0) throw new IllegalArgumentException("precision not enough");
              padded = parts[0] + parts[1] + zero.substring(0, end);
              break;
             
          default: throw new IllegalArgumentException("too many dots");
      }
      return Integer.parseInt(padded);
  }
 
  public String fromCents(int cents) {
      String amount = String.valueOf(cents);
      return amount.substring(0, amount.length() - precision)
             +"."+ amount.substring(amount.length() - precision, amount.length());
  }
}


# Abusing finalize()

- abusive code
public class FileBackedCache {
   private File backingStore;
  
   ...
  
   protected void finalize() throws IOException {
      if (backingStore != null) {
        backingStore.close();
        backingStore = null;
      }
   }
}

이 class에서는 file handle을 해제하기 위해서 finalize() method를 사용했다.
문제는 이 method가 언제 호출이 되는지 모른다는 것이다.
이 method는 garbage collector에 의해서 호출이 된다.
만약에 file handler을 다 쓰고 난 후에 이 method가 호출되기를 원할것이다
하지만, 이 method는 GC가 프로그램이 heap을 다 사용했을 경우에 호출하게 되어 있다.
garbage collector는 단지 메모리를 관리한다. 하지만, 메모리 관리 이외의, 다른 resource를 관리하도록 남용되어서는 안된다.

- proper code
public class FileBackedCache {
   private File backingStore;
  
   ...
  
   public void close() throws IOException {
      if (backingStore != null) {
        backingStore.close();
        backingStore = null;
      }
   }
}



# 출처 : http://www.odi.ch/prog/design/newbies.php#31

:
Posted by 뽀기
2007. 8. 14. 10:57

JDK 5.0의 새로운 기능 Annotation 그거/Java2007. 8. 14. 10:57

o annotation
  : source code에 사용하는 meta-tag

  @MyAnnotation(doSomething="What to do")
  public void myMethod() {
      ....
  }

  - JDK5.0에서 사용가능한 2가지 Annotation

    Simple Annotations : custom annotation type은 생성할 수 없는 기본으로 제공되는 Annotation.
    Meta Annotations : annotation-type 선언에 대한 annotation. 즉, annotation의 annotation

  - Simple Annotations

    기본으로 제공되는 annotation에는 3가지가 있다.

    1) Override
    super class의 method를 꼭 override 해야 됨을 표시.
    이 annotation을 사용한 method가 super class의 method를 override 하지 않으면, compile시 오류 발생

    Example:

    @Override
    public Strig tostring() {
       return super.toString() + " Testing annotation name : 'Override'";
    }

    위 예제처럼 Object class의 toString() method를 override하기 위해 annotation @Override를 사용한 후에 compile을 하면
    아래와 같은 오류가 발생한다.

       Compiling 1 source file to D:tempNew Folder (2)
                                     TestJavaApplication1buildclasses
       D:tempNew Folder (2)TestJavaApplication1srctest
          myannotationTest_Override.java:24: method does not override
                       a method from its superclass
       @Override
       1 error

 2) Deprecated
    code에 deprecated된 method등을 사용시에 compiler가 deprecated 된 부분에 대해서 warning을 보여주도록 함.

    Example:

    public class TestDeprecated {
       
@Deprecated
        public void doSomething() {
            System.out.println("Testing annotation name : 'Deprecated'");
        }
    }

    public class TestAnnotations {
        public static void main(String[] args) {
            new TestAnnotations();
        }

        public TestAnnotations() {
            TestDeprecated t2 = new TestDeprecated();
                  t2.doSomething();
        }
    }

    위 소스를 컴파일 해보면 둘 다 문제없이 compile이 되지만, TestAnnotations class는 compile시
    t2.doSomething() 부분에서 deprecated 된 method가 사용되고 있다고 warning을 보여준다.

 3) SuppressWarnings
    compiler에게 warning에 대해서 무시하도록 지시.

    Example:

    public class TestAnnotations {
        public static void main(String[] args) {
            new TestAnnotations().doSomeTestNow();
        }

      
@SuppressWarnings("deprecation")
       public void doSomeTestNow() {
           TestDeprecated t2 = new TestDeprecated();
           t2.doSomething();
       }
    }

   Deprecated annotation에서 사용했던 예제를 위와 같이 수정한 후에 compile 하면,
   전에 보였던 warning message가 보이지 않는 것을 확인할 수 있다.

o annotation type
  : annotation을 정의하는 type. custom annotation을 정의할 때 사용

  public @interface MyAnnotation {
      String toSomething();
  }

  - 3 annotation types

 1) Marker annotation
    annotation에 element 없이 이름 자체만으로 사용하는 경우

    Example:

       public @interface MyAnnotation {
    }

    Usage:

    @MyAnnotation
    public void myMethod() {
    }

 2) Single-Element
    annotation에 element를 하나 사용하는 경우
    data=value 또는 value 만 () 안에 사용

    Example:

    public @interface MyAnnotation {
      String doSomething();
      }

      Usage:

    @MyAnnotation("What to do")
    public void myMethod() {
        ....
    }

 3) Full-value or Multi-value
    annotation에 data를 여러개 사용하는 경우
    data=value 형식으로 ()안에 사용

    Example:

    public @interface MyAnnotation {
     int count;
     String date;
        String doSomething();
    }

    Usage:

    @MyAnnotation(doSomething="What to do", count=1, date="09-09-2005")
    public void myMethod() {
        ....
    }


  - Annotation type 정의 규칙
    Annotation 선언시 '@'다음에 interface keyword, '@' 다음에 annotation 이름의 형식으로 '@'로 시작해야 한다.
    method 선언에 대해서는 parameter, throws 절이 있어선 안된다.
    method의 반환 타입은 다음 중 하나여야 한다.
      primitives
      String
      Class
      enum
      array of the above types


- 4 meta-annotation

 1) Target

class의 어떤 element에 annotation이 적용 가능한지를 나타내는 annotation

   - Target annotation의 value
     @Target(Element.TYPE)                : class의 어떤 element 에나 적용 가능함
     @Target(Element.FIELD)               : field 또는 property에만 적용 가능함
     @Target(Element.METHOD)              : method level에서 적용 가능함
     @Target(Element.PARAMETER)           : method의 parameter에 적용 가능함
     @Target(Element.CONSTRUCTOR)         : 생성자에 적용 가능함
     @Target(Element.LOCAL_VARIABLE)      : local variables에 적용 가능함
     @Target(Element.ANNOTATION_TYPE)     : 선언된 유형 자체가 annotation 임을 가르킴

  
   Example:

   // annotation 정의
   @Target(ElementType.METHOD)
   public @interface Test_Target {
          public String doTestTarget();
   }

   // 위에서 정의한 annotation을 사용하는 class
   public class TetAnnotations {
       public static void main(String[] args) {
           new TestAnnotations().doTestTarget();
       }

      @Test_Target(doTestTarget="Hello World!")
       public void doTestTarget() {
           System.out.println("Testing Target Annotation");
       }
   }

   위 예제를 보면 Test_Target 이라는 custom annotation이 method level(ElementType.METHOD)로 정의되어 있기 때문에
      @Test_Target(doTestTarget="Hello World!") 를 아래와 같이 method가 아닌 field level로 이동하면 오류가 발생한다.

   public class TetAnnotations {
       @Test_Target(doTestTarget="Hello World!")
       private String str;

       public static void main(String[] args) {
           new TestAnnotations().doTestTarget();
       }

       public void doTestTarget() {
           System.out.println("Testing Target Annotation");
       }
   }


 2) Retention

annotation이 얼마 동안 유지되는지 여부를 나타내는 annotation

   - Retention annotation의 value
     RetentionPolicy.SOURCE    : source level에서 유지되며, compiler에서는 무시됨
     RetentionPolicy.CLASS     : compiler에 의해서 compile time에 유지됨
     RetentionPolicy.RUNTIME   : VM에 의해 유지되고, runtime에만 읽을 수 있음.

   Example:

   @Retention(RetentionPolicy.RUNTIME)
   public @interface TestRetention {
       String doSomeTestRetention();
   }


 3) Documented

   javadoc tool에 의해서 문서로 만들어져야 되는 부분을 나타냄


   Example:

   @Documented
   public @interface TestDocumented {
       String getDocumented();
   }

   public class TestAnnotations {
       public static void main(String[] args) {
        new TestAnnotations().doSomeTestRetention();
        new TestAnnotations().doSomeTestDocumented();
    }

    @TestRetention(doTestRetention='Hello retention test")
    public void doSomeTestRetention() {
        System.out.println("Test annotation 'Retention'");
    }

    @TestDocumented(doTestDocumented='Hello Documented")
    public void doSomeTestDocumented() {
        System.out.println("Test annotation 'Documented'");
    }
   }

 4) Inherited

 

  이 annotation이 붙으면 해당 class는 자동으로 상속된 class 임을 나타낸다.

   Example:

  @Inherited
   public @interface TestInherited {
       boolean isInherited() default true;
    String doSomething() default "Do What?";
   }

   @TestInherited
   public class TestAnnotations {
       // no implementation for TestInherited class
   }

   위 예를 보면 TestAnnotations class는 TestInherited의 method를 implements할 필요가 없다.
   @TestInherited annotation을 통해 자동으로 상속이 이루어지기 때문이다.

   old-style-java way

   public class TestAnnotations implements TestInherited {
       public boolean isInherited() {
        return false;
    }

    public String doSomething() {
        return "";
    }

    public boolean equals(Object obj) {
        return false;
    }

    public int hashCode() {
        return 0;
    }

    public String toString() {
        return "";
    }

    public Class annotationClass() {
        return null;
    }
   }

   위 예제를 보면 TestInherited의 method 뿐만 아니라, Object class의 method인 equals(), hashCode(), toString() method와
   java.lang.annotation.Annotation class의 method인 annotationClass() method까지 원하던 원하지 않던 override를 해야 한다.


 

'그거 > Java' 카테고리의 다른 글

[Java Quiz] 주민등록 번호와 바코드 유효성 체크  (0) 2007.09.12
Java Anti-Pattern  (2) 2007.08.20
JDK 5.0의 새로운 기능 Generics  (1) 2007.08.13
java Collection 들  (0) 2007.08.10
객체의 hashcode에 대한 고찰  (0) 2007.08.09
:
Posted by 뽀기
2007. 8. 13. 11:01

JDK 5.0의 새로운 기능 Generics 그거/Java2007. 8. 13. 11:01

# Generics

@ 특징
  1. Collection 사용시 Type-Safe 한 Collection을 사용할 수 있다.(Compile time type safety)
  2. 이전에는 Collection에 어떤 객체든 넣을 수 있었지만, 지정 객체만 넣음으로써
     실행시간이 아닌 컴파일 시점에 오류를 미리 체크할 수 있다.
  3. Collection 사용시 Casting 작업이 필요 없다.
 

  기존 코드

  List myList = new ArrayList();
  myList.add(new String("this is a test");
  myList.add(new Integer(100));

 // 실행 시간에 오류가 체크됨
  Integer integer = (Integer)myList.iterator().next();


  변경 코드

  List<Integer> myList = new ArrayList<Integer>();
 
 // 컴파일 시점에 오류가 체크됨
  myList.add(new String("this is a test");
  myList.add(new Integer(100));

  Integer integer = (Integer)myList.iterator().next();

@ 선언 방법 및 사용 방법

  < > 기호를 사용한다.
  E : 선언하여 사용할 때 들어가는 실제 데이터 타입

public class ArrayList<E> extends AbstractList<E> {
      public boolean add(E o) {
          ....
      }
  }

  String 객체만 담을 수 있는 ArrayList를 만들 경우 아래와 같이 한다.

  List<String> arrayList = new ArrayList<String>();

  이 경우 위 선언에서 봤던 E 는 String 이 되며, 위 선언은 아래와 같이 변경된다고 보면 된다.

  public class ArrayList<String> extends AbstractList<String> {
        public boolean add(String o) {
             ....
        }
  }

  또한, 아래와 같이 특정 부류(?)에 대해서만 적용이 가능한 선언을 할 수도 있다.

                                 Animal
                                  / \
                                 /   \
                                /     \
                              Dog      Cat

※ Dog, Cat class는 Animal class를 상속받음

  위와 같이 클래스가 구성되어 있고, 동물병원에서 현재 병원에 있는 모든 동물들의 목록을 뽑고자 한다면
  아래와 같이 하면 된다.

  // 현재 병원에 있는 동물들의 목록
  List<Animal> animals = new ArrayList<Animal>();
  animals.add(new Dog());
  animals.add(new Cat());
  animals.add(new Dog());
  animals.add(new Dog());
  animals.add(new Cat());
  animals.add(new Dog());

  public void printAnimals(ArrayList<Animal> list) {
      for( Animal a : list ) {
          System.out.println(a);
      }
  }

  이와 같이 printAnimals() 라는 method에 병원의 동물들의 목록이 들어가있는 animals(ArrayList<Animal>) 객체를 넘겨서
  각 동물들의 정보를 찍을 수 있다.

  그런데, 병원에 있는 동물들의 목록을 동물별로 따로 뽑고 싶을 경우는 어떻게 해야 될까?
  아래와 같이 강아지들만의 목록이 따로 있다고 하면,

  List<Dog> dogs = new ArrayList<Dog>();
  dogs.add(new Dog());
  dogs.add(new Dog());
  dogs.add(new Dog());
  dogs.add(new Dog());
  dogs.add(new Dog());

  위에서 정의된 printAnimals(ArrayList<Animal> list) method로는 강아지들 정보만 있는 dogs(ArrayList<Dog>) 객체를 넘겨서
  각 강아지에 대한 정보를 찍을 수 없다.

  왜냐하면, printAnimals(ArrayList<Animal> list) method는 인자로 ArrayList<Animal>만 받을 수 있도록 되어 있기 때문이다.
  그렇다고 해서 printAnimals(ArrayList<Dog> list) method를 추가한다고 해서 해결되지도 않는다.
  중복된 method라고 컴파일시 오류가 발생하기 때문이다.

  그러면 어떻게 해야 될까? 바로 아래와 같이 와일드카드를 이용해서 generics를 확장시켜야 한다.

  public <T extends Animal> void printAnimals(ArrayList<T> list) {
      for( Animal a : list ) {
          System.out.println(a);
      }
  }

  public void printAnimals(ArrayList<? extends Animal> list) {
      for( Animal a : list ) {
          System.out.println(a);
      }
  }

  T : 앞에서 말했던 E 와 같은 개념이다.
  extends : 확장, 상속, 구현 등의 개념으로써, 간단하게 'T extends Animal' 은 T는 Animal 부류이다(T의 하위 클래스) 라고 생각하면 된다.

  위의 두 가지 선언은 같은 기능을 하며,  아래와 같이 여러가지로 작동한다고 보면 된다.

  public void printAnimals(ArrayList<Dog> list) {
      for( Dog a : list ) {
          System.out.println(a);
      }
  }

  public void printAnimals(ArrayList<Cat> list) {
      for( Cat a : list ) {
          System.out.println(a);
      }
  }


  이제 이 메소드에 dogs(ArrayList<Dog>) 객체를 넘겨서 강아지들의 목록을 제대로 찍을 수 있게 됐다.

  여기서 한 가지 알아둘 것은 와일드카드를 사용하면 method의 인자로 넘어온 객체에 대해서 수정을 가할 수 없다는 것이다.

  즉, 아래와 같이 하면 컴파일시에 오류가 발생한다.

  public void printAnimals(ArrayList<Cat> list) {
      list.add(new Dog());
      for( Cat a : list ) {
          System.out.println(a);
      }
  }

 이 Generic을 다음과 같이 사용할 수도 있다.

public final class Pair<A,B> {
    public final A first;
    public final B second;

    public Pair(A first, B second) {
        this.first = first;
        this.second = second;
    }
}



위와 같이 Pair 라는 class를 정의할 때 generic을 사용하면,
아래와 같이, 비슷한 유형의 class를 여러개 생성하지 않고, collection 처럼 사용할 수 있다.

Pair<String, String> pair1 = new Pair<String, String>("Hello", "World");
Pair<File, Boolean> pair2 = new Pair<File, Boolean>(new File("C:\\"), false);
Pair<String, Integer> pair3 = new Pair<String, Integer>("Won", 1000);




  팁!

  public void doIt(ArrayList<? extends Animal> animals1, ArrayList<? extends Animal> animals2) {
      ....
  }

  는 아래와 같이 사용하면 된다.

  public <T extends Animal> void doIt(ArrayList<T> animals1, ArrayList<T> animals2) {
      ....
  }


@ 실행 소스

# Animal.java
  public abstract class Animal {
      pubilc void eat() {
      }
  }

  # Dog.java
  public class Dog extends Animal {
      public void bark() {
   }
  }

  # Cat.java
  public class Cat extends Animal {
      public void meow() {
   }
  }

  # TestGenerics1.java
  import java.util.ArrayList;

  public class TestGenerics2 {

      public void go() {

           ArrayList<Animal> animals = new ArrayList<Animal>();
           animals.add(new Dog());
           animals.add(new Cat());
           animals.add(new Dog());

           ArrayList<Dog> dogs = new ArrayList<Dog>();
            dogs.add(new Dog());
            dogs.add(new Dog());
            dogs.add(new Dog());

            takeAnimals(animals);
            takeAnimals(dogs);
      }

      // public void takeAnimals(ArrayList<? extends Animal> animals) {
      public <T extends Animal> void takeAnimals(ArrayList<T> animals) {
            // wildcard를 사용하면 아래처럼 method 매개변수에 의해 참조되는 목록에 손상이 갈 만한 작업을 할 수 없습니다.
            // animals.add(new Dog());
            for(Animal a: animals ) {
                  a.eat();
            }
      }

      public static void main(String[] args) {
            new TestGenerics2().go();
      }
  }

'그거 > Java' 카테고리의 다른 글

Java Anti-Pattern  (2) 2007.08.20
JDK 5.0의 새로운 기능 Annotation  (0) 2007.08.14
java Collection 들  (0) 2007.08.10
객체의 hashcode에 대한 고찰  (0) 2007.08.09
The Factory Method Pattern - Design Patterns in Java -  (1) 2007.05.30
:
Posted by 뽀기
2007. 8. 10. 10:57

java Collection 들 그거/Java2007. 8. 10. 10:57


List - 순서가 중요할 때
  인덱스 위치를 알고 있는 컬렉션
  목록(list)을 사용하면 어떤 원소가 그 목록의 어느 위치에 있는지 알 수 있습니다.
  같은 객체를 참조하는 원소가 두 개 이상 있어도 됩니다.

Set - 유일성이 중요할 때
  중복을 허용하지 않는 컬렉션
  집함(set)에서는 어떤 것이 이미 컬렉션에 들어있는 지를 알 수 있습니다.
  똑같은(또는 동치인 것으로 간주되는) 객체를 참조하는 원소가 두 개 이상 들어갈 수 없습니다.

Map - 키를 가지고 뭔가를 찾는 것이 중요할 때
  키-값 쌍을 사용하는 컬렉션
  맵(map) 에서는 주어진 키에 대응되는 값을 알고 있습니다.
  서로 다른 키로 같은 값을 참조하는 것은 가능하지만 같은 키가 여러 개 들어갈 수는 없습니다.
  보통 String을 키로 사용하지만(그렇게 하면 이름/값 속성 목록 등을 만들 수 있겠죠.)
  키로 사용ㅇ할 수 있는 객체의 유형에는 제한이 없습니다.


ArrayList
  : 크기 조절이 가능한 배열

TreeSet
  : 원소들을 정렬된 상태로 유지하며 원소가 중복되어 들어가지 않게 해줍니다.

HashMap
  : 원소들을 이름/값 쌍 형식으로 저장하고 접근할 수 있게 해줍니다.

LinkedList
  : 컬렉션 중간에서 원소를 추가하거나 삭제하는 작업을 더 빠르게 처리할 수 있게 해주는 컬렉션

HashSet
  : 컬렉션에 중복된 원소가 들어가지 않도록 해주고, 컬렉션 내에서 어떤 원소를 빠르게 찾을 수 있게 해줍니다.

LinkedHashMap
  : 일반 HashMap과 거의 똑같지만 원소(이름/값 쌍)가 삽입된 순서를 그대로 유지시켜줄 수도 있고,
    원소에 마지막으로 접근했던 순서를 기억하도록 설정할 수도 있다는 점이 다릅니다.



# 출처 :
Head First Java
:
Posted by 뽀기
2007. 8. 9. 18:26

객체의 hashcode에 대한 고찰 그거/Java2007. 8. 9. 18:26

# hashcode
  대부분 버전의 Java 에서 hashcode는 Heap에 있는 객체의 메모리 주소를 바탕으로 생성됩니다.
  따라서 서로 다른 객체가 같은 hashcode를 가질 수는 없습니다.

# 두 객체가 같다는 것의 의미는?
  1. reference 동치
    Heap에 있는 한 객체를 서로 다른 reference로 참조하는 경우
    두 reference에 대해서 hashCode() method를 호출하면 똑같은 결과가 나옵니다.
 

    String a = "test";
    String b = "test";

    System.out.println("# a.hashCode() : " + a.hashCode());
    System.out.println("# b.hashCode() : " + b.hashCode());

    # a.hashCode() : 3556498
    # b.hashCode() : 3556498


  2. 객체 동치
    Heap에 객체가 두 개 들어있고, 두 reference가 각 객체를 참조하지만 그 두 객체가 동치인 것으로 간주할 수 있는 경우
    Object class로부터 상속받은 hashCode() 와 equals() method를 모두 override 해야 합니다.
    hashCode() method를 override 하지 않으면 기본적으로 객체마다 유일한 hashcode 값을 반환하게 됩니다.

# hashCode() 와 equals() 에 관련된 규칙

  1. 두 객체가 같으면 반드시 같은 hashcode를 가져야 한다.
  2. 두 객체가 같으면 equals() method를 호출했을 때 true를 반환해야 한다.
     즉, a, b가 같으면 a.equals(b)와  b.equals(a) 둘 다 true 여야 한다.
  3. 두 객체의 hashcode 값이 같다고 해서 반드시 같은 것은 아니다.
     하지만 두 객체가 같으면 두 hashcode는 반드시 같아야 한다.
  4. equals()를 override 하면 반드시 hashCode()도 override 해야 한다.
  5. hashCode() 에서는 기본적으로 Heap에 있는 각 객체마다 서로 다른 값을 가지는 유일한 정수를 반환합니다.
     Class에서 hashCode() method를 override 하지 않으면 절대로 그 유형의 두 객체가 같은 것으로 간주될 수 없습니다.
  6. equals() method 에서는 기본적으로 == 연산자를 써서 객체를 비교합니다.
     즉, 두 reference가 Heap 에 있는 한 객체를 참조하는지를 확인하죠.
  따라서 equals()를 override 하지 않으면 절대 그 유형의 두 객체가 같은 것으로 간주될 수 없습니다.
  서로 다른 객체에 대한 reference에 들어있는 bit들이 같을 수가 없으니까요.
  7. a.equals(b)가 true 라면 a.hashCode() == b.hashCode() 도 성립합니다.
     하지만 a.hashCode() == b.hashCode() 가 성립해도 a.equals(b) 가 반드시 true 인 것은 아닙니다.

# hashCode() method 에서 사용하는 'Hashing Algorithm' 에서 서로 다른 객체들에 대해 같은 hashcode 값을 만들어낼 수 있기 때문에
  객체가 같지 않더라도 hashcode는 같을 수 있습니다.


 

'그거 > Java' 카테고리의 다른 글

JDK 5.0의 새로운 기능 Generics  (1) 2007.08.13
java Collection 들  (0) 2007.08.10
The Factory Method Pattern - Design Patterns in Java -  (1) 2007.05.30
MVC 기반 게시판 만들기  (2) 2007.04.27
Collection Framework  (0) 2007.04.23
:
Posted by 뽀기
2007. 8. 2. 18:10

TOAD에서 Explain plan 사용하기 그거/DB2007. 8. 2. 18:10


1. TOAD 에서 설정하기
   TOAD 툴 메뉴에서

View -> Options -> Oracle -> General -> Explain name for Table name


   위 메뉴로 이동한다.

   explain plan에서 사용할 plan table 명을 적는 곳이 있는데,
   초기에는 TOAD_PLAN_TABLE 이라고 적혀있는데,
   자신에 맞는 PLAN_TABLE 로 변경한다.

2. 사용할 plan table이 없다면 아래 스크립트를 이용하여 생성한다.

CREATE TABLE PLAN_TABLE
(
  STATEMENT_ID     VARCHAR2(30 BYTE),
  TIMESTAMP        DATE,
  REMARKS          VARCHAR2(80 BYTE),
  OPERATION        VARCHAR2(30 BYTE),
  OPTIONS          VARCHAR2(30 BYTE),
  OBJECT_NODE      VARCHAR2(128 BYTE),
  OBJECT_OWNER     VARCHAR2(30 BYTE),
  OBJECT_NAME      VARCHAR2(30 BYTE),
  OBJECT_INSTANCE  INTEGER,
  OBJECT_TYPE      VARCHAR2(30 BYTE),
  OPTIMIZER        VARCHAR2(255 BYTE),
  SEARCH_COLUMNS   NUMBER,
  ID               INTEGER,
  PARENT_ID        INTEGER,
  POSITION         INTEGER,
  COST             INTEGER,
  CARDINALITY      INTEGER,
  BYTES            INTEGER,
  OTHER_TAG        VARCHAR2(255 BYTE),
  PARTITION_START  VARCHAR2(255 BYTE),
  PARTITION_STOP   VARCHAR2(255 BYTE),
  PARTITION_ID     INTEGER,
  OTHER            LONG,
  DISTRIBUTION     VARCHAR2(30 BYTE)
);

위 테이블을 생성하면, 1번의 plan table 명에 테이블명을 적는다.
:
Posted by 뽀기
2007. 8. 2. 13:46

[Oracle] recursive select 그거/DB2007. 8. 2. 13:46

아래와 같은 구조의 테이블이 있다.
이 테이블에는 조직정보가 들어있는데
각 조직에는 상위 조직을 알 수 있는 정보(UP_SOSOK)가 있다.

이 테이블을 이용하여 조직도tree 구조로 select 해보자!!!

SQL> desc organization
 이름                                        널?          유형
 ----------------------------------------- -------- -------------
 ORG_NO                                    NOT NULL VARCHAR2(6)
 ORG_CAT                                   NOT NULL VARCHAR2(2)
 UP_SOSOK                                           VARCHAR2(6)
 ORG_NM                                             VARCHAR2(50)


테이블의 데이터를 select 해보자!

SQL> select * from organization;

ORG_NO ORG_NM               UP_SOS OR
------ -------------------- ------ --
IT0000 정보기술실                  03
IT1002 내부통제팀           IT0000 02
IT3012 차세대TFT            IT0000 02
IT1017 운영팀               IT0000 02
IT1003 업무서비스1팀        IT0000 02
IT1004 업무서비스2팀        IT0000 02
IT1007 고객서비스팀         IT0000 02
IT1008 경영서비스팀         IT0000 02
IT1019 시스템기술팀         IT0000 02
IT1018 서비스지원팀         IT0000 02
IT2003 화재특종             IT1003 01

ORG_NO ORG_NM               UP_SOS OR
------ -------------------- ------ --
IT2004 해상                 IT1003 01
IT2005 글로벌               IT1003 01
IT2008 자동차계약           IT1003 01
IT2009 보상                 IT1003 01
IT2027 손사                 IT1003 01
IT2006 장기신계약           IT1004 01
IT2026 장기보전             IT1004 01
IT2028 퇴직보험             IT1004 01
IT2013 인터넷               IT1007 01
IT2014 방카/채널            IT1007 01
IT2025 통합콜센타           IT1007 01

ORG_NO ORG_NM               UP_SOS OR
------ -------------------- ------ --
IT2010 마케팅지원           IT1008 01
IT2015 경리                 IT1008 01
IT2018 인사총무             IT1008 01
IT2029 투융자               IT1008 01
IT2030 경영정보             IT1008 01
IT2032 영업포탈             IT1008 01
IT2002 디자인               IT1018 01
IT2033 기획                 IT1018 01
IT2034 공통지원             IT1018 01
IT2019 운영지원             IT1019 01
IT2020 DW                   IT1019 01

33 개의 행이 선택되었습니다.

아.. 복잡하다. 조직도라고 하기에는 어설프다.

이제, 이 테이블의 데이터를 select 한 번만으로 리컬~~~~~~~~~~씨브 하게 뽑아보자!

SQL> select org_no, lpad(' ', 3*(1/org_cat)) || org_nm org_nm, up_sosok from organization start with org_no = 'IT0000' connect by prior org_no = up_sosok order siblings by up_sosok;

ORG_NO ORG_NM              UP_SOS
------ ------------------- ------
IT0000 정보기술실
IT1002  내부통제팀         IT0000
IT3012  차세대TFT          IT0000
IT1017  운영팀             IT0000
IT1003  업무서비스1팀      IT0000
IT2003    화재특종         IT1003
IT2004    해상             IT1003
IT2005    글로벌           IT1003
IT2008    자동차계약       IT1003
IT2009    보상             IT1003
IT2027    손사             IT1003
ORG_NO ORG_NM              UP_SOS
------ ------------------- ------
IT1004  업무서비스2팀      IT0000
IT2006    장기신계약       IT1004
IT2026    장기보전         IT1004
IT2028    퇴직보험         IT1004
IT1007  고객서비스팀       IT0000
IT2013    인터넷           IT1007
IT2014    방카/채널        IT1007
IT2025    통합콜센타       IT1007
IT1008  경영서비스팀       IT0000
IT2010    마케팅지원       IT1008
IT2015    경리             IT1008
ORG_NO ORG_NM              UP_SOS
------ ------------------- ------
IT2018    인사총무         IT1008
IT2029    투융자           IT1008
IT2030    경영정보         IT1008
IT2032    영업포탈         IT1008
IT1019  시스템기술팀       IT0000
IT2019    운영지원         IT1019
IT2020    DW               IT1019
IT1018  서비스지원팀       IT0000
IT2002    디자인           IT1018
IT2033    기획             IT1018
IT2034    공통지원         IT1018

33 개의 행이 선택되었습니다.
SQL>


여기서 주목해야 할 부분은 다음과 같다.

start with column_name = 'xxx'   -- 조회할 초기 정보
connect by prior column_name = column_name -- 리컬씨브 하게 돌아갈 조건
:
Posted by 뽀기
2007. 8. 1. 10:07

테이블의 FK 잠깐 꺼 놓는 방법 그거/DB2007. 8. 1. 10:07


# 끄기
alter table <테이블명> disable constraint <constraint 명>    

# 켜기
alter table <테이블명> enable constraint <constraint 명>


예전 회사에 있을 때는

이런거 다 알았었는데, 역시나 운영쪽의 일을 하다보니 이런건 다 까먹게 되는구나..

그래도 개발회사에 있을 때는 이것 저것 많이 찝쩍대기도 했는데.. 쩝..

:
Posted by 뽀기
2007. 7. 31. 16:54

css로 문자열 길이 잘라내기 그거/Tech2007. 7. 31. 16:54


 아래와 같이 하면 된다!

white-space:nowrap; text-overflow : ellipsis; overflow : hidden;


<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=1 cellPadding=0 bgColor=#000000 border=1>
<TR bgColor=#ffffff>
 <TD style="OVERFLOW: hidden; WORD-BREAK: break-all; WHITE-SPACE: nowrap; TEXT-OVERFLOW: ellipsis" width=100>
 
<nobr>
  This is a test hahaha merong good good good
 
</nobr>
 </TD>
</TR>
</TABLE>

그러면

This is a test hahaha merong good good good

이랬던 테이블이

This is a test hahaha merong good good good

이렇게 변한닷.

'그거 > Tech' 카테고리의 다른 글

Tomcat에서 JDBC driver 설정~!  (0) 2007.10.22
JavaScript를 이용해서 Class 작성하기  (0) 2007.10.16
Sun 서버 DNS 설정하기  (0) 2007.05.15
Solaris 시스템 모니터링 방법  (0) 2007.05.11
[펌] SUN 사용 설명서  (0) 2007.05.04
:
Posted by 뽀기

15 Exercises for Learning a new Programming Language by: Prashant N Mhatre
새로운 프로그래밍 언어를 배우는데 필요한 15가지 훈련 : Prashant N Mhatre

I've working knowledge of a bunch of programming languages but job demands to learn a new language frequently in a short time.
나는 프로그래밍하는 일을 하는데 종종 일 때문에 짧은 시간안에 새로운 언어를 배워야 하는 경우가 있다.

Instead of reading hundreds manual/book pages, I quickly read 10-15 pages of tutorial or primer.
나는 수백페이지의 매뉴얼이나 책을 읽는 대신, 10~15 페이지 분량의 튜토리얼이나 안내서를 읽는다.

(As you know google is the best search engine to look for such stuff).
(여러분도 알다시피 'google'은 이런 튜토리얼이나 안내서 따위를 검색하는데는 최상의 검색 엔진이다.)

I keep printed copy of the language syntax reference card handy.
나는 언어 문법 reference card를 인쇄해 놓는다.
(There are many reference cards available over internet. Type in 'language to learn' + 'reference card' in google.)
(인터넷에는 수 없이 많은 reference card들이 존재한다. 'google'에서 'language to learn' + 'reference card'를 검색해보라)


First of all, get familiar with Compiler, compiler option, editor shortcuts or integrated development environment (IDE).
무엇보다도, Compiler, compiler option, editor shortcuts, IDE 툴 등에 익숙해져야 한다.

Start with a simple 'Hello World' program.
간단한 'Hello World' 프로그램부터 시작한다.

Compile it. Use basic functionalities of debugger like setting break points, printing variable values, moving to the next or specific position, stopping debugger etc.
컴파일하고, 디버거로 break points 설정, 변수값 출력, 특정 위치로 이동 등의 기본적인 기능들을 사용해보라.


To grasp basics of a new language quickly, here are the exercises I use.
새로운 언어의 기초를 빨리 다지기 위해 내가 사용했던 훈련들이 있다.

Remember some programs may not good for beginners.
몇몇 프로그램들은 초보자들에게 적합하지 않을지도 모른다는 것을 알아두길 바란다.


(1) Display series of numbers (1,2,3,4, 5....etc) in an infinite loop.
    무한 loop 에서 1, 2, 3, 4, 5 등과 같이 연속된 숫자들을 출력하도록 해보라.

    The program should quit if someone hits a specific key (Say ESCAPE key).
 이 프로그램은 누군가가 특정 키-예를 들면 ESC키-를 입력하면 멈추도록 해야 한다.

(2) Fibonacci series, swapping two variables, finding maximum/minimum among a list of numbers.
    피보나치 수열, swap, 여러개의 숫자들 중에 최대/최소값 찾기등을 해보라.

(3) Accepting series of numbers, strings from keyboard and sorting them ascending, descending order.
    사용자가 연속된 숫자나 문자열들을 입력하도록 하고, 입력받은 데이터들을 sorting 해보라.

(4) Reynolds number is calculated using formula (D*v*rho)/mu Where D = Diameter, V= velocity, rho = density mu = viscosity
    Reynolds number는 다음과 같은 식으로 계산된다. (D*v*rho)/mu Where D = Diameter, V= velocity, rho = density mu = viscosity

    Write a program that will accept all values in appropriate units (Don't worry about unit conversion)
 적합한 단위의 모든 값들을 받아들일 수 있는 프로그램을 작성해보라.(단위 변환에 대해서는 신경쓰지 마라.)

 If number is < 2100, display Laminar flow, If it’s between 2100 and 4000 display 'Transient flow' and if more than '4000', display 'Turbulent Flow' (If, else, then...)
 숫자가 2100보다 작으면 'Laminar flow' 라고 출력하고, 숫자가 2100과 4000 사이이면 'Transient flow'라고 출력하고, 숫자가 4000 보다 크면 'Turbulent Flow' 라고 출력하라( if, else, then 을 공부할 수 있다.)

(5) Modify the above program such that it will ask for 'Do you want to calculate again (y/n), if you say 'y', it'll again ask the parameters.  If 'n', it'll exit. (Do while loop)
    위 프로그램을 '다시 계산하시겠습니까? (y/n)' 하고 물어서, 'y' 이면 값들을 다시 입력받도록 하고, 'n' 이면 프로그램이 종료되도록 수정해보라. ( Do while loop에 대해 공부할 수 있다.)

    While running the program give value mu = 0. See what happens.
 프로그램 수행중에 mu 값에 0을 넣어서 무슨 일이 벌어지는지 확인해보라.

 Does it give 'DIVIDE BY ZERO' error?
 '0으로 나누기' 오류가 발생하는가?

 Does it give 'Segmentation fault..core dump?'.
 '세그먼테이션 결함.. 코어 덤프' 오류가 발생하는가?

 How to handle this situation.
 이런 상황들을 어떻게 처리할 것인가.

 Is there something built in the language itself? (Exception Handling)
 언어 자체에 이런 것들을 처리하기 위한 장치가 마련되어 있는가? (예외 처리에 대해서 공부할 수 있다.)

(6) Scientific calculator supporting addition, subtraction, multiplication, division, square-root, square, cube, sin, cos, tan, Factorial, inverse, modulus
    덧셈, 뺄셈, 곱셉, 나눗셈, squre-root, squre, cube, sin, cos, tan, 팩토리얼, inverse, modulus 등을 지원하는 계산기를 만들어보라.

(7) Printing output in different formats (say rounding up to 5 decimal places, truncating after 4 decimal places, padding zeros to the right and left, right and left justification) (Input output operations)
 올림, 내림, 반올림, 반내림, 문자열의 왼쪽 또는 오른쪽에 0을 붙여보거나, 왼쪽 또는 오른쪽으로 문자열을 정렬해보거나 등의 출력 포맷을 다르게 해보라. (입/출력에 대해서 공부할 수 있다.)

(8) Open a text file and convert it into HTML file. (File operations/Strings)
    text 파일을 HTML 파일로 변환해보라. (파일 조작과 문자열에 대해서 공부할 수 있다.)

(9) Time and Date : Get system time and convert it in different formats 'DD-MON-YYYY', 'mm-dd-yyyy', 'dd/mm/yy' etc.
    시스템 시간을 얻어서 'DD-MON-YYYY', 'mm-dd-yyyy, 'dd/mm/yy' 등의 다른 포맷으로 변경해보라

(10) Create files with date and time stamp appended to the name
    파일을 생성할 때 시간과 날짜를 사용하여 파일명을 작성해보라.

(11) Input is HTML table, Remove all tags and put data in a comma/tab separated file.
    HTML 코드로 table을 입력하고, 입력된 데이터에서 모든 tag를 삭제한 후에, comma/tab 구분자를 이용하여 파일로 저장해보라.

(12) Extract uppercase words from a file, extract unique words
    파일에서 문자열을 읽어서 대문자인 문자나, unique한 단어들을 추출해보라.

(13) Implement word wrapping feature (Observe how word wrap works in windows 'notepad')
    단어 감싸기 기능을 구현해보라. (윈도우의 '메모장' 프로그램에서 단어 감싸기 기능이 어떻게 작동하는지 잘 살펴보라.)

(14) Adding/removing items in the beginning, middle and end of the array.
    배열에서 처음, 중간, 끝에 데이터를 추가하거나 삭제해보라.

(15) Are these features supported by your language: Operator overloading, virtual functions, references, pointers etc.
    연산자 오버로딩, virtual function, 참조, 포인터 등이 지원되는지 확인해보라.

About The Author

Prashant N Mhatre

I mainly developed software for Stock Markets and Chemical Instrustries. To learn more about programming, you could refer to the Programming page I maintain.
http://www.onesmartclick.com/programming/programming.html
Also Engineering page
http://www.onesmartclick.com/engineering/engineering.html
prashant_n_mhatre@yahoo.com


# 출처 : http://smartprogrammer.blogspot.com/2006/04/15-exercises-for-learning-new.html
# 번역 : 뽀기

'그거 > 기타' 카테고리의 다른 글

HP 이벤트 진행중~~  (0) 2008.07.02
IDE & ATA & SCSI  (0) 2007.11.05
AJAX 사이트  (0) 2007.10.26
FUD(Fear, Uncertainty, Doubt)  (0) 2007.05.31
당신의 블로그는 얼마짜리 입니까?  (0) 2007.05.03
:
Posted by 뽀기