Scala 中Set(以及Map的 Key)的去重规则是:
- 先比较两个对象的
hashCode,若不相等则认为是不同对象; - 若
hashCode相等,再比较equals,若equals返回true则认为是同一对象。
因此仅重写equals是不够的,必须同时重写hashCode(否则Set会认为stu1和stu2是不同对象,无法去重)
object class32 {
// Set是可以去重的
// 创建一些对象,把他们装入Set,看看是否能去重!
class Stu(var name: String, var age: Int) {
override def equals(obj: Any): Boolean = {
val other = obj.asInstanceOf[Stu]
other.age == age && other.name == name
}
// 必须重写hashCode,否则Set无法正确去重
override def hashCode(): Int = {
name.hashCode() + age * 31
}
override def toString: String = s"Stu($name, $age)"
}
def main(args: Array[String]): Unit = {
val stu1 = new Stu("小花", 18)
val stu2 = new Stu("小花", 18)
println(stu1)
println(stu1 == stu2) // 输出true(因为重写了equals)
val set1 = Set(stu1, stu2)
println(set1) // 输出Set(Stu(小花, 18))(去重成功)
}
}
- 创建对象可省略 new(语法便捷性) • 普通类创建对象必须写 new(比如上一段代码的 new Stu("小花",18)); • case class 可以直接写 Stu("小花",18),编译器会自动帮你调用构造方法,简化代码。
- 属性默认只读(不可修改,数据安全) • 代码中注释了 stu1.age = 19 和 stu1.name = "xxx",这两行如果取消注释会编译报错; • 因为 case class 的属性默认是 val 修饰(只读),除非手动显式声明为 var(比如 case class Stu(var name:String, var age:Int)),否则不能修改属性值,适合封装 “不可变数据”(比如配置信息、传输数据)。
- 自动重写关键方法(无需手动编码) 这是 case class 最核心的优势,编译器自动帮你重写了 4 个常用方法,无需像普通类那样手动重写 equals、hashCode、toString: • toString:默认输出 类名(属性1,属性2)(比如 Stu(小花,18)),不用手动写 override def toString...; • equals:按 “属性值” 比较对象,而不是比较内存地址(普通类默认比较内存地址)。所以 stu1 和 stu2 虽然是两个不同对象,但属性值相同,stu1 == stu2 返回 true; • hashCode:基于属性值计算哈希值,配合 equals 让 Set、Map 等集合能正确去重(比如代码中 Set(stu1, stu2) 最终只有一个元素); • copy 方法(代码没用到,但重要):可以快速复制对象并修改部分属性,比如 val stu3 = stu1.copy(age=20),得到 Stu(小花,20),原对象 stu1 不变。
object class33 {
/**
* case class 与普通的类,它有三点不同
* 1、在创建对象时,可以省略 new
* 2、默认情况下,属性都是只读的
* 3、自己会帮你重写eqauls, tostring 等方法。
* 特别适合用来做数据的封装。
*/
case class Stu(name:String, age:Int)
def main(args: Array[String]): Unit = {
val stu1 = Stu("小花", 18)
// 2、默认情况下,属性都是只读的
// stu1.age = 19
// stu1.name = "xxx"
val stu2 = Stu("小花", 18)
println(stu1)
println(stu1 == stu2)
val set1 = Set(stu1, stu2)
println(set1)
}
}
修正了 ListBuffer 的拼写(原 listbuffer → 正确 ListBuffer); 2. 补充了 addNewBook 方法的参数(原代码缺少book参数); 3. 完善了 “图书是否存在” 的判断逻辑(示例中按id判断唯一性,也可根据需求改为bookName+author); 4. 修正了遍历输出的语法格式。
object class34 {
// 1. 定义Book的case class
case class Book(id: String, bookName: String, author: String, price: Double)
def main(args: Array[String]): Unit = {
val book1 = Book("001", "高效能人士的七个习惯", "韦帕", 50)
val book2 = Book("002", "沉思录", "奥勒留", 20)
val book3 = Book("003", "自控力", "xx", 50)
// 2. 定义可变的ListBuffer
val bookList = scala.collection.mutable.ListBuffer(book1, book2, book3)
val book4 = Book("004", "高效能人士的七个习惯", "韦帕", 50)
// 4.5 调用addNewBook方法
if (addNewBook(bookList, book4)) {
println("添加成功")
} else {
println("图书已经存在,添加失败")
}
// 10. 遍历输出bookList中的图书信息
bookList.foreach(ele => {
println(s"书名:《${ele.bookName}》")
println(s"作者:${ele.author}")
println()
})
}
// 定义添加图书的方法(检查是否已存在)
def addNewBook(bookList: scala.collection.mutable.ListBuffer[Book], book: Book): Boolean = {
// 检查图书是否存在(根据id/书名等唯一标识,这里假设按id判断)
if (bookList.exists(_.id == book.id)) {
false
} else {
bookList += book
true
}
}
}
object class35 {
/**
* 1. next() 获取当前的元素,移动一次迭代器
* 2. hasNext 判断是否有下一个元素
* 3. drop(n) 从当前位置开始,跳过n个元素,它返回一个新的迭代器
*/
def main(args: Array[String]): Unit = {
// 获取一个迭代器
val it = List(1,2,3,4).iterator
// 以下是迭代器的基本操作(注释掉的示例)
// println(it.next())
// println(it.next())
// println(it.next())
// println(it.next())
// println(it.next()) // NoSuchElementException: head of empty list
// 使用drop跳过元素,返回新迭代器
val it1 = it.drop(2)
// 遍历新迭代器
while(it1.hasNext){
println(it1.next()) // 输出 3, 4
}
}
}
object class36 {
/**
* 1. next() 获取当前的元素,移动一次迭代器
* 2. hasNext 判断是否有下一个元素
* 3. drop(n) 从当前位置开始,跳过n个元素,它返回一个新的迭代器
* 4. take(n) 从当前位置开始,获取前n个元素,返回新迭代器
* 5. duplicate 复制迭代器,返回两个独立的新迭代器
*/
def main(args: Array[String]): Unit = {
// 获取List的迭代器
val it = List(1,2,3,4,5,6,7,8,9,10).iterator
// 复制迭代器,得到两个独立的迭代器it1、it2
val (it1, it2) = it.duplicate
// 使用it1访问前3个元素
val its = it1.take(3)
while(its.hasNext){
println(its.next()) // 输出:1、2、3
}
println("-----------------")
// 使用it2访问所有元素(不受it1的遍历影响)
while(it2.hasNext){
println(it2.next()) // 输出:1、2、3、4、5、6、7、8、9、10
}
}
}