【UCB CS 61B SP24】Lecture 11 - Inheritance 4: Iterators, Object Methods学习笔记

本文内容为集合(Set)的介绍与使用,并通过数组手动实现集合,接着介绍了迭代器,使用迭代器我们能够更方便地遍历集合中的元素。

1. Set

1.1 Set介绍与Java实现类的使用

集合(Set)是一种常见的数据结构,用于存储一组唯一的不重复的元素。集合的核心特点是不允许重复元素,并且通常不保证元素的顺序。其核心特性如下:

  • 唯一性:集合中的元素是唯一的,不允许重复。如果尝试添加一个已经存在的元素,集合不会发生改变。
  • 无序性:集合通常不保证元素的顺序(除非使用有序集合实现,如 Java 中的 TreeSetLinkedHashSet)。
  • 动态性:集合的大小可以动态调整,支持添加、删除和查找操作。
  • 高效性:集合的实现通常基于哈希表或平衡树,因此添加、删除和查找操作的时间复杂度通常为 O ( 1 ) O(1) O(1) O ( l o g n ) O(log n) O(logn)

在 Java 中,Set 是一个不包含重复元素的集合接口。它是 java.util 包的一部分,继承自 Collection 接口。Java 已经默认提供了多个 Set 的实现类,常用的有:

(1)HashSet

基于哈希表实现,不保证元素的顺序,允许有 null 元素,插入、删除和查找操作的时间复杂度为 O ( 1 ) O(1) O(1),在 java.util.HashSet 包中:

Set<String> hashSet = new HashSet<>();
hashSet.add("Apple");
hashSet.add("Banana");
hashSet.add("Cherry");
System.out.println(hashSet);  // 输出可能是[Apple, Cherry, Banana],不保证顺序

(2)LinkedHashSet

继承自 HashSet,基于哈希表和链表实现,能够维护元素的插入顺序,允许有 null 元素,插入、删除和查找操作的时间复杂度为 O ( 1 ) O(1) O(1),在 java.util.LinkedHashSet 包中:

Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("Apple");
linkedHashSet.add("Banana");
linkedHashSet.add("Cherry");
System.out.println(linkedHashSet);  // 输出一定是[Apple, Banana, Cherry]

(3)TreeSet

基于红黑树实现,元素按照自然顺序或指定的比较器进行排序,不允许有 null 元素,插入、删除和查找操作的时间复杂度为 O ( l o g n ) O(log n) O(logn),在 java.util.TreeSet 包中:

Set<String> treeSet = new TreeSet<>();
treeSet.add("Cherry");
treeSet.add("Banana");
treeSet.add("Apple");
System.out.println(treeSet);  // [Apple, Banana, Cherry]

Set 接口继承了 Collection 接口的所有方法,以下是一些常用的方法:

  • add(T item):将指定的元素添加到集合中(如果尚未存在)。
  • remove(T item):从集合中移除指定的元素(如果存在)。
  • contains(T item):判断集合中是否包含指定的元素。
  • size():返回集合中的元素数量。
  • isEmpty():判断集合是否为空。
  • clear():移除集合中的所有元素。
  • iterator():返回一个迭代器,用于遍历集合中的元素。
package CS61B.Lecture11;

import java.util.HashSet;
import java.util.Set;

public class SetExample {
    public static void main(String[] args) {
        Set<String> fruits = new HashSet<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");

        // 尝试添加重复元素
        boolean isAdded = fruits.add("Apple");
        System.out.println(isAdded);  // false

        // 遍历集合
        for (String fruit : fruits)
            System.out.println(fruit);

        // 检查元素是否存在
        System.out.println(fruits.contains("Banana"));  // true

        // 移除元素
        fruits.remove("Cherry");
        System.out.println(fruits);  // [Apple, Banana]
    }
}

1.2 手动实现ArraySet

现在我们尝试用数组自己实现一个集合,代码类似之前实现的 AList

package CS61B.Lecture11;

public class ArraySet<T> {
    private T[] items;
    private int size;
    private int capacity;

    private void expandCapacity() {
        this.capacity *= 2;
        T[] newItems = (T[]) new Object[this.capacity];
        System.arraycopy(this.items, 0, newItems, 0, this.size);
        this.items = newItems;
    }

    public ArraySet() {
        this.capacity = 2;
        this.items = (T[]) new Object[this.capacity];
        this.size = 0;
    }

    public int size() {
        return this.size;
    }

    public boolean contains(T item) {
        for (int i = 0; i < this.size; i++)
            if (this.items[i].equals(item)) return true;
        return false;
    }

    public void add(T item) {
        if (this.contains(item)) return;
        if (this.size == this.capacity) this.expandCapacity();
        this.items[this.size++] = item;
    }

    public static void main(String[] args) {
        ArraySet<String> fruits = new ArraySet<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");
        fruits.add("Apple");
        System.out.println(fruits.size());  // 3
    }
}

2. 迭代器

2.1 增强型For循环

在前面使用 Java 的 Set 实现类的例子中我们用的遍历方法是这样的:

for (String fruit : fruits)

这被称为增强型 For 循环,我们手动实现的 ArraySet 并不能这样遍历,这是怎么实现的?其实这种循环是由以下代码组成的:

Iterator<String> it = fruits.iterator();
while (it.hasNext()) {
    String fruit = it.next();
    // Do something ...
}

2.2 Iterator & Iterable

上面这段代码中的 Iterator 称为迭代器,在 java.util.Iterator 中定义,是一个用于遍历集合(如 ListSetMap 等)中元素的接口,它提供了两个核心方法:hasNext()next()

  • boolean hasNext():检查集合中是否还有更多的元素可以遍历。若集合中还有未遍历的元素则返回 true,已经遍历完所有元素则返回 false
  • T next():返回集合中的下一个元素,每次调用 next() 都会自动将迭代器的指针移动到下一个元素。如果集合中没有更多的元素(即 hasNext() 返回 false),调用 next() 会抛出 NoSuchElementException 异常。因此在调用 next() 之前,通常需要先调用 hasNext() 进行检查。

因此我们的 ArraySet 中必须能够通过 iterator() 方法创建并返回一个自己的 Iterator 对象,并且这个对象具有 hasNext()next() 方法,此外还需要让我们的 ArraySet 实现 Iterable 接口(该接口具有 forEach() 方法),这样才能让 Java 知道我们这个类的对象是可以用迭代器遍历的:

package CS61B.Lecture11;

import org.jetbrains.annotations.NotNull;

import java.util.Iterator;

public class ArraySet<T> implements Iterable<T> {
    private T[] items;
    private int size;
    private int capacity;

    private void expandCapacity() {
        this.capacity *= 2;
        T[] newItems = (T[]) new Object[this.capacity];
        System.arraycopy(this.items, 0, newItems, 0, this.size);
        this.items = newItems;
    }

    public ArraySet() {
        this.capacity = 2;
        this.items = (T[]) new Object[this.capacity];
        this.size = 0;
    }

    public int size() {
        return this.size;
    }

    public boolean contains(T item) {
        for (int i = 0; i < this.size; i++)
            if (this.items[i].equals(item)) return true;
        return false;
    }

    public void add(T item) {
        if (this.contains(item)) return;
        if (this.size == this.capacity) this.expandCapacity();
        this.items[this.size++] = item;
    }

    private class ArraySetIterator implements Iterator<T> {
        private int pos;

        public ArraySetIterator() {
            this.pos = 0;
        }

        @Override
        public boolean hasNext() {
            return this.pos < size;
        }

        @Override
        public T next() {
            T item = items[this.pos];
            this.pos++;
            return item;
        }
    }

    public @NotNull Iterator<T> iterator() {
        return new ArraySetIterator();
    }

    public static void main(String[] args) {
        ArraySet<String> fruits = new ArraySet<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");
        fruits.add("Apple");
        System.out.println(fruits.size());  // 3
        for (String fruit : fruits)
            System.out.print(fruit + " ");  // Apple Banana Cherry
    }
}

3. 优化ArraySet

3.1 toString()

toString() 方法在之前的例子中我们已经重写过了,该方法提供对象的字符串表示形式,当我们要输出某个对象时 System.out.println(Object obj),这时会调用 obj.toString() 方法,如果没有重写这个方法那么默认返回的是 类名@地址 的形式。现在我们再重写一遍 toString()

package CS61B.Lecture11;

import org.jetbrains.annotations.NotNull;

import java.util.Iterator;

public class ArraySet<T> implements Iterable<T> {
    ...

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("[");
        for (T item : this) sb.append(item + ", ");
        if (sb.length() > 1) sb.delete(sb.length() - 2, sb.length());
        sb.append("]");
        return sb.toString();
    }

    public static void main(String[] args) {
        ArraySet<String> fruits = new ArraySet<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");
        fruits.add("Apple");
        System.out.println(fruits.size());  // 3
        System.out.println(fruits);  // [Apple, Banana, Cherry]
    }
}

3.2 equals()

Java 中的 == 是按比特位比较两个变量的数值,而不会比较两个地址所分别指向的两个对象中的内容是否相等,先看下面这段代码:

package CS61B.Lecture11;

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

public class EqualsExample {
    public static void main(String[] args) {
        ArrayList<Integer> l1 = new ArrayList<>(Arrays.asList(1, 2, 3));
        ArrayList<Integer> l2 = new ArrayList<>(Arrays.asList(1, 2, 3));
        System.out.println(l1 == l2);  // false
        System.out.println(l1.equals(l2));  // true
    }
}

因为 l1l2 是两个不同列表对象的引用地址,如下图所示,因此用 == 去比较这两个引用那肯定是不相等的:

在这里插入图片描述

如果想要比较自定义类的两个对象的内容是否相等,需要重写 equals(Object obj) 方法,该方法也是所有类从 Object 那边继承过来的:

package CS61B.Lecture11;

import org.jetbrains.annotations.NotNull;

import java.util.Iterator;

public class ArraySet<T> implements Iterable<T> {
    ...

    @Override
    public boolean equals(Object obj) {
        // 判断obj是否是ArraySet的实例,如果是则将obj转换成ArraySet类型的对象arraySet
        if (obj instanceof ArraySet arraySet) {
            if (this.size() != arraySet.size()) return false;
            for (T item : this)
                if (!arraySet.contains(item)) return false;
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        ArraySet<Integer> s1 = new ArraySet<>();
        ArraySet<Integer> s2 = new ArraySet<>();
        for (int i = 1; i <= 3; i++) {
            s1.add(i);
            s2.add(i);
        }
        System.out.println(s1 == s2);  // false
        System.out.println(s1.equals(s2));  // true
    }
}

注意我们用到了 obj instanceof ArraySet arraySet,这是 instanceof 关键字在 Java 14 中引入的新特性,该特性在 Java 16 中正式成为标准特性,被称为模式匹配(Pattern Matching),能够避免手动类型转换可能导致的错误(如忘记转换或转换错误)。

在 Java 14 之前,如果你想检查一个对象是否是某个类的实例,并对其进行类型转换,通常需要写两行代码:

if (obj instanceof ArraySet) {
    ArraySet arraySet = (ArraySet) obj;  // 显式类型转换
    // 使用arraySet...
}

从 Java 14 开始,你可以将 instanceof 检查和类型转换合并为一行代码:

if (obj instanceof ArraySet arraySet) {
    // 直接使用arraySet...
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/977410.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

玩机日记 12 fnOS使用lucky反代https转发到外网提供服务

目录 1、安装lucky 2、更新lucky 3、上传ssl证书 4、设置安全入口&#xff0c;替换fnOS的应用url 5、添加https反代 这一篇主要是解决一下飞牛反代https的问题。可以先看玩机日记 12.5 在PVE Windows11上部署本地AI模型&#xff0c;使用群晖反代https转发到外网提供服务&a…

神经网络八股(3)

1.什么是梯度消失和梯度爆炸 梯度消失是指梯度在反向传播的过程中逐渐变小&#xff0c;最终趋近于零&#xff0c;这会导致靠前层的神经网络层权重参数更新缓慢&#xff0c;甚至不更新&#xff0c;学习不到有用的特征。 梯度爆炸是指梯度在方向传播过程中逐渐变大&#xff0c;…

【ARM】MDK如何生成指定大小的bin文件,并指定空区域的填充数据

1、 文档目标 解决MDK如何生成指定大小的bin文件&#xff0c;并指定空区域的填充数据 2、 问题场景 客户有这样的需求&#xff0c;客户本身的工程编译生成bin文件后&#xff0c;bin文件大小为200k。整体芯片的内存有512k。客户想要最终生成的bin文件可以达到512k的一个情况&a…

Linux-----进程间通信

一、按通信范围分类 同一主机进程通信 传统IPC方式&#xff1a; 管道&#xff08;无名管道、有名管道&#xff09;信号&#xff08;Signal&#xff09; System V IPC&#xff1a; 共享内存&#xff08;效率最高&#xff09;消息队列信号量 POSIX IPC&#xff08;较新标准&#…

Part 3 第十二章 单元测试 Unit Testing

概述 第十二章围绕单元测试展开&#xff0c;阐述了单元测试的实践与重要性&#xff0c;通过对比其他测试类型&#xff0c;突出其特点&#xff0c;还介绍了单元测试的最佳实践、避免的反模式以及与测试替身相关的内容&#xff0c;为编写高质量单元测试提供指导。 章节概要 1…

Windows10配置C++版本的Kafka,并进行发布和订阅测试

配置的环境为&#xff1a;Release x64下的环境 完整项目&#xff1a;https://gitee.com/jiajingong/kafka-publisher 1、首先下载相应的库文件&#xff08;.lib&#xff0c;.dll&#xff09; 参考链接&#xff1a; GitHub - eStreamSoftware/delphi-kafka GitHub - cloade…

Deepseek引爆AI热潮 防静电地板如何守护数据中心安全

近期&#xff0c;Deepseek的爆火将人工智能推向了新的高度&#xff0c;也引发了人们对AI背后基础设施的关注。作为AI运行的“大脑”&#xff0c;数据中心承载着海量数据的存储、处理和传输&#xff0c;其安全稳定运行至关重要。而在这背后&#xff0c;防静电地板扮演着不可或缺…

Spring框架基本使用(Maven详解)

前言&#xff1a; 当我们创建项目的时候&#xff0c;第一步少不了搭建环境的相关准备工作。 那么如果想让我们的项目做起来方便快捷&#xff0c;应该引入更多的管理工具&#xff0c;帮我们管理。 Maven的出现帮我们大大解决了管理的难题&#xff01;&#xff01; Maven&#xf…

QSplashScreen --软件启动前的交互

目录 QSplashScreen 类介绍 使用方式 项目中使用 THPrinterSplashScreen头文件 THPrinterSplashScreen实现代码 使用代码 使用效果 QSplashScreen 类介绍 QSplashScreen 是 Qt 中的一个类&#xff0c;用于显示启动画面。它通常在应用程序启动时显示&#xff0c;以向用户显…

【Vscode 使用】集合1

一、使用make工具管理工程 windows下&#xff0c;下载mingw64&#xff0c;配置好mingw64\bin 为 Win10系统全局变量后。 在mingw64/bin目录下找到mingw32-make.exe工具。复制一份改名为&#xff1a;make.exe&#xff0c;没错&#xff0c;就是那么简单&#xff0c;mingw64自带m…

PHP-create_function

[题目信息]&#xff1a; 题目名称题目难度PHP-create_function2 [题目考点]&#xff1a; create_function ( string args , string args , string code )[Flag格式]: SangFor{wWx5dEGHHhDUwmST4bpXwfjSzq43I6cz}[环境部署]&#xff1a; docker-compose.yml文件或者docker …

golang内存泄漏

golang也用了好几年了&#xff0c;趁着有空 整理归纳下&#xff0c;以后忘了好看下 一般认为 Go 10次内存泄漏&#xff0c;8次goroutine泄漏&#xff0c;1次是真正内存泄漏&#xff0c;还有1次是cgo导致的内存泄漏 1:环境 go1.20 win10 2:goroutine泄漏 单个Goroutine占用内存&…

Python Seaborn库使用指南:从入门到精通

1. 引言 Seaborn 是基于 Matplotlib 的高级数据可视化库,专为统计图表设计。它提供了更简洁的 API 和更美观的默认样式,能够轻松生成复杂的统计图表。Seaborn 在数据分析、机器学习和科学计算领域中被广泛使用。 本文将详细介绍 Seaborn 的基本概念、常用功能以及高级用法,…

修改与 Git 相关的邮箱

要修改与 Git 相关的邮箱信息&#xff0c;需要区分以下两种情况&#xff1a; 1. 修改 Git 提交时使用的邮箱&#xff08;影响提交记录&#xff09; Git 提交记录中的邮箱由本地 Git 配置的 user.email 决定&#xff0c;与 SSH 密钥无关。修改方法如下&#xff1a; 全局修改&a…

用PyTorch从零构建 DeepSeek R1:模型架构和分步训练详解

DeepSeek R1 的完整训练流程核心在于&#xff0c;在其基础模型 DeepSeek V3 之上&#xff0c;运用了多种强化学习策略。 本文将从一个可本地运行的基础模型起步&#xff0c;并参照其技术报告&#xff0c;完全从零开始构建 DeepSeek R1&#xff0c;理论结合实践&#xff0c;逐步…

基于SpringBoot的“流浪动物救助系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“流浪动物救助系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统功能结构图 局部E-R图 系统首页界面 系统…

从零开始玩转TensorFlow:小明的机器学习故事 5

图像识别的挑战 1 故事引入&#xff1a;小明的“图像识别”大赛 小明从学校里听说了一个有趣的比赛&#xff1a;“美食图像识别”。参赛者需要训练计算机&#xff0c;看一张食物照片&#xff08;例如披萨、苹果、汉堡等&#xff09;&#xff0c;就能猜出这是什么食物。听起来…

学习笔记--电磁兼容性EMC

一、基本概念 电磁兼容性&#xff08;Electromagnetic Compatibility&#xff0c;EMC&#xff09;是电子电气设备在特定电磁环境中正常工作的能力&#xff0c;同时不会对其他设备产生不可接受的电磁干扰。其核心目标是确保设备在共享的电磁环境中既能抵抗干扰&#xff0c;又能避…

unity学习51:所有UI的父物体:canvas画布

目录 1 下载资源 1.1 在window / Asset store下下载一套免费的UI资源 1.2 下载&#xff0c;导入import 1.3 导入后在 project / Asset下面可以看到 2 画布canvas&#xff0c;UI的父物体 2.1 创建canvas 2.1.1 画布的下面是 event system是UI相关的事件系统 2.2 canvas…

ArcGIS Pro中创建最低成本路径的详尽教程

一、引言 在地理信息系统&#xff08;GIS&#xff09;的应用场景中&#xff0c;路径分析扮演着至关重要的角色。而最低成本路径分析&#xff0c;则是路径分析中的一种高级应用&#xff0c;它综合考虑了地形、植被、土地利用类型等多种因素&#xff0c;通过加权计算得出一条从起…