为什么我不建议你用 “obj ! = null " 做判空?

本文探讨了在编程中如何优雅地处理空指针问题,提出避免大量判空代码的方法,并介绍了合理使用null的情况及替代方案。

点击“开发者技术前线”,选择“星标”


 
让一部分开发者看到未来

b534a4061d2ba3c63c807a8f3795f9c3.png

转自:优快云  作者:lizeyang

blog.youkuaiyun.com/lizeyang/article/details/40040817

问题

为了避免空指针调用,我们经常会看到这样的语句   

...if (someobject != null) {
    someobject.doCalc();}...

最终,项目中会存在大量判空代码,多么丑陋繁冗!如何避免这种情况?我们是否滥用了判空呢?

精华回答:

这是初、中级程序猿经常会遇到的问题。他们总喜欢在方法中返回null,因此,在调用这些方法时,也不得不去判空。另外,也许受此习惯影响,他们总潜意识地认为,所有的返回都是不可信任的,为了保护自己程序,就加了大量的判空。

吐槽完毕,回到这个题目本身:

进行判空前,请区分以下两种情况:

1、null 是一个有效有意义的返回值(Where null is a valid response in terms of the contract; and)

2、null是无效有误的(Where it isn't a valid response.)

你可能还不明白这两句话的意思,不急,继续往下看,接下来将详细讨论这两种情况

第2种情况

null就是一个不合理的参数,就应该明确地中断程序,往外抛错误。这种情况常见于api方法。例如你开发了一个接口,id是一个必选的参数,如果调用方没传这个参数给你,当然不行。你要感知到这个情况,告诉调用方“嘿,哥们,你传个null给我做甚"。

相对于判空语句,更好的检查方式有两个

(1)assert语句,你可以把错误原因放到assert的参数中,这样不仅能保护你的程序不往下走,而且还能把错误原因返回给调用方,岂不是一举两得。(原文介绍了assert的使用,这里省略)

(2)也可以直接抛出空指针异常。上面说了,此时null是个不合理的参数,有问题就是有问题,就应该大大方方往外抛。

第1种情况会更复杂一些。

这种情况下,null是个”看上去“合理的值,例如,我查询数据库,某个查询条件下,就是没有对应值,此时null算是表达了“空”的概念。

这里给一些实践建议:

1、假如方法的返回类型是collections,当返回结果是空时,你可以返回一个空的collections(empty list),而不要返回null,这样调用侧就能大胆地处理这个返回,例如调用侧拿到返回后,可以直接print list.size(),又无需担心空指针问题。(什么?想调用这个方法时,不记得之前实现该方法有没按照这个原则?所以说,代码习惯很重要!如果你养成习惯,都是这样写代码(返回空collections而不返回null),你调用自己写的方法时,就能大胆地忽略判空)

2、返回类型不是collections,又怎么办呢?

那就返回一个空对象(而非null对象),下面举个“栗子”,假设有如下代码

public interface Action {
  void doSomething();}
 
public interface Parser {
  Action findAction(String userInput);}

其中,Parse有一个接口FindAction,这个接口会依据用户的输入,找到并执行对应的动作。假如用户输入不对,可能就找不到对应的动作(Action),因此findAction就会返回null,接下来action调用doSomething方法时,就会出现空指针。

解决这个问题的一个方式,就是使用Null Object pattern(空对象模式)

我们来改造一下

类定义如下,这样定义findAction方法后,确保无论用户输入什么,都不会返回null对象   

public class MyParser implements Parser {
  private static Action DO_NOTHING = new Action() {
    public void doSomething() { /* do nothing */ }
  };
 
  public Action findAction(String userInput) {
    // ...
    if ( /* we can't find any actions */ ) {
      return DO_NOTHING;
    }
  }}

对比下面两份调用实例

1、冗余:每获取一个对象,就判一次空

Parser parser = ParserFactory.getParser();
if (parser == null) {
  // now what?
  // this would be an example of where null isn't (or shouldn't be) a valid response
}
Action action = parser.findAction(someInput);
if (action == null) {
  // do nothing} else {
  action.doSomething();}

2、精简

ParserFactory.getParser().findAction(someInput).doSomething();

因为无论什么情况,都不会返回空对象,因此通过findAction拿到action后,可以放心地调用action的方法。

其他回答精选:

1、如果要用equal方法,请用object<不可能为空>.equal(object<可能为空>))

例如使用   :

"bar".equals(foo)

而不是   

foo.equals("bar")

2、Java8或者guava lib中,提供了Optional类,这是一个元素容器,通过它来封装对象,可以减少判空。不过代码量还是不少。不爽。

3、如果你想返回null,请停下来想一想,这个地方是否更应该抛出一个异常

END

开发者技术前线 ,汇集技术前线快讯和关注行业趋势,大厂干货,是开发者经历和成长的优秀指南。

历史推荐

微信13亿日活下,腾讯把Elasticsearch用的到底有多牛?

五款顶级的 Docker GUI工具

今日头条、抖音推荐算法原理全文详解

2022科技公司薪酬排行榜

1bc7d7acecca074a3e1fd6dccb8f3c45.jpeg

d119b3e04f7a0a95930e5d39e067c9db.gif

989207648b7075c1fe33dbdbb8c22a40.png

好文点个在看吧!

b25a2dcf8bd69b6873f708d696c1d630.gif

但还有一个问题:package com.example.bus; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.activity.OnBackPressedCallback; import com.amap.api.location.AMapLocation; import com.amap.api.location.AMapLocationClient; import com.amap.api.location.AMapLocationClientOption; import com.amap.api.location.AMapLocationListener; import com.amap.api.maps.AMap; import com.amap.api.maps.CameraUpdateFactory; import com.amap.api.maps.MapView; import com.amap.api.maps.model.LatLng; import com.amap.api.maps.model.LatLngBounds; import com.amap.api.maps.model.PolylineOptions; import com.amap.api.services.core.LatLonPoint; import com.amap.api.services.route.BusPath; import com.amap.api.services.route.BusRouteResult; import com.amap.api.services.route.BusStep; import com.amap.api.services.route.RouteSearch; import com.amap.api.maps.AMapUtils; // ✅ 新增导入 import java.util.ArrayList; import java.util.List; public class RoutePlanActivity extends AppCompatActivity implements RouteSearch.OnRouteSearchListener, AMapLocationListener { // 来源类型常量 public static final String EXTRA_SOURCE = "extra_source"; public static final int SOURCE_FROM_HOME_SEARCH = 1001; public static final int SOURCE_FROM_MAP_DIRECT = 1002; private MapView mapView; private AMap aMap; private RouteSearch routeSearch; private RecyclerView recyclerSteps; private TextView tvRouteInfo; private boolean isSearching = false; private int sourceType = -1; // 定位相关 private AMapLocationClient locationClient; private LatLonPoint currentLocation; private LatLonPoint targetPoint; private boolean hasStartedSearch = false; private OnBackPressedCallback onBackPressedCallback; // ✅ 新增:虚拟步行段类,用于表示“我的位置 → 公交站”等人工添加的步行 public static class VirtualWalkStep { public final LatLonPoint from; public final LatLonPoint to; public final String description; public VirtualWalkStep(LatLonPoint from, LatLonPoint to, String desc) { this.from = from; this.to = to; this.description = desc; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_route_plan); sourceType = getIntent().getIntExtra(EXTRA_SOURCE, -1); double startLat = getIntent().getDoubleExtra("start_lat", 0); double startLng = getIntent().getDoubleExtra("start_lng", 0); double targetLat = getIntent().getDoubleExtra("target_lat", 0); double targetLng = getIntent().getDoubleExtra("target_lng", 0); Log.d("RoutePlan", "📥 收到参数:"); Log.d("RoutePlan", " sourceType = " + sourceType); Log.d("RoutePlan", " start = " + startLat + "," + startLng); Log.d("RoutePlan", " target = " + targetLat + "," + targetLng); if (targetLat == 0 || targetLng == 0) { Toast.makeText(this, "目标位置无效", Toast.LENGTH_SHORT).show(); finish(); return; } targetPoint = new LatLonPoint(targetLat, targetLng); mapView = findViewById(R.id.map_view); recyclerSteps = findViewById(R.id.recycler_steps); tvRouteInfo = findViewById(R.id.tv_route_info); mapView.onCreate(savedInstanceState); if (aMap == null) { aMap = mapView.getMap(); } try { routeSearch = new RouteSearch(this); routeSearch.setRouteSearchListener(this); } catch (Exception e) { Log.e("RoutePlan", "RouteSearch 初始化失败", e); tvRouteInfo.setText("路线服务初始化失败"); finish(); return; } if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setTitle("公交路线规划"); } initLocation(); if (sourceType == SOURCE_FROM_MAP_DIRECT) { startRouteSearch(); } onBackPressedCallback = new OnBackPressedCallback(true) { @Override public void handleOnBackPressed() { if (sourceType == SOURCE_FROM_MAP_DIRECT) { Intent intent = new Intent(RoutePlanActivity.this, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); intent.putExtra("open_tab", "map"); startActivity(intent); finish(); } else { setEnabled(false); getOnBackPressedDispatcher().onBackPressed(); setEnabled(true); } } }; getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback); } private void initLocation() { try { locationClient = new AMapLocationClient(this); AMapLocationClientOption option = new AMapLocationClientOption(); option.setOnceLocation(true); option.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy); option.setMockEnable(false); locationClient.setLocationOption(option); locationClient.setLocationListener(this); locationClient.startLocation(); tvRouteInfo.setText("正在获取您的位置..."); } catch (Exception e) { Log.e("RoutePlan", "定位初始化失败", e); tvRouteInfo.setText("定位服务启动失败"); finish(); } } private void startRouteSearch() { if (isSearching || hasStartedSearch) return; hasStartedSearch = true; LatLonPoint start = null; if (sourceType == SOURCE_FROM_HOME_SEARCH) { if (currentLocation == null) { Log.e("RoutePlan", "📍 定位未完成,无法发起 HOME_SEARCH"); tvRouteInfo.setText("无法获取您的位置,请稍后重试"); finish(); return; } start = currentLocation; Log.d("RoutePlan", "🏠 使用定位位置作为起点: " + start.getLatitude() + "," + start.getLongitude()); } else if (sourceType == SOURCE_FROM_MAP_DIRECT) { double startLat = getIntent().getDoubleExtra("start_lat", 0); double startLng = getIntent().getDoubleExtra("start_lng", 0); if (Math.abs(startLat) < 1e-6 || Math.abs(startLng) < 1e-6) { Log.e("RoutePlan", "❌ MapDirect: 起点坐标无效 (" + startLat + ", " + startLng + ")"); tvRouteInfo.setText("起点坐标错误"); finish(); return; } start = new LatLonPoint(startLat, startLng); Log.d("RoutePlan", "🗺️ 使用地图中心作为起点: " + start.getLatitude() + "," + start.getLongitude()); } else { tvRouteInfo.setText("未知操作来源"); Log.e("RoutePlan", "❌ 未知来源: " + sourceType); finish(); return; } RouteSearch.FromAndTo fromAndTo = new RouteSearch.FromAndTo(start, targetPoint); RouteSearch.BusRouteQuery query = new RouteSearch.BusRouteQuery(fromAndTo, RouteSearch.BUS_DEFAULT, "全国", 0); isSearching = true; tvRouteInfo.setText("正在规划路线..."); routeSearch.calculateBusRouteAsyn(query); } private void addStartMarker(LatLonPoint startPoint) { if (startPoint == null || aMap == null) return; aMap.addMarker(new com.amap.api.maps.model.MarkerOptions() .position(new LatLng(startPoint.getLatitude(), startPoint.getLongitude())) .title("出发点")); } @Override public void onLocationChanged(AMapLocation loc) { if (loc != null && loc.getErrorCode() == 0) { currentLocation = new LatLonPoint(loc.getLatitude(), loc.getLongitude()); Log.d("RoutePlan", "✅ 定位成功: " + loc.getLatitude() + "," + loc.getLongitude()); if (sourceType == SOURCE_FROM_HOME_SEARCH && !hasStartedSearch) { startRouteSearch(); } } else { Log.e("RoutePlan", "❌ 定位失败: " + (loc != null ? loc.getErrorInfo() : "null")); tvRouteInfo.setText("定位失败,请检查权限"); Toast.makeText(this, "无法获取位置", Toast.LENGTH_LONG).show(); finish(); } } // ✅ 替换原 onBusRouteSearched 方法:支持首尾步行段 @Override public void onBusRouteSearched(BusRouteResult result, int rCode) { isSearching = false; if (rCode != 1000 || result == null || result.getPaths().isEmpty()) { tvRouteInfo.setText("未找到公交路线"); return; } BusPath bestPath = result.getPaths().get(0); // 原始路径 List<BusStep> originalSteps = bestPath.getSteps(); List<Object> enhancedSteps = new ArrayList<>(); // ✅ 混合类型列表 LatLonPoint start = null; if (sourceType == SOURCE_FROM_HOME_SEARCH && currentLocation != null) { start = currentLocation; } else if (sourceType == SOURCE_FROM_MAP_DIRECT) { double slat = getIntent().getDoubleExtra("start_lat", 0); double slng = getIntent().getDoubleExtra("start_lng", 0); if (Math.abs(slat) > 1e-6 && Math.abs(slng) > 1e-6) { start = new LatLonPoint(slat, slng); } } // ✅ 添加:从起点到第一个公交站的步行段 if (start != null && !originalSteps.isEmpty()) { BusStep firstStep = originalSteps.get(0); LatLonPoint firstBusStation = null; if (firstStep.getBusLine() != null && firstStep.getBusLine().getDepartureBusStation() != null) { firstBusStation = firstStep.getBusLine().getDepartureBusStation().getLatLonPoint(); } else if (firstStep.getEntrance() != null) { firstBusStation = firstStep.getEntrance().getLatLonPoint(); } if (firstBusStation != null && !start.equals(firstBusStation)) { String desc = "从当前位置步行至【" + (firstStep.getBusLine() != null ? firstStep.getBusLine().getDepartureBusStation().getBusStationName() : "公交站") + "】"; enhancedSteps.add(new VirtualWalkStep(start, firstBusStation, desc)); } } // ✅ 添加原始步骤 enhancedSteps.addAll(originalSteps); // ✅ 添加:最后一个公交站到目标点的步行段 if (!originalSteps.isEmpty()) { BusStep lastStep = originalSteps.get(originalSteps.size() - 1); LatLonPoint lastBusStation = null; if (lastStep.getBusLine() != null && lastStep.getBusLine().getArrivalBusStation() != null) { lastBusStation = lastStep.getBusLine().getArrivalBusStation().getLatLonPoint(); } else if (lastStep.getExit() != null) { lastBusStation = lastStep.getExit().getLatLonPoint(); } if (lastBusStation != null && !lastBusStation.equals(targetPoint)) { String exitName = ""; if (lastStep.getExit() != null && lastStep.getExit().getName() != null) { exitName = lastStep.getExit().getName(); } String desc = "从【" + (lastStep.getBusLine() != null ? lastStep.getBusLine().getArrivalBusStation().getBusStationName() : "公交站") + "】下车后步行至目的地" + (exitName.isEmpty() ? "" : " (" + exitName + ")"); enhancedSteps.add(new VirtualWalkStep(lastBusStation, targetPoint, desc)); } } // ✅ 绘制增强路线 drawRouteOnMapEnhanced(enhancedSteps); // ✅ 更新适配器(使用混合数据) StepAdapter adapter = new StepAdapter(enhancedSteps); recyclerSteps.setLayoutManager(new LinearLayoutManager(this)); recyclerSteps.setAdapter(adapter); // ✅ 更新信息栏 calculateAndShowRouteInfo(enhancedSteps, bestPath); // ✅ 标出起点 if (start != null) { addStartMarker(start); } } // ✅ 新增:绘制包含首尾步行的完整路线 private void drawRouteOnMapEnhanced(List<Object> steps) { aMap.clear(); List<LatLng> allPoints = new ArrayList<>(); for (Object stepObj : steps) { if (stepObj instanceof BusStep) { BusStep step = (BusStep) stepObj; if (step.getBusLine() != null && step.getBusLine().getPolyline() != null) { for (LatLonPoint point : step.getBusLine().getPolyline()) { allPoints.add(new LatLng(point.getLatitude(), point.getLongitude())); } } else if (step.getWalk() != null && step.getWalk().getPolyline() != null) { for (LatLonPoint point : step.getWalk().getPolyline()) { allPoints.add(new LatLng(point.getLatitude(), point.getLongitude())); } } else { if (step.getEntrance() != null) { LatLonPoint p = step.getEntrance().getLatLonPoint(); allPoints.add(new LatLng(p.getLatitude(), p.getLongitude())); } if (step.getExit() != null) { LatLonPoint p = step.getExit().getLatLonPoint(); allPoints.add(new LatLng(p.getLatitude(), p.getLongitude())); } } } else if (stepObj instanceof VirtualWalkStep) { VirtualWalkStep vStep = (VirtualWalkStep) stepObj; allPoints.add(new LatLng(vStep.from.getLatitude(), vStep.from.getLongitude())); allPoints.add(new LatLng(vStep.to.getLatitude(), vStep.to.getLongitude())); } } if (!allPoints.isEmpty()) { PolylineOptions options = new PolylineOptions() .addAll(allPoints) .color(0xFF4A90E2) .width(12f); aMap.addPolyline(options); LatLngBounds.Builder builder = new LatLngBounds.Builder(); for (LatLng point : allPoints) { builder.include(point); } aMap.animateCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), 100)); } else { tvRouteInfo.setText("路线数据为"); } } // ✅ 新增:重新计算总时间和换乘 private void calculateAndShowRouteInfo(List<Object> steps, BusPath originalPath) { long totalDuration = 0; int busRideCount = 0; for (Object obj : steps) { if (obj instanceof BusStep) { BusStep step = (BusStep) obj; if (step.getBusLine() != null && step.getBusLine().getDuration() > 0) { totalDuration += step.getBusLine().getDuration(); busRideCount++; } else if (step.getWalk() != null) { totalDuration += step.getWalk().getDuration(); } else if (step.getRailway() != null) { totalDuration += step.getDuration(); } } else if (obj instanceof VirtualWalkStep) { VirtualWalkStep v = (VirtualWalkStep) obj; double dist = AMapUtils.calculateLineDistance( new LatLng(v.from.getLatitude(), v.from.getLongitude()), new LatLng(v.to.getLatitude(), v.to.getLongitude()) ); totalDuration += (dist / 80) * 60; // 估算步行时间(秒) } } int transferNum = Math.max(0, busRideCount - 1); String info = String.format("⏱️ %d分钟 | 💰 ¥%.2f | 🚌 %d次换乘", totalDuration / 60, originalPath.getCost(), transferNum); tvRouteInfo.setText(info); } @Override public boolean onSupportNavigateUp() { getOnBackPressedDispatcher().onBackPressed(); return true; } @Override public void onDriveRouteSearched(com.amap.api.services.route.DriveRouteResult r, int c) {} @Override public void onWalkRouteSearched(com.amap.api.services.route.WalkRouteResult r, int c) {} @Override public void onRideRouteSearched(com.amap.api.services.route.RideRouteResult r, int c) {} @Override protected void onResume() { super.onResume(); mapView.onResume(); } @Override protected void onPause() { super.onPause(); mapView.onPause(); } @Override protected void onDestroy() { super.onDestroy(); mapView.onDestroy(); if (routeSearch != null) { routeSearch.setRouteSearchListener(null); } if (locationClient != null) { locationClient.onDestroy(); } } @Override protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); mapView.onSaveInstanceState(outState); } } 出现报错Cannot resolve method 'getDuration' in 'BusStep'
最新发布
11-22
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值