一起来学Kotlin:概念:4. Kotlin 函数注解:Suppress,Volatile, Synchronized, Bindable, RequiresApi,SerializedName
这篇博客我们解释 Kotlin 函数注解:Suppress,Volatile, Synchronized, Bindable, RequiresApi,SerializedName 等。
文章目录
1. Deprecated
如果需要废弃一个方法,只需要在方法钱加上 @Deprecated
即可。
Kotlin 对于 @Deprecated
的定义:
/**
* Marks the annotated declaration as deprecated.
*
* A deprecated API element is not recommended to use, typically because it's being phased out or a better alternative exists.
*
* To help removing deprecated API gradually, the property [level] could be used.
* Usually a gradual phase-out goes through the "warning", then "error", then "hidden" or "removed" stages:
* - First and by default, [DeprecationLevel.WARNING] is used to notify API consumers, but not to break their compilation or runtime usages.
* - Then, some time later the deprecation level is raised to [DeprecationLevel.ERROR], so that no new Kotlin code can be compiled
* using the deprecated API.
* - Finally, the API is either removed entirely, or hidden ([DeprecationLevel.HIDDEN]) from code,
* so its usages look like unresolved references, while the API remains in the compiled code
* preserving binary compatibility with previously compiled code.
*
* @property message The message explaining the deprecation and recommending an alternative API to use.
* @property replaceWith If present, specifies a code fragment which should be used as a replacement for
* the deprecated API usage.
* @property level Specifies how the deprecated element usages are reported in code.
* See the [DeprecationLevel] enum for the possible values.
*/
@Target(CLASS, FUNCTION, PROPERTY, ANNOTATION_CLASS, CONSTRUCTOR, PROPERTY_SETTER, PROPERTY_GETTER, TYPEALIAS)
@MustBeDocumented
public annotation class Deprecated(
val message: String,
val replaceWith: ReplaceWith = ReplaceWith(""),
val level: DeprecationLevel = DeprecationLevel.WARNING
)
所以说,Deprecated
的输入有三个,一个是message
,解释弃用并建议使用替代 API 的信息;第二个是 replaceWith
,指定可用于替换已弃用的函数,属性或类的代码片段;而 level
则指定如何在代码中报告已弃用的元素用法(WARNING, ERROR,HIDDEN三个选项)。后两个参数是有预设值的,第一个参数没有。所以,如果需要使用Deprecated
函数注解的话,例如下面代码:
@Deprecated("xxx")
fun testKt(){
}
replaceWith
的一个例子:
2. Suppress
如果需要消除一些编译时的警告,可以使用 @Suppress("xxx")
。
比如:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@Suppress("UNUSED VARIABLE")
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this,R.layout.activity_main)
}
}
又比如:
fun unChecked(){
val list: List<Any> = emptyList()
@Suppress("UNCHECKED_CAST")
list as List<String>
}
3. Volatile
为了强制变量中的更改立即对其他线程可见,我们可以使用注解 @Volatile
,在以下示例中为:
@Volatile var shutdownRequested = false
这将保证在值更改后其他线程的更改可见性。 这意味着如果线程 X 修改了 shutdownRequested
的值,线程 Y 将能够立即看到更改。
@Volatile
的官方解释是:
Marks the JVM backing field of the annotated property as volatile, meaning that writes to this field are immediately made visible to other threads.
4. Synchronized
总结:Java 有 synchronized
关键字,可以将其应用于方法以确保一次只有一个线程可以访问它们。进入同步方法的线程获得锁(被锁定的对象是包含类的实例),并且在释放锁之前没有其他线程可以进入该方法。Kotlin通过 @Synchronized
注解提供了相同的功能。
在多线程的世界中,我们需要跨线程访问共享对象,如果我们不同步我们的工作,就会发生不希望的情况。比如下面例子:
import kotlinx.coroutines.*
fun main() = runBlocking {
var sharedCounter = 0
val scope = CoroutineScope(newFixedThreadPoolContext(4, "synchronizationPool")) // We want our code to run on 4 threads
scope.launch {
val coroutines = 1.rangeTo(1000).map { //create 1000 coroutines (light-weight threads).
launch {
for(i in 1..1000){ // and in each of them, increment the sharedCounter 1000 times.
sharedCounter++
}
}
}
coroutines.forEach {
corotuine->
corotuine.join() // wait for all coroutines to finish their jobs.
}
}.join()
println("The number of shared counter should be 1000000, but actually is $sharedCounter")
}
在上面的例子中,我们在 4 个线程上启动了 1000 个协程,并且每个协程将 sharedCounter
递增 1000 倍,所以 sharedCounter
的最终值应该是 1000000。但我们运行上述代码后会发现,并不会输出这个值。
在我们从技术上解释这里发生了什么之前,假设有一个思考室(柜台),很多人(线程)想要使用它,但一次只允许一个人。 这个房间有一扇门,当房间被占用时,它是关闭的。 在这种情况下发生的情况是,当一个人在房间内时,其他人也可以打开门,进来使用房间。 但是门需要锁!
在上面的代码中,为了增加 sharedCounter
,每个线程都尝试执行以下操作以在内部增加 sharedCounter
值:
- 获取其当前值,
- 将其存储在临时变量中,并将临时变量加 1,
- 将临时变量保存到 sharedCounter。
但是,如果一个线程获取当前值,并且由于我们处于多线程世界中,另一个线程跳入并尝试获取当前值怎么办?他们都将获得相同的价值!因此,它们中的每一个都将该值增加 1,并存储相同的值。
这个问题可能以类似的其他方式发生,例如,一个线程超过了第二个级别,但在存储它之前,其他线程递增并保存 sharedCounter
值,当第一个线程跳转到它的第三步时,它保存了一个旧的 sharedCounter
的版本。这就是为什么最终值不是我们所期望的。
如果我们使用 Synchronized
,代码如下:
import kotlinx.coroutines.*
var sharedCounter = 0
@Synchronized fun updateCounter(){
sharedCounter++
}
fun main() = runBlocking {
val scope = CoroutineScope(newFixedThreadPoolContext(4, "synchronizationPool")) // We want our code to run on 4 threads
scope.launch {
val coroutines = 1.rangeTo(1000).map { //create 1000 coroutines (light-weight threads).
launch {
for(i in 1..1000){ // and in each of them, increment the sharedCounter 1000 times.
updateCounter() // call the newly created function that is now synchronized
}
}
}
coroutines.forEach {
corotuine->
corotuine.join() // wait for all coroutines to finish their jobs.
}
}.join()
println("The number of shared counter is $sharedCounter")
}
如我们所见,输出值始终是正确的。 在这个场景中,Synchronized
类似于门上的锁,只有一把钥匙,人们需要用它来开门和锁门。 因此,当一个人(一个线程)进入房间使用时,除非该人离开房间并归还钥匙,否则其他人都无法进入。
其他例子:
@Database(entities = [Note::class], version = 1)
abstract class NoteDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDao
companion object {
private var instance: NoteDatabase? = null
@Synchronized
fun getInstance(ctx: Context): NoteDatabase {
if(instance == null)
instance = Room.databaseBuilder(ctx.applicationContext, NoteDatabase::class.java,
"note_database")
.fallbackToDestructiveMigration()
.addCallback(roomCallback)
.build()
return instance!!
}
private val roomCallback = object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
populateDatabase(instance!!)
}
}
private fun populateDatabase(db: NoteDatabase) {
val noteDao = db.noteDao()
subscribeOnBackground {
noteDao.insert(Note("title 1", "desc 1", 1))
noteDao.insert(Note("title 2", "desc 2", 2))
noteDao.insert(Note("title 3", "desc 3", 3))
}
}
}
}
5. Bindable
数据绑定有两种与数据交互的基本方式:BaseObservable 类及其关联的 @Bindable 注释,以及 LiveData 可观察包装器(LiveData observable wrapper)。
如果您从 BaseObservable 继承您的 ViewModel,那么您注释为 @Bindable 的所有变量都将添加到 BR 全局引用对象中。 BR 是可绑定资源(Bindable Resource)。 与您的 R 引用对象类似,这只是引用特定视图的静态整数的集合。
比如,我们在build.gradle(:app)
中,我们先设置dataBinding:
buildFeatures{
dataBinding = true
}
在fragment_login.xml
中,我们设置了需要需要binding的ViewModel:
<data>
<variable
name="myLoginViewModel"
type="com.example.mydatabaseapp.login.LoginViewModel" />
</data>
我们在LoginFragment.kt
中
loginViewModel = ViewModelProvider(this, factory).get(LoginViewModel::class.java)
binding.myLoginViewModel = loginViewModel
最后,我们在 LoginViewModel.kt
中使用 @bindable
:
@Bindable
val inputUsername = MutableLiveData<String>()
@Bindable
val inputPassword = MutableLiveData<String>()
6. RequiresApi
在我们写代码的时候,我们可能会碰到一个问题,需要调用一些之前版本的API,比如下面这个例子:
val dateTime = LocalDateTime.now()
val currentTime = dateTime.format(DateTimeFormatter.ofPattern("M/d/y H:m:ss"))
如果我们不加 @RequiresApi(Build.VERSION_CODES.O)
的话,会出现下面这类报错:
Call requires API level 26 (current min is 21): java.time.LocalDateTime#now
所以,我们需要调用API 26,Android 8 (Oreo),即,在函数的上一行加上 @RequiresApi(Build.VERSION_CODES.O)
即可。
Android API与Android版本对应关系
7. SerializedName
我们通常使用@SerializedName
批注来映射JSON字段。比如下面的代码:
data class Country(
// 我们使用@SerializedName批注来映射JSON字段,
// name是JSON字段的,我们将其映射为countryName,用于我们这个项目
@SerializedName("name")
val countryName: String?,
@SerializedName("capital")
val capital: String?,
@SerializedName("flagPNG")
val flag: String?
)
我们读取的JSON文件长这样:
[
{
"alpha2Code": "AF",
"alpha3Code": "AFG",
"altSpellings": [
"AF",
"Af\u0121\u0101nist\u0101n"
],
"area": 652230,
"borders": [
"IRN",
"PAK",
"TKM",
"UZB",
"TJK",
"CHN"
],
"callingCodes": [
"93"
],
"capital": "Kabul",
"currencies": [
{
"code": "AFN",
"name": "Afghan afghani",
"symbol": "\u060b"
}
],
"demonym": "Afghan",
"flagPNG": "https://raw.githubusercontent.com/DevTides/countries/master/afg.png",
"gini": 27.8,
"languages": [
{
"iso639_1": "ps",
"iso639_2": "pus",
"name": "Pashto",
"nativeName": "\u067e\u069a\u062a\u0648"
},
{
"iso639_1": "uz",
"iso639_2": "uzb",
"name": "Uzbek",
"nativeName": "O\u02bbzbek"
},
{
"iso639_1": "tk",
"iso639_2": "tuk",
"name": "Turkmen",
"nativeName": "T\u00fcrkmen"
}
],
...
]
我们希望将JSON文件中的name
,capital
,flagPNG
的值分别提取出来,然后放到我们自己的数据类 Country
里,所以我们使用@SerializedName
来将name
映射到countryName
,capital
映射到capital
,flagPNG
映射到flag
。
另外一个例子,比如我们这个json数据长这样:
//json1
{
"class":"1"
"public":"2"
}
//json2
{
"a":"1"
"b":"2"
}
如 json1 中数据我们如果建立一个相同字段名的实体类显然是不可行的,因为class和public都是java/kotlin语言中的关键字,@SerializedName注解就起到作用了。
public class Json1{
@SerializedName("class")
private String cla;
@SerializedName("public")
private String pub;
}
Reference
- Kotlin之Deprecated和Suppress注解使用
- Kotlin Suppress 的非常规用法
- How to use volatile in Kotlin
- @Volatile
- Volatile Properties in Kotlin
- Synchronization, Thread-Safety and Locking Techniques in Java and Kotlin
- Synchronized methods
- Reducing Data Binding Boilerplate With Kotlin
- @TargetApi和@RequiresApi含义
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.O 何意
- Gson中@SerializedName 注解使用
- @SerializedName注解的简单使用
- kotlin - 使用@field :SerializedName annotation instead of @SerializedName?的目的是什么