乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

"白歌在代码审查时指着小崔的代码:'if (employeeName != null & employeeName.equals("大翔")) —— 这里用 & 是不安全的,如果 employeeName 为 null,equals() 会抛 NullPointerException。改成 && 短路与。'小崔恍然大悟:'原来 && 比 & 多一层空指针防护!'黄俪补充道:'还有 || 也是同理——日常开发几乎总是用短路版本。'"

逻辑运算符

概述

逻辑运算符用于组合多个 boolean 表达式,构成复杂的条件判断。Java SE 8 提供5种逻辑运算符:短路版本(&&、||)和非短路版本(&、|),以及逻辑非 !。短路版本在日常开发中占绝对主导地位,因为它们不仅能提升性能,更是空指针防御的核心工具。

操作符行为表

操作符含义示例(boolean x=true, y=false)结果短路?
&&逻辑与(短路)x && yfalse是
||逻辑或(短路)x || ytrue是
!逻辑非!xfalse——
&逻辑与(不短路)x & yfalse否
|逻辑或(不短路)x | ytrue否

核心区分:&& vs &:前者短路(左侧为 false 时不执行右侧),后者不短路(两侧都执行)。|| vs | 同理。日常开发几乎总是使用短路版本 && 和 ||,除非你有特殊理由需要两侧都执行。


深度原理分析

一、短路求值(Short-Circuit Evaluation)机制

短路求值是Java编译器的一项重要优化,也是防御性编程的重要工具。其原理如下:

短路与 &&

表达式: left && right

执行流程:
1. 先求值 left
2. 如果 left == false → 整个表达式必为 false,跳过 right 的求值(短路)
3. 如果 left == true  → 必须求值 right,表达式结果 = right

短路或 ||

表达式: left || right

执行流程:
1. 先求值 left
2. 如果 left == true → 整个表达式必为 true,跳过 right 的求值(短路)
3. 如果 left == false → 必须求值 right,表达式结果 = right

二、短路求值的工程价值——空指针防御

短路求值最常见的应用是空指针检查前置:

// ✅ 利用 && 短路:如果 name 为 null,右侧不执行,避免 NullPointerException
if (name != null && name.equals("大翔")) {
    System.out.println("欢迎大翔登录飞翔科技系统");
}

// ❌ 如果写成 & (不短路),name == null 时仍会执行 name.equals(),抛 NullPointerException
if (name != null & name.equals("大翔")) {  // 危险!

完整示例一:飞翔科技员工登录与权限验证

场景

白歌负责飞翔科技内部系统的登录模块,用短路逻辑运算符实现多重条件检查与部门权限判断。

/**
 * 飞翔科技员工登录验证系统
 * 演示短路逻辑运算符
 */
public class EmployeeLogin {
    public static void main(String[] args) {
        String inputUsername = "大翔";        // 用户输入的用户名
        String inputPassword = "feixiang888"; // 用户输入的密码
        String correctPassword = "feixiang888";// 正确密码
        boolean isAccountLocked = false;       // 账号是否被锁定
        String department = "技术部";          // 部门
        int loginAttempts = 2;                 // 已尝试登录次数
        int maxAttempts = 5;                  // 最大允许尝试次数

        System.out.println("===== 飞翔科技内部系统 feixiang.net =====");

        // --- 短路与 &&:多重条件检查 ---
        // 利用短路:如果账号被锁,后续检查直接跳过
        if (!isAccountLocked
                && inputPassword.equals(correctPassword)
                && loginAttempts < maxAttempts) {
            System.out.println("登录成功!欢迎 " + inputUsername);
        } else {
            System.out.println("登录失败!请检查账号状态或密码");
        }

        // --- 逻辑或 ||:部门权限判断 ---
        // 技术部 或 架构组 成员可访问代码仓库
        boolean canAccessRepo = department.equals("技术部")
                || department.equals("架构组")
                || department.equals("测试部");
        System.out.println("\n===== 代码仓库权限 =====");
        System.out.println("部门: " + department);
        System.out.println("可访问Git仓库: " + canAccessRepo);

        // --- 逻辑非 !:取反 ---
        boolean isWeekend = false;
        System.out.println("\n===== 加班判定 =====");
        if (!isWeekend) {  // 不是周末 → 工作日
            System.out.println("工作日,正常考勤");
        }
    }
}

运行输出:

===== 飞翔科技内部系统 feixiang.net =====
登录成功!欢迎 大翔

===== 代码仓库权限 =====
部门: 技术部
可访问Git仓库: true

===== 加班判定 =====
工作日,正常考勤

完整示例二:短路 vs 不短路的本质差异

场景

小崔想验证 & 和 && 的区别,白歌让他写一段代码来观察右侧表达式是否被执行。

/**
 * 短路(&&/||) vs 不短路(&/|) 的行为对比
 */
public class ShortCircuitDemo {
    public static void main(String[] args) {
        System.out.println("===== 短路与 && vs 不短路与 & =====\n");

        // --- 测试1:左侧为 false ---
        System.out.println("【测试1】左侧为 false 时:");
        int x = 0;

        // 短路与 &&:右侧不执行
        System.out.print("false && (++x > 0) → ");
        boolean r1 = false && (++x > 0);
        System.out.println(r1 + ",x = " + x + "(x未变,证明右侧未执行)");

        // 不短路与 &:右侧依然执行
        System.out.print("false & (++x > 0)  → ");
        boolean r2 = false & (++x > 0);
        System.out.println(r2 + ",x = " + x + "(x被++了,证明右侧执行了)");

        // --- 测试2:左侧为 true ---
        System.out.println("\n【测试2】左侧为 true 时:");
        int y = 0;

        // 短路或 ||:右侧不执行
        System.out.print("true || (++y > 0)  → ");
        boolean r3 = true || (++y > 0);
        System.out.println(r3 + ",y = " + y + "(y未变,证明右侧未执行)");

        // 不短路或 |:右侧依然执行
        System.out.print("true | (++y > 0)   → ");
        boolean r4 = true | (++y > 0);
        System.out.println(r4 + ",y = " + y + "(y被++了,证明右侧执行了)");

        // --- 实战意义:空指针防御 ---
        System.out.println("\n===== 实战:空指针防御 =====");
        String name = null;
        int z = 0;

        // && 短路:name为null时不执行 name.equals()
        if (name != null && name.equals("大翔")) {
            System.out.println("匹配到 大翔");
        } else {
            System.out.println("安全通过(&&短路避免了NullPointerException)");
        }

        // 去掉短路保护,尝试 & (注释掉以避免实际崩溃,仅供理解)
        // if (name != null & name.equals("大翔"))  // ← 这会抛 NullPointerException!
    }
}

运行输出:

===== 短路与 && vs 不短路与 & =====

【测试1】左侧为 false 时:
false && (++x > 0) → false,x = 0(x未变,证明右侧未执行)
false & (++x > 0)  → false,x = 1(x被++了,证明右侧执行了)

【测试2】左侧为 true 时:
true || (++y > 0)  → true,y = 0(y未变,证明右侧未执行)
true | (++y > 0)   → true,y = 1(y被++了,证明右侧执行了)

===== 实战:空指针防御 =====
安全通过(&&短路避免了NullPointerException)

易错场景

反例:&& 右侧有副作用且依赖其执行

// ⚠️ 危险:依赖 && 右侧的副作用
int count = 0;
if (true || (++count > 0)) {  // || 短路,右侧不执行!
    System.out.println(count);  // 期望 1,实际 0
}

纠正:如果必须执行右侧,应将其提前到判断之前,或使用不短路版本:

// ✅ 方案一:提前执行
int count = 0;
count++;  // 先执行副作用
if (true || (count > 0)) {
    System.out.println(count);  // 1
}

// ✅ 方案二:使用不短路版本(确认你确实需要右侧执行)
int count = 0;
if (true | (++count > 0)) {  // & 不短路,右侧一定执行
    System.out.println(count);  // 1
}

面试考点

Q1:&& 和 & 有什么区别?什么时候用哪个?

&& 是短路逻辑与:如果左侧为 false,右侧不执行,直接返回 false。& 是不短路逻辑与:无论左侧是什么,右侧都会执行。

使用建议:日常开发中99%的场景应使用 &&(短路版本),因为(1)性能更优(跳过不必要的计算);(2)可以做空指针防御(obj != null && obj.method())。仅当你必须让右侧表达式执行(例如右侧有方法调用产生必要的副作用)时才使用 &。此外,& 也可作位运算符(按位与),这是另一层含义。

Q2:! 运算符后面能跟什么?优先级如何?

! 是一元逻辑非运算符,后面必须跟 boolean 表达式或 boolean 变量。它的优先级很高(一元运算符级别),高于关系运算符和逻辑运算符。因此 !a > b 是编译错误(!a 的结果是 boolean,不能与 int b 比较),正确写法是 !(a > b)。

Q3:|| 和 | 的性能差异在什么场景下最明显?

当右侧表达式是一个开销较大的操作时(如数据库查询、网络调用、复杂计算),|| 的短路特性可以避免不必要的执行,性能优势显著。例如 if (cacheHit || expensiveDbLookup())——缓存命中时直接跳过数据库查询。此外,当右侧表达式可能抛出异常时(如空指针),|| 提供安全保障。唯一的代价是:如果你的代码依赖右侧的副作用(如计数器++),短路会破坏预期行为——但这是设计问题,不是 || 的问题。

上一页
关系运算符
下一页
位运算符