标签(空格分隔): scala
属性的声明: scala类的属性有4种方法定义:var , val , private var , 同类对象私有字段private[this] var
class Person{ var varage = 0 // 类中所有var产生公有的setter和getter val valage = -1 // 类中所有val产生公有的getter private var age1 = 0 // 产生私有的setter和getter private[this] var age2 = 3 // 不产生setter,getter // 通过复写setter和getter def age = age2 // getter def age_=(newAge:Int) = { // setter : field_= this.age2 = newAge } }scala的setter和getter调用
val p = new Person p.varage_=(2) // setter p.age // getter主副构造器 (1)如果一个类没有显示的声明主构造器,则会自动加入无参构造 (2)辅助构造器名称为this(避免修改类名时要修改多个辅助构造器的名称)' (3)辅助构造器的开头必须以主构造器开始 (4)主构造器中的字段会自动被解析成类中的属性
class Person(var name:String,var age:Int){ def this(name:String){ this(name,-1) } def update(name:String,age:Int) = { print("update function is called") this.age = age } }伴生对象 (1)伴生对象适用于既有实例方法,又有静态方法的时候 (2)伴生对象中的apply方法可以用来不带new产生对象,apply的方法体要调用伴生类的主/辅助构造器方法 (3)伴生对象中的unapply方法,可以在模式匹配中用于属性匹配 ```scala object Person{ def apply(name: String): Person = new Person(name) def unapply(arg: Person): Option[Int] = Option(arg.age) }
// 继承的写法 class Student(name:String,age:Int,val sid:String) extends Person(name){ }
object Main extends App{ val p = Person("lj") // 利用伴生对象的apply方法产生对象 p match { // 模式匹配相当于手动调用了下面的unapply方法 case Person(-1) => println("match success") } if (Person.unapply(p).get == -1) println("unapply match success")
val s1 = new Student("lj",26,"09101306")
p("lj") = 27 // update function } ```
声明枚举类型 (1)object继承Enumeration (2)枚举的属性调用Value方法 (3)枚举的name自动设置为属性名 ```scala object Color extends Enumeration{ val Red = Value val Yellow = Value }
object Test1 extends App{ println(Color.Yellow.toString) // Yellow println(Color.Yellow.id) // 1 }
```
(1)所有集合继承自Iterable特质,因此,访问所有集合的通用代码为:
val coll = ... // 某种集合 val iter = coll.iterator while(iter.hasnext) iter.next(2)scala的集合大致分为3类: i) Seq:按照插入顺序排序的序列 ii) Set:每次插入一个元素,都会根据某种经排序方法决定元素在集合中所处的位置。set中没有重复的元素 iii) Map:键值对对偶
不可变序列: (1)Vector是ArrayBuffer的不可变版本,它拥有下标,以树型结构存储节点,支持快速的随机访问。每个节点最多可存放32个子节点。因此,对于一个100万个元素的向量,只需要四层节点(\(10^6 \approx32^4\)),访问任意一个元素,最多只需要4眺 (2)Range是一个整数序列,比如1,2,3,4,5,6,7,8,9 它不存储所有元素,只存储起始值,结束值和增值
可变序列:
scala中的列表要么是Nil(空表),要么是一个head元素加上一个tail(列表)。以下列表的声明等价 ```scala scala> 9::4::2::Nil res5: List[Int] = List(9, 4, 2)
scala> List(9,4,2) res6: List[Int] = List(9, 4, 2)
scala> 9::List(4,2) res7: List[Int] = List(9, 4, 2)
```集合操作 (1)向后追加元素(:+),向前追加元素(+:) (只用于向插入顺序有关的集合中追加)
scala> val v1 = Vector(1,2,3,4) scala> v1 :+ 8 res27: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4, 8) scala> 9 +: v1 res28: scala.collection.immutable.Vector[Int] = Vector(9, 1, 2, 3, 4)(2)+操作符,向Set等与插入顺序无关的集合中追加元素scala scala> val s1 = Set(1,2,3,4) scala> s1 +9 res31: scala.collection.immutable.Set[Int] = Set(1, 9, 2, 3, 4) (3)++与--分别为向集合中追加多个元素
reduceLeft和reduceRight (1)coll.reduceLeft(op)表达式,将op函数相继应用到集合中的元素,如图形成一个树形结构 (2)reduceLeft是从集合的左端开始,reduceRight是从集合的右端开始
scala> List(1,7,2,9).reduceLeft(_-_) // 1-7-2-9 scala> List(1,7,2,9).reduceRight(_-_) // 1-(7-(2-9)) res31: Int = -13foldLeft和foldRight (1)折叠方法让调用者可以自定义集合计算的初始元素,进行树型结构的计算scala scala> List(1,7,2,9).foldRight(5)(_-_) // 1-(7-(2-(9-5))) = -8 scala> List(1,7,2,9).foldLeft(5)(_-_) // 5-1-7-2-9 res33: Int = -14 (2)foldLeft和foldRight的简写形式:/:和:\
scala> (5 /: List(1,2,3))(_-_) // 5-1-2-3 res35: Int = -1 (3)利用折叠方法计算词频 初始化一个空Map[Char,Int],每一步将频率映射和新遇到的字母结合在一起scala scala> (Map[Char,Int]() /: "Mississippi"){(m,c) => m + (c -> (m.getOrElse(c,0)+1))} res43: scala.collection.mutable.Map[Char,Int] = Map(M -> 1, s -> 4, p -> 2, i -> 4)scanLeft和scanRight将折叠和映射组合在一起,得出每一次计算的中间结果
scala> List(1,2,3,4).scanLeft(0)(_-_) res46: List[Int] = List(0, -1, -3, -6, -10)定义: (1)Stream是一个尾部被懒计算的不可变列表,通过操作符#::可以构造一个流 (2)Strea的尾部懒计算后会缓存起来
scala> val tenMore = numsFrom(10) tenMore: Stream[BigInt] = Stream(10, ?) scala> tenMore.tail.tail res51: scala.collection.immutable.Stream[BigInt] = Stream(12, ?) res52: Stream[BigInt] = Stream(10, 11, 12, ?) // 10,11,12已经过缓存(3)用take获得多个答案,然后用force强制求值scala scala> tenMore.take(5).force res54: scala.collection.immutable.Stream[BigInt] = Stream(10, 11, 12, 13, 14) (4)不要不经take直接调用force,否则会一次计算出stream的所有尾值,知道内存溢出
所有的集合都能通过view方法转换为懒计算的视图,视图不同于流,连第一个元素也不去计算
scala> (0 to 5) res55: scala.collection.immutable.Range.Inclusive = Range(0, 1, 2, 3, 4, 5) scala> (0 to 5).view // 视图 res56: scala.collection.SeqView[Int,scala.collection.immutable.IndexedSeq[Int]] = SeqView(...) scala> (0 to 5).view.map(_*2) // SeqView(....):所有元素全不计算 res57: scala.collection.SeqView[Int,Seq[_]] = SeqViewM(...) scala> (0 to 5).view.map(_*2).force res58: Seq[Int] = Vector(0, 2, 4, 6, 8, 10)本章要点:
列表项(1)错误写法:Document类的setter方法最后返回了this,而该方法的返回值类型如果直接写成Document,虽然可以串联调用setTile和setAuthor,但是一旦出现Document的子类Book,则Book产生的对象调用setatile后返回的类型被写成Document,也就不能串联调用setATitle和setAuthor
// 错误示例 class Document { def setTitle(title: String):Document = { println("set title:" + title) this } def setAuthor(author:String):Documente = { println("set author:" + author) this } } val doc = new Document() doc.setAuthor("lj").setTitle("scala Education") // setAuthor返回的Document类型,可以串联调用setTitle class Book extends Document{ def addChgapter(chapter:String) :Book = { println("add chapter:" + chapter) this } } val book = new Book() book.setTitle("another book").addChgapter("1 chapter") // 编译报错,因为setTitle返回的类型是Document,而Document类没有addChapter方法(2)正确写法:为了使继承Document的Book类的对象也能串联调用,可以改造这些setter方法的返回值为this.type,这样,Book类的对象book在调用setTitle方法时,返回的类型就是book.type,而由于book对象有一个addChapter方法,因此可以串接起来
class Document { def setTitle(title: String):this.type = { println("set title:" + title) this } def setAuthor(author:String):this.type = { println("set author:" + author) this } } class Book extends Document{ def addChgapter(chapter:String) :this.type = { println("add chapter:" + chapter) this } } val book = new Book() book.setTitle("another book").addChgapter("1 chapter")(3)其次,如果想要定义一个接收object实例作为参数的方法,也可以使用单例类型。那么为什么对于单例对象的方法不直接调用,还要传进一个object对象作为参数在调用呢?因为有人喜欢构造那种调用起来像是一句话的代码book set Title to "Scala for the impatient"
object Title class Document { private var useNextArgAs:Any = null // 用Title.type声明传入的是Title单例对象, 用this.type声明返回值使得集成类的setter方法也能串联调用 def set(obj:Title.type):this.type = { useNextArgAs = obj this } def to(arg:String) : Unit={ if (this.useNextArgAs==Title) println("set finish") else "" } } object Test extends App{ val doc = new Document() doc set Title to "scala for the impatient" // 构造英文语句 }scala中,嵌套类属于它包含的外部对象,即每个实例都有自己的内部类 如下,chatter.member和myFace..member是不同的类。不能讲任何一个网络(NetWork)的成员(Member)加到另一个网络中 ```scala class NetWork{ class Member(val name:String) { val contacts = new ArrayBuffer[Member] // 这个泛型Member,指的是[对象.Member] }
private val members = new ArrayBuffer[Member]
def join(name:String) :Member= { val m = new Member(name) members += m m } }
val chatter = new NetWork val myFace = new NetWork
val Fred = chatter.join("Fred") val Barney = myFace.join("Barney")
Fred.contacts += Barney ```
内部类从属于每个对象这种约束是默认存在的,如果不想要这种约束,应该把Member类挪到NetWork类的外面。更好的选择是在Network的伴生对象中。如果想使用更为松散的定义,可以用类型投影NetWork#Member,表示任何Network的Memberscala class NetWork{ class Member(val name:String) { val contacts = new ArrayBuffer[NetWork#Member] //val contacts = new ArrayBuffer[Member] // 这个泛型Member,指的是[对象.Member] } ... }
结构类型是一组关于抽象方法,字段和类型的规格说明,这些抽象方法,字段,类型是该规格类型必须具备的。 写法上:用大括号包围这些抽象方法,字段,类型
下例所示,appendlines方法的形参是任何具有append方法的对象和一个string泛型的iterater,appendlines会调用这个对象的append方法
def appendLines(target: {def append(str:String): Any},lines:Iterable[String]): Unit ={ for(l <- lines){ target.append(l);target.append("\n") } }鸭子类型就像python这种动态类型语言,变量没有类型,当你写下obj.quack()时,运行时回去检查obj指向的对象在那一刻是否具有quack方法。换句话说:你不需要把obj声明为Duck类型,只要它运行时有Duck的方法(走起来,叫起来像鸭子一样)
符合类型的定义形式如下: \(T_1\) with \(T_2\) with \(T_3\) ...,表示要成为该复合类型的实例,必须满足每一个类型的要求(比如实现了这几个特质的方法),因此,符合类型也称作交集类型
val images = new ArrayBuffer[java.awt.Shape with java.io.Serializable] val rect = new Rectangle(5,10,20,30) // Rectangle extends Rectangle2D implements Shape,java.io.Serializable images += rect表示这个ArrayBuffer里的对象既要满足Shape和Serializable接口,还要存在setBounds方法
(1)scala提供了一种让类型的描述趋于数学中置表达式形式的写法:用中置表达式组合多个泛型。eg:用String Map Int来表示Map[String,Int] (2)写法:泛型1 类 泛型2 (3)中置表达式也可以用来模式匹配,eg:
case class Person[S,T](val name:S,val age:T) val p : String Person Int= Person("摇摆少年梦",19) p match { case "摇摆少年梦" Person 18=> println("matching is ok") case name Person age=> println("name:"+name+" age="+age) }(1)scala的存在类型是为了与java的类型统配符兼容 (2)写法:在类型表达式后面跟上forSome{},里面包含了type和val的声明,这些声明是对被forSome修饰的类型做一个限制。
下例中,t1的存在类型和t2的类型通配符是等价的,类型通配符是存在类型的语法糖
type t1 = Array[T] forSome { type T<:JComponent} type t2 = Array[_<:JComponent] // 存在类型允许使用更复杂的类型关系 type t3 = Map[T,U] forSome {type T,type U<:T}有的嵌套类通过类型投影NetWork#Member,扩大了泛型范围。但一些方法又想把嵌套类局限于每个对象的嵌套类,就是用存在类型加以限定
val chatter = new NetWork val myFace = new NetWork val Fred = chatter.join("Fred") // 同一个网络下的成员 val Fred2 = chatter.join("Fred2") // 同一个网络下的成员 val Barney = myFace.join("Barney") // 不同网络 Fred.contacts += Barney def process[M <: n.Member forSome { val n:NetWork }](m1:M,m2:M) = (m1,m2) process(Fred,Fred2) //process(Barney,Fred2) => 错误:process方法通过forSome里面的val n:NetWork限制了n.Member为对象自身的嵌套类,使得方法接收相同网络的成员,拒绝不同网络的成员(1)自身类型是对特质自身的一种限制,它指出该特质只能被混入哪个类中,或智能被混入哪个类的子类中 (2)形式:this: 类型 =>
trait Logged{ def log(msg:String) } trait LoggerException extends Logged{ this:Exception => // 这个this指代混入特质后的对象 def log(){ log(getMessage()) // getMessage方法来自于this,而this又是Exception的子类 } } object Test extends App{ type f = JFrame with LoggerException // 定义类型时不报错,创建对象时会因为LoggerException的自身类型限制而报错 // val v1 = new f // 报错:f类型是JFrame混入LoggerException,而JFrame不是Exception的子类 }(1)如果自带有自身类型限制的特质被另一个特质集成,则子特质必须重复写出自身类型,表示自己和父特质一样也有混入的限制
trait ManagedException extends LoggerException{ this:ArrayIndexOutOfBoundsException => // 这里的类型限制要定义成Exception或其子类 def say(){print("aaa")} }(1)设想一个应用,他需要日志和验证功能,当然,验证功能也需要用到日志。 (2)设计:因此,可以把日志和验证设为2个特质Logger和Auth,这两个特质分别有自己不同的实现。而真正的应用类App,只要混入这些不同的实现组合,就能使得App拥有日志和验证功能。而Auth需要用到Logger,所以在Auth特质中,通过自身类型调用Logger类型的方法
trait Logger{ def log(msg:String) } trait Auth{ this:Logger => // 自身类型调用Logger特质的方法 def login(id:String,passwd:String):Boolean } trait FileLogger extends Logger{ override def log(msg: String): Unit = { println(msg) } } trait MockAuth extends Auth{ this:FileLogger => override def login(id: String, passwd: String): Boolean = { if(id.equals("guest")) { log("guest login fail..") false }else true } } object App extends FileLogger with MockAuth{ // 此处通过依赖注入变换实现 def main(args: Array[String]): Unit = { login("guest","123456") } }(3)这种方法的怪异之处在于,一个App并非是验证器和文件日志器的组合。更自然地表述方式是使用成员变量来实现功能组件,而不是把App通过混入特质变成一个巨大的类型。
trait LogComponent{ // 最外层的大组件 trait Logger{ def log(msg:String) } val logger:Logger // 抽象变量 class FileLogger extends Logger{ override def log(msg: String): Unit = { println("write in file: "+msg) } } } trait AuthCompnonent{ // 最外层的大组件 this:LogComponent => // 使用抽象变量logger trait Auth{ def login(id:String,passwd:String):Boolean } val auth:Auth // 抽象变量 class MockAuth extends Auth{ override def login(id: String, passwd: String): Boolean = { if (id.equals("guest")){ logger.log("guest cannot login") // 这个logger变量来自于LoggerComponent,到底是哪个实现取决于继承的特质 false }else true } } } object App extends LogComponent with AuthCompnonent{ override val logger = new FileLogger // 成员变量 override val auth = new MockAuth // 成员变量 def main(args: Array[String]): Unit = { auth.login("guest","123456") } }(1)类或特质中,定义一个在子类中被具体化的抽象类型。eg:如下的Reader特质:
trait Reader{ type Contents def read(fileName:String):Contents } class StringReader extends Reader{ override type Contents = String override def read(fileName: String):Contents = Source.fromFile(fileName,"UTF-8").mkString // mkString method return string, corresponding with TYPE contents } class ImageReader extends Reader{ override type Contents = BufferedImage override def read(fileName: String): Contents = ImageIO.read(new File(fileName)) }(2)当然,在需要子类给出抽象类型的实现这种方法,还可以通过类型参数实现
trait Reader[C]{ def read(fileName:String):C } class StringReader extends Reader[String]{ override def read(fileName: String) = Source.fromFile(fileName,"UTF-8").mkString } class ImageReader extends Reader[BufferedImage]{ override def read(fileName: String) = ImageIO.read(new File(fileName)) }(1)如果类型是在创建对象时给出时(不存在继承该类的子类),就应当使用类型参数。比如构建HashMap[String,Int] (2)如果类型是在子类中给出,则使用抽象类型。比如上面的Reader就是子类中给出。 (3)当然还有一种情况是,在子类中给出类型参数。这种方法没什么不好,但是一旦父类或父特质中有多个类型参数,子类的定义就会变得冗长。eg:Reader[File,BufferedImage],这样会使得伸缩性变差
(1)隐士转换:以implicit声明的带有单个参数的函数 (2)这个函数自动将一种类型转换成另一种类型
case class Fraction(a:Int,b:Int){ def *(second:Fraction) = Fraction(second.a*a,second.b*b) } object Test extends App{ implicit def int2Fraction(n:Int) = Fraction(n,1) val result = 3*Fraction(4,5) // Int没有*(Fraction)的方法,但Fraction有*(Fraction)方法 println(result) }(1)你多想用new File(README"").read来读取一个文件,但是jdk的File并未提供这个方法。作为java,你只能向Oracle公司提交申请,但是scala却能通过一年隐士转换来丰富这个api
case class RichFile(filepath:String){ def read() = Source.fromFile(filepath).mkString("") } object Test extends App{ implicit def file2RichFile(from:File) = new RichFile(from.getAbsolutePath) new File("README").read() }(1)当表达式所得值的类型,和所处位置的期待类型不一样时 (2)当访问一个不存在的成员或方法时。eg : File.read()。也就是说当调用a.fun(b)时,如果存在2个隐士转换使得:convert(a)的结果有fun方法,或者存在方法a.fun(convert(b)),则编译器会使用convert(a)隐士转换。因为编译器会把没有调用成员的对象隐士转换
(1)如果在不进行隐士转换的情况下可以通过编译,则不进行隐士转换 (2)变量只能经过一次隐士转换,而不能进行形如convert1(convert2(a))这样的多次转换 (3)如果两个隐士转换都满足条件,编译器报错。即:convert1(a)b与convert2(a)b都成立
隐士参数是函数的参数列表中,带有implicit标记的形参。此时在调用该方法时,编译器会查询缺省值
case class Delimiters(left:String,right:String) object Test extends App{ def quote(what:String)(implicit delims:Delimiters) = delims.left + what + delims.right println(quote("impatient scala")(Delimiters("《","》"))) // 显式调用 implicit val quoteDelimiters = Delimiters("'","'") // 隐士调用 println(quote("hello world")) }(1)隐士转换是一个隐士方法,当方法作为参数传入另一个方法,就形成了含有隐士参数的高阶方法
def smaller[T](a:T,b:T)(implicit order: T => Ordered[T]) = { if(order(a) < b) a else b } println(smaller("a","b"))(2)隐士转换作为隐士参数的简化声明
def smaller2[T](a:T,b:T)(implicit order: T=>Ordered[T]) = { if(a<b) a else b // 隐士转换自动执行,因此不用显式调用 } println(smaller2(2,4))(1)形如T:M的泛型,表示程序的上下文中,存在一个类型为M[T]的隐式值。通常用于类的泛型限制 (2)类中的方法使用这个上下文界定的隐式值有两种方法: (i.) 通过定义implicit隐士形参 (ii) 在函数体中使用Predef类的implicitly()方法,传入上下文界定类型还原出这个隐式值
// method 1 class Pair[T:Ordering](val first:T,val second:T){ def smaller(implicit ord:Ordering[T])= if (ord.compare(first,second)<0) first else second } // method 2 class Pair1[T:Ordering](val first:T,val second:T){ def smaller = if( implicitly[Ordering[T]].compare(first,second) < 0 ) first else second } object Test1 extends App{ println(new Pair1(24,35).smaller) }(3)用泛型定义的类,在实例化时,编译器会通过成员变量推断出泛型的类型。
(1)类型证明是形如implicit ev T <:< U的一个隐士参数,其中<:<还可以是<=<,<%<. (2)三个符号分别表示:T是否是U的子类型,T是否等于U,T是否可以通过隐士转换为U
(1)<:<,<=<,<%<3个符号并非是语言特性,而是定义在Predef中的三个类 (2)举例:<:<类的定义 下面的三行代码,解释了scala通过<:<类,实现类型证明的过程。 (i.) 首先:定义了一个带有泛型的抽象类<:<,这个类继承的(From=>To),实际上就是一个Function1(带有一个形参的函数) (ii) 其次:初始化了一个singleton_<:<对象,这个对象的-From和+To泛型都是Any,而且复写了apply方法为传入一个Any类型的参数x,然后把x返回出去 (iii)最后:定义了一个隐士转换$conforms[A],它的返回值类型为A <:< A(<:<[A,A]的中置写法)。该方法就是将<:<[Any,Any]强转为<:<[A,A](即:Function1[Any,Any]转换为Function1[A,A])。而这个泛型A到底能否让编译器推断出来,就是这个类型证明能否通过的关键 (v.)编译器推断:由于<:<类的泛型一个逆变,一个协变。eg:对于<:<[String,AnyRef],编译器就能推断出A是String(String逆变成String,AnyRef协变成String)
@implicitNotFound(msg = "Cannot prove that ${From} <:< ${To}.") sealed abstract class <:<[-From, +To] extends (From => To) with Serializable private[this] final val singleton_<:< = new <:<[Any,Any] { def apply(x: Any): Any = x } implicit def $conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A](1)@implicitNotFound注解加载类上,该类需要时隐士转换函数From->To的To类。意义在于告知编译器,再不能构建出这个To类时爆出错误信息 (2)例如:
@implicitNotFound(msg = "Cannot prove that ${From} <:< ${To}.") sealed abstract class <:<[-From, +To] extends (From => To) with Serializable转载于:https://www.cnblogs.com/moonlord/p/6672409.html
相关资源:快学scala第二版 中文