本节讲解如何使用自定义函数,非应用程序员如不需了解可以跳过,不影响正常阅读。
在集算器中,除了使用系统提供的各类函数,还可以调用用户自定义的函数,用来处理一些比较特殊的运算,或者封装某些计算过程。在这里,我们了解一下用invoke函数调用自定义函数的方法,并简单说明如何在自定义函数中使用参数及返回所需结果。
使用invoke函数,可以调用指定的自定义JAVA类中的静态方法。如下面的JAVA类test.Calc01:
package test;
public class Calc01 {
public static Double distance1(Number loc) {
double len = Math.abs(loc.doubleValue());
len = Math.round(len*1000)/1000d;
return Double.valueOf(len);
}
}
在这个简单的类中,静态方法distance1计算给定的一个坐标,计算与原点之间的距离,计算结果保留3位小数。在集算器中能够调用的方法,除了必须是静态static外,还必须公开public。使用时,需要把自定义函数类放置在集算器的类路径,即[安装目录]\esProc\classes路径下,如果在web端使用,相应的,应该放置在WEB-INF/classes路径下。这样,就可以在网格中,用invole函数调用自定义函数了:
|
A |
1 |
-12.34567 |
2 |
=invoke(test.Calc01.distance1,A1) |
3 |
=invoke(test.Calc01.distance1, -512) |
如例子中所示,使用invoke函数时,先指明所调用的类的全路径以及静态方法名称,然后把需要用到的参数依次列出。使用的参数既可以是网格中的格值、网格参数等,也可以直接输入。计算后,A2与A3中结果分别如下:
一个类中,可以有多个自定义函数,如再增加一个方法:
public static Double distance2(Number loc1, Number loc2) {
double len = Math.abs(loc1.doubleValue()-loc2.doubleValue());
len = Math.round(len*1000)/1000d;
return Double.valueOf(len);
}
新增的distance2用来计算数轴上两个坐标之间的距离。集算器只根据方法的名称来调用,因此不同的自定义函数需要使用不同的方法名称。只要自定义函数类放置在了应用的类路径中,无论多个自定义函数来自于多个类还是同一个类,都可以在同一个网格中调用:
|
A |
1 |
=invoke(test.Calc01.distance1,-12.3456) |
2 |
=invoke(test.Calc01.distance2,12,-12) |
调用时,使用参数的数目和类型需与对应的方法匹配。A1与A2中的计算结果分别如下:
在自定义函数中,可以返回计算结果,也可以不返回,如:
public static void distance3(Number loc1, Number loc2) {
double len = Math.abs(loc1.doubleValue()-loc2.doubleValue());
len = Math.round(len*1000)/1000d;
System.out.println(Double.toString(len));
}
静态方法distance3完成计算后,仅在后台打印出结果,而不返回任何数据。这样的函数类似于网格中执行格的情况,调用时可以用>开头,如:
|
A |
1 |
>invoke(test.Calc01.distance3,-12.3,15) |
此时,可以在控制台中看到执行时后台输出的情况,打开控制台可以在菜单栏中选择Tool>Options,并在Option窗口的General页面中选定Console takeover。结果如下:
如果自定义函数中需要使用参数或返回结果,应该使用可与集算器匹配的数据类型,否则有可能在展现或调用时出现错误。集算器中常用数据类型对应的Java类型如下:
整数 |
java.lang.Integer |
长整数 |
java.lang.Long |
浮点数 |
java.lang.Double |
长实数 |
java.math.BigInteger |
实数 |
java.lang.Number |
布尔型 |
java.lang.Boolean |
字符串 |
java.lang.String |
日期 |
java.sql.Date |
时间 |
java.sql.Time |
日期时间 |
java.sql.TimeStamp |
序列 |
com.scudata.dm.Sequence |
序表 |
com.scudata.dm.Table |
二进制数据 |
byte[] |
在前面的例子中,返回的数据都是Double对象,相当于集算器中的浮点数,而使用的参数都是Number,相当于集算器中的实数。在自定义函数中可以根据需要返回结果,例如,在test.Calc01中再增添两个自定义函数,返回不同类型的数据:
public static String distance4(Number loc1, Number loc2) {
double len = Math.abs(loc1.doubleValue()-loc2.doubleValue());
len = Math.round(len*1000)/1000d;
return Double.toString(len);
}
public static Sequence distance5(Number loc1, Number loc2) {
double len = Math.abs(loc1.doubleValue()-loc2.doubleValue());
len = Math.round(len*1000)/1000d;
com.scudata.dm.Sequence result = new com.scudata.dm.Sequence();
result.add(loc1);
result.add(loc2);
result.add(Double.valueOf(len));
return result;
}
这两个静态方法仍然用来计算数轴上两点间的距离,distance4返回字符串结果,distance5返回序列作为结果,其中存储了两点坐标与它们之间的距离。在网格中调用时,仍然使用invoke函数:
|
A |
1 |
=invoke(test.Calc01.distance2,-12.3,15) |
2 |
=invoke(test.Calc01.distance4,-12.3,15) |
3 |
=invoke(test.Calc01.distance5,-12.3,15) |
为了对比,A1中调用了前面使用过的distance2。执行后,A1~A3中的结果如下:
注意其中A1与A2中结果的数据类型不同,显示模式是有区别的。自定义函数返回的结果,可以在后面的计算中使用。
自定义函数中使用的参数,是来自于集算器的,各类数据类型所对应的Java对象和上一节中介绍的相同。在调用自定义函数时,要注意参数类型要静态方法中的参数匹配。
特别的,序列是集算器中最常使用的数据类型,在自定义函数中,除了返回序列作为结果,同样可以使用序列类型的参数。如:
public static Double distance6(com.scudata.dm.Sequence seq1, com.scudata.dm.Sequence seq2) {
int len1 = seq1.length();
int len2 = seq2.length();
double x1 = len1 > 0 ? ((Number) seq1.get(1)).doubleValue(): 0;
double x2 = len2 > 0 ? ((Number) seq2.get(1)).doubleValue(): 0;
double y1 = len1 > 1 ? ((Number) seq1.get(2)).doubleValue(): 0;
double y2 = len2 > 1 ? ((Number) seq2.get(2)).doubleValue(): 0;
double len = Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
len = Math.round(len*1000)/1000d;
return Double.valueOf(len);
}
自定义函数distance6计算平面直角坐标系中两个点之间的距离。在调用时,两个点的坐标需用序列类型的参数传入。如:
|
A |
1 |
[1,1] |
2 |
[3,3] |
3 |
=invoke(test.Calc01.distance6,A1,A2) |
计算后,A3中的结果如下:
如果需要用自定义函数返回游标,那么需要返回的类要实现接口com.scudata.dm.IlineInput。如下面的test.RandDataCursor:
package test;
public class RandDataCursor implements com.scudata.dm.ILineInput {
private int rowno = 0;
private int range = 1000;
public RandDataCursor(Integer rg) {
this.range = rg;
}
public Object[] readLine() throws java.io.IOException {
rowno++;
Object[] result = new Object[2];
result[0] = Integer.valueOf(rowno);
result[1] = Integer.valueOf((int) (Math.random() * range ));
return result;
}
public boolean skipLine() throws java.io.IOException {
rowno++;
if (rowno <= 10000) return true;
return false;
}
public void close() throws IOException {
rowno = 10001;
}
}
IlineInput类中需要实现readLine,skipLine和close这3个方法。上面的RandDataCursor比较简单,用来生成由随机整数构成的序列,其中随机数的范围可以用参数指定。readLine返回的每条记录,都由顺次增加的行号和随机数构成,类似于报表的两个字段。在skipLine方法中,可以跳过1条记录,并返回游标中是否仍有剩余数据,上面例子中设定这个游标最多返回10000条数据。close方法则用来关闭游标,释放不需要的资源,如数据库连接等。
这样,返回游标的自定义函数即可将RandDataCursor作为返回类,如:
package test;
public class Calc02 {
public static test.RandDataCursor getCursor(Integer range) {
RandDataCursor rdc = new RandDataCursor(range);
return rdc;
}
}
这个自定义函数只需定义一个参数,即随机数的范围,返回的结果就是RandDataCursor,用来生成随机记录的游标。在调用时,返回的IlineInput类并不能直接作为游标使用,还需要调用com.scudata.dm.UserUtils类中的newCursor方法,如:
|
A |
1 |
=invoke(test.Calc02.getCursor,1000000) |
2 |
=invoke(com.scudata.dm.UserUtils.newCursor,A1,"") |
3 |
>A2.skip(100) |
4 |
=A2.fetch@x(100) |
A1中,返回自定义游标类,RandDataCursor。A2中,调用UserUtils中的newCursor方法,将A1中的结果作为参数传入,A2中的数据即为集算器中的游标数据了。A3中跳过前100条记录后,A4中取出100条数据后关闭游标,A4中结果如下:
返回数据时,也可以类似与读取文本数据一样,添加@t选项,将读到的第1行数据作为标题,或者添加@1选项,将数据读为1列。如将A2中的代码改为=invoke(com.scudata.dm.UserUtils.newCursor,A1,"t"),即相当于添加了@t选项,A4中结果如下:
当然,这里的RandDataCursor中实际上并未设计类名,执行的结果只是将第1条数据作为列名返回。从结果中可以看到,数据中的第2列和之前的结果不同,是随机整数。