原始types引起的代码重复:如何避免精神错乱?

在我的一个Java项目中,由于Java处理(不)基元的方式,我被代码重复所困扰。 再次手动将相同的更改复制到四个不同的位置( intlongfloatdouble之后 ,我第三一次 又一次地逼近(?)来捕捉。

以各种forms,现在这个问题已经在StackOverflow上提出来了:

  • 在Java中pipe理高度重复的代码和文档
  • 处理原始types时如何避免重复?
  • 将原始dynamic列表传递给Java方法

共识似乎汇聚到了两种可能的select:

  • 使用某种代码生成器。
  • 你能做什么? 这就是生活!

那么第二个解决scheme就是我现在正在做的事情,它正在慢慢地变得对我的理智危险,就像众所周知的酷刑技术一样 。

自从这些问题被问及Java 7出现以来,已经过去了两年。 因此,我希望有一个更简单和/或更标准的解决scheme。

  • Java 7是否有可能缓解这种情况下的压力 ? 在浓缩的变更摘要中我找不到任何内容,但是也许在某处有一些晦涩的新function?

  • 虽然源代码生成是一种替代scheme,但我更喜欢使用标准JDKfunction集支持的解决scheme。 当然,使用cpp或其他代码生成器可以工作,但它增加了更多的依赖关系,并需要对生成系统进行更改。

    似乎只有JDK支持的唯一的sorting代码生成系统是通过注释机制。 我设想一个处理器可以像这样扩展源代码:

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

    理想的输出文件将包含这个方法的四个要求的变化,最好与相关的Javadoc注释等。有没有一个注释处理器来处理这种情况? 如果不是,那么build设一个怎么办?

  • 也许最近出现的其他一些诡计呢?

编辑:

一个重要的注意事项:我不会使用原始types,除非我有一个原因。 即使现在,在某些应用程序中,使用盒装types也会带来非常实际的性能和内存影响。

编辑2:

使用max()作为示例允许使用在所有数字装箱types中可用的compareTo()方法。 这有点棘手:

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

怎么会有人支持这种方法的所有数字盒型没有实际写六七次?

如果我仍然想要一个原始的话,我倾向于使用“超types”,如longdouble 。 性能通常非常接近,避免了大量的变化。 顺便说一句:64位JVM中的寄存器无论如何都是64位的。

你为什么挂在原始人身上? 包装是非常轻量级和自动装箱和generics做其余的:

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

这一切都编译和运行正确:

 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); } 

编辑

OP询问了sum()一个通用的自动装箱解决scheme,并且将会在这里。

 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]); } 

这有点蹩脚,但不像执行大量的instanceof和委托给完全types的方法那样蹩脚。 instanceof是必需的,因为虽然所有的Numbers都有一个String构造函数,但除了FloatDouble之外的其他Numbers只能parsing一个整数(无小数点)。 虽然总数是一个整数,但是我们必须Double.toString()中的小数点移除到这些其他types的构造函数中。

Java 7是否有可能缓解这种情况下的压力?

没有。

是否有注释处理器来处理这种情况?

不是我所知道的。

如果不是,那么build设一个怎么办?

时间,或金钱。 🙂

在我看来,这似乎是一个问题空间,在这个空间里很难提出一个很好的解决scheme。 传统的源代码生成或(文本)预处理器似乎对我更有希望。 (但我不是注释处理器专家。)

如果Java非常冗长,请查看一些运行在JVM上的新的高级语言,并且可以与Java进行互操作,例如Clojure,JRuby,Scala等。 你失控的原始重复将成为一个没有问题的问题。 但是好处还远不止于此 – 上面提到的语言有很多种方法可以让你用不太详细的,重复的,容易出错的代码(与Java相比)做更多的事情。

如果性能出现问题,则可以将性能关键位(使用基元types)放回到Java中。 但是您可能会惊讶于在高级语言中您仍然能够获得高水平的性能。

我个人使用JRuby和Clojure; 如果您来自Java / C / C#/ C ++背景,那么您都有可能改变您对编程的看法。

嘿。 为什么不偷偷摸摸? 通过reflection,您可以为方法(类似于您发布的示例的注释)提取注释。 然后,您可以使用reflection来获取成员名称,并将其放入适当的types中…在system.out.println语句中。

你会运行一次,或者每次你改变class级。 输出可以复制粘贴。这可能会为您节省大量的时间,而不是太难开发。

嗯,至于方法的内容…我的意思是,如果你所有的方法是微不足道的,你可以硬编码风格(即如果methodName.equals(“最大”)打印返回a> b:a:b等where methodName是通过reflection来确定的),或者你可以,ummmmm …嗯。 我想象的内容可以很容易地复制粘贴,但这似乎更多的工作。

哦! 不要做另一个名为“contents”的注释,给它一个方法内容的string值,把它添加到成员,现在你也可以打印出内容。

花费在编程这个帮手上的时间,即使只是做了那些单调乏味的工作,好吧,这会更有意思吗?

你的问题相当复杂,因为你已经知道所有'好'的答案。 由于语言devise,我们不允许使用原始types作为generics参数types,所以最好的实际答案是@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); } } 

原始types的列表很小,希望在将来的语言演变中保持不变, 双重types是最广泛 /最一般的。

在最坏的情况下,使用32位就足够了的64位variables进行计算。 对于转换(微小)和将值传递给一个更多的方法(小),会有一个性能损失,但是不会创build对象,因为这是使用原始包装器的主要(也是非常巨大的)损失。

我也使用了静态方法,所以它绑定的早,而不是在运行时,虽然它只是一个,这是JVM优化通常照顾,但它不会伤害无论如何。 可能取决于实际情况。

如果有人testing,会很可爱,但我相信这是最好的解决scheme。

更新:基于@ thkala的评论,double可能只代表long-s,直到某个数量级,因为它丢失了精度(在处理long-s时变得不精确):

 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 } } 

从性能angular度来看(我也制作了很多CPU绑定algorithm),我使用自己的盒子,这些盒子不是不可变的。 这允许使用像ArrayListHashMap这样的集合中的可变数字来处理高性能。

需要花费一个很长的准备步骤来制作所有具有重复代码的原始容器,然后才可以使用它们。 因为我也处理二维,三维等价值观,我也为自己创造了。 这是你的select。

喜欢:
Vector1i – 1整数replaceInteger
Vector2i – 2整数,代替PointDimension
Vector2d – 2个双打,replacePoint2D.Double
Vector4i – 4个整数,可以代替Rectangle
Vector2f – 二维浮点vector
Vector3f – 三维浮点vector
…等等…
所有这些都代表了math中的一个广义的“向量”,因此也就是所有这些原始的名称。

一个缺点是你不能做a+b ,你已经使得像a.add(b)这样的方法,对于a=a+b我select了像a.addSelf(b)这样的方法。 如果这让你感到困扰,请看看我最近发现的锡兰 。 它是Java(JVM / Eclispe compatbile)之上的一个层,特别是为了解决它的局限性(如运算符重载)而创build的。

还有一件事,当把这些类作为Map一个时要小心,因为当值发生变化时,sorting/散列/比较将会失去作用。

我同意以前的答案/评论,说使用标准的JDKfunction集没有一种方法可以完全实现你想要的function。 因此,你将不得不做一些代码生成,虽然它不一定需要修改构build系统。 既然你问:

…如果不是的话,要build造一个怎么样?

…对于一个简单的例子,我想不是太多。 假设我把我的原始操作放在一个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 } 

然后,我可以写一个简单的处理器,less于30行,如下所示:

 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(); } } } 

这将填充@GeneratedPrimitiveMethods部分,在@PrimitiveMethods部分中使用long,float和double版本的方法。

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

这是一个有意的简单的例子,我相信它并不能涵盖所有的情况,但是你明白了这一点,并且可以看到如何扩展它,例如search多个文件,或者使用正常的注释和检测方法结束。

而且,虽然你可以将它设置为构build系统中的一个步骤,但是我将它设置为在我的eclipse项目中的Java构build器之前作为构build器运行。 现在每当我编辑文件并保存; 它会在不到四分之一秒的时间内自动更新。 因此,这比编译系统中的一个步骤更成为编辑工具。

只是一个想法…