什么是延迟初始化,它有什么用处?

98

什么是对象的懒初始化?如何实现它,有哪些优点?

10个回答

124

延迟初始化(Lazy Initialization)是一种性能优化方法,它推迟(可能代价高昂的)对象创建,直到你实际需要使用它。

一个好的例子就是不提前创建数据库连接,在你需要从数据库获取数据之前才进行创建。

这样做的主要原因是,如果你从未需要使用该对象,通常可以避免完全创建它。


43
不言而喻,但以防万一他可能没有注意到:之所以这是一种优化,是因为你常常会发现根本不需要创建,同时也可以节省在电脑繁忙时进行一些工作的时间,直到它不那么繁忙的时候再执行。 - Joel Coehoorn
好的评论。我已经修改了答案,以纳入您的观点。 - Bevan
在以下的SO问题中,提出了一个非常好(而且不同寻常)使用Lazy的用例/原因:https://dev59.com/I2kv5IYBdhLWcg3wfA4P#15894928。这个建议恰好解决了我们需要解决的问题 - 使用Lazy作为关键原则。 - lev haikin

62
如其他人所提到的,惰性初始化是将初始化延迟到组件或对象被使用时。您可以将惰性初始化视为“你不需要它”(YAGNI)原则的运行时应用。
从应用程序角度来看,惰性初始化的优点是用户不必为他们不会使用的功能支付初始化时间。假设您要一开始就初始化应用程序的每个组件。这可能会导致潜在的长启动时间 - 用户必须等待几十秒或几分钟,才能使用您的应用程序。他们正在等待和支付可能永远不会使用或不会立即使用的功能的初始化。
相反,如果您推迟初始化这些组件直到使用时,您的应用程序将启动得更快。用户仍然需要在使用其他组件时支付启动成本,但这个成本将分摊到程序运行中,并且用户可以将这些对象的初始化时间与他们正在使用的功能相关联。

太遗憾了,我只能给+1!顺便说一下,理由很好。 - Rajendra Uppal
3
YAGNI 的意思是“你不会需要它[该功能,代码]”。懒惰初始化是一种优化,本身就是一种特性。我更愿意说反过来:除非你确定需要懒惰初始化,否则不要使用它,因为你不会需要它,就是 YAGNI。 - Flavius

23

懒加载是一种将对象的创建推迟到首次使用对象时才进行的概念。如果使用得当,可以显著提高性能。

就我个人而言,在 .NET 2.0 中创建自己的 ORM 时使用了懒加载。从数据库加载集合时,集合中的实际项是惰性初始化的。这意味着集合可以快速创建,但每个对象只在需要时加载。

如果您熟悉 Singleton 模式,您可能也看到了懒加载的应用。

public class SomeClassSingleton
{
    private static SomeClass _instance = null;

    private SomeClassSingleton()
    {
    }

    public static SomeClass GetInstance()
    {
        if(_instance == null)
            _instance = new SomeClassSingleton();

        return _instance;
    }
}
在这种情况下,SomeClass的实例直到被SomeClassSingleton使用者首次需要时才会被初始化。

移除显式的私有变量设置为“null”以进一步提高速度;)不管怎样,这是多余的。 - nicodemus13
不错的观点,SomeClassSingleton必须是SomeClass的相等或更低级别的类。 - Fredrick Gauss
我不知道是否有人会阅读旧的回复,但我认为私有静态 get instance 方法应该是公共的? - Biscuit128
@Biscuit128 - 我确实看过它们,是的,它应该被更新。我更新了它,以防还有人阅读它。 - Justin Niessner
应该不是 _instance = new SomeClassSingleton(); 而是 _instance = new SomeClass(); 吧?还是我从不同的角度理解了这个问题。 - ashishdhiman2007

7

在计算机领域中,“惰性求值”通常指将处理推迟到实际需要时。主要思想是,如果你最终不需要它,或者在使用它之前该值会改变,有时可以避免昂贵的操作。

这种情况的一个简单例子是System.Exception.StackTrace。这是一个异常上的字符串属性,但是只有在访问它时才会实际构建。内部实现类似于:

String StackTrace{
  get{
    if(_stackTrace==null){
      _stackTrace = buildStackTrace();
    }
    return _stackTrace;
  }
}

这样可以避免在有人想查看时才调用buildStackTrace而产生的开销。
属性是提供这种行为的简单方式之一。

2
确实,但我不确定为什么您会在多个线程之间共享异常的引用。 - Dolphin

4
对象的惰性初始化意味着其创建被推迟到首次使用时。 (对于这个主题,惰性初始化和惰性实例化是同义词。)惰性初始化主要用于提高性能,避免浪费计算资源和减少程序内存需求。以下是最常见的情况:
当您有一个昂贵的对象需要创建,而程序可能不使用它。例如,假设在内存中有一个客户对象,其中包含一个包含订单对象数组的Orders属性,为了初始化这些订单对象需要与数据库建立连接。如果用户从未请求显示订单或在计算中使用数据,则没有必要在对象未被使用时使用系统内存或计算周期来创建它。通过使用Lazy声明Orders对象以进行惰性初始化,您可以避免在对象未被使用时浪费系统资源。
当您有一个昂贵的对象需要创建,但希望将其创建推迟到其他昂贵操作完成之后。例如,假设您的程序在启动时加载多个对象实例,但只有一些对象需要立即使用。通过推迟未被需要的对象的初始化,您可以提高程序启动的性能。
虽然您可以编写自己的代码执行惰性初始化,但我们建议使用Lazy代替。 Lazy及其相关类型还支持线程安全并提供一致的异常传播策略。

3

在这里,您可以阅读有关延迟初始化的内容以及示例代码。

  • 当您拥有一个昂贵的对象,并且程序可能不会使用它时。例如,假设您在内存中有一个客户对象,它具有包含大量订单对象的订单属性,为了初始化需要数据库连接。如果用户从未要求显示订单或在计算中使用数据,则没有理由使用系统内存或计算周期来创建它。通过使用Lazy来声明订单对象进行延迟初始化,当对象未使用时,可以避免浪费系统资源。

  • 当您拥有昂贵的对象,并且希望将其创建推迟到其他昂贵的操作完成之后时。例如,假设您的程序在启动时加载多个对象实例,但只有一些需要立即使用。您可以通过推迟未被使用的对象的初始化,提高程序启动性能,直到必需的对象已创建。


1
//Lazy instantiation delays certain tasks. 
//It typically improves the startup time of a C# application.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LazyLoad
{
    class Program
    {
        static void Main(string[] args)
        {
            Lazy<MyClass> MyLazyClass = new Lazy<MyClass>(); // create lazy class
            Console.WriteLine("IsValueCreated = {0}",MyLazyClass.IsValueCreated); // print value to check if initialization is over

            MyClass sample = MyLazyClass.Value; // real value Creation Time
            Console.WriteLine("Length = {0}", sample.Length); // print array length

            Console.WriteLine("IsValueCreated = {0}", MyLazyClass.IsValueCreated); // print value to check if initialization is over
            Console.ReadLine();
        }
    }

    class MyClass
    {
        int[] array;
        public MyClass()
        {
            array = new int[10];

        }

        public int Length
        {
            get
            {
                return this.array.Length;
            }
        }
    }
}


// out put

// IsValueCreated = False
// Length = 10
// IsValueCreated = True

1

就我目前对于延迟初始化的理解,程序不会一次性加载/请求所有数据。它会在需要使用时从例如SQL服务器中请求。

如果你有一个包含大量子表的大型表格数据库,并且除非进入“编辑”或“查看详细信息”,否则不需要来自其他表格的详细信息,则延迟初始化将帮助应用程序更快地运行,并在需要时首先“懒加载”详细数据。

在SQL或LINQ中,您可以针对每个数据元素设置此“设置”。

希望这对您的问题有所帮助?

Web客户端(例如浏览器)也是如此。图像在HTML之后进行“懒加载”,而如果正确使用,AJAX也是“一种延迟初始化”。


1
到目前为止提到的数据库示例很好,但不仅限于数据访问层。您可以将相同的原则应用于任何性能或内存可能成为问题的情况。一个很好的例子(虽然不是.NET)是在Cocoa中,您可以等待用户请求窗口以实际从nib加载它(及其关联对象)。这可以帮助保持内存使用量并加快初始应用程序加载速度,特别是当您谈论像偏好设置窗口这样的东西时,这些窗口直到以后某个时间甚至永远都不需要。

-2

其他人谈到了它的好处,但是要小心它的问题; 就像 Uncle Bob 所说,当使用惰性初始化/评估时,您会

  1. 依赖于硬编码对象及其构造函数所需的所有依赖项。
  2. 测试存在问题;我们将构造逻辑与正常运行时逻辑混合在一起,因此我们有很多测试
  3. 同2,我们违反了 SRP(单一职责原则)
  4. 同1,包含 creator 方法的类应该如何知道惰性创建的对象(具有特定状态)是否适用于所有用例?
  5. 程序中所有地方都散乱地实例化对象,这是一种丑陋的策略

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接