GVKun编程网logo

使用Optional来减少null检查(optional处理null)

9

在这里,我们将给大家分享关于使用Optional来减少null检查的知识,让您更了解optional处理null的本质,同时也会涉及到如何更有效地api–请说明使用Option的orNull方法、c#

在这里,我们将给大家分享关于使用Optional来减少null检查的知识,让您更了解optional处理null的本质,同时也会涉及到如何更有效地api – 请说明使用Option的orNull方法、c# – bool检查比null检查更快吗?、c# – MVC ActionResult – Nullable或Optional参数、Collections.emptyList()而不是null检查?的内容。

本文目录一览:

使用Optional来减少null检查(optional处理null)

使用Optional来减少null检查(optional处理null)

由来

平常我们使用null检查在项目中简直太常见了,从数据库中查询到的数据可能不存在返回null,service中处理中发现不存在返回一个null,在互相调用的时候每次都需要做(if obj != null)的判断,散落在程序中很难看。更难看的是当你遗漏了一个空指针判断,程序就会无情的给你抛出一个NPE让你知道谁才是老大。

假设我们有一个用户类User,用户可以有收货地址类Addr,收货地址中肯定会有省province属性啦,如果我们要获取用户的收货省,简单的来一个链式操作user.getAddr().getProvince(),这样操作可以么?

以往的null检查方式

用户在新注册之后可能是没有收货地址的,因此user.getAddr()返回null,再调用就会给你点颜色看看。因此我们必须要先判断null,然后才能决定是否可以调用getProvince方法

public String getUserConsigneeProvince(User user){
  if (user != null){
     Addr addr = user.getAddr();
     if (addr != null){
       return addr.getProvince();
     }
  }
  return null;  
}

或者使用防御式编程方式(以前我喜欢的编程方式),在检查到null后直接处理

public String getUserConsigneeProvince(User user){
  if (user == null){
    return null;
  }
  Addr addr = user.getAddr();
  if (addr == null){
    return null;
  }
  return addr.getProvince();
}

在这里我们也很鸡贼的返回了一个null来表示用户的收货省不存在,给以后的使用这个方法的人(当然也包括自己)挖了一个坑,如果直接返回给前端,那页面上就会有一个大大的null等着QA给你提bug吧。

1.8中对Null的处理

在Haskell中有一个Maybe类来处理可能的null,Scala中也提供了Option[T]来表示,Kotlin中使用在调用后加?来安全的处理返回值为null的情况。Java1.8借鉴了Haskell和Scala中方式,提供了一个Optional类来帮助程序员避免null检查。

设计哲学

我们看到在获取收货省的api中返回值直接是一个String,我们是不可能从这个返回值上面看出用户的收货省是否存在的,因此在设计时,对于可能不存在的值,我们选择返回一个Optional来表示你需要处理不存在的情况。

因此我们把返回值改写成Optional<String>

public Optional<String> getUserConsigneeProvince(User user){
  if (user == null){
    return Optional.empty();
  }
  Addr addr = user.getAddr();
  if (addr == null){
    return Optional.empty();
  }
  return Optional.of(addr.getProvince());
}

不过这并没有改善我们的内部处理,还是判断了null,因此我们先来看一下Optional的API,来来看一下用Optional如何简化我们的书写方式。

处理方式

API

Optional<T> Api:

名称 返回值 参数 说明
isPresent boolean void 如果不存在值则false,存在为true
ifPresent void Consumer 如果存在则调用Consumer消费值
map Function Optional<U> 对值做映射
flatMap Function Optional<U> 对值做扁平映射
orElse T T 存在返回包含的值,不存在就返回这里面的值
orElseGet T Supplier<T> 返回用supplier 生成的一个值
filter Predicate Optional<T> 过滤值
get T void 获取包装的值,不存在会抛出异常

可以看到API设计中使用到了函数式相关的东西,使得我们调用的时候可以使用lambda或者行为参数化的方式更方便的使用 在map和flatMap等API中隐含了null的判断,使得我们不用在应用中显式的去做null判断了。

一个参数的实践

接下来就是见证奇迹的时刻:

public Optional<String> getUserConsigneeProvince(User user){
  return Optional.ofNullable(user).map(User::getAddr).map(Addr::getProvince);
}

我们使用1行代码代替了6行,而且表达的更加清晰

当然如果这个API很多人使用,很难改变返回值的话我们可以使用orElse做值处理,如下:

public String getUserConsigneeProvince(User user){
  return Optional.ofNullable(user).map(User::getAddr).map(Addr::getProvince).orElse("");
}

get方法只有我们在确定值一定存在的情况下才能使用。

可以看到我们并未改变接口参数和返回值,但在内部处理上经过重写我们已经简化了非常多的代码,逻辑也变得清晰,更具有可读性。

为何可以如此写呢?Optional类其实是将null判断内化了,将null判断从用户手中接过来变成自己API的一部分,把用户从null判断的深渊中解放出来,只用关注自己的业务处理逻辑。

两个参数的处理

上面是一个参数的处理,如果我们有两个参数该怎么办呢。

假设有两个用户,我们需要判断一下他们是否是同一个省的,就使用上面已经提供的获取省的方法

public static boolean isSameProvince(User user1, User user2) {
    String user1Province = getUserConsigneeProvince(user1);
    String user2Province = getUserConsigneeProvince(user2);
    if (user1Province == null || user2Province == null) {
        return false;
    }
    if (user2Province.equals(user2Province)) {
        return true;
    }
    return false;
}

还好还好,只有一个if判断了null的情况,可是我们也用了9行才完成了这个简单的功能,我们其实最需要的equals这个判断,上面的三行null相关应该是需要避免的。 我们用Optional来改写一下,来感受下威力。

*public static boolean isSameProvince2(User user1, User user2) {
        return Optional.ofNullable(getUserConsigneeProvince(user1))
            .map(p -> Optional.ofNullable(getUserConsigneeProvince(user2))
                .filter(p2 -> p.equals(p2)))  
            .isPresent();
}

为了可读性我们写了4行,就算如此我们也减少了一半的代码,而且逻辑上更连贯,没有打断我们的可恶的null了。

总结

如上可以看出Optional在使用上带给我们的变化,让我们可以摆脱以往的null,用更加健康的调用方式来编写。也增加代码的可读性,逻辑上一气呵成。希望大家在平常多多使用。尽快远离恼人的null。

api – 请说明使用Option的orNull方法

api – 请说明使用Option的orNull方法

Scala的Option类具有orNull方法,其签名如下所示.

@H_301_9@orNull [A1 >: A](implicit ev : <:<[Null,A1]) : A1

我被隐含的东西迷惑了.有人可以解释一下如何使用,理想情况下可以举个例子?

解决方法

@H_301_9@scala> Some(1).orNull <console>:10: error: Could not find implicit value for parameter ev: <:<[Null,Int] Some(1).orNull ^ scala> (None : Option[Int]).orNull <console>:10: error: Could not find implicit value for parameter ev: <:<[Null,Int] (None : Option[Int]).orNull scala> Some("hi").orNull res21: java.lang.String = hi scala> Some(null : String).orNull res22: String = null scala> (None : Option[String]).orNull res23: String = null

解释隐含的东西:orNull是从Some |无成语回到Java的值| null成语(这当然是坏的)的一种方式.现在只有AnyRef值(类的实例)可以接受一个空值.

所以我们本来希望的是def orNull [A&gt ;: Null] = …..但是A已经设置了,我们不想限制它在trait的定义中.因此,orNull希望证明A是可空类型.这个证据是一个隐含变量(因此命名为’ev’)的形式

<:< [Null,A1]可以被写为Null<:< A1看到这样,它类似于“Null”:A1“. <:其中在Predef中定义,以及提供名为conforms的隐含值的方法. 我认为使用A1并不是严格要求的,因为orNull使用getorElse(其中默认给定可以是超类型A)

@H_301_9@scala> class Wrapper[A](option: Option[A]) { | def orNull(implicit ev: Null <:< A): A = if(option.isEmpty) null else option.get | } defined class Wrapper scala> new Wrapper(Some("hi")).orNull res18: java.lang.String = hi

c# – bool检查比null检查更快吗?

c# – bool检查比null检查更快吗?

例如.我需要提取吗?

bool xIsNull = x == null

从我检查x == null的循环?

据我所知,如果(a == true)和if(x == null)都使用相同的IL指令.但指针由32位或64位组成. CLR应该检查每一位与null进行比较吗?

UPDATE
快速测试显示没有区别,但我仍然希望有人解释这一点.

UPDATE2
我使用IL发射,所以我不能指望编译器优化我的代码.只有JIT.

解决方法

记住“过早优化是所有邪恶的根源”,优化的第一条规则是“不要”(第二条,仅针对专业人士,是“不要做它”),这就是发生的事情.

TL; DR
如果您不想深入研究某些汇编代码,我不会责怪您;)结果表明,使用临时变量不会得到优化并生成更多指令.总而言之,除非您编写非常重要的时间任务,否则它不会有任何区别.

考虑以下代码:

string x = null;
bool a = x == null;

if ( a == true ) { Console.WriteLine( ); }
if ( x == null ) { Console.WriteLine( ); }

这是在调试模式下生成的IL(我添加了一些注释):

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       46 (0x2e)
  .maxstack  2
  .locals init ([0] string x,[1] bool a,[2] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldnull
  IL_0002:  stloc.0    // string x = null
  IL_0003:  ldloc.0
  IL_0004:  ldnull
  IL_0005:  ceq        // compare x and null
  IL_0007:  stloc.1    // and store the result in a
  IL_0008:  ldloc.1
  IL_0009:  ldc.i4.0 
  IL_000a:  ceq
  IL_000c:  stloc.2    // compare a and false
  IL_000d:  ldloc.2
  IL_000e:  brtrue.s   IL_0018   // if true (that is,a is false),skip
  IL_0010:  nop
  IL_0011:  call       void [mscorlib]System.Console::WriteLine()
  IL_0016:  nop
  IL_0017:  nop
  IL_0018:  ldloc.0
  IL_0019:  ldnull  
  IL_001a:  ceq        // compare x and null
  IL_001c:  ldc.i4.0
  IL_001d:  ceq        // and compare with false
  IL_001f:  stloc.2
  IL_0020:  ldloc.2
  IL_0021:  brtrue.s   IL_002b  // if true (that is,x == null),skip
  IL_0023:  nop
  IL_0024:  call       void [mscorlib]System.Console::WriteLine()
  IL_0029:  nop
  IL_002a:  nop
  IL_002b:  br.s       IL_002d
  IL_002d:  ret
} // end of method Program::Main

总的来说,有很多ldloc和stloc可以读写内存数据;它们非常有用,可以帮助调试器.但是你可以看到有一个隐藏的局部变量,它具有与a完全相同的功能:所以如果你不使用临时变量,编译器就会为你使用它.还要注意使用泛型null.

现在这里是启用了优化的Release IL:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       24 (0x18)
  .maxstack  2
  .locals init ([0] string x,[1] bool a)
  IL_0000:  ldnull
  IL_0001:  stloc.0     // set x to null
  IL_0002:  ldloc.0
  IL_0003:  ldnull
  IL_0004:  ceq
  IL_0006:  stloc.1     // bool a = x == null
  IL_0007:  ldloc.1
  IL_0008:  brfalse.s  IL_000f  // if false skip
  IL_000a:  call       void [mscorlib]System.Console::WriteLine()
  IL_000f:  ldloc.0
  IL_0010:  brtrue.s   IL_0017  // if true (so x != null) skip
  IL_0012:  call       void [mscorlib]System.Console::WriteLine()
  IL_0017:  ret
} // end of method Program::Main

在优化版本中,编译器不执行显式比较,也不使用临时变量.尽管如此,在未经优化的版本中,它会存储并在此之后加载它以检查条件;这是因为stloc弹出一个堆栈,所以它必须再次推送它.

现在,让我们比较JITter生成的代码(我设置x = Console.Readline()以防止整个代码被优化掉).这适用于调试配置(如Visual Studio中所示):

string x = null;
00000043  xor         edx,edx 
00000045  mov         dword ptr [ebp-40h],edx 
            bool a = x == null;
00000048  cmp         dword ptr [ebp-40h],0 
0000004c  sete        al 
0000004f  movzx       eax,al 
00000052  mov         dword ptr [ebp-44h],eax 

            if ( a == true ) { Console.WriteLine( ); }
00000055  cmp         dword ptr [ebp-44h],0 
00000059  sete        al 
0000005c  movzx       eax,al 
0000005f  mov         dword ptr [ebp-48h],eax 
00000062  cmp         dword ptr [ebp-48h],0 
00000066  jne         00000070 
00000068  nop 
00000069  call        6027B57C 
0000006e  nop 
0000006f  nop 
            if ( x == null ) { Console.WriteLine( ); }
00000054  cmp         dword ptr [ebp-0Ch],0 
00000058  jne         00000065 
0000005a  mov         ecx,dword ptr ds:[0350208Ch] 
00000060  call        602DD5E0 
            return;
00000065  nop 
00000066  mov         esp,ebp 
00000068  pop         ebp 
00000069  ret

正如您所看到的,此代码紧跟相应的未优化IL,并在检查a的条件时使用临时变量.另一方面,由于null在我的机器上实现为0,因此比较x和null更快.

这是通过OllyDbg看到的发布代码:

string x = Console.ReadLine( );
002F0075    E8 EA808A60     CALL mscorlib_ni.60B98164
002F007A    8BC8            MOV ECX,EAX
002F007C    8B01            MOV EAX,DWORD PTR DS:[ECX]
002F007E    8B40 2C         MOV EAX,DWORD PTR DS:[EAX+2C]
002F0081    FF50 1C         CALL DWORD PTR DS:[EAX+1C]

                bool a = x == null;
002F0084    8BF0            MOV ESI,EAX
002F0086    85F6            TEST ESI,ESI
002F0088    0F94C0          SETE AL
002F008B    0FB6C0          MOVZX EAX,AL
002F008E    8BF8            MOV EDI,EAX

                Systed.Diagnostics.Debugger.Break( );
002F0090    E8 E37C8E60     CALL mscorlib_ni.60BD7D78

                if ( a == true ) { Console.ReadLine( ); }
002F0095    85FF            TEST EDI,EDI
002F0097    74 0E           JE SHORT 002F00A7
002F0099    E8 A6F92D60     CALL mscorlib_ni.605CFA44
002F009E    8BC8            MOV ECX,EAX
002F00A0    8B01            MOV EAX,DWORD PTR DS:[ECX]
002F00A2    8B40 38         MOV EAX,DWORD PTR DS:[EAX+38]
002F00A5    FF10            CALL DWORD PTR DS:[EAX]

                if ( x == null ) { Console.ReadLine( ); }
002F00A7    85F6            TEST ESI,ESI
002F00A9    75 0E           JNE SHORT 002F00B9
002F00AB    E8 94F92D60     CALL mscorlib_ni.605CFA44
002F00B0    8BC8            MOV ECX,EAX
002F00B2    8B01            MOV EAX,DWORD PTR DS:[ECX]
002F00B4    8B40 38         MOV EAX,DWORD PTR DS:[EAX+38]
002F00B7    FF10            CALL DWORD PTR DS:[EAX]

                return;
002F00B9    5E              POP ESI
002F00BA    5F              POP EDI
002F00BB    5D              POP EBP
002F00BC    C3              RETN

在这段代码中,a保存在edi中,x保存在esi中,并且有一些调用mscorlib来检索指向ReadLine和WriteLine的指针.话虽如此,这两种方法实际上存在差异;在将x与null(test esi,esi)进行比较之后,结果从零标志移动到al(sete al),然后扩展到整个eax(movzx eax,al).

所以,即使在如此简单的情况下,JITter也没有做好工作;因此,如果没有临时变量,您可以获得较小的性能提升.

c# – MVC ActionResult – Nullable或Optional参数

c# – MVC ActionResult – Nullable或Optional参数

将页码传递给我的MVC操作以获取分页列表时,不需要提供页码.如果未选择页码,则数字默认为第一页.
大多数文档似乎都支持使用空合并运算符,例如:

Public ActionResult Demo(int? page)
{
    const int pageSize = 10
    var model _db.Model.ToPagedList(page ?? 1,pageSize);
    return View(model);
}

或类似的东西.

我想知道使用默认参数是否有好处,例如

Public ActionResult Demo(int page = 1)
{
    const int pageSize = 10
    var model _db.Model.ToPagedList(page,pageSize);
    return View(model);
}

甚至

Public ActionResult Demo(int? page = 1)
{
    const int pageSize = 10
    var model _db.Model.ToPagedList(page.Value,pageSize);
    return View(model);
}

虽然在这种情况下将页面作为可空类型似乎是多余的.

使用这些方法中的任何一种是否有任何好处,或者它只是语法糖.

编辑:修复了第三个示例中的null异常

解决方法

最后一个没有意义Public ActionResult Demo(int?page = 1).

根据您的方案,前两个操作方法不会有任何不同,因为参数是页码.如果没有提供参数,人们希望登陆第一页.

但是,如果您关心该值,则不希望使用可选参数.

public ActionResult UserDetails(int? id)
{
    if (!id.HasValue)
        return View("UserNotFound"); // Or return a message.

    int userId = id.Value; 
    var user = _userService.GetUserById(userId);
    // Do something
}

您不能在上面的场景中使用可选参数 – public ActionResult UserDetails(int id = 123)

仅供参考:您不想使用公共ActionResult UserDetails(int id).如果没有提供id,它将导致崩溃.

Collections.emptyList()而不是null检查?

Collections.emptyList()而不是null检查?

如果我在某个类中有一个很少使用的集合,该集合可能被实例化很多次,则有时我可能会求助于以下“习惯用法”以节省不必要的对象创建:

List<Object> list = null;

void add(Object object) {
    if (list == null)
        list = new ArrayList<Object>();

    list.add(object);
}

// somewhere else
if (list != null)
    for (Object object : list)
         ;

现在,我想知道是否无法使用来消除这些空检查Collections.emptyList(),但是我将不得不add()像这样更改if检查:

if (list == Collections.<Object>emptyList())
    list = new ArrayList<Object>();

除了每次都分配一个新的空集合以外,还有更好的方法来处理此问题吗?

编辑: 为了清楚起见,我
使用Collections.emptyList(),但是在add()中进行上述检查确实非常丑陋……我想知道是否有更好的方法甚至是其他方法处理这个。

今天关于使用Optional来减少null检查optional处理null的分享就到这里,希望大家有所收获,若想了解更多关于api – 请说明使用Option的orNull方法、c# – bool检查比null检查更快吗?、c# – MVC ActionResult – Nullable或Optional参数、Collections.emptyList()而不是null检查?等相关知识,可以在本站进行查询。

本文标签: