기본 MDAS Java 스윙 계산기
저는 최근에 Java를 배우기 시작했고 Swing에서 기본 MDAS 계산기를 만들기로 결정했습니다. 프로그래밍에 완전히 익숙하지는 않지만 일반적인 실수를하거나 가장 효율적인 코드를 작성하지 않을 수 있습니다.
매 연산 후 답을 반환하고 다음 번에 사용하는 대신 MDAS를 사용하여 답을 찾기 전에 여러 숫자와 연산을 취할 수있는 계산기를 만들고 싶었습니다.
예 : 1 대신 2 * 3 + 4 - 5 / 5 = 9
내 코드는 단일 클래스로 구성됩니다. 코드가 많지 않아서 여러 클래스로 나눌 이유가 있는지 몰랐지만 이런 식으로 작성한 적이 없으니 부담없이 수정 해주세요.
예제 gif 및 실행 가능한 jar가있는 저장소
package calculator;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class GUI extends JFrame {
private static final long serialVersionUID = 1L;
private String title = "Basic MDAS Calculator";
private int currentNumber;
private JLabel displayLabel = new JLabel(String.valueOf(currentNumber), JLabel.RIGHT);
private JPanel panel = new JPanel();
private boolean isClear = true;
final String[] ops = new String[] {"+", "-", "x", "/"};
private ArrayList<Integer> numHistory = new ArrayList<Integer>();
private ArrayList<String> opHistory = new ArrayList<String>();
public GUI() {
setPanel();
setFrame();
}
private void setFrame() {
this.setTitle(title);
this.add(panel, BorderLayout.CENTER);
this.setBounds(10,10,300,700);
this.setResizable(false);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private void setPanel() {
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
panel.setLayout(new GridLayout(0, 1));
displayLabel.setFont(new Font("Verdana", Font.PLAIN, 42));
panel.add(displayLabel);
panel.add(Box.createRigidArea(new Dimension(0, 0)));
createButtons();
}
private void createButtons() {
// 0-9
for (int i = 0; i < 10; i++) {
final int num = i;
JButton button = new JButton( new AbstractAction(String.valueOf(i)) {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
// If somebody presses "=" and then types a number, start a new equation instead
// of adding that number to the end like usual
if (!isClear) {
currentNumber = 0;
isClear = true;
}
if (currentNumber == 0) {
currentNumber = num;
} else {
currentNumber = currentNumber * 10 + num;
}
displayLabel.setText(String.valueOf(currentNumber));
}
});
panel.add(button);
}
// +, -, x, /
for (String op : ops) {
JButton button = new JButton( new AbstractAction(op) {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
numHistory.add(currentNumber);
currentNumber = 0;
opHistory.add(op);
displayLabel.setText(op);
}
});
panel.add(button);
}
// =
JButton button = new JButton( new AbstractAction("=") {
private static final long serialVersionUID = 1L;
private int i;
@Override
public void actionPerformed(ActionEvent e) {
// Display result
numHistory.add(currentNumber);
while (opHistory.size() > 0) {
if (opHistory.contains("x")) {
i = opHistory.indexOf("x");
numHistory.set(i, numHistory.get(i) * numHistory.get(i+1));
} else if (opHistory.contains("/")) {
i = opHistory.indexOf("/");
numHistory.set(i, numHistory.get(i) / numHistory.get(i+1));
} else if (opHistory.contains("+")) {
i = opHistory.indexOf("+");
numHistory.set(i, numHistory.get(i) + numHistory.get(i+1));
} else if (opHistory.contains("-")) {
i = opHistory.indexOf("-");
numHistory.set(i, numHistory.get(i) - numHistory.get(i+1));
}
opHistory.remove(i);
numHistory.remove(i+1);
}
displayLabel.setText(String.valueOf(numHistory.get(0)));
currentNumber = numHistory.get(0);
numHistory.clear();
if (isClear) {
isClear = false;
}
}
});
panel.add(button);
}
public static void main(String[] args) {
new GUI();
}
}
어떤 팁이라도 감사하겠습니다.
답변
package calculator;
패키지 이름은 소프트웨어와 작성자를 연결해야합니다 (예 : com.github.razemoon.basicmdasjavacaluclator.
public class GUI extends JFrame {
Java nming 규칙의 경우 일반적으로 UpperCamelCase를 사용하고 "Gui", "HtmlWidgetToolkit"또는 "HtmlCssParser"와 같은 약어에도 소문자를 사용합니다.
private static final long serialVersionUID = 1L;
이 필드는 클래스가 직렬화 될 가능성이 높은 경우에만 필요합니다.이 경우에는 그렇지 않을 가능성이 높습니다.
final String[] ops = new String[] {"+", "-", "x", "/"};
왜 그렇 package-private습니까?
또한 final배열은 final생각한대로 가 아니므로 개별 값을 변경할 수 있습니다. Enum을 원할 가능성이 큽니다 .... 실제로는 인터페이스가 필요하지만이 예제에서는 Enum이 충분할 가능성이 높습니다.
private ArrayList<Integer> numHistory = new ArrayList<Integer>();
private ArrayList<String> opHistory = new ArrayList<String>();
항상 가장 낮은 공통 인터페이스를 선언에 사용하십시오 List.
private void setFrame() {
this.setTitle(title);
this.add(panel, BorderLayout.CENTER);
this.setBounds(10,10,300,700);
this.setResizable(false);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
왜 this여기서 사용하고 있지만 다른 곳에서는 사용 하지 않습니까?
this.setResizable(false);
왜? 내가 볼 수있는 한 프레임 크기를 완벽하게 조정할 수 있습니다. 크기를 조정할 수 없도록 설정하면 다른 LaF 및 글꼴 크기에서 응용 프로그램을 사용할 수 없게됩니다.
for (int i = 0; i < 10; i++) {
저는 차원을 다루는 경우 한글자 변수 이름 만 사용할 수 있다는 점을 매우 끈질 기게 옹호합니다.
for (int number = 0; number <= 9; number++) {
// Or
for (int digit = 0; digit <= 9; digit++) {
final int num = i;
가능하다고해서 변수 이름을 줄이지 마십시오. 입력량을 줄여도 가독성이 떨어질 가치가 없습니다.
버튼 생성과 관련하여 코드를 읽기 쉽게 만드는 도우미 메서드와 클래스를 만들고 싶습니다.이 경우에는 다음과 같이 람다를 사용합니다.
private JButton createButton(String text, Runnable action) {
return new JButton(new AbstractButton(text) {
@Override
public void actionPerformed(ActionEvent e) {
action.run();
}
})
}
// In createButtons:
panel.add(createButton(Integer.toString(number), () -> {
// Code for the number button goes here.
}));
또 다른 대안은 private class NumberAction생성자에서 숫자를 받아들이고 관련 작업을 수행하는를 만드는 것 입니다. 또한 최종 선언을 제거 할 수 있습니다.
private int i;
그것은 매우 나쁜 변수 이름입니다.
public GUI() {
setPanel();
setFrame();
}
private void setFrame() {
this.setTitle(title);
this.add(panel, BorderLayout.CENTER);
this.setBounds(10,10,300,700);
this.setResizable(false);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
// ...
public static void main(String[] args) {
new GUI();
}
여기서 책임을 나누는 것이 좋습니다. 프레임 자체는 자체 레이아웃을 가져 오는 데에만 책임이 있으며, 메인 메서드는 프레임을 표시하는 데 책임이 있습니다.
public GUI() {
setPanel();
setFrame();
}
private void setFrame() {
this.setTitle(title);
this.add(panel, BorderLayout.CENTER);
this.setBounds(10,10,300,700);
this.setResizable(false);
}
// ...
public static void main(String[] args) {
GUI gui = new GUI();
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gui.setVisible(true);
}
귀하의 논리에는 오류 처리가 포함되어 있지 않은 것 같습니다. 연산자 버튼을 연속으로 두 번 누르면 오류가 발생한다고 생각합니다.
더 나은 방법은 입력 된대로 전체 표현식을 화면에 인쇄 한 다음 Shunting Yard Algorithm 을 적용하여 해당 표현식을 처리하는 것입니다.
논리는 소수를 수행하지 않으며 오버플로를 정상적으로 처리하지도 않습니다. 논리를 변경 BigDecimal하면 두 가지를 모두 쉽게 처리 할 수 있습니다. 적절한 정확성과 동작을 가지 려면 BigDecimal적절한 s를 만들어야합니다 MathContext.
당신이 이미 존재하는 구현을 읽고 싶다면, 내가 추천 할 수 exp4j 수학 표현식, 수레를 사용하여 라이브러리에 대한 EvalEx을 사용 하나 BigDecimal, 내 자신의 프로젝트 jMathPaper 스포츠 가지의 GUI (추상화에 대한) 계산기합니다.
배열 선언을 사용할 수 있습니다.
상수가 될 수있는 미리 정의 된 값이있는 배열을 원하는 경우 배열을 익명으로 선언 할 수 있습니다.
final String[] ops = {"+", "-", "x", "/"};
작업에 Enum을 사용합니다.
일련의 작업을하는 대신 Enum을 만드는 것이 좋습니다.
public enum Operators {
PLUS("+"), MINUS("-"), MUL("x"), DIV("/");
private final String operator;
Operators(String operator) {
this.operator = operator;
}
public String getOperator() {
return operator;
}
}
중복을 제거 할 수 있기 때문에 어레이보다 더 많은 이점을 제공합니다.
//[...]
for (Operators op : Operators.values()) {
JButton button = new JButton( new AbstractAction(op.getOperator()) {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
numHistory.add(currentNumber);
currentNumber = 0;
opHistory.add(op);
displayLabel.setText(String.valueOf(op.getOperator()));
}
});
panel.add(button);
}
//[...]
//[...]
if (opHistory.contains(Operators.MUL)) {
i = opHistory.indexOf(Operators.MUL);
numHistory.set(i, numHistory.get(i) * numHistory.get(i + 1));
} else if (opHistory.contains(Operators.DIV)) {
i = opHistory.indexOf(Operators.DIV);
numHistory.set(i, numHistory.get(i) / numHistory.get(i + 1));
} else if (opHistory.contains(Operators.PLUS)) {
i = opHistory.indexOf(Operators.PLUS);
numHistory.set(i, numHistory.get(i) + numHistory.get(i + 1));
} else if (opHistory.contains(Operators.MINUS)) {
i = opHistory.indexOf(Operators.MINUS);
numHistory.set(i, numHistory.get(i) - numHistory.get(i + 1));
}
//[...]
또한 제 생각에 이것은 코드 작업과 리팩토링을 더 쉽게 만들어 줄 것입니다.
나눌 때는 나눗셈을 divisor하기 전에 항상 확인하십시오 .
0으로 나눌 때 java.lang.ArithmeticExceptionjava에 의해 던져집니다. 나는 당신이 수표를 추가하는 것이 좋습니다 :)
Queue대신 사용하여 List기록을 유지하십시오.
를 사용 List하면 인덱스를 사용할 필요는은 Queue(첫 번째 항목을 제거하는 java.util.Queue#poll); 유일한 단점은 실제 코드를 리팩터링하여 indexOf.
private Queue<String> opHistory = new ArrayDeque<>();
이렇게하면 코드를 더 짧게 만들 수 있습니다.
while (opHistory.size() > 0) {
Operators currentOperator = opHistory.poll();
switch (currentOperator) { //Java 14+ Switch, you can use if or the older version of the switch.
case MUL -> numHistory.set(i, numHistory.get(i) * numHistory.get(i+1));
case DIV -> numHistory.set(i, numHistory.get(i) / numHistory.get(i+1));
case PLUS -> numHistory.set(i, numHistory.get(i) + numHistory.get(i+1));
case MINUS -> numHistory.set(i, numHistory.get(i) - numHistory.get(i+1));
}
numHistory.remove(i + 1);
}
여러 번 사용할 경우 표현식을 변수로 추출하십시오.
코드에서 유사한 표현식을 변수로 추출 할 수 있습니다. 이렇게하면 코드가 더 짧고 읽기 쉬워집니다.
final Integer first = numHistory.get(i);
final Integer second = numHistory.get(i + 1);
switch (currentOperator) {
case MUL -> numHistory.set(i, first * second);
case DIV -> numHistory.set(i, first / second);
case PLUS -> numHistory.set(i, first + second);
case MINUS -> numHistory.set(i, first - second);
}