上一篇說了利用JXL的jar包來讀取Excel的代碼。在Java中,還可以用另外一種jar包來讀取Excel的內容,那就是Apache的POI。
這里和之前一樣,需要導入POI的jar包,建議導入這三個:poi-4.0.0.jar,poi-ooxml-4.0.0.jar,poi-ooxml-schemas-4.0.0.jar,
下載地址:https://mvnrepository.com/search?q=POI
我們先從最小的概念開始,讀取一個Cell,即Excel中一個“格子”的內容。
private static String getValue(Cell cell) { if (null == cell) { return ""; } else if (cell.getCellTypeEnum() == CellType.BOOLEAN) { // 返回布爾類型的值 return String.valueOf(cell.getBooleanCellValue()); } else if (cell.getCellTypeEnum() == CellType.NUMERIC) { // 返回數值類型的值 return String.valueOf(cell.getNumericCellValue()); } else { // 返回字符串類型的值 return String.valueOf(cell.getStringCellValue()); } }
這里會根據每個格子里的數據類型不同,來獲取不同的值。(這里設置了三種,布爾型,數字型,字符串型)
然后,根據我們來把整個行的內容存入一個List中。
private static List<Object> getRow(Row xssfRow) { List<Object> cells = new ArrayList<Object>(); if (xssfRow != null) { for (short cellNum = 0; cellNum < xssfRow.getLastCellNum(); cellNum++) { Cell xssfCell = xssfRow.getCell(cellNum); cells.add(getValue(xssfCell)); } } return cells; }
這里的是從行的第1列開始讀,因此,我們在設計Excel表格的時候,需要注意一下。
不過,在這里我想說的是,我們除了需要讀取Excel的內容外,我們還希望“按需讀取”。什么意思呢?就是說,我們之前是按照Excel的固有格式或者數據結構來讀取內容的,比如我去指定開始/結束行,開始/結束列。
這樣的話,我就必須要知道我要讀取的范圍是什么。但是,一般來說,我們使用Excel的習慣不是這樣的。我們習慣於把某列或某行的數據提取或者過濾出來。舉個簡單的例子來說:假如一個Excel中有A,B,C三列,我們只想要A,C列的數據而忽略B列,這樣的話如果用之前的方法,就會很不方便。另外,如果我們需要一次讀取N個Excel文件中的A列和C列,也需要對代碼進行重新審視。
為了能夠“按需讀取”,我們首先需要設計一下這個“需”。在這里,我們引入一個概念,就是構造器(當然,這個也算是一種簡單的javaBean),下面就逐步來分析,怎么實現這些功能。
按照之前我們對頁面元素的定義,我們在這里對Excel里面的內容也進行以下定義,即假如我使用Excel存儲頁面元素的內容,我應該是以什么樣的格式去寫。一般來說我想以以下的方式:
在這里,pageName是頁面名稱,positionName就是我們給想點擊的頁面元素起的名字,type是尋找方式,sec是等待時間,path是尋找元素的具體路徑的值。
在這個Excel中,第一行的列名為我們定義的頁面元素屬性名稱,從第二行的內容開始,我們需要填寫每個頁面元素實際的屬性值。
那么在這里我們先做一個記錄頁面元素屬性值的構造器,或者叫Bean
package webui.bean; public class positionBean { //此處定義的是Excel里面列的名字,必須要一模一樣,才能正常讀取相應的數據! String pageName; String positionName; String path; int sec; String type; public String getPageName() { return pageName; } public void setPageName(String pageName) { this.pageName = pageName; } public String getPositionName() { return positionName; } public void setPositionName(String positionName) { this.positionName = positionName; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public int getSec() { return sec; } public void setSec(int sec) { this.sec = sec; } public String getType() { return type; } public void setType(String type) { this.type = type; } }
我們根據頁面元素記錄的屬性,編寫了這個構造器后,我們怎么讓Excel按照這個構造器的內容來讀取數據呢?我們這里需要用到Java中類反射的概念。先看下面一段代碼:
private static Map<String, Method> getSetMethod(Class<?> clz,List<Object> heads) { Map<String, Method> map = new HashMap<String, Method>(); Method[] methods = clz.getMethods(); for (Object head : heads) { for (Method method : methods) { if (method.getName().toLowerCase().equals("set" + head.toString().toLowerCase()) && method.getParameterTypes().length == 1) { map.put(head.toString(), method); break; } } } return map; }
這段代碼稍微有些抽象,我們需要根據兩個參數(泛型Class<?>來指代我們剛才書寫的構造器,List<Object> heads來對應寫在Excel里面第一行的列名),我這里用實例來說明一下。
Excel上有頁面元素屬性的幾列數據(參考之前的Excel圖片),構造器里是通過方法返回來取得的實際數據的。如果,我們把相應的列名和相應的方法對應起來,這樣就可以把數據對應起來了。例如:pageName對應setPageName(String pageName)這個方法。
Method[] methods = clz.getMethods(); //這一句,實際上是獲取這個類的所有公共方法。
if (method.getName().toLowerCase().equals("set" + head.toString().toLowerCase())&& method.getParameterTypes().length == 1)
//這個判定實際上也是一個過濾,也就是尋找來自於這個類當中,由編寫構造器時,生成的setter方法。(如果方法的名字與set + head的小寫字母相同,且方法的參數類型長度為1,即只有1個參數)
{map.put(head.toString(), method);}
//將頭名與方法對應放入HashMap中。------>可以理解為(pageName對應setPageName方法)
這樣一來,我們就用一個HashMap把這個關系給對應起來了。
我們在取得這個對應關系之后,我們需要用Java的反射機制,來調用具體的方法來設置pageName的值。
首先,由於Method的invoke方法,參數必須是一個底層的Object,所以,我們設計我們這個設置值的方法必須有這幾個參數:
Object obj------>其實這個可以是positionBean這個類一個實例,List<Object> data --------> 這個是讀取的Excel的具體數據集合,List<Object> heads -------->這個List是讀取列名的集合,Map<String,Method> methods ----->這個就是我們之前獲取的列名與方法的對應關系集合。
來看下面一段代碼
private static void setValue(Object obj, List<Object> data,List<Object> heads, Map<String, Method> methods)throws IllegalArgumentException, IllegalAccessException,InvocationTargetException {
//在獲取了對應關系的HashMap之后,我們要對這個Map進行遍歷。 for (Map.Entry<String, Method> entry : methods.entrySet()) { Object value = ""; int dataIndex = heads.indexOf(entry.getKey());
//按照當前列的序號小於數據List的長度(例如數據List的長度為5,當前為0~4的情況) if (dataIndex < data.size()) {
//使用一個Object來取得當前項的數據。 value = data.get(heads.indexOf(entry.getKey())); }
//取得HashMap里對應的方法 Method method = entry.getValue();
//取得方法里的第一個參數的類型 Class<?> param = method.getParameterTypes()[0];
//如果參數類型為String if (String.class.equals(param)) {
//方法反射,將具體的值賦給列名所代表的obj。 method.invoke(obj, value);
//如果參數類型為整數 } else if (Integer.class.equals(param) || int.class.equals(param)) {
//加入判斷是否為空字符,因為很多時候Excel里空着就是0的意思 if(value.toString()==""){ value=0; }
//方法反射 method.invoke(obj, new BigDecimal(value.toString()).intValue()); } else if (Long.class.equals(param) || long.class.equals(param)) { if(value.toString()==""){ value=0; } method.invoke(obj, new BigDecimal(value.toString()).longValue()); } else if (Short.class.equals(param) || short.class.equals(param)) { if(value.toString()==""){ value=0; } method.invoke(obj, new BigDecimal(value.toString()).shortValue()); } else { // Date method.invoke(obj, value); } } }
關於反射,可能稍微比較難於理解。我再貼一段代碼,大家可以是否容易理解。
public class MethodDemo { public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { Method[] methods = SampleClass.class.getMethods(); SampleClass sampleObject = new SampleClass(); methods[1].invoke(sampleObject, "data"); System.out.println(methods[0].invoke(sampleObject)); } } class SampleClass { private String sampleField; public String getSampleField() { return sampleField; } public void setSampleField(String sampleField) { this.sampleField = sampleField; } }
以上運行的結果為:
data
寫了這么多,好像離我們的所想要的功能越來越近了。我們把Excel的列名和構造類中的方法關聯,然后讀取數據來給他們賦值。下一章我們就具體來“按需讀取”Excel吧。