'java'에 해당되는 글 2건

  1. 2010.06.22 Don't mess with the iterator in the for-each loop body... 4 by Dansoonie
  2. 2007.07.05 Parsing Strings in C# : Tokenizing strings while Ignoring White Spaces 2 by Dansoonie
I have learned a lesson at work about what I shouldn't do when using a for-each loop in Java. The for-each loop in Java is used as the following.

for (type var : arr) {
   // body of loop
}

// This is equivalent to the following for loop
for (int i=0; i<arr.length; i++) {
   type var = arr[i];
   // body of loop


The for-each loop structure can be also used with collections. So, it can also be used as the following.
for (type var : coll) {
   // body of loop
}

// This is equivalent to the following for loop using the iterator
for (iterator<type> iter=coll.iterator; iter.hasNext(); ) {
   type var = iter.next();
   // body of loop
}

At work I was working with collections, and had to do an iterative task on the items in the collection. After I finished my code, I tried to execute it and was surprised to see that I got a ConcurrentModificationException.

My shifu(master) at work, advised me that I might be carrying out some operations incorrectly among threads. However, I double checked my critical section points and I found myself doing everything right(probably???)... 

I was tracking down the point where the exception occurred, and finally realized I was doing something I wasn't supposed to do. And yet I was even more surprised to see I wasn't able to find any official technical reference to my mistake.

Here is what I was doing wrong. Some of you might have already figured out the point I'm trying to make from the title of this post. I think you aren't supposed to mess with the iterator of the collection that are using in the for loop.

So here is an example code that I have constructed to illustrate the problem.

public class IteratorTest {
private LinkedList<Integer> list = new LinkedList<Integer>();
public static void main(String[] argv) {
IteratorTest test = new IteratorTest();
test.initList();
test.test();
}

public void initList() {
for (int i=0; i<10; i++) {
list.add(new Integer(i));
}
}

public void test() {
for (Integer i : list) {
if (i.equals(3))
list.remove(list.get(1));
}
}
}

If you execute this short program, this will result in the following error message.
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.LinkedList$ListItr.checkForComodification(Unknown Source)
at java.util.LinkedList$ListItr.next(Unknown Source)
at IteratorTest.test(IteratorTest.java:18)
at IteratorTest.main(IteratorTest.java:9)

The remove method call on the list will mess with the iterator, and supposedly the ordering of the iterator for the for loop will be corrupted. I guess that is why Java throws a ConcurrentModificationException.

After having to get to know what my problem is, the behavior of how all the for-each loop and the iterator thing works seems so obvious. However, I just think that the for-each loop thing shouldn't be part of the Java language.

Basically, for many of the developers out in the wild, the for-each loop is just another option for the repetition control structure. But, the for-each loop is using the Iterator class for traversing all the items in a collection. While the Iterator class is not part of the language, but a class that is part of the core Java library which is created with the language (I'm not sure if I'm using the correct terminology, but I'm sure you'll get my point), I don't think Java should be supporting this feature. The syntax for the basic control structures should not be dependent to something which is not part of the language itself.

The for-each loop certainly makes the code more simpler and elegant. But I'm not sure what Sun had in mind when they were adding this feature. I'm just curious what people think about my position on the for-each loop and the iterator.

Posted by Dansoonie


 Introduction:
오늘 회사에서 일을 하다가 C#으로 문자열을 빈칸을 기준으로 여러개의 문자열로 나눠야 할 일이 생겼다. String class의 Split Method를 사용하면 가능할것으로 보였다. MSDN .Net Framework Reference(http://msdn2.microsoft.com/en-us/library/b873y76a.aspx)에 보면 만약에 "Darb\nSmarba"을 Split하게 되면 리턴값이 {"Darb", "Smarba"} 된다고 나와있다. 하지만 나는 "Darb\r\nSmarba\r\n"을 Split으로 넘겨줬더니 리턴값이 {"Darb", "", "Smarba", ""}이 되었다.
오늘 나는 C#에서 "Darb\r\nSmarba\r\n"를 {"Darb", "Smarba"} 으로 만드는 방법을 찾았다.



프로그래밍을 배웠지만 아직 사회에 나가서 전문적으로 프로그래밍을 하지 않는 우리나라 사람이라면 아직까지는 C가 C++보다 익숙하고, C++이 Java보다 더 익숙하고, Java가 기타 언어보다 더 익숙하리라 생각된다.


잘은 모르지만 우리나라는 아직까지 학교에서 미국과는 달리 프로그래밍은 C부터 가르친다는 말을 많이 듣는것 같다. 아니라면, 그만큼 나도 나이가 들었고, 요즘 대학생들과의 교류가 상당히 없어졌다는 얘기일것 같다...^^


어쨌든, 밀레니엄학번인 나는 미국 대학에서 프로그래밍을 배울때 C++부터 배웠다. 한때는 C++이 제일 편했지만, Java와 C#과 같은 C++보다는 좀더 객체지향적인 언어를 많이 사용하다보니 polymorphism같은 내용이나 여러가지 구체적이고 세부적인 C++의 내용은 상당히 많이 까먹은것 같다. 한때는 C++이 제일 편했지만, 최근에는 Java 나 C#과 같이 C++보다 객체지향 개념이 강한 언어를 선호하다보니 C++의 복잡한 문법들이 가물가물하다. 지금은 C++이 가장 편하다고 하지는 못하지만, 상당히 제한적이고 간단한 작업을 수행하는 프로그램을 짤때는 C++만큼 편한 언어는 없다.


내가 언급한 상당히 제한적이고 간단한 작업이란 텍스트 기반으로도 쉽게 짜고 사용할 수 있는 프로그램들을 말한다. 그중에 가장 많은 비중을 차지하는 작업이 Text의 parsing(문자열 분석(?))이 요구되는 작업들이다. 가령 역대 로또 당첨 번호들을 텍스트로 읽어들여서 분석하는 것과 같은 일이다... <-- 실제로 작업중이다


C로는 parsing 작업을 많이 해보지 않았으므로, 얼마나 그 작업을 용이하게 할 수 있는지 잘 모르겠다. 그리고 언어에 따른 parsing 작업의 편리함 정도는 아주 주관적인 것이므로 parsing 작업은 C++이 가장 편하다고 하지는 않겠다. 하지만 적어도 나에게는 가장 편하다.


오늘 문득 회사에서 일을 하다가 C#으로 문자열을 분석할 일이 생겼다. 어려운 작업은 아니었고, 문자열에 포함된 빈칸(White Space)를 기준으로 문자열을 나누고 싶었다. C++같으면 이 작업을 iostream으로부터 파생된 class와 extraction operator(>>)으로 쉽게 수행할 수 있다. 하지만 C++에서 사용하던 parsing 방법과 비슷한 효과를 낼 수 있는 방법을 못찾아 잠시 애를 먹었다. Google을 통해 그 방법을 찾아보고 어렵사리 그 방법을 터득하게 되었다. 그래서 parsing 작업이 C++에서는 익숙하지만 C#에서 익숙하지 않은 사람들을 위해 그 방법을 공유하고자 한다. 대단한건 아니지만, google에서 쉽게 찾지 못했으므로 여기에 기록해 두는것만으로도 많은 사람들에게 도움이 될것 같다는 생각에 충분히 가치있는 일이라고 생각된다.


그럼 본론으로 들어가서, C++처럼 iostream으로부터 파생된 클래스와 ">>" 연산자를 사용해서 하는 방법과 똑같은 방법은 없지만(C#에서는 operator overload가 가능한것으로 알고 있는데, 그래서 구현은 가능할것으로 보인다), 예전에 Java를 조금씩 하면서 사용했던 방법과 비슷한 방법으로 parsing하는 방법을 찾았다.


*앞으로 사용될 예제들에서 text_to_parse라는 문자열을 사용하겠다.
Java에는 StringTokenizer class를 사용해서 다음과 같이 문자열을 parsing할 수 있다.

     StringTokenizer st = new StringTokenizer(text_to_parse);
     while (st.hasMoreTokens()) {
         System.out.println(st.nextToken());
     }

StringTokenizer 객체는 string 객체를 매개변수로 받아들여 생성되고, 그 string object를 토큰(쉽게 말해 프로그래머가 원하는 문자열의 단위)으로 분리시켜준다. Default는 가장흔히 쓰이는 빈칸 기준으로 문자열을 나눠준다. 만약 다른 방법으로 나누고 싶다면 문자열 분리의 기준이 되는 문자열을 StringTokenizer 객체 생성시 추가로 매개변수로 넘겨주면 된다. 자세한 내용은 다음 링크를 참고하기 바람.
http://java.sun.com/j2se/1.4.2/docs/api/java/util/StringTokenizer.html


그래서 위의 예제는 빈칸을 기준으로 분리된 문자열을 차례차례 빈칸이 없을때까지 문자열을 나누어 출력하게 된다.


introduction에서 언급했듯이 C#에도 Java에서 StringTokenizer가 해주는 일이 비슷한 String class의 Split이라는 method가 있다. Split method 역시 매개변수로 문자열을 넘겨줘서 문자열을 나누는 기준을 정해줄 수 있다. 하지만 StringTokenizer와 마찬가지로 default는 빈칸이다. 그리고 MSDN .Net Framework Reference에 보면 만약에 문자열이 "Darb\nSmarba"을 Split하게 되면 결과가 {"Darb", "Smarba"} 나온다고 했지만, "Darb\r\nSmarba\r\n"을 넘겨주니 예상했던 것과 사뭇 다른 {"Darb", "", "Smarba", ""}라는 결과를 얻었다.


Split을 조금더 정밀하게 하기 위해서 Regular Expression을 써야 함을 알게 되었고 다음과 같은 방법으로 해결책을 찾았다.
string[] tokens;
tokens = Regex.Split(text_to_parse.TrimStart().TrimEnd(), "\\s+");
결국 "\s+"라는 regular expression을 써서 빈칸에 대한 정의를 좀더 명확히 해줌으로써 문자열에서 빈칸을 완전히 배제하고 문자열을 나눌 수 있었다.


그리고 오늘 Regular Expressions 사용할일 있을때 참고할만한 좋은 자료를 찾았다.
http://opencompany.org/download/regex-cheatsheet.pdf

Posted by Dansoonie