tipos ejemplos datos java primitive code-duplication

datos - tipos de variables en java ejemplos



Duplicación de código causada por tipos primitivos: ¿cómo evitar la locura? (8)

En uno de mis proyectos Java, estoy plagado de repetición de código debido a la forma en que Java maneja (not) primitivas. Después de tener que copiar manualmente el mismo cambio en cuatro ubicaciones diferentes ( int , long , float , double ) nuevamente , por tercera vez, una y otra vez me acerqué mucho (?) Al ajuste.

En varias formas, este problema se ha planteado de vez en cuando en StackOverflow:

  • Gestionar código y documentación altamente repetitivos en Java
  • ¿Cómo evitar la repetición cuando se trabaja con tipos primitivos?
  • Pasar lista dinámica de primitivas a un método Java

El consenso parecía converger a dos alternativas posibles:

  • Usa algún tipo de generador de código.
  • ¿Qué puedes hacer? ¡Así es la vida!

Bueno, la segunda solución es lo que estoy haciendo ahora y lentamente se está volviendo peligrosa para mi cordura, al igual que la conocida técnica de tortura .

Han pasado dos años desde que se hicieron estas preguntas y apareció Java 7. Por lo tanto, tengo la esperanza de una solución más fácil y / o más estándar.

  • ¿Tiene Java 7 algún cambio que pueda aliviar la tensión en tales casos? No pude encontrar nada en los resúmenes de cambios condensados, pero tal vez hay alguna nueva característica oscura en alguna parte.

  • Si bien la generación de código fuente es una alternativa, preferiría una solución compatible con el conjunto de funciones JDK estándar. Claro, el uso de cpp u otro generador de código funcionaría, pero agrega más dependencias y requiere cambios en el sistema de compilación.

    El único tipo de sistema de generación de código que parece ser compatible con el JDK es a través del mecanismo de anotaciones. Imagino un procesador que expandiría el código fuente así:

    @Primitives({ "int", "long", "float", "double" }) @PrimitiveVariable int max(@PrimitiveVariable int a, @PrimitiveVariable int b) { return (a > b)?a:b; }

    El archivo de salida ideal contendría las cuatro variaciones solicitadas de este método, preferiblemente con los comentarios de Javadoc asociados, etc. ¿Existe algún procesador de anotación para manejar este caso? Si no, ¿qué se necesitaría para construir uno?

  • Tal vez algún otro truco que ha aparecido recientemente?

EDITAR:

Una nota importante: no usaría tipos primitivos a menos que tuviera una razón. Incluso ahora hay un impacto muy real en el rendimiento y la memoria mediante el uso de tipos de cajas en algunas aplicaciones.

EDICION 2:

El uso de max() como ejemplo permite el uso del compareTo() que está disponible en todos los tipos de recuadros numéricos. Esto es un poco más complicado:

int sum(int a, int b) { return a + b; }

¿Cómo se puede seguir apoyando este método para todos los tipos de recuadros numéricos sin escribirlo realmente seis o siete veces?


¿Tiene Java 7 algún cambio que pueda aliviar la tensión en tales casos?

No.

¿Hay algún procesador de anotaciones en algún lugar para manejar este caso?

No que yo sepa.

Si no, ¿qué se necesitaría para construir uno?

Tiempo o dinero :-)

Esto me parece un problema-espacio donde sería difícil encontrar una solución general que funcione bien ... más allá de casos triviales. La generación de código fuente convencional o un preprocesador (textual) me parece más prometedor. (Aunque no soy un experto en procesadores de anotaciones).


¿Por qué estás colgado de primitivos? Las envolturas son extremadamente livianas y el auto-boxeo y los genéricos hacen el resto:

public static <T extends Number & Comparable<T>> T max(T a, T b) { return a.compareTo(b) > 0 ? a : b; }

Todo esto se compila y se ejecuta correctamente:

public static void main(String[] args) { int i = max(1, 3); long l = max(6,7); float f = max(5f, 4f); double d = max(2d, 4d); byte b = max((byte)1, (byte)2); short s = max((short)1, (short)2); }

Editado

OP ha preguntado acerca de una solución genérica de auto-recuadro para sum() , y aquí está.

public static <T extends Number> T sum(T... numbers) throws Exception { double total = 0; for (Number number : numbers) { total += number.doubleValue(); } if (numbers[0] instanceof Float || numbers[0] instanceof Double) { return (T) numbers[0].getClass().getConstructor(String.class).newInstance(total + ""); } return (T) numbers[0].getClass().getConstructor(String.class).newInstance((total + "").split("//.")[0]); }

Es un poco cojo, pero no tan cojo como hacer una gran serie de instanceof y delegar en un método completamente tipado. La instanceof se requiere porque, aunque todos los Numbers tienen un constructor de String , los Numbers no sean Float y Double solo pueden analizar un número entero (sin punto decimal); aunque el total será un número entero, debemos eliminar el punto decimal de Double.toString() antes de enviarlo al constructor para estos otros tipos.


Desde el punto de vista del rendimiento (también hago muchos algoritmos de CPU), utilizo mis propios boxings que no son inmutables. Esto permite utilizar números mutables en conjuntos como ArrayList y HashMap para trabajar con alto rendimiento.

Se necesita un largo paso de preparación para hacer todos los contenedores primitivos con su código repetitivo, y luego simplemente los usa. Como también trato con valores bidimensionales, tridimensionales, etc., también los creé para mí. La decisión es tuya.

me gusta:
Vector1i - 1 entero, reemplaza Integer
Vector2i - 2 entero, reemplaza el Point y la Dimension
Vector2d - 2 dobles, reemplaza a Point2D.Double
Vector4i - 4 enteros, podría reemplazar Rectangle
Vector2f - vector flotante bidimensional
Vector3f - vector flotante tridimensional
... etc ...
Todos ellos representan un "vector" generalizado en matemáticas, de ahí el nombre de todos estos primitivos.

Una desventaja es que no puedes hacer a+b , tienes que hacer métodos como a.add(b) , y para a=a+b elijo nombrar los métodos como a.addSelf(b) . Si esto te molesta, echa un vistazo a Ceylon , que descubrí hace muy poco. Es una capa sobre Java (JVM / Eclispe compatbile) creada especialmente para abordar sus limitaciones (como la sobrecarga del operador).

Otra cosa es tener cuidado al usar estas clases como una clave en un Map , ya que la clasificación / hashing / comparación se volverá loca cuando el valor cambie.


Estoy de acuerdo con las respuestas / comentarios anteriores que dicen que no hay una manera de hacer exactamente lo que quiere "utilizando el conjunto de funciones JDK estándar". Por lo tanto, tendrá que hacer algo de generación de código, aunque no necesariamente requerirá cambios en el sistema de compilación. Ya que preguntas:

... Si no, ¿qué se necesitaría para construir uno?

... Para un caso simple, no demasiado, creo. Supongamos que pongo mis operaciones primitivas en una clase util:

public class NumberUtils { // @PrimitiveMethodsStart /** Find maximum of int inputs */ public static int max(int a, int b) { return (a > b) ? a : b; } /** Sum the int inputs */ public static int sum(int a, int b) { return a + b; } // @PrimitiveMethodsEnd // @GeneratedPrimitiveMethodsStart - Do not edit below // @GeneratedPrimitiveMethodsEnd }

Entonces puedo escribir un procesador simple en menos de 30 líneas de la siguiente manera:

public class PrimitiveMethodProcessor { private static final String PRIMITIVE_METHODS_START = "@PrimitiveMethodsStart"; private static final String PRIMITIVE_METHODS_END = "@PrimitiveMethodsEnd"; private static final String GENERATED_PRIMITIVE_METHODS_START = "@GeneratedPrimitiveMethodsStart"; private static final String GENERATED_PRIMITIVE_METHODS_END = "@GeneratedPrimitiveMethodsEnd"; public static void main(String[] args) throws Exception { String fileName = args[0]; BufferedReader inputStream = new BufferedReader(new FileReader(fileName)); PrintWriter outputStream = null; StringBuilder outputContents = new StringBuilder(); StringBuilder methodsToCopy = new StringBuilder(); boolean inPrimitiveMethodsSection = false; boolean inGeneratedPrimitiveMethodsSection = false; try { for (String line;(line = inputStream.readLine()) != null;) { if(line.contains(PRIMITIVE_METHODS_END)) inPrimitiveMethodsSection = false; if(inPrimitiveMethodsSection)methodsToCopy.append(line).append(''/n''); if(line.contains(PRIMITIVE_METHODS_START)) inPrimitiveMethodsSection = true; if(line.contains(GENERATED_PRIMITIVE_METHODS_END)) inGeneratedPrimitiveMethodsSection = false; if(!inGeneratedPrimitiveMethodsSection)outputContents.append(line).append(''/n''); if(line.contains(GENERATED_PRIMITIVE_METHODS_START)) { inGeneratedPrimitiveMethodsSection = true; String methods = methodsToCopy.toString(); for (String primative : new String[]{"long", "float", "double"}) { outputContents.append(methods.replaceAll("int//s", primative + " ")).append(''/n''); } } } outputStream = new PrintWriter(new FileWriter(fileName)); outputStream.print(outputContents.toString()); } finally { inputStream.close(); if(outputStream!= null) outputStream.close(); } } }

Esto llenará la sección @GeneratedPrimitiveMethods con versiones larga, flotante y doble de los métodos en la sección @PrimitiveMethods.

// @GeneratedPrimitiveMethodsStart - Do not edit below /** Find maximum of long inputs */ public static long max(long a, long b) { return (a > b) ? a : b; } ...

Este es un ejemplo intencionalmente simple, y estoy seguro de que no cubre todos los casos, pero entiendes el punto y puedes ver cómo podría ampliarse, por ejemplo, para buscar múltiples archivos o usar anotaciones normales y detectar los fines del método.

Además, aunque podría configurar esto como un paso en su sistema de compilación, configuré esto para que se ejecute como generador antes que el generador de Java en mi proyecto de eclipse. Ahora cada vez que edito el archivo y presiono guardar; se actualiza automáticamente, en su lugar, en menos de un cuarto de segundo. Por lo tanto, esto se convierte más en una herramienta de edición que en un paso en el sistema de compilación.

Solo un pensamiento...


Je. ¿Por qué no ser astuto? Con la reflexión, puede extraer las anotaciones de un método (anotaciones similares al ejemplo que ha publicado). A continuación, puede usar reflection para obtener los nombres de los miembros y colocar los tipos apropiados ... En una instrucción system.out.println.

Podrías ejecutar esto una vez, o cada vez que modificaras la clase. El resultado podría copiarse y pegarse. Esto probablemente le ahorraría mucho tiempo y no sería demasiado difícil de desarrollar.

Hm, en cuanto al contenido de los métodos ... Quiero decir, si todos tus métodos son triviales, podrías codificar el estilo (es decir, si methodName.equals ("max") imprimir devuelve a> b: a: b, etc. Donde methodName se determina a través de la reflexión), o podría, ummmmm ... Hm. Me imagino que el contenido puede copiarse fácilmente pegado, pero eso parece más trabajo.

Oh! No haga otra anotación llamada "contenidos", proporciónele un valor de cadena del contenido del método, agréguelo al miembro y ahora puede imprimir el contenido también.

Por lo menos, el tiempo dedicado a la codificación de este ayudante, incluso si es tan largo como hacer el trabajo tedioso, bueno, sería más interesante, ¿verdad?


Si la verbosidad extraordinaria de Java le está afectando, busque en algunos de los nuevos lenguajes de alto nivel que se ejecutan en la JVM y pueden interoperar con Java, como Clojure, JRuby, Scala, etc. Su repetición primitiva fuera de control se convertirá en un problema. Pero los beneficios irán mucho más allá: hay todo tipo de formas que los lenguajes que acabamos de mencionar le permiten hacer más cosas con un código menos detallado, repetitivo y propenso a errores (en comparación con Java).

Si el rendimiento es un problema, puede regresar a Java para los bits críticos para el rendimiento (usando tipos primitivos). Pero es posible que se sorprenda de la frecuencia con la que todavía puede obtener un buen nivel de rendimiento en el lenguaje de nivel superior.

Yo personalmente uso tanto JRuby como Clojure; si proviene de un fondo Java / C / C # / C ++, ambos tienen el potencial de cambiar la forma en que piensa acerca de la programación.


Tiendo a usar un "super tipo" como long o double si todavía quiero un primitivo. El rendimiento suele ser muy cercano y evita crear muchas variaciones. Por cierto: los registros en una JVM de 64 bits serán de todos modos de 64 bits.


Tu pregunta es bastante elaborada ya que pareces saber todas las "buenas" respuestas. Debido a que debido al diseño del lenguaje no se nos permite usar primitivos como tipos de parámetros genéricos, la mejor respuesta práctica es hacia dónde se dirige @PeterLawrey.

public class PrimitiveGenerics { public static double genericMax( double a, double b) { return (a > b) ?a:b; } public int max( int a, int b) { return (int) genericMax(a, b); } public long max( long a, long b) { return (long) genericMax(a, b); } public float max( float a, float b) { return (float) genericMax(a, b); } public double max( double a, double b) { return (double) genericMax(a, b); } }

La lista de tipos primitivos es pequeña y, con suerte, constante en la evolución futura del lenguaje, y el tipo doble es el más amplio / más general.

En el peor de los casos, calcula usando variables de 64 bits donde 32 bits serían suficientes. Hay una penalización de rendimiento para la conversión (pequeña) y para pasar por valor en un método más (pequeño), pero no se crean Objetos ya que esta es la penalización principal (y realmente enorme) por usar envoltorios primitivos.

También utilicé un método estático , por lo que se enlazó temprano y no en tiempo de ejecución, aunque solo es uno y es algo que la optimización de JVM generalmente cuida, pero de todos modos no le hará daño. Puede depender del escenario de un caso real.

Sería encantador si alguien lo probara, pero creo que esta es la mejor solución.

ACTUALIZACIÓN: Basado en el comentario de @ thkala, el doble solo puede representar long-s hasta cierta magnitud a medida que pierde precisión (se vuelve impreciso cuando se trata de long-s) después de eso:

public class Asdf2 { public static void main(String[] args) { System.out.println(Double.MAX_VALUE); //1.7976931348623157E308 System.out.println( Long.MAX_VALUE); //9223372036854775807 System.out.println((double) Long.MAX_VALUE); //9.223372036854776E18 } }