[英]Code duplication caused by primitive types: How to avoid insanity?
在我的一個Java項目中,由於Java處理(而非)原語的方式,我受到代碼重復的困擾。 在必須再次手動將相同的更改復制到四個不同的位置( int
, long
, float
, double
) 之后 , 第三次,我一次 又一次地接近(?)捕捉。
在各種形式中,這個問題已經在StackOverflow上提出了:
共識似乎趨向於兩種可能的替代方案:
好吧,第二個解決方案就是我現在正在做的事情,它對我的理智慢慢變得危險,就像眾所周知的折磨技術一樣 。
自從提出這些問題並且Java 7出現以來已過去兩年了。 因此,我希望有一個更簡單和/或更標准的解決方案。
Java 7是否有任何可能在這種情況下緩解壓力的變化? 我在簡明的變更摘要中找不到任何內容,但也許在某處有一些不起眼的新功能?
雖然源代碼生成是另一種選擇,但我更喜歡使用標准JDK功能集支持的解決方案。 當然,使用cpp
或其他代碼生成器可以工作,但它增加了更多依賴項並需要對構建系統進行更改。
似乎JDK支持的唯一代碼生成系統是通過注釋機制。 我設想一個可以像這樣擴展源代碼的處理器:
@Primitives({ "int", "long", "float", "double" }) @PrimitiveVariable int max(@PrimitiveVariable int a, @PrimitiveVariable int b) { return (a > b)?a:b; }
理想的輸出文件將包含此方法的四個請求變體,最好使用相關的Javadoc注釋等。是否有某處注釋處理器來處理這種情況? 如果沒有,構建一個會怎樣?
也許最近出現了一些其他技巧?
編輯:
一個重要的注意事項:除非我有理由,否則我不會使用原始類型。 即使是現在,在某些應用程序中使用盒裝類型也會產生非常真實的性能和內存影響。
編輯2:
使用max()
作為示例允許使用所有數字盒裝類型中可用的compareTo()
方法。 這有點棘手:
int sum(int a, int b) {
return a + b;
}
怎么可以為所有數字盒裝類型支持這種方法而不實際寫入六到七次?
如果我還想要一個原語,我傾向於使用像long
或double
這樣的“超類型”。 性能通常非常接近,避免產生大量變化。 BTW:無論如何,64位JVM中的寄存器都是64位的。
你為什么要掛在原始人身上? 包裝器非常輕巧,自動裝箱,其余的是泛型:
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()
的通用自動盒式解決方案,並且將在這里。
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
並委托給一個完全類型化的方法那樣蹩腳。 instanceof
是必需的,因為雖然所有Numbers
都有一個String
構造函數,但Float
和Double
以外的Numbers
只能解析整數(無小數點); 雖然總數將是一個整數,但我們必須從Double.toString()
刪除小數點,然后再將其發送到這些其他類型的構造函數中。
Java 7是否有任何可能在這種情況下緩解壓力的變化?
沒有。
有沒有一個注釋處理器來處理這種情況?
不是我知道的。
如果沒有,構建一個會怎樣?
時間還是金錢。 :-)
在我看來,這似乎是一個問題空間,很難找到一個運行良好的通用解決方案......超越瑣碎的案例。 傳統的源代碼生成或(文本)預處理器似乎對我更有希望。 (雖然我不是Annotation處理器專家。)
如果Java的非凡冗長,請研究一些在JVM上運行並可與Java互操作的新的更高級語言,如Clojure,JRuby,Scala等。 你失控的原始重復將成為一個無問題。 但是它的好處還遠遠不止於此 - 有很多種方法可以讓您通過不那么詳細,重復,容易出錯的代碼(與Java相比)來完成更多工作。
如果性能是一個問題,您可以回退到Java以獲得性能關鍵位(使用基本類型)。 但是,您可能會驚訝於您仍然可以在更高級別的語言中獲得良好的性能水平。
我個人同時使用JRuby和Clojure; 如果您來自Java / C / C#/ C ++背景,那么兩者都有可能改變您對編程的看法。
嘿。 為什么不偷偷摸摸? 通過反射,您可以為方法提取注釋(注釋類似於您發布的示例)。 然后,您可以使用反射來獲取成員名稱,並輸入相應的類型...在system.out.println語句中。
你可以運行一次,或者每次修改類。 然后可以將輸出復制粘貼。這可能會為您節省大量時間,並且不會太難開發。
嗯,至於方法的內容......我的意思是,如果所有的方法都很簡單,你可以硬編碼樣式(即如果methodName.equals(“max”)print返回a> b:a:b等。其中methodName是通過反射確定的,或者你可以,嗯...嗯。 我想象的內容可以很容易地復制粘貼,但這似乎更多的工作。
哦! 沒有做另一個稱為“內容”的注釋,給它一個方法內容的字符串值,將其添加到成員,現在你也可以打印出內容。
至少,花在編寫這個幫助程序上的時間,即使只要進行繁瑣的工作,也就是說,它會更有趣,riiiight?
你的問題非常詳細,因為你似乎已經知道了所有“好”的答案。 由於語言設計,我們不允許使用原語作為通用參數類型,最好的實際答案是@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);
}
}
原始類型列表很小,希望在將來的語言演變中保持不變, 雙類型是最廣泛 /最普遍的。
在最壞的情況下,您使用64位變量進行計算,其中32位就足夠了。 轉換(微小)和將值傳遞到另一個方法(小)時會有性能損失,但是沒有創建對象,因為這是使用原始包裝器的主要(並且確實很大)懲罰。
我還使用了一個靜態方法,所以它是早期綁定而不是在運行時,雖然它只是一個,這是JVM優化通常需要處理的東西,但無論如何它都不會受到傷害。 可能取決於實際情況。
如果有人測試它會很可愛,但我相信這是最好的解決方案。
更新:基於@ 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
}
}
從性能的角度來看(我也制作了很多CPU綁定算法),我使用了自己的不可變的盒子。 這允許在像ArrayList
和HashMap
這樣的集合中使用可變數字來實現高性能。
需要一個很長的准備步驟來使用重復代碼制作所有原始容器,然后您只需使用它們。 由於我還處理二維,三維等值,我也為自己創建了這些值。 這是你的選擇。
喜歡:
Vector1i
- 1個整數,替換Integer
Vector2i
- 2整數,取代了Point
和Dimension
Vector2d
- 2個雙打,取代了Point2D.Double
Vector4i
- 4個整數,可以替換Rectangle
Vector2f
- 二維浮動矢量
Vector3f
- 三維浮動矢量
...等等...
所有這些都代表了數學中的廣義“向量”,因此是所有這些基元的名稱。
一個缺點是你不能做a+b
,你有make方法如a.add(b)
,而a=a+b
我選擇命名方法如a.addSelf(b)
。 如果這讓你煩惱,那就去看看我最近發現的錫蘭 。 它是Java(JVM / Eclispe compatbile)之上的一個層,專門用於解決它的局限性(如運算符重載)。
另外一點,當使用這些類作為Map
的鍵時要小心,因為當值發生變化時,排序/散列/比較會變得混亂。
我同意以前的答案/評論,說沒有辦法按照你想要的方式“使用標准的JDK功能集”。 因此,您將不得不進行一些代碼生成,盡管它不一定需要更改構建系統。 既然你問:
......如果沒有,構建一個會怎樣?
...對於一個簡單的案例,不要太多,我想。 假設我將原始操作放在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
}
然后我可以用少於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();
}
}
}
這將使用@PrimitiveMethods部分中的方法的long,float和double版本填充@GeneratedPrimitiveMethods部分。
// @GeneratedPrimitiveMethodsStart - Do not edit below
/** Find maximum of long inputs */
public static long max(long a, long b) {
return (a > b) ? a : b;
}
...
這是一個有意的簡單示例,我確信它並不涵蓋所有情況,但您明白了它是如何擴展的,例如搜索多個文件或使用普通注釋和檢測方法結束。
此外,雖然您可以將其設置為構建系統中的一個步驟,但我將其設置為在我的eclipse項目中的Java構建器之前作為構建器運行。 現在每當我編輯文件並點擊保存; 它會在不到四分之一秒的時間內自動更新。 因此,這比編譯系統中的步驟更像是一種編輯工具。
只是一個想法...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.