本文為舊站搬過來的內容,原寫於2016-12-17

最近在工作時會研究一下前人寫的程式碼,才發現說有些開源框架提供的工具類別可以拿來用,這裡先寫寫我在專案中比較常看到的StringUtils、CollectionUtils與BeanUtils

Apache Commons

在開始之前來看看Apache Commons,它是Apache的一個專案,提供可重用的、開源的Java程式碼,分成Proper、Sandbox和Dormant三個部分,我們主要在用的是Proper底下的組件,差不多有四十種左右可以自由使用,例如:

組件 說明
BeanUtils 針對Bean的包裝和各種操作
Collections 對Java的Collection做加強
Lang 讓java.lang套件中的類別增加更多功能

StringUtils

StringUtils是Apache Commons Lang當中的類別( org.apache.commons.lang3.StringUtils),所以使用前需要先加入Apache Commons Lang的jar檔,如果用maven的話可以用加入dependency的方式,目前的最新版本是3.5,Apache Commons Lang除了StringUtils以外還有像ArrayUtils、andomStringUtils、Tokenizer、WordUtils之類的工具類別

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.5</version>
</dependency>

StringUtils類別的方法都是null safe的,也就是說就算遇到傳進來的String是Null,也不會有NullPointerException出現,我比較常用到的方法大概有幾個:

CollectionUtils

CollectionUtils是Apache Commons Collections當中的類別(org.apache.commons.collections4.CollectionUtils),使用前也是要加入jar檔,目前最新版本是4.1

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.1</version>
</dependency>

CollectionUtils我比較常用的就是isEmpty()跟isNotEmpty()這兩個

之前可能要這麼寫:

List<String> testList = null;
System.out.println(testList != null && testList.size() > 0 ); //false

使用CollectionUtils.isNotEmpty():

List<String> testList = null;
System.out.println(CollectionUtils.isNotEmpty(testList)); //false

BeanUtils

BeanUtils是Apache Commons BeanUtils當中的類別(org.apache.commons.beanutils.BeanUtils),目前Apache Commons BeanUtils的最新版本是1.9.3

<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.3</version>
</dependency>

不過我所接觸到的專案所使用的BeanUtils是Spring的(org.springframework.beans.BeanUtils),其實除了Apache、Spring以外,還有其他開源框架也有提供類似功能的類別,各具特色,但都有Bean屬性複製的方法,只是實作的方式不盡相同(例:Apache、Spring使用反射機制,cglib使用動態代理…),效能上會有所差別,可以參考Bean复制的几种框架性能比较(Apache BeanUtils、PropertyUtils,Spring BeanUtils,Cglib BeanCopier)這篇文章

假設先定義兩個JavaBean

public class EmpVO implements java.io.Serializable {
    private Integer empno;
    private String ename;
    private String job;
    private Date hiredate;
    private Double sal;
    private Double comm;
    private DeptVO deptVO;
    
    //省略getter/setter
}
public class MyEmpVO extends EmpVO implements java.io.Serializable {
    private String addr;
    private String phone;
    
    //省略getter/setter
}

另外定義一個有同名但型態不同的Java Bean

public class DifferentEmpVO implements java.io.Serializable {
    private Integer empno;
    private String ename;
    private String job;
    private String hiredate; //型態不同
    private String address;
    private String phone;
    private Integer sal; //型態不同

    //省略getter/setter
}

org.apache.commons.beanutils.BeanUtils

//將orig的屬性複製到dest中
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException
//將beanA的某個屬性值複製到beanB
public static void copyProperty(Object bean, String name, Object value) throws IllegalAccessException, InvocationTargetException

例如:

public static void main(String[] args) {
    EmpVO emp1 = new EmpVO();
    emp1.setEmpno(1001);
    emp1.setEname("Carrie");
    emp1.setHiredate(new java.sql.Date(new Date().getTime()));
    emp1.setJob("Manager");
    emp1.setSal(50000D);
    emp1.setComm(200D);
    DeptVO dept = new DeptVO();
    emp1.setDeptVO(dept);
    dept.setDeptno(10);
    dept.setDname("業務部");
    dept.setLoc("台北市");

    MyEmpVO emp2 = new MyEmpVO();
    MyEmpVO emp3 = new MyEmpVO();
    DifferentEmpVO emp4 = new DifferentEmpVO();
    try {
        BeanUtils.copyProperties(emp2, emp1);
        dept.setLoc("高雄市"); //會修改兩個Bean所參考的物件
        System.out.println(emp2);

        BeanUtils.copyProperty(emp3, "ename", emp1.getEname()); //只複製ename
        System.out.println(emp3);

        BeanUtils.copyProperties(emp4, emp1); //有同名但型態不同的屬性
        System.out.println(emp4);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
}
empno=1001,ename=Carrie,job=Manager,hiredate=2016-12-18,comm=200.0,sal=50000.0,deptno=10,dname=業務部,loc=高雄市,address=null,phone=null
empno=null,ename=Carrie,job=null,hiredate=null,comm=null,sal=null,address=null,phone=null
empno=1001,ename=Carrie,job=Manager,hiredate=2016-12-20,sal=50000,address=null,phone=null

org.apache.commons.beanutils.PropertyUtils

public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException

例如:

        MyEmpVO emp5 = new MyEmpVO();
        DifferentEmpVO emp6 = new DifferentEmpVO();
            try {
                PropertyUtils.copyProperties(emp5, emp1);
                System.out.println(emp5);

                PropertyUtils.copyProperties(emp6, emp1);   // 出現例外錯誤
                System.out.println(emp6);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
empno=1001,ename=Carrie,job=Manager,hiredate=2016-12-20,comm=200.0,sal=50000.0,deptno=10,dname=業務部,loc=台北市,address=null,phone=null

java.lang.IllegalArgumentException: Cannot invoke com.emp.model.DifferentEmpVO.setHiredate on bean class 'class com.emp.model.DifferentEmpVO' - argument type mismatch - had objects of type "java.sql.Date" but expected signature "java.lang.String"

org.springframework.beans.BeanUtils

//把source的屬性複製給target
public static void copyProperties(Object source, Object target) throws BeansException
//把source的屬性複製給target,但要忽略掉ignoreProperties所列的屬性
public static void copyProperties(Object source, Object target, String... ignoreProperties) throws BeansException

例如: 複製全部屬性

        MyEmpVO emp7 = new MyEmpVO();
        BeanUtils.copyProperties(emp1, emp7);
        System.out.println(emp7);
empno=1001,ename=Carrie,job=Manager,hiredate=2016-12-20,comm=200.0,sal=50000.0,deptno=10,dname=業務部,loc=台北市,address=null,phone=null

忽略部份屬性

        MyEmpVO emp8 = new MyEmpVO();
        String[] ignoreProperties = {"ename","empno"}; //忽略ename跟empno
        BeanUtils.copyProperties(emp1, emp8, ignoreProperties); 
        System.out.println(emp8);
empno=null,ename=null,job=Manager,hiredate=2016-12-20,comm=200.0,sal=50000.0,deptno=10,dname=業務部,loc=台北市,address=null,phone=null

遇到同名但不同型態的屬性

        MyEmpVO1 emp9 = new MyEmpVO1();
        BeanUtils.copyProperties(emp1, emp9);
        System.out.println(emp9);
 org.springframework.beans.FatalBeanException: Could not copy properties from source to target; nested exception is java.lang.IllegalArgumentException: argument type mismatch

參考資料