2026年4月

本文包含异常概念、体系分类、处理方式、自定义异常四大部分。

一、什么是异常

异常就是Java程序在编译或运行过程中出现的错误,会导致程序非正常终止。

开发中最常见的异常场景:

  1. 数组索引越界:访问了数组不存在的下标
  2. 算术异常:除数为0
  3. 空指针异常:调用null对象的方法
  4. 文件不存在:读取本地不存在的文件
// 常见运行时异常示例
public class ExceptionTest {
    public static void main(String[] args) {
        int[] arr = {10, 20, 30};
        System.out.println(arr[3]); // 数组索引越界异常
        System.out.println(10 / 0); // 算术异常
        String str = null;
        System.out.println(str.length()); // 空指针异常
    }
}

异常的核心作用

  1. 定位BUG:异常信息会精准打印错误位置和原因,是调试程序的关键
  2. 流程通知:作为方法的特殊返回值,告诉调用者方法执行失败的原因

二、Java异常完整体系

Java中所有异常和错误的根类是 java.lang.Throwable,整体分为两大分支:

1. Error(系统级错误)

属于JVM底层的严重问题(如内存溢出、系统崩溃),也无法通过代码处理。

2. Exception(程序异常,开发核心处理对象)

程序运行/编译中可预见的错误,我们所有异常处理都围绕这个类展开,又细分为运行时异常编译时异常

三、异常的两大分类

1. 运行时异常(RuntimeException)

  • 定义:RuntimeException 及其所有子类
  • 特点:编译阶段不报错,运行时才触发
  • 常见示例:数组越界、空指针、算术异常、类型转换异常

2. 编译时异常(受检异常)

  • 定义:非 RuntimeException 的Exception子类
  • 特点:编译阶段强制报错,必须处理才能运行
  • 常见示例:日期解析异常、文件读取异常、IO流异常
// 编译时异常示例(必须处理,否则编译失败)
import java.text.SimpleDateFormat;
import java.util.Date;

public class CompileException {
    public static void main(String[] args) throws Exception {
        String time = "2024-07-09";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        Date date = sdf.parse(time); // 日期解析异常(编译时异常)
    }
}

四、异常的两种核心处理方式

Java提供了抛出异常捕获异常两种处理方案,开发中可根据场景灵活使用。

1. 抛出异常(throws)

  • 作用:方法内部不处理异常,抛给调用者处理
  • 语法:在方法声明后添加 throws 异常类名
  • 简化写法:直接抛出 Exception 接收所有异常
// 抛出异常示例
public class ThrowException {
    public static void main(String[] args) throws Exception {
        // 调用者接收异常,也可继续抛出
        testException();
    }

    // 方法抛出异常,交给上层处理
    public static void testException() throws Exception {
        int num = 10 / 0;
    }
}

2. 捕获异常(try...catch)

  • 作用:直接拦截并处理异常,保证程序不终止
  • 语法:

    try {
        // 可能出现异常的代码
    } catch (异常类型 变量名) {
        // 异常处理逻辑
    }
  • 简化写法:catch (Exception e) 捕获所有异常
// 捕获异常示例
public class TryCatchException {
    public static void main(String[] args) {
        try {
            int num = 10 / 0;
        } catch (Exception e) {
            e.printStackTrace(); // 打印异常堆栈信息
            System.out.println("程序出现异常,已处理!");
        }
        // 异常处理后,程序继续执行
        System.out.println("程序正常结束");
    }
}

五、自定义异常

Java内置的异常类无法覆盖所有业务场景(如年龄非法、手机号格式错误),此时需要自定义异常来管理业务错误。

自定义异常的两种类型

1. 自定义编译时异常

  • 继承:Exception
  • 特点:编译强制处理,提醒严格
// 自定义编译时异常
public class AgeIllegalException extends Exception {
    // 无参构造
    public AgeIllegalException() {}
    // 带异常信息的构造
    public AgeIllegalException(String message) {
        super(message);
    }
}

2. 自定义运行时异常

  • 继承:RuntimeException
  • 特点:编译不报错,运行触发,开发更常用
// 自定义运行时异常
public class AgeIllegalRuntimeException extends RuntimeException {
    public AgeIllegalRuntimeException() {}
    public AgeIllegalRuntimeException(String message) {
        super(message);
    }
}

自定义异常使用示例

public class CustomExceptionTest {
    public static void main(String[] args) {
        try {
            saveAge(300); // 传入非法年龄
        } catch (AgeIllegalException e) {
            e.printStackTrace();
            System.out.println("年龄校验失败!");
        }
    }

    // 业务方法:校验年龄并抛出自定义异常
    public static void saveAge(int age) throws AgeIllegalException {
        if(age < 1 || age > 200){
            throw new AgeIllegalException("年龄非法,范围必须是1-200");
        }
        System.out.println("年龄保存成功:" + age);
    }
}

六、企业级异常处理实践

  1. 分层抛出,统一捕获
    底层方法不处理异常,全部向上抛出,最外层统一捕获,打印日志并返回友好提示
  2. 异常重试修复
    针对用户输入错误等场景,捕获异常后引导用户重新输入,保证程序不中断
// 异常重试修复示例
import java.util.Scanner;

public class ExceptionRetry {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (true) {
            try {
                System.out.print("请输入商品价格:");
                double price = sc.nextDouble();
                System.out.println("价格设置成功:" + price);
                break;
            } catch (Exception e) {
                System.out.println("输入格式错误,请重新输入数字!");
                sc.next(); // 清空错误输入
            }
        }
    }
}

七、总结

  1. 异常根类 Throwable,分 Error(无需处理)和 Exception(开发处理)
  2. 异常分类:运行时异常(编译不报错)、编译时异常(编译强制处理)
  3. 处理方式:throws 抛出(上层处理)、try...catch 捕获(自行处理)
  4. 自定义异常:继承 Exception(编译时)/RuntimeException(运行时)
  5. 开发规范:底层抛异常,上层统一捕获,避免程序崩溃

Java集合HashSet与TreeSet

聚焦Set集合两大核心实现类HashSetTreeSet,梳理核心特性、底层数据结构、实战用法及避坑要点。

一、Set集合基础特性

Set是Java单列集合Collection的子接口,全系列通用三大核心特性

  1. 不重复:集合中无法存储相同元素
  2. 无索引:不支持通过下标获取元素,无法普通for循环遍历
  3. 无独有方法:常用API全部继承自Collection接口

本文仅讲解最常用的两个实现类:HashSet(无序去重)、TreeSet(排序去重)。


二、HashSet 详解

2.1 核心特点

  • 无序:元素添加顺序 ≠ 遍历输出顺序
  • 不重复、无索引(Set通用特性)
  • 增删改查性能优异,是日常开发最常用的Set实现类

2.2 底层原理:哈希表

HashSet的高性能源于哈希表数据结构,JDK版本不同结构有差异:

  • JDK 8之前:数组 + 单向链表
  • JDK 8及以后:数组 + 链表 + 红黑树(优化查询性能)

关键概念:哈希值

  1. 所有Java对象均可调用Object.hashCode()获取int类型哈希码
  2. 特性:同一对象多次调用返回值相同;不同对象可能哈希值相同(哈希碰撞)

元素存储完整流程

  1. 初始化:创建默认长度16、加载因子0.75的数组table
  2. 计算位置:通过元素哈希值 % 数组长度算出存储下标
  3. 空位直接存:下标位置为null,直接存入元素
  4. 碰撞去重:位置非空时,调用equals()比较元素内容

    • 内容相同:判定为重复元素,不存储
    • 内容不同:形成链表,JDK8后新元素挂在链表尾部
  5. 树化优化:链表长度>8 且 数组长度≥64,链表自动转为红黑树
  6. 扩容机制:数组元素达到 16*0.75=12 时,自动扩容为原容量2倍

2.3 自定义对象去重(重点)

HashSet默认仅能对String、Integer等基础类型去重;自定义对象必须重写两个方法才能实现内容去重:

  1. hashCode():保证相同内容对象哈希值一致
  2. equals():判断对象内容是否相同

实战代码示例

  1. 实体类Student(重写核心方法)

    import java.util.Objects;
    
    public class Student {
     private String name;
     private int age;
     private String address;
     private String phone;
    
     // 无参、全参构造
     public Student() {}
     public Student(String name, int age, String address, String phone) {
         this.name = name;
         this.age = age;
         this.address = address;
         this.phone = phone;
     }
    
     // getter/setter省略
     public String getName() { return name; }
     public void setName(String name) { this.name = name; }
     public int getAge() { return age; }
     public void setAge(int age) { this.age = age; }
     public String getAddress() { return address; }
     public void setAddress(String address) { this.address = address; }
     public String getPhone() { return phone; }
     public void setPhone(String phone) { this.phone = phone; }
    
     // 重写equals:比较对象内容
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         Student student = (Student) o;
         return age == student.age && Objects.equals(name, student.name)
                 && Objects.equals(address, student.address)
                 && Objects.equals(phone, student.phone);
     }
    
     // 重写hashCode:根据内容生成哈希值
     @Override
     public int hashCode() {
         return Objects.hash(name, age, address, phone);
     }
    
     // 重写toString:格式化输出
     @Override
     public String toString() {
         return "Student{name='" + name + "', age=" + age + "
                         , address='" + address + "', phone='" + phone + "'}";
     }
    }
  2. 测试类:HashSet去重效果

    import java.util.HashSet;
    import java.util.Set;
    
    public class HashSetTest {
     public static void main(String[] args) {
         Set<Student> studentSet = new HashSet<>();
         // 创建两个内容完全相同的对象
         Student s1 = new Student("张三", 18, "北京", "123456");
         Student s2 = new Student("张三", 18, "北京", "123456");
    
         studentSet.add(s1);
         studentSet.add(s2);
    
         // 输出仅1个元素,去重成功
         System.out.println(studentSet);
     }
    }

三、TreeSet 详解

3.1 核心特点

  • 可排序:默认对元素升序排列
  • 不重复、无索引(Set通用特性)
  • 排序基于红黑树实现,兼顾排序与性能

3.2 底层原理:红黑树

TreeSet底层完全基于红黑树(自平衡二叉排序树),特性:

  1. 自动排序,增删改查性能稳定
  2. 去重规则:排序比较结果为0 即判定为重复元素

3.3 默认排序规则

  • 数值类型(Integer/Double):按数值大小升序
  • 字符串类型:按字符ASCII码值升序
  • 自定义对象:无法直接排序,必须手动指定排序规则

3.4 自定义对象排序(两种方案)

TreeSet支持两种排序方式,比较器优先级 > 类自带排序

方案1:实体类实现Comparable接口

实体类重写compareTo()方法,定义默认排序规则

// 教师实体类:实现Comparable接口
public class Teacher implements Comparable<Teacher> {
    private String name;
    private int age;
    private double salary;

    // 无参、全参构造
    public Teacher() {}
    public Teacher(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    // getter/setter省略
    public String getName() { return name; }
    public int getAge() { return age; }
    public double getSalary() { return salary; }

    // 核心:定义排序规则(按年龄升序)
    @Override
    public int compareTo(Teacher o) {
        // 正数:当前对象大;负数:当前对象小;0:重复
        return this.age - o.age;
    }

    @Override
    public String toString() {
        return "Teacher{name='" + name + "', age=" + age + ", salary=" + salary + "}";
    }
}

方案2:创建TreeSet时传入Comparator比较器

匿名内部类/Lambda表达式,灵活定义临时排序规则

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class TreeSetTest {
    public static void main(String[] args) {
        // 方式1:使用类默认排序(年龄升序)
        Set<Teacher> teacherSet1 = new TreeSet<>();

        // 方式2:自定义比较器(薪资降序,Lambda简化)
        Set<Teacher> teacherSet2 = new TreeSet<>((o1, o2) -> Double.compare(o2.getSalary(), o1.getSalary()));

        // 添加元素
        teacherSet2.add(new Teacher("老陈", 20, 6232.4));
        teacherSet2.add(new Teacher("dlei", 18, 3999.5));
        teacherSet2.add(new Teacher("老王", 22, 9999.9));

        // 按薪资降序输出
        System.out.println(teacherSet2);
    }
}

四、HashSet vs TreeSet 选型指南

特性HashSetTreeSet
底层结构哈希表(数组+链表+红黑树)红黑树
顺序无序可排序(默认升序)
性能增删查最优(O(1))排序场景最优(O(logn))
去重依据hashCode()+equals()比较器返回值为0
适用场景通用去重、无排序需求元素排序、范围查询

五、学习总结

  1. HashSet:Java开发首选Set集合,核心是哈希表,自定义对象必须重写hashCode()equals()实现去重;
  2. TreeSet:专注排序场景,核心是红黑树,自定义对象需通过ComparableComparator指定排序规则;
  3. 通用原则:仅去重选HashSet,需要排序选TreeSet

Java基础:Collection与List集合

聚焦单列集合Collection及其子体系List,核心特性、常用API、遍历方式、底层原理与实战用法

一、集合基础概述

数组是Java基础容器,但长度固定,无法适配动态数据存储;而集合是Java提供的动态容器,长度可变,支持灵活的增删改查,是开发中最常用的数据存储工具。

Java集合分为两大核心体系:

  • 单列集合(Collection):每个元素仅存储一个值
  • 双列集合(Map):每个元素为键值对(key-value)

二、Collection 单列集合

2.1 集合体系分支

Collection 是所有单列集合的顶层接口,两大核心子接口:

  1. List 系列有序、可重复、有索引
    实现类:ArrayList、LinkedList
  2. Set 系列无序、不重复、无索引
    实现类:HashSet、TreeSet

2.2 基础特性演示

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class CollectionTest {
    public static void main(String[] args) {
        // List:有序、可重复、有索引
        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Java");
        list.add("C++");
        System.out.println(list); // [Java, Java, C++]

        // Set:无序、不重复、无索引
        Set<String> set = new HashSet<>();
        set.add("Java");
        set.add("Java");
        set.add("C++");
        System.out.println(set); // [Java, C++]
    }
}

2.3 Collection 通用API

作为顶层接口,Collection 定义的方法所有单列集合通用,核心方法如下:

方法名功能说明
boolean add(E e)添加元素
void clear()清空集合
boolean remove(E e)删除指定元素
boolean contains(Object obj)判断是否包含指定元素
boolean isEmpty()判断集合是否为空
int size()获取元素个数
Object[] toArray()集合转数组

API 实战代码

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

public class CollectionApiTest {
    public static void main(String[] args) {
        Collection<String> coll = new ArrayList<>();
        // 1. 添加元素
        coll.add("张三");
        coll.add("李四");
        coll.add("王五");
        System.out.println(coll); // [张三, 李四, 王五]

        // 2. 元素个数
        System.out.println(coll.size()); // 3

        // 3. 删除元素
        coll.remove("李四");
        System.out.println(coll); // [张三, 王五]

        // 4. 判断包含
        System.out.println(coll.contains("张三")); // true

        // 5. 集合转数组
        Object[] arr = coll.toArray();
        System.out.println(Arrays.toString(arr)); // [张三, 王五]
        
        // 6. 清空集合
        coll.clear();
        System.out.println(coll.isEmpty()); // true
    }
}

2.4 Collection 三种遍历方式

遍历是集合最常用操作,Java 提供3种标准遍历方案,仅遍历不修改时均可使用

方式1:迭代器遍历(集合专属)

迭代器 Iterator 是集合遍历的原生方式,核心方法:

  • hasNext():判断是否有下一个元素
  • next():获取当前元素并后移指针
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class IteratorTest {
    public static void main(String[] args) {
        Collection<String> coll = new ArrayList<>();
        coll.add("张无忌");
        coll.add("玄冥二老");
        coll.add("宋青书");

        // 获取迭代器
        Iterator<String> it = coll.iterator();
        // 循环遍历
        while (it.hasNext()) {
            String name = it.next();
            System.out.println(name);
        }
    }
}

⚠️ 注意:越界调用 next() 会抛出 NoSuchElementException 异常。

方式2:增强for循环(简化迭代器)

语法简洁,本质是迭代器的语法糖,支持集合+数组:

import java.util.ArrayList;
import java.util.Collection;

public class ForEachTest {
    public static void main(String[] args) {
        Collection<String> coll = new ArrayList<>();
        coll.add("张无忌");
        coll.add("玄冥二老");
        
        // 增强for遍历
        for (String name : coll) {
            System.out.println(name);
        }
    }
}

方式3:Lambda 表达式遍历(JDK8+)

极简写法,基于函数式编程:

import java.util.ArrayList;
import java.util.Collection;

public class LambdaTest {
    public static void main(String[] args) {
        Collection<String> coll = new ArrayList<>();
        coll.add("张无忌");
        coll.add("玄冥二老");
        
        // Lambda 遍历
        coll.forEach(System.out::println);
    }
}

2.5 并发修改异常解决方案

异常场景

遍历集合时,直接增删元素会触发 ConcurrentModificationException 并发修改异常。

核心解决方案(3种)

  1. 索引for循环:删除后 i-- 修正索引
  2. 倒序索引遍历:无需修正索引
  3. 迭代器自带 remove() 方法(推荐)

⚠️ 禁止:增强for、Lambda 遍历中增删元素

实战代码

import java.util.ArrayList;
import java.util.Iterator;

public class ModifyTest {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Java入门");
        list.add("宁夏枸杞");
        list.add("黑枸杞");

        // 方案1:迭代器删除(无异常)
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            String s = it.next();
            if (s.contains("枸杞")) {
                it.remove(); // 迭代器原生删除
            }
        }
        System.out.println(list); // [Java入门]
    }
}

三、List 集合(Collection 子接口)

List 继承 Collection,独有索引特性,是开发中使用频率最高的集合。

3.1 List 核心特性

  • 有序:元素存储顺序与取出顺序一致
  • 可重复:允许存储重复元素
  • 有索引:支持通过索引精准操作元素

3.2 List 独有索引API

List 新增索引相关方法,Collection 无此功能:

方法名功能说明
void add(int index, E e)指定索引插入元素
E remove(int index)删除指定索引元素,返回被删元素
E set(int index, E e)修改指定索引元素,返回原元素
E get(int index)获取指定索引元素

API 实战

import java.util.ArrayList;
import java.util.List;

public class ListApiTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("张三");
        list.add("李四");

        // 索引插入
        list.add(1, "赵敏");
        System.out.println(list); // [张三, 赵敏, 李四]

        // 索引删除
        list.remove(2);
        System.out.println(list); // [张三, 赵敏]

        // 索引修改+获取
        list.set(0, "金毛狮王");
        System.out.println(list.get(0)); // 金毛狮王
    }
}

3.3 List 四种遍历方式

在 Collection 三种遍历基础上,新增普通for索引遍历

  1. 普通for(索引)
  2. 迭代器
  3. 增强for
  4. Lambda
// 普通for索引遍历(List专属)
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

3.4 List 两大实现类对比

1. ArrayList(数组实现)

  • 底层:动态数组
  • 特性:查询快、增删慢(首尾增删除外)
  • 场景:高频查询、少量增删(开发首选)

2. LinkedList(双链表实现)

  • 底层:双向链表
  • 特性:查询慢、首尾增删极快
  • 场景:高频首尾操作、模拟队列/栈

3.5 LinkedList 实战:队列 & 栈

利用 LinkedList 首尾操作特性,快速实现经典数据结构:

  1. 队列:先进先出(FIFO)
  2. :后进先出(LIFO)
import java.util.LinkedList;

public class LinkedListTest {
    public static void main(String[] args) {
        // 1. 模拟队列
        LinkedList<String> queue = new LinkedList<>();
        queue.addLast("A"); // 入队
        queue.addLast("B");
        System.out.println(queue.removeFirst()); // 出队 A

        // 2. 模拟栈
        LinkedList<String> stack = new LinkedList<>();
        stack.push("1"); // 压栈
        stack.push("2");
        System.out.println(stack.pop()); // 弹栈 2
    }
}

四、总结

  1. Collection:单列集合顶层接口,通用增删查+3种遍历方式
  2. List:有序可重复有索引,独有索引API,4种遍历方式
  3. 实现类选型

    • 查多改少 → ArrayList
    • 首尾增删 → LinkedList
  4. 避坑指南:遍历增删用迭代器,禁止增强for/Lambda直接修改集合

一、对象与类的核心认知

1.1 对象的本质

对象是一种特殊的数据结构,用于承载一个事物的完整数据,从而在程序中对该事物进行具象化表达。

相较于变量、数组等基础数据存储结构,对象具备更强的结构化能力:可将一个事物的多维度属性与行为进行整合,实现数据与处理逻辑的一体化管理。例如对于影视演员这一事物,可通过对象承载其姓名、年龄、身高、体重等多维度属性;对于学生这一事物,可通过对象承载姓名、各科成绩等数据,同时内置成绩计算的相关行为。

1.2 类的核心定位

类也被称为对象的设计图或模板,是创建对象的基础。在Java中,必须先定义类,才能通过类创建对应的对象;每一次通过new关键字实例化类,都会得到一个全新的、独立的对象。

类的核心组成分为两部分:

  • 成员变量(属性):用来说明对象可存储的数据,描述事物的静态特征;
  • 成员方法(行为):描述对象具备的功能,即对数据可执行的处理操作。

二、类的基础语法与对象实例化

2.1 类的基础语法结构

/**
 * 学生类:作为创建学生对象的模板
 */
public class Student {
    // 成员变量:学生的属性
    String name;        // 姓名
    double chinese;     // 语文成绩
    double math;        // 数学成绩

    // 成员方法:计算并打印总成绩
    public void printTotalScore() {
        System.out.println(name + "同学的各科总分是:" + (chinese + math));
    }

    // 成员方法:计算并打印平均成绩
    public void printAverageScore() {
        System.out.println(name + "同学的各科平均分是:" + (chinese + math) / 2.0);
    }
}

2.2 对象的实例化与使用

通过new关键字完成类的实例化,得到对象后,可通过对象名.属性名访问成员变量,通过对象名.方法名()调用成员方法。

/**
 * 测试类:完成学生对象的创建与功能验证
 */
public class StudentTest {
    public static void main(String[] args) {
        // 实例化第一个学生对象并赋值
        Student s1 = new Student();
        s1.name = "播妞";
        s1.chinese = 100;
        s1.math = 100;
        // 调用对象的方法
        s1.printTotalScore();
        s1.printAverageScore();

        // 实例化第二个学生对象并赋值
        Student s2 = new Student();
        s2.name = "播仔";
        s2.chinese = 59;
        s2.math = 100;
        // 调用对象的方法
        s2.printTotalScore();
        s2.printAverageScore();
    }
}

程序执行结果:

播妞同学的各科总分是:200.0
播妞同学的各科平均分是:100.0
播仔同学的各科总分是:159.0
播仔同学的各科平均分是:79.5

2.3 对象的内存模型

Java程序运行时,对象的相关数据会分布在三个核心内存区域:

  1. 栈内存:存储对象的引用(地址),方法的局部变量,方法执行完毕后会自动释放;
  2. 堆内存:存储实例化对象的实际数据(成员变量),每一个new出来的对象都会在堆内存中分配独立的空间;
  3. 方法区:存储类的字节码信息,包括类的成员变量、成员方法定义,类加载时仅会加载一次。

三、构造器的定义与应用

3.1 构造器的核心作用

构造器是类的特殊成员方法,在对象通过new关键字创建时,会被自动调用,核心应用场景为完成对象成员变量的初始化赋值

3.2 构造器的语法规范

构造器的名称必须与类名完全一致,且无返回值类型声明(无需写void),基础语法如下:

public class Student {
    String name;
    int age;

    // 无参构造器
    public Student() {
    }

    // 有参构造器
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

3.3 构造器的使用规则

  1. 类在定义时,会默认自带一个无参构造器,无需手动声明;
  2. 若为类显式定义了有参构造器,默认的无参构造器会自动失效,若需使用无参构造器,必须手动显式定义;
  3. 一个类可定义多个重载的构造器,根据参数列表的不同完成不同的初始化逻辑。

3.4 构造器的实战使用

public class ConstructorTest {
    public static void main(String[] args) {
        // 调用无参构造器创建对象,后续通过set方法赋值
        Student s1 = new Student();
        s1.name = "张三";
        s1.age = 20;

        // 调用有参构造器创建对象,同时完成初始化赋值
        Student s2 = new Student("李四", 22);
    }
}

四、this关键字的核心作用

4.1 this的本质

this是一个引用类型变量,存在于类的成员方法与构造器中,指向当前调用该方法的对象,即“哪个对象调用方法,this就指向哪个对象”。

4.2 核心应用场景

this的核心作用是解决成员变量与方法内局部变量的命名冲突问题。当方法内的局部变量与类的成员变量名称相同时,通过this.变量名的方式,显式指定访问的是类的成员变量。

public class Student {
    // 成员变量
    String name;
    double score;

    // 带参方法,局部变量与成员变量重名
    public void checkPass(double score) {
        // this.score 代表当前对象的成员变量score
        // score 代表方法传入的局部变量score
        if(this.score >= score) {
            System.out.println("成绩达标");
        } else {
            System.out.println("成绩未达标");
        }
    }
}

除此之外,this也可用于在构造器中调用本类的其他重载构造器,简化初始化代码。

五、封装特性的设计与实现

5.1 封装的核心定义

封装是面向对象编程的三大核心特征(封装、继承、多态)之一,其核心设计思想是:将事物的数据与处理数据的方法设计在同一个类的对象中,遵循合理隐藏、合理暴露的设计规范。

5.2 封装的代码实现

通过Java的访问权限修饰符,控制类成员的可见性,实现封装的设计规范:

  • 隐藏成员:使用private(私有)修饰符修饰成员变量,仅当前类内部可访问,外部类无法直接操作;
  • 暴露成员:使用public(公开)修饰符修饰成员方法,对外提供统一的访问与操作入口。

5.3 封装的设计优势

  1. 提升数据安全性,避免外部类直接修改成员变量,可在暴露的方法中增加数据校验逻辑;
  2. 规范数据的访问方式,降低代码耦合度;
  3. 隐藏代码的实现细节,仅对外暴露必要的功能入口。

封装后的标准类示例:

public class Student {
    // 私有成员变量:对外隐藏
    private String name;
    private double chinese;
    private double math;

    // 公开的get/set方法:对外暴露,提供访问入口
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getChinese() {
        return chinese;
    }

    public void setChinese(double chinese) {
        // 增加数据校验,确保成绩合法
        if(chinese >= 0 && chinese <= 100) {
            this.chinese = chinese;
        } else {
            System.out.println("语文成绩输入不合法,需在0-100之间");
        }
    }

    public double getMath() {
        return math;
    }

    public void setMath(double math) {
        if(math >= 0 && math <= 100) {
            this.math = math;
        } else {
            System.out.println("数学成绩输入不合法,需在0-100之间");
        }
    }

    // 公开的业务方法:对外暴露功能
    public void printTotalScore() {
        System.out.println(name + "同学的各科总分是:" + (chinese + math));
    }
}

六、JavaBean(实体类)规范与应用

6.1 JavaBean的核心规范

JavaBean是Java开发中约定俗成的实体类规范,是一种仅负责数据存取的特殊类,核心作用是实现数据与业务处理逻辑的分离,是MVC、分层开发架构的基础。

标准JavaBean需满足以下3个核心要求:

  1. 类中的所有成员变量必须使用private修饰实现私有化;
  2. 为每一个私有化的成员变量,提供public修饰的getter(取值)与setter(赋值)方法;
  3. 必须提供一个public修饰的无参构造器,有参构造器为可选定义。

6.2 标准JavaBean示例

/**
 * 教师实体类:标准JavaBean规范实现
 */
public class Teacher {
    // 私有成员变量
    private String name;
    private String workId;

    // 无参构造器(必须)
    public Teacher() {
    }

    // 有参构造器(可选)
    public Teacher(String name, String workId) {
        this.name = name;
        this.workId = workId;
    }

    // getter与setter方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getWorkId() {
        return workId;
    }

    public void setWorkId(String workId) {
        this.workId = workId;
    }

    // 成员方法
    public void teach() {
        System.out.println("工号为" + workId + "的" + name + "老师正在讲课");
    }
}

6.3 JavaBean的使用方式

public class TeacherTest {
    public static void main(String[] args) {
        // 方式1:无参构造器创建对象 + setter方法赋值
        Teacher t1 = new Teacher();
        t1.setName("杨老师");
        t1.setWorkId("t001");
        t1.teach();

        // 方式2:有参构造器创建对象,直接完成赋值
        Teacher t2 = new Teacher("李老师", "t002");
        t2.teach();
    }
}

6.4 应用场景

JavaBean实体类的对象仅负责数据的承载与存取,数据的业务处理逻辑由专门的业务类实现,从而实现数据与业务的解耦,提升代码的可维护性与可扩展性。

七、static关键字与静态成员

static意为静态,可用于修饰类的成员变量与成员方法,被修饰的成员属于类本身,而非类的实例对象,与类一起加载,无需创建对象即可访问。

7.1 静态成员变量(类变量)

7.1.1 核心定义与特性

成员变量根据是否被static修饰,分为两类:

类型定义核心特性访问方式
静态变量(类变量)static修饰的成员变量与类一起加载,内存中仅存在一份,被该类的所有实例对象共享推荐:类名.静态变量名
不推荐:对象.静态变量名
实例变量(对象变量)未被static修饰的成员变量属于每个独立的对象,每个对象都有一份独立副本,互不影响仅支持:对象.实例变量名

7.1.2 应用场景与代码示例

当某个数据仅需一份,且需要被所有对象共享访问、修改时,可定义为静态变量。典型场景:统计类的实例化对象总数。

public class User {
    // 静态变量:统计对象创建数量,被所有对象共享
    public static int userCount = 0;

    // 无参构造器:每创建一个对象,计数+1
    public User() {
        User.userCount++;
    }
}
public class StaticFieldTest {
    public static void main(String[] args) {
        // 类名直接访问静态变量
        System.out.println(User.userCount); // 输出:0

        // 创建3个对象
        new User();
        new User();
        new User();

        // 静态变量已被所有对象共享修改
        System.out.println(User.userCount); // 输出:3
    }
}

7.2 静态成员方法(类方法)

7.2.1 核心定义与特性

成员方法根据是否被static修饰,分为两类:

类型定义核心特性访问方式
静态方法(类方法)static修饰的成员方法属于类本身,无需创建对象即可调用推荐:类名.静态方法名
不推荐:对象.静态方法名
实例方法(对象方法)未被static修饰的成员方法属于对象,必须创建实例对象后才能调用仅支持:对象.实例方法名

7.2.2 核心应用场景:工具类设计

静态方法的核心应用场景是开发工具类。工具类是一种通用功能类,其中的方法均为静态方法,每个方法完成一个通用功能,可直接被开发者调用,无需创建工具类的对象。

工具类设计的核心优势:

  1. 提高代码复用性,通用功能可全局调用;
  2. 调用便捷,无需实例化对象,直接通过类名调用;
  3. 节省内存,避免创建大量仅用于调用方法的对象。

7.2.3 工具类设计规范与示例

工具类无需创建对象,建议将构造器私有化,避免被外部类实例化。

/**
 * 字符串工具类:静态方法实现通用功能
 */
public class StringUtil {
    // 私有化构造器:禁止外部实例化
    private StringUtil() {
    }

    // 静态方法:判断字符串是否为空
    public static boolean isEmpty(String str) {
        return str == null || str.trim().length() == 0;
    }

    // 静态方法:判断邮箱格式是否合法
    public static boolean isEmail(String email) {
        if(isEmpty(email)) {
            return false;
        }
        return email.matches("^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$");
    }
}

工具类的使用:

public class UtilTest {
    public static void main(String[] args) {
        // 直接通过类名调用静态方法,无需创建对象
        System.out.println(StringUtil.isEmpty("")); // 输出:true
        System.out.println(StringUtil.isEmail("test@163.com")); // 输出:true
    }
}

7.3 静态成员的访问注意事项

  1. 静态方法中可以直接访问静态成员,无法直接访问实例成员
  2. 实例方法中既可以直接访问静态成员,也可以直接访问实例成员;
  3. 实例方法中可以使用this关键字,静态方法中不允许使用this关键字

八、实战案例:简易电影信息展示系统

8.1 需求说明

  1. 展示系统中的全部电影信息(每部电影展示名称、价格);
  2. 支持根据电影编号(id)查询对应电影的详细信息。

8.2 代码实现

步骤1:定义电影实体类(JavaBean)

public class Movie {
    // 私有成员变量
    private int id;         // 电影编号
    private String name;    // 电影名称
    private double price;   // 电影票价
    private String director;// 导演
    private String cast;    // 主演
    private double score;   // 电影评分

    // 无参构造器
    public Movie() {
    }

    // 有参构造器
    public Movie(int id, String name, double price, String director, String cast, double score) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.director = director;
        this.cast = cast;
        this.score = score;
    }

    // getter与setter方法
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public String getDirector() {
        return director;
    }

    public void setDirector(String director) {
        this.director = director;
    }

    public String getCast() {
        return cast;
    }

    public void setCast(String cast) {
        this.cast = cast;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }
}

步骤2:定义电影业务处理类

public class MovieOperator {
    private Movie[] movies;

    // 构造器:传入电影数据数组
    public MovieOperator(Movie[] movies) {
        this.movies = movies;
    }

    // 功能1:展示全部电影的名称与价格
    public void showAllMovies() {
        System.out.println("===== 系统全部电影信息 =====");
        for (Movie movie : movies) {
            System.out.println("电影名称:" + movie.getName() + ",票价:" + movie.getPrice() + "元");
        }
    }

    // 功能2:根据电影编号查询电影详情
    public void queryMovieById(int id) {
        for (Movie movie : movies) {
            if(movie.getId() == id) {
                System.out.println("===== 电影详情信息 =====");
                System.out.println("电影编号:" + movie.getId());
                System.out.println("电影名称:" + movie.getName());
                System.out.println("电影评分:" + movie.getScore());
                System.out.println("电影票价:" + movie.getPrice() + "元");
                System.out.println("导演:" + movie.getDirector());
                System.out.println("主演:" + movie.getCast());
                return;
            }
        }
        System.out.println("未查询到编号为" + id + "的电影信息");
    }
}

步骤3:测试主类,实现功能调用

public class MovieSystem {
    public static void main(String[] args) {
        // 1. 初始化电影数据
        Movie[] movies = new Movie[3];
        movies[0] = new Movie(1, "长津湖之水门桥", 45.0, "徐克", "吴京、易烊千玺", 9.6);
        movies[1] = new Movie(2, "一点就到家", 38.0, "许宏宇", "刘昊然、彭昱畅", 8.7);
        movies[2] = new Movie(3, "月球陨落", 52.0, "罗兰·艾默里奇", "哈莉·贝瑞", 7.9);

        // 2. 创建业务处理对象
        MovieOperator operator = new MovieOperator(movies);

        // 3. 调用功能1:展示全部电影信息
        operator.showAllMovies();

        // 4. 调用功能2:根据编号查询电影详情
        operator.queryMovieById(2);
        operator.queryMovieById(5);
    }
}

8.3 程序执行结果

===== 系统全部电影信息 =====
电影名称:长津湖之水门桥,票价:45.0元
电影名称:一点就到家,票价:38.0元
电影名称:月球陨落,票价:52.0元
===== 电影详情信息 =====
电影编号:2
电影名称:一点就到家
电影评分:8.7
电影票价:38.0元
导演:许宏宇
主演:刘昊然、彭昱畅
未查询到编号为5的电影信息

Java入门学习笔记:程序流程控制

程序流程控制决定了代码的执行顺序,是简单输出语句,到能实现复杂业务逻辑的关键一步。

一、程序的三大基本执行结构

Java中所有代码的执行逻辑,都可以归为三种基础结构,所有复杂的程序都是这三种结构的组合与嵌套。

1. 顺序结构

最基础的执行结构,代码会自上而下依次执行,没有任何判断和跳转,是绝大多数基础代码都默认遵循这个结构。

public class Test {
    public static void main(String[] args) {
        // 会按照从上到下的顺序,依次输出A、B、C
        System.out.println("A");
        System.out.println("B");
        System.out.println("C");
    }
}

2. 分支结构

根据预设的条件,选择性地执行某一段代码,实现逻辑的判断与分流。Java中主要提供了ifswitch两种分支实现方式。

3. 循环结构

控制某一段代码重复执行多次,大幅减少冗余代码,灵活控制程序的执行节奏。Java中提供了forwhiledo-while三种循环实现方式。


二、分支结构详解

分支结构的核心是「条件判断」,根据判断结果的真假,决定执行哪一段代码,是实现业务逻辑判断的核心语法。

1. if分支结构

if分支是最常用的分支语法,支持区间判断、复杂条件判断,功能覆盖范围最广,共有三种书写形式。

(1)单if分支

仅针对单个条件做判断,条件为true时执行对应代码,为false则跳过。

语法格式

if (条件表达式) {
    语句体; // 条件为true时执行的代码
}

执行流程

  1. 先计算条件表达式的结果,结果为布尔类型(true/false
  2. 结果为true,执行大括号内的语句体;结果为false,直接跳过整个分支

使用注意

  • if(条件)后的括号不能跟分号;,否则大括号内的代码将不受if条件控制
  • 如果大括号内只有一行代码,大括号可以省略,但不推荐,会降低代码可读性

示例代码

// 判断BMI是否在正常范围
public class BMITest {
    public static void main(String[] args) {
        double weight = 65; // 体重 单位:kg
        double height = 1.75; // 身高 单位:m
        double bmi = weight / (height * height);
        
        // 单if判断:BMI正常时输出提示
        if (bmi >= 18.5 && bmi <= 24.9) {
            System.out.println("你的BMI指数正常,继续保持!");
        }
    }
}

(2)if-else双分支

针对一个条件,提供「满足/不满足」两种执行路径,非此即彼。

语法格式

if (条件表达式) {
    语句体1; // 条件为true时执行
} else {
    语句体2; // 条件为false时执行
}

执行流程

  1. 计算条件表达式的布尔结果
  2. 结果为true,执行语句体1;结果为false,执行语句体2

示例代码

// 判断一个数是奇数还是偶数
public class OddEvenTest {
    public static void main(String[] args) {
        int num = 15;
        if (num % 2 == 0) {
            System.out.println(num + "是偶数");
        } else {
            System.out.println(num + "是奇数");
        }
    }
}

(3)if-else if-else多分支

针对多个连续的条件做判断,从上到下依次匹配,匹配到第一个符合的条件后,执行对应代码并结束整个分支,适合多区间、多条件的判断场景。

语法格式

if (条件表达式1) {
    语句体1;
} else if (条件表达式2) {
    语句体2;
} else if (条件表达式3) {
    语句体3;
} 
// 可以写任意多个else if
else {
    语句体n; // 所有条件都不满足时,执行这里的代码
}

执行流程

  1. 先判断条件1,结果为true则执行语句体1,分支结束;为false则继续判断条件2
  2. 条件2为true则执行语句体2,分支结束;为false则继续判断后续条件
  3. 若所有条件都为false,则执行else中的语句体n

示例代码(完整BMI指数判断)

public class BMIFullTest {
    public static void main(String[] args) {
        double weight = 80;
        double height = 1.75;
        double bmi = weight / (height * height);
        System.out.println("你的BMI指数为:" + bmi);

        // 多分支判断BMI所处区间
        if (bmi < 18.5) {
            System.out.println("体重过低");
        } else if (bmi <= 24.9) {
            System.out.println("体重正常");
        } else if (bmi <= 29.9) {
            System.out.println("超重");
        } else {
            System.out.println("肥胖");
        }
    }
}

2. switch分支结构

switch分支专门针对固定值匹配的场景,通过匹配表达式的值与case后的值是否相等,决定执行哪条分支,代码结构更清晰,性能更优。

(1)基础语法与执行流程

语法格式

switch(表达式) {
    case 值1:
        执行代码1;
        break; // 跳出分支,避免穿透
    case 值2:
        执行代码2;
        break;
    // 可以写任意多个case
    default:
        执行代码n; // 所有case都不匹配时,执行这里的代码
}

执行流程

  1. 先计算表达式的值,再拿着这个值与case后的值依次匹配
  2. 与哪个case的值匹配成功,就执行该case块内的代码,遇到break就跳出整个switch分支
  3. 所有case都匹配失败,则执行default块内的代码

(2)使用注意事项

  1. 表达式的类型有严格限制:仅支持byteshortintchar,JDK5开始支持枚举,JDK7开始支持String不支持doublefloatlong
  2. case后的值不允许重复,且必须是字面量,不能是变量
  3. 正常使用时不要忘记写break,否则会出现case穿透现象

(3)case穿透性的应用

当多个case分支需要执行的代码完全相同时,可以利用穿透性简化代码,不用重复书写相同逻辑。

示例代码

// 根据星期数,输出当日安排
public class WeekPlanTest {
    public static void main(String[] args) {
        int week = 3;
        switch (week) {
            case 1:
                System.out.println("埋头苦干,解决bug");
                break;
            // 周二、周三、周四执行相同逻辑,利用穿透简化
            case 2:
            case 3:
            case 4:
                System.out.println("请求大牛程序员帮忙");
                break;
            case 5:
                System.out.println("整理代码");
                break;
            // 周六、周日执行相同逻辑
            case 6:
            case 7:
                System.out.println("打游戏放松");
                break;
            default:
                System.out.println("输入的星期数有误");
        }
    }
}

3. if与switch的适用场景对比

语法核心优势最佳适用场景
if功能更强大,支持区间判断、复杂逻辑判断条件为区间范围、需要做复杂逻辑运算的场景
switch代码结构清晰,固定值匹配性能更优条件为单个固定值、多个值匹配的场景

三、循环结构详解

循环结构的核心是「重复执行」,可以让一段代码按照规则循环执行,避免手动重复书写代码,是批量处理数据、重复执行逻辑的核心语法。

1. for循环

for循环是最常用的循环语法,适合明确知道循环次数的场景,语法结构紧凑,循环变量的生命周期仅在循环内有效,更安全。

(1)基础语法与执行流程

语法格式

for (初始化语句; 循环条件; 迭代语句) {
    循环体语句; // 重复执行的代码
}

执行流程

// 示例代码
for (int i = 0; i < 3; i++) {
    System.out.println("Hello World");
}
  1. 循环开始时,先执行一次初始化语句int i = 0,整个循环只会执行一次
  2. 判断循环条件i < 3,结果为true则执行循环体,结果为false则直接结束循环
  3. 循环体执行完毕后,执行迭代语句i++,然后再次回到步骤2判断循环条件
  4. 重复步骤2-3,直到循环条件为false,循环结束

(2)案例

求1-5之间的数字和

public class SumTest {
    public static void main(String[] args) {
        int sum = 0; // 定义求和变量
        // 循环生成1-5的数字
        for (int i = 1; i <= 5; i++) {
            sum += i; // 累加每个数字到sum中
        }
        System.out.println("1-5的和为:" + sum); // 输出结果15
    }
}

查找所有水仙花数
水仙花数定义:是一个三位数,且个位、十位、百位的数字立方和等于原数。

public class NarcissisticNumberTest {
    public static void main(String[] args) {
        int count = 0; // 统计水仙花数的个数
        // 遍历所有三位数
        for (int i = 100; i <= 999; i++) {
            // 提取个位、十位、百位
            int ge = i % 10;
            int shi = i / 10 % 10;
            int bai = i / 100 % 10;
            // 判断是否符合水仙花数规则
            if (ge*ge*ge + shi*shi*shi + bai*bai*bai == i) {
                System.out.println(i);
                count++;
            }
        }
        System.out.println("水仙花数总共有:" + count + "个");
    }
}

2. while循环

while循环的功能与for循环完全一致,核心区别是适合不知道循环次数的场景,循环变量在循环结束后仍可使用。

(1)基础语法与执行流程

语法格式

初始化语句;
while (循环条件) {
    循环体语句;
    迭代语句;
}

执行流程

  1. 先执行一次初始化语句
  2. 判断循环条件,结果为true则执行循环体和迭代语句,结果为false则结束循环
  3. 每次迭代语句执行完毕后,再次回到步骤2判断循环条件,重复执行

(2)经典实战案例

纸张折叠多少次能达到珠峰高度
珠峰高度8848.86米=8848860毫米,纸张厚度0.1毫米,每次折叠厚度翻倍,求折叠次数。

public class PaperFoldTest {
    public static void main(String[] args) {
        double peakHeight = 8848860; // 珠峰高度 单位:毫米
        double paperThickness = 0.1; // 纸张初始厚度 单位:毫米
        int count = 0; // 统计折叠次数
        
        // 不知道循环次数,用while循环
        while (paperThickness < peakHeight) {
            paperThickness *= 2; // 每次折叠厚度翻倍
            count++;
        }
        System.out.println("需要折叠的次数:" + count);
    }
}

3. do-while循环

do-while循环的核心特点是先执行,后判断,哪怕循环条件一开始就不成立,循环体也会至少执行一次。

(1)基础语法与执行流程

语法格式

初始化语句;
do {
    循环体语句;
    迭代语句;
} while (循环条件);

执行流程

  1. 先执行一次初始化语句,然后直接执行循环体和迭代语句
  2. 执行完毕后,再判断循环条件,结果为true则再次执行循环体,结果为false则结束循环

(2)适用场景

适合需要先执行一次操作,再判断是否继续循环的场景,比如12306抢票时,先执行一次刷票,再判断是否需要继续刷新。

4. 三种循环的核心区别总结

  1. 执行顺序区别

    • forwhile:先判断循环条件,再执行循环体,条件不成立则一次都不执行
    • do-while:先执行循环体,再判断循环条件,至少执行一次
  2. 使用场景区别

    • 明确知道循环次数:优先使用for循环
    • 不知道循环次数:优先使用while循环
  3. 循环变量区别

    • for循环的初始化变量定义在循环内,仅在循环中可使用
    • whiledo-while的初始化变量定义在循环外,循环结束后仍可继续使用

5. 死循环

死循环是指循环条件一直为true,会一直执行下去的循环,除非手动干预,否则不会停止。在服务器程序、持续监听的场景中非常常用。

三种常见写法

// 1. for循环死循环
for ( ; ; ) {
    System.out.println("死循环执行");
}

// 2. while循环死循环(最经典、最常用)
while (true) {
    System.out.println("死循环执行");
}

// 3. do-while循环死循环
do {
    System.out.println("死循环执行");
} while (true);

6. 循环嵌套

循环嵌套指的是在一个循环内部,又包含了另一个循环,外层循环每执行一次,内层循环会完整执行完一轮。

示例代码

public class LoopNestTest {
    public static void main(String[] args) {
        // 外层循环控制行数
        for (int i = 1; i <= 4; i++) {
            // 内层循环控制每行的星号个数
            for (int j = 1; j <= 5; j++) {
                System.out.print("*");
            }
            // 每行打印完后换行
            System.out.println();
        }
    }
}

四、循环控制关键字:break & continue

breakcontinue是专门用来控制循环执行流程的关键字,可以灵活地中断循环、跳过某次循环。

1. break关键字

核心作用:跳出并结束当前所在的整个循环,也可以用来结束switch分支的执行。
使用限制:只能在循环和switch分支中使用。

示例代码

// 循环到5时,结束整个循环
public class BreakTest {
    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
            if (i == 5) {
                break; // 遇到5,直接结束整个循环
            }
            System.out.println(i);
        }
        // 最终只会输出1、2、3、4
    }
}

2. continue关键字

核心作用:跳出当前循环的本次执行,直接进入循环的下一次迭代。
使用限制:只能在循环中使用。

示例代码

// 跳过偶数,只输出奇数
public class ContinueTest {
    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
            if (i % 2 == 0) {
                continue; // 遇到偶数,跳过本次循环,进入下一次
            }
            System.out.println(i);
        }
        // 最终输出1、3、5、7、9
    }
}

五、补充工具:Random随机数生成

在很多实战案例中(比如猜数字游戏、验证码生成),我们需要生成随机数,Java提供了java.util.Random类专门用来处理随机数生成。

1. 使用步骤

  1. 导包:在类的最上方导入Random类

    import java.util.Random;
  2. 创建Random对象

    Random r = new Random();
  3. 调用方法生成随机数

    // 生成0到9之间的随机数(包含0,不包含10)
    int num = r.nextInt(10);

2. 区间随机数生成技巧

nextInt(n)只能生成0 ~ n-1之间的随机数,要生成指定区间[min, max]的随机数,使用减加法
公式:r.nextInt(max - min + 1) + min

示例

// 生成1-100之间的随机数
int num1 = r.nextInt(100) + 1;

// 生成65-91之间的随机数
int num2 = r.nextInt(27) + 65;

六、案例

求三个整数中的最大值

import java.util.Scanner;

public class MaxNumberTest {
    public static void main(String[] args) {
        // 创建键盘录入对象
        Scanner sc = new Scanner(System.in);
        // 录入三个整数
        System.out.println("请输入第一个整数:");
        int a = sc.nextInt();
        System.out.println("请输入第二个整数:");
        int b = sc.nextInt();
        System.out.println("请输入第三个整数:");
        int c = sc.nextInt();
        
        // 用if分支求最大值
        int max;
        if (a >= b && a >= c) {
            max = a;
        } else if (b >= a && b >= c) {
            max = b;
        } else {
            max = c;
        }
        // 输出结果
        System.out.println("三个数中的最大值是:" + max);
        sc.close();
    }
}

根据工龄计算涨薪额度

import java.util.Scanner;

public class SalaryTest {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // 录入工龄和基本工资
        System.out.println("请输入你的工作工龄:");
        int workYears = sc.nextInt();
        System.out.println("请输入你的基本工资:");
        int baseSalary = sc.nextInt();
        
        // 计算应涨工资
        int addSalary;
        if (workYears >= 10 && workYears < 15) {
            addSalary = 20000;
        } else if (workYears >= 5 && workYears < 10) {
            addSalary = 10000;
        } else if (workYears >= 3 && workYears < 5) {
            addSalary = 5000;
        } else if (workYears >= 1 && workYears < 3) {
            addSalary = 3000;
        } else {
            addSalary = 0;
        }
        
        // 输出结果
        System.out.println("您目前工作了" + workYears + "年,基本工资为 "
                + baseSalary + "元, 应涨工资 " + addSalary + "元,涨后工资 " + (baseSalary + addSalary) + "元");
        sc.close();
    }
}

求1-100中既是3又是5的倍数的数字及和

public class MultipleSumTest {
    public static void main(String[] args) {
        int sum = 0;
        // 遍历1-100的数字
        for (int i = 1; i <= 100; i++) {
            // 判断既是3的倍数,又是5的倍数
            if (i % 3 == 0 && i % 5 == 0) {
                System.out.println(i);
                sum += i;
            }
        }
        System.out.println("以上满足条件的数字之和:" + sum);
    }
}

筛选指定区间内符合条件的三位数

需求:录入一个大于100的三位数,打印100到该数字之间,满足「个位数不为7、十位数不为5、百位数不为3」的数字、个数及和。

import java.util.Scanner;

public class NumberFilterTest {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入大于100的三位整数:");
        int maxNum = sc.nextInt();
        
        int count = 0;
        int sum = 0;
        // 遍历100到输入的数字
        for (int i = 100; i <= maxNum; i++) {
            // 提取个位、十位、百位
            int ge = i % 10;
            int shi = i / 10 % 10;
            int bai = i / 100 % 10;
            // 判断条件
            if (ge != 7 && shi != 5 && bai != 3) {
                System.out.println(i);
                sum += i;
                count++;
            }
        }
        // 输出统计结果
        System.out.println("以上满足条件的数字之和:" + sum);
        System.out.println("以上满足条件的数字个数:" + count);
        sc.close();
    }
}

筛选符合条件的四位数

需求:打印所有四位数中,满足「个位 + 千位 == 百位 + 十位」的数字,并统计总个数。

public class FourDigitFilterTest {
    public static void main(String[] args) {
        int count = 0;
        // 遍历所有四位数
        for (int i = 1000; i <= 9999; i++) {
            // 提取个位、十位、百位、千位
            int ge = i % 10;
            int shi = i / 10 % 10;
            int bai = i / 100 % 10;
            int qian = i / 1000 % 10;
            // 判断条件
            if ((ge + qian) == (bai + shi)) {
                System.out.print(i + " ");
                count++;
                // 每行输出5个数字,换行
                if (count % 5 == 0) {
                    System.out.println();
                }
            }
        }
        // 输出总个数
        System.out.println("\n以上满足条件的四位数总共有 " + count + " 个");
    }
}