scala

最近在准备面试,也当自己对知识点的复习了。
学习Scala是看了《快学Scala》这本书,最近有准备面试,把知识点重新整理一下,方便以后自己查询,顺便也做一下知识回顾。

1. 基础

val 定义常量
var定义变量

常用类型

var str:String = "easy"
// 可在:后加入变量的类型,不加也可以(scala会进行类型推断)
val x,y = 100
//将两个值都赋值成100

scala基本数据类型有Byte,Char,Short,Int,Long,Float,Double,Boolean。这些类型是类,不可以区分基本类型和引用类型。比如可以对数字执行方法。scala的String与java的java.lang.String来封装字符串,并通过StringOps类给字符串加上了上百种操作。

scala还提供了RichIntRichDouble等为Int,Double提供快捷方法。

// 基本类型是类,可以调用方法
val string: String = 1.toString
// RichInt类的方法
val inclusive: Range.Inclusive = 1.to(10)

scala并没有提供++ 或者–的操作,只能使用+= -=

方法调用

如果一个无参方法不修改对象,那么调用时就可以不写括号
scala中静态方法实现是在伴生对象中中定义,包也有包对象,导入包之后就可以使用包对象的方法。

import scala.math._

object test {
  def main(args: Array[String]): Unit = {
    sqrt(2)
    //如果不导包就需要这样写
    //scala.math.sqrt(2)
  }
}

使用scala.开头的包时,可以省去scala前缀。
类有一个伴生对象,其方法就跟java中的静态方法一样。

apply方法

在scala中,如果s是一个字符串,s(4)就是取字符串的第4个字节,()操作符背后的实现原理是apply()方法

有时()会跟scala的特性,隐式参数冲突。

//"abc".sorted(3)
("abc".sorted)(3)
"abc".sorted.apply(3)

控制结构

条件表达式

val x = 0
val i: Int = if (x > 0) 1 else 0

语句终止

行尾不需要分号,除非将两行代码写到一行

val x = 0;var b = 1

如果一行代码相分成两行来写,需要保证第一行不能用作语句结尾的符号结尾,通常使用操作符结尾

var i = 1 + //
2

块表达式

scala中,{}块包含一系列表达式,其结果也是一个表达式,块中国最后一个表达式的值就是块的值

val i = {
      var a = 3
      1+2
    }
    print(i)//3

输入和输出

使用print或者println函数进行输出打印。
printf用法

printf("my name is %s, %d years old","wang",12)

也可以使用字符串插值实现相同的效果

var name = "wang"
println(f"Hello, my name is ${name}")

可以使用scala.io.StdIn的readLine方法从控制台读取一行输入,如果要读取数字,Boolean可以使用对应的readInt,readBoolean

StdIn.readLine("your name:")

循环

while (i > 0){
      i -= 1
}
for(i <- 1 to 10){ //to 的用法为返回1到10的Range区间
      println(i)
}

for(i <- collection)使变量i遍历右边集合的值

scala中没有break方法
如果想实现break方法可以考虑

1. 使用Boolean型的空值变量
2. 使用return
3. 使用Breaks对象中的break
for(i <- 1 to 10){
      if (i>1){
        Breaks.break()//强烈不建议使用,不好用而且还抛异常
      }
      println(i)
    }

高级for循环

    for( i <- 1 to 3 ;j <- 1 to 2){
      println(i+""+j)
    }
    // 11 12 21 22 31 32

每个控制器都可以带上守卫,一个以if为开头的Boolean表达式

    var a = true
    for( i <- 1 to 3 ;j <- 1 to 2 if a){
      println(i+""+j)
    }

如果for循环以yield开始,则该循环会构造一个集合,这类循环叫做for推导式

val strings: immutable.IndexedSeq[String] = for (i <- 1 to 3; j <- 1 to 2 if a) yield i + "" + j

函数

只要函数不是递归的,scala编译器都能推断出返回的类型,如果是递归函数就需要指定返回的类型。

默认参数与带名参数

	def fun(name:String="wang",age:Int): Unit ={
      println(name+"-"+age)
    }

提供参数时,可以指定参数名

fun(age = 12,name = "lalala")

变长参数

    def fun(names:String*): Unit ={
      for(i <- names){
        println(i)
      }
    }
    fun(names = "1","2","3")
	def fun(names:String*): Unit ={
      for(i <- names){
        println(i)
      }
    }

    val strings: Array[String] = Array("1", "2", "3")
    fun(strings:_*)
    //fun(Strings)这样写是不对的,需要使用_*来高速编译器,希望将strings当做参数序列来处理

类型转换

isInstance判断是否处于该类型

	var a = true
    println(a.isInstanceOf[Boolean])

asInstanceOf将对象转为指定类型

val i1: String = a.asInstanceOf[String]

懒值(有点有用,记住就对了)

val被声明为lazy时,它的初始化将被推迟,知道我们首次对他取值

异常

跟java基本很像

    var url = new URL("")

    try {
      process(url)
    }catch {
      case _: MalformedURLException => println("bad url")
      case ex: IOException => ex.printStackTrace()
    } finally {
      println("end")
    }

数组相关操作

定长数组

Scala中的Array是定长数组

val ints = new Array[Int](3)
//初始化赋值
val strings: Array[String] = Array("1", "2")
strings(0) = "3"
//可以存放不同类型的元素
val array: Array[Any] = Array(1, "2")

变长数组

ArrayBuffer

    val ints = new ArrayBuffer[Int]()
//    结尾添加一个元素
    ints+=2
//    结尾添加多个元素
    ints+=(1,2,3,4)
//    移除末尾多个元素
    ints.trimEnd(2)
//    Array与ArrayBuffer可以相互转换
    val array: Array[Int] = ints.toArray
    val buffer: mutable.Buffer[Int] = array.toBuffer

遍历数组

for (i <- ints){
      println(i)
    }
    
for (i <- 0 until ints.length-1 by 2){
  println(ints(i))
}

常用算法

    val sum: Int = ints.sum
    val sorted: ArrayBuffer[Int] = ints.sorted
    println(sorted)
    val ints1: ArrayBuffer[Int] = ints.sortWith(_ > _)
    println(ints1)
    //ArrayBuffer(1, 2, 2)
	//ArrayBuffer(2, 2, 1)

mkString可以显示数组或者数组缓冲的内容,还可以指定分隔符

    println(ints.mkString(","))
    println(ints.mkString("<",",",">"))
    //2,1,2
	//<2,1,2>

与Java的互操作

如果想调用一个带有Object[]参数额java方法,比如java.util.ArraysbinartSearch(Object [] a,Object Key)

val strings: Array[String] = Array("1", "2", "3", "4")
    //    这样写不行
    //    val value: Any = java.util.Arrays.binarySearch(strings, "xxx")
    val value: Any = java.util.Arrays.binarySearch(strings.asInstanceOf[Array[Object]], "xxx")

如果调用返回java.util.List的java方法,可以再Scala中会用java.util.List,但是入乡随俗,完全可以使用scala.collection.JavaConversions里的隐式转换方法,这样就可以在代码中使用Scala缓冲,在调用Java方法时,这些对象会被自动包装成Java列表。

    import scala.collection.JavaConversions.bufferAsJavaList

    val strings: ArrayBuffer[String] = ArrayBuffer("1", "2", "3")
    val builder = new ProcessBuilder(strings)//scala到Java的转换
	import collection.JavaConversions.asScalaBuffer
    import scala.collection.mutable.Buffer
    val strings1: Buffer[String] = builder.command()

映射和元组

映射(Hash表,字典)

	val intToInt = Map((1, 2), (2, 3))
	//默认的Map值是不能修改的
	import collection.mutable.Map
    val intToInt1 = Map((1, 2), (3, 4))
    intToInt1(1) = 3
    println(intToInt1)
    <<< Map(1 -> 3, 3 -> 4)

获取指定键的值

val intToInt1 = Map((1, 2), (3, 4))
intToInt1(1) = 3
//如果键不存在就会抛异常,可以使用contains方法来检查
val i: Int = intToInt1.getOrElse(10, 9)
//设置默认值
intToInt1.withDefaultValue(0)

更新映射中的值

//修改元素
intToInt1(1) = 3
//添加键值对
intToInt1 += ((4,3),(8,4))
//删除键
intToInt1 -= 3

迭代映射

for((k,v) <- intToInt1){
      println(k,v)
    }
    for (k <- intToInt1.keySet){
      println(k)
    }

    for (k <- intToInt1.values){
      println(k)
    }

与java的互操作

    import scala.collection.JavaConversions.mapAsScalaMap
    val stringToInt:scala.collection.mutable.Map[String,Int] = new util.TreeMap[String, Int]()

元组

元组是不能被修改的,而且有最大长度限制

    val tuple: (Int, Int) = (1, 2)
    println(tuple._1)//取第一个元素

拉链操作

	val ints1: Array[Int] = Array(1, 2)
    val strings1: Array[String] = Array("1", "2")
    //拉链操作
    val tuples: Array[(Int, String)] = ints1.zip(strings1)
    // 将元组转换为Map
    val map: Predef.Map[Int, String] = tuples.toMap

简单类和无参方法

类中的字段自动带有`getter`和`setter`方法
class Counter{
  private var count = 0 //字段必须初始化
  def increment(): Unit ={//方法默认是公有的
    count+=1
  }
}

val counter = new Counter()//实例化对象
counter.increment//调用无参方法时,可以写上圆括号,也可以不写,一般对于修改字段值得无参方法需要加括号,获取值得无参方法不加括号

在scala中,一个scala源文件,可以包含多个类,所有这些类都具有公有可见性

class Counter{
  private var count = 0
  def increment = count//强制声明必须使用不带括号
}
val counter = new Counter()
counter.increment

scala为每一个字段都提供了setter和getter方法,私有字段的对应方法也是私有的,公有字段的对应方法时共有的。
在scala中getter和setter方法分别叫做ageage_

class Counter{
  var count = 0
}
println(counter.count)//调用了counter.count()
counter.count = 12//调用了counter.count_=(12)

可以重新定义settergetter

class Counter{
  var mycount = 0
  def count = mycount
  def count_=(newcount:Int) {
    mycount = newcount
  }
}

println(counter.count)//调用了counter.count()
    counter.count = 12//调用了counter.count_(12)

如果字段是val类型,那么只有getter方法被生成
如果不需要任何settergetter可以向字段生命为private[this]

对象私有字段

class Counter{
  private var value = 0
  def isLess(other: Counter) = {
    value< other.value//是不是很诧异可以访问其他Counter类型的属性,怎么去避免这种情况呢?
    
  }
}

class Counter{
  private[this] var value = 0
  def isLess(other: Counter) = {
    value< other.value//这里会出现错误
  }
}

Bean属性

可以使用@BeanProperty,修饰字段,将为字段提供setter方法和getter方法。
具体来说会生成四个方法

1. name: String
2. name_ = (newValue: String) :Unit
3. getName()
4. SetName(newValue:String): Unit

此时这个字段就可以被当做JavaBean的字段了

辅助构造器

scala也可以有任意多的辅助构造器

1. 辅助构造器的名称为this
2. 每一个辅助构造器必须以一个先前定义好的其他辅助构造器或主构造器的调用开始
class Person(){
  private var name = ""
  private var age = 0
  def this(name:String){
    this()
    this.name = name
  }
  def this(name:String,age:Int){
    this(name)
    this.age = age
  }
}

主构造器不以this方法定义,而是与类定义交织在一起,主构造器的参数直接放置在类名之后,主构造器会执行类定义中的所有语句。

如果不带val或者var的参数至少被一个方法所使用,他将被升格为字段。

嵌套类

可以在函数中定义函数,也可以在类中定义类

class Network{
  class Member(val name:String){
    val contacts = new ArrayBuffer[Member]()
  }
  private val members = new ArrayBuffer[Member]()

  def join(name:String) = {
    val m = new Member(name)
    members+=m
    m
  }
}
 val chatter = new Network()
    val myFace = new Network
    val member1: chatter.Member = chatter.join("1")
    val member2: chatter.Member = chatter.join("2")
    member1.contacts += member2
    val member3: myFace.Member = myFace.join("3")
    member1.contacts += member3
    //不能将myFace的Member添加到chatter.Member元素缓冲中

怎么解决以上的问题?
1. 将Member类移到Network的伴生对象中
2. 使用类型投影Network#Member,其含义是任何的Network的Member

在内部类中可以使用外部类.this来获取外部类的this引用

6. 对象

单例对象

Scala中没有静态方法也没有静态字段。可以使用object这个语法结构来达到同样的目的。

object Accounts {
  private var lastNumber = 0
  def newUniqueNumber() = {
    lastNumber+=1;
    lastNumber
  }
}
//当需要一个新的账号时,可以使用Accounts.newUniqueNumber来获得

对象的构造器初始化在该对象第一次使用时调用,在本例中Accounts的构造器在Accounts.newUniqueNumber的首次调用时执行。

伴生对象

类和它的伴生对象可以互相访问私有属性,他们必须存在于同一个源文件中。
注意类的伴生对象的功能特性并不在类的作用域内(就是也需要像静态方法一样去访问对象的方法,不能搞特殊)

object Accounts {
  private var lastNumber = 0
  def newUniqueNumber() = {
    lastNumber+=1;
    lastNumber
  }
}

class Accounts{
  val id = Accounts.newUniqueNumber()
}

扩展类或特质

一个Object可以扩展类以及一个或多个特质,其结果是一个扩展了指定类以及特质的类的对象,同时拥有在对象定义中给出的所有特性。

abstract class UndiableAction(val description:String){
  def undo:Unit
}

object DoNothing extends UndiableAction("nothing"){
  override def undo: Unit = {
    println("nothing")
  }
}

apply方法

通常会自定义和使用伴生对象的apply方法

class Account(id:Int){
  
}

object Account{
  def apply(id: Int): Account = new Account(id)
}

应用程序对象

每个程序都需要从main方法开始,这个方法的类型为Array[String] => unit

def main(args: Array[String]): Unit = {

7. 包和引入

包对象

每个包都会有一个包对象,需要在父包中定义,然后在子包中使用。

package com.atmb.test

package object myPackage{
  val defaultName = "name"
}
package myPackage{
  class myPackage{
    val name = defaultName
  }
}

包的可见性

private[me] val name = defaultName

引入

引入全部成员
import java.wat._
可在任何地方引入,import的作用于为该语句块的结尾

def fun(): Unit ={
      import scala.collection.mutable.Map
      val intToInt:collection.mutable.Map[Int,Int] = Map((1, 2))
    }

重命名和隐藏方法

//引入几个成员
import scala.collection.mutable.{Map,ArrayBuffer}
//重命名
import scala.collection.mutable.{Map => myMap,ArrayBuffer}
val intToInt:collection.mutable.Map[Int,Int] = myMap((1, 2))

隐式引入

scala程序会将隐式的以一下代码开始

import java.lang._
import scala._
import Perdef._

8. 继承

扩展类

scala扩展扩展类的方式和Java一样,使用extends关键字

class me{
    def fun(): Unit ={
      import scala.collection.mutable.{Map => myMap,ArrayBuffer}
      val intToInt:collection.mutable.Map[Int,Int] = myMap((1, 2))
    }
  }
  
  class mySon extends me{
    this.fun()
  }

和Java一样,可以将类生命为final,这样它就不能被扩展,还能将单个方法或者字段声明为final,以确保他们不能被重写。

重写方法

重写一个方法必须使用Override关键字

  class mySon extends me{
    override def toString: String  ={
      ""
    }
  }

调用父类的方法和java一样使用super关键字。

类型检查与转换

要测试某个对象是否属于某个给定的类,可以使用isInstanceOf方法,如果指向的是给定参数类或者其子类的对象,则返回true,否则返回False.
如果p不是给定参数类的对象,则o.asInstanceOf[T]将会抛出异常。

如果想要判断是否是该类的对象而不是该类或者该子类的对象。可以使用

private val bool: Boolean = son.getClass == classOf[me]

受保护的字段

可以将字段或者方法声明为protected这样的成员可以被任何子类访问,但不能从其他位置访问。protected[this]的用法与private[this]相似,只能在当前对象中访问。

超类额构造

  class father(name:String){
    
  }
  class son(name:String,fatherName:String) extends father(fatherName){
    
  }

匿名子类

    val person = new son("",""){
      
    }

抽象类

和java一样,可以使用abstract关键字来标记不能被实例化的类,通常因为该类有几个方法没有被完整定义。

无需对抽象方法使用abstract,因为省去方法体就是抽象方法

抽象字段

抽象字段时没有初始值的字段

abstract class son(name:String,fatherName:String) extends father(fatherName){
      val id :Int 
    }

构造顺序与提前定义

class Creature{
  val range = 10
  val env = new Array[Int](range)
}

class Ant extends Creature{
  override val range: Int = 2
}
1. 调用Creature的构造器
2. Creature将range字段设为10
3. Creature构造器初始化env数组,调用range()取值器
4. 该方法被重写已交出(还未初始化的)Ant类的range字段值
5. range方法返回0,(对象被分配空间是所有整形的初始值)
6. env被设为长度为0的数组 
7. Ant构造器继续执行,将其range字段设为2

尽管range字段不是10就是2,但是env被设为了长度为0的数组。不要再构造中依赖val值。

有以下几种解决方式

	1. 将val声明为final
	2. 在超类中将val 声明为lazy,
	3. 在子类中使用提前定义语法

所谓的提前定义可以在超类的构造器执行之前初始化子类的val字段。

class Ant extends {override val range = 2} with Creature

提前定义只能引用之前已有的提前定义,而不能使用类中的其他字段或方法。

Scala类继承方法

在这里插入图片描述
Null类型唯一的实例是null值,可以将null赋值给任何引用,但不能赋值给值类型的变量

???方法被声明为返回类型为Nothing,他从不返回,只是在被调用时,抛出NotImplementError,可以将它用于需要实现的方法。

10 特质

为什么没有多继承

Scala和java一样不允许类从多个超类继承,会引起菱形继承问题。

scala提供特质(trait)而非接口,特质可以同时拥有抽象方法和具体方法,以及状态,而类可以实现多个特质。

当做接口使用的特质

trait Logger{
  def log(msg:String) = ???
}

无须将方法声明为abstract,特质中未被实现的方法默认就是抽象的。

class ConsileLogger extends Logger{//使用extends
   def log(msg: String):Unit  = {//不需要写override,写了也没错
    println(msg)
  }
}

继承多个特质

class ConsileLogger extends Logger with Serializable {
   def log(msg: String):Unit  = {
    println(msg)
  }
}

在scala中类只能有一个父类,但是可以实现多个特质

带有具体实现的特质

在scala中特质中的方法不一定是抽象的

带有特质的对象

abstract class SavingAccount extends Account with Logger{
  def withdraw(amount:Double){
    if(amount>balance){
      log("funds")
    }else{
      log("ok")
    }
  }
}

这个类是抽象的,因为他还不能做任何日志输出的动作,但是可以在构造器中混入一个具体的日志实现器实现

trait ConsoleLogger extends Logger with Serializable { //特质之间可以继承
   def log(msg: String):Unit  = {
    println(msg)
  }
}
 val account = new SavingAccount with ConsoleLogger

叠加在一起的特质

trait ConsoleLogger extends Logger with Serializable {
  def log(msg: String):Unit  = {
    println(msg)
  }
}

trait TimestampLogger extends ConsoleLogger{
  override def log(msg: String): Unit = {
    super.log(msg+"long")
  }
}

trait shortTimestampLogger extends ConsoleLogger{
  override def log(msg: String): Unit = {
    super.log(msg+"short")
  }
}

val account = new SavingAccount with TimestampLogger with shortTimestampLogger
val account1 = new SavingAccount with shortTimestampLogger  with TimestampLogger
account.log("1")
account1.log("1")
<<<
1shortlong
1longshort

执行顺序为从后往前执行,如果需要控制哪一个特质的方法被调用,可以使用super[TimestampLogger].logg()

特质中的具体的字段

特质中的字段你可以是具体的,也可以是抽象的,如果给了初始值,那么字段就是具体的。

对于特质中的每一个具体字段,使用该特质都会获得一个字段预支对应,这些字段不是继承的,只是简单的被添加到了子类当中。

对于父类中的字段,是继承的。

特质中的抽象字段

特质中未被初始化的字段在具体的子类中必须被重写

特质的构造顺序

特质也有构造顺序

  1. 首先调用父类的构造器
  2. 特质构造器在超类构造器之后、类构造器之前执行
  3. 特质由左到右被构造
  4. 在每个特质当中,父特质先被构造
  5. 如果多个特质公有一个父特质,而那个父特质已经被构造,则该父类特质不会被再次构造
  6. 所有特质构造完毕,子类被构造。

初始化特质中的字段

特质不能有构造器参数,每个特质都有一个无参数的构造器

扩展类的特质

特质可以扩展类,这个类将自动成为所有混入该特质的超类。
如果我们的类已经扩展了另一个类,怎么办?只要扩展的类是特质父类的一个子类就可以。

只有抽象方法的特质被简单的变成了一个java接口。

高阶函数

作为值的函数

import math._
val fun = ceil _

ceil函数后边的_指的是这个函数,而不是碰巧忘记了给他传参数(将方法转化为函数需要使用_)

在一个预期需要函数的上下文里使用方法名时,_后缀不是必须的。

val f:(Double) => Double = ceil//不需要下划线

匿名函数

val f = (x:Double) => x*3

无需指定函数名,将其传递给另一个函数

Array(1.0,2.0,3.0,4.0,5.0).map(f)

任何以def定义的都是方法而不是函数

带函数参数的方法

def value(f:Double => Double,value:Double) = f(value)

参数类型推断

如果参数只用到一次,可以使用_代替。

高阶函数

map
foreach

闭包

可以在任何作用域定义函数,包,类甚至是函数中,在函数体内,可以访问到作用域内的任何变量。

SAM转换

还没用到过

柯力化

将原来接受两个参数的函数变成新的接受一个参数的函数的过程,新的函数返回一个以原有第二个参数作为参数的函数。

    def fun(num1:Int)(num2:Int): Int ={
      num1*num2
    }

    val intToInt: Int => Int = fun(2)

集合

模式匹配和样例类

switch

如果没有模式能匹配,将会抛出MatchError,scala模式匹配不会调入下一个分支(不用像java一样显示调用break)

    var ch:Char = 'q'
    ch match {
      case 'b' => println("1")
      case 'c' => println("2")
      case _ => println("default")//匹配以上没有匹配到的结果
    }
//多种情况
    var ch:Char = 'q'
    ch match {
      case 'b' | 'd' => println("1")
      case 'c' => println("2")
      case _ => println("default")
    }

守卫

守卫可以是任何的Boolean条件

    ch match {
      case _ if Character.isDigit(ch) => println("1")
      case 'c' => println("2")
      case _ => println("default")
    }

模式中的变量

如果case关键字后边跟着一个变量名,那么匹配的表达式会被赋值给那个变量。

    ch match {
      case _ if Character.isDigit(ch) => println("1")
      case 'c' => println("2")
      case temp => println(temp)
    }

变量必须以小写字母开头,小写字母开头的常量,必须将其包含在反引号中。

case Pi => {}

类型模式

     ch match {
      case x:Int => x
      case s:String => Integer.parseInt(s)
      case _ =>Integer.MAX_VALUE
    }

匹配数组、列表和元组

    var ch = Array(1,2,3)
    ch match {
      case Array(0) => "" //包含0元素的数组
      case Array(0,rest @ _*) => "1" //任意带有两个元素的数组,并将变量保存到rest
      case _ => "else"
    }

for表达式中的模式

  def main(args: Array[String]): Unit = {
    var a = "b"
    for(i <- 0 to 10 if a == "a"){
      
    }

样例类

case class people(name:String,age:Int)

Option类型

标准库中的Option类型用样例类来表示可能存在也有可能不存在的值。

Map的get方法返回一个Option,如果对于给定的值没有对应的键,则gey返回None。如果有值,就会包在Some返回。可以使用isEmpty或者getOrElse

偏函数

被包在花括号内一组case语句就是一个偏函数,一个并非对所有输入值都有定义的函数

val f: PartialFunction[Char,Int] = {case '+' => 1 ;case '-' => 2}

15注解

什么是注解

注解是那些插入到代码中以便有工具可以对他们进行处理的标签

隐式转换

所谓隐式转换函数指的是以implicit关键字声明的带有单个参数的函数,这样的函数将被自动应用,将值从一种类型转换为另一种类型。

object test {
  def main(args: Array[String]): Unit = {
    
    implicit def int2Fraction(n:Int) = new Fraction(n,1)
    
    val result:Fraction = 3 * new Fraction(1,2)
  }
  class Fraction(num1:Int,num2:Int){
    def *(other:Fraction) = new Fraction(num1*other.num1,num2*other.num2)
  }
}

可以引入局部化以尽量避免不必要的转换发生,

隐式转换的规则

隐式转换可能在一下三种情况下发生

  1. 当表达式的类型与预期的类型不同时:
  2. 当对象访问一个不存在的成员时
  3. 当对象调用某个方法时

希望以后每获得一个技能点就要做好笔记,不然需要用的时候就全忘记了。加油!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值