3.1類和構造方法
class Bird{
val weight:Double = 500.0
val color:String = "blue"
val age:Int = 1
fun fly(){}//全局可見
}
///反編譯後的java代碼
public final class Bird{
private final double weight = 500.0
@NotNull
private final String color = "blue"
private final Int age = 1
public final double getWeight(){
return this.weight
}
@NotNull
public final String getColor(){
return this.color
}
public final Int getAge(){
return this.age
}
public final void fly(){}
}
不同點:
1不可變的屬性成員。利用java的final修飾符實現不可變的屬性成員。
2屬性默認值。因爲java都有默認值(int爲0,引用類型爲null),所以在聲明屬性的時候我們不需要指定默認值。而在Kotlin中,除非顯示地聲明延遲初始化,否則就要指定屬性的默認值。
3不同的可訪問修飾符。Kotlin類中的成員默認是全局可見的,而java默認是包作用域,因此在java版本中要用public修飾符才能達到相同的效果。
接口:
接口方法的默認實現使得我們在向接口中新增方法的時候,之前繼承過該接口的類則可以不需要實現這個新方法。
public interface Fly{
public String kind()
default public void fly(){//接口方法的默認實現,java8引入
System.out.println("i can fly")
}
}
kotlin的寫法
interface Fly{
val speed:Int
fun kind()
fun fly(){
println("i can fly")
}
}
支持抽象屬性(如上面的speed),然而因爲kotlin是支持java6的,那麼他是如何支持這種行爲的呢?轉爲java代碼如下:
public interface Fly{
int getSpeed();
void kind();
void fly();
public static final class DefaultImpls{
public static void fly(Fly $this){
String var1 = "i can fly"
System.out.println(var1)
}
}
}
通過定義一個DefaultImpls靜態內部類,來提供fly方法的默認實現,同時雖然kotlin接口支持屬性聲明,然而他在java源碼中是通過一個get方法來實現的。在接口中屬性並不能像java那樣直接被賦值一個常量。
如下代碼是錯誤的:
interface Fly{
val height = 1000
//Property initializers are not allowed in interfaces
}
kotlin提供了另一種方式實現這種效果
interface Fly{
val height
get() = 1000
}
接口中的普通屬性,同方法一樣,若沒有指定默認行爲,則在接口的實現類中必須多該屬性進行初始化。
java通過類的構造方法重載實現不同參數組合new對象。不同組合實現其的構造方法非常多;每個構造方法中存在相同賦值操作存在冗餘。
kotlin採用新的構造方法
class Bird(val weight:Double = 0.00,val age:Int = 0,val color:String = "blue")
//可以省略{}
val b1 = Bird(color = "black")
val b2 = Bird(100.0,1,"black")
//val b3 = Bird(100.0,"black")//!!必須按照順序賦值
構造方法中可以沒有var和val,帶上他們之後接等價於在類的內部聲明瞭一個同名的屬性。
class Bird(var name:String,var age:Int ){
val weight = 1000;
}
等價於
class Bird(name:String,age:Int){
var name:String? = null
var age :Int = 0
val weight = 1000;
init{
this.name = name
this.age = age
}
}
kotlin引入了一種叫做init語句塊的語法,屬於構造方法的一部分。
kotlin規定所有類中所有非抽象屬性成員都必須在對象創建時被初始化值。
延遲初始化:by lazy 和lateinit
class Bird(){
val sex:String by lazy{
"male"
}
fun print(){
println(sex)
}
}
by lazy特點:
變量必須是應用不可變的,而不能通過var聲明
被首次調用時,纔會執行賦值操作,一旦被賦值之後將不能被更改。(後續返回記錄的結果,還會默認加上同步鎖).
class Bird(){
val sex:String by lazy(LazyThreadSafetyMode.PUBLICATION){
"male"
}
...
}
lateinit主要用於var聲明的變量,然而他不能用於基本數據類型,如Int,Long等我們需要用Integer這種包裝類作爲替代。
class Bird(){
lateinit val sex:String
fun print(){
this.sex =" male"
println(sex)
}
}
主從構造方法:
通過constructor方法定義了一個新的構造方法,被稱爲從構造方法。相應的我們熟悉的在類外部定義的構造方法叫做主構造方法。每個類最多存在一個主構造方法和多個從構造方法,如果柱構造方法存在註解或可見性修飾符,也必須像從構造方法一樣加上constructor關鍵字。
每個從構造方法由兩部分組成,對其他構造方法的委託,花括號包裹的代碼塊。
執行順序上,主構造器>init代碼塊>次構造器
/**
* kotlin 企鵝類
*/
class Penguin {
var weight:Int=100 //體重
var age:Int=0
var name:String?=null
//空參次構造方法,如果沒有邏輯,可以省略大括弧
constructor()
//有參次構造方法,如果沒有邏輯,可以省略括弧,不過有參的空構造方法是沒有意義的
//有參次構造器
constructor(name:String,age:Int){
this.name=name
this.age=age
}
}
當主構造器和次構造器同時存在時,次構造器必須直接或者間接調用主構造器
class Penguin constructor(name: String) {
var weight:Int=100 //體重
var age:Int=0
var name:String?=null
//空參次構造器,調用了下面的次要構造方法
constructor():this("奔波兒霸",10)
//有參次構造器,調用了主構造方法
constructor(name:String,age:Int):this(name){
this.name=name
this.age=age
}
}
與java相似,當我們不聲明任何構造方法時,Kotlin編譯器會默認給我們增加一個空參構造方法
class Penguin constructor() {
var weight:Int=100 //體重
var age:Int=0
var name:String?=null
}
3.2不同的訪問控制原則
kotlin中沒有個採用java的extends 和implements關鍵詞,而是用:來代替類的繼承和接口的實現
由於kotlin中的類和方法默認是不可被繼承或重寫的,所以必須加上open修飾符
open class Bird(){
open fun fly(){
println("i can fly")
}
}
class Penguin:Bird(){
override fun fly(){
println("i can not fly")
}
}
*子類應該避免重寫父類的非抽象方法--一旦父類方法變化,子類會出錯,李氏替換原則
kotlin中類、方法和屬性默認加了final修飾符
- 如果你不指定任何可見性修飾符,默認爲
public
,這意味着你的聲明將隨處可見; - 如果你聲明爲
private
,它只會在聲明它的文件內可見; - 如果你聲明爲
internal
,它會在相同模塊內隨處可見; -
protected
不適用於頂層聲明。
private 意味着只在這個類內部(包含其所有成員)可見;
protected—— 和 private一樣 + 在子類中可見。
internal —— 能見到類聲明的 本模塊內 的任何客戶端都可見其 internal 成員;
public —— 能見到類聲明的任何客戶端都可見其 public 成員。
3.3解決多繼承的問題
接口實現多繼承
用super關鍵字指定繼承哪個父類接口的方法:
fun main() {
val b = Bird()
println(b.kind())//[Flyer] flying animals
}
interface Flyer{
fun kind() = "[Flyer] flying animals"
}
interface Animals{
fun kind() = "[Animals] flying animals"
}
class Bird():Flyer,Animals{
override fun kind () = super<Flyer>.kind()
//或者主動覆蓋父類接口
//override fun kind () = "a flying Bird"
}
*kotlin編譯器自動幫我們生成了getter和setter方法。
1用val聲明的屬性將只有getter方法,因爲它不可修改;用var修飾的屬性將同時擁有getter和setter方法。
2用private修飾的屬性編譯器會省略getter和setter方法,因爲類外部已經無法訪問他了,這兩個方法也就沒意義了。
內部類解決多繼承問題的方案
java通過在內部類語法上增加一個static關鍵詞,把它變成嵌套類;kotlin默認是嵌套類,必須加上inner關鍵字纔是一個內部類,也就是說可以把靜態內部類看成嵌套類。
內部類包含着對其外部類實例的引用,在內部類中可以使用外部類中的屬性。
fun main() {
val b = Bird()
b.kindFly()
}
open class Flyer{
fun kind() = println("[Flyer] flying animals")
}
open class Animals{
fun kind() = println("[Animals] flying animals")
}
class Bird(){
fun kindFly(){
FlyerC().kind()
}
fun kindAnimals(){
AnimalsC().kind()
}
private inner class FlyerC:Flyer()
private inner class AnimalsC:Animals()
}
使用委託代替多繼承:by 關鍵字
fun main() {
val flyer = Flyer()
val animal = Animal()
val b = Bird(flyer,animal)
b.fly()
b.eat()
}
class Bird(flyer:Flyer,animal:Animal):CanFly by flyer,CanEat by animal{}
interface CanFly{
fun fly()
}
interface CanEat{
fun eat()
}
open class Flyer:CanFly{
override fun fly(){
println("i can cly")
}
}
open class Eater:CanEat{
override fun eat(){
println("i can eat")
}
}
open class Animal :CanEat{
override fun eat(){
println("Animal can eat")
}
}
//
i can cly
Animal can eat
委託使使用時更加直觀,比如需要繼承A,委託對象是BC,具體調用時不是A.B.method()而是A.method()。
真正的數據類 data class
data class Bird(var weight:Double,var age:Int)
一行代碼就擁有了getter/setter,equals,hashcode,構造函數,還有兩個特別的方法copy和componentN
*data class 之前不能用abstract、open、inner、sealed修飾
3.5從static到object
伴生對象companion object
伴生是相對一個類而言,意爲伴隨某個類的對象,因此伴生對象跟java中static修飾效果性質一樣,全局只有一個單例。他需要聲明在類的內部,在類被裝載時會被初始化。
fun main() {
println("Hello, world!!!")
val prize = Prize("紅包",10,Prize.TYPE_REDPACK)
println(Prize.isRedPack(prize))
}
class Prize(val name:String,val count:Int,val type:Int){
companion object{
val TYPE_REDPACK = 0
val TYPE_COUPON = 1
fun isRedPack(prize :Prize):Boolean{
return prize.type == TYPE_REDPACK
}
}
}
伴生對象也是實現工廠方法模式的一種思路
fun main() {
val redpackPrize = Prize.newRedPackPrize("紅包",10)
val couponPrize = Prize.newCouponPrize("代金券",10)
val commonPrize = Prize.defaultCommonPrize()
}
class Prize(val name:String,val count:Int,val type:Int){
companion object{
val TYPE_COMMON = 1
val TYPE_REDPACK = 2
val TYPE_COUPON = 3
val defaultCommonPrize = Prize("普通獎品",10,Prize.TYPE_COMMON)
fun newRedPackPrize(name:String,count:Int) = Prize(name,count,Prize.TYPE_REDPACK)
fun newCouponPrize(name:String,count:Int) = Prize(name,count,Prize.TYPE_COUPON)
fun defaultCommonPrize() = defaultCommonPrize
}
}
天生的單例object
可以直接用它來實現單例
object DatabaseConfig{
var host :String = "127.0.0.1"
var post :int= 3306
}
由於object全局聲明的對象只有一個,所以他並不用語法上的初始化,甚至都不需要構造方法。
他還有一個作用就是替代java中的匿名內部類
List <String> list = ArrayList.asList("red","ss","cc")
Collections.sort(list,new Comparator<String>(){
@Override
public int compare(String s1,String s2){
if(s1==null)
return -1;
if(s2==null)
return 1;
return s1.cpmpareTo(s2)
}
})
//利用object表達式對它進行改善
val comparator = object:Compararor<String>{
override fun compare(s1:String?,s2:String?):Int{
if(s1==null)
return -1
else if(s2 == null)
return 1
return s1.compareTo(s2)
}
}
Collections.sort(list,comparator)
優點:
object表達式可以賦值給一個變量,重複使用時減少很多代碼;
object可以繼承和實現接口,匿名內部類只能繼承和實現一個接口,而object卻沒有這個限制。
用於代替匿名內部類的object表達式在運行中不像我們在單例中那樣全集只有一個對象,而是每次運行時都會生成一個對象
當匿名內部類使用的類的接口只需要實現一個方法時,使用lambda表達式更適合,當匿名內部類有多個方法實現的時候,使用object表達式更加合適。
val comparator = Compararor<String>{s1,s2->
if(s1==null)
return @Comparator-1
else if(s2 == null)
return @Comparator1
return s1.compareTo(s2)
}
Collections.sort(list,comparator)