레이아웃 메니저(LayoutManager)

자바에서 컨테이너안에 버튼이나 체크박스 같은 컴포넌트를 보기좋게 배치하는 일은 쉬운 일이 아닙니다. 그럴때 레이아웃 메니저를 사용하면 됩니다. 레이아웃 메니저는 컨테이너가 컴포넌트를 어떻게 배치할 것인지를 도와주는 클래스들입니다. 여기서 소개서는 레이아웃 4개를 소개합니다. 그 4개가 BorderLayout, FlowLayout, GridLayout, CardLayout입니다. 이번 포스팅은 이 4개의 레이아웃 메니저에 대해 무슨 특징이 있고 어떻게 사용하면 되는 것인지 알아보도록 하겠습니다.

공통적으로 컨테이너에게 레이이웃 메니저를 적용시키려면 setLayout메소드로 사용할 LayoutManager를 지정해주시면 됩니다. 

1. BorderLayout

BorderLayout은 컴포넌트를 동, 서, 남, 북, 중앙으로 배치시킵니다. 아래의 코드를 통해서 화면배치가 어떻게 이루어지는 지 확인해봅시다.

public class Main {
	
	public static void main(String[] ar){
		Frame frame=new Frame("Reakwon");
		
		frame.setSize(500,300);
		frame.setLayout(new BorderLayout());
		Button eastButton=new Button("East");
		Button westButton=new Button("West");
		Button southButton=new Button("South");
		Button northButton=new Button("North");
		Button centerButton=new Button("Center");
		
		frame.add("East",eastButton);
		frame.add("West",westButton);
		frame.add("South",southButton);
		frame.add("North",northButton);
		frame.add("Center",centerButton);
		
		frame.setVisible(true);
	}
}

BorderLayout

 

Frame의 add()로 위치도 같이 지정해줍니다. add("위치", Component) 또는 add(Component, "위치")로 구분없이 사용할 수 있습니다. 이때 위치의 종류에는 "East", "West", "South", "North", "Center" 가 있는데, 각각은 동, 서, 남, 북, 가운데의 위치를 나타냅니다. 앞글자는 항상 대문자여야합니다.

Panel 사용하기 - 다른 Component를 더 배치

이렇게 버튼 5개만 추가하기보다는 가운데에 더 많은 Component를 결합하여 Frame을 구성하고 싶다면 어떻게하면 좋을까요? 이때 사용할 수 있는 것이 Panel입니다. Panel은 Frame과 같이 자신의 영역이 있어서 그 안에서 다른 Component를 구성할 수 있습니다. Panel은 두 종류가 있는데, 자신의 영역에서 담고 있는 컴포넌트들이 넘친다면 스크롤되지 않는 Panel과 스크롤될수 있는 ScrollPane이 있습니다. 여기서는 단순 Panel만을 사용하는 예를 보도록 하겠습니다.

public static void main(String[] ar){
	Frame frame=new Frame("Reakwon");
	frame.setSize(500,300);
	frame.setLayout(new BorderLayout());
	Button eastButton=new Button("East");
	Button westButton=new Button("West");
	Button southButton=new Button("South");
	Button northButton=new Button("North");
		
    //Panel의 Component 추가    
	Panel panel=new Panel(new BorderLayout());
	Button panelSouth=new Button("Panel_South");
	Button panelNorth=new Button("Panel_North");
	Button panelCenter=new Button("Panel_Center");
	panel.add("South",panelSouth);
	panel.add("North",panelNorth);
	panel.add("Center",panelCenter);
		
		
	frame.add("East",eastButton);
	frame.add("West",westButton);
	frame.add("South",southButton);
	frame.add("North",northButton);
    //Center에 panel 추가
	frame.add("Center",panel);
		
	frame.setVisible(true);
}

 

Frame과 별 차이가 있나요? Frame과 같이 add로 버튼을 추가할 수 있습니다. 그리고 Frame은 그런 Panel을 추가할 수 있죠. 왜냐면 Panel 역시 Component이니까요. 다음 보이는 배치가 위 코드의 결과입니다.

 

간격 넓히기

BorderLayout의 생성자를 주어서 상하 좌우 간격을 조절 할 수 있습니다. 아래처럼 생성자를 이용하는 방법이 있습니다. 처음 arguement는 Horizontal(좌우)의 간격, 두번째는 Vertical(상하)의 간격입니다.

frame.setLayout(new BorderLayout(10,2));

 

생성자를 사용하지 않고 이미 생성된 객체에서 setHgap과 setVgap을 통해서도 바꿀 수 있습니다. 위의 생성자를 통한 간격은 아래의 메소드를 통한 간격 조정과 동일하게 됩니다.

	BorderLayout layout=new BorderLayout();
	layout.setHgap(10);
	layout.setVgap(2);
	frame.setLayout(layout);

 

어떻게 변하는지 볼까요?

 

2. FlowLayout

이름부터 직관적으로 알 수 있듯이 기본적으로 물 흐르듯 먼저 더해진 Component들이 왼족에서 오른쪽으로 추가되고  모두 가운데로 정렬이 되며 자리가 모자라면 아랫줄에 Component들이 배치됩니다. 아래와 같이 FlowLayout을 사용하여 Layout 배치를 해보도록 하겠습니다.

public class Main {
	
	public static void main(String[] ar){
		Frame frame=new Frame("Reakwon");
		frame.setSize(500,300);
		frame.setLayout(new FlowLayout());
		
		for(int i=1;i<=10;i++) {
			frame.add(new Button("Button"+i));
		}
		
		frame.setVisible(true);
	}
}

 

setLayout으로 frame의 레이아웃을 FlowLayout으로 정하고 난 이후에 Frame에 버튼 10개를 추가해줍니다. 이런 Frame이 결과로 보여질겁니다.

Flowlayout1

 

왼쪽부터 차례대로 버튼이 추가되는 것을 알 수 있습니다. 이 프레임 사이즈를 줄여보세요. 자리가 모자르면 아래처럼 밑으로 버튼들이 내려가는 것을 확인할 수 있습니다.

Flowlayout2

 

정렬하는 방법에는 왼쪽정렬(FlowLayout.LEFT), 가운데 정렬(FlowLayout.CENTER), 오른쪽 정렬(FlowLayout.RIGHT)이 있고 간격도 역시 조정할 수 있습니다. 이 모든것은 생성자를 통해서도 가능하고 메소드를 통해서도 가능합니다. 간격 조정은 BorderLayout과 같고, 정렬하는 메소드는 setAlignment()를 이용하면 됩니다. 

위의 코드에서 생성자를 이렇게 고쳐봅시다.

frame.setLayout(new FlowLayout(FlowLayout.RIGHT,10,20));	//오른쪽 정렬, 좌우 간격, 상하 간격

 

그러면 정렬과 간격이 변한것을 볼 수 있습니다.

 

3. GridLayout

격자형태의 Component를 배치하고 싶다면 GridLayout을 사용하시면 됩니다. 기본적으로 생성자에 아무것도 없다면 한 행에 컴포넌트를 배치합니다. 그래서 행과 열의 수를 지정하여 사용합니다. 

GridLayout을 이용해서 Component를 배치해봅시다.

public static void main(String[] ar){
	Frame frame=new Frame("Reakwon");
	frame.setSize(500,300);
		
	frame.setLayout(new GridLayout(4, 3, 10, 3)); //행, 열 , 좌우 간격, 상하 간격
	for(int i=1;i<=9;i++) {
		frame.add(new Button(""+i));
	}
	frame.add(new Button("*"));
	frame.add(new Button("0"));
	frame.add(new Button("#"));
		
	frame.setVisible(true);
}

 

4행 3열의 격자 형태의 레이아웃을 사용했습니다. 그리고 좌우의 간격, 상하의 간격도 주어 Component를 떨어뜨렸지요. 보통 계산기 같은 버튼의 격자형 배치를 만들때 GridLayout이 많이 사용되며 사진의 격자형 배치고 GridLayout을 사용할 수 있습니다.

GridLayout

 

 

4. CardLayout

CardLayout은 카드를 겹쳐놓은듯이 배치하고 이전의 카드, 다음의 카드를 보여주듯 Component 또는 Container를 보여줍니다. 사용법은 아래와 같습니다.

public static void main(String[] ar){
	Frame frame=new Frame("Reakwon");
	frame.setSize(500,300);
		
	Panel card1=new Panel();
	card1.setBackground(Color.DARK_GRAY);
	card1.add(new Label("Card 1"));
		
	Panel card2=new Panel();
	card2.setBackground(Color.ORANGE);
	card2.add(new Label("Card 2"));
		
	CardLayout layout=new CardLayout();
	frame.setLayout(layout);
		
		//card1의 이름은 "card1" 
	frame.add(card1, "card1");
    	//card2의 이름은 "card2"
	frame.add(card2, "card2");
		
	layout.show(frame, "card1");
	frame.setVisible(true);
	
    //5초 대기
	try {
		Thread.sleep(5000);
	}catch(InterruptedException e) {
		e.printStackTrace();
	}
	//5초 후 frame에 추가한 다음 card를 보여줌
	layout.next(frame);
}

 

여기에는 Panel을 두개 사용하여 card1과 card2라는 변수로 이름을 정해줍니다. 이 둘을 구분하기 위해서 배경색과 이름을 보여주는 Label을 추가했습니다. Frame은 CardLayout으로 세팅하는데, 여기서 객체를 계속사용해야하니까 객체를 생성하여 setLayout의 인자로 전달해줍니다. 그리고 차례대로 card1, card2를  Frame에 추가하죠. 이제 가장 처음 보여야할 container를 정하는데 show()메소드를 이용합니다. 

그럼 현재 보이는 것은 card1입니다. 그리고 5초를 기다린 후 next() 메소드로 카드를 다음 것으로 바꿔줍니다. CardLayout은 메소드에 Container를 전달해줘야하는데, CardLayout이 설정된 Container를 전달하면 되는데 여기서는 frame이라는 개체가 그 Container입니다. 이 밖에도 처음, 마지막의 카드로 바꿔줄때는 아래와 같이 사용하면 됩니다.

layout.first(frame);
layout.last(frame);

 

우리가 구현한 위 코드의 결과는 아래처럼 처음 보이는 것은 card1입니다.

Card1

이후에 5초가 흐르면 card2 패널로 바뀌죠.

 

아직은 Event에 대해서 자세히 배우지 않았기 때문에 Thread를 잠시 sleep 시킨후 다음 카드로 변경했는데, Event를 배우면 버튼과 이것 저것을 합쳐서 Card를 바꿀 수 있습니다. 

 

이처럼 JAVA AWT에서 사용하는 LayoutManager에 대한 기본적인 개념과 사용법을 알아보았습니다. 어려운 개념이 있었나요? 우리는 LayoutManager를 사용하여 Component를 효율적으로 그리고 쉽게 배치할 수 있습니다. 이 LayoutManager들은 Swing에서도 그대로 사용됩니다.

 

반응형
블로그 이미지

REAKWON

와나진짜

,

AWT(Abstract Window Toolkit)

AWT는 이름에서도 알 수 있듯이 Window프로그래밍을 하기 위한 GUI(Graphical User Interface)의 도구입니다. GUI가 무엇인지 모르시나요? 여러분이 Windows의 운영체제를 사용한다면 여기 보이는 화면들이 GUI라고 한답니다. 이전의 컴퓨터들은 명령어 방식의 UI를 썼다면 이제 그래픽이 추가되어 X버튼을 눌러 창을 닫고 하고 있지요. 그리고 JAVA는 AWT를 이용해서 윈도우의 GUI로 된 프로그램을 제작할 수 있습니다. 우리가 AWT를 클래스들을 이용하려면 java.awt.*과 동작에 대한 정의를 하려면 java.awt.event.*를 import하여 사용해야합니다.

이제 본격적으로 AWT에 대해서 알아보도록 하겠습니다. GUI에서 각각의 요소들을 우리는 컴포넌트라고 하며 클래스의 상속 계층도에서 메뉴(MenuComponent)를 제외한 모든 컴포넌트는 Component라는 클래스를 상속받고 있습니다. 이 컴포넌트 중에 조금 특별한 컴포넌트가 있는데 그 이름은 컨테이너(Container)입니다. 이름에서도 알 수 있듯이 무엇을 포함하는 클래스인것 같죠? 다른 컴포넌트를 포함하거나 컨테이너도 포함할 수 있습니다. 그래서 우리는 처음 시작할때 이 Container부터 배울 겁니다. 그 중 가장 일반적인 컨테이너는 Frame이라고 합니다.

아참, AWT보다 자바에서 진보적인 GUI는 Swing입니다. Swing이 여러면에서 더 성능이 좋긴한데, 사용법은 AWT와 거의 동일하므로 AWT 먼저 배우고 난 이후에 Swing 컴포넌트를 배워도 상관없습니다. 그리고 AWT 그래픽이 우리한테는 더 친숙할 겁니다. Swing컴포넌트들은 앞에 J가 붙습니다. 예를들어 Frame의 Swing컴포넌트 버전은 JFrame이죠.

이제는 코드를 짜면서 Frame이 무엇인지 알아보고 어떤 기능이 있는지 배워봅시다.

Frame 알아보기

우선 아래의 코드를 쓰고 실행해봅시다. 

import java.awt.*;
public class Main {
	
	public static void main(String[] ar){
		Frame frame=new Frame("Title!");
		frame.setSize(500,300);
		
	}
}

 

Frame을 생성하는 코드인데 생성자에는 Title명을 받는 생성자를 사용했습니다. 그리고 너비와 높이는 500, 300 pixel로 맞춰줍니다. 그리고 실행을 하면 아무것도 뜨지 않습니다. 왜냐면 프레임을 보이게 하려면 setVisible()을 true로 설정해야 보이기 때문이죠. 그래서 위 코드를 아래와 같이 수정해보면 이제 Frame이라는 것이 나올 겁니다.

public static void main(String[] ar){
	Frame frame=new Frame("Title!");
	frame.setSize(500,300);
	frame.setVisible(true);
}

 

자, Frame이 생성되었다는 것을 확인할 수 있습니다. 그리고 Title부분은 우리가 지정한 문자열과 같다는 것을 알 수 있습니다. 이제 구경했으니 닫아봅시다. 

Frame

 

닫히시나요? X버튼을 클릭해도 아무런 변화가 없다는 것을 알 수 있을 겁니다. 왜냐면 X버튼이 눌렸을때 JAVA가 이 프로그램을 닫아야하는지, 혹은 멈춰야하는지, 또는 확인 종료를 묻는 대화창을 띄어야하는지 우리가 정의를 해준적이 없죠. 그래서 X버튼을 눌렸을때를 우리는 Event, 즉 X버튼을 누르는 Event가 발생했다고 하고 동작을 처리해주는 것을 우리는 EventHandling이라고 합니다. 아직은 배우지 않았으니 Eclipse의 화면에 아래의 빨간색을 눌러 종료합시다.

 

X버튼 이벤트 발생시 Handling - WindowListener 추가하기

혹은 잠깐 맛보고 싶다면 아래의 코드를 추가하고 X버튼을 눌러보세요. 추가하기전에 import java.awt.event.*를 하여 import해주세요.

	frame.setVisible(true);
	frame.addWindowListener(new WindowListener() {

		@Override
		public void windowOpened(WindowEvent e) {}

		@Override
		public void windowClosing(WindowEvent e) {
			// TODO Auto-generated method stub
			e.getWindow().setVisible(false);	//보이지 않게	
			e.getWindow().dispose();			//메모리에서 제거
			System.exit(0);						//프로그램 종료
		}

		@Override
		public void windowClosed(WindowEvent e) {}
		@Override
		public void windowIconified(WindowEvent e) {}
		@Override
		public void windowDeiconified(WindowEvent e) {}
		@Override
		public void windowActivated(WindowEvent e) {}
		@Override
		public void windowDeactivated(WindowEvent e) {}
			
	});

 

setLocation() 으로 위치 조정

Frame이 자꾸 왼쪽 구석에만 뜨는데, 여간 불편한게 아니죠. 이때 위치를 조정할 수 있도록 setLocation() 메소드가 존재합니다. 위치는 Frame의 가장 왼쪽, 위 꼭지점을 위치로 잡습니다. 이것도 역시 pixel단위로 화면 왼쪽(x축)에서의 거리, 그리고 화면 위쪽에서의 거리(y축)를 지정할 수 있습니다. 화면 가운데에 Frame을 위치시키려면 이렇게 하면 됩니다. 

x축 위치 = (모니터의 화면 너비/2) - (프레임 너비/2)

y축 위치 = (모니터의 화면 높이/2) - (프레임 높이/2)

모니터의 화면 너비와 높이를 얻어오려면 Toolkit 클래스를 이용해서 아래의 코드처럼 구현하면 됩니다.

	frame.setSize(500,300);
	Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
	frame.setLocation((dim.width/2)-(frame.getWidth()/2), (dim.height/2)-(frame.getHeight()/2));
	frame.setVisible(true);

 

이 후 실행하게 되면 Frame이 정중앙에 보이는 것을 확인할 수 있습니다. 하지만 이것보다 더 간편한 방법이 존재하는데요. 위의 dim과 setLocation의 두줄이 아래 한줄로 표현될 수 있습니다. 바꿔서 실행해도 정중앙에 보이는것을 확인할 수 있습니다.

frame.setLocationRelativeTo(null);

 

배경색을 지정하기 - setBackground()

조금 심심할 수도 있으니 setBackground() 메소드로 배경색을 지정할 수도 있습니다. setBackground()에 전달할 Color의 객체를 사용한다면 원하는 색으로 바꿔줄 수 있습니다. 

frame.setBackground(new Color(180,211,211));

 

또는 Color의 static 멤버로도 가능하긴 합니다.

add 메소드로 다른 Component 추가해보기

Frame에서 다른 Component를 추가해보려면 add 메소드를 이용하면 됩니다. 여기서는 Button 컴포넌트를 사용해서 버튼을 추가해봤습니다. 

	Button button = new Button("My Button");
	frame.add(button);

 

아래처럼 버튼이 Frame을 꽉채운 것을 볼 수 있습니다. 그리고 아무리 눌러봤자 우리는 event를 handling하지 않았으므로 아무런 변화가 없습니다. 

 

이처럼 버튼 뿐만 아니라 다른 Component들도 add메소드로 추가가 가능합니다. 예를들어 체크박스 (Checkbox), 라디오 버튼(RadioButton) 등 말이죠. 한번 JAVA API doc을 참고하여 추가해보고 가지고 놀아보세요.

이상으로 Frame에 관한 간략한 사용법을 알아보았습니다. 다음 포스팅도 GUI의 포스팅을 이어나가도록 하겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

MessageFormat

MessageFormat클래스는 양식에 맞게 메시지를 형식화하는 편리한 클래스입니다. 주로 DB query에 이 MessageFormat이 쓰이는데, 한번 코드를 보면서 이해하도록 하겠습니다.

기본적인 클래스 사용법은 이렇습니다. 

MessageFormat format=new MessageFormat(
		"Name : {0}\n"+
		"Age : {1}\n"+
		"Name, Age : {0}, {1}");
String[] args= {"reakwon","7"};
System.out.println(format.format(args));

 

중괄호의 숫자가 Arguement들의 인덱스를 나타냅니다. Arguement들은 배열의 행태로 위의 args변수에 문자열 배열로 담겨있습니다. 그래서 {0}은 "reakwon", {1}은 "7"로 치환되게 됩니다. 그래서 그 결과는 아래와 같습니다. 

Name : reakwon
Age : 7
Name, Age : reakwon, 7

 

이런 MessageFormat의 사용처는 MySQL과 같은 query에 주로 사용합니다. SELECT * FROM my_table WHERE id = 'reakwon' 이런 쿼리들을 일반화할때 SELECT * FROM {0} WHERE {1} = '{2}' 식으로 사용할 수가 있죠.

MessageFormat format=new MessageFormat(
		"SELECT * FROM {0} WHERE {1} = {2}");
String[] args= {"my_table","id","'reakwon'"};
System.out.println(format.format(args));

 

결과

 

SELECT * FROM my_table WHERE id = 'reakwon'

 

static 메소드로 객체 생성하지 않고 사용할 수도 있습니다. 일회성으로 사용하려면 아래의 방법으로 간편하게 사용할 수도 있지요. MessageFormat의 static 메소드를 바로 사용하는 것을 알 수 있죠.

	String message="SELECT * FROM {0} WHERE {1} = {2}";
	String[] args= {"my_table","id","'reakwon'"};
	System.out.println(MessageFormat.format(message,args));

 

날짜 형식 

MessageFormat은 문자열로만 받는 것은 아닙니다. 날짜와 시간도 이런식으로 받아볼 수 있습니다. 아래의 예시처럼 출력할 수도 있습니다. 

	String message= "오늘 Date: {0,date}, Time :{0,time}";
	System.out.println(MessageFormat.format(message, new Date()));

 

결과

 

오늘 Date: 2021. 4. 6., Time :오전 12:04:34

 

쉼표와 date, time 등으로 날짜와 시간도 지정할 수 있습니다. new Date()로 시간과 날짜를 볼 수 있습니다. 이때 더 많은 정보를 보고 싶다면 아래처럼 사용하면 됩니다.

String message= "오늘 Date: {0,date,full}, Time :{0,time,full}";
System.out.println(MessageFormat.format(message, new Date()));

 

아래와 같이 요일까지 출력이되며 시간 같은 경우에는 나라의 정보도 확인할 수 있습니다.

 

오늘 Date: 2021년 4월 6일 화요일, Time :오전 12시 7분 11초 대한민국 표준시

 

반대로 간략한 정보를 원한다면 short로 바꿔서 입력해주면 됩니다. 더 많은 FormatType과 FormatStyle을 보시려면 지원하는 형식은 아래를 참고해주세요. 

java8 api document

 

기존의 형식 패턴을 applyPattern()으로 다시 지정해서 사용할 수도 있습니다. 이런 방식은 static 메소드가 아닌 객체를 만들어서 계속 사용할때 사용하는 방법입니다.

	String message= "오늘 Date: {0,date,short}, Time :{0,time,short}";
	MessageFormat format = new MessageFormat(message);
	Object[] args= {new Date()};
	System.out.println(format.format(args));
		
	String newMessage="자세히 \n"+
			"오늘 Date: {0,date,full}, Time :{0,time,full}";
	format.applyPattern(newMessage);
	System.out.println(format.format(args));
		

 

결과

 

오늘 Date: 21. 4. 6., Time :오전 12:14
자세히 
오늘 Date: 2021년 4월 6일 화요일, Time :오전 12시 14분 29초 대한민국 표준시

 

자, 여기까지 MessageFormat에 대한 소개와 사용 예제를 알아보았습니다. 이런 형식 클래스들은 유용한 경우가 많으니 알아두시면 좋을 것 같습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

DecimalFormat 클래스

우리의 일상에서 가장 많이 쓰이는 진수, 10진수를 형식화하는 역할을 하는 클래스가 JAVA에서 DecimalFormat이라는 클래스입니다. 이 클래스는 NumberFormat을 상속하고 있는 클래스이죠. 10진수를 다양한 형식에 맞게 출력해줄 수 있습니다. C언어에서는 이미 fprintf 등이 그 역할을 담당하고 있죠. 이제부터 어떻게 사용되는지 배워보도록 하겠습니다.

 

Decimal Pattern 적용

DecimalFormat은 java.text 패키지안에 존재하므로 따로 import를 해야합니다.  DecimalFormat은 문자 '#'과 '0'이 숫자를 나타내는데 쓰이며 쉼표(,), 마침표(.), 대시(-) 등으로 숫자 형식을 나타낼 수 있습니다. 많이 사용패턴은 아래의 표에 정리해놓았습니다.

 

 

Format 설명
0 10진수, 값이 없는 자리는 0으로 채움
# 10진수, 값이 없는 자리는 나타나지 않음
. 소수점을 이하 나타냄
- 음수 부호를 나타냄
, 단위 구분자를 나타냄
E 지수 기호를 나타냄, E 이후 0를 써서 표현(ex E0)
% 퍼센트 기호
' escape문자, 만약 #을 문자로 나타내고 싶다면 '#' 으로 표현
그외 문자 문자로 취급

 

 

DecimalFormat을 사용하는 방법은 생성자를 통해서 형식을 지정해주는 방법이 있습니다. 아래처럼 말이죠.

DecimalFormat format=new DecimalFormat("###,###.#######");

 

또는 applyPattern을 사용하여 패턴을 적용할 수 있습니다.

format.applyPattern("###,###.#######");

 

 

 

중요한것은 우리가 패턴을 적용하고 난 후 형식에 맞는 문자열을 뽑아와야합니다. 이때 format이라는 메소드를 사용하지요. 아래와 같이 사용합니다.

double n = 11223344.5678;
String formattedStr = format.format(n);

 

패턴과 사용예

DecimalFormat의 사용법은 어렵지 않습니다. 주로 사용하는 방식은 패턴을 지정하고 난 후 format으로 지정된 형식을 가진 숫자 형식 문자열을 가져오는 용도가 대부분입니다. 아래의 코드를 통해서 어떻게 출력되는지 확인해보세요.

public static void main(String[] ar){
		
	DecimalFormat format=new DecimalFormat();
		
	String patterns[]= {
			"0",
			"#",
			"0.0",
			"000.000",				//소수점
			"000,000,000.0",
			"000,000,000.000",
			"000,000,000.000000",	//숫자가 나타나지 않는 경우 나머지 빈자리를 0으로 채워줌
			"#,#,#,#.###",			//한글자씩 ,이 붙어서 나옴
			"###,###,###.#",
			"###,###,###.###",
			"###,###,###.######",
			"-###,###,###.######",	//숫자가 나타나지 않는 경우 출력하지 않음
			"###.##E0",				//지수 형식으로 출력
			"my number: ###.##%",	//my number라는 문자열이 합쳐짐
			"'#' ###,###.####",		//escape로 #을 문자화
			"'0' 000,000.00000000",	//escape로 0을 문자화
			"###,###.000000000"	//섞어서도 쓸 수 있음
	};
		
	double number=1234123123.1234;
		
	for(int i=0;i<patterns.length;i++) {
		format.applyPattern(patterns[i]);
		System.out.println("[pattern "+patterns[i]+"] "+format.format(number));
	}
		
}

 

결과

 

 

 

[pattern 0] 1234123123
[pattern #] 1234123123
[pattern 0.0] 1234123123.1
[pattern 000.000] 1234123123.123
[pattern 000,000,000.0] 1,234,123,123.1
[pattern 000,000,000.000] 1,234,123,123.123
[pattern 000,000,000.000000] 1,234,123,123.123400
[pattern #,#,#,#.###] 1,2,3,4,1,2,3,1,2,3.123
[pattern ###,###,###.#] 1,234,123,123.1
[pattern ###,###,###.###] 1,234,123,123.123
[pattern ###,###,###.######] 1,234,123,123.1234
[pattern -###,###,###.######] -1,234,123,123.1234
[pattern ###.##E0] 1.2341E9
[pattern my number: ###.##%] my number: 123412312312.34%
[pattern '#' ###,###.####] # 1,234,123,123.1234
[pattern '0' 000,000.00000000] 0 1,234,123,123.12340000
[pattern ###,###.000000000] 1,234,123,123.123400000

 

이상으로 간단하게 DecimalFormat클래스를 소개했고 사용법을 알아보았습니다. 워낙 어렵지 않은 형식 클래스이고 편리하게 십진수를 표현할 수 있으므로 적어도 #과 0의 차이와 format() 메소드만 알고 있으면 무난히 사용할 수 있겠습니다.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

예외(Exception)

프로그램을 실행하다가 보면 어떤 원인때문에 비정상적인 동작을 일으키며 프로그램이 종료되는 상황을 보신적 있으실 겁니다. 이때 우리는 프로그램이 오류가 발생했다고 합니다. 에러의 종류는 우리가 컴파일할때 발생할 수 있는 컴파일 오류와 실행 중 발생되는 런타임 오류 두 종류가 있지요. 컴파일 오류는 우리가 잡기가 쉽지만, 런타임 오류는 잡기가 까다롭습니다. 자바에서는 런타임 오류를 두 종류로 보고 있습니다. 바로 에러(Error)예외(Exception)으로 말이죠. 

에러는 프로그램이 코드로 복구될 수 없는 오류를 의미하고 예외는 프로그래머가 직접 예측하여 막을 수 있는 처리가능한 오류라고 보시면 됩니다. 예를 들어 메모리가 부족한 경우 프로그래머가 직접 제어할 수 없으므로 이런 경우는 메모리 부족(OutOfMemoryError) 에러가 발생하고 함수 호출이 많아 스택이 쌓일 경우에는 StackOverFlowError가 발생할 수 있습니다.

그런데 아래의 코드처럼 어떤 수를 0으로 나눈다면 어떤 상황이 발생할까요?

int a,b;
a=10;
b=0;
		
int c=a/b;
System.out.println(c);

어떤 수를 0으로 나눌수는 없기 때문에 오류를 내보내게 됩니다.

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at aa.Main.main(Main.java:11)

 

하지만 조건문을 통해서 우리는 0으로 못나누게 할 수 있죠. 이처럼 우리가 예측가능한 상황에서 오류를 제어할 수 있는것이 예외입니다.

예외는 Compile시에 발견할 수 있는 예외와 프로그램 실행시에 발생하는 예외 두 종류가 있습니다. Compile시에 발생할 수 있는 예외는 아래의 사진과 같이 Eclipse와 같은 IDE를 쓰신다면 빨간줄로 예외를 처리하라고 욕합니다.

하지만 위에서의 예처럼 Compile시에 발견하지 못하는 에러를 Runtime에러라고 하는데, 이때는 프로그래머가 예측하여 처리해주어야합니다. 

그리고 그런 예외가 발생했을때 어떤 동작을 처리해야하는지를 우리는 예외 처리라고 합니다.

예외 처리

1. try, catch

예외가 발생했을때 우리는 try ... catch ... finally 라는 키워드로 예외를 처리할 수 있거나 메소드를 호출한 곳으로 던질 수 있습니다. 한 가지 중요한 점은 자바에서 모든 예외는 Exception이라는 클래스를 상속받습니다. Exception의 상속 트리를 아래에 간략하게 나타내었습니다.

예외 처리하는 방식은 이렇습니다.

try{
	//예외가 발생될만한 코드
}catch(FileNotFoundException e){	//FileNotFoundException이 발생했다면

}catch(IOException e){ //IOException이 발생했다면

}catch(Exception e){	//Exception이 발생했다면

}finally{	
	///어떤 예외가 발생하던 말던 무조건 실행
}

 

try 블록 : 이 블록에서 예외가 발생할만한 코드가 쓰여집니다. 

catch (예외 종류) 블록 : 이 부분에서 예외가 발생되었을때 처리하는 동작을 명시합니다. catch블록은 여러 개가 있을 수 있습니다. 맨 처음 catch 블록에서 잡히지 않는 예외라면 다음 catch의 예외를 검사합니다. 이때 상속관계에 있는 예외 중 부모가 위의 catch, 그리고 그 자식 예외 클래스가 아래의 catch로 놓일 순 없습니다. 예를 들어 아래와 같이말이죠.

try{
	//.. 중략 ..//
} catch (Exception e){
	//컴파일 오류 발생
} catch (IOException e){

}

Exception 클래스는 모든 예외의 부모이기 때문에 Exception을 IOException보다 위에서 처리할 수는 없다는 뜻입니다. 왜냐면 IOException의 catch블록은 도달할 수 없는 코드이기 때문이죠.

finally 블록 : 여기서는 예외가 발생하건 발생하지 않건 공통으로 수행되어야할 코드가 쓰여집니다.  임시 파일의 삭제 등 뒷정리 코드가 쓰입니다.

이것을 이용해서 우리는 위의 코드를 예외처리할 수 있습니다.

public static void main(String[] ar){
	int a,b;
	a=10;
	b=0;
	try {
		int c=a/b;
		System.out.println(c);	//예외발생으로 실행 불가한 코드
	}catch(ArithmeticException e) {
		System.out.println("ArithmeticException 발생");
		System.out.println("0으로 나눌 수는 없습니다");
		e.printStackTrace();
	}finally {
		System.out.println("finally 실행");
	}
}

 

printStackTrace()라는 메소드는 어느 부분에서 예외가 발생했는지 알려주는 추적로그를 보여줍니다. Exception이 발생했을때 기본 동작이죠. 결과는 아래와 같은 것을 알 수 있습니다.

ArithmeticException 발생
java.lang.ArithmeticException: / by zero
	at aa.Main.main(Main.java:11)		//Main.java에서 11번째 줄에서 발생했다는 printStackTrace
finally 실행

 

2. throws

아까전에 예외를 그냥 던질 수 있다고 했죠? 그 의미가 어떤 의미냐면 예외를 여기서 처리하지 않을테니 나를 불러다가 쓰는 녀석에게 에러 처리를 전가하겠다는 의미이며 코드를 짜는 사람이 이 선언부를 보고 어떤 예외가 발생할 수 있는지도 알게 해줍니다. 어떤 뜻인지 모르겠다구요? 아래의 코드를 통해서 알아보도록 합니다. 

public static void divide(int a,int b) throws ArithmeticException {
	if(b==0) throw new ArithmeticException("0으로 나눌 수는 없다니까?");
	int c=a/b;
	System.out.println(c);
}
public static void main(String[] ar){
	int a=10;
	int b=0;
		
	divide(a,b);
}

 

divide()메소드는 a와 b를 나눈 후에 출력하는 역할을 하는데, 이 나누기 부분에서 우리는 예외가 발생할 수 있음을 알았습니다. 그래서 try, catch로 예외 처리를 해야하지만, divide()를 호출하는 부분에서 처리하기를 원합니다. 왜냐면 divide()를 호출한 곳에서 예외가 발생한 다음의 처리를 divide() 메소드가 정하지 않기 때문입니다. 예를 들어 main메소드에서는 예외가 발생하면 다시 divide()를 호출하거나, 프로그램을 끝내거나, b의 값을 다시 입력받거나 해야하기 때문이고, divide()메소드가 그 결정을 할 수 없다는 의미입니다. 그래서 throws ArithmeticException을 divide를 호출한 main에다가 던지는 것(throw)입니다. 여기서 예외를 던지는 방법은 아래와 같습니다.

(아, 참고로 Exception 생성자 중에서 메시지를 받는 생성자가 있는데, 메시지를 보려면 getMessage()메소드를 이용할 수 있습니다. 아래에서 그 메소드를 사용합니다.)

throw 예외객체
ex) throw new Exception("예외 발생!")

 

예외를 발생시키는 키워드는 throw입니다. 이때 main은 그 예외를 처리하기 위해 try, catch블록을 쓰면 됩니다. 아래처럼 말이죠.

try {
	divide(a,b);
}catch(ArithmeticException e) {
	e.getMessage();
	e.printStackTrace();
}

 

throws 키워드로 처리되어야할 예외가 여러개가 존재한다면 쉼표로 끊어서 예외를 넘겨줄 수 있습니다. 그 결과는 아래와 같습니다.

java.lang.ArithmeticException: 0으로 나눌 수는 없다니까?
	at aa.Main.divide(Main.java:8)
	at aa.Main.main(Main.java:17)

 

이상으로 자바 Exception에 대한 기본 개념과 예외 처리하는 방법을 마치도록 하겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

벡터(Vector)

벡터는 ArrayList와 같이 선형적으로 자료를 담을 수 있는 List 컬렉션입니다. ArrayList와는 다르게 동기화 처리를 하기 때문에 ArrayList보다는 속도가 느리다는 단점이 있습니다. 하지만 여러 쓰레드에서 같은 List를 써야할 필요가 있다면 Vector를 사용하시는 것이 동기화 오류를 만들지 않는 방법이겠죠. 

동기화에 대한 예는 맨 아래에 설명하도록 하겠습니다. 지금부터 사용법에 대해서 알아보도록 합시다.

사용법

벡터는 Generic을 사용합니다. 꺽쇠 '<', '>' 에 자료 타입을 지정해주고 수행합니다. 꺽쇠 안에는 원소로 사용할 객체의 클래스를 명시해주면 됩니다. 아래는 Integer 형의 자료를 Vector에서 다룬다는 선언을 보여줍니다.

 

 

Vector<Integer> v=new Vector();

 

1 - 원소 추가와 삭제, 읽기(add, remove, get)

선형으로 자료를 담고 있습니다. 그래서 데이터를 넣으면 차례차례 뒤에 데이터들이 붙어서 쌓이게 됩니다. 특정 위치의 데이터도 삭제할 수 있습니다. 그리고 정수형 index로 원소를 가져올 수 있습니다. 아래가 그 예제를 보여줍니다. 

import java.util.Vector;	//클래스 import
public class Main {
	
	public static void main(String[] ar){
		Vector<Integer> v=new Vector();
		v.add(20);	//0
		v.add(50);	//1
		v.add(70);	//2
		v.add(100);	//3
		
		for(int i=0;i<v.size();i++) {
			System.out.println(i+"번째 원소:"+v.get(i));
		}
		
		System.out.println("\n index 1 원소 삭제");
		v.remove(1);
		
		for(int i=0;i<v.size();i++) {
			System.out.println(i+"번째 원소:"+v.get(i));
		}
	}
}

 

결과

0번째 원소:20
1번째 원소:50
2번째 원소:70
3번째 원소:100

 index 1 원소 삭제
0번째 원소:20
1번째 원소:70
2번째 원소:100

 

예제를 보면 차례차례 20, 50, 70, 100의 데이터를 넣어주고 있습니다. 그리고 for문을 돌면서 출력해주고 있습니다. 차례대로 출력이 되는 것을 확인할 수 있죠? 그런데 index가 1인 50을 삭제하게 되면 삭제된 뒤의 원소들이 전부 앞으로 당겨지게 됩니다.

 

 

remove

만약 size()보다 큰 index를 갖는 원소를 삭제하거나 get()으로 읽어온다면 ArrayIndexOutOfBoundsException이 발생하게 되므로 size() 체크를 항상 해주셔야합니다. 맨 아래 라인에 v.remove(10); 코드를 추가해서 확인해보도록 하세요.

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 10
	at java.base/java.util.Vector.remove(Vector.java:844)
	at aa.Main.main(Main.java:23)

 

원소를 모두 삭제하고 싶다면 clear(), removeAllElements()를 사용하면 모두 지워집니다.

 

2. Vector를 반복할 수 있는 여러 방법

위의 예제는 단순 for문으로 i를 인덱스 삼아서 get()으로 원소를 읽어왔습니다. 이밖에도 여러 방법이 있는데, 그 방법을 소개합니다. 

2-1 forEach 메소드 사용

	Vector<String> v=new Vector();
	v.add("korea");
	v.add("england");
	v.add("rusia");
		
	v.forEach((item)->{
		System.out.println(item);
	});

람다식을 이용해서 순회할 수 있습니다. 함수 안 괄호에는 원소가 들어갑니다. 그 변수명이 item이지요. 그리고 중괄호('{}')에는 동작부를 구현하면 됩니다. 람다식을 몰라도 사용하는 데에는 문제가 없습니다.

2-2 Iterator객체로 순회

	Iterator<String> it=v.iterator();
	while(it.hasNext()) {
		String item=it.next();
		System.out.println(item);
	}

 

사이즈를 체크하지 않으면서 순회하고 싶다면 Iterator클래스로 객체를 만들어 사용하는 방법이 있습니다. Iterator클래스는 Java.util 패키지에 존재하니 import하여 사용해보세요. 순회에 필요한 메소드는 hasNext()와 원소를 가져오는 next()메소드만 알고 있으면 됩니다.

메소드 설명
hasNext() 이 다음에 원소가 있는지 확인합니다. 있으면 true, 없으면 false를 반환하지요. 대체로 while안의 조건문에 사용합니다.
next() 다음 원소를 가져옵니다. 반환되는 객체는 Generic으로 넘겨준 원소의 자료형입니다.

위 코드의 결과는 그 이전의 결과와 같습니다.

 

3. Vector와 Vector를 합치기

 

 

public static void main(String[] ar){
		Vector<String> v1=new Vector();
		v1.add("r");
		v1.add("e");
		v1.add("a");
		v1.add("k");
		
		Vector<String> v2=new Vector();
		v2.add("w");
		v2.add("o");
		v2.add("n");
		
		v1.addAll(v2);
		v1.forEach((item)->{
			System.out.print(item);
		});
		System.out.println();
	}

 

결과

reakwon

 

addAll() 메소드로 벡터와 벡터를 합칠 수 있습니다. 단, 두 벡터는 같은 Generic 형식을 사용해야하고 합칠 벡터 뒤에 그 벡터가 붙습니다. 또는 생성자를 이용해서 객체 생성시에 벡터를 합칠 수 있습니다. 아래와 같은 형식으로 사용할 수 있습니다. 아래 예는 v2에 v1을 객체 생성시에 합칩니다.

Vector<String> v2=new Vector(v1);

 

4. 원소가 존재하는 지 확인

	public static void main(String[] ar){
		Vector<String> v=new Vector();
		v.add("r");	v.add("e");
		v.add("a"); v.add("kwon");
		
		System.out.println(v.contains("r"));
		System.out.println(v.contains("z"));
	}

 

결과

true
false

 

contains() 메소드로 Vector에 원소가 있는지 확인할 수 있습니다. 있으면 true, 없으면 false를 반환합니다.

 

5. Vector 정렬

public static void main(String[] ar){
	Vector<String> strV=new Vector();
	strV.add("reakwon");
	strV.add("hello");
	strV.add("world");
		
	//알파벳 순으로 정렬
	Collections.sort(strV);
	strV.forEach((item)->{
		System.out.println(item);
	});
		
	System.out.println();
		
	Vector<Integer> intV=new Vector();
	intV.add(5);
	intV.add(1);
	intV.add(3);
		
	//오름차순 정렬
	Collections.sort(intV);
	intV.forEach((item)->{
		System.out.println(item);
	});
}

Collections.sort() 메소드를 이용해서 Vector의 데이터를 정렬할 수 있습니다. 기본적으로 숫자는 오름차순, 문자열은 사전순으로 정렬됩니다.

결과

hello
reakwon
world

1
3
5

혹은 내가 만든 객체를 정렬하려면 어떻게할까요? 사람 객체를 키순으로 정렬하고 싶다면, 또는 이름 순으로 정렬, 나이순으로 정렬하고 싶다면 어떻게할까요? 우리가 직접 비교해서 Collections에게 알려줘야합니다. 그것에 대한 설명은 아래의 링크를 참고해주세요. 사용법은 동일합니다.

reakwon.tistory.com/91

 

[자바/JAVA] Collections와 ArrayList를 이용한 객체 정렬

정렬 우리는 정렬에 관해서 배우기도 하였고 구현도 해보았습니다. 그래서 어떻게 정렬이 되는지 알고 있죠. 하지만 실제 프로그래밍하는 상황에서 이 정렬을 직접구현해서 프로그램을 만들지

reakwon.tistory.com

6. Thread-Safe 예

아까 이야기했던 동기화에 대한 부분을 확인해보도록 합시다. 우선 ArrayList로 아래의 코드를 짜면서 관찰해보죠. 

 

 

 

public class Main {
	
	static ArrayList<String> list=new ArrayList();
	public static void main(String[] ar){
		list.add("reakwon");
		list.add("hello");
		list.add("world");
		
		Thread thread=new Thread(new Runnable() {
			@Override
			public void run() {
				list.forEach((item)->{
					//1초마다 원소를 출력
					try {
					Thread.sleep(1000);
					}catch(InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(item);
				});
			}
		});
		
		thread.start();	//thread 시작
		
		//thread가 forEach문을 먼저 수행할 여유를 주기 위해 1초 기다림
		try {
			Thread.sleep(1000);
		}catch(InterruptedException e) {
			e.printStackTrace();
		}
		//thread가 forEach() 하는 중에 원소추가
		list.add("thread-unsafe");
	}
}

 

결과

reakwon
Exception in thread "Thread-0" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1513)
	at aa.Main$1.run(Main.java:19)
	at java.base/java.lang.Thread.run(Thread.java:831)

 

위의 코드는 아주 간단한 코드입니다. thread라는 쓰레드와 메인 스레드는 list라는 ArrayList객체를 공유합니다. thread는 그 list를 forEach()로 1초마다 그 원소를 출력하는 역할을 하고, 메인 스레드는 스레드 생성하고 1초가 지난 다음 list에 원소를 추가하는 상황입니다. 이때 메인스레드가 thread가 forEach()를 수행하는 중에 list에 원소를 집어넣는 접근을 하게 되는데, 이때 두 스레드 동시에 list에 접근하게 됩니다. 그렇게 되면 위처럼 ConcurrentModificationException이 발생하게 되는 상황이 되죠. 어떻게 고칠까요?

ArrayList를 Vector로만 바꿔주면 이 문제는 해결됩니다.

static Vector<String> list=new Vector();

MainThread는 thread가 forEach()를 종료할때까지 Vector객체에 접근할 수 없고, 반대로 메인스레드의 add()가 끝날때까지 thread는 Vector객체에 접근할 수 없습니다.

Vector는 동작마다 동기화를 걸어줍니다. 이런 일은 속도롤 떨어지게 만드는 작업이지만 멀티 스레드 환경에서는 안전한 작업이죠. 그래서 여러 스레드가 있는 환경에서 개발한다면 Vector를 사용하고 단일 스레드 환경에서는 ArrayList를 활용하시면 되겠습니다.

이상으로 Vector에 대한 포스팅을 마칩니다.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

해시테이블(Hashtable)

해시테이블은 해시맵(HashMap)과 함께 Map을 구현한 키와 값 쌍을 데이터로 저장한 컬렉션(Collection)입니다. 여기서 키는 유일해야만 합니다. 해시테이블은 이 키를 가지고 키에 해당하는 값을 찾습니다.

Hashtable과 HashMap의 차이

Hashtable과 HashMap과의 차이점은 Thread-Safe인지 아닌지가 그 차이점인데요. Hashtable은 동기화가 걸려있어서 Thread-Safe하다고 할 수 있으며 HashMap은 동기화가 없어 unsafe하다고 할 수 있습니다. 그래서 안전성을 추구한다면 Hashtable을 쓰시면 되고, 데이터의 빠른 처리를 위해서라면 HashMap을 사용하시면 됩니다.

여기서 Hastable의 Thread-Safe가 무슨 의미를 하는지 맨 아래의 예제를 통해서 알아보도록 하세요.

해시맵의 사용법을 알고싶으신 분은 아래의 링크를 참고해주세요.

reakwon.tistory.com/151

 

[자바] 해시맵(HashMap)의 개념과 사용 예, 기본 메소드와 같은 키인지 판별하는 방법

해시맵(HashMap) 해시맵은 이름 그대로 해싱(Hashing)된 맵(Map)입니다. 여기서 맵(Map)부터 짚고 넘어가야겠죠? 맵이라는 것은 키(Key)와 값(Value) 두 쌍으로 데이터를 보관하는 자료구조입니다. 여기서

reakwon.tistory.com

 

인터페이스인 Map을 구현한 만큼 HashMap과 사용법이 거의 비슷합니다. put으로 키와 값을 저장하고 get으로 키에 해당하는 값을 읽어옵니다. Hashtable에서 table은 소문자라는 점! 유의해주시길 바라요.

예 ) put, get 메소드 사용방법

import java.util.Hashtable;
import java.util.HashMap;
import java.util.Map;
public class Main {
	public static void main(String[] ar) {
		Map<String,Integer> ht=new Hashtable();
		ht.put("key1", 50);
		ht.put("key2",100);
		ht.put("key2",250);	//같은 키가 들어갈 경우에는?
		System.out.println(ht);
		System.out.println(ht.get("key1"));
		System.out.println(ht.get("key2"));
		System.out.println(ht.get("key3"));	//키가 없을 경우에는?
		
	}
}

 

결과

{key2=250, key1=50}
50
250
null

 

만약 동일한 키를 가지고 데이터를 집어넣게 된다면 가장 마지막의 키와 값 요소로 저장이 됩니다. 그리고 키가 존재하지 않으면 null을 반환합니다. 

예) 갖고 있는 모든 키를 순회(keySet)

만약 Hashtable이 갖고 있는 모든 키를 알고 싶다면 어떻게 할까요? 그럴때는 keySet()메소드로 모든 키를 가져올 수 있습니다. 이때 반환하는 자료형은 Set입니다. 아래의 코드같이 키와 값을 추출할 수 있습니다.

public static void main(String[] ar) {
	Map<String,Integer> ht=new Hashtable();
	ht.put("foo",100);
	ht.put("bar",250);
	for(String key:ht.keySet()) {
		System.out.println("{"+key+","+ht.get(key)+"}");
	}
}

 

결과

{bar,250}
{foo,100}

 

예) Hashtable에 Hashtable을 추가(putAll)

만약 두 Hashtable을 합치고 싶다면 putAll() 메소드를 사용하면 됩니다. 다만 putAll()의 인자로 전달한 Hashtable의 키, 값의 변화가 있다해도 합쳐진 Hastable에는 영향을 받지 않습니다.

public static void main(String[] ar) {
	Map<String,Integer> ht1=new Hashtable();
	ht1.put("key1",9999);
	ht1.put("key2",2020);
		
	Map<String,Integer> ht2=new Hashtable();
	ht2.put("key3",3030);
	ht2.put("key4",8888);
		
	ht2.putAll(ht1);
	System.out.println(ht2);
		
	ht1.put("key5",5555);	//ht1에 데이터 추가
	System.out.println(ht2);
}

 

결과

{key4=8888, key3=3030, key2=2020, key1=9999}
{key4=8888, key3=3030, key2=2020, key1=9999}

 

예) ForEach() 메소드로 키와 값 순회하기

Hashtable에는 ForEach메소드가 존재하는데, 이것을 가지고 키와 값으로 동작을 정의할 수 있습니다. 람다식을 알긴해야하는데, 사용법만 알아도 상관은 없습니다. 

public static void main(String[] ar) {
	Map<String,Integer> ht=new Hashtable();
	ht.put("application layer", 7);
	ht.put("presentation layer", 6);
	ht.put("session layer", 5);
	ht.put("tranport layer", 4);
	ht.put("network layer", 3);
	ht.put("datalink layer", 2);
	ht.put("physical layer", 1);
	ht.forEach((key,value)->
	{
		System.out.println("{"+key+","+value+"}");
		
	});
 }

 

결과

{session layer,5}
{physical layer,1}
{datalink layer,2}
{presentation layer,6}
{tranport layer,4}
{network layer,3}
{application layer,7}

 

 

Hashtable의 Thread-Safe

이제 여기서 Hashtable이 왜 Thread-Safe한지 설명하도록 하겠습니다. 우선 쓰레드에 대한 개념에 대해서 알 필요가 있습니다. 자바 쓰레드의 사용법은 아래의 링크를 참고해주세요.

reakwon.tistory.com/84

 

[JAVA/자바] 쓰레드(Thread) 다루기( Thread상속, Runnable 구현, join)

쓰레드(Thread) 쓰레드(Thread)는 간단히 정의하면 하나의 프로세스(실행중인 프로그램이라고 합니다.)에서 독립접으로 실행되는 하나의 일, 또는 작업의 단위를 말합니다. 뭐, 더 간단히 말해 쓰레

reakwon.tistory.com

 

우선 Thread-Safe하지 않은 HashMap의 코드로 아래의 코드를 작성해보고 결과를 봅시다.

public class Main {
	static Map<String,Integer> hm=new HashMap();	//thread-unsafe
	public static void main(String[] ar) throws InterruptedException {
	
		Runnable runnable=new Thread() {
			@Override
			public void run(){
				hm.put("http",80);
				hm.put("ssh", 22);
				hm.put("dns", 53);
				hm.put("telnet",23);
				hm.put("ftp", 21);
				hm.forEach((key,value)->
				{
					try {
						Thread.sleep(1000);
					}catch(InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("{"+key+","+value+"}");
				
				});
			}
		};
		Thread thread=new Thread(runnable);
		thread.start();
		Thread.sleep(1000);
		hm.put("dhcp",67);
		System.out.println("MainThread end");

	}
}

 

보세요. 이 코드의 대한 저의 의도는 생성된 쓰레드(이하 thread라고 칭하도록 하겠습니다.)가 1초마다 run() 메소드의 저장된 키와 값을 순회하면서 출력하는 겁니다. 물론 예외없이 정상적으로요. 그러니 출력은 아래와 같아야하는 것이 이 코드의 의도입니다. 물론 MainThread가 끝나는 시점은 언제인지 예측 불가능하므로 어디서 나오든 상관없습니다. 중요한 것은 ftp~ssh까지만 출력되어야하는 것이 저의 의도입니다.

MainThread end
{ftp,21}
{telnet,23}
{dns,53}
{http,80}
{ssh,22}

 

하지만 우리의 예상과는 다르게 아래와 같이 출력됩니다. ConcurrentModificationException이라는 예외(Exception)가 발생하게 됩니다. 쓰레드들이 동시에 데이터를 변경할때 이 예외가 발생합니다.

MainThread end
{ftp,21}
{telnet,23}
{dns,53}
{http,80}
{ssh,22}
{dhcp,67}
Exception in thread "Thread-1" java.util.ConcurrentModificationException
	at java.base/java.util.HashMap.forEach(HashMap.java:1428)
	at aa.Main$1.run(Main.java:18)
	at java.base/java.lang.Thread.run(Thread.java:831)

 

왜 이런 예외가 발생할까요? 코드를 하나씩 해석해보도록 합시다.

자, HashMap은 두 스레드가 공용으로 사용할 수 있도록 static으로 지정해놓았고, 생성된 쓰레드는 해시맵에 http~ftp까지의 키와 값을 put하고 있습니다(여기서 value는 그 프로토콜의 포트번호를 의미합니다. 뭐 여기서는 중요한게 아니지요.) thread는 put으로 키,값을 저장하고 난 후에 forEach() 메소드를 이용해서 1초마다 갖고 있는 키와 값을 출력합니다. 

메인스레드에서는 thread가 forEach() 메소드를 수행할 시간을 벌어주기 위해서 1초간 잠재워줍니다. 그 후 thread는 자신의 루틴(routine) 중 forEach()메소드를 실행합니다. 

이때 thread가 forEach() 메소드가 수행하는 중에 메인 쓰레드에서 put으로 데이터를 집어넣고 있는 상황이죠. 즉, HashMap에 동시에 접근해서 변경이 발생하게 되는 그런 코드입니다. 

우리의 의도대로 동작하게 만드려면 어떻게 해야할까요? 이럴때 synchronized라는 키워드로 우리가 직접 동기화를 처리할 수도 있지만 더 간단한 방법으로는 바로 Hashtable을 사용하는 것입니다. 이 문제를 해결하려면 단 한줄만 바꾸면 됩니다.

static Map<String,Integer> hm=new Hashtable();	//thread-safe

 

물론 hm(HashMap)이라는 변수도 ht(Hashtable)로 바꾸면 더 일관성있겠죠. 그 후의 결과는 우리의 의도대로 아래와 같이 동작함을 알 수 있습니다.

{ftp,21}
{ssh,22}
{http,80}
{dns,53}
{telnet,23}
MainThread end

 

이처럼 스레드에 대해서 동시접근에 안전하려면 Hashtable을 쓰는 것이 좋고, 단일 쓰레드에서 사용한다면 HashMap을 쓰는 것이 좋습니다.

지금까지 Hashtable의 개념, 그리고 사용방법과 HashMap과의 차이점을 코드를 통해서 알아보았습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

해시맵(HashMap)

해시맵은 이름 그대로 해싱(Hashing)된 맵(Map)입니다. 여기서 맵(Map)부터 짚고 넘어가야겠죠? 맵이라는 것은 키(Key)와 값(Value) 두 쌍으로 데이터를 보관하는 자료구조입니다. 여기서 키는 맵에 오직 유일하게 있어야합니다. 즉, 같은 맵에 두 개 이상의 키가 존재하면 안된다는 것입니다. 이름 그대로 열쇠이기 때문에 그 열쇠로 짝인 값(Value)를 찾아야하기 때문입니다. 값은 중복된 값이어도 상관이 없습니다. 

HashMap과 사용법이 거의 동일한 컬렉션(Collection)에는 Hashtable이 있습니다. 두 클래스의 차이점은 Thread 관점에서 안전하냐(Hashtable), 안전하지 않은 대신 속도가 빠르냐(HashMap)입니다. 여기서는 Thread-Safe하지 않은 HashMap사용법을 가장 아래에서 살펴보도록 하겠습니다. Hashtable의 개념과 사용법, Thread-Safe한 예제를 보려면 아래의 링크를 참고해주세요.

reakwon.tistory.com/152

 

자바 해시테이블(Hashtable) 개념과 사용법, HashMap과의 차이점을 코드로 이해할 수 있는 예제

해시테이블(Hashtable) 해시테이블은 해시맵(HashMap)과 함께 Map을 구현한 키와 값 쌍을 데이터로 저장한 컬렉션(Collection)입니다. 여기서 키는 유일해야만 합니다. 해시테이블은 이 키를 가지고 키에

reakwon.tistory.com

자신이 만든 객체도 키로 사용할 수 있는데, 그 사용법은 가장 아래쪽 부분에 설명되어있습니다.

 

 

map

 

Map 인터페이스를 구현한 HashMap은 키를 해싱하여 자료를 저장하고 꺼내오기 때문에 속도가 빠릅니다. 이번 포스팅에서는 사용방법 위주로 설명하도록 하겠습니다.

사용법

사용전에 HashMap과 Map은 java.util 안에 위치합니다. 두개를 import 하여 사용준비해주세요.

 

1. 키,값 저장(put), 읽기(get) 예

import java.util.HashMap;
import java.util.Map;
public class Main {
	public static void main(String[] ar) {
		Map<String,Integer> map=new HashMap();	//<키 자료형, 값 자료형>
		map.put("A", 100);
		map.put("B", 101);
		map.put("C", 102);
		map.put("C", 103); //중복된 key가 들어갈때는 이전 키,값을 지금의 것으로 업데이트
		System.out.println(map);
		System.out.println(map.get("A"));
		System.out.println(map.get("B"));
		System.out.println(map.get("C"));
	}
}

 

 

결과: 

{A=100, B=101, C=103}
100
101
103

map을 그냥 println으로 출력하게 되면 중괄호('{ }')로 묶여서 키와 값들이 출력됩니다. 기본적으로 map의 put과 get이 아주 많이 사용됩니다. map을 사용하려면 반드시 알아야하는 메소드입니다. put을 키와 값을 map에 저장하는 메소드이며 get은 입력받은 key와 대응되는 값을 돌려줍니다. 만약 해당하는 key가 없다면 null을 넘겨주게 됩니다.

 

2. containsKey 사용예 (이미 HashMap에 키가 있으면 값을 덮어쓰지 않는 예)

public static void main(String[] ar){
	Map<String,Integer> map=new HashMap();
	map.put("key1", 100);
	map.put("key2", 200);
	if(!map.containsKey("key2"))	//키가 들어있는지 확인. 있으면 덮어쓰지 않는다.
		map.put("key2", 300); 
	System.out.println(map);
	System.out.println(map.get("key1"));
	System.out.println(map.get("key2"));
}

 

 

 

결과:

{key1=100, key2=200}
100
200

 

containsKey메소드로 키가 존재하는지 존재하지 않는지 알 수 있습니다. 이것과 비슷한 메소드로는 containsValue가 있죠. 이것은 반대로 값이 존재하는지 알아보는 메소드입니다. 존재시 true, 없을때는 false를 반환합니다. 위의 if문과 put메소드를 한꺼번에 처리할 수 있는 메소드가 존재합니다. 그래서 두 라인을 아래와 같이 바꿔써도 같은 동작을 합니다.

//if(!map.containsKey("key2"))	//키가 들어있는지 확인. 있으면 덮어쓰지 않는다.
			//map.put("key2", 300); 
map.putIfAbsent("key2",300);

 

3. putAll 사용예 (Map에 다른 Map을 전부 포함)

public static void main(String[] ar) {
	Map<String,Integer> map1=new HashMap();
	Map<String,Integer> map2=new HashMap();
	//map1 put
	map1.put("map1-key1", 100);
	map1.put("map1-key2", 200);
		
	//map2 put
	map2.put("map2-key3", 300);
	map2.put("map2-key4", 400);
		
	System.out.println("map1:"+map1);
	System.out.println("map2:"+map2);
		
	//map2에 map1을 합침
	map2.putAll(map1);
	System.out.println("map2 includes map1:"+map2);
		
	//map1의 키, 값 변경
	map1.put("map1-key1", 1000);
	//map2에는 영향 없음.
	System.out.println("map2 includes map1:"+map2);
}

 

결과

map1:{map1-key1=100, map1-key2=200}
map2:{map2-key4=400, map2-key3=300}
map2 includes map1:{map2-key4=400, map1-key1=100, map1-key2=200, map2-key3=300}
map2 includes map1:{map2-key4=400, map1-key1=100, map1-key2=200, map2-key3=300}

 

아예 map을 통째로 인자로 넘겨주고 싶다면 putAll 메소드를 사용하면 됩니다. 주의해야할 점은 반드시 키와 값의 자료형이 같은 map이어야한다는 점입니다. 다른 자료형의 키, 값은 받을 수 없습니다.

 

putAll 대신 생성자를 이용해서 생성과 동시에 map의 데이터를 전부 넘겨줄 수도 있습니다.

Map<String,Integer> map2=new HashMap(map1);

 

 

 

4. keySet 사용예 (모든 키를 순회하는 코드)

list처럼 증가하는 index를 사용할 방법이 없지만 keySet메소드를 이용하여 키를 Set으로 넘겨주어 Map에 존재하는 키를 모두 순회할 수 있습니다. 

public static void main(String[] ar) {
	Map<String,Integer> map=new HashMap();
	map.put("key1",50);
	map.put("key2",100);
	map.put("key3",150);
	map.put("key4",200);
		
	System.out.println("All key-value pairs");
	for(String key:map.keySet()) {
		System.out.println("{"+key+","+map.get(key)+"}");
	}

}

 

결과

All key-value pairs
{key1,50}
{key2,100}
{key3,150}
{key4,200}

 

5. Foreach() 메소드로 순환하기

Foreach() 메소드를 사용하기전, 람다식을 이해하고 있어야합니다. 하지만 사용법만 알아도 유용하게 사용할 수 있습니다. 아래의 사용법을 보면서 익혀보세요.

public static void main(String[] ar) {
	Map<String,Integer> hm=new HashMap();
	hm.put("http",80);
	hm.put("ssh", 22);
	hm.put("dns", 53);
	hm.put("telnet",23);
	hm.put("ftp", 21);
	hm.forEach((key,value)->
	{
		System.out.println("{"+key+","+value+"}");
		
	});
	
}

 

위에 보이는 -> 가 있는 라인이 람다식입니다. key와 value를 사용하며 -> 이후 동작을 구현해주면 됩니다.

결과

{ftp,21}
{telnet,23}
{dns,53}
{http,80}
{ssh,22}

 

6. 내가 만든 객체를 Key로 사용하기(나의 객체를 같은 키로 판단하는 방법)

public class Main {
	public static void main(String[] ar) {
		Person person1=new Person("reakwon","666666-7777777");
		Person person2=new Person("putty","123456-1234567");
		
		Person who=new Person("reakwon","666666-7777777");
		Map<Person,Integer> map=new HashMap();
		map.put(person1, 90);
		map.put(person2, 80);
		
		System.out.println("map includes "+who.getName()+"? "+map.containsKey(who));
	
		map.put(who, 70);
		System.out.println(map);
	}
}
class Person{
	private String name;
	private String id;
	public Person(String name,String id) {
		this.name=name;
		this.id=id;
	}
	public String getName() {
		return name;
	}
	
	@Override
	public String toString() {
		return this.name;
	}
}

 

결과

map includes reakwon? false
{putty=80, reakwon=70, reakwon=90}

 

위의 코드에서 약간 불편한 점을 눈치 채셨나요? 저는 Person의 name과 id가 같으면 같은 키로 보고 중복처리를 하지 않을 생각이었습니다. 즉, 위의 map에는 putty와 reakwon만 있는 것이 저의 의도였고, who와 person1은 같은 키로 map이 인식해줬으면 좋겠습니다. 어떻게 구현할까요?

 

● equals() 메소드 Overriding

Object 클래스의 equals는 서로 같은 객체인지 아닌지 판별해주는 메소드입니다. String이 바로 이 메소드를 오버라이딩했지요. 그래서 문자열이 같은 경우에 equals의 결과는 true입니다. 이처럼 equals를 통해서 같은지 아닌지를 판별해주면 됩니다. 그래서 아래의 코드를 Person에 추가하면 됩니다.

	@Override
	public boolean equals(Object o) {
		if(o instanceof Person) {
			Person p=(Person)o;
			return this.id.equals(p.id) && this.name.equals(p.name);
		}
		return false;
	}

 

결과

 

 

map includes reakwon? false
{putty=80, reakwon=70, reakwon=90}

 

분명 equals를 오버라이딩해서 같은 객체라고 명시했는대도 역시 false를 반환하고 있습니다. 어떻게 해결할 수 있을까요?

 

● hashCode() 메소드 Overriding

equals()와 쌍으로 기억해두셔야할 것은 바로 hashCode()입니다. hashCode는 각 객체가 갖는 유일한 값(Code)을 의미합니다. Object의 hashCode()는 원래 주소값에 의한 hashCode()로 각 객체가 전부 다른값을 가지고 있습니다. HashMap은 우선 hashCode를 비교하고 같을 때만 equals를 수행하여 정말 제대로 같은것인지 판별합니다. 그래서 HashMap은 애초에 hashCode()가 반환하는 값이 다르면 equals는 수행하지도 않습니다.

그래서 위 코드에서 같은 해시코드를 오버라이딩해서 name과 id에 따라 다른 hashCode를 갖게 만들어주어야합니다. 구현의 편의성을 위해서 String클래스를 사용합니다. String 클래스가 이미 그렇게 다 구현(문자열이 같으면 hashCode도 같고 equals도 true) 이 되어있습니다. 우리는 위의 예제에서 String을 키로 쓴적이 있었죠. String 클래스는 hashCode와 equals가 문자열이 같을때 같은 객체라고 판별할 수 있도록 두 메소드를 전부 재정의한 상태입니다.

그래서 우리는 아래의 코드를 위의 코드 대신 추가해주면 됩니다.

	@Override
	public int hashCode() {
		return name.hashCode()+id.hashCode();
	}	
	
	@Override
	public boolean equals(Object o) {
		return this.hashCode()==o.hashCode();
	}

 

 

 

 

결과

map includes reakwon? true
{reakwon=70, putty=80}

 

위의 코드는 대체적으로 잘 동작하지만 만약 운이 굉장히 좋지 않을 경우 다른 객체로 의도했던 것이 같은 객체로 인식될 수 있습니다. String 클래스의 hashCode의 구현 방식때문이지요. 하지만 이해를 돕기 위해서 더 완벽한 구현은 하지 않았습니다. 귀찮기도 하구요.

알아두셔야할 점은 은 객체를 판별하는 코드를 넣고자하여 equals를 재정의할때 반드시 hashCode도 다시 정의해주어야한다는 점을 기억하세요.

 

HashMap의 Thread-Unsafe 예

아래의 코드를 봅시다.

public class Main {
	static Map<String,String> hashmap=new HashMap();
	public static void main(String[] ar) throws InterruptedException {
	
		Runnable runnable=new Thread() {
			@Override
			public void run(){
				hashmap.put("seoul","02");
				hashmap.put("kyeongkido", "031");
				hashmap.put("busan", "051");
				hashmap.put("daejeon","042");
				hashmap.forEach((key,value)->
				{
					try {
						Thread.sleep(1000);
					}catch(InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("{"+key+","+value+"}");
				
				});
			}
		};
		Thread thread=new Thread(runnable);
		thread.start();
		Thread.sleep(1000);
		hashmap.put("jeju","064");
	}
}

 

우선 Thread의 개념부터 잡아야합니다. 자바 쓰레드의 개념과 사용방법을 알아보려면 아래의 링크로 가서 학습해봅시다.

reakwon.tistory.com/84

 

[JAVA/자바] 쓰레드(Thread) 다루기( Thread상속, Runnable 구현, join)

쓰레드(Thread) 쓰레드(Thread)는 간단히 정의하면 하나의 프로세스(실행중인 프로그램이라고 합니다.)에서 독립접으로 실행되는 하나의 일, 또는 작업의 단위를 말합니다. 뭐, 더 간단히 말해 쓰레

reakwon.tistory.com

 

코드에 대한 결과는 예외를 던지며 프로그램이 종료됩니다. 아래와 같이 말이죠.

{seoul,02}
{daejeon,042}
{busan,051}
{jeju,064}
{kyeongkido,031}
Exception in thread "Thread-1" java.util.ConcurrentModificationException
	at java.base/java.util.HashMap.forEach(HashMap.java:1428)
	at aa.Main$1.run(Main.java:17)
	at java.base/java.lang.Thread.run(Thread.java:831)

위 코드에 대한 의도는 thread라는 쓰레드에서 hashmap의 저장된 데이터 seoul ~ daejeon의 데이터를 모두 출력하는 의도입니다. 하지만 메인 쓰레드와 thread간의 HashMap에 동시에 접근했기 때문에 위 코드에 예외가 발생하게 된것입니다. 왜 동시에 접근했을까요? 

메인 스레드는 thread의 run() 메소드가 forEach가 수행할 수 있도록 잠시 1초간 기다려줍니다. 그리고 thread는 1초마다 저장했던 데이터들을 출력합니다. 그런데 그때 메인스레드에서 put() 메소드로 데이터를 저장하는 상황이 발생했습니다. 그래서 두 쓰레드가 동시에 HashMap에 접근하게 된것이죠.

이처럼 HashMap은 쓰레드에 대해서 안전하지 않은 컬렉션입니다. 오직 단일 쓰레드에서만 사용하면 안전한 컬렉션이죠. 이를 보완한 컬렉션이 바로 Hashtable입니다. 위의 링크로 들어가서 Hashtable로 이 문제를 해결한 코드를 확인해보세요.

이상으로 HashMap에 대한 기본적인 개념과 사용법 위주로 알아보았습니다. 이 정도만 알고 있어도 HashMap을 잘 사용할 수 있을 것으로 생각합니다. 감사합니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

 

SimpleDateFormat에는 문자열로 형식을 전달하는 생성자가 있는데 아래의 예시를 참고하면 될것.

 

Date and Time PatternResult

"yyyy.MM.dd G 'at' HH:mm:ss z" 2001.07.04 AD at 12:08:56 PDT
"EEE, MMM d, ''yy" Wed, Jul 4, '01
"h:mm a" 12:08 PM
"hh 'o''clock' a, zzzz" 12 o'clock PM, Pacific Daylight Time
"K:mm a, z" 0:08 PM, PDT
"yyyyy.MMMMM.dd GGG hh:mm aaa" 02001.July.04 AD 12:08 PM
"EEE, d MMM yyyy HH:mm:ss Z" Wed, 4 Jul 2001 12:08:56 -0700
"yyMMddHHmmssZ" 010704120856-0700
"yyyy-MM-dd'T'HH:mm:ss.SSSZ" 2001-07-04T12:08:56.235-0700
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX" 2001-07-04T12:08:56.235-07:00
"YYYY-'W'ww-u" 2001-W27-3

 

1) SimpleDateFormat와 Date로 현재 시간을 구하는 코드

SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd a hh:mm:ss");
Date date=new Date();
		
System.out.println("now  ("+format.format(date)+")");

 

2) System.currentTimeMillis 메소드를 이용하여 현재 시스템 시간을 구하는 코드

SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd a hh:mm:ss");
Date date=new Date(System.currentTimeMillis());
		
System.out.println("now  ("+format.format(date)+")");

 

3) 반대로 long형태의 millisecond로 반환하는 코드

String str = "2020-01-31 14:20:59";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = null;

try{
	date = format.parse(str);
}catch(ParseException e){}

long timeMillis = date.getTime();
System.out.println("time millis : "+timeMillis);
		

 

위 코드는 2020년 1월 31일 오후 2시 20분 59초를 밀리세컨드로 반환하는데 어떤 시간을 기준으로 반환할까?

 

아래 코드를 실행해보면 답이 나온다.

String str = "1970-01-01 09:00:00";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = null;

try{
	date = format.parse(str);
}catch(ParseException e){}

long timeMillis = date.getTime();
System.out.println("time millis : "+timeMillis);

 

아래와 같이 시간 차이(계산)도 쉽게 구할 수 있다. 

final long daySeconds = 86400L;
String str = "1991-09-21 03:00:00";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date birthDay = null;

try{
	birthDay = format.parse(str);
}catch(ParseException e){}

long birthDayMillis = birthDay.getTime();
long now = System.currentTimeMillis();
long passed = ((now/1000L) - (birthDayMillis/1000L)) / daySeconds;
System.out.println("개 늙었네 : "+passed);

 

 

위의 소스에서 now나 birthdayMillis는 milliseconds단위이므로 1000을 곱하여 시간초로 바꾸고 서로 빼면 지금까지 흐른 초가 나오겠죠? 그걸 또 하루로 나눠버리면 , 아 참고로 하루는 86400초, 지금까지 흐른 일 수가 나오죠? 

 

4) 현재 SimpleDateFormat의 Pattern 알아보기

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("pattern : "+format.toPattern());

 

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

정렬

우리는 정렬에 관해서 배우기도 하였고 구현도 해보았습니다. 그래서 어떻게 정렬이 되는지 알고 있죠. 하지만 실제 프로그래밍하는 상황에서 이 정렬을 직접구현해서 프로그램을 만들지는 않을 거에요. 우리에겐 JAVA에서 제공하는 정렬 메소드가 있기 때문이죠. 저희보다 똑똑한 사람들이 만들어 놓은 것이니 우리는 그저 감사하며 사용하면됩니다. 그 전에 사용법을 알아야 잘 사용할 수 있겠죠? 이제부터 어떻게 사용하는지 알아보도록 합시다.

 

Collections.sort

Collections.sort의 메소드를 이용해서 정렬할 수 있습니다. 바로 기본 자료형의 wrapper 클래스의 객체들은 전부 이 Collections.sort를 통해서 알아서 정렬이 가능합니다. 

바로 예제를 통해 확인하도록 하지요.

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
public class Sort {

	public static void printList(String title,List list){
		System.out.println(title);
		System.out.println(list);
	}
	public static void main(String []ar){
		List<Integer> intList=new ArrayList<>();
		
		intList.add(4);
		intList.add(5);
		intList.add(1);
		intList.add(8);
		intList.add(3);
		
		printList("정수 정렬 전",intList);
		
		Collections.sort(intList);
		
		printList("정수 정렬 후",intList);
		
		System.out.println("==============================");
		
		List<String> strList=new ArrayList<>();
		
		strList.add("de");
		strList.add("dc");
		strList.add("ci");
		strList.add("ad");
		strList.add("aa");
		
		printList("문자열 정렬 전",strList);
		
		Collections.sort(strList);
		
		printList("문자열 정렬 후",strList);
		
		
	}
}

 정렬이 잘되는 것을 아래의 결과를 통해서 확인할 수 있네요. 

실행결과

정수 정렬 전
[4, 5, 1, 8, 3]
정수 정렬 후
[1, 3, 4, 5, 8]
==============================
문자열 정렬 전
[de, dc, ci, ad, aa]
문자열 정렬 후
[aa, ad, ci, dc, de]

 

 

Collections와 ArrayList를 이용한 객체 정렬

하지만 우리는 기본 자료형을 정렬하는 것이 아닌 우리가 만든 클래스의 객체를 정렬하려는 상황이 발생한다면 어떻게 하면 좋을까요?

아래와 같은 User라는 클래스가 있다고 칩시다.

class User{
	public String name;
	public int age;
	public User(String name,int age){
		this.name=name;
		this.age=age;
	}
	@Override
	public String toString(){
		return "(name:"+name+", age:"+age+")";
	}
}

그리고 이 클래스의 객체를 리스트에 5개를 담도록 하겠습니다. 

List<User> list=new ArrayList();

list.add(new User("A",40));
list.add(new User("B",25));
list.add(new User("C",10));
list.add(new User("D",30));
list.add(new User("E",26));

System.out.println(list);

그리고 출력한다면 뭐 뻔한 결과가 나올테죠.

실행결과

[(name:A, age:40), (name:B, age:25), (name:C, age:10), (name:D, age:30), (name:E, age:26)]

이 User의 객체들을 나이순으로 정렬하고 싶습니다(참고로 꼰대는 아니구요). 어떻게 정렬할 수 있을까요?

 

Comparator 구현

Collections.sort를 입력했을때 eclipse에 나오는 recommended method의 2번째 메소드를 보시기바랍니다. Comparator라는 객체를 받고 있는 것을 확인할 수 있습니다.

 

네, 답은 Comparator를 구현하면 이 상황을 해결할 수 있습니다. 

그러면 바로 구현해보도록 합시다. 구현할 것이 메소드 하나 compare밖에 없어요. 그 내부 코드양도 얼마되지 않습니다.

class UserComparator implements Comparator<User>{
	@Override
	public int compare(User a,User b){
		if(a.age>b.age) return 1;
		if(a.age<b.age) return -1;
		return 0;
	}
}

compare는 2개의 인자를 받는데 이 인자들은 우리가 앞서 구현했던 User객체입니다. 만약 a라는 객체가 b라는 객체보다 더 크다면 1, 작다면 -1, 같다면 0을 반환하도록 합니다. 이게 오름차순의 방법이고, 만약 내림차순으로 구현하고 싶다면 반대로 구현하면 되겠죠? (모르면 그냥 찍어서 return하고 원하는 결과와 반대다 싶으면 return을 반대로 해줍시다^^.)

오름차순

첫번째 인자가 더 큰 객체라면 1

두번째 인자가 더 큰 객체라면 -1

같다면 0

내림차순

첫번째 인자가 더 작은 객체라면 1

두번째 인자가 더 작은 객체라면 -1

같다면 0

 

 

이 클래스의 객체를 Collections.sort의 2번째 인자로 전달하기만 하면 됩니다. 이제 전체코드와 결과를 보겠습니다.

 

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Sort {

	public static void main(String[] ar){
		List<User> list=new ArrayList();
		
		list.add(new User("A",40));
		list.add(new User("B",25));
		list.add(new User("C",10));
		list.add(new User("D",30));
		list.add(new User("E",26));
		
		System.out.println("정렬 전 ->"+list);
		Collections.sort(list,new UserComparator());
		//list.sort(new UserComparator()); 이 방법 역시 정렬 방법
		System.out.println("정렬 후->"+list);
	}
}

class User{
	public String name;
	public int age;
	public User(String name,int age){
		this.name=name;
		this.age=age;
	}
	@Override
	public String toString(){
		return "(name:"+name+", age:"+age+")";
	}
}

class UserComparator implements Comparator<User>{
	@Override
	public int compare(User a,User b){
		if(a.age>b.age) return 1;
		if(a.age<b.age) return -1;
		return 0;
	}
}

 

실행결과

정렬 전 ->[(name:A, age:40), (name:B, age:25), (name:C, age:10), (name:D, age:30), (name:E, age:26)]
정렬 후->[(name:C, age:10), (name:B, age:25), (name:E, age:26), (name:D, age:30), (name:A, age:40)]

저희가 원하던 결과를 얻을 수 있습니다. 참고로 Collections.sort를 주석처리하고 list.sort를 실행해도 결과는 같습니다. List도 역시 Comparator를 받을 수 있으니까요.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,