BoxLang 1.14.0: BoxSet来了,BoxLang的一等集合类型
目录
为什么要用集合?先看问题认识 BoxSet创建集合:所有方式
[LOADING...]
BoxLang 1.14.0 附带了一个新的动态一流 Set 类型,直接内置于语言中。这不是一个需要手动调用的包装器,也不是多年前从 Stack Overflow 答案中粘贴的 createObject( "java", "java.util.HashSet" ) 咒语。这是一个真正的 BoxSet,具有字面量语法、运算符重载、完整的函数式管道、变更监听器、JSON 序列化和深度 Java 互操作。
如果你曾经用循环对数组进行去重、逐个元素比较两个集合,或者基于结构体建模权限系统——那么 Set 就是你一直缺少的工具。让我们深入探讨。
为什么要用集合?先看问题
数组是有序、有索引且允许重复的。结构体是键值映射。两者都是基础,但都没有建模现实世界中最常见的形状之一:一袋唯一的东西。
想一想:
- 用户的已分配角色:
[ "admin", "editor", "admin" ]——这个重复是一个潜在的 bug - 博客文章上的标签——顺序通常不重要,唯一性才是关键
- 活动特性开关——成员检测是你需要的唯一操作
- 需要比较的两个数据集——什么是新的、什么消失了、什么共享了?
- 需要知道所有传入参数的精确顺序和内容?
- 爬虫中的 URL 去重
- 权限交集:这个用户在这个资源上能做什么?
在 BoxSet 出现之前,你需要用数组(慢的 arrayContains 查找、手动去重循环)或结构体(把键当作值,序列化别扭)来近似所有这些场景。两者都是变通方案。BoxSet 才是真正的答案。
认识 BoxSet
BoxSet 是一个一流的 BoxLang 类型,它在 java.util.Set 基础上进行了包装,并实现了完整的语言集成。底层会根据你的需求选择三种 Java 后端实现之一:
每种变体都会自动强制唯一性。每种变体都支持完整的成员函数 API、运算符重载和函数式管道。从第三方库获得的 Java Set 对象可以直接插入——BoxLang 会包装它们而不进行复制。
创建集合:所有方式
BoxLang 提供了几种符合人体工程学的创建路径,取决于你的上下文。
setNew() —— 主力函数
setOf() —— 可变参数简写
当你预先知道你的值时,setOf() 是最简洁的表达:
参数列表中的重复项会被静默丢弃——没有错误,没有麻烦。
字面量语法:set{ ... }
BoxLang 1.14.0 引入了一种集合字面量语法,读起来就像概念本身:
"set" 关键字由解析器守卫,因此不会与名为 set 的变量冲突。你现有的代码是安全的。
从其他类型转换
三种变体实战对照
选择正确的变体关乎正确性和性能。
默认(HashSet)—— 不关心顺序时
有序(LinkedHashSet)—— 需要保持插入顺序时
排序(TreeSet)—— 始终需要自然顺序时
成员检测与迭代
检测成员
迭代
集合代数:真正的力量
这是 BoxSet 的价值所在。四种代数运算,既可以作为成员方法使用,也可以通过重载运算符使用。
并集 —— 两个集合的所有唯一元素
交集 —— 两个集合共有的元素
差集 —— 在 A 中但不在 B 中的元素
对称差集 —— 在两者之一但不同时存在的元素
运算符接受"松散"右操作数
你不需要先将所有内容转换为 Set:
当两边的操作数都不是 Set 时,运算符会回退到标准数学运算:
2 + 3 = 5,4 ** 3 = 12,2 ^ 3 = 8。你现有的算术代码是安全的。
函数式编程管道
BoxSet 配备了与你从 Arrays 中了解的相同的函数式词汇表。每个操作都返回一个新的 Set(或一个标量),原始 Set 保持不变。
链式调用自然工作:
class PermissionService {
}
svc = new PermissionService()
userRoles = setOf( "editor", "analyst", "viewer" ) groupRoles = setOf( "manager", "viewer" )
effective = svc.mergeRoles( userRoles, groupRoles ) // {editor, analyst, viewer, manager}
canSeeReports = svc.canAccess( effective, "/reports" ) // true(analyst 或 manager 匹配)
canSeeAdmin = svc.canAccess( effective, "/admin" ) // false
// 内容标签系统 post1Tags = setNew( type="linked", values=[ "boxlang", "jvm", "performance", "oss" ] ) post2Tags = setNew( type="linked", values=[ "jvm", "java", "boxlang", "interop" ] ) post3Tags = setNew( type="linked", values=[ "performance", "caching", "redis", "oss" ] )
// 出现在多篇文章中的标签 commonTagsP1P2 = post1Tags * post2Tags // {boxlang, jvm}
// 内容库中的所有唯一标签 allTags = post1Tags + post2Tags + post3Tags // {boxlang, jvm, performance, oss, java, interop, caching, redis}
// post1 独有的标签——适合“独家”徽章 exclusiveToPost1 = post1Tags - post2Tags - post3Tags // {boxlang}——只有“boxlang”出现在p1而不在另外两篇中……实际上结果因情况而异
// 查找与 post1 共享至少2个标签的文章(相关文章) relatedThreshold = 2 isRelated = ( post1Tags * post2Tags ).size() >= relatedThreshold // true
function detectChanges( required previousIds, required currentIds ) { prevSet = previousIds.toSet() currSet = currentIds.toSet()
}
yesterdayUsers = queryGetColumn( getYesterdayQuery(), "user_id" ) todayUsers = queryGetColumn( getTodayQuery(), "user_id" )
diff = detectChanges( yesterdayUsers, todayUsers )
writeDump( "今日新增注册: #diff.added.size()#" ) writeDump( "流失用户: #diff.removed.size()#" )
// 只向真正的新用户发送欢迎邮件 diff.added.each( userId => { emailService.sendWelcome( userId ) } )
class CrawlerPipeline {
}
s = setNew( values=[ "Hello", "hello", "HELLO", "hElLo" ] ) s.size() // 1
s.contains( "HELLO" ) // true s.contains( "hElLo" ) // true
tokens = setNew( caseSensitive=true, values=[ "Bearer", "bearer", "BEARER" ] ) tokens.size() // 3
tokens.contains( "bearer" ) // true tokens.contains( "Bearer" ) // true tokens.contains( "beareR" ) // false
nums = setNew( values=[ 1, 1.0, 1L, 1.00 ] ) nums.size() // 1
import java.util.HashSet
javaSet = createObject( "java", "java.util.HashSet" ).init() javaSet.add( "a" ) javaSet.add( "b" )
// 包装——不复制,修改双向传递 bxSet = javaSet castAs "Set" bxSet.add( "c" )
javaSet.contains( "c" ) // true——同一底层对象 bxSet.size() // 3
config = { "host" : "localhost", "port" : 5432, "ssl" : true, "database" : "myapp" }
// 键作为集合 keys = config.keySet() keys.contains( "port" ) // true
// 值作为集合(已去重) values = config.valueSet()
// 用于检查是否有任何键与禁止列表重叠 forbidden = setOf( "password", "secret", "token", "key" ) hasSensitiveKeys = !keys.isDisjointFrom( forbidden ) // false——我们的键都不在禁止列表中
ALLOWED_METHODS = setOf( "GET", "POST", "PUT", "DELETE", "PATCH" ).toUnmodifiable()
ALLOWED_METHODS.size() // 5 ALLOWED_METHODS.contains( "GET" ) // true
// 修改尝试会抛出 UnmodifiableException 运行时异常 ALLOWED_METHODS.add( "TRACE" ) // 抛出异常!
// 解冻以获取可修改的副本进行扩展 extended = ALLOWED_METHODS.toModifiable() extended.add( "HEAD" ) extended.size() // 6——ALLOWED_METHODS 仍为5
class HttpConstants {
}
s = setNew( type="linked", values=[ "boxlang", "jvm", "oss" ] )
json = jsonSerialize( s ) // ["boxlang","jvm","oss"]
// 或者通过成员函数 json = s.toJSON()
// 嵌套在结构体中 payload = { "user" : "alice", "roles" : setOf( "editor", "viewer" ), "tags" : setNew( type="linked", values=[ "premium", "beta" ] ) } jsonSerialize( payload ) // {"user":"alice","roles":["editor","viewer"],"tags":["premium","beta"]}