唯一的对象

重点 (Top highlight)

In Java, the static keyword is used to denote methods and properties that belong to an object, not to an instance of an object. The static keyword is also used to create Singletons, one of the most widely used design patterns. Singletons help you to create a single instance of an object, which can be accessed and shared by other objects.

在Java中, static关键字用于表示属于对象而不是对象 实例的方法和属性。 static关键字还用于创建Singletons (最广泛使用的设计模式之一)。 单例帮助您创建一个对象的单个实例,其他对象可以访问和共享该实例。

Kotlin has a more elegant way to deal with this. You can use a single keyword: object, to implement the Singleton pattern. Read on to find out the differences between implementing a Singleton in Java vs Kotlin, how you can create Singletons in Kotlin without using the static keyword (spoiler this is achieved by using the object keyword), and to find out what’s happening under the hood when you’re using object.

Kotlin有一个更优雅的方式来处理此问题。 您可以使用单个关键字: object来实现Singleton模式。 继续阅读以了解在Java和Kotlin中实现Singleton,在不使用static关键字的情况下如何在Kotlin中创建Singleton(通过使用object关键字来实现这一点)之间的区别,以及了解在什么情况下幕后发生的事情。您正在使用object

First, let’s back up a bit and find out why we need a single instance of an object, aka Singleton.

首先,让我们备份一下,找出为什么我们需要一个对象的单个实例,也就是Singleton。

什么是单例? (What is a Singleton?)

Singleton is a design pattern which ensures that a class has only one instance and provides a global point of access to the object. The Singleton pattern is particularly useful for objects which need to be shared between different parts in your app and for resources that are expensive to create.

Singleton是一种设计模式,可确保一个类只有一个实例,并提供对对象的全局访问点。 Singleton模式对于需要在应用程序中不同部分之间共享的对象以及创建昂贵的资源特别有用。

Java中的Singleton (Singleton in Java)

To guarantee that a class has only one instance, you need to control how the object is created. To create a class with only one instance, make the constructor private and create a publicly accessible static reference of the object. While doing this, you don’t really want to create the Singleton at startup since Singletons are used for objects which are expensive to create. To achieve this, provide a static method which checks if the object is created. The method must return the previously created instance or call the constructor and return the instance.

为了保证一个类只有一个实例,您需要控制如何创建对象。 要创建仅包含一个实例的类,请将构造函数设为私有,并创建该对象的可公开访问的静态引用。 这样做时,您实际上并不想在启动时创建Singleton,因为Singleton用于创建昂贵的对象。 为此,请提供一个静态方法来检查是否创建了对象。 该方法必须返回先前创建的实例或调用构造函数并返回该实例。

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->


public class Singleton{
    private static Singleton INSTANCE;
    private Singleton(){}
    public static Singleton getInstance(){
        if (INSTANCE == null){
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
    private int count = 0;
    public int count(){ return count++; }
}

The above code seems fine, but there is a major issue. This code is not thread-safe. At any time one thread can pass this if check but be put on hold while another thread creates the singleton. When the first thread resumes inside the if block, it creates another instance.

上面的代码看起来不错,但是有一个主要问题。 此代码不是线程安全的 。 在任何时候,一个线程都可以通过此检查,但在另一个线程创建单例时将其搁置。 当第一个线程在if块内恢复时,它将创建另一个实例。

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->


public class Singleton{
    private static Singleton INSTANCE;
    private Singleton(){}
    public static Singleton getInstance(){
        if (INSTANCE == null) {                // Single Checked
            synchronized (Singleton.class) {
                if (INSTANCE == null) {        // Double checked
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
    private int count = 0;
    public int count(){ return count++; }
}

To fix the threading issue, you can use double checked locking. With double checked locking, if the instance is null, synchronized keyword creates a lock and a second check ensures the instance is still null. If the instance is null, then creates the Singleton. Yet, this is not enough and the instance also needs to be marked volatile. Volatile keyword tells the compiler that a variable might be modified asynchronously by concurrently running threads.

要解决线程问题,您可以使用双重检查锁定。 使用双重检查锁定时,如果实例为null ,则synchronized关键字将创建一个锁,然后进行第二次检查以确保该实例仍为null 。 如果实例为null ,则创建Singleton。 但是,这还不够,实例还需要标记为volatile Volatile关键字告诉编译器可以通过并发运行线程异步修改变量。

All of this leads to a lot of boilerplate that you need to repeat each time you need a singleton. Since this code is too complicated for such a simple task, enums are used for creating singletons in Java most of the time.

所有这些导致很多样板,每次需要单例时都需要重复。 由于此代码对于这样一个简单的任务来说太复杂了,因此大多数时候都使用枚举在Java中创建单例。

Kotlin的辛格尔顿 (Singleton in Kotlin)

Now, let’s take a look at Kotlin. Kotlin doesn’t have static methods or fields so how can we create a Singleton in Kotlin?

现在,让我们看一下Kotlin。 Kotlin没有静态方法或字段,那么如何在Kotlin中创建Singleton?

Actually, Android Studio/IntelliJ can help us to understand. When you convert the Singleton code in Java to Kotlin, all static properties and methods are moved to a companion object.

实际上,Android Studio / IntelliJ可以帮助我们理解。 当您将Java中的Singleton代码转换为Kotlin时,所有静态属性和方法都将移至一个companion object

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->


class Singleton private constructor() {
    private var count = 0
    fun count(): Int {
        return count++
    }


    companion object {
        private var INSTANCE: Singleton? = null// Double checked


        // Single Checked
        val instance: Singleton?
            get() {
                if (INSTANCE == null) { // Single Checked
                    synchronized(Singleton::class.java) {
                        if (INSTANCE == null) { // Double checked
                            INSTANCE =
                                Singleton()
                        }
                    }
                }
                return INSTANCE
            }
    }
}

The converted code works as expected but we can make it simpler. To simplify the code, remove the constructor and companion keyword from object. The differences between object and companion objects are covered later in this post.

转换后的代码可以按预期工作,但是我们可以使其更简单。 为了简化代码,请从object除去构造函数和companion关键字。 objectcompanion objects之间的区别将在本文后面介绍。

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->


object Singleton {
    private var count: Int = 0


    fun count() {
        count++
    }
}

When you want to use the count() method, you can access it over the Singleton object. In Kotlin, object is a special class that only has one instance. If you create a class with the object keyword instead of class, the Kotlin compiler makes the constructor private, creates a static reference for the object, and initializes the reference in a static block.

当您想使用count()方法时,可以通过Singleton对象访问它。 在Kotlin中, object是一个只有一个实例的特殊类。 如果您使用object关键字而不是class创建class ,则Kotlin编译器会将构造函数设为私有,为该对象创建静态引用,并在静态块中初始化该引用。

Static blocks are called only once when the static field is first accessed. The JVM handles the static blocks in a similar way to synchronized blocks, even though they don’t have the synchronized keyword. When this Singleton class is initializing, the JVM acquires a lock on the synchronized block, making it impossible for another thread to access it. When the lock is released, the Singleton instance is already created so the static block won’t execute again. This guarantees that there is only one instance of the Singleton, which fulfills the Singleton contract. Plus, the object is both thread-safe and lazily created the first time it is accessed. Voila!

首次访问静态字段时,静态块仅被调用一次。 JVM处理静态块的方式与同步块类似,即使它们没有synchronized关键字也是如此。 当此Singleton类初始化时,JVM会在同步块上获得一个锁,从而使另一个线程无法访问它。 释放锁定后,已经创建了Singleton实例,因此不会再次执行静态块。 这样可以保证只有一个Singleton实例可以满足Singleton合同。 另外,该对象是线程安全的,并且在第一次访问该对象时是延迟创建的。 瞧!

Let’s take a look at decompiled Kotlin byte code to understand what’s happening under the hood.

让我们看一下反编译的Kotlin字节码,以了解幕后情况。

To check the byte code of a Kotlin class, select Tools > Kotlin > Show Kotlin Bytecode. Once Kotlin byte code is displayed, click Decompile to reveal the decompiled Java code.

要检查Kotlin类的字节码,请选择工具> Kotlin>显示Kotlin字节码 。 显示Kotlin字节码后,单击“ 反编译”以显示反编译的Java代码。

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->


public final class Singleton {
   private static int count;
   public static final Singleton INSTANCE;
   public final int getCount() {return count;}
   public final void setCount(int var1) {count = var1;}
   public final int count() {
      int var1 = count++;
      return var1;
   }
   private Singleton() {}
   static {
      Singleton var0 = new Singleton();
      INSTANCE = var0;
   }
}

However, object comes with a limitation. object declarations can not have constructors which means they can not take parameters. Even if they did, it would be impossible to pass a parameter since the non-static parameter passed in the constructor isn’t accessible from the static block.

但是, object具有局限性。 对象声明不能具有构造函数,这意味着它们不能采用参数。 即使它们做到了,也将无法传递参数,因为无法从静态块访问构造函数中传递的非静态参数。

⚠️The static initialization blocks, just like other static methods, can only access static properties of a class. Static blocks are called before the constructors so there is no way they can access properties of an object or parameters passed in the constructor.

⚠️静态初始化块,就像其他静态方法,可以一类的只能访问静态属性。 静态块在构造函数之前被调用,因此它们无法访问构造函数中传递的对象的属性或参数。

伴随对象 (companion object)

companion object is similar to object. companion object is always declared in a class and their properties can be accessed by using the host object. The companion object doesn’t require a name. If the companion object has a name, the caller can access the members using the companion object’s name.

companion object类似于objectcompanion object始终在类中声明,并且可以使用宿主对象访问其属性。 companion object不需要名称。 如果companion object具有名称,则调用者可以使用companion object的名称访问成员。

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->


class SomeClass {
    //…
    companion object {
        private var count: Int = 0
        fun count() {
            count++
        }
    }
}
class AnotherClass {
    //…
    companion object Counter {
        private var count: Int = 0
        fun count() {
            count++
        }
    }
}
// usage without name
SomeClass.count()
// usage with name
AnotherClass.Counter.count()

For example, here we have the similar companion objects with and without a name. Any caller can access the count() method on SomeClass, just like it is a static member of SomeClass. Alternatively any caller can access the count() method by using Counter just like a static member of AnotherClass.

例如,这里我们有带有或不带有名称的相似的伴随对象。 任何调用者都可以访问SomeClass上的count()方法,就像它是SomeClass的静态成员一样。 另外,任何调用者都可以使用Counter来访问count()方法,就像AnotherClass的静态成员AnotherClass

The companion object decompiles into an inner class with a private constructor. The host class initializes the inner class through a synthetic constructor, which only it can access. The host class keeps a public reference to the companion object which is accessible from other classes.

伴随对象使用私有构造函数反编译为内部类。 主机类通过综合构造函数初始化内部类,只有它可以访问。 宿主类保留对可从其他类访问的伴随对象的公共引用。

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->


public final class AnotherClass {
    private static int count;
    public static final AnotherClass.Counter Counter = new AnotherClass.Counter((DefaultConstructorMarker)null);


    public static final class Counter {
        public final void count() {
            AnotherClass.count = AnotherClass.count + 1;
        }
        private Counter() { }
        // $FF: synthetic method
        public Counter(DefaultConstructorMarker $constructor_marker) {
            this();
        }
    }
    
    public static final class Companion {
        public final void count() {
            AnotherClass.count = AnotherClass.count + 1;
        }
        private Companion() {}
    }
}

对象表达式 (Object Expressions)

So far we’ve seen the object keyword used in object declarations. object keyword can be used in object expressions as well. When used as an expression, object keyword helps you to create anonymous objects and anonymous inner classes.

到目前为止,我们已经看到了对象声明中使用的object关键字。 object关键字也可以在对象表达式中使用。 当用作表达式时, object关键字可帮助您创建匿名对象和匿名内部类。

Let’s say you need a temporary object to hold some values. You can declare and initialize your object with the desired values on the spot and access them later.

假设您需要一个临时对象来保存一些值。 您可以当场用所需的值声明和初始化对象,以后再访问它们。

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->


val tempValues = object : {
    var value = 2
    var anotherValue = 3
    var someOtherValue = 4
}


tempValues.value += tempValues.anotherValue

In the generated code, this translates into an anonymous Java class, marked by <undefinedtype>, to store the anonymous object with generated getters and setters.

在生成的代码中,这将转换为以<undefinedtype>标记的匿名Java类,以存储带有生成的getter和setter的匿名对象。

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->


<undefinedtype> tempValues = new Object() {
    private int value = 2;
    private int anotherValue = 3;
    private int someOtherValue = 4;


    // getters and setters for x, y, z
    //...
};

The object keyword also helps you to create anonymous classes without writing any boilerplate code. You can use an object expression and the Kotlin compiler generates the wrapper class declaration to create an anonymous class.

object关键字还可以帮助您创建匿名类,而无需编写任何样板代码。 您可以使用对象表达式,并且Kotlin编译器会生成包装器类声明以创建匿名类。

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->


val t1 = Thread(object : Runnable {    
    override fun run() {
         //do something 
    }
})
t1.start()


//Decompiled Java
Thread t1 = new Thread((Runnable)(new Runnable() {
     public void run() {
     
     }
}));
t1.start();

The object keyword helps you create thread-safe singletons, anonymous objects and anonymous classes with less code. With object and companion object, Kotlin generates all the code to achieve the same functionality offered by static keyword. Plus, you can use object expressions to create anonymous objects and classes without any boilerplate code.

object关键字可帮助您以更少的代码创建线程安全的单例,匿名对象和匿名类。 使用objectcompanion object ,Kotlin生成所有代码以实现static关键字提供的相同功能。 另外,您可以使用对象表达式创建匿名对象和类,而无需任何样板代码。

翻译自: https://medium.com/androiddevelopers/the-one-and-only-object-5dfd2cf7ab9b

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值