Java设计模式 ——— 工厂模式

简单工厂模式

简单工厂模式的模型非常简单,定义一个抽象类及抽象方法,具体产品类继承抽象类,在简单工厂类中提供具体产品,供给客户端使用

例如,一家餐馆,可以提供多种食品,供用户选择:

  • 定义抽象类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    抽象类
    */
    public abstract class SimpleFood {
    /**
    * 提供食物
    */
    public abstract void food();
    }
  • 提供面条–具体产品类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * 具体产品类
    */
    public class Noodles extends SimpleFood {
    @Override
    public void food() {
    System.out.println("面条");
    }
    }
  • 提供米饭–具体产品类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * 具体产品类
    */
    public class Rice extends SimpleFood {
    @Override
    public void food() {
    System.out.println("米饭");
    }
    }
  • 准备工作结束,创建餐馆–简单工厂类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class SimpleFoodFactory {
    public static final int TYPE_NOODLES = 1;//面条
    public static final int TYPE_RICE = 2;//米饭
    public static SimpleFood createFood(int type) {
    switch (type) {
    case TYPE_NOODLES:
    return new Noodles();
    case TYPE_RICE:
    return new Rice();
    default:
    return new Rice();
    }
    }
    }
  • 客人点餐,指明要吃的食物,只要有原料,就可以提供:

    1
    2
    SimpleFood food = SimpleFoodFactory.createFood(SimpleFoodFactory.TYPE_RICE);
    food.food();

工厂方法模式

这个和简单工厂有区别,简单工厂模式只有一个工厂,工厂方法模式对每一个产品都有相应的工厂。

同样是一家餐馆,可以提供食物

  • 创建基类,声明创建食物的方法

    1
    2
    3
    public abstract class Food {
    public abstract void createFood();
    }
  • 声明工厂类

    1
    2
    3
    public abstract class FoodStore {
    public abstract Food createFood(String type);
    }
  • 构建两个窗口,一个卖面工厂,一个卖盖饭工厂

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class NoodlesStore extends FoodStore {
    @Override
    public Food createFood(String type) {
    if (type.equals("lznoodles")) {
    return new LZNoodles();
    } else if (type.equals("cqnoodles")) {
    return new CQNoodles();
    } else return null;
    }
    }
    public class RiceStore extends FoodStore {
    @Override
    public Food createFood(String type) {
    if (type.equals("yuxiangrousi")) {
    return new YXRSRice();
    } else if (type.equals("huiguorou")) {
    return new HGRRice();
    } else return null;
    }
    }
  • 客户端,点餐:

    1
    2
    3
    4
    5
    RiceStore store = new RiceStore();
    Food food = store.createFood("YXRS");
    NoodlesStore noodlesStore = new NoodlesStore();
    Food food1 = noodlesStore.createFood("CQNoodles");

抽象工厂模式:

定义:为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。

现在餐馆不仅有食物,还有饮料,提供饮料抽象类:

1
2
3
4
5
6
public abstract class IDrinks {
/**
* 饮料价格
*/
public abstract void prices();
}

  • 具体产品类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class ColaDrinks extends IDrinks {
    @Override
    public void prices() {
    System.out.println("可乐三块五");
    }
    }
    public class WaterDrinks extends IDrinks {
    @Override
    public void prices() {
    System.out.println("开水免费!");
    }
    }
  • 为食物和饮料创建总工厂

    1
    2
    3
    4
    public abstract class RestaurantFactory {
    public abstract Food getFood(String type);
    public abstract IDrinks getDrink(String price) ;
    }
  • 具体的食物工厂

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class FoodFactory extends RestaurantFactory {
    @Override
    public Food getFood(String type) {
    if (type.equals("yuxiangrousi")) {
    return new YXRSRice();
    } else if (type.equals("huiguorou")) {
    return new HGRRice();
    } else if (type.equals("lznoodles")) {
    return new LZNoodles();
    } else if (type.equals("cqnoodles")) {
    return new CQNoodles();
    } else return null;
    }
    @Override
    public IDrinks getDrink(String price) {
    return null;
    }
    }
  • 饮料工厂

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class DrinkFactory extends RestaurantFactory {
    @Override
    public Food getFood(String type) {
    return null;
    }
    @Override
    public IDrinks getDrink(int price) {
    if(price < 3 ){
    return new WaterDrinks();
    }else if (price >= 3){
    return new ColaDrinks();
    }
    return null;
    }
    }

Android进阶 ——— Android官方架构组件Lifecycle

初探

Lifecycle是Android官方推出的架构组件,在谷歌开发者官微发布的正式发布 Android 架构组件 1.0 稳定版 | 附带中文介绍视频
简明扼要的阐述了推出的架构组件的作用及使用,也可以查看Android开发者官网

先来看看架构组件的第一部分Lifecycle。
在长篇大论之前,先要了解它的作用,为什么谷歌要推出Lifecycle?

就是为了解决常见架构模式中,声明周期管理的痛点,例如MVP模式:
为了管理声明周期:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface IPresenter {
void onCreate();
void onStart();
void onResume();
void onPause();
void onStop();
void onDestroy();
}

继承Ipresenter接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class MainPresenter implements IPresenter {
public void xxx(){
}
@Override
public void onCreate() {
}
@Override
public void onStart() {
}
@Override
public void onResume() {
}
@Override
public void onPause() {
}
@Override
public void onStop() {
}
@Override
public void onDestroy() {
}
}

Activity或Fragment中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* Created by dai
* Created time 2018/8/20
* function:lifecycle.example.com.lifecycleexample
*/
public class MainActivity extends AppCompatActivity {
private MainPresenter presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
presenter = new MainPresenter();
presenter.onCreate();
}
@Override
protected void onStart() {
super.onStart();
presenter.onStart();
}
@Override
protected void onResume() {
super.onResume();
presenter.onResume();
}
@Override
protected void onPause() {
super.onPause();
presenter.onPause();
}
@Override
protected void onStop() {
super.onStop();
presenter.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.onDestroy();
}
}

需要在Activity的每个声明周期中,依次调用接口IPresenter定义的方法,无疑影响开发效率,更麻烦的是,如果忘记调用声明周期的方法,例如APP被销毁,presenter中依然调用UI层,这必然产生严重后果。

使用

Lifecycle就是为了解决这种重复工作的。先来看看如何使用:
导入Java8依赖

1
implementation "android.arch.lifecycle:common-java8:1.1.1"

继承DefaultLifecycleObserver

1
2
3
public interface IPresenter extends DefaultLifecycleObserver {
}

实现生命周期方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class MainPresenter implements IPresenter {
private final String TAG = this.getClass().getSimpleName();
public void xxx(){
}
@Override
public void onCreate(LifecycleOwner owner) {
Log.e(TAG,"oncreate");
}
@Override
public void onStart(LifecycleOwner owner) {
Log.e(TAG,"start");
}
@Override
public void onResume(LifecycleOwner owner) {
Log.e(TAG,"resume ");
}
@Override
public void onPause(LifecycleOwner owner) {
Log.e(TAG,"pause");
}
@Override
public void onStop(LifecycleOwner owner) {
Log.e(TAG,"stop");
}
@Override
public void onDestroy(LifecycleOwner owner) {
Log.e(TAG,"destory");
}
}

在Activity中,只需要注册一下,就能完成生命周期的监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MainActivity extends AppCompatActivity {
private MainPresenter presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
presenter = new MainPresenter();
//注册
getLifecycle().addObserver(presenter);
}
}

IPrestener继承DefaultLifecycleObserver,MainPresenter实现IPresenter接口,并实现DefaultLifecycleObserver中定义的方法,可以全部实现,也可以选择需要的生命周期去实现。

了解了Lifecycle的使用,当然也要简单了解一下实现原理,知其然而不知其所以然,不是我们的目的。

原理

1.先了解几个重要的类和接口

  • Lifecycle
    Lifecycle是一个持有组件生命周期状态(如Activity或Fragment)的信息的类,并允许其他对象观察此状态。
  • Event :从框架和Lifecycle类派发的生命周期事件。这些事件映射到活动和片段中的回调事件。
  • State :由Lifecycle对象跟踪的组件的当前状态。
  • LifecycleOwner (重要)Lifecycle持有者 (例如Activity、Fragment)
    实现该接口的类持有生命周期(Lifecycle对象),该接口的生命周期(Lifecycle对象)的改变会被其注册的观察者LifecycleObserver观察到并触发其对应的事件。
  • LifecycleObserver(重要)Lifecycle观察者
    实现该接口的类,通过注解的方式,可以通过被LifecycleOwner类的addObserver(LifecycleObserver o)方法注册,被注册后,LifecycleObserver便可以观察到LifecycleOwner的生命周期事件。

2.LifecycleOwner(Lifecycle持有者)

官网介绍:LifecycleOwner是一个单一的方法接口,表示该类有一个 Lifecycle。它有一个方法,getLifecycle()这个方法 必须由这个类来实现。如果您试图管理整个应用程序进程的生命周期,请参阅 ProcessLifecycleOwner。该接口从各个类(如Fragment和AppCompatActivity)抽象生命周期的所有权,并允许编写与它们一起工作的组件。
任何自定义应用程序类都可以实现LifecycleOwner接口
实现LifecycleObserver的组件与实现LifecycleOwner的组件无缝协作,因为所有者可以提供生命周期,观察者可以注册以观看

简单理解:LifecycleOwner就是一个接口,谁继承了它,就持有了lifecycle对象,可以提供生命周期。
然后可以调用getLifecycle()方法获取继承了抽象类Lifecycle的LifecycleRegistry,然后调用addObserver(@NonNull LifecycleObserver observer) 方法来注册监听。
总而言之:
实现该接口的持有者,其生命周期(Lifecycle对象)的改变会被其注册的观察者LifecycleObserver观察到并触发其对应的事件

注意:Support Library 26.1.0 及其以后的版本,Activity 和Fragment 已经实现了LifecycleOwner 接口,所以,我们可以直接在Activity 和Fragment中使用getLifecycle()方法来获取lifecycle对象,来添加观察者监听。

3.LifecycleObserver(Lifecycle观察者)

LifecycleObserver 是一个观察者接口,实现了它,可以通过注解或者继承的方式,来管理声明周期的监听。只要在持有lifecycle的类中注册了它,当声明周期发生变化时,它就能收到,进行我们自定义的操作。

简单理解:任何注解或继承实现了该接口,只要在持有Lifecycle的类中注册了,就可以监听到生命周期的变化

两种实现方式:
1)、实现DefultLifecyceObserver接口,然后重写里面生命周期方法;
2)、直接实现LifecycleObserver接口,然后通过注解的方式来接收生命周期的变化;
Lifecycle.java文档中是建议使用第一种方式,因为文档中说明了,随着Java8成为主流,注解的方式会被弃用。DefaultLifecycleObserver是需要另外声明的java8 ,所以上面我们添加了Java8的依赖,再回顾上面的例子:

IPrestener继承DefaultLifecycleObserver,MainPresenter实现IPresenter接口,并实现DefaultLifecycleObserver中定义的方法,而Activity默认实现了LifecycleOwner接口,持有声明周期,最后调用getLifecycle().addObserver(presenter)注册了LifecycleObserver。

Android进阶 ——— Android官方架构组件LiveData

上一篇文章,学习了Android官方架构组件的第一部分Android进阶 ——— Android官方架构组件Lifecycle

初探

LiveData 是一个可以感知 Activity 、Fragment生命周期的数据容器。当 LiveData 所持有的数据改变时,它会通知相应的界面代码进行更新。同时,LiveData 持有界面代码 Lifecycle 的引用,这意味着它会在界面代码(LifecycleOwner)的生命周期处于 started 或 resumed 时作出相应更新,而在 LifecycleOwner 被销毁时停止更新。

  • 意味着LiveData可以被观察者订阅,并且感知LifeCycleOwner组件的生命周期。
  • 如果LifecycleOwner的状态为Lifecycle.State.STARTEDLifecycle.State.RESUMED,才会通知观察者更新
  • 可以通过observeForever(Observer)添加的观察者,使其始终处于活动状态,不仅仅是处于活跃状态。但是这种方式添加的观察者,需要手动调用removeObserver(Observer)解除

上面的描述介绍了LiveData的优点:不用手动控制生命周期,不用担心内存泄露,数据变化时会收到通知。

不仅如此,官方推荐,LiveData和Viewmodel搭配使用,ViewModel是Android官方构件的另一成员。

ViewModel 将视图的数据和逻辑从具有生命周期特性的实体(如 Activity 和 Fragment)中剥离开来。直到关联的 Activity 或 Fragment 完全销毁时,ViewModel 才会随之消失,也就是说,即使在旋转屏幕导致 Fragment 被重新创建等事件中,视图数据依旧会被保留。ViewModels 不仅消除了常见的生命周期问题,而且可以帮助构建更为模块化、更方便测试的用户界面。

ViewModel的优点:为Activity 、Fragment存储数据,直到完全销毁。尤其是屏幕旋转的场景,常用的方法都是通过onSaveInstanceState()保存数据,再在onCreate()中恢复,真的是很麻烦。
其次因为ViewModel存储了数据,所以ViewModel可以在当前Activity的Fragment中实现数据共享。

使用

LiveData有几种使用方式:

  • 使用LiveData对象
  • 继承LiveData

1.使用LiveData对象

  使用LiveData对象主要有以下几个步骤:

  • 创建保存特定数据类型的LiveData实例(ViewModel中);
  • 创建Observer对象,作为参数传入LiveData.observe()方法,添加观察者;
  • 通过 observe()方法连接观察者和LiveData。observe()方法需要携带一个LifecycleOwner类。这样就可以让观察者订阅LiveData中的数据;
创建LiveData实例,一般继承自MutableLiveData

MutableLiveData是LiveData的子类,添加了公共方法setValue和postValue,方便开发者直接使用。setValue必须在主线程调用。postValue可以在后台线程中调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Created by dai
* Created time 2018/8/20
* function:lifecycle.example.com.lifecycleexample.LifeCycle
*/
public class MainViewModel extends ViewModel{
private MutableLiveData<Student> student = new MutableLiveData<>();
public MutableLiveData<Student> getStudent() {
return student;
}
public void addStudent(){
Student student1 = new Student();
student1.setGender(55);
student1.setName("zhangsan");
student.setValue(student1);
}
}

创建Observer对象,作为参数添加观察者

通过LiveData.observe()方法添加观察者,当数据变化时会通过回调方法通知观察者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Created by dai
* Created time 2018/8/20
* function:lifecycle.example.com.lifecycleexample
*/
public class MainActivity extends AppCompatActivity {
private MainPresenter presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
presenter = new MainPresenter();
getLifecycle().addObserver(presenter);
final TextView textView = findViewById(R.id.text);
Button button = findViewById(R.id.button);
ViewModelProvider.NewInstanceFactory factory = new ViewModelProvider.NewInstanceFactory();
final MainViewModel model = factory.create(MainViewModel.class);
//订阅数据变化
model.getStudent().observe(this, new Observer<Student>() {
@Override
public void onChanged(@Nullable Student student) {
textView.setText(student.getName());
Log.e(student.getName(),student.getGender() + "年级");
}
});
}

更新LiveData中的数据

点击button,修改LiveData数据,观察者观察到数据变化,打印出信息

1
2
3
4
5
6
7
8
//修改数据
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
model.addStudent();
}
});
}

2.继承LiveData

在LiveData中,onActive方法回调表明当前Activity处于激活状态,也就是Activity处于生命周期的活动状态中(onStart,onResume),可以简单认为当前的Activity处于前台。LiveData的onInactive处理涉及onActive剩下的生命周期

1)创建继承于MutableLiveData

自定义一个MyThread继承MutableLiveData,内部实现一个简单的功能,后台运行一个长时的线程任务,该线程实现一个简单功能:
(1)如果当前的Activity处于运行(用户可见)状态,则线程任务不断累计计数器并postValue一个值给任何Observer使用。
(2)如果当前Activity处于没有激活状态,则暂停线程任务,停止累计计数器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* Created by dai
* Created time 2018/8/21
* function:lifecycle.example.com.lifecycleexample
*/
public class MyThread extends MutableLiveData<Integer> {
private Integer a = 0;
private boolean Active = true;
private Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (Active){
a++;
postValue(a);
}
}
});
public MyThread(){
thread.start();
}
@Override
protected void onInactive() {
super.onInactive();
Log.e("aa","onInactive");
Active = false;
}
@Override
protected void onActive() {
super.onActive();
Log.e("aa","onActive");
Active = true;
thread.interrupt();
}
}
2)构建Observer

再构建Observer,在Observer的onChanged中监听变化
在LiveData中的数据变化,通过postValue(可后台线程)或者setValue(主线程)设置后,将触发Observer的onChanged,开发者只需onChanged等待最新数据回调即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MainActivity extends AppCompatActivity {
private MainPresenter presenter;
private TextView textView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.text);
MyThread thread = new MyThread();
thread.observe(this,new MyObserver());
}
private class MyObserver implements Observer<Integer> {
@Override
public void onChanged(@Nullable Integer number) {
Log.e("Main",number.intValue()+"");
textView.setText(number.toString());
}
}
}

LiveData原理

先来熟悉一下类图,再分源码

这里参考一下Android架构组件(二)——LiveData

类关系图

LiveData的类关系图相对比较简单,从上面的类图我们就能看到。和LiveData组件相关的类和接口有:LiveData类、Observer接口、GenericLifecycleObserver接口。
LiveData类是个抽象类,但是它没有抽象方法,抽象类有个特点是:不能在抽象类中实例化自己。为什么LiveData会被定义成abstract而又没有抽象方法呢,这个…我也不知道,看了下LiveData的提交记录,是在将hasObservers()替换getObserverCount()方法时将LiveData改成了abstract,在此之前它是被定义为public,可以翻墙的可以看下这里的修改记录

  • MediatorLiveData继承自MutableLiveData,MutableLiveData继承自LiveData。MediatorLiveData可以看成是多个LiveData的代理,当将多个LiveData添加到MediatorLiveData,任何一个LiveData数据发生变化时,MediatorLiveData都会收到通知。

  • LiveData有个内部类LifecycleBoundObserver,它实现了GenericLifecycleObserver,而GenericLifecycleObserver继承了LifecycleObserver接口。在这里可以回顾下Lifecycle组件相关的内容。当组件(Fragment、Activity)生命周期变化时会通过onStateChanged()方法回调过来。

  • Observer接口就是观察者,其中定义了LiveData数据变化的回调方法onChanged()。

时序图
时序图

LiveData主要涉及到的时序有三个:

  • 在Fragment/Activity中通过LiveData.observer()添加观察者(observer()方法中的第二个参数)。
  • 根据Fragment/Activity生命周期发生变化时,移除观察者或者通知观察者更新数据。
  • 当调用LiveData的setValue()、postValue()方法后,通知观察者更新数据。

最让人头疼的源码,这里只抽取出常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//添加观察者,普通模式,只在LifecycleOwner处于活跃状态,监听LiveData的变化
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
//将LifecycleOwner对象和Observer对象封装成LifecycleBoundObserver对象。
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
// mObservers可以理解成一个类似Map的容器,putIfAbsent()方法是判断容器中的observer(key)
// 是否有已经和wrapper(value)关联,如果已经关联则返回关联值,否则关联并返回wrapper。
LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && existing.owner != wrapper.owner) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
owner.getLifecycle().addObserver(wrapper);
//条件LifecycleOwner的生命周期观察者
}
/**通过observeForever()添加观察者,观察者会一直受到数
据的变化回到,而不是在组件处于STARTED和RESUMED状态下才
会收到,因为这是LifecycleOwner对象就不再是组件了,而是
ALWAYS_ON;另外通过该方法添加观察者后,要手动调用
removeObserver()方法来停止观察者接收回调通知。
observeForever()方法体很简单,调用了observe()方法
,传入的一个参数是ALWAYS_ON常量
*/
@MainThread
public void observeForever(@NonNull Observer<T> observer) {
}
/**移除观察者,如果不是主线程,则会警告,然后根据
observer取出对应的ObserverWrapper(SafeIterableMap中存储Observer和
ObserverWrapper),真正实现移除是在
removed.activeStateChanged(false)中
*/
@MainThread
public void removeObserver(@NonNull final Observer<T> observer) {
assertMainThread("removeObserver");
//mObservers是一个SafeIterableMap对象
ObserverWrapper removed = mObservers.remove(observer);
if (removed == null) {
return;
}
removed.detachObserver();
//真正移除Observer的监听
removed.activeStateChanged(false);
}
/**
移除全部观察者,同样必须主线程,然后遍历所有Observer,调用removeObserver移除Observer
*/
@MainThread
public void removeObservers(@NonNull final LifecycleOwner owner) {
assertMainThread("removeObservers");
for (Map.Entry<Observer<T>, ObserverWrapper> entry : mObservers) {
if (entry.getValue().isAttachedTo(owner)) {
removeObserver(entry.getKey());
}
}
}
//主/分线程都可以调用,如果主线程多次调用,只调度最后一个值
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
}
// 会在主线程中执行 mPostValueRunnable中的内容。
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
//判断主线程,
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
//获取数据,很简单
public T getValue() {
Object data = mData;
if (data != NOT_SET) {
//noinspection unchecked
return (T) data;
}
return null;
}
//活跃状态,空方法,子类实现
protected void onActive() {
}
//非活跃状态,空方法,子类实现
protected void onInactive() {
}
//是否有观察者
public boolean hasObservers() {
return mObservers.size() > 0;
}
//是否有活跃观察者
public boolean hasActiveObservers() {
return mActiveCount > 0;
}
private abstract class ObserverWrapper {
final Observer<T> mObserver;
boolean mActive;
int mLastVersion = START_VERSION;
ObserverWrapper(Observer<T> observer) {
mObserver = observer;
}
abstract boolean shouldBeActive();
boolean isAttachedTo(LifecycleOwner owner) {
return false;
}
void detachObserver() {
}
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
// immediately set active state, so we'd never dispatch anything to inactive
// owner
mActive = newActive;
//是否有活跃状态的观察者
boolean wasInactive = LiveData.this.mActiveCount == 0;
//增加或减少活跃状态观察者数量
LiveData.this.mActiveCount += mActive ? 1 : -1;
//存在活跃状态观察者且新增观察者,则是活跃状态
if (wasInactive && mActive) {
onActive();
}
//如果活跃状态观察者为0,且移除观察者,则非活跃状态
if (LiveData.this.mActiveCount == 0 && !mActive) {
onInactive();
}
if (mActive) {
dispatchingValue(this);
}
}
}

总结

简单了解LiveData的使用以及原理,发现内部并不太复杂,一直是维护一个观察者和观察者管理对象的 SafeIterableMap,现在就可以在轻松的开始尝试了。

iOS 自定义View 中跳转UIViewController

自定义View中实现控制器的跳转,可以有多种实现方式:

  • 1 使用Notification

    使用Notification,在父控制器打开需要跳转的控制器

  • 2 使用代理

    使用代理通知父控制器跳转

  • 3 使用闭包

    原理同第2条

  • 4 使用主窗口的根控制器

    拿到主窗口的根控制器,用根控制器打开需要跳转的控制器

Swift

1
2
3
let nav = UIApplication.shared.keyWindow?.rootViewController as! UIViewController
let feedVC = FeedBackViewController.init()
nav.present(feedVC!, animated: true, completion: nil)

OC

1
2
UIViewController *root = [UIApplication sharedApplication].keyWindow.rootViewController;
[root presentViewController:<#(nonnull UIViewController *)#> animated:YES completion:nil];

Kotlin 学习笔记(八)—— Kotlin类与对象之接口


Kotlin学习笔记系列教程

Kotlin 学习笔记(一)—— 概述、学习曲线、开发工具、参考资料
Kotlin 学习笔记(二)—— 基础语法
Kotlin 学习笔记(三)—— 习惯用法
Kotlin 学习笔记(四)—— Kotlin基础之基本类型
Kotlin 学习笔记(五)—— Kotlin基础之控制流、返回与跳转、 包与导入
Kotlin 学习笔记(六)—— Kotlin类与对象之类和继承
Kotlin 学习笔记(七)—— Kotlin类与对象之属性与字段


接口

Kotlin 的接口和 Java8 类似,既包含抽象方法的声明,也包含实现。与抽象类不同的是,接口无法保存柱状。它可以有属性但必须声明为抽象或提供访问器实现。

使用关键字 interface 来定义接口

1
2
3
4
5
6
7
interface A {
fun setName()
fun setAge(){
//可选方法体
}
}

实现接口

一个类或者对象可以实现一个或多个接口。

1
2
3
4
5
class B : A {
override fun setName() {
}
}

接口中的属性

你可以在接口中定义属性。在接口中声明的属性要么是抽象的,要么提供访问器的实现,在接口中声明的属性不能有幕后字段,因此接口中声明的访问器不能引用它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface A {
val time:Int
var name:String
get() = "aaa"
set(value) {
}
fun setName()
fun setAge(){
//可选方法体
}
}
class B : A {
override val time: Int
get() = 5
override fun setName() {
}
}

解决覆盖冲突

实现多个接口或继承父类时,可能会遇到同一方法继承多个实现的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface A {
fun foo() { print("A") }
fun bar()
}
interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}
class C : A {
override fun bar() { print("bar") }
}
class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
override fun bar() {
super<B>.bar()
}
}

接口 A 和 B 都定义了方法 foo() 和 bar(),两者都实现了foo(),但是只要 B 实现了bar(),bar()在 A 中没有标记为抽象,因为没有方法体时默认为抽象。因为 C 实现了 A 的具体类,所以必须重写 bar() 并实现这个抽象方法。

然而,如果我们从 A 和 B 派生 D,我们需要实现我们从多个接口继承的所有方法,并指明 D 应该如何实现它们。这一规则既适用于继承单个实现(bar())的方法也适用于继承多个实现(foo())的方法。


以上就是类与对象第三篇之接口,第四篇将学习可见性修饰符


个人博客地址:http://outofmemory.top/
CSDN地址:http://blog.csdn.net/dazhaoDai
GitHub地址:https://github.com/dazhaoDai


Kotlin 学习笔记(七)—— Kotlin类与对象之属性与字段


Kotlin学习笔记系列教程

Kotlin 学习笔记(一)—— 概述、学习曲线、开发工具、参考资料
Kotlin 学习笔记(二)—— 基础语法
Kotlin 学习笔记(三)—— 习惯用法
Kotlin 学习笔记(四)—— Kotlin基础之基本类型
Kotlin 学习笔记(五)—— Kotlin基础之控制流、返回与跳转、 包与导入
Kotlin 学习笔记(六)—— Kotlin类与对象之类和继承


属性与字段

声明属性

Kotlin的类可以有属性,属性可以用关键词 var 声明为可变, 否则使用只读关键字 val。

1
2
3
4
5
class Kotlin3 {
var name: String = "name"
var age: Int = 5
var city: String = "Beijing"
}

要使用一个属性,只需要用名称引用即可,就像Java中的字段:

1
2
3
4
5
6
fun copyKotlin3(kotlin3: Kotlin3):Kotlin3{
val kotlin3 = Kotlin3()
kotlin3.age = 30
kotlin3.name = "kotlin4"
return kotlin3
}

Getters 与 Setters

声明一个属性的完整语法是

1
2
3
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]

其初始化器(initializer)、getter 和 setter 都是可选的。属性类型如果可以从初始化器(或者从其getter 返回值,如下文所示)中推断出来,也可以省略。
例如:

1
2
var defaultSize: Int? // 错误:需要显式初始化器,隐含默认 getter 和 setter
var defaultTime = 1 // 类型 Int、默认 getter 和 setter

一个只读属性的语法和一个可变的属性的语法有两方面的不同:1、只读属性的用 val 开始代替 var ,2、只读属性不允许 setter

1
2
val defaultSize: Int? // 错误:默认 getter
val defaultTime = 1 // 类型 Int、默认 getter

我们可以自定义访问器,非常像普通函数,刚好在属性声明内部。这里有一个自定义getter 的例子:

1
2
3
4
5
var defaultName: String
get() = this.toString()
set(value) {
value.toUpperCase()
}

按照惯例,setter 参数的名称是 value,如果你喜欢你可以选择其他名称。
自Kotlin 1.1 起,如果可以从getter 推断出属性类型,则可以省略属性类型:

1
2
3
4
5
var defaultAge
get() = this.toString() //省略String类型
set(value) {
value.toUpperCase()
}

如果你需要改变一个访问器的可见性或者对其注解,但是不需要改变默认的实现,你可以定义访问器而不定义其实现:

1
2
3
4
5
var changeAge: String = "aaa"
private set //此 setter 是私有的并且有默认实现
var changeName: String? = null
@Inject set // 用 Inject 注解此 setter

幕后字段

在kotlin 类中不能直接声明字段。然而,当一个属性需要一个幕后字段时,Kotlin 会自动提供,这个幕后字段可以使用field 标识符在访问器中引用:

1
2
3
4
var count = 1
set(value) {
if (value > 0) field = value
}

field 标识符只能用在属性的访问器内

如果属性至少一个访问器使用默认实现,或者自定义访问器通过 field 引用幕后字段,将会为该属性生成一个幕后字段

例如,下面情况,就没有幕后字段:

1
2
val height: Int
get() = defaultTime

幕后属性

如果你的需求不符合这套”隐式的幕后字段”方案,那么总可以使用幕后属性(backing property):

1
2
3
4
5
6
7
8
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // 类型参数已推断出
}
return _table ?: throw AssertionError("Set to null by another thread")
}

从各方面来看,这正是与 Java 相同的方式。因为通过默认 getter 和 setter 访问私有属性会被优化,所以不会引入函数调用开销

编译期常量

已知值的属性可以使用 const 修饰符标记为 编译期常量。这些属性需要满足以下条件:

  • 位于顶层或者是 object 的一个成语
  • 用 String 或原生类型 值初始化
  • 没有自定义 getter
    这些属性可以用在注解中:
    1
    2
    3
    const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
    @Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { …… }

延迟初始化属性与变量

一般地,属性声明为非空类型必须在构造函数中初始化。然而,这经常不方便。例如:属性可以通过依赖注入来初始化,或者在单元测试的setup方法中初始化,这种情况下,你不能在构造函数内提供一个非空初始器,但你仍想在类体中引用该属性时避免空检查。
为处理这种情况,你可以用 lateinit 修饰符标记该属性:

1
2
3
4
5
6
7
8
9
10
11
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // 直接解引用
}
}

该修饰符只能用于类体中的属性(如果在主构造函数中声明的 var 属性,并且仅当该属性没有自定义 getter 或 setter 时),而自 Kotlin 1.2 起,也用于顶层属性与局部变量。该属性或变量必须为非空类型,并且不能是原生类型。

在初始化前访问一个 lateinit 属性会抛出一个特定异常,该异常明确标识该属性被访问及它没有初始化的事实。

检测一个 lateinit var 是否已初始化(自 1.2 起)

要检测一个 lateinit var 是否经过初始化,请在该属性的引用上使用 .isInitialized:

1
2
3
if (foo::bar.isInitialized) {
println(foo.bar)
}

此检测仅对可词法级访问的属性可用,即声明位于同一个类型内、位于其中一个外围类型中或者位于相同文件的顶层的属性。

覆盖属性

参见第 Kotlin 学习笔记(六)—— Kotlin类与对象之类和继承 —- 覆盖属性

委托属性

最常见的一类属性就是简单地从幕后字段中读取(以及可能的写入)。另一方面,使用自定义getter 和 setter 可以实现属性的任何行为,介于两者之间,属性如何工作有一些常见的模式,一些例子:惰性值、通过键值从映射读取。访问数据库。访问时通知侦听器等等。

这些常见行为可以通过委托属性实现为库,会在后续文章中介绍


本篇主要介绍了Kotlin类与对象之属性与字段,下篇文章学习kotlin之接口


个人博客地址:http://outofmemory.top/
CSDN地址:http://blog.csdn.net/dazhaoDai
GitHub地址:https://github.com/dazhaoDai


Kotlin 学习笔记(六)—— Kotlin类与对象之类和继承


Kotlin学习笔记系列教程

Kotlin 学习笔记(一)—— 概述、学习曲线、开发工具、参考资料
Kotlin 学习笔记(二)—— 基础语法
Kotlin 学习笔记(三)—— 习惯用法
Kotlin 学习笔记(四)—— Kotlin基础之基本类型
Kotlin 学习笔记(五)—— Kotlin基础之控制流、返回与跳转、 包与导入


Kotlin中使用关键字class声明类

1
2
class Invoice {
}

类声明由类名、类头(指定其类型参数、主构造函数等)以及花括号包围的类体构成。类头和类体都是可选的;如果一个类没有类体,可以省略花括号。

1
class Empty

构造函数

在Kotlin中的一个类可以有一个主构造函数和一个或多个次构造函数,主构造函数是类头的一部分:它跟在类名(和可选的类型参数)后。

1
2
class Person constructor(firstName: String) {
}

如果主构造函数没有任何注解或者可见性修饰符,可以省略这个constructor关键字。

1
2
class Person(firstName: String) {
}

主构造函数不能包含任何的代码。初始化代码可以放到以init关键字作为前缀的初始化块initializer blocks)中。
在实例初始化期间,初始化块按照它们出现在类体重的顺序执行,与属性初始化器交织在一起:

1
2
3
4
5
6
7
8
9
class Kotlin1(fileName: String){
val time = fileName
init {
var size = time.length
}
}

注意:主构造的参数可以在初始化块中使用,它们也可以在类体内声明的属性初始化器中使用:

1
val time = fileName

事实上,声明属性以及从主构造函数初始化属性,Kotlin有简洁的语法:

1
2
3
class Kotlin1(fileName: String,val size: Int,var age: Int){
//....
}

与普通属性一样,主构造函数中声明的属性可以是可变的(var)或只读的(val)。
如果构造函数有注解或可见性修饰符,这个constructor关键词是必须的,并且这些修饰符在它的前面:

1
class Customer public @Inject constructor(name: String) { …… }

次构造函数

1
2
3
4
5
6
7
8
class Kotlin1{
constructor(fileName: String, size: Int, age: Int) {
val time = fileName
}
}

如果类有一个主构造函数,每个次构造函数需要委托给主构造函数,可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用this关键字即可:

1
2
3
4
5
6
7
class Kotlin1(fileName: String){
constructor(fileName: String, size: Int, age: Int) : this(fileName){
}
}

请注意,初始化块中的代码实际上会成为主构造函数的一部分。委托给主构造函数会作为次构造函数的第一条语句,因此所有初始化块中的代码都会在次构造函数体之前执行。即使该类没有主构造函数,这种委托仍会隐式发生,并且仍会执行初始化块:

1
2
3
4
5
6
7
8
9
10
11
class Kotlin1{
init {
print("first")
}
constructor(fileName: String, size: Int, age: Int){
print("second")
}
}

如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的不带参数的主构造函数,构造函数的可见性是public,如果你不希望你的类有一个公有构造函数,你需要声明一个带有非默认可见性的空的主构造函数:

1
2
3
class Kotlin2 private constructor(){
}

1
2
3
4
5
注意:在JVM上,如果主构造函数的所有参数都有默认值,编译器会生成一个额外的无参构造函数,它将使用默认值,这使得Kotlin更易于像Jackson或者JPA这样的通过无参构造函数创建类的实例的库。
class Kotlin2 (val fileName: String = ""){
}

###创建类的实例
要创建一个类的实例,我们就像普通函数一样调用构造函数:

1
val kotlin2 = Kotlin2("music")

注意Kotlin没有new 关键字
创建嵌套类、内部类和匿名内部类的类实例在嵌套类中有述。

类成员

类可以包含:

  • 构造函数和初始化块
  • 函数
  • 属性
  • 嵌套类和内部类
  • 对象声明

继承

在Kotlin 中所有类都有一个共同的超类 Any,这对于没有超类型声明的类是默认超类:

1
class Example // 从 Any 隐式继承

1
注意:Any 并不是 java.lang.Object;尤其是,它除了 equals()、hashCode()和toString()外没有任何成员。 更多细节请查阅Java互操作性部分。

要声明一个显式的超类型,我们把类型放到类头的冒号之后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
open class Kotlin1{
init {
print("first")
}
constructor(fileName: String, size: Int, age: Int){
print("second")
}
}
class Kotlin2 : Kotlin1("music",20,20) {
}

1
类上的 open 标注与 Java 中 final相反,它允许其他类从这个类继承。默认情况下,在Kotlin中所有的类都是final,对应于 Effective Java书中的第17条:**要么为继承而设计,并提供说明文档,要么就禁止继承**。

如果派生类有一个主构造函数,其基类型可以(并且必须)用基类的主构造函数参数就地初始化。

如果累没有主构造函数,那么每个次构造函数必须使用 super 关键字初始化其基类型,或委托给另一个构造函数做到这一点。注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数:

1
2
3
4
5
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

覆盖方法

我们之前提到过,Kotlin力求清晰显示,与Java 不同,Kotlin 需要显示标注可覆盖的成员(我们称之为开放)和覆盖后的成员:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
open class Kotlin1{
init {
print("first")
}
constructor(fileName: String, size: Int, age: Int){
print("second")
}
open fun addSize(){}
fun addAge(){}
}
class Kotlin2 : Kotlin1("music",20,20) {
override fun addSize() {
super.addSize()
}
}

Kotlin2.addSize()函数上必须加上override标注,如果没写,编译器将会报错。如果函数没有标注open 和 Kotlin1.addAge(),则子类中不允许定义相同签名的函数,不论加不加 override。在一个final 类中,开放成员是禁止的。

标记为 override 的成员本身是开放的,也就是说,它可以在子类中覆盖。如果你想禁止再次覆盖,使用final 关键字:

1
2
3
4
5
6
class Kotlin2 : Kotlin1("music",20,20) {
final override fun addSize() {
super.addSize()
}
}

覆盖属性

属性覆盖和方法覆盖类似:在超类中声明然后在派生类中重新声明的属性必须以override 开头,并且它们必须具有兼容的类型。每个声明的属性可以由具有初始化的属性或者具有getter方法的属性覆盖。

1
2
3
4
5
6
7
8
9
10
open class Kotlin1{
open val aa: Int get() {
return 5
}
}
class Kotlin2 : Kotlin1 {
override val aa: Int
get() = super.aa + 5
}

你也可以用一个 var 属性覆盖一个 val 属性,但是反之则不行。这是允许的,因为一个 val 属性本质上声明了 一个 getter 方法,而将其覆盖为 var 只是在子类中额外 声明一个 setter方法。

请注意,你可以在主构造函数中使用 override 关键字作为属性声明的一部分。

1
2
3
4
5
6
7
8
9
interface Foo {
val count: Int
}
class Bar1(override val count: Int) : Foo
class Bar2 : Foo {
override var count: Int = 0
}

派生类初始化顺序

在构造派生类的新实例的过程中,第一步完成其基类的初始化(在之前只有对基类构造函数参数的求值),因此发生在派生类的初始化逻辑运行之前。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
open class Base(val name: String) {
init { println("Initializing Base") }
open val size: Int =
name.length.also { println("Initializing size in Base: $it") }
}
class Derived(
name: String,
val lastName: String
) : Base(name.capitalize().also { println("Argument for Base: $it") }) {
init { println("Initializing Derived") }
override val size: Int =
(super.size + lastName.length).also { println("Initializing size in Derived: $it") }

输出:

1
2
3
4
5
6
Constructing Derived("hello", "world")
Argument for Base: Hello
Initializing Base
Initializing size in Base: 5
Initializing Derived
Initializing size in Derived: 10

这意味着,基类构造函数执行时,派生类中声明或覆盖的属性都还没有初始化,如果在基类初始化逻辑中(直接或通过另一个覆盖的 open 成员的实现间接)使用了任何一个这种属性,那么都可能导致不正确的行为或者运行时故障,设计一个基类时,应该避免这种在构造函数、属性初始化器以及init 块中使用 open 成员

调用超类实现

派生类中的代码可以使用super 关键字调用其超类的函数与属性访问器的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
open class Kotlin1{
init {
print("first")
}
constructor(fileName: String, size: Int, age: Int){
print("second")
}
open fun addSize(){
print("Kotlin1")
}
fun addAge(){}
}
class Kotlin2 : Kotlin1("music",20,20) {
override fun addSize() {
super.addSize()
print("Kotlin2")
}
}

在一个内部类中访问外部类的超类,可以通过由外部类名限定的super关键字来实现: super@Outer

1
2
3
4
5
6
7
8
9
10
11
class Bar : Foo() {
override fun f() { /* …… */ }
override val x: Int get() = 0
inner class Baz {
fun g() {
super@Bar.f() // 调用 Foo 实现的 f()
println(super@Bar.x) // 使用 Foo 实现的 x 的 getter
}
}
}

覆盖规则

在Kotlin中,实现继承由下述规则规定:如果一个类从它的直接超类继承相同成员的多个实现,它必须覆盖这个成员并提供自己的实现(也许用继承来的其中之一)。为了表示采用从哪个超类型继承的实现,我们使用尖括号中超类型名限定的super,例如 super<Base>:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
open class Kotlin1{
open fun addSize(){}
}
interface A {
fun addSize(){
println("aa")
}
}
class Kotlin2 : Kotlin1("music",20,20),A {
override fun addSize() {
super<Kotlin1>.addSize()
super<A>.addSize()
}
}

同时继承Kotlin1 和 A 没问题,但是addSize() 由 Kotlin2 继承了两个实现,所以我们必须在 Kotlin2 中覆盖addSize() 并且提供我们自己的实现来消除歧义。

抽象类

类和其中的某些成员可以声明为abstract。 抽象成员在本类中可以不用实现,需要注意的是,我们并不需要用 open 标注一个抽象类火灾函数 – 因为这不言而喻

我们可以用一个抽象成员覆盖一个非抽象的开放成员

1
2
3
4
5
6
7
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}

伴生对象

与Java 或者 C# 不同,在Kotlin 中类没有静态方法,在大多数情况下,它建议简单地使用包级函数。

如果你需要写一个无需用一个类的实例来调用、但需要访问类内部的函数(例如,工厂方法),你可以把它写成该类内对象声明中的一员。

更具体的讲,如果在你的类内部声明了一个伴生对象,你就可以使用像在Java/C# 中调用静态方法相同的语法来调用其成员,只使用类名作为限定符。


>以上就是类与对象的第一篇–类和继承

个人博客地址:http://outofmemory.top/
CSDN地址:http://blog.csdn.net/dazhaoDai
GitHub地址:https://github.com/dazhaoDai


Kotlin 学习笔记(五)—— Kotlin基础之控制流、返回与跳转、 包与导入


Kotlin学习笔记系列教程

Kotlin 学习笔记(一)—— 概述、学习曲线、开发工具、参考资料
Kotlin 学习笔记(二)—— 基础语法
Kotlin 学习笔记(三)—— 习惯用法
Kotlin 学习笔记(四)—— Kotlin基础之基本类型


控制流:if、when、for、while

if 表达式

在Kotlin中,if是一个表达式,即它会返回一个值。因此就不需要三元运算符(条件? 然后: 否则),因为普通的if就能胜任这个角色。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 传统用法
var max = a
if (a < b) max = b
// With else
var max: Int
if (a > b) {
max = a
} else {
max = b
}
// 作为表达式
val max = if (a > b) a else b

if的分支可以是代码块,最后的表达式作为该块的值:

1
2
3
4
5
6
7
8
9
val a1 = 101
val b1 = 5
var d1 = if (a1 > b1) {
print(a1)
a1
}else{
print(b1)
b1
}

When表达式

when 取代了C语言的switch操作符。最简单的形式如下:

1
2
3
4
5
6
7
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // 注意这个块
print("x is neither 1 nor 2")
}
}

when 将它的参数和所有分支条件顺序比较,直到某个分支满足条件。when既可以被当做表达式使用也可以被当做语句使用。如果它被当作表达式,符合条件的分支的值就是整个表达式的值,如果当作语句使用,则忽略个别分支的值。(像if 一样,每一个分支可以是一个代码块,它的值是块中最后的表达式的值)

如果其他分支都不满足条件将会求值else分支。如果一个when作为一个表达式使用,则必须有else分支,除非编译器能够检测出所有的可能情况都已经覆盖了。

如果很多分支需要用相同的方式处理,则可以把多个分支放在一起,用逗号隔开:

1
2
3
4
5
6
7
8
var max = 100
fun compareTo(){
when(max){
0,1 -> print("max == 0 or max == 1")
else -> print("otherwise")
}
}

我们可以用任意表达式(而不是常量)作为分支条件

1
2
3
4
when(max){
parseInt(a1.toString()) -> print("max encodes x")
else -> print("otherwise")
}

我们也可以检测一个值在(in)或者不在(!in)一个区间或者集合中:

1
2
3
4
5
6
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}

另一种可能是检测一个值是(is)或者不是(!is)一个特定类型的值。注意:由于智能转换,你可以访问该类型的方法和属性而不需要任何额外的检测。

1
2
3
4
fun hasPrefix(x: Any) = when(x){
is String -> x.startsWith("prefix")
else -> false
}

when 也可以用来取代 if-else if链,如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:

1
2
3
4
5
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}

For循环

for循环可以对任何提供迭代器(iterator)的对象进行遍历,这相当于C#这样的语言中的foreach循环,语法如下:

1
for (item in lists) print(item)

循环体可以是一个代码块

1
2
3
for (item in lists){
print(item)
}

综上所述,for可以循环遍历任何提供了迭代器的对象,即:

  • 有一个成员函数或者拓展函数iterator,它的返回类型
  • 有一个成员函数或者拓展函数next(),并且返回 Boolean
  • 有一个成员函数或者扩展函数 hasNext() 返回 Boolean。

这三个函数都需要标记为operator

对数组的for循环会被编译为并不创建迭代器的基于索引的循环。

如果你想通过索引遍历一个数组或者list,可以这么做:

1
2
3
for (i in array.indices) {
print(array[i])
}

注意这种”在区间遍历”会编译成优化的实现而不会创建额外对象;

或者你可以用库函数withIndex

1
2
3
for ((index, value) in array.withIndex()) {
println("the element at $index is $value")
}

While循环

while和do..while照常使用

1
2
3
4
5
6
7
while (x > 0) {
x--
}
do {
val y = retrieveData()
} while (y != null) // y 在此处可见


返回和跳转

Kotlin有三种结构化跳转表达式:

  • return。默认从最直接包围它的函数或者匿名函数返回。
  • break。终止最直接包围它的循环。
  • continue。继续下一次最直接包围它的循环。

所有这些表达式都可以用作更大表达式的一部分:

1
val s = person.name ?: return

这些表达式的类型是Nothing类型。

Break 与 Continue 标签

在 Kotlin 中任何表达式都可以用标签(Label)来标记,标签的格式为标识符后跟 @ 符号,例如:adc@ 、fast@ 都是有效的标签(参见语法)。要为一个表达式加标签,我们只要在其前加标签即可。

1
2
3
4
5
loop@ for (i in 1..100){
for (j in 1..100){
if (j == 50) break@loop
}
}

标签限制的 break 跳转到刚好位于该标签指定的循环后面的执行点。continue 继续标签制定的循环的下一次迭代。

标签处返回

Kotlin有函数字面量、局部函数和对象表达式。因此 Kotlin 的函数可以被嵌套。标签限制的return允许我们从外层函数返回。最重要的一个用途就是从 lambda 表达式中返回。回想一下我们这么写的时候:

1
2
3
4
5
6
7
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return // 非局部直接返回到 foo() 的调用者
print(it)
}
println("this point is unreachable")
}

这里 return 表达式从最直接包围它的函数即 foo 中返回。(注意,这种非局部的返回只支持传给内联函数的 lambda 表达式)如果我们需要从 lambda 表达式中返回,我们必须给它加上标签并用以限制 return。

1
2
3
4
5
6
7
fun foo() {
listOf(1, 2, 3, 4, 5).forEach lit@{
if (it == 3) return@lit // 局部返回到该 lambda 表达式的调用者,即 forEach 循环
print(it)
}
print(" done with explicit label")
}

现在,它只会从lambda 表达式中返回。通常情况下使用隐式标签更方便。该标签与接受该lambda 的函数同名。

1
2
3
4
5
6
7
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return@forEach // 局部返回到该 lambda 表达式的调用者,即 forEach 循环
print(it)
}
print(" done with implicit label")
}

或者我们用一个匿名函数替代 lambda 表达式。匿名函数内部的 return 语句将从该匿名函数自身返回

1
2
3
4
5
6
7
fun testReturn(){
lists.forEach(fun(value: String) {
if (value.startsWith("a")) return //局部返回到匿名函数的调用者,即foreach 循环
print(value)
})
print("finfish")
}

请注意,前文三个实例中使用的局部返回类似于在常规循环中使用continue。并没有 break 的直接等价形式,不过可以通过增加另一层嵌套 lambda 表达式并不从其中非局部返回来模拟:

1
2
3
4
5
6
7
8
9
fun testReturn(){
run loop@{
lists.forEach {
if (it.equals("a")) return@loop
print(it)
}
}
print("out")
}

当要返一个回值得时候,解析器优先选用标签限制的return,即

1
return@a 1

意为“从标签 @a 返回 1”,而不是“返回一个标签标注的表达式 (@a 1)”。


源文件通常以包声明开头:

1
2
3
4
5
6
7
package foo.bar
fun baz() {}
class Goo {}
// ……

源文件所有内容(无论是类还是函数)都包含在声明的包内。所以上例中baz()的全名是foo.bar.bazGoo 的全名是 foo.bar.Goo
如果没有指明包,该文件的内容属于无名字的默认包。

默认导入

有多个包会默认导入到每个 Kotlin 文件中:

  • kotlin.*
  • kotlin.annotation.*
  • kotlin.collections.*
  • kotlin.comparisons.*
  • kotlin.io.*
  • kotlin.ranges.*
  • kotlin.sequences.*
  • kotlin.text.*

根据目标平台还会导入额外的包:

  • JVM:
    • java.lang.*
    • kotlin.jvm.*
  • JS:
    • kotlin.js.*

导入

除了默认导入之外,每个文件可以包含它自己的导入指令。
可以导入一个单独的名字,如:

1
import foo.Bar // 现在 Bar 可以不用限定符访问

也可以导入一个作用域下的所有内容(包、类、对象等):

1
import foo.* // “foo”中的一切都可访问

如果出现名字冲突,可以使用 as 关键字在本地重命名冲突项来消歧义:

1
2
import foo.Bar // Bar 可访问
import bar.Bar as bBar // bBar 代表“bar.Bar”

关键词 import 并不仅限于导入类;也可用它来导入其他声明:

  • 顶层函数及属性;
  • 在对象声明中声明的函数和属性;
  • 枚举常量。

与 Java 不同,Kotlin 没有单独的“import static”语法; 所有这些声明都用 import 关键字导入。

顶层声明的可见性

如果顶层声明是 private 的,它是声明它的文件所私有的(参见 可见性修饰符)。

Kotlin 学习笔记(四)—— Kotlin基础之基本类型


Kotlin学习笔记系列教程

Kotlin 学习笔记(一)—— 概述、学习曲线、开发工具、参考资料
Kotlin 学习笔记(二)—— 基础语法
Kotlin 学习笔记(三)—— 习惯用法


在Kotlin中,所有东西都是对象,在这个意义上讲我们可以在任何变量上调用成员函数和属性,一些类型可以有特殊的内部表示–例如,数字、字符和布尔值可以在运行时表示为原生类型值,但是对于用户来说,他们看起来就像普通类,在本篇中,将描述Kotlin中使用的基本类型:数字、字符、布尔值、数组与字符串。

数字

Kotlin处理数字在某种程度上接近Java,但是不完全相同,例如,对于数字没有隐式拓宽转换(例如Java中int 可以隐式转换为long),另外有些情况的字面值略有不同。

Kotlin 提供了如下的内置类型来表示数字:

Type Bit Width
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8

Kotlin中字符不是数字

字面常量

数值常量字面值有以下几种:

  • 十进制
    -Long 类型用大写L标记:32L
  • 十六进制:0x0F
  • 二进制:0b00001011

    注意:不支持八进制
    Kotlin同样支持浮点数的常规表示方法:
    –默认double:22.2240.5e10
    – Float用 f 或者 F 标记:234.5f

数字字面值的下划线(自1.1起)
可以使用下划线使数字常量更具有可读性:

1
2
3
val sixMillion = 6_000_000
val phoneNumber = 138_0013_8000
val creditCardNumber = 1234_5678_9012_3456L

表达方式

在Java平台数字是物理存储为JVM的原生类型,除非我们需要一个可空的引用(如Int?) 或泛型,后者情况下会把数字装箱。
注意数字装箱不必保留同一性:

1
2
3
4
5
val a: Int = 10000
print(a === a) // 输出“true”
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA === anotherBoxedA) // !!!输 出“false”!!!

另一方面,保留了相等性:

1
2
3
4
5
val a: Int = 10000
print(a == a) // 输出“true”
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA == anotherBoxedA) // 输出“true”

显式转换

由于不同的表达方式,较小类型并不是较大类型的子类型。如果它们是的话,就会出现下面问题:

1
2
3
4
// 假想的代码,实际上并不能编译:
val a: Int? = 1 // 一个装箱的 Int (java.lang.Integer)
val b: Long? = a // 隐式转换产生一个装箱的 Long (java.lang.Long)
print(a == b) // 惊!这将输出“false”鉴于 Long 的 equals() 检测其他部分也是 Long

所以同一性还有相等性都会在所有地方悄无声息地失去

因此较小的类型不能隐式转换为较大的类型。这意味着在不进行显式转换的情况下我们不能把byte类型赋值给一个Int类型变量:

1
2
val a: Byte = 1
val b: Int = b //错误,无法赋值

不过,我们可以通过显式转换来拓宽数字:

1
val c: Int = a.toInt() //OK:显式拓宽

每个数字类型都支持如下的转换:

  • toByte(): Byte
  • toShort(): Short
  • toInt(): Int
  • toLong(): Long
  • toFloat(): Float
  • toDouble(): Double
  • toChar(): Char
    缺乏隐式类型转换并不显著,因此类型会从上下文来推断出来,而算术运算会有重载做适当转换,例如:
    1
    val l = 1L + 3 // Long + Int => Long

运算

Kotlin 支持数字运算的标准集,运算被定义为相应的类成员(但编译器会将函数调用优化为相应的指令)。
对于运算符,没有特殊字符表示,而只可用中椎方式调用命令函数,例如:

1
val x = (1 shl 2) and 0x000FF000

这是完整的位运算列表(只用于 IntLong):

  • shl(bits) – 有符号左移 (Java 的 <<)
  • shr(bits) – 有符号右移 (Java 的 >>)
  • ushr(bits) – 无符号右移 (Java 的 >>>)
  • and(bits) – 位与
  • or(bits) – 位或
  • xor(bits) – 位异或
  • inv() – 位非

浮点数比较

本节讨论的浮点数操作如下:

  • 相等性检测:a == ba != b
  • 比较操作符:a < ba > ba <= ba >= b
  • 区间实例以及区间检测:a..bx in a..bx !in a..b

其中的操作数ab都是静态已知的FLoatDouble 或者它们对呀的可空类型(声明为该类型,或者推断为该类型,或者智能类型转换的结果是该类型),两数字所形成的操作或者区间遵循IEEE 754浮点运算标准。

然而,为了支持泛型场景并提供全序支持,当这些操作符并非静态类型为浮点数(例如是 Any、 Comparable<……>、 类型参数)时,这些操作使用为 Float 与 Double 实现的不符合标准的 equals 与 compareTo,这会出现:

  • 认为 NaN 与其自身相等
  • 认为 NaN 比包括正无穷大(POSITIVE_INFINITY)在内的任何其他元素都大
  • 认为 -0.0 小于 0.0

字符

字符使用Char类型表示。它们不能直接当作数字

1
2
3
4
5
fun check(char : Char){
if (char == 1){ //❎,类型不兼容
}
}

字符字面值要用单引号括起来 '1',特殊字符可以用反斜杠转义。支持以下转义序列:\t\b\n\r\'\"\\\$。编码其他字符要用Unicode转义序列语法: '\uFF00'
我们可以显式把字符转为Int数字:

1
2
3
4
5
6
fun chartoInt(char: Char) : Int {
if (char in '0'..'9'){
return char.toInt() - '0'.toInt()
}
throw IllegalArgumentException("out of range")
}

当需要可空引用时,像数字、字符会被装箱。装箱操作不会保留同一性

布尔

布尔使用Boolean类型表示,只有两个值:truefalse
布尔运算符:

  • || -短路逻辑或
  • && -短路逻辑与
  • -逻辑非

数组

数组在Kotlin中使用Array类来表示,它定义了getset函数(按照运算符重载约定这会转变为[])和size属性,以及一些其他有用的成员函数:

1
2
3
4
5
6
7
8
class Array<T> private constructor() {
val size: Int
operator fun get(index: Int): T
operator fun set(index: Int, value: T): Unit
operator fun iterator(): Iterator<T>
// ……
}

我们可以使用库函数arrayOf()来创建一个数组并传递元素值给它,这样arrayOf(1, 2, 3)创建了array[1, 2, 3]。或者,库函数arrayOfNulls()可以创建一个制定大小的、所有元素都为空的数组。

另一个选项是用接受数组大小和一个函数参数的Array构造函数,用作参数的函数能够返回给定索引的每个元素初始值:

1
2
//创建一个 Array<Int> 初始值为[0,1,4,9]数组
val intArray = Array(4,{i -> i * i })

如上所述,[]运算符代表调用成员函数get()set()

注意:与Java不同的是,Kotlin中数组是不型变的(invariant),这意味着不能把Array<String> 赋值给 Array<Any>,以防止可能的运行时失败(但是你可以用Array<Out Any>

Kotlin 也有无装箱开销的专门的类来表示原生类型数组:ByteArayShortArrayIntArray 等等。这些类和Array并没有继承关系,但是它们有相同的方法属性集。它们也有相应的工厂方法:

1
2
val intArray1 = intArrayOf(1,1,1,1)
intArray1[0] = intArray1[1] + intArray1[2]

字符串

字符串用 String 类型来表示,字符串是不可变的,字符串的元素 – 字符 可以使用索引运算符访问:s[i],可以用for循环迭代字符串:

1
2
3
4
5
6
7
val str = "Kotlin and Java"
fun sortStr(){
for (s in str){
print(s)
}
}

字符串字面值

Kotlin 有两种类型的字符串字面值:转义字符串可以有转义字符,以及原生字符串可以包含换行和任意文本。转义字符串很像Java字符串:

1
val s = "Hello, world!\n"

转义采用传统的反斜杠方式。参见上面的 字符 查看支持的转义序列。
原生字符串 使用三个引号(“””)分解符括起来,内部没有转义并且可以包含换行和其他字符:

1
2
3
4
val text = """
for (c in "foo")
print(c)
"""

你可以通过 trimMargin() 函数去除前导空格:

1
2
3
4
5
6
val text = """
|Tell me and I forget.
|Teach me and I remember.
|Involve me and I learn.
|(Benjamin Franklin)
""".trimMargin()

默认使用|用作边界前缀,但你可以选择其他字符作为参数传入,例如trimMargin(“a”)。

字符串模板

字符串可以包含模板表达式,即一些小段代码,会求值并把结果合并到字符串中。模板表达式以美元符($)开头,由一个简单的名字构成:

1
2
val str = "Kotlin and Java"
val newStr = "i = $str"

或者使用花括号括起来的任意表达式:

1
2
val str = "Kotlin and Java"
val newStr = "$str.length is ${str.length}" //"Kotlin and Java.length is 15"

原生字符串和转义字符串内部都支持模板,如果你需要在原生字符串中表示字面值$字符(它不支持反斜杠转义),你可以用下列语法:

1
2
3
val price = """
${'$'}0.88
"""


本篇主要学习Kotlin基本类型,包括 数字、字符、布尔、数组字符串


个人博客地址:http://outofmemory.top/
CSDN地址:http://blog.csdn.net/dazhaoDai
GitHub地址:https://github.com/dazhaoDai


Kotlin 学习笔记(三)—— 习惯用法


Kotlin学习笔记系列教程

Kotlin 学习笔记(一)—— 概述、学习曲线、开发工具、参考资料
Kotlin 学习笔记(二)—— 基础语法


下面是一些在Kotlin中广泛使用的语法习惯。

创建DTOs(POJOs/POCOs)

1
2
3
data class Customer(val name: String,val email: String){
}

会为 Customer 类提供以下功能:

  • 所有属性的 getters (对于 var 定义的还有 setters)
  • equals()
  • hashCode()
  • toString()
  • copy()
  • 所有属性的 component1()、 component2()……等等

函数的默认参数

1
fun foo(a: Int = 0, b: String = "") { …… }

过滤 list

1
2
3
4
5
6
val lists = listOf("aa", "ab", "ac","1","2","3","4")
fun sortList(){
val AList = lists.filter { x -> x.startsWith("a") }
}

或者可以更短:

1
2
3
fun sortList(){
val AList = lists.filter {it.startsWith("a") }
}

String 内插

1
println("Name $name")

类型判断

1
2
3
4
5
when (x) {
is Foo //-> ……
is Bar //-> ……
else //-> ……
}

遍历 map/pair型list

1
2
3
4
5
6
7
val map = mapOf("a" to 1,"b" to 2,"c" to 3)
fun sortMap(){
for ((k, v) in map){
print("$k -> $v")
}
}

使用区间

1
2
3
4
5
for (i in 1..100) { …… } // 闭区间:包含 100
for (i in 1 until 100) { …… } // 半开区间:不包含 100
for (x in 2..10 step 2) { …… }
for (x in 10 downTo 1) { …… }
if (x in 1..10) { …… }

只读 list

1
val list = listOf("a", "b", "c")

只读 map

1
val map = mapOf("a" to 1, "b" to 2, "c" to 3)

访问 map

1
2
println(map["key"])
map["key"] = value

延迟属性

1
2
3
val p: String by lazy {
// 计算该字符串
}

扩展函数

1
2
3
fun String.spaceToCamelCase() { …… }
"Convert this to camelcase".spaceToCamelCase()

创建单例

1
2
3
object Resource {
val name = "Name"
}

If not null 缩写

1
2
3
val files = File("Test").listFiles()
println(files?.size)

If not null and else 缩写

1
2
3
val files = File("Test").listFiles()
println(files?.size ?: "empty")

if null 执行一个语句

1
2
val values = ……
val email = values["email"] ?: throw IllegalStateException("Email is missing!")

在可能会空的集合中取第一元素

1
2
val emails = ... // 可能会是空集合
val mainEmail = emails.firstOrNull() ?: ""

if not null 执行代码

1
2
3
4
5
val value = ……
value?.let {
…… // 代码会执行到此处, 假如data不为null
}

映射可空值(如果非空的话)

1
2
3
val value = ……
val mapped = value?.let { transformValue(it) } ?: defaultValueIfValueIsNull

返回 when 表达式

1
2
3
4
5
6
7
8
fun transform(color: String): Int {
return when (color) {
"Red" -> 0
"Green" -> 1
"Blue" -> 2
else -> throw IllegalArgumentException("Invalid color param value")
}
}

“try/catch”表达式

1
2
3
4
5
6
7
8
9
fun test() {
val result = try {
count()
} catch (e: ArithmeticException) {
throw IllegalStateException(e)
}
// 使用 result
}

“if”表达式

1
2
3
4
5
6
7
8
9
fun foo(param: Int) {
val result = if (param == 1) {
"one"
} else if (param == 2) {
"two"
} else {
"three"
}
}

返回类型为 Unit 的方法的 Builder 风格用法

1
2
3
fun arrayOfMinusOnes(size: Int): IntArray {
return IntArray(size).apply { fill(-1) }
}

单表达式函数

1
fun theAnswer() = 42

等价于

1
2
3
fun theAnswer(): Int {
return 42
}

单表达式函数与其它惯用法一起使用能简化代码,例如和 when 表达式一起使用:

1
2
3
4
5
6
fun transform(color: String): Int = when (color) {
"Red" -> 0
"Green" -> 1
"Blue" -> 2
else -> throw IllegalArgumentException("Invalid color param value")
}

对一个对象实例调用多个方法 (with)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Turtle {
fun penDown()
fun penUp()
fun turn(degrees: Double)
fun forward(pixels: Double)
}
//获取实例
val myTurtle = Turtle()
with(myTurtle) { // 画一个 100 像素的正方形
penDown()
for(i in 1..4) {
forward(100.0)
turn(90.0)
}
penUp()
}

Java 7 的 try with resources

1
2
3
4
val stream = Files.newInputStream(Paths.get("/some/file.txt"))
stream.buffered().reader().use { reader ->
println(reader.readText())
}

对于需要泛型信息的泛型函数的适宜形式

1
2
3
4
5
6
// public final class Gson {
// ……
// public <T> T fromJson(JsonElement json, Class<T> classOfT) throws JsonSyntaxException {
// ……
inline fun <reified T: Any> Gson.fromJson(json: JsonElement): T = this.fromJson(json, T::class.java)

使用可空布尔

1
2
3
4
5
6
val b: Boolean? = ……
if (b == true) {
……
} else {
// `b` 是 false 或者 null
}

第三篇文章主要学习Kotlin开发中的习惯用法


个人博客地址:http://outofmemory.top/
CSDN地址:http://blog.csdn.net/dazhaoDai
GitHub地址:https://github.com/dazhaoDai


|