도순씨의 코딩일지

자바 :: GridLayout, eval을 이용하여 계산기 구현하기 본문

𝐏𝐑𝐎𝐆𝐑𝐀𝐌𝐌𝐈𝐍𝐆/𝐉𝐀𝐕𝐀

자바 :: GridLayout, eval을 이용하여 계산기 구현하기

도순씨 2020. 7. 6. 00:10

시험 문제 중에서 계산기를 구현하는 문제가 나왔는데 for 문으로 돌린 addActionListener에서 오류가 나서 당황했다.

그래서 결국 for문을 사용하지 않고 하나하나 작성해서 제출하긴 했지만 ... 뭔가 찝찝해서 다시 고쳐보려고 한다

 

코드를 하나하나 살펴보도록 하자

 

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.swing.*;

import java.awt.*;
import java.awt.event.*;

import 부분이다

계산기 문제가 자바로 나와서 솔직히 당황했다

 

파이썬으로 자바를 구현할 때 eval 함수를 사용했었다

eval함수는 중위표기식을 후위표기식으로 바꾼 다음 계산을 한다

 

열심히 구글링을 해 본 결과 자바에는 파이썬처럼 eval 함수가 내장되어 있지 않다

반면 자바 스크립트에서는 eval 함수를 지원한다.

이 eval 함수를 사용하기 위해서 위에 세개를 import 해주었다

 

나머지는 GUI 관련이다

 

public class Calculator extends JFrame{
	private int a = 0, b = 12;
	JButton[] label = new JButton[16];
	String[] cal = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "CE", "계산", "+", "-", "*", "/"};
	JPanel up = new JPanel();
	JPanel mid = new JPanel();
	JPanel down = new JPanel();
	JTextField tf1 = new JTextField(20);
	JTextField tf2 = new JTextField(20);

클래스 하나에 모든 것을 때려 박았는데 패널이 세개 존재하는 만큼 클래스를 여러개 만드는 것이 가독성에 좋을 것이다

 

패널은 세 개로 구성했다.

위에 수식을 입력받는 부분이 up JPanel, 중간에 버튼이 mid JPanel, 그리고 마지막에 결과를 도출하는 부분이 down JPanel이다

 

public Calculator() {
		setTitle("산술 계산기");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		Container c = getContentPane();
		c.setLayout(new BorderLayout());
		
		up.setLayout(new FlowLayout());
		mid.setLayout(new GridLayout(4, 4, 5, 5));
		down.setLayout(new FlowLayout());

		up.setBackground(Color.LIGHT_GRAY);
		mid.setBackground(Color.WHITE);
		down.setBackground(Color.YELLOW);
		
		up.add(new JLabel("연산수식"));
		up.add(tf1);

 

up과 down은 가운데 정렬로 한 개의 라벨, 한 개의 텍스트 필드만 들어가면 되기 때문에 FlowLayout으로 설정했다

mid는 16개의 버튼이 들어갈 공간이기 때문에 GridLayout으로 설정하였다. 가독성을 위해서 여백도 5, 5로 설정해주었다

 

for(int i = 0 ; i < 16 ; i++) {
			label[i] = new JButton(cal[i]);
			if(i >=12 ) 
				label[i].setBackground(Color.CYAN);
			mid.add(label[i]);
		}

JButton 형식의 label 버튼에 순차적으로 텍스트를 넣는다

(지금 생각해보니 왜 label이라고 JButton 변수를 선언했지 .. button으로 선언하지..)

JPanel에 하나씩 버튼을 추가해준다

 

과제할때는 대충 제출해서(...) 잘 몰랐는데 맥 이클립스 기준 button setBackground 관련 오류가 있다⚠️

 

for문으로 setBackground를 적용했을 때 색이 제대로 적용되지 않는다

처음에는 내가 또 무슨 코드를 잘못짰나 ... 오랜시간 허둥지둥했는데 (구글링해도 시원한 답이 나오질 않음ㅜ)

윈도우 노트북으로 확인한 결과 정상적으로 색이 적용되는 것을 확인했다

for(int i = 0 ; i < 16 ; i++) {
			if(i <= 9 || i >= 12) { 
				label[i].addActionListener(new ActionListener() {
					public void actionPerformed(ActionEvent e) {
						JButton tempButton = (JButton) e.getSource();
						String s1 = tf1.getText();
						tf1.setText(s1 + tempButton.getText());
					}
				});
			}
		}

여기가 내가 시험때 엄청 당황했던 부분😥

처음에 JButton을 생성함과 동시에 for문의 i와 연동시켜서 addActionListener를 해주었는데

cal 배열에 담겨있는 순서대로 수식이 입력되었다

 

내가 실수했던 점은 for문과 같이 순차적으로 수식이 입력되지 않는다는 것.

따라서 ActionEvent가 발생한 버튼을 받아온 후에, 그 버튼에 담겨져있는 텍스트를 수식입력칸에 추가해주어야 한다

 

텍스트는 간단하게 원래 있던 것 + 현재 받아온 정보를 추가해주면 된다

 

// 버튼 처리
		label[10].addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				tf1.setText("");
			}
		});

버튼 10은 CE 버튼인데, 이는 모든 수식 입력 내용을 삭제한다.

따라서 ""(NULL)로 내용을 바꾸어 주면 된다

 

// 자바스크립트에서 eval 함수 가져옴
		label[11].addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
			    try {
			    	ScriptEngineManager mgr = new ScriptEngineManager();
			        ScriptEngine engine = mgr.getEngineByName("JavaScript");
			        String evalValue = tf1.getText();
			        String tfOutput = String.valueOf(engine.eval(evalValue));
			        tf2.setText(tfOutput);

				} catch (ScriptException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}

			}
		});

여기가 시험 때 당황했던 부분 2....

계산을 어떻게 구현해야할지 고민이 많았다

스택으로 구현하는 방법도 좋은 방법이겠지만 ,

제한된 시간 내에 스택 구현 + 중위표기식->후위표기식 알고리즘 작성이 나한테는 쉬워보이지 않았다😇

 

혹시 스택을 사용하여 구현하고 싶으신 분들이 있다면 pseudo code는 다음과 같다

for token in 후위표기식
    if(token이 피연산자)
        push(s, token)
    else        // token이 연산자
        second <- pop(s)
        first <- pop(s)
        result <- first op second
        push(s, result)
final_result <- pop(s)

tokenizer를 이용하여 + - * / 를 기준으로 구분하고 순차적으로 stack을 사용하여 연산해주면 된다

 

하지만 .. 나에겐 그럴 시간 따위 없었기 때문에 eval함수를 사용했다

// 자바스크립트에서 eval 함수 가져옴
		label[11].addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
			    try {
			    	ScriptEngineManager mgr = new ScriptEngineManager();
			        ScriptEngine engine = mgr.getEngineByName("JavaScript");
			        String evalValue = tf1.getText();
			        String tfOutput = String.valueOf(engine.eval(evalValue));
			        tf2.setText(tfOutput);

				} catch (ScriptException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}

			}
		});

이 방법의 단점은 0으로 나누었을 때 infinity로 표시된다는 점이다

스택을 사용하면 ArithmeticException을 이용하여

op가 /이고 second가 0일 경우에 대해서 답을 0으로 도출하는 예외처리를 해주면 된다.

 

down.add(new JLabel("계산결과"));
		down.add(tf2);
		
		c.add(up, BorderLayout.NORTH);
		c.add(mid, BorderLayout.CENTER);
		c.add(down, BorderLayout.SOUTH);
		
		setSize(400, 400);
		setVisible(true);
	}
	
	public static void main(String[] args) {
		new Calculator();
	}
}

그 밖의 부분에서는 contentPane에 패널을 부착해주고 사이즈를 조정해주었다

 

전체코드는 다음과 같다

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.swing.*;

import java.awt.*;
import java.awt.event.*;

public class Calculator extends JFrame{
	private int a = 0, b = 12;
	JButton[] label = new JButton[16];
	String[] cal = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "CE", "계산", "+", "-", "*", "/"};
	JPanel up = new JPanel();
	JPanel mid = new JPanel();
	JPanel down = new JPanel();
	JTextField tf1 = new JTextField(20);
	JTextField tf2 = new JTextField(20);
	
	public Calculator() {
		setTitle("산술 계산기");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		Container c = getContentPane();
		c.setLayout(new BorderLayout());
		
		up.setLayout(new FlowLayout());
		mid.setLayout(new GridLayout(4, 4, 5, 5));
		down.setLayout(new FlowLayout());

		up.setBackground(Color.LIGHT_GRAY);
		mid.setBackground(Color.WHITE);
		down.setBackground(Color.YELLOW);
		
		up.add(new JLabel("연산수식"));
		up.add(tf1);
		
		for(int i = 0 ; i < 16 ; i++) {
			label[i] = new JButton(cal[i]);
			if(i >=12 ) 
				label[i].setBackground(Color.CYAN);
			mid.add(label[i]);
		}
		
		
		for(int i = 0 ; i < 16 ; i++) {
			if(i <= 9 || i >= 12) { 
				label[i].addActionListener(new ActionListener() {
					public void actionPerformed(ActionEvent e) {
						JButton tempButton = (JButton) e.getSource();
						String s1 = tf1.getText();
						tf1.setText(s1 + tempButton.getText());
					}
				});
			}
		}
		
		// 버튼 처리
		label[10].addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				tf1.setText("");
			}
		});
		
		// 자바스크립트에서 eval 함수 가져옴
		label[11].addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
			    try {
			    	ScriptEngineManager mgr = new ScriptEngineManager();
			        ScriptEngine engine = mgr.getEngineByName("JavaScript");
			        String evalValue = tf1.getText();
			        String tfOutput = String.valueOf(engine.eval(evalValue));
			        tf2.setText(tfOutput);

				} catch (ScriptException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}

			}
		});

		down.add(new JLabel("계산결과"));
		down.add(tf2);
		
		c.add(up, BorderLayout.NORTH);
		c.add(mid, BorderLayout.CENTER);
		c.add(down, BorderLayout.SOUTH);
		
		setSize(400, 400);
		setVisible(true);
	}
	
	public static void main(String[] args) {
		new Calculator();
	}
}

 

 

Comments