从事了5年的iOS开发,所做过的项目都是用Object-C语言开发的,中间有自学过一部分Swift,可以说是只学习了一些皮毛吧。公司项目现在用到Swift了,很高兴可以真正的在项目中去应用和学习Swift。为此我将会记录我的Swift学习历程,从零开始系统的总结下Swift的知识点,希望能够对大家有所帮助,以下内容都是从Swift51.com学习的,然后进行了自己的总结。希望能帮助到大家。有任何问题随时接受指教。
前言
Swift是一种安全,快速和互动的编程语言
Swift 通过采用现代编程模式来避免大量常见编程错误:
1.变量始终在使用前初始化
2.检查数组索引超出范围的错误
3.检查整数是否溢出
4.可选值确保明确处理 nil 值
5.内存被自动管理
6.错误处理允许从意外故障控制恢复
初识
一、简单值
1.let:声明常量(在编译的时候并不需要有明确的值,但是只能为它赋值一次)
var:声明变量(使用前初始化)
print("Hello world!")
var a = 42
a = 50
let b = 42
print("\(a) - \(b)") // 50 -42
2.常量或者变量的类型必须和你赋给它们的值一样。然而,你不用明确地声明类型。当你通过一个值来声明变量和常量时,编译器会自动推断其类型。如果初始值没有提供足够的信息(或者没有初始值),那你需要在变量后面声明类型,用冒号分割。
let a: String
var b: String
let c: Double = 70
let d: Float = 1
a = "123"
b = "666"
3.值永远不会被隐式转换为其他类型。如果你需要把一个值转换成其他类型,请显式转换。
let a = "What?"
let b = 88
let ab = a + String(b) // "What?88"
let abNew = "Look \(a) \(b)" // "Look What? 88"
4.有一种更简单的把值转换成字符串的方法:把值写到括号中,并且在括号之前写一个反斜杠(\)
let a = 3
let b = 5
let aLog = "I have \(a)" // "I have 3"
let abLog = "I have \(a + b)" // "I have 8"
let c = 0.6
let d = "haha"
let e = "\(c)d" // "0.6+d"
5.使用三个双引号(""")来包含多行字符串内容。每行行首的缩进会被去除,直到和结尾引号的缩进相匹配。举个
let a = 2
let b = 3
let c = """
I said "I Love \(a)"
And then I said "Your Love \(a + b) "
""" // "I said \"I Love 2\"\nAnd then I said \"Your Love 5 \""
6.使用方括号 [] 来创建数组和字典,并使用下标或者键(key)来访问元素。最后一个元素后面允许有个逗号。数组在添加元素时会自动变大。
var a = ["1", "2", "3", "4", "5"]
a[1] = "8"
a.append("6")
print(a) // ["1", "8", "3", "4", "5", "6"]
var b = [
"key1":"www",
"key2":"qqq"
]
b["key3"] = "eee"
7.使用初始化语法来创建一个空数组或者空字典。
let arr = [String]()
let dict = [String:Float]()
8.如果类型信息可以被推断出来,你可以用 [] 和 [:] 来创建空数组和空字典——比如,在给变量赋新值或者给函数传参数的时候。
a = []
b = [:]
二、控制流
1.使用 if 和 switch 来进行条件操作,使用 for-in、while 和 repeat-while 来进行循环。包裹条件和循环变量的括号可以省略,但是语句体的大括号是必须的。在 if 语句中,条件必须是一个布尔表达式——这意味着像 if score { ... } 这样的代码将报错,而不会隐形地与 0 做对比。
let array = [1,2,3,4,5,6]
var a = 0
for i in array {
if i > 3 {
a += 1;
} else {
a += 3;
}
}
2.你可以一起使用 if 和 let 一起来处理值缺失的情况。这些值可由可选值来代表。一个可选的值是一个具体的值或者是 nil 以表示值缺失。在类型后面加一个问号(?)来标记这个变量的值是可选的。
var a:String? = "Hello"
print(a == nil) // false
var b:String? = "你好"
var c = "nihao"
if let d = b {
c = "nihao,\(d)" // "nihao,你好"
}
3.另一种处理可选值的方法是通过使用 ?? 操作符来提供一个默认值。如果可选值缺失的话,可以使用默认值来代替。
let a: String? = nil
let b: String = "Hello!"
let c = "Hi \(a ?? b)" // Hi Hello!
let a: String? = "111"
let b: String = "Hello!"
let c = "Hi \(a ?? b)" // Hi 111
4.switch支持任意类型的数据以及各种比较操作——不仅仅是整数以及测试相等。运行 switch 中匹配到的 case 语句之后,程序会退出 switch 语句,并不会继续向下运行,所以不需要在每个子句结尾写 break。必须要有default
let a = "111"
switch a {
case "222":
print("222被打印了")
case "333", "555":
print("333或555被打印了")
case let x where x.hasSuffix("11"): // 它将匹配等式的值赋给常量 x
print("X值为:\(x)?")
default:
print("没有任何")
} // X值为:111?
5.使用for-in遍历字典,字典是无序集合
let abb = [
"key1":[1,2,3],
"key2":[5,6,7],
"key3":[8,9,0],
]
var b = 0
for (key, values) in abb {
for value in values {
if value > b {
b = value
}
}
}
print(b) // 9 说白了这个就是取出最大值
6.使用 while 来重复运行一段代码直到条件改变。循环条件也可以在结尾,保证能至少循环一次。
var a = 2
while a < 100 {
a *= 2
}
print(a) // 128
7.使用repeat循环
var b = 2
repeat {
b *= 2
} while b < 100
print(b) // 128
8.循环中使用..<来表示下标范围,包含的范围是...三个点
var c = 0
for i in 0..<4 {
c += i
}
print(c) // 6
三、函数和闭包
闭包:就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
1.使用 func 来声明一个函数,使用名字和参数来调用函数。使用 -> 来指定函数返回值的类型
func eat (who: String, what: String) -> String {
return "\(who) eat \(what)"
}
eat(who: "I", what: "foods")
print(eat(who: "I", what: "foods")) // I eat foods
2.默认情况下,函数使用它们的参数名称作为它们参数的标签,在参数名称前可以自定义参数标签,或者使用 _ 表示不使用参数标签。(效果就是无参数标签了)
func eat (_ who: String, what: String) -> String {
return "\(who) eat \(what)"
}
eat("I", what: "foods")
print(eat("I", what: "foods"))
3.使用元组来生成复合值,比如让一个函数返回多个值。该元组的元素可以用名称或数字来获取。
func getSum(numArray:[Int]) -> (min:Int, max:Int, sum:Int) {
var min = numArray[0]
var max = numArray[0]
var sum = 0
for num in numArray {
if num > max {
max = num
} else if num < min {
min = num
}
sum += num
}
return (min, max, sum)
}
let result = getSum(numArray: [1, 2, 3])
print(result.min) // 1
print(result.max) // 3
print(result.sum) // 6
print(result.1) // 3
print(result.2) // 6
4.函数可以嵌套。被嵌套的函数可以访问外侧函数的变量,你可以使用嵌套函数来重构一个太长或者太复杂的函数。
func aMethod() -> Int {
var a = 10
func bMethod() {
a += 5
}
bMethod()
return a
}
aMethod()
print(aMethod()) // 15
5.函数是第一等类型,这意味着函数可以作为另一个函数的返回值。
func aMethod() -> ((Int) -> Int) {
func bMethod(num: Int) -> Int {
return 1 + num
}
return bMethod
}
var a = aMethod()
print(a) // (Function)
a(7)
print(a(7)) // 8
6.函数也可以当做参数传入另一个函数。
func aMethod(array:[Int], method:(Int) -> Bool) -> Bool {
for num in array {
if method(num) {
return true
}
}
return false
}
func bMethod(num: Int) -> Bool {
return num < 10
}
var numbers = [1, 3, 5, 2, 0]
aMethod(array: numbers, method: bMethod)
print(aMethod(array: numbers, method: bMethod)) // ture
7.函数实际上是一种特殊的闭包:它是一段能之后被调取的代码。闭包中的代码能访问闭包作用域中的变量和函数,即使闭包是在一个不同的作用域被执行的——你已经在嵌套函数的例子中看过了。你可以使用 {} 来创建一个匿名闭包。使用 in 将参数和返回值类型的声明与闭包函数体进行分离。
numbers.map({
(number: Int) -> Int in
let result = 3 * number
return result
})
print(numbers.map({
(number: Int) -> Int in
let result = 3 * number
return result
})) // [3, 9, 15, 6, 0]
8.有很多种创建更简洁的闭包的方法。如果一个闭包的类型已知,比如作为一个代理的回调,你可以忽略参数,返回值,甚至两个都忽略。单个语句闭包会把它语句的值当做结果返回。
let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers) // // [3, 9, 15, 6, 0]
9.你可以通过参数位置而不是参数名字来引用参数——这个方法在非常短的闭包中非常有用。当一个闭包作为最后一个参数传给一个函数的时候,它可以直接跟在圆括号后面。当一个闭包是传给函数的唯一参数,你可以完全忽略圆括号。
let sortedNumbers = numbers.sorted { $0 > $1 } // 从大到小排序
print(sortedNumbers) // [5, 3, 2, 1, 0]
四、对象和类
1.使用 class 和类名来创建一个类。类中属性的声明和常量、变量声明一样,唯一的区别就是它们的上下文是类。同样,方法和函数声明也一样。要创建一个类的实例,在类名后面加上括号。使用点语法来访问实例的属性和方法。
class Person {
var weight = 0 // 体重
func logWeight() -> String {
return "体重\(weight)斤"
}
}
var person1 = Person()
person1.weight = 80
var logWeightMethod = person1.logWeight()
print(logWeightMethod) // 体重80斤
2.这个版本的 Person 类缺少了一些重要的东西:一个构造函数来初始化类实例。使用 init 来创建一个构造器。
当你创建实例的时候,像传入函数参数一样给类传入构造器的参数。每个属性都需要赋值——无论是通过声明(就像 numberOfSides)还是通过构造器(就像 name)。
3.如果你需要在对象释放之前进行一些清理工作,使用 deinit 创建一个析构函数。
子类的定义方法是在它们的类名后面加上父类的名字,用冒号分割。创建类的时候并不需要一个标准的根类,所以你可以根据需要添加或者忽略父类。
子类如果要重写父类的方法的话,需要用 override 标记——如果没有添加 override 就重写父类方法的话编译器会报错。编译器同样会检测 override 标记的方法是否确实在父类中。
4.除了简单的存储属性,还有使用 getter 和 setter 的计算属性。
5.如果你不需要计算属性,但是仍然需要在设置一个新值之前或者之后运行代码,使用 willSet 和 didSet。写入的代码会在属性值发生改变时调用,但不包含构造器中发生值改变的情况。比如,下面的类确保三角形的边长总是和正方形的边长相同。
6.处理变量的可选值时,你可以在操作(比如方法、属性和子脚本)之前加 ?。如果 ? 之前的值是 nil,? 后面的东西都会被忽略,并且整个表达式返回 nil。否则,可选值会被解包,之后的所有代码都会按照解包后的值运行。在这两种情况下,整个表达式的值也是一个可选值。
// Person类
class Person {
var name: String // 姓名
var weight = 0 // 体重
init(name:String) {
self.name = name
}
func logName() -> String {
return "姓名\(name)"
}
func logWeight() -> String {
return "体重\(weight)斤"
}
}
var person1 = Person(name: "小黄") // 或者用:Person.init(name: "小黄黄")
person1.weight = 80
var logNameMethod = person1.logName()
var logWeightMethod = person1.logWeight()
print(logNameMethod) // 姓名小黄
print(logWeightMethod) // 体重80斤
// Teacher类
class Teacher: Person {
var type: Int // 类型:1.语文老师 2.数学老师 3.英语老师
init(type: Int, name: String) {
self.type = type
super.init(name: name)
weight = 90
}
func logType() -> String {
return "\(type)"
}
override func logWeight() -> String {
return "Teacher类重写体重:\(weight)斤"
}
}
let teacher1 = Teacher.init(type: 1, name: "大黄")
teacher1.logName()
teacher1.logWeight()
teacher1.logType()
print(teacher1.logName()) // 姓名大黄
print(teacher1.logWeight()) // 重写体重:90斤
print("teacher1类型:" + teacher1.logType()) // 1
// Student类
class Student: Person {
var grade: Int = 0
init(grade: Int, name: String) {
self.grade = grade // 设置子类声明的属性值
super.init(name: name) // 调用父类的构造器
weight = 100 // 改变父类定义的属性值。其他的工作比如调用方法、getters 和 setters 也可以在这个阶段完成
}
var level: Int {
get {
return grade * 2
}
set {
grade = newValue / 2
}
}
override func logWeight() -> String {
return "Student类重写体重:\(weight)斤"
}
}
var student1 = Student(grade: 3, name: "黄黄")
print(student1.level) // 6
student1.level = 5
print(student1.level) // 4
print(student1.grade) // 2
print(student1.logName()) // 姓名黄黄
print(student1.logWeight()) // Student类重写体重:100斤
// Principal类校长
class Principal {
var student: Student {
willSet {
student.grade = newValue.grade
}
}
var teacher: Teacher {
willSet {
teacher.type = newValue.type
}
}
init(size: Double, name: String) {
teacher = Teacher(type: 3, name: "黄老师")
student = Student(grade: 1, name: "黄学生")
}
}
var principal = Principal(size: 2, name: "我是校长")
print(principal.teacher.type) // 3
print(principal.student.grade) // 1
principal.teacher = Teacher(type: 1, name: "我是老师")
print(principal.student.grade) // 1
let optionalTeacher: Teacher? = Teacher(type: 1, name: "老师")
let type = optionalTeacher?.type
六、枚举和结构体
1.使用 enum 来创建一个枚举。就像类和其他所有命名类型一样,枚举可以包含方法。默认情况下,Swift 按照从 0 开始每次加 1 的方式为原始值进行赋值,不过你可以通过显式赋值进行改变。在上面的例子中,Ace 被显式赋值为 1,并且剩下的原始值会按照顺序赋值。你也可以使用字符串或者浮点数作为枚举的原始值。使用 rawValue 属性来访问一个枚举成员的原始值。
enum XHEnum: Int {
case ace = 1
case two, three, four, five, six, seven, eight, nine, ten
case jack, queen, king
func simpleDescription() -> String {
switch self {
case .ace:
return "ace"
case .jack:
return "jack"
case .queen:
return "queen"
case .king:
return "king"
default:
return String(self.rawValue)
}
}
}
let ace = XHEnum.ace
let aceRawValue = ace.rawValue // rawValue原始值
if aceRawValue == 1 {
print("Yes")
}
if let convertedRank = XHEnum(rawValue: 3) { // 使用 init?(rawValue:) 初始化构造器来从原始值创建一个枚举实例。如果存在与原始值相应的枚举成员就返回该枚举成员,否则就返回 nil。
let threeDescription = convertedRank.simpleDescription()
}
enum Animal: String {
case dog = "dog"
case cat = "cat"
}
let myAnimal = Animal.dog
if myAnimal.rawValue == "dog" { // 使用 rawValue 使语句更具可读性
print("myAnimal is a dog!")
}
if myAnimal == .dog { // 不使用 rawValue
print("myAnimal is a dog!")
}
2.枚举的关联值是实际值,并不是原始值的另一种表达方法。实际上,如果没有比较有意义的原始值,你就不需要提供原始值。注意在下面的例子中用了两种方式引用 hearts 枚举成员:给 hearts 常量赋值时,枚举成员 Suit.hearts 需要用全名来引用,因为常量没有显式指定类型。在 switch 里,枚举成员使用缩写 .hearts 来引用,因为 self 的值已经是一个 suit 类型。在任何已知变量类型的情况下都可以使用缩写。
enum Suit {
case spades, hearts, diamonds, clubs
func simpleDescription() -> String {
switch self {
case .spades:
return "spades"
case .hearts:
return "hearts"
case .diamonds:
return "diamonds"
case .clubs:
return "clubs"
}
}
}
let hearts = Suit.hearts
let heartsDescription = hearts.simpleDescription()
3.如果枚举成员的实例有原始值,那么这些值是在声明的时候就已经决定了,这意味着不同枚举实例的枚举成员总会有一个相同的原始值。当然我们也可以为枚举成员设定关联值,关联值是在创建实例时决定的。这意味着同一枚举成员不同实例的关联值可以不相同。你可以把关联值想象成枚举成员实例的存储属性。例如,考虑从服务器获取日出和日落的时间的情况。服务器会返回正常结果或者错误信息。
enum ServerResponse {
case result(String, String)
case failure(String)
}
let success = ServerResponse.result("6.00 pm", "8:00 pm")
let failure = ServerResponse.failure("Out of cheese")
switch success {
case let ServerResponse.result(sunrise, sunset):
print("Sunrise is at \(sunrise) and sunset is at \(sunset)")
case let .failure(message):
print("Failure... \(message)")
default:
print("Other")
}
4.使用 struct 来创建一个结构体。结构体和类有很多相同的地方,包括方法和构造器。它们之间最大的一个区别就是结构体是传值,类是传引用。
struct Card {
var rank: XHEnum
var suit: Suit
func simpleDescription() -> String {
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
}
}
let threeOfSpades = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
七、协议和扩展
扩展:mutating可改变,使用场景有结构体、枚举类型中声明修饰方法,extension、protocol修饰方法。swift 中struct,enum 均可以包含类方法和实例方法,swift官方是不建议在struct,enum 的普通方法里修改属性变量,但是在func 前面添加 mutating 关键字之后就可以方法内修改。对于protocol 方法也是适用的,mutating 可以修饰的代理方法,如果,struct,enum,class 实现协议之后可以在对应的 mutating 代理方法内修改本身的属性变量.(class 不影响,因为属性变量对于类的类方法,实例方法 是透明的,即随时都可以改变)
1.使用 protocol 来声明一个协议。
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust() // mutating:可改变
}
2.类、枚举和结构体都可以遵循协议。注意声明 SimpleStructure 时候 mutating 关键字用来标记一个会修改结构体的方法。SimpleClass 的声明不需要标记任何方法,因为类中的方法通常可以修改类属性(类的性质)。
class SimpleClass: ExampleProtocol {
var simpleDescription: String = "A very simple class"
var anotherProperty: Int = 69105
func adjust() {
simpleDescription += " Now 100% adjusted."
}
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
struct SimpleStructure: ExampleProtocol {
var simpleDescription: String = "A simple structure"
mutating func adjust() {
simpleDescription += " (adjusted)"
}
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription
3.使用 extension 来为现有的类型添加功能,比如新的方法和计算属性。你可以使用扩展让某个在别处声明的类型来遵守某个协议,这同样适用于从外部库或者框架引入的类型。
extension Int: ExampleProtocol {
var simpleDescription: String {
return "The number \(self)"
}
mutating func adjust() {
self += 42
}
}
print(7.simpleDescription) // The number 7
4.你可以像使用其他命名类型一样使用协议名,比如创建一个有不同类型但是都实现一个协议的对象集合。当你处理类型是协议的值时,协议外定义的方法不可用。即使 protocolValue 变量运行时的类型是 simpleClass ,编译器还是会把它的类型当做 ExampleProtocol。这表示你不能调用在协议之外的方法或者属性。
let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
// print(protocolValue.anotherProperty) // 去掉注释可以看到错误,类型“ExampleProtocol”的值没有成员“anotherProperty”
八、错误处理
// 1.使用采用Error协议的类型来表示错误。
enum PrinterError: Error {
case outOfPaper // 纸用完了
case noToner // 没有碳粉了
case onFire // 着火了
}
// 2.使用throw来抛出一个错误,使用throws 来表示一个可以抛出错误的函数。如果在函数中抛出一个错误,这个函数会立刻返回并且调用该函数的代码会进行错误处理。
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}
return "Job sent"
}
// 3.do-catch错误处理,在 do 代码块中,使用try来标记可以抛出错误的代码。在 catch 代码块中,除非你另外命名,否则错误会自动命名为 error 。
do {
let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
print(printerResponse)
} catch {
print(error)
}
do {
let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
print(printerResponse)
} catch PrinterError.onFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}
// 4.try?错误处理,将结果转换为可选的。如果函数抛出错误,该错误会被抛弃并且结果为 nil。否则,结果会是一个包含函数返回值的可选值。
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
// 5.使用 defer 代码块来表示在函数返回前,函数中最后执行的代码。无论函数是否会抛出错误,这段代码都将执行。使用 defer,可以把函数调用之初就要执行的代码和函数调用结束时的扫尾代码写在一起,虽然这两者的执行时机截然不同。
var fridgeIsOpen = false // 冰箱
let fridgeContent = ["milk", "eggs", "leftovers"]
func fridgeContains(_ food: String) -> Bool {
fridgeIsOpen = true
print("defer执行顺序---1")
defer {
fridgeIsOpen = false
print("defer执行顺序---2")
}
let result = fridgeContent.contains(food)
print("defer执行顺序---3")
return result
}
fridgeContains("banana")
print("fridgeIsOpen值:\(fridgeIsOpen)")
九、泛型
泛型:把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型。泛型设计原则:只要在编译时期没有出现警告,那么运行时期就不会出现ClassCastException类转换异常。ArrayList<E>中的E称为类型参数变量。ArrayList<Integer>中的Integer称为实际类型参数。整个称为ArrayList<E>泛型类型。整个ArrayList<Integer>称为参数化的类型ParameterizedType。
拓展:早期Java是使用Object来代表任意类型的,但是向下转型有强转的问题,这样程序就不太安全。首先,我们来试想一下:没有泛型,集合会怎么样,Collection、Map集合对元素的类型是没有任何限制的。本来我的Collection集合装载的是全部的Dog对象,但是外边把Cat对象存储到集合中,是没有任何语法错误的。把对象扔进集合中,集合是不知道元素的类型是什么的,仅仅知道是Object。因此在get()的时候,返回的是Object。外边获取该对象,还需要强制转换,有了泛型以后:代码更加简洁【不用强制转换】、程序更加健壮【只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常】、可读性和稳定性【在编写集合的时候,就限定了类型】
// 1.在尖括号里写一个名字来创建一个泛型函数或者类型。
func makeArray<Item>(repating item: Item, numberOfTimes: Int) -> [Item] { // repating:涉及、关联
var result = [Item]()
for _ in 0..<numberOfTimes {
result.append(item)
}
return result
}
makeArray(repating: "knock", numberOfTimes: 3)
print(makeArray(repating: "knock", numberOfTimes: 3)) // ["knock", "knock", "knock"]
// 2.可以创建泛型函数、方法、类、枚举和结构体。
enum OptionalValue<Wrapped> { // 可选值覆盖(重新实现 Swift 标准库中的可选类型)
case none
case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)
print(possibleInteger)
// 3.在类型名后面使用 where 来指定对类型的一系列需求,比如,限定类型实现某一个协议,限定两个类型是相同的,或者限定某个类必须有一个特定的父类。
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool where T.Element: Equatable, T.Element == U.Element // common共同的常见的,elements原理元素,sequence序列顺序是一个集合协议,lhs等式左边,rhs等式右边,Equatable公平的公正的
{
for lhsItem in lhs {
for rhsItem in rhs {
if lhsItem == rhsItem {
return true
}
}
}
return false
}
anyCommonElements([1, 2, 3], [3])
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
//遍历,由于明确了类型.我们可以增强for
for (String s : list) {
System.out.println(s);
}
遗留问题:
Playground
网友评论