Scala Spark UDF空值处理求助:GMT转MST函数遇Null报错
GMT转MST的Spark UDF空值处理问题
我编写了一个Scala版Spark UDF用于将GMT时区时间转换为MST,但传入null时触发空指针异常(NPE)。原UDF代码如下:
val Gmt2Mst = (dtm_str: String, inFmt: String, outFmt: String) => { if ("".equals(dtm_str) || dtm_str == null || dtm_str.length() < inFmt.length()) { null } else { val gmtZoneId = ZoneId.of("GMT", ZoneId.SHORT_IDS); val mstZoneId = ZoneId.of("MST", ZoneId.SHORT_IDS); val inFormatter = DateTimeFormatter.ofPattern(inFmt); val outFormatter = DateTimeFormatter.ofPattern(outFmt); val dateTime = LocalDateTime.parse(dtm_str, inFormatter); val gmt = ZonedDateTime.of(dateTime, gmtZoneId) val mst = gmt.withZoneSameInstant(mstZoneId) mst.format(outFormatter) } } spark.udf.register("Gmt2Mst", Gmt2Mst)
执行以下Spark SQL语句时触发报错:
spark.sql("select null as col1, Gmt2Mst(null,'yyyyMMddHHmm', 'yyyyMMddHHmm') as col2").show()
报错核心信息:
Caused by: java.lang.NullPointerException at org.commonspirit.sepsis_bio.recovery.SepsisRecoveryBundle$$anonfun$3.apply(SepsisRecoveryBundle.scala:123) at org.commonspirit.sepsis_bio.recovery.SepsisRecoveryBundle$$anonfun$3.apply(SepsisRecoveryBundle.scala:122)
问题原因
空值判断顺序错误:原代码先判断"".equals(dtm_str),再判断dtm_str == null,虽然逻辑或的特性理论上会在dtm_str == null为true时跳过后续判断,但实际执行中可能因JVM表达式求值顺序或Spark代码生成逻辑触发NPE。更稳妥的做法是优先判断null,再处理其他非空校验,同时采用Scala的Option类型处理空值更符合语言习惯,能彻底避免NPE。
修正后的UDF代码
方案1:调整空值判断顺序
直接修改判断条件的顺序,确保null值直接进入返回分支,避免后续可能的NPE:
val Gmt2Mst = (dtm_str: String, inFmt: String, outFmt: String) => { if (dtm_str == null || "".equals(dtm_str) || dtm_str.length() < inFmt.length()) { null } else { val gmtZoneId = ZoneId.of("GMT", ZoneId.SHORT_IDS); val mstZoneId = ZoneId.of("MST", ZoneId.SHORT_IDS); val inFormatter = DateTimeFormatter.ofPattern(inFmt); val outFormatter = DateTimeFormatter.ofPattern(outFmt); val dateTime = LocalDateTime.parse(dtm_str, inFormatter); val gmt = ZonedDateTime.of(dateTime, gmtZoneId) val mst = gmt.withZoneSameInstant(mstZoneId) mst.format(outFormatter) } } spark.udf.register("Gmt2Mst", Gmt2Mst)
方案2:使用Scala Option处理空值(推荐)
采用Scala的Option类型封装参数,通过链式调用过滤无效值,代码更简洁安全:
import java.time.{LocalDateTime, ZoneId, ZonedDateTime} import java.time.format.DateTimeFormatter val Gmt2Mst = (dtm_str: String, inFmt: String, outFmt: String) => { Option(dtm_str) // 过滤空字符串和长度不足的情况 .filter(str => str.nonEmpty && str.length >= inFmt.length) .map { str => val gmtZoneId = ZoneId.of("GMT", ZoneId.SHORT_IDS) val mstZoneId = ZoneId.of("MST", ZoneId.SHORT_IDS) val inFormatter = DateTimeFormatter.ofPattern(inFmt) val outFormatter = DateTimeFormatter.ofPattern(outFmt) val dateTime = LocalDateTime.parse(str, inFormatter) val gmt = ZonedDateTime.of(dateTime, gmtZoneId) val mst = gmt.withZoneSameInstant(mstZoneId) mst.format(outFormatter) } // 无有效输入时返回null .orNull } spark.udf.register("Gmt2Mst", Gmt2Mst)
额外优化建议
如果需要更健壮的处理,可以加入对inFmt和outFmt的非空校验,避免因格式参数为null导致的异常:
val Gmt2Mst = (dtm_str: String, inFmt: String, outFmt: String) => { Option(dtm_str) .filter(str => str.nonEmpty && str.length >= inFmt.length) .filter(_ => inFmt != null && outFmt != null) .map { str => // 原转换逻辑 } .orNull }
内容的提问来源于stack exchange,提问作者Adarsh NamdevCHS
相关产品推荐
相关产品推荐

