【小ネタ】リフレクションを使って Apache Commons CSV で BeanToCSV 的なことをする

### やりたいこと
JavaでCSVファイル出力を実装しようと思った際に、いくつかのライブラリが選択肢にあがるかと思います。

たとえば以下のようにフィールド名に対応してヘッダー・値をシンプルに出力したいとします。

```UserDto.java
public class UserDto {
	
	private Integer userId;

	private String userName;

	private Integer age;

	private String gender;
}
```

```Sample.java
UserDto dto1 = new UserDto(1, "田中", 15, "女性");
UserDto dto2 = new UserDto(2, "鈴木", 21,"男性");
UserDto dto3 = new UserDto(3, "吉田", 18,"女性");
UserDto dto4 = new UserDto(4, "佐藤", 19,"女性");
UserDto dto5 = new UserDto(5, "山田", 28,"男性");
```

```user.csv
userId,userName,age,gender
1,田中,15,女性
2,鈴木,21,男性
3,吉田,18,女性
4,佐藤,19,女性
5,山田,28,男性
```

OpenCSVでは「BaenToCSV」の機能を使うことで簡単に実装ができるようです。

---
### Apache Commons CSVで実現するには
一方Apache Commons CSVには同様の機能が備わっていないため、リフレクション等で実装する必要があります。
以下は実装例です。

```Sample.java
public void writeCsv(List<?> objects, String filePath) throws IOException {
	if (objects == null || objects.isEmpty()) {
		return;
	}
	Class<?> clazz = objects.get(0).getClass();
	Field[] fields = clazz.getDeclaredFields();

	try (FileWriter writer = new FileWriter(filePath);
			CSVPrinter printer = new CSVPrinter(writer, CSVFormat.DEFAULT)) {

		// ヘッダーの書き込み
		List<String> headers = Arrays.stream(fields)
				.map(Field::getName)
				.collect(Collectors.toList());
		printer.printRecord(headers);

		// データの書き込み
		for (Object obj : objects) {
			List<Object> values = new ArrayList<>();
			for (Field field : fields) {
				field.setAccessible(true);
				values.add(field.get(obj));
			}
			printer.printRecord(values);
		}
	} catch (IllegalAccessException e) {
		e.printStackTrace();
	}
}
 ```

リフレクションのfield.getName()によって、フィールド名を取得してヘッダーに使用しています。
 ```sample.java
 // ヘッダーの書き込み
List<String> headers = Arrays.stream(fields)
		.map(Field::getName)
		.collect(Collectors.toList());
printer.printRecord(headers);
 ```

データ自体は、field.get()を使用してフィールド値を取得します。
```sample.java
// データの書き込み
for (Object obj : objects) {
	List<Object> values = new ArrayList<>();
	for (Field field : fields) {
		field.setAccessible(true);
		values.add(field.get(obj));
	}
	printer.printRecord(values);
}
```

出力したいオブジェクトのリストと出力先ファイルパスを渡すことで、CSVファイルを出力することができます。

```sample.java
List<UserDto> userDtoList = new ArrayList<>();

// userId・userName・age・genderのフィールドを持つDto
UserDto dto1 = new UserDto(1, "田中", 15, "女性");
UserDto dto2 = new UserDto(2, "鈴木", 21,"男性");
UserDto dto3 = new UserDto(3, "吉田", 18,"女性");
UserDto dto4 = new UserDto(4, "佐藤", 19,"女性");
UserDto dto5 = new UserDto(5, "山田", 28,"男性");

userDtoList.add(dto1);
userDtoList.add(dto2);
userDtoList.add(dto3);
userDtoList.add(dto4);
userDtoList.add(dto5);
		
String filePath = "C:\\temp\\user.csv";
writeCsv(userDtoList,filePath);

List<CompanyDto> compayDtoList = new ArrayList<>();
// id・nameのフィールドを持つDto
CompanyDto companyDto1 = new CompanyDto(1,"株式会社テスト");
CompanyDto companyDto2 = new CompanyDto(2,"テスト株式会社");
CompanyDto companyDto3 = new CompanyDto(3,"有限会社テスト");
		
compayDtoList.add(companyDto1);
compayDtoList.add(companyDto2);
compayDtoList.add(companyDto3);
		
String filePath2 = "C:\\temp\\compay.csv";
writeCsv(compayDtoList,filePath2);
```

出力結果は以下のようになります。
```user.csv
userId,userName,age,gender
1,田中,15,女性
2,鈴木,21,男性
3,吉田,18,女性
4,佐藤,19,女性
5,山田,28,男性
```
```company.csv
id,name
1,株式会社テスト
2,テスト株式会社
3,有限会社テスト
```

上記のケースであればOpenCSVを使ったほうが楽に実装できる気がしますが、リフレクションを使うことで同様の機能を実現することができました。
詳細なフォーマットの指定などでApache Commons CSVを使いたい場合に、参考になれば幸いです。