2010-04-09 11 views
109

que tienen una cadena como la siguiente:¿Hay una función eval() en Java?

String str = "4*5"; 

ahora tengo que conseguir el resultado de 20 mediante el uso de la cadena.

Sé en otros idiomas que la función eval() hará esto. ¿Cómo puedo hacer esto en Java?

+0

me gustaría sugerir olvidarse de Java para esta tarea y utilizar Clojure. Clojure le permite analizar y/o compilar código en tiempo de ejecución (y en tiempo de compilación) y ejecutarlo, y también generar código en tiempo de compilación, así como muchas otras cosas que son, por cierto, bastante habituales en el mundo LISP. Java es demasiado aburrido. Y Clojure puede hacer todo lo que puede hacer Java, porque se ejecuta en JVM (aunque también existen otras implementaciones) Y se puede combinar con Java sin problemas, incluso dentro de un proyecto compartido. –

+2

Esto es * no * un duplicado de una pregunta que pregunta [cómo evaluar una expresión aritmética] (http://stackoverflow.com/questions/3422673/evaluating-a-math-expression-given-in-string-form) porque específicamente _ pregunta sobre eval(). – Raedwald

Respuesta

117

Puedes usar la clase ScriptEngine y evaluarla como una cadena de Javascript.

ScriptEngineManager manager = new ScriptEngineManager(); 
ScriptEngine engine = manager.getEngineByName("js"); 
Object result = engine.eval("4*5"); 

Puede haber una manera mejor, pero esta funciona.

+20

Cargando un intérprete de JavaScript completo para hacer algunas operaciones matemáticas parece una gran pérdida. Pero tienes razón, funciona, especialmente si la eficiencia no es una prioridad. –

+68

Además de ser un poco excesivo, usar el intérprete de JavaScript lo abre a la inyección de código. Si no controla estrictamente la expresión, alguien podría enviarle 'while (true) {3 + 4;}' y colgar su JVM. – Thilo

+2

Es eficiente en términos de tiempo de codificación. Si lo usa todo el tiempo, podría considerar mantenerlo en la memoria, quizás en un solo objeto. –

6

No, no puede tener un "eval" genérico en Java (o en cualquier lenguaje compilado). A menos que esté dispuesto a escribir un compilador Java Y una JVM para que se ejecute dentro de su programa Java.

, puede tener alguna biblioteca para evaluar expresiones algebraicas numéricas como la de arriba - see this thread for discussion.

40

No hay una clase o método Java estándar que haga lo que desee. Sus opciones incluyen:

  • Seleccione y use alguna biblioteca de evaluación de expresiones de terceros. Por ejemplo, JEL o cualquiera de las bibliotecas de media docena enumeradas here.

  • Envuelva la expresión en el código fuente de Java para una clase con un método eval, envíe eso al compilador de Java y luego cargue la clase compilada resultante.

  • Utilice un lenguaje de scripts que pueda invocarse desde Java como evaluador de expresiones. Las posibilidades incluyen Javascript, BeanShell, etc.

  • Escriba su propio evaluador de expresiones desde cero.

El primer enfoque es probablemente el más simple. El segundo y tercer enfoque son un posible riesgo de seguridad si obtiene la expresión para ser evaluada por un usuario que no es de confianza. (Piensa en la inyección de código.)

0

No hay nada que haga esto en JavaSE; tendrías que buscar bibliotecas de terceros o escribir las tuyas propias.

+1

¡Es una respuesta incorrecta! – user2284570

5

Como respuestas anteriores, no hay una API estándar en Java para esto.

Puede agregar archivos groovy jar a su camino y groovy.util.Eval.me ("4 * 5") hace su trabajo.

+4

Mismo código de problemas de inyección que con el uso de JavaScript. – Thilo

+0

Hay. 'ScriptEngine'. – EJP

+2

Compruebe la fecha de la respuesta. – Adi

25

Hay muy pocos casos de uso real en el que poder evaluar un String como un fragmento de código Java es necesario o deseable . Es decir, preguntar cómo hacer esto es realmente un XY problem: en realidad tiene un problema diferente, que se puede resolver de otra manera.

Primero pregúntese, ¿de dónde viene este String que desea evaluar? ¿Otra parte de tu programa lo generó, o fue una entrada proporcionada por el usuario?

  • Otra parte de mi programa generó: así, usted quiere una parte de su programa para decidir el tipo de operación a realizar, pero no lleva a cabo la operación, y una segunda parte que realiza la operación elegida . En lugar de generar y luego evaluar un String, utilice el patrón de diseño Strategy, Command o Builder, según corresponda para su caso particular.

  • Es la entrada del usuario: el usuario puede ingresar nada, incluyendo comandos que, al ejecutarse, podrían hacer que su programa de portarse mal, accidente, exponer la información que debe ser secreta, daños información persistente (como el contenido de una base de datos), y otras tales maldad. La única manera de evitar eso sería analizar el String usted mismo, comprobar que no era malicioso y luego evaluarlo. Pero analizarlo usted mismo es una gran parte del trabajo que la función eval solicitada haría, por lo que no se ha ahorrado nada. Peor aún, comprobar que arbitrariamente Java no fue malicioso es imposible, porque marcando que es el halting problem.

  • Es la entrada del usuario, pero la sintaxis y la semántica del texto es permitida para evaluar se ve mitigada,: No hay instalaciones de uso general puede implementar fácilmente un analizador de propósito general y evaluador para lo que restringe sintaxis y la semántica que ha elegido. Lo que necesita hacer es implementar un analizador y un evaluador para su sintaxis y semántica elegidas. Si la tarea es simple, puede escribir un analizador simple de recursive-descent o máquina de estados finitos a mano. Si la tarea es difícil, puede usar un compiler-compiler (como ANTLR) para hacer parte del trabajo por usted.

  • ¡Solo quiero implementar una calculadora de escritorio!: una tarea de tarea, ¿eh? Si pudieras implementar la evaluación de la expresión de entrada usando una función eval, no sería una gran tarea, ¿verdad? Su programa sería de tres líneas de largo. Su instructor probablemente espera que escriba el código para un analizador/grabador aritmético simple. Existe un algoritmo conocido, shunting-yard, que puede serle útil.

7

Yo podría aconsejarle que use Exp4j. Es fácil de entender como se puede ver en el siguiente código de ejemplo:

Expression e = new ExpressionBuilder("3 * sin(y) - 2/(x - 2)") 
    .variables("x", "y") 
    .build() 
    .setVariable("x", 2.3) 
    .setVariable("y", 3.14); 
double result = e.evaluate(); 
0

Una manera divertida para resolver su problema podría ser la codificación de una función eval() por su cuenta! ¡Lo he hecho por ti!

Puede utilizar la biblioteca FunctionSolver simplemente escribiendo FunctionSolver.solveByX (función, valor) dentro de su código. El atributo función es una cadena que representa la función que desea resolver, el valor es el valor de la variable independiente de su función (que DEBE ser x).

Si desea resolver una función que contiene más de una variable independiente, puede utilizar FunctionSolver.solve ( función, los valores ) donde el valores de atributo es un HashMap(String,Double) que contiene toda su independiente atributos (como cadenas) y sus respectivos valores (como dobles).

Otra pieza de información: He codificado una versión simple de FunctionSolver, por lo que sus soportes única Math methods, que devuelven un valor doble y que acepta uno o dos valores dobles como campos (sólo tiene que utilizar FunctionSolver.usableMathMethods() si tiene curiosidad) (Estos métodos son: bs, sin, cos, tan, atan2, sqrt, log, log10, pow, exp, mínimo, máximo, copySign, signum, IEEEremainder, acos, asin, atan, crtrt , ceil, cosh, expm1, piso, hypot, log1p, nextAfter, nextDown, nextUp, random, rint, sinh, tanh, toDegrees, toRadians, ulp). Además, esa biblioteca admite los siguientes operadores: */+ -^(incluso si java normalmente no admite el operador ^).

Una última cosa: al crear esta biblioteca tuve que usar reflections para llamar al Math methods. ¡Creo que es genial, solo have a look at this si estás interesado!

Eso es todo, aquí está el código (y el library):

package core; 

import java.lang.reflect.InvocationTargetException; 
import java.lang.reflect.Method; 
import java.util.ArrayList; 
import java.util.Arrays; 
import java.util.HashMap; 

public abstract class FunctionSolver { 

public static double solveNumericExpression (String expression) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { 
    return solve(expression, new HashMap<>()); 
} 

public static double solveByX (String function, double value) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { 

    HashMap<String, Double> values = new HashMap<>(); 
    values.put("x", value); 
    return solveComplexFunction(function, function, values); 
} 

public static double solve (String function, HashMap<String,Double> values) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { 

    return solveComplexFunction(function, function, values); 
} 

private static double solveComplexFunction (String function, String motherFunction, HashMap<String, Double> values) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { 

    int position = 0; 
    while(position < function.length()) { 
     if (alphabetic.contains(""+function.charAt(position))) { 
      if (position == 0 || !alphabetic.contains(""+function.charAt(position-1))) { 
       int endIndex = -1; 
       for (int j = position ; j < function.length()-1 ; j++) { 
        if (alphabetic.contains(""+function.charAt(j)) 
          && !alphabetic.contains(""+function.charAt(j+1))) { 
         endIndex = j; 
         break; 
        } 
       } 
       if (endIndex == -1 & alphabetic.contains(""+function.charAt(function.length()-1))) { 
        endIndex = function.length()-1; 
       } 
       if (endIndex != -1) { 
        String alphabeticElement = function.substring(position, endIndex+1); 
        if (Arrays.asList(usableMathMethods()).contains(alphabeticElement)) { 
         //Start analyzing a Math function 
         int closeParenthesisIndex = -1; 
         int openedParenthesisquantity = 0; 
         int commaIndex = -1; 
         for (int j = endIndex+1 ; j < function.length() ; j++) { 
          if (function.substring(j,j+1).equals("(")) { 
           openedParenthesisquantity++; 
          }else if (function.substring(j,j+1).equals(")")) { 
           openedParenthesisquantity--; 
           if (openedParenthesisquantity == 0) { 
            closeParenthesisIndex = j; 
            break; 
           } 
          }else if (function.substring(j,j+1).equals(",") & openedParenthesisquantity == 0) { 
           if (commaIndex == -1) { 
            commaIndex = j; 
           }else{ 
            throw new IllegalArgumentException("The argument of math function (which is "+alphabeticElement+") has too many commas"); 
           } 
          } 
         } 
         if (closeParenthesisIndex == -1) { 
          throw new IllegalArgumentException("The argument of a Math function (which is "+alphabeticElement+") hasn't got the closing bracket)"); 
         } 
         String functionArgument = function.substring(endIndex+2,closeParenthesisIndex); 
         if (commaIndex != -1) { 
          double firstParameter = solveComplexFunction(functionArgument.substring(0,commaIndex),motherFunction,values); 
          double secondParameter = solveComplexFunction(functionArgument.substring(commaIndex+1),motherFunction,values); 
          Method mathMethod = Math.class.getDeclaredMethod(alphabeticElement, new Class<?>[] {double.class, double.class}); 
          mathMethod.setAccessible(true); 
          String newKey = getNewKey(values); 
          values.put(newKey, (Double) mathMethod.invoke(null, firstParameter, secondParameter)); 
          function = function.substring(0, position)+newKey 
             +((closeParenthesisIndex == function.length()-1)?(""):(function.substring(closeParenthesisIndex+1))); 
         }else { 
          double firstParameter = solveComplexFunction(functionArgument, motherFunction, values); 
          Method mathMethod = Math.class.getDeclaredMethod(alphabeticElement, new Class<?>[] {double.class}); 
          mathMethod.setAccessible(true); 
          String newKey = getNewKey(values); 
          values.put(newKey, (Double) mathMethod.invoke(null, firstParameter)); 
          function = function.substring(0, position)+newKey 
             +((closeParenthesisIndex == function.length()-1)?(""):(function.substring(closeParenthesisIndex+1))); 
         } 
        }else if (!values.containsKey(alphabeticElement)) { 
         throw new IllegalArgumentException("Found a group of letters ("+alphabeticElement+") which is neither a variable nor a Math function: "); 
        } 
       } 
      } 
     } 
     position++; 
    } 
    return solveBracketsFunction(function,motherFunction,values); 
} 

private static double solveBracketsFunction (String function,String motherFunction,HashMap<String, Double> values) throws IllegalArgumentException{ 

    function = function.replace(" ", ""); 
    String openingBrackets = "([{"; 
    String closingBrackets = ")]}"; 
    int parenthesisIndex = 0; 
    do { 
     int position = 0; 
     int openParenthesisBlockIndex = -1; 
     String currentOpeningBracket = openingBrackets.charAt(parenthesisIndex)+""; 
     String currentClosingBracket = closingBrackets.charAt(parenthesisIndex)+""; 
     if (contOccouranceIn(currentOpeningBracket,function) != contOccouranceIn(currentClosingBracket,function)) { 
      throw new IllegalArgumentException("Error: brackets are misused in the function "+function); 
     } 
     while (position < function.length()) { 
      if (function.substring(position,position+1).equals(currentOpeningBracket)) { 
       if (position != 0 && !operators.contains(function.substring(position-1,position))) { 
        throw new IllegalArgumentException("Error in function: there must be an operator following a "+currentClosingBracket+" breacket"); 
       } 
       openParenthesisBlockIndex = position; 
      }else if (function.substring(position,position+1).equals(currentClosingBracket)) { 
       if (position != function.length()-1 && !operators.contains(function.substring(position+1,position+2))) { 
        throw new IllegalArgumentException("Error in function: there must be an operator before a "+currentClosingBracket+" breacket"); 
       } 
       String newKey = getNewKey(values); 
       values.put(newKey, solveBracketsFunction(function.substring(openParenthesisBlockIndex+1,position),motherFunction, values)); 
       function = function.substring(0,openParenthesisBlockIndex)+newKey 
          +((position == function.length()-1)?(""):(function.substring(position+1))); 
       position = -1; 
      } 
      position++; 
     } 
     parenthesisIndex++; 
    }while (parenthesisIndex < openingBrackets.length()); 
    return solveBasicFunction(function,motherFunction, values); 
} 

private static double solveBasicFunction (String function, String motherFunction, HashMap<String, Double> values) throws IllegalArgumentException{ 

    if (!firstContainsOnlySecond(function, alphanumeric+operators)) { 
     throw new IllegalArgumentException("The function "+function+" is not a basic function"); 
    } 
    if (function.contains("**") | 
     function.contains("//") | 
     function.contains("--") | 
     function.contains("+*") | 
     function.contains("+/") | 
     function.contains("-*") | 
     function.contains("-/")) { 
     /* 
     * (-+ , +- , *- , *+ , /- , /+)> Those values are admitted 
     */ 
     throw new IllegalArgumentException("Operators are misused in the function"); 
    } 
    function = function.replace(" ", ""); 
    int position; 
    int operatorIndex = 0; 
    String currentOperator; 
    do { 
     currentOperator = operators.substring(operatorIndex,operatorIndex+1); 
     if (currentOperator.equals("*")) { 
      currentOperator+="/"; 
      operatorIndex++; 
     }else if (currentOperator.equals("+")) { 
      currentOperator+="-"; 
      operatorIndex++; 
     } 
     operatorIndex++; 
     position = 0; 
     while (position < function.length()) { 
      if ((position == 0 && !(""+function.charAt(position)).equals("-") && !(""+function.charAt(position)).equals("+") && operators.contains(""+function.charAt(position))) || 
       (position == function.length()-1 && operators.contains(""+function.charAt(position)))){ 
       throw new IllegalArgumentException("Operators are misused in the function"); 
      } 
      if (currentOperator.contains(function.substring(position, position+1)) & position != 0) { 
       int firstTermBeginIndex = position; 
       while (firstTermBeginIndex > 0) { 
        if ((alphanumeric.contains(""+function.charAt(firstTermBeginIndex))) & (operators.contains(""+function.charAt(firstTermBeginIndex-1)))){ 
         break; 
        } 
        firstTermBeginIndex--; 
       } 
       if (firstTermBeginIndex != 0 && (function.charAt(firstTermBeginIndex-1) == '-' | function.charAt(firstTermBeginIndex-1) == '+')) { 
        if (firstTermBeginIndex == 1) { 
         firstTermBeginIndex--; 
        }else if (operators.contains(""+(function.charAt(firstTermBeginIndex-2)))){ 
         firstTermBeginIndex--; 
        } 
       } 
       String firstTerm = function.substring(firstTermBeginIndex,position); 
       int secondTermLastIndex = position; 
       while (secondTermLastIndex < function.length()-1) { 
        if ((alphanumeric.contains(""+function.charAt(secondTermLastIndex))) & (operators.contains(""+function.charAt(secondTermLastIndex+1)))) { 
         break; 
        } 
        secondTermLastIndex++; 
       } 
       String secondTerm = function.substring(position+1,secondTermLastIndex+1); 
       double result; 
       switch (function.substring(position,position+1)) { 
        case "*": result = solveSingleValue(firstTerm,values)*solveSingleValue(secondTerm,values); break; 
        case "/": result = solveSingleValue(firstTerm,values)/solveSingleValue(secondTerm,values); break; 
        case "+": result = solveSingleValue(firstTerm,values)+solveSingleValue(secondTerm,values); break; 
        case "-": result = solveSingleValue(firstTerm,values)-solveSingleValue(secondTerm,values); break; 
        case "^": result = Math.pow(solveSingleValue(firstTerm,values),solveSingleValue(secondTerm,values)); break; 
        default: throw new IllegalArgumentException("Unknown operator: "+currentOperator); 
       } 
       String newAttribute = getNewKey(values); 
       values.put(newAttribute, result); 
       function = function.substring(0,firstTermBeginIndex)+newAttribute+function.substring(secondTermLastIndex+1,function.length()); 
       deleteValueIfPossible(firstTerm, values, motherFunction); 
       deleteValueIfPossible(secondTerm, values, motherFunction); 
       position = -1; 
      } 
      position++; 
     } 
    }while (operatorIndex < operators.length()); 
    return solveSingleValue(function, values); 
} 

private static double solveSingleValue (String singleValue, HashMap<String, Double> values) throws IllegalArgumentException{ 

    if (isDouble(singleValue)) { 
     return Double.parseDouble(singleValue); 
    }else if (firstContainsOnlySecond(singleValue, alphabetic)){ 
     return getValueFromVariable(singleValue, values); 
    }else if (firstContainsOnlySecond(singleValue, alphanumeric+"-+")) { 
     String[] composition = splitByLettersAndNumbers(singleValue); 
     if (composition.length != 2) { 
      throw new IllegalArgumentException("Wrong expression: "+singleValue); 
     }else { 
      if (composition[0].equals("-")) { 
       composition[0] = "-1"; 
      }else if (composition[1].equals("-")) { 
       composition[1] = "-1"; 
      }else if (composition[0].equals("+")) { 
       composition[0] = "+1"; 
      }else if (composition[1].equals("+")) { 
       composition[1] = "+1"; 
      } 
      if (isDouble(composition[0])) { 
       return Double.parseDouble(composition[0])*getValueFromVariable(composition[1], values); 
      }else if (isDouble(composition[1])){ 
       return Double.parseDouble(composition[1])*getValueFromVariable(composition[0], values); 
      }else { 
       throw new IllegalArgumentException("Wrong expression: "+singleValue); 
      } 
     } 
    }else { 
     throw new IllegalArgumentException("Wrong expression: "+singleValue); 
    } 
} 

private static double getValueFromVariable (String variable, HashMap<String, Double> values) throws IllegalArgumentException{ 

    Double val = values.get(variable); 
    if (val == null) { 
     throw new IllegalArgumentException("Unknown variable: "+variable); 
    }else { 
     return val; 
    } 
} 

/* 
* FunctionSolver help tools: 
* 
*/ 

private static final String alphabetic = "abcdefghilmnopqrstuvzwykxy"; 
private static final String numeric = "."; 
private static final String alphanumeric = alphabetic+numeric; 
private static final String operators = "^*/+-"; //--> Operators order in important! 

private static boolean firstContainsOnlySecond(String firstString, String secondString) { 

    for (int j = 0 ; j < firstString.length() ; j++) { 
     if (!secondString.contains(firstString.substring(j, j+1))) { 
      return false; 
     } 
    } 
    return true; 
} 

private static String getNewKey (HashMap<String, Double> hashMap) { 

    String alpha = "abcdefghilmnopqrstuvzyjkx"; 
    for (int j = 0 ; j < alpha.length() ; j++) { 
     String k = alpha.substring(j,j+1); 
     if (!hashMap.containsKey(k) & !Arrays.asList(usableMathMethods()).contains(k)) { 
      return k; 
     } 
    } 
    for (int j = 0 ; j < alpha.length() ; j++) { 
     for (int i = 0 ; i < alpha.length() ; i++) { 
      String k = alpha.substring(j,j+1)+alpha.substring(i,i+1); 
      if (!hashMap.containsKey(k) & !Arrays.asList(usableMathMethods()).contains(k)) { 
       return k; 
      } 
     } 
    } 
    throw new NullPointerException(); 
} 

public static String[] usableMathMethods() { 

    /* 
    * Only methods that: 
    * return a double type 
    * present one or two parameters (which are double type) 
    */ 

    Method[] mathMethods = Math.class.getDeclaredMethods(); 
    ArrayList<String> usableMethodsNames = new ArrayList<>(); 
    for (Method method : mathMethods) { 
     boolean usable = true; 
     int argumentsCounter = 0; 
     Class<?>[] methodParametersTypes = method.getParameterTypes(); 
     for (Class<?> parameter : methodParametersTypes) { 
      if (!parameter.getSimpleName().equalsIgnoreCase("double")) { 
       usable = false; 
       break; 
      }else { 
       argumentsCounter++; 
      } 
     } 
     if (!method.getReturnType().getSimpleName().toLowerCase().equals("double")) { 
      usable = false; 
     } 
     if (usable & argumentsCounter<=2) { 
      usableMethodsNames.add(method.getName()); 
     } 
    } 
    return usableMethodsNames.toArray(new String[usableMethodsNames.size()]); 
} 

private static boolean isDouble (String number) { 
    try { 
     Double.parseDouble(number); 
     return true; 
    }catch (Exception ex) { 
     return false; 
    } 
} 

private static String[] splitByLettersAndNumbers (String val) { 
    if (!firstContainsOnlySecond(val, alphanumeric+"+-")) { 
     throw new IllegalArgumentException("Wrong passed value: <<"+val+">>"); 
    } 
    ArrayList<String> response = new ArrayList<>(); 
    String searchingFor; 
    int lastIndex = 0; 
    if (firstContainsOnlySecond(""+val.charAt(0), numeric+"+-")) { 
     searchingFor = alphabetic; 
    }else { 
     searchingFor = numeric+"+-"; 
    } 
    for (int j = 0 ; j < val.length() ; j++) { 
     if (searchingFor.contains(val.charAt(j)+"")) { 
      response.add(val.substring(lastIndex, j)); 
      lastIndex = j; 
      if (searchingFor.equals(numeric+"+-")) { 
       searchingFor = alphabetic; 
      }else { 
       searchingFor = numeric+"+-"; 
      } 
     } 
    } 
    response.add(val.substring(lastIndex,val.length())); 
    return response.toArray(new String[response.size()]); 
} 

private static void deleteValueIfPossible (String val, HashMap<String, Double> values, String function) { 
    if (values.get(val) != null & function != null) { 
     if (!function.contains(val)) { 
      values.remove(val); 
     } 
    } 
} 

private static int contOccouranceIn (String howManyOfThatString, String inThatString) { 
    return inThatString.length() - inThatString.replace(howManyOfThatString, "").length(); 
} 
} 
Cuestiones relacionadas