概要
Javaで同名フィールドのコピーとパフォーマンスの計測
内容
Javaで同名フィールドのコピーを行う手法とそのパフォーマンス検証について。
ある2つのデータクラスがあり、同名のフィールドは同じ型であるとします。
この2つのデータクラスの同名フィールドのみコピーしたい場合に
・この処理を汎用化し、1回コーディングすれば以降はその機能を呼び出せばいいようにした場合
・ベタ書きで似たようなケースが登場するたびにデータのsetter、getterのコードを書く場合
の2つのパフォーマンスを比較します。
処理で共通利用するクラス
データクラスの内部で利用するEnum
package gr.java_conf.tb.java_sample_code.copyutil_blog; public enum CountEnum { ONE("1"),TWO("2"),THREE("3"); private String type; private CountEnum(final String type) { this.type = type; } public String toValue() { return this.type; } public CountEnum fromValue(String value) throws Exception { if (value.equals("1")) { return ONE; } else if (value.equals("2")) { return TWO; } else if (value.equals("3")) { return THREE; } else { throw new Exception(); } } }
データクラスの内部で使用するデータ型
package gr.java_conf.tb.java_sample_code.copyutil_blog; import java.math.BigDecimal; public class Line { private String str1 = null; private String str2 = null; private BigDecimal bigDecimal1 = null; private BigDecimal bigDecimal2 = null; private CountEnum hogeCls1 = null; private CountEnum hogeCls2 = null; public CountEnum getHogeCls1() { return hogeCls1; } public void setHogeCls1(CountEnum hogeCls1) { this.hogeCls1 = hogeCls1; } public CountEnum getHogeCls2() { return hogeCls2; } public void setHogeCls2(CountEnum hogeCls2) { this.hogeCls2 = hogeCls2; } public String getStr1() { return str1; } public void setStr1(String str1) { this.str1 = str1; } public String getStr2() { return str2; } public void setStr2(String str2) { this.str2 = str2; } public BigDecimal getBigDecimal1() { return bigDecimal1; } public void setBigDecimal1(BigDecimal bigDecimal1) { this.bigDecimal1 = bigDecimal1; } public BigDecimal getBigDecimal2() { return bigDecimal2; } public void setBigDecimal2(BigDecimal bigDecimal2) { this.bigDecimal2 = bigDecimal2; } }
コピー元データクラス
package gr.java_conf.tb.java_sample_code.copyutil_blog; import java.math.BigDecimal; import java.util.List; public class From { private String str1 = null; private String str2 = null; private BigDecimal bigDecimal1 = null; private BigDecimal bigDecimal2 = null; private CountEnum hogeCls1 = null; private CountEnum hogeCls2 = null; private List<Line> lineList = null; public CountEnum getHogeCls1() { return hogeCls1; } public void setHogeCls1(CountEnum hogeCls1) { this.hogeCls1 = hogeCls1; } public CountEnum getHogeCls2() { return hogeCls2; } public void setHogeCls2(CountEnum hogeCls2) { this.hogeCls2 = hogeCls2; } public String getStr1() { return str1; } public void setStr1(String str1) { this.str1 = str1; } public String getStr2() { return str2; } public void setStr2(String str2) { this.str2 = str2; } public BigDecimal getBigDecimal1() { return bigDecimal1; } public void setBigDecimal1(BigDecimal bigDecimal1) { this.bigDecimal1 = bigDecimal1; } public BigDecimal getBigDecimal2() { return bigDecimal2; } public void setBigDecimal2(BigDecimal bigDecimal2) { this.bigDecimal2 = bigDecimal2; } public List<Line> getLineList() { return lineList; } public void setLineList(List<Line> lineList) { this.lineList = lineList; } }
コピー先データクラス
package gr.java_conf.tb.java_sample_code.copyutil_blog; import java.math.BigDecimal; import java.util.List; public class To { private String str1 = null; private String str3 = null; private BigDecimal bigDecimal1 = null; private BigDecimal bigDecimal3 = null; private CountEnum hogeCls1 = null; private CountEnum hogeCls3 = null; private List<Line> lineList = null; public String getStr1() { return str1; } public void setStr1(String str1) { this.str1 = str1; } public String getStr3() { return str3; } public void setStr3(String str3) { this.str3 = str3; } public BigDecimal getBigDecimal1() { return bigDecimal1; } public void setBigDecimal1(BigDecimal bigDecimal1) { this.bigDecimal1 = bigDecimal1; } public BigDecimal getBigDecimal3() { return bigDecimal3; } public void setBigDecimal3(BigDecimal bigDecimal3) { this.bigDecimal3 = bigDecimal3; } public CountEnum getHogeCls1() { return hogeCls1; } public void setHogeCls1(CountEnum hogeCls1) { this.hogeCls1 = hogeCls1; } public CountEnum getHogeCls3() { return hogeCls3; } public void setHogeCls3(CountEnum hogeCls3) { this.hogeCls3 = hogeCls3; } public List<Line> getLineList() { return lineList; } public void setLineList(List<Line> lineList) { this.lineList = lineList; } public String toString() { String out = ""; out += "str1:" + getStr1() + "\n"; out += "str3:" + getStr3() + "\n";; out += "bigDecimal1:" + getBigDecimal1() + "\n";; out += "bigDecimal3:" + getBigDecimal3() + "\n";; out += "HogeCls1:" + getHogeCls1() + "\n";; out += "HogeCls3:" + getHogeCls3() + "\n";; out += "LineList:" + getLineList() + "\n";; return out; } }
汎用版データコピー機能
package gr.java_conf.tb.java_sample_code.copyutil_blog; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; /** * フィールドコピーユーティリティー。 * * @author tbpgr * */ public class FieldCopyUtility { /** * copyFromに持っているフィールドの内容をcopyToにコピーする。 * * <pre> * 前提条件:copyFromとcopyToで同名かつ同じデータ型をコピー * </pre> * * @param copyFrom * @param copyTo * @return copyFromから値をコピーしたcopyTo */ public static Object copyFields(Object copyFrom, Object copyTo) { try { PropertyDescriptor[] propertiesFrom = Introspector.getBeanInfo(copyFrom.getClass()).getPropertyDescriptors(); PropertyDescriptor[] propertiesTo = Introspector.getBeanInfo(copyTo.getClass()).getPropertyDescriptors(); for (PropertyDescriptor propertyFrom : propertiesFrom) { for (PropertyDescriptor propertyTo : propertiesTo) { if (hasSameName(propertyFrom, propertyTo)) { if (canWriteField(propertyTo)) { copyField(copyFrom, copyTo, propertyFrom, propertyTo); } break; } } } } catch (IntrospectionException e) { // TODO 自動生成された catch ブロック e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO 自動生成された catch ブロック e.printStackTrace(); } catch (IllegalAccessException e) { // TODO 自動生成された catch ブロック e.printStackTrace(); } catch (InvocationTargetException e) { // TODO 自動生成された catch ブロック e.printStackTrace(); } return copyTo; } /** * Fieldのコピーを行う。 * * @param copyFrom * コピー元 * @param copyTo * コピー先 * @param propertyFrom * コピー元PropertyDescriptor * @param propertyTo * コピー先PropertyDescriptor * @throws IllegalAccessException * @throws InvocationTargetException */ private static void copyField(Object copyFrom, Object copyTo, PropertyDescriptor propertyFrom, PropertyDescriptor propertyTo) throws IllegalAccessException, InvocationTargetException { Object copyFromValue = propertyFrom.getReadMethod().invoke(copyFrom); propertyTo.getWriteMethod().invoke(copyTo, copyFromValue); } /** * 同名のフィールドを持っているか判定する。 * * @param propertyFrom * 判定元プロパティ * @param propertyTo * 判定先プロパティ * @return 同名であれば、true。同名でなければfalse */ private static boolean hasSameName(PropertyDescriptor propertyFrom, PropertyDescriptor propertyTo) { return propertyFrom.getName().equals(propertyTo.getName()); } /** * 書き込み可能か判定する。 * * @param propertyTo * @return 書き込み可能ならTrue,不可能ならfalse */ private static boolean canWriteField(PropertyDescriptor propertyTo) { return propertyTo.getWriteMethod() != null; } }
ベタ書き版データコピー機能
package gr.java_conf.tb.java_sample_code.copyutil_blog; import java.util.ArrayList; import java.util.List; /** * フィールドコピーユーティリティー。 * * @author tbpgr * */ public class FieldCopyBetaCoding { /** * ObjectFromに持っているフィールドの内容をObjectToにコピーする。 * * <pre> * 前提条件:ObjectFromとObjectToで同名かつ同じデータ型をコピー * </pre> * * @param copyFrom * @param copyTo * @return copyFromから値をコピーしたcopyTo */ public static To copyFields(From copyFrom, To copyTo) { copyTo.setBigDecimal1(copyFrom.getBigDecimal1()); copyTo.setBigDecimal3(null); copyTo.setHogeCls1(copyFrom.getHogeCls1()); copyTo.setHogeCls3(null); copyTo.setStr1(copyFrom.getStr1()); copyTo.setStr3(null); List<Line> lineList = new ArrayList<Line>(); for (int i = 0; i < copyFrom.getLineList().size(); i++) { Line dataLineFrom = copyFrom.getLineList().get(i); Line dataLineTo = new Line(); dataLineTo.setBigDecimal1(dataLineFrom.getBigDecimal1()); dataLineTo.setBigDecimal2(dataLineFrom.getBigDecimal2()); dataLineTo.setHogeCls1(dataLineFrom.getHogeCls1()); dataLineTo.setHogeCls2(dataLineFrom.getHogeCls2()); dataLineTo.setStr1(dataLineFrom.getStr1()); dataLineTo.setStr2(dataLineFrom.getStr2()); lineList.add(dataLineTo); } copyTo.setLineList(lineList); return copyTo; } }
パフォーマンス計測コード
package gr.java_conf.tb.java_sample_code.copyutil_blog; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import org.junit.Test; public class FieldCopyUtilityTest { private static final int MAX = 10; private static final int TEST_COUNT = 5; @Test public void testCopyFields_UseBeanInfo() { // Mockデータの取得 From dataFrom = getDataFromMock(); for (int count = 0; count < TEST_COUNT; count++) { long startTime = System.currentTimeMillis(); for (int i = 0; i < MAX; i++) { To dataTo = new To(); dataTo = (To) FieldCopyUtility.copyFields(dataFrom, dataTo); } long endTime = System.currentTimeMillis(); printResultTime("一回書けばいいDRYな共通処理", count, startTime, endTime); } } @Test public void testCopyFields_BetaCoding() { // Mockデータの取得 From dataFrom = getDataFromMock(); for (int count = 0; count < TEST_COUNT; count++) { long startTime = System.currentTimeMillis(); for (int i = 0; i < MAX; i++) { To dataTo = new To(); dataTo = (To) FieldCopyBetaCoding.copyFields(dataFrom, dataTo); } long endTime = System.currentTimeMillis(); printResultTime("気合でベタ書き", count, startTime, endTime); } } private void printResultTime(String title, int count, long startTime, long endTime) { System.out.println(title + "版" + (count + 1) + "回目:" + (endTime - startTime) + "ミリ秒"); } @SuppressWarnings("unused") private void checkAfterCopyData(To dataTo) { System.out.println("--------------処理結果--------------"); System.out.println(dataTo); System.out.println(dataTo.getLineList().get(0).getStr1()); System.out.println(dataTo.getLineList().get(0).getStr2()); System.out.println(dataTo.getLineList().get(0).getBigDecimal1()); System.out.println(dataTo.getLineList().get(0).getBigDecimal2()); System.out.println(dataTo.getLineList().get(0).getHogeCls1()); System.out.println(dataTo.getLineList().get(0).getHogeCls2()); System.out.println(dataTo.getLineList().get(1).getStr1()); System.out.println(dataTo.getLineList().get(1).getStr2()); System.out.println(dataTo.getLineList().get(1).getBigDecimal1()); System.out.println(dataTo.getLineList().get(1).getBigDecimal2()); System.out.println(dataTo.getLineList().get(1).getHogeCls1()); System.out.println(dataTo.getLineList().get(1).getHogeCls2()); } private From getDataFromMock() { From dataFrom = new From(); dataFrom.setStr1("str1"); dataFrom.setStr2("str2"); dataFrom.setBigDecimal1(new BigDecimal(1)); dataFrom.setBigDecimal2(new BigDecimal(2)); dataFrom.setHogeCls1(CountEnum.ONE); dataFrom.setHogeCls2(CountEnum.TWO); List<Line> dataLineFrom = new ArrayList<Line>(); dataLineFrom = appendDataLine(dataLineFrom, "str1_1", "str2_1", new BigDecimal(1), new BigDecimal(2), CountEnum.ONE, CountEnum.TWO); dataLineFrom = appendDataLine(dataLineFrom, "str1_2", "str2_2", new BigDecimal(11), new BigDecimal(12), CountEnum.TWO, CountEnum.THREE); dataFrom.setLineList(dataLineFrom); return dataFrom; } private List<Line> appendDataLine(List<Line> dataLineFrom, String string1, String string2, BigDecimal bigDecimal1, BigDecimal bigDecimal2, CountEnum hogeEnum1, CountEnum hogeEnum2) { Line line1 = new Line(); line1.setStr1(string1); line1.setStr2(string2); line1.setBigDecimal1(bigDecimal1); line1.setBigDecimal2(bigDecimal2); line1.setHogeCls1(hogeEnum1); line1.setHogeCls2(hogeEnum2); dataLineFrom.add(line1); return dataLineFrom; } }
計測結果
■100万回ループ
一回書けばいいDRYな共通処理版1回目:1312ミリ秒
一回書けばいいDRYな共通処理版2回目:1291ミリ秒
一回書けばいいDRYな共通処理版3回目:1292ミリ秒
一回書けばいいDRYな共通処理版4回目:1292ミリ秒
一回書けばいいDRYな共通処理版5回目:1283ミリ秒
気合でベタ書き版1回目:87ミリ秒
気合でベタ書き版2回目:84ミリ秒
気合でベタ書き版3回目:83ミリ秒
気合でベタ書き版4回目:84ミリ秒
気合でベタ書き版5回目:83ミリ秒
■10万回ループ
一回書けばいいDRYな共通処理版1回目:152ミリ秒
一回書けばいいDRYな共通処理版2回目:133ミリ秒
一回書けばいいDRYな共通処理版3回目:135ミリ秒
一回書けばいいDRYな共通処理版4回目:132ミリ秒
一回書けばいいDRYな共通処理版5回目:129ミリ秒
気合でベタ書き版1回目:12ミリ秒
気合でベタ書き版2回目:8ミリ秒
気合でベタ書き版3回目:9ミリ秒
気合でベタ書き版4回目:8ミリ秒
気合でベタ書き版5回目:8ミリ秒
■1万回ループ
一回書けばいいDRYな共通処理版1回目:34ミリ秒
一回書けばいいDRYな共通処理版2回目:14ミリ秒
一回書けばいいDRYな共通処理版3回目:13ミリ秒
一回書けばいいDRYな共通処理版4回目:15ミリ秒
一回書けばいいDRYな共通処理版5回目:13ミリ秒
気合でベタ書き版1回目:4ミリ秒
気合でベタ書き版2回目:1ミリ秒
気合でベタ書き版3回目:1ミリ秒
気合でベタ書き版4回目:1ミリ秒
気合でベタ書き版5回目:1ミリ秒
総括
コピー機能を大量に呼び出せば呼び出すほど、パフォーマンス差は顕著に。
また、このサンプルの場合はフィールド数が7個ですが現実のシステム開発では1クラスに
大量のフィールドがある場合も多いため差はより顕著になるでしょう。
コピー機能をあまり呼び出さない場合はパフォーマンス差も小さくなり、
処理時間自体も1万回ループで10-30ミリ秒程度である。
汎用版を利用する基準は、
・同様の処理が多くあること
・フィールド数が大量ではないこと
・大量のループなどがない箇所
になる。