메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

한빛랩스 - 지식에 가능성을 머지하다 / 강의 콘텐츠 무료로 수강하시고 피드백을 남겨주세요. ▶︎

IT/모바일

자바 2SE 1.4의 열 가지 새로운 특징

한빛미디어

|

2002-04-23

|

by HANBIT

10,673

저자: 『Java In a Nutshell, 4th Edition』의 저자 데이비드 플라나긴, 역 이상훈

Java 1.4가 출시되었다. Java 1.4는 아주 새로운 버전으로 Java 1.3에 비해 62퍼센트 이상의 클래스와 인터페이스가 추가되었으며 말할 나위 없이 새로운 특징들을 많이 포함하고 있다. 이 기사에서 필자는 개인적으로 좋아하는 자바 1.4의 상위 10개 특징들을 참신한 것부터 아주 참신한 것 순으로 기술하였다(여기에서 기술한 자바 1.4의 특징은 모두 다 너무 너무 참신한 기술임에 틀림없다). 여러분은 『Java in a Nutshell』 개정 4판에서 이러한 특징에 대해 더 많은 것을 배울 수 있을 것이다. 필자가 이번 기사에서 선정한 10가지 특징은 이 책에서 발췌한 것임을 밝히는 바이다. 따라서 그래픽과 GUI 혹은 엔터프라이즈 특징에 대한 사항은 포함되지 않았다. 이러한 것들은 『JFC 인어넛셀』『Java Enterprise in a Nutshell』에서 다루는 주제들이기 때문이다.

이 기사의 제한된 분량으로 인해 기술할 10개의 새로운 특징을 완벽하게 동작하는 예제를 포함시킬 수는 없었지만, 이러한 기능이 어떻게 사용될 수 있는 가를 설명하는 포괄적인 코드는 일부 포함시켰다. 하지만 표시된 코드들을 컴파일하고 실행할 수 있다고 기대하지는 않는 게 좋겠다.

10. XML 파싱

자바에서 XML을 파싱하는 능력은 상당히 중요하다. 그렇지만 필자가 작성한 목록 상위에 랭크하지 않은 이유는 XML을 파싱하는 도구로서 JAXP API가 한동안 표준으로서 사용할 수 있었다는 이유 단 한가지 때문이다. 그렇지만 자바 1.4에서는 JAXP가 핵심 플랫폼으로서 추가되었다는 것이 새로운 점이며, XML을 도처에서 파싱할 수 있게 하기 때문에 중요하다. 기존 org.xml.sax와 그 하위 패키지, 그리고 org.w3c.dom 패키지와 함께 사용되던 JAXP 파싱 API가 SAX와 DOM파싱을 위한 클래스와 함께 javax.xml.parsers에서 정의되었으며 그들 패키지 또한 Java 1.4에 포함되었다. 여기서는 어떻게 JAXP API를 이용해서 XML 문서를 DOM 트리로 파싱하고 DOM API를 이용해서 그 문서에서 정보를 추출하는 가를 보이고자 한다.
import java.io.*; 
import javax.xml.parsers.*; 
import org.w3c.dom.*; 
 
File f; // 파싱할 파일. 어느 곳에선가 초기화 되었다고 가정하자

// DOM 파서를 만들기 위한 팩토리 오브젝트를 만든다
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 
// 이제 패토리를 이용해서 DOM 파서를 만들자 (이것은 DocumentBuilder라고도 알려져 있다) 
DocumentBuilder parser = factory.newDocumentBuilder(); 
// 파일을 파싱해서 그 내용을 나타내는 문서 트리를 작성한다
Document document = parser.parse(f); 
//  태그를 포함하는 모든 목록을 요청한다 
NodeList sections = document.getElementsByTagName("sect1"); 
//  요소를 한번에 하나씩 루프를 돌리고  태그의 내용을 추출한다 
int numSections = sections.getLength(); 
for(int i = 0; i < numSections; i++) { 
Element section = (Element)sections.item(i); // A <sect1> 
// 선택된 (<title> 요소)의 첫번째 자식 요소를 찾고, 포함하고 있는 본문을 출력한다 
Node title = section.getFirstChild(); 
while(title != null && title.getNodeType() != Node.ELEMENT_NODE)  
title = title.getNextSibling(); 
// 이 요소의 자식 텍스트 노드에 포함된 본문을 출력한다 
if (title!=null) System.out.println(title.getFirstChild().getNodeValue()); 
}
</pre></font>
<font class=hd2>9. XML 변환</font>
<br><br>
JAXP API에는 XML 파싱 뿐만 아니라 XML 변환에 사용되는 클래스도 포함되어 있다. <font color="#006699">javax.xml.transform</font>과 하부 패키지를 이용하면 XML 문서를 XML 태그 스트림, SAX 이벤트 스트림, 혹은 DOM 트리와 같은 표현에서 또 다른 표현으로 전환할 수 있고, 문서 내용에 XSLT 변환을 동시에 적용할 수도 있다. 이러한 작업은 위에서 본 XML 파싱 기능같이 매우 쉽게 사용할 수 있으며 자바 1.4가 출시되기 전에 표준확장으로 사용할 수 없었다면 보다 높은 순위에 오를 수도 있었을 것이다.
<!--  article_sidebar2.view begins  -->
<hr width="60%" size="1" noshade align=center>
<center>
<table width="60%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td valign="top" width="50%" align="center">
<a href="http://www.hanbitbook.co.kr/look.htm?book_code=020401-00003"><img src="http://www.hanbitbook.co.kr/images/_m_7283m.gif" alt="Java in a Nutshell, 4th Edition" border="0" hspace="20" /></a>
</td>
<td valign="top" width="50%" align=left><p><B>참고 도서</B></p>
<p><span class="title"><a href="http://www.hanbitbook.co.kr/look.htm?book_code=020401-00003"><B>Java in a Nutshell, 4th Edition</B></a></span><br />
<a href="http://www.hanbitbook.co.kr/look.htm?book_code=020401-00003&tags=au">David Flanagan</a>
</p>
<div class="secondary">
<!--  builds links to list in sidebar  -->
<a href="http://www.hanbitbook.co.kr/look.htm?book_code=020401-00003&tags=index">목차보기</a><br /> 
<a href="http://www.onjava.com/pub/a/onjava/excerpt/javanut4_ch04/index.html">샘플챕터보기</a>
</div>
</td>
</tr>
</table>
</center>
<hr width="60%" size="1" noshade align=center>
<!--  article_sidebar2.view ends  -->
아래 코드는 어떻게 DOM 문서트리에 XSLT 변환을 적용하고 XML 텍스트를 표준 출력으로 하듯이 변환된 문서를 출력하는 가를 보여준다.
<font color="#006699"><pre>
import javax.xml.transform.*; 
import javax.xml.transform.dom.*; 
import javax.xml.transform.stream.*; 
import org.w3c.dom.*; 
 
File xsltfile; // XSLT 파일로 어딘가에서 초기화되었다 
Document document; // 이미 이 DOM문서를 만들었거나 읽었다고 가정하자 
 
Source xsltSource = new StreamSource(xsltfile); // 변환을 위한 자원 
Source source = new DOMSource(document); 변환될 문서 
Result result = new StreamResult(System.out); // 결과문서를 저장할 곳 
 
// 팩토리 오브젝트에서 시작 
TransformerFactory tf = TransformerFactory.newInstance(); 
// 팩토리를 이용해서 XSLT 파일을 읽어서 텔플릿 오브젝트에 넣기 
Templates transformation = tf.newTemplates(xsltSource); 
// 템플릿 오브젝트로부터 변환 오브젝트를 생성 
Transformer transformer = transformation.newTransformer(); 
// 마지막으로 변환을 수행 
transformer.transform(source, result);
</pre></font>
<font class=hd2>8. Preferences</font>
<br><br>
자바 1.4의 새로운 특징에서 중요한 것은 <font color="#006699">java.util</font> 패키지를 구성하는 많은 유틸리티들이라고 할 수 있다. <font color="#006699">java.util.prefs</font>가 그러한 유틸리티 중에 하나인데 지속적으로 저장되고 사용자특성 preference를 조회하며 전반적인 시스템 애플리케이션을 구성하는 정보에 대한 지원을 담당한다. 핵심 클래스는 <font color="#006699">Preferences</font> 이고 패키지에 대한 명명된 preference의 집합을 나타낸다 (패키지의 모든 클래스는 고유한 동일 사용자특성과 시스템 전반의 <font color="#006699">Preferences</font> 오브젝트를 공유한다). <font color="#006699">Preferences</font>는 패키지의 사용자와 시스템 <font color="#006699">Preferences</font> 오브젝트를 획득하는 정적 메소드를 정의하고, 여러 형태의 명명된 값을 지정하고 조회하는 인스턴스 메소드를 정의한다. 다음 코드는 preferences 자신이 초기화 되었을 때 조회하는데 사용될 수 있는 애플리케이션이다. Preferences API의 좋은 점은 단순히 말해서 사용하기 쉽다는 것이다 그 동안의 설정파일을 다루는 고생은 이제 거의 사라진 것이다.
<font color="#006699"><pre>
// TextEditor 클래스를 포함하는 패키지의 사용자와 시스템 preferences에 대한 Preferences 오브젝트를 가져온다. 
Preferences userprefs = Preferences.userNodeForPackage(TextEditor.class); 
Preferences sysprefs = Preferences.systemNodeForPackage(TextEditor.class); 
 
// 정수값을 갖는 사용자 preferences를 조회한다. 
// 항상 기본 값을 전달하고 있음에 주목 
int width = userprefs.getInt("width", 80); 
// 기본값으로 시스템 preferences에서 사용하는 사용자 preference를 조회하자. 
String dictionary = userprefs.get("dictionary" sysprefs.get("dictionary", "default_dictionary"));
</pre></font>
<font class=hd2>7. Logging</font>
<br><br>
Logging은 <font color="#006699">java.util.logging</font> 패키지에서 정의된 또 하나의 중요한 유틸리티이다. 이것은 특별히 서버, 모니터링 되지 않거나 유저 인터페이스를 갖지 않는 애플리케이션에 유용하다. 대개 애플리케이션 개발자는 <font color="#006699">Level</font> 클래스에서 정의된 7단계의 오류 중에 어떤 것에 해당하는 로그 메시지를 생성한 애플리케이션 클래스나 패키지 이름과 대응되는 이름으로 <font color="#006699">Logger</font> 오브젝트를 사용할 수 있다. 
<br><br>
이러한 메시지들은 에러나 경고를 보고할 수도 있고 애플리케이션의 동작 중에 관심을 가질 만한 정보를 제공할 수도 있다. 또한 디버깅 정보와 프로그램 내의 중요한 메소드 실행을 추적할 수도 있다. 모든 경우에 이러한 메시지들이 실제로 생성되고 기록되는 것을 의미하지는 않는다. 콘솔, 파일, 네트워크 소켓 혹은 이것들의 조합 중 어디에 로그 메시지를 기록할 것인지 일반 텍스트 혹은 XML문서와 같은 포멧 형식과 오류를 기록할 레벨(정의된 오류 레벨보다 낮은 경우는 시스템 성능에 별 영향을 미치지 않고 작은 부하를 주는 작업을 통해 무시됨)을 정의하는 기본설정파일이 존재한다. 애플리케이션 시스템 관리자나 사용자들은 요구에 맞게 기본 설정을 변경할 수 있다.
<br><br>
<font color="#006699">java.util.logging</font> 패키지는 아주 유연하며 다양한 방법으로 사용될 수 있지만 대부분의 경우에 있어서 아주 쉽게 Logging API를 사용할 수 있다. 언제든지 필요한 경우 <font color="#006699">Logger</font> 라고 명명된 오브젝트는 <font color="#006699">Logger.getLogger()</font>라는 정적 메소드를 호출하여 얻을 수 있고 logger네임으로 클래스 혹은 패키지 명을 넘겨준다. 그런 다음에는 로그메시지를 생성하기 위해 <font color="#006699">Logger</font> 인스턴스 메소드를 다양하게 활용할 수 있다. 가장 쉬운 사용 예는 <font color="#006699">severe()</font>, <font color="#006699">warning()</font>, <font color="#006699">info()</font>와 <font color="#006699">debug()</font> 같은 오류레벨에 대응하는 이름의 메소드를 사용하는 것이다.
<font color="#006699"><pre>
import java.util.logging.*;

// 현패키지명을 따라 Logger오브젝트 이름을 가져온다
Logger logger = Logger.getLogger("com.davidflanagan.servers.pop");
logger.info("Starting server.");       
// 로그정보메시지를 남긴다.
ServerSocket ss;
try { ss = new ServerSocket(110); }
catch(Exception e) {                   
  // 예외사항 로그
  logger.log(Level.SEVERE, "Can"t bind port 110", e);  
  // 복잡한 로그 메시지
  logger.warning("Exiting");                           
  // 간단한 경고
  return;
}
logger.fine("got server socket");  
    // 낮은 수준의 충분히 자세한 디버깅 메시지
</pre></font>
<font class=hd2>6. 보안 소켓과 HTTPS</font>
<br><br>
자바의 강력한 능력은 자바 1.0부터 발휘되었으며 이러한 능력 중 하나는 단 몇 줄의 코드로도 강력한 네트워크 성능을 발휘하는 것이다. 자바 1.4에서는 SSL과 TLS 프로토콜을 사용하는 보안소켓을 지원하므로 강력하고 안전한 네트워크 프로그램을 쉽게 만들 수 있게 되었다. SSL 지원은 <font color="#006699">javax.net.ssl</font> 패키지에서 제공된다. <font color="#006699">java</font>가 아니라 <font color="#006699">javax</font>임에 주의하자. 또한 자바와 관련된 기타 다른 보안관련 패키지와 마찬가지로 고도로 복잡한 다양한 설정이 지원된다. 그렇지만 다행스럽게도 가장 흔하게 이용되는 SSL은 아주 쉽게 실행된다. 다음 코드는 SSL 소켓을 생성하고httpS 프로토콜을 이용하여 웹 서버와 안전한 통신을 수행하는 예제이다.
<font color="#006699"><pre>
import javax.net.ssl.*; 
import java.io.*; 
 
// SSL 소켓을 생성하기 위하여 SocketFactory 오브젝트를 가져온다. 
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault(); 
 
// 서버의httpS 포트(포트 443)에 연결될 보안 소켓을 생성한다. 
SSLSocket sslsock = (SSLSocket) factory.createSocket(hostname, 443); 
 
// 웹서버가 제공하는 인증서를 가져온다. 이 작업은 서버가 인증서를 제공하지 않을 때를 대비해서 예외 처리될 수 있다. 
인증서의 발급자를 보고 신뢰여부를 판단한다. 
SSLSession session = sslsock.getSession(); 
X509Certificate cert = (X509Certificate)session.getPeerCertificates()[0]; 
String issuer = cert.getIssuerDN().getName();  
 
// 인증서를 믿을 수 있다고 가정하면, 이제 일반적인 java.net.Socket 오브젝트를 사용하듯이 생성된 소켓을 사용한다. 
하던데로HTTP 요청을 보내고 반응을 읽는다. 
PrintWriter out = new PrintWriter(sslsock.getOutputStream()); 
out.print("GET " + args[1] + "http/1.0\r\n\r\n"); 
out.flush(); 
 
// 다음으로 서버의 반응을 읽고 콘솔에 출력한다.. 
BufferedReader in = 
new BufferedReader(new InputStreamReader(sslsock.getInputStream())); 
String line; 
while((line = in.readLine()) != null) System.out.println(line); 
 
// 마지막으로 소켓을 닫는다. 
sslsock.close();
</pre></font>
<font class=hd2>5. LinkedHashMap</font>
<br><br>
Java 1.4 장점 중, 하나는 <font color="#006699">java.util.LinkedHashMap</font> 클래스이다. Collections API에 새롭게 첨가된 것은 <font color="#006699">HashMap</font>처럼 해쉬테이블에 기반을 둔 <font color="#006699">Map</font> 도구이다. <font color="#006699">LinkedHashMap</font>이 다른 점은 맵 엔트리에서 실행되는 링크된 리스트를 관리할 수 있고 또한 그 엔트리에 대해서 예측할 수 있는 반복 작업을 수행할 수 있다는 것이다. 대부분 이 순서는 맵에 엔트리가 추가된 순서대로 되고 가끔씩 아주 유용한 특징으로 사용할 수 있는 상황도 있다. 물론 자바 1.4도 이와 아주 유사한 <font color="#006699">LinkedHashSet</font>을 포함하고 있다.
<br><br>
만약 <font color="#006699">HashMap</font>과 <font color="#006699">LinkedList</font>를 합치는 생각이 별로 참신하지 않다고 생각되면 다음을 고려해 보자. <font color="#006699">LinkedHashMap</font>은 생성자의 세 번째 인자로 <font color="#006699">true</font> 값을 넘겨줌으로써 가장 최근에 접근(즉 조회되거나 설정된) 순서에서 가장 오래 전에 접근된 순서로 엔트리 순서를 설정할 수 있다. 이 것은 매우 멋진 특징이지만 보다 나은 게 있다! <font color="#006699">LinkedHashMap</font>은 프로텍트된 메소드인 <font color="#006699">removeEldestEntry()</font>를 가지고 있는데 맵에 새로운 엔트리가 추가될 때마다 호출된다. 만약 이 메소드가 <font color="#006699">true</font>를 반환하면 맵에서 "가장 오래된" 엔트리, 즉 가장 오래 전에 입력되거나 사용된 것은 자동적으로 삭제된다. <font color="#006699">removeEldestEntry()</font>의 기본값으로 항상 <font color="#006699">false</font>를 반환하지만 맵이 정해진 어떤 최고 크기에 다다르면 <font color="#006699">true</font> 값을 반환하도록 오버라이드 할 수 있다. 자 이제 바로, LRU(least-recently-used) 캐시를 만든 것이다!
<font color="#006699"><pre>
public class LRUCache extends java.util.LinkedHashMap {
    public LRUCache(int maxsize) {
	super(maxsize*4/3 + 1, 0.75f, true);
	this.maxsize = maxsize;
    }
    protected int maxsize;
    protected boolean removeEldestEntry() 
	      { return size() > maxsize; }
}
</pre></font>
<font class=hd2>4, 파일 채널</font>
<br><br>
Java 1.4의 새로운 특징 중 아마도 가장 중요한 것은 새로운 I/O API일 것이다. 이 새로운 API는 <font color="#006699">java.nio</font> 패키지와 그 하부 패키지에서 정의되고 기존 <font color="#006699">java.io</font> API와 연관성이 최소화 된 아키텍처를 이용한 고성능 입출력을 지원한다. 새로운 클래스 중 하나는 <font color="#006699">java.nio.channels.FileChannel</font>로 파일을 읽고 쓰는 새로운 방식을 정의하고 있다. 파일을 읽고 쓴다는 것은 그 자체로 별로 참신할 것이 없다. 우리는 이미 기존의 <font color="#006699">java.io</font> 클래스를 이용하여 그 일을 하고 있기 때문이다. 그렇다면 <font color="#006699">FileChannel</font>의 새로운 점은 무엇인가? 그것은 메모리 매핑과 파일잠금 기능 제공이다. 메모리 매핑은 파일의 내용을 마치 메모리에 직접 적재된 것처럼 볼 수 있게 해준다. <font color="#006699">FileChannel</font> API 는 배열을 직접 이용하지는 않는 대신, <font color="#006699">java.nio.ByteBuffer</font>를 사용한다. 여기서 메모리 매핑을 이용하는 코드를 보자.
<font color="#006699"><pre>
import java.io.*;              // FileInputStream
import java.nio.*;             // ByteBuffer
import java.nio.channels.*;    // FileChannel

// FileChannel을 생성하고 파일의 크기를 가져오고 ByteBuffer에 파일을 매핑한다.
FileChannel in = new FileInputStream("test.data").getChannel();
int filesize = (int)in.size();
ByteBuffer mappedfile = in.map(FileChannel.MapMode.READ_ONLY, 0, filesize);
// 파일은 고정크기의 바이너리 데이터 레코드를 가지고 있다고 가정하자
static final int RECORDSIZE = 80; 
    // 각 레코드의 크기
int recordNumber = 1;             
    // 우리가 읽고자 하는 레코드
byte[] recorddata = new byte[RECORDSIZE];     
    // 레코드 데이터를 가진 배열
mappedfile.position(recordNumber*RECORDSIZE); 
    // 원하는 레코드를 시작한다.
mappedfile.get(recorddata);
    // 버퍼로부터 배열을 채운다.
</pre></font>
메모리 매핑은 상당한 재미를 주지만 그에 따른 비용을 지불해야 한다. 전형적인 자바구축에서 일반적으로 작은 크기의 파일(대략 100 킬로바이트 미만의 파일)을 읽을 때는 메모리 매핑을 쓰지 않고 그냥 읽어 오는 것이 훨씬 효율적이다. 
<br><br>
<font color="#006699">FileChannel</font>의 다른 멋진 점인 파일전체 또는 파일 일부를 잠그는 파일잠금 기능을 살펴보자. 파일잠금은 파일에 대한 모든 동시 접속을 막는 배타적인 잠금이나 동시 쓰기를 막는 공유된 잠금으로 사용될 수 있다. 몇몇 운영체제에서는 모든 잠금을 엄격하게 강화하고 있는 한편, 다른 것들은 단지 권고 정도의 잠금 기능을 지원하므로 공유 파일을 읽거나 쓸 때 잠금 기능을 획득하기 위한 시도를 하고 협조하는 프로그램을 요구한다는 것을 지적하는 바이다. 다음 코드는 현재 읽고 있는 파일에 다른 프로그램이나 협업이 쓰는 것을 방지하기 위하여 파일의 일부분에 공유 잠금을 할 수 있는데 사용할 수 있다.
<font color="#006699"> <pre>
FileChannel in;  // 어딘가에서 초기화 되었다
FileLock lock = 
  in.lock(recordNumber*RECORDSIZE, // 잠금 시작 부분
  RECORDSIZE,                     // 잠금 부분의 길이
  true);          // 공유 잠금: 동시 갱신 금지
// 원하는 레코드를 읽고나서 잠금을 해제한다.
lock.release()
</pre></font>
<font class=hd2>3. Non-Blocking I/O</font>
<br><br>
고성능 네트워크 서버를 개발하는 프로그래머에게 가장 중요한 새로운 I/O API는 non-blocking I/O의 구현이라고 할 수 있다. 네트워크 연결에서 읽고 쓰기를 할 수 있는 <font color="#006699">java.nio.channels</font>의 <font color="#006699">SocketChannel</font> 클래스는 <font color="#006699">java.net.Socket</font>과 상응하는 새로운 입출력이다.
<br><br>
<font color="#006699">SocketChannel</font>과 다른 <font color="#006699">SelectableChannel</font>의 하위 클래스들은 non-blocking 모드로 들어갈 수 있고 <font color="#006699">Selector</font> 오브젝트와 함께 등록될 수 있다. <font color="#006699">Selector</font> 클래스는 <font color="#006699">select()</font>라는 block을 수행하는 메소드를 정의하는데 하나 혹은 그 이상의 채널을 모니터링하고 그 중 입출력이 준비된 것을 반환한다. <font color="#006699">Selector</font> 오브젝트 하나로 서버는 어떤 <font color="#006699">SocketChannel</font> 클라이언트 접속도 모니터링 할 수 있다. 이 새로운  API가 나오기 전, 각 클라이언트 접속은 그들 고유의 스레드와 blocking된 <font color="#006699">Socket</font> 오브젝트를 가지고서 다루어야만 했었다. 그것은 아주 무거운 솔루션이었고 대용량 서버에서는 그다지 확장성이 좋지 않았다. 다음 코드는 non-blocking I/O를 수행하는 서버에서 사용되는 기본 루프를 보여준다.
<font color="#006699"> <pre>
import java.nio.channels.*;
import java.util.*;

// 모든 클라이언트 접속을 모니터링하는 selector오브젝트를 생성
Selector selector = Selector.open();

// 새로운 non-blocking erverSocketChannel을 생성하고 8000포트에 바인딩 시키고 Selector와 함께 등록한다.
ServerSocketChannel server = ServerSocketChannel.open();
    // 채널을 열고
server.configureBlocking(false);                        
    // Non-blocking
server.socket().bind(new java.net.InetSocketAddress(8000));
    // 포트 바인딩
SelectionKey serverkey = server.register(selector, SelectionKey.OP_ACCEPT);
for(;;) {  // 메인 서버 루프. 서버는 영원히 동작한다.
    // 등록된 채널의 중 하나에 활동이 있을 때 까지 호출은 block된다.
이것이 non-blocking I/O에 핵심적인 방법이다.
    selector.select();

    // I/O에 준비된 모든 채널에 대한 SelectionKey 오브젝트를 가지고 있는 java.util.Set을 가져온다. 
    Set keys = selector.selectedKeys();

    // java.util.Iterator을 이용하여 선택된 키에 대해 루프를 실행한다.
    for(Iterator i = keys.iterator(); i.hasNext(); ) {
	SelectionKey key = (SelectionKey) i.next();
	i.remove();  // 선택된 키 집합에서 키를 삭제한다.

	// ServerSocketChannle에 등록할 때 키가 SelectionKey인지 검증한다.
	if (key == serverkey) {
	    // ServerSocketChannel에서 활동은 클라이언트가 서버와 접속하려고 시도하는 것을 의미한다.
	    if (key.isAcceptable()) {
		// 클라이언트 접속을 받는다.
		SocketChannel client = server.accept();
		// 클라이언트 채널을 non-blocking 모드로 할당한다.
		client.configureBlocking(false);
		// 클라이언트 채널을 Selector 오브젝트를 가지고 등록한다.
		SelectionKey clientkey = client.register(selector,
		   SelectionKey.OP_READ);
	    }
	}
	else {  // 다른 경우, 클라이언트 채널에는 활동이 존재해야하고 읽어드릴 
	        // 데이터가 있는지 이중 검증한다.
	    if (!key.isReadable()) continue;       
		    // 읽어드릴 자료를 가진 클라이언트 채널을 가져온다.
	    SocketChannel client = (SocketChannel) key.channel();

	    // 어딘가에 할당된 버퍼에 클라이언트에서 읽은 자료를 저장한다.
	    int bytesread = client.read(buffer);

	    // Read()가 -1을 반환하면 스트림의 끝이라는 것을 나타내고 클라이언트가 
	    // 접속해제되었음을 의미한다. 따라서 선택된 키를 등록해제하고 채널을 닫는다.
	    if (bytesread == -1) {  
		key.cancel();
		client.close();
		continue;
	    }
	}
    }
}
</pre></font>
<font class=hd2>2. 정규표현식</font>
<br><br>
자바 1.4의 참신한 새로운 특징 10개중 빼놓을 수 없는 하나가 바로 정규표현식이다. 정규표현식은 텍스트의 패턴을 기술하는 표현방식이다. <font color="#006699">java.util.regex</font> 패키지를 이용하면 정규표현식을 적용해 스트링을 비교한 후 일치 여부를 결정하고 패턴과 정확하게 일치하는 부분을 비롯하여 가능한 한 하부패터까지 일치여부를 결정할 수 있다. 정규표현식은 또한 강력한 찾고 바꾸기 기능을 제공한다.
<br><br>
정규표현식은 펄프로그램밍 언어에서 아주 비중있는 언어이며, 자바 1.4에서는 펄 5버전의 문법에서 따온 정규 표현 패턴을 채용했다. 따라서 펄 프로그래머는 자바에서 정규표현식 사용법을 아주 쉽게 배울 수 있을 뿐만 아니라 어떤 경우에는 <font color="#006699">java.util.regex</font> 패키지를 사용하여 <font color="#006699">String</font> 클래스에 정규표현 유틸리티 메소드를 추가하여 사용할 수도 있다. 다음 코드는 자바의 정규표현식을 사용하여 할 수 있는 멋진 작업을 보여준다. 불행하게도 여기에 할당된 지면으로는 정규표현 문법을 설명할 수는 없지만, 만약 펄이나 이와 유사한 언어에서 정규표현식 사용에 익숙한 독자라면 그다지 혼란스럽지는 않을 것이다. 
<font color="#006699"> <pre>
// 대소문자에 상관없이 "java"라는 모든 단어에 대해서 정확하게 표현된 
// "Java"로 대체하는 String 메소드를 사용한다.
s = s.replaceAll("(?i)\\bjava\\b", // 패턴: "java", 대소문자를 상관하지 않는다.
"Java");          // 변경할 문자열

// java.util.regex 클래스의 패턴과 일치를 사용하는 보다 복잡한 정규표현식을 이용해보자
// "Java"로 시작하는 어떤 단어라도 기술할 수 있고 Java를 접두사로 갖는 어떠한 접미어도
// 캡쳐하는 패턴을 만든다. 
CASE_INSENTITIVE 플래그를 이용하므로 대소문자와 상관없이 매치된다.

Pattern p = Pattern.compile("\\bJava(\\w*)", 
            Pattern.CASE_INSENSITIVE);
// 이 문장으로 패턴을 비교한다
String text = "Java is fun; JavaScript is funny.";
// 텍스트 패턴을 비교할 Matcher 오브젝트를 생성한다.
Matcher m = p.matcher(text);
// 루프를 돌려서 텍스트 안의 모든 일치하는 패턴을 찾는다.
while(m.find()) {
  // 각각의 일치에 대해서 패턴과 일치하는 텍스트와 문자열에서 위치를 출력한다.
  System.out.println("Found "" + m.group(0) + "" at position "
                     + m.start(0));
  // 또한 다른 접미사가 있다면 출력한다.
  if (m.start(1) < m.end(1)) 
     System.out.println("Suffix 
is " + m.group(1));
}
</pre></font>
<font class=hd2>1. 표명(Assertion)</font>
<br><br>
자바 1.4의 가장 멋진 새로운 특징은 <font color="#006699">assert</font> 구문이다.
<br><br>
<font color="#006699">assert</font> 구문은 자바코드에서 문장과 디자인 전제를 검증하는데 사용된다. 표명은 <font color="#006699">assert</font> 키워드와 프로그래머가 항상 <font color="#006699">true</font>로 평가되기를 예상하는 불린 표현으로 구성된다. 기본적으로 표명은 활성화되어 있지 않고, <font color="#006699">assert</font> 구문은 실제로 아무 것도 하지 않는다. 그러나 디버깅과 테스팅 도구로써 표명을 활성화 할 수 있고 활성화된 다음에는 <font color="#006699">assert</font> 구문을 이용하여 표현을 평가할 수 있다. 만약 그 표현값이 <font color="#006699">true</font>이면 <font color="#006699">assert</font>는 아무것도 하지않는다. 반면에 표현값이 <font color="#006699">false</font>면 표명은 실패하고 <font color="#006699">assert</font> 구문은 <font color="#006699">java.lang.AssertionError</font>를 반환한다.
<br><br>
<font color="#006699">assert</font> 구문은 옵션으로 콜론으로 구분되는 두 번째 표현을 포함할 수 있다. 표명이 활성화 되었고 첫번째 표현값이 <font color="#006699">false</font>면 두 번째 표현은 오류 코드나 혹은 오류 메시지와 같은 종류를 포함할 수 있고 그것은 <font color="#006699">AssertionError()</font> 생성자에게 전달한다. 이 구문의 전체 문법은 다음과 같다.
<font color="#006699"><pre>
assert assertion ;
</pre></font>
혹은
<font color="#006699"><pre>
assert assertion : errorcode ;
</pre></font>
표명 표현은 불린 표현이 되어야 한다는 사실을 기억해 두고 있어야 한다. 실제적인 의미는 비교 연산자나 불린 값을 갖는 메소드를 호출해야한다는 의미이다. errorcode 표현은 어떤 값이라도 가질 수 있다.
<br><br>
이제 assert가 실제 코드에서 사용되는 법을 살펴보자. 변수 x가 항상 0이나 1인 값을 가질 것으로 예상되는 메소드를 작성하고 있다고 가정해보자. 이것을 표명을 써서 명확하게 표현할 수 있다.
<font color="#006699"><pre>
if (x == 0) {
  ...
}
else {  
  assert x == 1 : x;  // x는 0이거나 1이어야 한다.
  ...
}
</pre></font>
비슷한 용례로는 <font color="#006699">switch</font> 구문을 사용하는 것이 있다. <font color="#006699">default</font>인 경우를 포함하지 않는 <font color="#006699">switch</font>를 작성하는 것이 아니라면 가능한 한 모든 경우를 명확하게 열거하는 표명을 이용하여 모든 가정을 코딩할 수 있다. 예를 들면
<font color="#006699"><pre>
switch(x) {
case -1: return LESS;
case 0: return EQUALS;
case 1: return GREATER;
default: assert false:x; 
    // x가 -1, 0 혹은 1이 아니면 AssertionError로 넘긴다.
}
</pre></font>
<font color="#006699">assert false;</font>는 항상 실패한다는 것에 주목하자. 이런 형태의 구문은 여러분이 결코 도달할 수 없다고 믿는 "dead end" 구문에서 특히 유용하다. 
<br><br>
위에서 든 두 가지 예는 표명이 유용하게 사용될 수 있는 보편적인 경우일 뿐으로 클래스의 invariant와 private 메소드의 preconditions을 검사하는 것과 같은 부분에서도 중요하게 사용될 수 있다. 표명을 사용하는 것을 이론과 실질적인 응용면에서 더 많이 알고 싶다면 <a href="http://www.hanbitbook.co.kr/look.htm?book_code=020401-00003" target="_blank">『Java in a Nutshell, 4th Edition』</a>의 2장을 참조하면 더 자세한 내용을 볼 수 있다.
<br><br>
Java 1.4 이전에는 assert가 예약어로 되어있지 않기 때문에 <font color="#006699">javac</font> 컴파일러는 기본적으로는 <font color="#006699">assert</font> 구문을 인식할 수 없다. Asseertions을 포함하는 코드를 컴파일 하기 위해서는 <font color="#006699">-source 1.4</font>를 <font color="#006699">javac</font> 컴파일러시에 명시해야한다. 이것은 식별자로서 <font color="#006699">assert</font>를 사용한다는 것을 알려주어 컴파일 할 수 있게 한다.
<br><br>
위에서 보인바 대로 실행할 때에 테스트되도록 하기 위해서는 표명이 활성화되어야만 한다. 이 작업은 <font color="#006699">java</font> 인터프리터에서 <font color="#006699">-ea</font> 인자를 사용해서 수행할 수 있다. 이 인자는 모든 비시스템 클래스에서 표명을 활성화한다. 또한 클래스당 혹은 패키지당 기반으로 활성화 할 수도 있다.
<font color="#006699"><pre>
java -ea:com.example.sorters.MergeSort
    // 하나의 클래스에 대해 표명을 활성화한다.
java -ea:com.example.sorters...
    // 패키지에 대해 표명을 활성화한다.
</pre></font>
또한 <font color="#006699">-da</font> 인자를 이용하여 클래스나 패키지에서 표명을 비활성화 할 수도 있다.
<BR><BR>
David Flanagan은 오라일리 자바관련 서적을 집필한 저자이다. 그의 저서로는 <a href="http://www.hanbitbook.co.kr/look.htm?book_code=001209-00001" target="_blank">『자바 인어넛셀, 3판』</a>을 비롯하여 <a href="http://www.hanbitbook.co.kr/look.htm?book_code=010622-00010" target="_blank">『예제로 배우는 자바 프로그래밍』</a>, <a href="http://www.hanbitbook.co.kr/look.htm?book_code=011228-00001" target="_blank">『JavaScript: The Definitive Guide, 4th Edition』</a>, <a href="http://www.hanbitbook.co.kr/look.htm?book_code=020401-00003" target="_blank">『Java in a Nutshell, 4th Edition』</a>등이 있다.                                    </div>
				
				<!-- 좋아요 버튼 -->
				<div class="btn_area">
											<div  class="btn_like" ><a href="javascript:setLike('CMS2038585226','');">좋아요</a></div>
									</div>
				
                <!-- 태그 정보 영역 -->
								<div class="tag_area"><span>TAG : </span></div>
								<!-- //태그 정보 영역 -->
      <!-- //콘텐츠 영역 -->
      </div>  
      <!-- // 콘텐츠 board 영역 -->
      <!-- 이전 다음 -->
            <div class="view_page_move">
        <p class="vpml"><span>이전 글 : </span>
                      <a href="./category_view.html?cms_code=CMS8221919823&cate_cd=001">Doctype을 올바르게 사용하자</a>
                  </p>
        <p class="vpmr"><span>다음 글 : </span>
                      <a href="./category_view.html?cms_code=CMS3414624622&cate_cd=001">에필로그 - J2SE의 미래, 자바의 앞날</a>
                  </p>
      </div>      <!-- //이전 다음 -->

      <!-- 관련콘텐츠 -->
            <!-- //관련콘텐츠 -->

      <!-- 홈페이지 댓글 모듈 영역 -->
      
			<div class="detail_reply_area">				
				<!-- 댓글 리스트 -->
				<div  id="cms_reply_div"></div>
              <!--// 댓글 리스트 --> 
              
              <!-- 댓글 입력 -->
				<form name="re_frm" id="re_frm" method="post">  					
					<input type="hidden" id="fk_m_idx"  name="fk_m_idx" value="">
					<input type="hidden" id="fk_m_name"  name="fk_m_name" value="">
					<input type="hidden" id="fk_cms_code"  name="fk_cms_code" value="CMS2038585226">
					
					<div class="reply_form">
	                    <fieldset>
	                    <legend>댓글 입력</legend>
	                        <label><textarea name="comment" id="comment" class="i_textarea"></textarea></label>	                        
	                        <label><button  type="button" name="" value="작성" class="btn_repl" onclick="cms_reply_data('ins','');">댓글 작성</button></label>
	                    </fieldset>
					</div>					
				</form>
				<!--// 댓글 입력 -->				
            </div>
                        
            <script>  
            		function cms_reply_frm(str,re_idx,re_com_idx,status){        
            			     	
            			var result_postdata= { 
            				str:str,
            				re_idx : re_idx 	,
            				re_com_idx:re_com_idx 
            			}							
																														
						$.ajax({
							type:"post",
							url: "/channel/cms_reply_frm.php",
							data:result_postdata,
							contentType: "application/x-www-form-urlencoded; charset=UTF-8",
							success: function(data){	
								//alert(data);			
								if(str == "mod"){
									$("#mod_com_div_"+re_idx).html();																						
									$("#mod_com_div_"+re_idx).html(data);											
								}								
								
								if(str == "re_mod"){
									$("#mod_recom_div_"+re_com_idx).html();														
									$("#mod_recom_div_"+re_com_idx).html(data);											
								}							
							}	 			
	            		});					           																						
            		}
            	
            	          
	            	//댓글달기       
	            	// @str:상태값  @idx: 댓글idx
	            	function cms_reply_data(str,idx){            		 
	            		           		            		
						var fk_m_idx 			= $("#fk_m_idx").val();				
						var fk_m_name 		= $("#fk_m_name").val();		
						var fk_cms_code 		= $("#fk_cms_code").val();												
						
//						var comment			= $("#comment").val();				
//						var re_comment		= $("#re_comment_"+rd_num).val();															
//						var mod_comment			= $("#mod_comment").val();				
//						var mod_re_comment		= $("#mod_re_comment_"+idx).val();	
		
						var comment			= $("#comment");		
						var re_comment		= $("#re_comment_"+idx);
						var mod_comment			= $("#mod_comment");
						var mod_re_comment		= $("#mod_re_comment_"+idx);

						
						if(str=="ins"){ comment = comment;
						}else if(str=="re_ins"){comment = re_comment;
						}else if(str=="mod"){comment = mod_comment;
						}else if(str=="re_mod"){comment = mod_re_comment;
						}
						
								
						if(!fk_m_idx){alert("로그인후 댓글을 작성하실수있습니다.");return;	}	
							
						if(str == "ins"||str == "mod"||str == "re_mod"||str == "re_ins"){		
							if(!comment.val()){
								alert("내용을  입력해주세요.");comment.focus(); return;	
							}													
						}else if(str == "del" || str == "re_del"){
							if(!confirm(" 삭제 후 데이터는 복구되지 않습니다. \n 해당 항목을 삭제 하시겠습니까? ")){ return; }	
							
						}
						var comment_val = comment.val();
										
						var result_postdata= { str:str, fk_m_idx : fk_m_idx, fk_m_name : fk_m_name,	fk_cms_code : fk_cms_code,	
							comment:comment_val ,idx:idx
						}							
																														
						$.ajax({
							type:"post",
							url: "/channel/cms_reply_proc.php",
							data:result_postdata,
							contentType: "application/x-www-form-urlencoded; charset=UTF-8",
							success: function(data){									
								alert($.trim(data));	
								cms_reply_list('CMS2038585226');
								/*	
								if(str == "mod_f"){					
									$("#re_"+div_n+"_"+div2_n).html("");
									$("#re_"+div_n+"_"+div2_n).html(data);
								}else{								
									alert(data.trim());	
									comment_list( fk_hbr_idx,div_n); //댓글 리스트					
								}	
								*/				
							}	 			
	            		});			
	            	}			
						
						
					//리뷰 댓글 리스트  ::  리뷰 idx , div_num
					function cms_reply_list(cms_code){																																											
						$.ajax({
							type:"post",
							url: "/channel/cms_reply_list.php",
							data: {	cms_code : cms_code},
							contentType: "application/x-www-form-urlencoded; charset=UTF-8",
							success: function(data){	
								$("#comment").val('');					
								$("#cms_reply_div").html(data);				
							}	 			
						});					
					}
					
					//답글 처리 버튼 
					function recom_frm(str,num){				
						
						if(str=="show"){													
							$("#recom_div_"+num).show();														
							$("#re_btn_"+num).html("");			
							$("#re_btn_"+num).html("<a href=\"javascript:recom_frm('hide','"+num+"');\">답글취소</a>");			
							
						}else if(str=="hide"){
							$("#recom_div_"+num).hide();																								
							$("#re_btn_"+num).html("");			
							$("#re_btn_"+num).html("<a href=\"javascript:recom_frm('show','"+num+"');\">답글</a>");			
						}						
					}		
												
					cms_reply_list('CMS2038585226');				
            </script>	      <!-- //홈페이지 댓글 모듈 영역 -->
      
      <div class="btn_area_board">
        <div class="left"><a href="/channel/category/category_list.html">목록</a></div>
      </div>
    </div>
    <!-- //상세 left 영역 -->
    
    <!-- 상세 right 영역 -->
        
    <div class="network_view_wrap_r">
      <!-- 배너영역 -->
      
      <div class="bn_area" style="padding:0; border:1px solid #dddddd;">
  <a href="https://www.hanbitn.com/" target="_blank">
    <img src="/data/banner/20240904165233_ban_pc_img_thumb.jpg" alt="" ></a>
  </a>  
</div>      
      <!-- //배너영역 -->
            
      <!-- 편집자 Choice -->
            <!-- //편집자 Choice -->
            
      <!-- 최신콘텐츠 -->
      			<div class="newest_con">
				<p class="tit">최신 콘텐츠</p>
				<ul>
										<li><a href="/channel/category/category_view.html?cms_code=CMS2359486892" target="_blank" title="새창열기">[바닷속 딥러닝 어드벤처] 5부. 피...</a></li>
										<li><a href="/channel/category/category_view.html?cms_code=CMS3783401104" target="_blank" title="새창열기">[바닷속 딥러닝 어드벤처] 4부. 제...</a></li>
										<li><a href="/channel/category/category_view.html?cms_code=CMS8289480351" target="_blank" title="새창열기">[바닷속 딥러닝 어드벤처] 3부. DeZ...</a></li>
										<li><a href="/channel/category/category_view.html?cms_code=CMS2551016804" target="_blank" title="새창열기">[바닷속 딥러닝 어드벤처] 2부. 상...</a></li>
										<li><a href="/channel/category/category_view.html?cms_code=CMS6397502491" target="_blank" title="새창열기">[바닷속 딥러닝 어드벤처] 1부. 시...</a></li>
										<li><a href="/channel/category/category_view.html?cms_code=CMS2758678476" target="_blank" title="새창열기">[챗GPT로 챗봇 만들기] 프롬프트 엔...</a></li>
										<li><a href="/channel/category/category_view.html?cms_code=CMS7962220408" target="_blank" title="새창열기">플러터의 필수개념 이해하기 - 아키...</a></li>
										<li><a href="/channel/category/category_view.html?cms_code=CMS1420013052" target="_blank" title="새창열기">TypeScript의 데이터 타입에 대해 ...</a></li>
										<li><a href="/channel/category/category_view.html?cms_code=CMS5848159514" target="_blank" title="새창열기">쉽게 배우는 AWS : 클라우드 정의부...</a></li>
										<li><a href="/channel/category/category_view.html?cms_code=CMS2401427325" target="_blank" title="새창열기">[테라폼으로 시작하는 IaC] 기존 리...</a></li>
									</ul>
			</div>      <!-- //최신콘텐츠 -->
      
      <!-- 인기콘텐츠 -->
      			<div class="newest_con">
				<p class="tit">인기 콘텐츠</p>
				<ul>
										<li><a href="/channel/category/category_view.html?cms_code=CMS4064858187" target="_blank" title="새창열기">[리뷰 함께 읽기]  100만원 마케팅,...</a></li>
										<li><a href="/channel/category/category_view.html?cms_code=CMS4695374004" target="_blank" title="새창열기">[CS기술 면접] 자주 출제되는 컴퓨...</a></li>
										<li><a href="/channel/category/category_view.html?cms_code=CMS5997817104" target="_blank" title="새창열기">파이썬 웹 프레임워크 비교 - D...</a></li>
										<li><a href="/channel/category/category_view.html?cms_code=CMS8302568366" target="_blank" title="새창열기">여름은 성장의 계절, 개발자 성장...</a></li>
										<li><a href="/channel/category/category_view.html?cms_code=CMS9625146475" target="_blank" title="새창열기">구글 스프레드시트의 날짜 형식 변...</a></li>
										<li><a href="/channel/category/category_view.html?cms_code=CMS7003473664" target="_blank" title="새창열기">개발자를 위한 코딩 파트너, 효과...</a></li>
										<li><a href="/channel/category/category_view.html?cms_code=CMS1166571063" target="_blank" title="새창열기">Redis(레디스) 서버 설치 및...</a></li>
										<li><a href="/channel/category/category_view.html?cms_code=CMS9813408902" target="_blank" title="새창열기">2030년 AI 시장 전망 & 주목할 상용...</a></li>
										<li><a href="/channel/category/category_view.html?cms_code=CMS7885156269" target="_blank" title="새창열기">개발자를 위한 커뮤니케이션 도구 ...</a></li>
										<li><a href="/channel/category/category_view.html?cms_code=CMS8893081462" target="_blank" title="새창열기">원리부터 이해하는 도커 - 컨테이...</a></li>
									</ul>
			</div>      <!-- //인기콘텐츠 -->
    </div>    <!-- //상세 right 영역 -->
    
  </div>
  <!-- //네트워크 상세 wrap -->

</div>
<!-- //Contents -->

<!-- Footer -->

<footer>
  <!-- 공지사항 -->
  <div class="foot_notice" style="height:6px;"></div>  <!-- //공지사항 -->

  <div class="foot_contents">
    <!-- 하단 메뉴 -->
    <div class="foot_menu">
      <!-- added by coffin -->
              <ul>
          <li><a href="https://www.hanbit.co.kr/publisher/index.html" target="_blank">회사소개</a>(<a
              href="https://www.hanbit.co.kr/publisher/index.html" target="_blank">KOR</a> | <a
              href="https://www.hanbit.co.kr/publisher/index.html?lang=e" target="_blank">ENG</a>) • <a
              href="https://www.hanbit.co.kr/publisher/contact.html?lang=k" target="_blank">약도</a></li>
          <li><a href="https://www.hanbit.co.kr/publisher/write.html" target="_blank">기획 및 원고 모집</a></li>
          <li><a href="https://www.hanbit.co.kr/publisher/career.html" target="_blank">채용</a> • 입사지원 <a
              href="mailto:jobs@hanbit.co.kr">jobs@hanbit.co.kr</a></li>
          <li><a href="https://www.hanbit.co.kr/publisher/foreignrights.html" target="_blank">FOREIGN RIGHTS</a></li>
          <li><a href="https://www.hanbit.co.kr/member/use_agreement.html">이용약관</a> • <a
              href="https://www.hanbit.co.kr/member/privacy_policy.html"><strong>개인정보처리방침</strong></a></li>
          <li><a href="https://www.hanbit.co.kr/sitemap/sitemap.html">사이트맵</a></li>
        </ul>
          </div>
    <!-- //하단 메뉴 -->

    <!-- SNS -->
    <div class="foot_sns">
                        <!-- 데브레터 구독하기 -->
                  <div class="devletter_contet">
                    <div class="devletter_sub">
                      <span class="dev_header_img">데브레터 구독하기</span><a class="dev_sit_a" href="/devletter/" target="_blank"><span
                          class="dev_site_img">데브레터 보러가기</span></a>
                    </div>

                    <form class="subscribe_form" action="/devletter/dev_process.php" method="post">
                      <input type="hidden" id="g-recaptcha" name="g-recaptcha">
                      <fieldset class="subscribe_fiel">
                        <legend>구독서비스</legend>
                        <input type="text" placeholder="Your Email Address" name="user_email"></input>
                        <button type="submit" class="sub_btn"><span>구독</span></button>
                      </fieldset>
                    </form>

                    <div class="main_foot_sns">
                      <ul>
                        <li class="main_foot_facebook"><a href="https://www.facebook.com/hanbitmedia"
                            target="_blank"><span>페이스북</span></a></li>
                        <li class="main_foot_youtube"><a href="https://www.youtube.com/한빛TV" target="_blank"><span>유튜브</span></a>
                        </li>
                        <li class="main_foot_bolg"><a href="http://blog.hanbit.co.kr/" target="_blank"><span>블로그</span></a></li>
                        <li class="main_foot_naverpost"><a href="https://m.post.naver.com/hanbitstory"
                            target="_blank"><span>네이버포스트</span></a></li>
                      </ul>
                    </div>
                  </div>
                  <!-- 데브레터 구독하기 //-->
      
          </div>
    <!-- //SNS -->

    <!-- 한빛 정보 -->
    <div class="foot_about">
      <div class="foot_about_area">

        
                <p><strong>한빛출판네트워크</strong></p>
                <p>(03785) 서울 서대문구 연희로2길 62</p>
                <!-- <p>EMAIL : support@hanbit.co.kr</p> -->
                <p>대표이사 : 김태헌, 전태호</p>
                <p>사업자등록번호 : 220-81-05665 <a href="http://www.ftc.go.kr/bizCommPop.do?wrkr_no=2208105665"
                    target="_blank">[확인]</a></p>
                <p>통신판매업신고 : 2017-서울서대문-0671호</p>
                <p>호스팅제공자 : 호스트센터(주)</p>
                <p>고객센터 : support@hanbit.co.kr <a href="/support/help_info.html">[바로가기]</a></p>
                <!--//패밀리사이트-->
        <div class="family_menu">
          <div data-family-menu>
            <span>관련사이트</span> <i class="family_icon" data-family-icon role="img"></i>
          </div>
          <ul data-family-menu-ul class="family_menu_ul" style="display:none;">
            <li><a href="https://www.hanbit.co.kr/media/" target="_blank">한빛미디어<svg data-v-c1d21be8="" data-v-f12001e6=""
                  xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 9" class="ico_outlink">
                  <g data-v-c1d21be8="" fill="none" fill-rule="evenodd">
                    <path data-v-c1d21be8="" d="M1.795 1.074L7.942 1.074 7.942 7.221M7.942 1.074L1.378 7.638"
                      transform="translate(-935 -867) translate(836 848) translate(14 14) translate(85 5)"></path>
                  </g>
                </svg></a></li>
            <li><a href="https://www.hanbit.co.kr/academy/" target="_blank">한빛아카데미<svg data-v-c1d21be8="" data-v-f12001e6=""
                  xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 9" class="ico_outlink">
                  <g data-v-c1d21be8="" fill="none" fill-rule="evenodd">
                    <path data-v-c1d21be8="" d="M1.795 1.074L7.942 1.074 7.942 7.221M7.942 1.074L1.378 7.638"
                      transform="translate(-935 -867) translate(836 848) translate(14 14) translate(85 5)"></path>
                  </g>
                </svg></a></li>
            <li><a href="https://www.hanbit.co.kr/biz/" target="_blank">한빛비즈<svg data-v-c1d21be8="" data-v-f12001e6=""
                  xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 9" class="ico_outlink">
                  <g data-v-c1d21be8="" fill="none" fill-rule="evenodd">
                    <path data-v-c1d21be8="" d="M1.795 1.074L7.942 1.074 7.942 7.221M7.942 1.074L1.378 7.638"
                      transform="translate(-935 -867) translate(836 848) translate(14 14) translate(85 5)"></path>
                  </g>
                </svg></a></li>
            <li><a href="https://www.hanbit.co.kr/life/" target="_blank">한빛라이프<svg data-v-c1d21be8="" data-v-f12001e6=""
                  xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 9" class="ico_outlink">
                  <g data-v-c1d21be8="" fill="none" fill-rule="evenodd">
                    <path data-v-c1d21be8="" d="M1.795 1.074L7.942 1.074 7.942 7.221M7.942 1.074L1.378 7.638"
                      transform="translate(-935 -867) translate(836 848) translate(14 14) translate(85 5)"></path>
                  </g>
                </svg></a></li>
            <li><a href="https://www.hanbit.co.kr/edu/" target="_blank">한빛에듀<svg data-v-c1d21be8="" data-v-f12001e6=""
                  xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 9" class="ico_outlink">
                  <g data-v-c1d21be8="" fill="none" fill-rule="evenodd">
                    <path data-v-c1d21be8="" d="M1.795 1.074L7.942 1.074 7.942 7.221M7.942 1.074L1.378 7.638"
                      transform="translate(-935 -867) translate(836 848) translate(14 14) translate(85 5)"></path>
                  </g>
                </svg></a></li>
            <li><a href="https://www.hanbitn.com/" target="_blank">한빛앤<svg data-v-c1d21be8="" data-v-f12001e6=""
                  xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 9" class="ico_outlink">
                  <g data-v-c1d21be8="" fill="none" fill-rule="evenodd">
                    <path data-v-c1d21be8="" d="M1.795 1.074L7.942 1.074 7.942 7.221M7.942 1.074L1.378 7.638"
                      transform="translate(-935 -867) translate(836 848) translate(14 14) translate(85 5)"></path>
                  </g>
                </svg></a></li>
            <li><a href="/realtime" target="_blank">리얼타임<svg data-v-c1d21be8="" data-v-f12001e6=""
                  xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 9" class="ico_outlink">
                  <g data-v-c1d21be8="" fill="none" fill-rule="evenodd">
                    <path data-v-c1d21be8="" d="M1.795 1.074L7.942 1.074 7.942 7.221M7.942 1.074L1.378 7.638"
                      transform="translate(-935 -867) translate(836 848) translate(14 14) translate(85 5)"></path>
                  </g>
                </svg></a></li>
            <li><a href="https://www.hanbit.co.kr/textbook/" target="_blank">한빛정보교과서<svg data-v-c1d21be8=""
                  data-v-f12001e6="" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 9" class="ico_outlink">
                  <g data-v-c1d21be8="" fill="none" fill-rule="evenodd">
                    <path data-v-c1d21be8="" d="M1.795 1.074L7.942 1.074 7.942 7.221M7.942 1.074L1.378 7.638"
                      transform="translate(-935 -867) translate(836 848) translate(14 14) translate(85 5)"></path>
                  </g>
                </svg></a></li>
            <li><a href="https://www.hanbit.co.kr/rent/" target="_blank">한빛대관서비스<svg data-v-c1d21be8="" data-v-f12001e6=""
                  xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 9" class="ico_outlink">
                  <g data-v-c1d21be8="" fill="none" fill-rule="evenodd">
                    <path data-v-c1d21be8="" d="M1.795 1.074L7.942 1.074 7.942 7.221M7.942 1.074L1.378 7.638"
                      transform="translate(-935 -867) translate(836 848) translate(14 14) translate(85 5)"></path>
                  </g>
                </svg></a></li>
            <li><a href="https://devground.hanbit.co.kr//newletter/" target="_blank">데브레터<svg data-v-c1d21be8=""
                  data-v-f12001e6="" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 9" class="ico_outlink">
                  <g data-v-c1d21be8="" fill="none" fill-rule="evenodd">
                    <path data-v-c1d21be8="" d="M1.795 1.074L7.942 1.074 7.942 7.221M7.942 1.074L1.378 7.638"
                      transform="translate(-935 -867) translate(836 848) translate(14 14) translate(85 5)"></path>
                  </g>
                </svg></a></li>
            <li><a href="https://hongong.hanbit.co.kr/" target="_blank">혼공러들의 스터디공간<svg data-v-c1d21be8="" data-v-f12001e6=""
                  xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 9" class="ico_outlink">
                  <g data-v-c1d21be8="" fill="none" fill-rule="evenodd">
                    <path data-v-c1d21be8="" d="M1.795 1.074L7.942 1.074 7.942 7.221M7.942 1.074L1.378 7.638"
                      transform="translate(-935 -867) translate(836 848) translate(14 14) translate(85 5)"></path>
                  </g>
                </svg></a></li>
          </ul>
        </div>
        <script>
          $(document).ready(function () {
            $("[data-family-menu]").click(function () {
              $("[data-family-menu-ul]").toggle().toggleClass('family_menu_ul');
              $("[data-family-icon]").toggleClass("rotate");
            });

            $('html').unbind("click").bind("click", function (e) {
              if (!$(e.target).closest(".family_menu").hasClass('family_menu')) {
                $("[data-family-menu-ul]").hide().removeClass(
                  'family_menu_ul'
                );

                $("[data-family-icon]").removeClass("rotate");
              }
            });
          });
        </script>
        <!--//패밀리사이트-->
      </div>
    </div>
    <!-- //한빛 정보 -->
  </div>

  <div class="copyright">©1993-2024 Hanbit Publishing Network, Inc. All rights reserved.</div>
</footer>
<!-- //푸터 -->
<div class="foot_download_btn"><a href="https://www.hanbit.co.kr/support/supplement_list.html">자료실</a></div>

<!-- 공통 JS 호출 -->
<script type="text/javascript" src="/js/common.js"></script>
<!-- //공통 JS 호출 -->

<!-- 퀵배너 -->
<section class="fly_menu_wrapper">
  
  <h3 class="fly_menu_h2">최근 본 상품<span class="fly_menu_count">0</span></h3>

  <div style="max-height:520px; overflow-y: scroll;">
          <div></div>
        </div>
</section>
<script>
  jQuery(function ($) {
    // 오늘 본 도서 top 속성값
    var fly_menu_wrapper_top = $(".fly_menu_wrapper").css("top").replace('px', '');

    // 상하 스크롤에 따른 최근 본 책 위치이동
    $(window).scroll(function () {
      var scrollValue = $(document).scrollTop();

      $(".fly_menu_wrapper").css(
        "top",
        (scrollValue > fly_menu_wrapper_top) ? $("#wrap_gnb").height() + "px" : fly_menu_wrapper_top + "px"
      )
    });
  });
</script>
<!-- //퀵배너 -->

</body>

</html>