Neste artigo veremos como criar uma calculadora simples em Java. Nossa calculadora será capaz se realizar as operações básicas de: Soma, Adição, Multiplicação, Divisão e Subtração. Este projeto servirá como protótipo para quem deseja incrementar adicionando mais funcionalidades como: Raiz quadrada, Mod, Potência e etc.

A construção do nosso projeto será realizada de uma forma que você possa adicionar novas funcionalidades a calculadora de forma simples e objetiva, como plugins. Não vamos nos ater muito a conceitos teóricos neste artigo, daremos maior enfoque a prática e a mostrar o software funcionando.

Interface

Começaremos a desenvolver nossa interface gráfica sem nenhuma lógica interna, apenas uma interface para que possamos ter a ideia do que faremos, como mostra a Figura 1.

Interface
Figura 1. Interface

Temos aqui o principal campo que é a fórmula que o usuário digitará para calcular algo. Essa fórmula pode conter apenas as operações básicas citadas acima, além disso o usuário fica livre para colocar a quantidade de operadores e valores que desejar, assim damos maior liberdade ao mesmo. Perceba também que temos um label “Executado em” logo no rodapé do nosso formulário, isso dará ao usuário maior percepção de quanto tempo leva o seu cálculo, apenas um bônus da nossa calculadora.

Nota: Não deixe de dar uma olhada no Checklist Programador Java que a DevMedia preparou para seus assinantes.

Criamos a interface acima na IDE Netbeans e vamos fornecer a você, leitor, o código do nosso arquivo .form para que você possa criar o mesmo formulário no seu ambiente, como mostra a Listagem 1. Mas antes iremos explicar a utilidade de cada componente da interface:

  • Formula: Campo onde ficará a fórmula que o usuário deseja calcular;
  • Resultado: Campo onde será mostrado o resultado obtido do cálculo;
  • Calcular: Dispara um método onde será realizado todo o processamento da nossa calculadora;
  • Executado em: Tempo de execução do cálculo da fórmula;

<?xml version="1.0" encoding="UTF-8" ?>
 
<Form version="1.3" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
  <Properties>
    <Property name="defaultCloseOperation" type="int" value="3"/>
  </Properties>
  <SyntheticProperties>
    <SyntheticProperty name="formSizePolicy" type="int" value="1"/>
  </SyntheticProperties>
  <AuxValues>
    <AuxValue name="FormSettings_generateMnemonicsCode" 
    type="java.lang.Boolean" value="false"/>
    <AuxValue name="FormSettings_layoutCodeTarget" 
    type="java.lang.Integer" value="1"/>
    <AuxValue name="FormSettings_listenerGenerationStyle" 
    type="java.lang.Integer" value="0"/>
    <AuxValue name="FormSettings_variablesLocal" 
    type="java.lang.Boolean" value="false"/>
    <AuxValue name="FormSettings_variablesModifier" 
    type="java.lang.Integer" value="2"/>
  </AuxValues>
 
<Layout>
  <DimensionLayout dim="0">
    <Group type="103" groupAlignment="0" attributes="0">
    <Group type="102" attributes="0">
        <Group type="103" groupAlignment="0" attributes="0">
            <Group type="102" alignment="0" attributes="0">
            <EmptySpace min="-2" pref="120" max="-2" attributes="0"/>
            <Component id="jLabel1" min="-2" max="-2" attributes="0"/>
        </Group>
        <Group type="102" alignment="0" attributes="0">
            <EmptySpace min="-2" pref="20" max="-2" attributes="0"/>
            <Group type="103" groupAlignment="0" attributes="0">
                <Component id="jLabel2" alignment="0" min="-2" max="-2" 
                attributes="0"/>
                <Component id="jTextFieldFormula" alignment="0" min="-2" 
                pref="343" max="-2" attributes="0"/>
                <Component id="jLabel3" alignment="0" min="-2" max="-2" 
                attributes="0"/>
                <Component id="jTextFieldResultado" alignment="0" min="-2" 
                pref="343" max="-2" attributes="0"/>
                <Group type="102" alignment="0" attributes="0">
                    <Component id="jButtonCalcular" min="-2" max="-2" 
                    attributes="0"/>
                    <EmptySpace max="-2" attributes="0"/>
                    <Component id="jLabel4" min="-2" max="-2" 
                    attributes="0"/>
                    <EmptySpace max="-2" attributes="0"/>
                    <Component id="jLabelTempoExecucao" min="-2" 
                    pref="119" max="-2" attributes="0"/>
                    </Group>
                </Group>
                </Group>
            </Group>
              <EmptySpace pref="37" max="32767" attributes="0"/>
          </Group>
      </Group>
    </DimensionLayout>
    <DimensionLayout dim="1">
      <Group type="103" groupAlignment="0" attributes="0">
          <Group type="102" alignment="0" attributes="0">
              <EmptySpace max="-2" attributes="0"/>
              <Component id="jLabel1" min="-2" max="-2" attributes="0"/>
              <EmptySpace min="-2" pref="26" max="-2" attributes="0"/>
              <Component id="jLabel2" min="-2" max="-2" attributes="0"/>
              <EmptySpace max="-2" attributes="0"/>
              <Component id="jTextFieldFormula" min="-2" 
              max="-2" attributes="0"/>
              <EmptySpace min="-2" pref="26" max="-2" attributes="0"/>
              <Component id="jLabel3" min="-2" max="-2" attributes="0"/>
              <EmptySpace max="-2" attributes="0"/>
              <Component id="jTextFieldResultado" min="-2" 
              max="-2" attributes="0"/>
              <EmptySpace min="-2" pref="28" max="-2" attributes="0"/>
              <Group type="103" groupAlignment="3" attributes="0">
                  <Component id="jButtonCalcular" 
                  alignment="3" min="-2" max="-2" attributes="0"/>
                  <Component id="jLabel4" alignment="3" min="-2" 
                  max="-2" attributes="0"/>
                  <Component id="jLabelTempoExecucao" 
                  alignment="3" min="-2" max="-2" attributes="0"/>
              </Group>
              <EmptySpace pref="23" max="32767" attributes="0"/>
          </Group>
      </Group>
    </DimensionLayout>
  </Layout>
  <SubComponents>
    <Component class="javax.swing.JLabel" name="jLabel1">
      <Properties>
        <Property name="text" type="java.lang.String" value="Calculadora Simples"/>
      </Properties>
    </Component>
    <Component class="javax.swing.JLabel" name="jLabel2">
      <Properties>
        <Property name="text" type="java.lang.String" value="Fórmula"/>
      </Properties>
    </Component>
    <Component class="javax.swing.JButton" name="jButtonCalcular">
      <Properties>
        <Property name="text" type="java.lang.String" value="Calcular"/>
      </Properties>
    </Component>
    <Component class="javax.swing.JTextField" name="jTextFieldFormula">
      <Properties>
        <Property name="text" type="java.lang.String" value="Ex: 1 + 1 * 3"/>
      </Properties>
    </Component>
    <Component class="javax.swing.JLabel" name="jLabel3">
      <Properties>
        <Property name="text" type="java.lang.String" value="Resultado"/>
      </Properties>
    </Component>
    <Component class="javax.swing.JTextField" name="jTextFieldResultado">
      <Properties>
        <Property name="editable" type="boolean" value="false"/>
      </Properties>
    </Component>
    <Component class="javax.swing.JLabel" name="jLabel4">
      <Properties>
        <Property name="text" type="java.lang.String" value="Executado em:"/>
      </Properties>
    </Component>
    <Component class="javax.swing.JLabel" name="jLabelTempoExecucao">
      <Properties>
        <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
          <Font name="Dialog" size="12" style="2"/>
        </Property>
        <Property name="text" type="java.lang.String" value="0.0 ms"/>
      </Properties>
    </Component>
  </SubComponents>
</Form>
Listagem 1. Fcalc.form

Acima vimos o nosso arquivo “.form” que é gerado automaticamente pelo Netbeans ao criarmos os documentos, colocamos ele apenas para que você leitor possa criar o formulário idêntico ao que mostramos na Figura 1. Vamos agora ver como ficou o código em Java do nosso formulário Fcalc.java por partes, como mostra a Listagem 2. Numeramos as linhas importantes para facilitar o entendimento


package br.com.dev.gui;
 
public class FCalc extends javax.swing.JFrame {
    
    /** Creates new form FCalc */
//(1)    public FCalc() {
        initComponents();
    }
    
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc=" 
    Código Gerado ">                          
//(2)    private void initComponents() {
        jLabel1 = new javax.swing.JLabel();
        jLabel2 = new javax.swing.JLabel();
        jButtonCalcular = new javax.swing.JButton();
        jTextFieldFormula = new javax.swing.JTextField();
        jLabel3 = new javax.swing.JLabel();
        jTextFieldResultado = new javax.swing.JTextField();
        jLabel4 = new javax.swing.JLabel();
        jLabelTempoExecucao = new javax.swing.JLabel();
 
        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        jLabel1.setText("Calculadora Simples");
 
        jLabel2.setText("F\u00f3rmula");
 
        jButtonCalcular.setText("Calcular");
 
        jTextFieldFormula.setText("Ex: 1 + 1 * 3");
 
        jLabel3.setText("Resultado");
 
        jTextFieldResultado.setEditable(false);
 
        jLabel4.setText("Executado em:");
 
        jLabelTempoExecucao.setFont(new java.awt
        .Font("Dialog", 2, 12));
        jLabelTempoExecucao.setText("0.0 ms");
 
        javax.swing.GroupLayout layout = new javax
        .swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout
            .Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(javax.swing
                .GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addGap(120, 120, 120)
                        .addComponent(jLabel1))
                    .addGroup(layout.createSequentialGroup()
                        .addGap(20, 20, 20)
                        .addGroup(layout.createParallelGroup(
                        javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(jLabel2)
                            .addComponent(jTextFieldFormula, 
                            javax.swing.GroupLayout.PREFERRED_SIZE, 343, 
                            javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(jLabel3)
                            .addComponent(jTextFieldResultado, 
                            javax.swing.GroupLayout.PREFERRED_SIZE, 343, 
                            javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addGroup(layout.createSequentialGroup()
                                .addComponent(jButtonCalcular)
                                .addPreferredGap(javax.swing.LayoutStyle
                                .ComponentPlacement.RELATED)
                                .addComponent(jLabel4)
                                .addPreferredGap(javax.swing.LayoutStyle
                                .ComponentPlacement.RELATED)
                                .addComponent(jLabelTempoExecucao, 
                                javax.swing.GroupLayout.PREFERRED_SIZE, 119, 
                                javax.swing.GroupLayout.PREFERRED_SIZE)))))
                .addContainerGap(37, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout
            .Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jLabel1)
                .addGap(26, 26, 26)
                .addComponent(jLabel2)
                .addPreferredGap(javax.swing.LayoutStyle
                .ComponentPlacement.RELATED)
                .addComponent(jTextFieldFormula, javax
                .swing.GroupLayout.PREFERRED_SIZE, javax
                .swing.GroupLayout.DEFAULT_SIZE, javax.swing
                .GroupLayout.PREFERRED_SIZE)
                .addGap(26, 26, 26)
                .addComponent(jLabel3)
                .addPreferredGap(javax.swing.LayoutStyle
                .ComponentPlacement.RELATED)
                .addComponent(jTextFieldResultado, javax.swing
                .GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout
                .DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addGap(28, 28, 28)
                .addGroup(layout.createParallelGroup(javax.swing
                .GroupLayout.Alignment.BASELINE)
                    .addComponent(jButtonCalcular)
                    .addComponent(jLabel4)
                    .addComponent(jLabelTempoExecucao))
                .addContainerGap(23, Short.MAX_VALUE))
        );
        pack();
    }// </editor-fold>                        
    
    /**
     * @param args the command line arguments
     */
//(3)    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new FCalc().setVisible(true);
            }
        });
    }
    
             
    private javax.swing.JButton jButtonCalcular;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JLabel jLabel4;
    private javax.swing.JLabel jLabelTempoExecucao;
    private javax.swing.JTextField jTextFieldFormula;
    private javax.swing.JTextField jTextFieldResultado;
Listagem 2. Fcalc.java

Na linha 1 temos o construtor da nossa classe, gerado automaticamente pelo Netbeans, onde este já coloca o método initComponents() automaticamente. Na linha 2 temos a implementação do método initComponents() que nada mais é do que a organização e criação de todos os nossos componentes na janela, ou seja, é lá em que é colocado o tamanho, localização, cores e fontes da nossa janela.

Por fim, na linha 3 temos o método main() que será chamado ao executarmos nosso programa. O método main() instancia a nossa classe Fcalc e coloca ela como visível, por isso há o setVisible(true).

Core

Vamos começar agora a desenvolver a lógica do nosso projeto, deixando o mais genérico possível para possibilitar a adição de novas funcionalidades sem a necessidade de grandes alterações. Apenas uma observação importante é que não implementaremos o uso do parênteses na nossa calculadora (prioridade de operações), visto que isto iria deixar a nossa lógica muito complexa e estender muito este artigo.

Primeiro vamos criar uma classe abstrata chamada Operador que servirá como base para criação das outras classes chamadas: Soma, Substracao, Divisao e Multiplicacao. Vejamos as Listagens 3 a 7.


package br.com.dev.gui;
 
//(1)
public abstract class Operador {            
       
       //(2)  
       private char simbolo;
       
       //(3)
       public abstract double calcula(double valor1, double valor2);
       
       
       public char getSimbolo(){
             return this.simbolo;
       }
 
       
       public void setSimbolo(char simbolo){
             this.simbolo = simbolo;
       }
   
       
}
Listagem 3. Operador.java

package br.com.dev.gui;
 
public class Soma extends Operador{
       
       public Soma(){
             setSimbolo('+');
       }
       
       @Override
       public double calcula(double valor1, double valor2) {
             return valor1 + valor2;
       }
 
}
Listagem 4. Soma.java

package br.com.dev.gui;
 
public class Subtracao extends Operador{
       
       public Subtracao(){
             setSimbolo('-');
       }
       
       @Override
       public double calcula(double valor1, double valor2) {
             return valor1 - valor2;
       }
 
}
Listagem 5. Subtracao.java

package br.com.dev.gui;
 
public class Multiplicacao extends Operador{
       
       public Multiplicacao(){
             setSimbolo('*');
       }
       
       @Override
       public double calcula(double valor1, double valor2) {
             return valor1 * valor2;
       }
 
}
Listagem 6. Multiplicação

package br.com.dev.gui;
 
//(1)
public class Divisao extends Operador{
       
       //(2) public Divisao(){
             setSimbolo('/');
       }
       
       @Override
       //(3) public double calcula(double valor1, double valor2) {
             return valor1 / valor2;
       }
 
}
Listagem 7. Divisão

A ideia aqui é que nós iremos usar sempre a classe Operador e chamar o método “calcula(), independente de qual instancia estamos trabalhando (Soma, Divisao ..), pois assim deixamos o projeto bem estruturado para que possam ser adicionadas outras funções como: Mod, Potência e etc.

Vejamos com mais detalhes a classe da Listagens 1 e 7 (Operador.Java e Divisao.java), que estão numeradas.

A nossa classe Operador.java é a “alma” da nossa lógica, é através dela que conseguimos abstrair as funcionalidades da calculadora e possibilitar a adição de novas funções sem mudar a estrutura da mesma. Na linha 1 temos a declaração da classe como abstrata, ou seja, não é possível instanciar a classe Operador e nem faz sentido já que ela é apenas um modelo a ser seguido. Na linha 2 temos o atributo simbolo do tipo char, este atributo é de extrema importância para dizer qual o símbolo que nossa classe espera da fórmula digitada pelo usuário , sem ele não temos como direcionar o cálculo para o local correto. A linha 3 possui a assinatura do método “calcula()” declarado como abstrato, assim garantimos que ele será implementado na classe que herdar Operador.java. É no método calcula() que a “mágica” começa, ou seja, a implementação deste método depende do tipo de operador que estamos trabalhando, isso chama-se “polimorfismo”.

Como exemplo temos a classe Divisao.java, que logo na primeira linha estende da classe abstrata Operador. Na linha 2 temos o construtor que seta o valor da variável simbolo de acordo com o que a classe deve suportar, neste caso um simbolo '/'. É ideal que o valor da variável simbolo seja definido logo na instanciação da classe pois precisaremos usá-lo no código mais a frente. Por fim, na linha 3 temos um Override (sobreescrita) do método calcula() que foi declarado na classe Operador, aqui nós definimos o que ele deverá fazer com os valores passados, e nesse caso será um divisão.

Visto isso, vamos começar a colocar o código na classe Fcalc, de acordo com a Listagem 8.


public class FCalc extends javax.swing.JFrame {
 
private List<Operador> opSuportados;
 
public FCalc() {
        initComponents();
        
        
        opSuportados = new ArrayList<Operador>();
        opSuportados.add(new Soma());
        opSuportados.add(new Subtracao());
        opSuportados.add(new Multiplicacao());
        opSuportados.add(new Divisao());
    }
...
Listagem 8. Construtor e atributos da Fcalc

Começamos adicionando na nossa classe um atributo chamado opSuportados, que irá servir de container para todos os operadores que a nossa calculadora aceita.


private void jButtonCalcularActionPerformed(java.awt.event.ActionEvent evt) {
   double tempoInicial = System.currentTimeMillis();
   inicarCalculo();
   double tempoFinal = System.currentTimeMillis() - tempoInicial;
   jLabelTempoExecucao.setText(String.valueOf(tempoFinal) + " ms");
}
Listagem 9. Ação do botão calcular

Lembre que temos na Listagem 9 um campo para mostrar o tempo em que foi realizado o cálculo. Para isso usamos o método currentTimeMillis(). Este método mostrado na Listagem 9 é chamado quando o botão jButtonCalcular é clicado, mais a frente mostraremos a classe Fcalc completa, por enquanto estamos mostrando por partes para facilitar o entendimento.


private void inicarCalculo(){
String formula = jTextFieldFormula.getText().trim();   
List<Integer> numeros = new ArrayList<Integer>();
List<Character> operadores = new ArrayList<Character>();
String numeroAsString = "";

for(int i = 0; i < formula.trim().length(); i++){                 

     if (isNumero(String.valueOf(formula.charAt(i)))){
            numeroAsString = numeroAsString.concat(String.valueOf(formula.charAt(i)));

     }else if (isOperador(formula.charAt(i))){
            numeros.add(Integer.parseInt(numeroAsString));
            operadores.add(formula.charAt(i));
            numeroAsString = "";              
     }
     

     if (i+1 == formula.trim().length()){
            numeros.add(Integer.parseInt(numeroAsString));
     }
}

double resultado = 0;

for (int i = 0; i < numeros.size(); i++){
     
     if (i == 0){
            resultado = numeros.get(i);
     }else{
            for(Operador op : opSuportados){
                   if (op.getSimbolo() == operadores.get(i-1)){
                          resultado = op.calcula(resultado, numeros.get(i));
                          break;
                   }
            }
     }
}

jTextFieldResultado.setText(String.valueOf(resultado));
}

private boolean isOperador(char c){

for(Operador op : opSuportados){
     if (op.getSimbolo() == c){
            return true;
     }
}

JOptionPane.showMessageDialog(this, "O caractere '"+c+"' não é suportado.");
     throw new RuntimeException("O caractere '"+c+"' não é suportado.");      
}

private boolean isNumero(String numero) {
     try {
            Integer i = Integer.parseInt(numero);
     } catch (NumberFormatException nfe) {
            return false;
     }
     return true;
}
Listagem 10. método iniciarCalculo()

Por fim temos o método que realiza o calculo propriamente dito, fazendo uso dos operadores suportados. Perceba que se adicionarmos mais operadores com novas fórmulas de cálculo o nosso método iniciaCalculo() não muda e é exatamente isso que queremos, adicionar funcionalidade sem perder a estrutura inicial que foi projetada.

Nova Funcionalidade

Vimos até agora toda a montagem da nossa calculadora, deixando um “espaço” para que possa ser adicionada novas funções a ela. Vamos adicionar a “Potência” a nossa calculadora e ver que nada na estrutura será mudado. Observe a Listagem 11.


package br.com.dev.gui;
 
public class Potencia extends Operador{
       
       public Potencia(){
             setSimbolo('^');
       }
       
       @Override
       public double calcula(double valor1, double valor2) {
             return Math.pow(valor1,valor2);
       }
 
}
Listagem 11. Potencia.java

A nossa classe acima define o símbolo como “^”, e no método calcula nós usamos a função pow() da classe Math para calcular a potência dos valores 1 e 2 passados como argumentos. Veja que o nosso método calcula() não tem limites, poderíamos definir um cálculo supercomplexo e o único limite é o nosso nível de conhecimento em matemática.

Mas além de criarmos a classe temos que adicioná-la ao nosso Fcalc para que ela possa ser entendida como “suportada”. Veja como é muito simples a inserção na Listagem 12.


public FCalc() {
    initComponents();
           
    opSuportados = new ArrayList<Operador>();
    opSuportados.add(new Soma());
    opSuportados.add(new Subtracao());
    opSuportados.add(new Multiplicacao());
    opSuportados.add(new Divisao());
    opSuportados.add(new Potencia());
}
Listagem 12. Adicionando classe Potencia.java ao Fcalc

Adicionamos na última linha do construtor da classe Fcalc a classe Potencia para que nossa calculadora possa suportar a nova operação.

Neste artigo vimos como criar uma calculadora simples com as operações matemáticas básicas, porém deixamos o projeto genérico o bastante para que esta simples calculadora possa se tornar mais poderosa e com mais recursos.