BoardInfinity-Java-全栈开发笔记-全-
BoardInfinity Java 全栈开发笔记(全)
001:课程介绍 🚀
在本课程中,我们将学习Java全栈开发专项课程的总体结构与目标。本专项课程旨在为你提供Java编程及其在构建Web应用中的全面理解。

专项课程包含三门核心课程,它们将为你提供成为一名熟练Java开发者所需的知识与技能。
以下是三门课程的简要介绍:
- Java编程基础:这门课程将向你介绍Java编程的核心概念与原则。你将学习Java编程的基础知识、核心概念以及面向对象编程原则。
- 使用Angular进行前端开发:这门课程专注于使用Angular框架构建和开发动态、响应式的前端Web应用。你将学习如何设计和开发能与API交互、并与后端系统集成的复杂Web应用。
- Java后端开发:这门课程专注于使用Spring和Spring Boot框架构建健壮、可扩展的后端系统。你将学习如何开发RESTful Web服务、将前端应用连接到后端,并将应用部署到云平台。

在完成本专项课程后,你将牢固掌握Java编程基础,能够使用Angular开发动态响应式的前端Web应用,并使用Spring和Spring Boot构建健壮可扩展的后端系统。
本节课中,我们一起了解了Java全栈开发专项课程的整体框架与三门核心课程的主要内容。下一节视频中,我们将正式开始学习。
Java全栈开发:01:课程介绍
在本节课中,我们将要学习Java编程基础课程的整体结构与核心内容。本课程旨在为零基础的学员构建坚实的Java编程基础,涵盖从基础语法到数据结构的完整知识体系。
欢迎来到Java编程基础课程。
在本课程中,你将学习Java编程语言的基础知识,以及如何使用Java编写高效且有效的代码。
本课程专为Java编程的完全新手设计,旨在帮助他们建立扎实的基础。
Java基础模块
上一节我们了解了课程的整体目标,本节中我们来看看第一个核心模块。

在第一个模块“Java基础”中,你将学习Java编程语言的基础知识,包括其语法、数据类型和运算符。
以下是该模块将涵盖的关键主题:
- 条件语句
- 循环
- 函数
深入Java编程
掌握了基础语法后,我们将进入更深入的Java编程世界。
在第二个模块“代码Java”中,你将通过探索异常处理、文件I/O和数组操作等主题,更深入地学习Java编程。你还将学习面向对象编程的概念,如继承、多态和抽象。
以下是该模块的核心概念:
- 异常处理:
try-catch-finally代码块。 - 文件I/O:使用
File,FileReader,BufferedReader等类。 - 面向对象编程:
class,extends,@Override等关键字。

面向对象与其他核心概念
理解了基本的面向对象思想后,本节我们将学习更高级的概念和其他编程要点。

在第三个模块“Oops和其他核心概念”中,你将学习更高级的面向对象编程概念,如接口、抽象类和封装。
你还将探索其他核心概念,例如多线程和并发。
以下是本模块的重点:
- 接口:
interface关键字。 - 抽象类:
abstract class关键字。 - 封装:使用
private修饰符和getter/setter方法。 - 多线程:
Thread类或Runnable接口。
Java数据结构

具备了扎实的编程基础后,最后我们将学习如何有效地组织和管理数据。
在第四个模块“Java数据结构”中,你将学习Java中最常用的数据结构,包括数组、列表、栈和队列。
你还将学习搜索和排序算法,以及如何在Java中实现它们。
以下是本模块的主要内容:
- 数据结构:
Array,ArrayList,Stack,Queue。 - 算法:例如,冒泡排序算法可以用
for循环嵌套实现。
课程总结
本节课中,我们一起学习了Java编程基础课程的全貌。课程结束时,你将牢固掌握Java编程的基础知识,并能够使用Java编写高效且有效的代码。
你也将为学习更高级的Java编程主题做好准备,并进一步发展你作为Java开发人员的技能。
003:认识你的讲师 👨🏫
在本节课中,我们将认识本课程的讲师,了解他的专业背景和教学经验,为后续的学习旅程建立初步的信任和了解。
大家好,我是帕布纳·格瓦尼,很高兴能担任本课程的讲师。
我拥有多年的软件工程经验,主要专注于使用微软技术进行后端开发。
我曾与多家公司合作,为他们提供技术和企业培训,并协助他们完成项目。多年来,我精通了 C#、.NET、Python、MEAN 和 MERN 技术栈。
我单独或组合使用这些技术来创建可扩展且健壮的系统,为数百万用户提供有价值的服务。作为一名教育者,我已向超过一千名学生教授过软件开发。
因此,我理解学习软件开发并非易事,但请不必担心,我将作为您学习旅程的向导,为您提供掌握全栈开发所需的所有必要知识和工具。
非常感谢。
本节课中,我们一起认识了讲师帕布纳·格瓦尼,了解了他丰富的行业实战经验与教学背景。这为我们后续系统学习全栈开发奠定了信心基础。在接下来的课程中,我们将跟随讲师的指导,逐步深入技术细节。
004:Java语言基础入门
在本节课中,我们将探索Java编程语言的基础知识。我们将了解Java的历史、核心特性、与其他语言的对比,以及如何搭建开发环境并理解代码的执行过程。

Java简介
Java是一种广泛使用的高级编程语言,最初由Sun Microsystems公司于1995年发布。自那时起,Java的普及度不断增长,如今被应用于多种场景。
以下是Java的一些主要应用领域:
- Web开发:用于构建服务器端应用程序。
- 移动应用开发:特别是Android平台。
- 企业软件开发:构建大型、分布式系统。
Java与C++的对比
上一节我们介绍了Java的概况,本节中我们来看看Java与另一种流行语言C++的异同。虽然Java和C++都是面向对象的语言,但它们之间存在几个关键区别。
以下是两者的一些主要差异:
- 学习与使用难度:Java通常被认为比C++更易于学习和使用。
- 内存管理:Java提供了更高级的特性,如垃圾回收(Garbage Collection)和自动内存管理,而C++需要手动管理内存。
- 平台依赖性:Java遵循“一次编写,到处运行”的原则,而C++代码通常需要针对特定平台进行编译。
开发环境与执行过程
了解了Java的特性后,接下来我们需要知道如何开始编写Java程序。这涉及设置开发环境并理解Java代码是如何被执行的。
以下是开始Java编程的基本步骤:
- 安装JDK:从Oracle官网下载并安装Java开发工具包。
- 配置环境变量:设置
JAVA_HOME和PATH,以便在命令行中访问Java编译器。 - 选择开发工具:可以使用文本编辑器(如VS Code)或集成开发环境(如IntelliJ IDEA)。
- 编写与运行程序:创建
.java源文件,使用javac命令编译,再用java命令运行。
Java代码的执行过程可以概括为:编写源代码 -> 编译为字节码 -> 由JVM解释执行。
本节课中我们一起学习了Java的基础知识,包括其历史、应用、与C++的对比,以及搭建开发环境和理解代码执行流程。这些是开启Java编程之旅的重要第一步。
Java全栈开发:01:什么是Java?🚀


在本节课中,我们将学习Java编程语言的基础知识,包括它的定义、历史、核心特性以及它为何在众多编程语言中脱颖而出。
Java是一种编程语言,也是一个平台。它是一种高级、健壮、面向对象且安全的编程语言。Java作为一个计算平台,最初由Sun Microsystems公司于1995年发布,由James Gosling领导的团队创建。
Java有助于开发小型应用程序模块或作为网页一部分的应用程序。同时,它也用于开发控制台应用程序、Web应用程序、移动应用程序、客户端-服务器架构等。
一个非常重要的点是,Java是一种平台无关的语言,遵循“一次编写,到处运行”的理念。任何运行Java的硬件或软件环境都被称为一个平台。我们需要Java运行时环境来运行Java程序。一旦我们将Java文件编译成.class文件,该文件就可以在任何地方运行。
上一节我们了解了Java的基本定义,接下来我们回顾一下它的发展历史。
正如之前提到的,Java最初由Sun Microsystems于1995年发布。随着每次迭代,Java都集成了新的更新和功能。在Java 8之后,Java标准版9问世,在这个版本中,我们不再需要单独安装JDK和JRE,它们作为一个整体或包提供。
在将Java作为编程语言学习之前,一个关键问题是:既然有这么多编程语言,为什么选择Java?
以下是选择Java的几个主要原因:
- 平台无关性:这是Java最强大的特点。它遵循“一次编写,到处运行”的规则。编译后从
.java文件生成的.class文件可以在任何地方运行。 - 简单易读:作为一种编程语言,Java易于阅读和理解。即使没有编程背景的新手,或者熟悉C++等基础编程语言的人,都能轻松上手。
- 纯面向对象:Java是纯粹的面向对象语言。不编写类,甚至无法编写
main方法。它完全基于面向对象的方法。 - 功能强大:它有助于开发分布式、解释型、健壮且安全的应用程序。
- 可移植性:因为可以“一次编写,到处运行”,所以我们称其为可移植的编程语言。
- 强大的网络支持:为服务器端计算提供了强大的网络基础设施,有助于开发客户端-服务器架构。
- 移动开发:Java被用于顶级移动操作系统中。例如,Android使用Java进行编码。
- 丰富的库:Java拥有非常广泛的标准库,这些库通过对现有C语言库头文件的修改和迭代实现而创建。


本节课中,我们一起学习了Java编程语言。我们了解了Java的定义、发展历史,并重点探讨了其平台无关性、面向对象、可移植性等核心特性,这些特性使其成为一门强大且广泛应用的编程语言。
Java全栈开发:P06:Java语言的核心特性


在本节课中,我们将探讨Java编程语言的核心特性。理解这些特性将帮助我们更好地运用Java进行项目开发。
Java语言的设计目标是使其成为一种可移植、简单且安全的编程语言。除此之外,它还有一些卓越的特性,这些特性对Java的流行起到了重要作用。
上一节我们提到了Java的设计目标,本节中我们来看看这些具体的特性。以下是Java的主要特性列表:
- 面向对象:Java是一门纯粹的面向对象语言。即使编写主方法(程序的入口点),也必须将其包含在一个类中。例如,一个基本的程序骨架必须从定义一个类开始。
- 安全性:Java以安全性著称,可用于开发无病毒系统。其安全性源于没有显式指针,并且Java程序运行在虚拟机沙箱中。
- 平台无关性:Java遵循“一次编写,到处运行”的原则。编译后的字节码可以在任何安装了Java虚拟机(JVM)的平台上运行。
- 解释执行:Java代码被逐行解释执行。这使得在遇到迭代、错误或程序漏洞时,可以即时进行调试和修复。
- 高性能:通过即时编译器(JIT)等技术,Java能够提供接近原生代码的高性能。
- 可移植性:得益于其平台无关性,Java程序可以轻松地在不同操作系统间移植。
- 多线程支持:Java内置对多线程编程的支持。线程类似于独立执行的并发程序,通过定义多个线程,Java程序可以同时处理多项任务。
- 体系结构中立:Java支持类的动态加载,即类在需要时才被加载。它还支持通过Java本地接口(JNI)调用用C或C++等语言编写的函数。
对于需要将项目从旧版本或其他语言迁移到更现代编程范式的开发者而言,Java是一个理想的选择。
了解了Java的特性后,我们来看看它的实际应用领域。Java用途广泛,你可以在以下场景中使用它:

- 控制台应用程序
- Web应用程序
- Android移动应用
- 客户端-服务器架构的服务器端应用(尤其在金融服务行业)
- 嵌入式系统
- 大数据技术
因此,Java为开发不同类型的程序或应用提供了广泛的可能性,你可以根据自己的需求选择合适的应用方向。


本节课中,我们一起学习了Java语言的核心特性,包括面向对象、安全性、平台无关性等,并了解了Java在控制台应用、Web开发、移动应用等多个领域的实际用途。掌握这些特性是有效使用Java进行开发的基础。
Java全栈开发:专项课程(上):07:C++与Java的核心差异 🆚


在本节课中,我们将探讨C++和Java这两种编程语言之间的主要区别。理解这些差异对于从C++背景转向Java开发,或深入理解面向对象编程在不同语言中的实现至关重要。
上一节我们介绍了编程语言的基本概念,本节中我们来看看C++与Java的具体不同之处。
平台依赖性与独立性
C++是平台依赖的。这意味着用C++编写的程序通常只能在编译它的操作系统上运行。
Java则是平台独立的,遵循“一次编写,到处运行”的原则。这得益于Java虚拟机(JVM)。
编译与解释过程
Java既可以编译也可以解释。源代码被编译成字节码(.class文件),然后由JVM解释执行。
C++则仅能编译,无法被解释。源代码被直接编译成特定平台的机器码。
内存管理机制
在Java中,内存管理是系统控制的,主要通过垃圾回收器(Garbage Collector)自动进行。
在C++中,内存管理是完全手动的,开发者需要自行使用 new 和 delete 来分配和释放内存。
库与代码复用支持
Java拥有丰富且多样的类库,为代码复用提供了强大支持。
C++使用头文件(.h),其库的实现和支持相对有限。
全局作用域支持
Java不再支持全局变量或全局函数。所有代码都必须封装在类中。
C++支持全局作用域,允许定义全局变量和函数,也支持命名空间。
以下是其他一些关键区别:
goto语句:Java不支持goto语句,而C++支持。- 多重继承:C++的类可以直接继承多个父类,实现多重继承。
- Java的类不能直接继承多个类,但可以通过接口(interface) 来实现类似多重继承的效果,因为一个类可以实现多个接口。
随着您更多地实践并根据需求实现Java代码,您将更深入地理解这些差异。

本节课中我们一起学习了C++与Java在平台特性、编译过程、内存管理、库支持、作用域规则以及特定语法(如goto和继承)上的核心差异。理解这些是掌握Java语言特性和进行全栈开发的重要基础。下一节我们将继续深入探讨Java编程语言的更多细节。
008:设置开发环境 🛠️
在本节课中,我们将学习如何在Windows操作系统上为Java设置开发环境。主要内容包括了解所需的工具、如何选择并安装集成开发环境(IDE)以及Java开发工具包(JDK)。
概述
要开始使用Java进行开发,你需要两个核心组件:一个集成开发环境(IDE)和Java开发工具包(JDK)。本教程将指导你完成这些工具的安装和基本配置。
选择集成开发环境(IDE)
有多种IDE可用于Java开发。我将使用Eclipse,你也可以选择其他IDE,如Visual Studio Code、IntelliJ IDEA,甚至记事本。但对于编写Java程序,我建议首先使用Eclipse。
Eclipse是Java开发的标准工具之一,并且免费提供。IntelliJ IDEA和其他一些IDE可能需要付费。Visual Studio Code虽然免费,但不会自动提供项目模板和库安装,需要手动通过命令行配置。
最终选择哪款IDE取决于你的个人偏好。
安装Eclipse IDE
要设置Eclipse IDE,你需要访问Eclipse官方网站,下载并安装Eclipse。之后,你需要配置Eclipse插件,以使用你计划采用的特定JDK版本,从而完成开发环境的搭建。
以下是具体步骤:
- 访问Eclipse官方网站。
- 下载适用于你操作系统的最新版本Eclipse(例如,2023-03版本)。
- 如果你使用Windows,可以点击下载
x86_64安装程序并进行安装。 - 如果你使用其他操作系统,请前往“下载包”页面,根据你的操作系统查找并安装相应的可执行文件或安装包。
一个建议:在下载时,你会看到为Java Web开发以及标准版安装的选项。如果你选择“下载包”,则需要自行注意。选择“Java和Web开发者”版本(适用于企业版开发)是一个好选择,因为它不仅支持核心Java项目,也便于你未来开发Web应用程序。
安装Java开发工具包(JDK)
JDK的版本选择完全取决于你。目前,JDK 20是标准版的最新发布,而JDK 17是最新的长期支持版本。我建议选择JDK 17而非JDK 20,因为JDK 20的功能可能在未来被降级。
关于安装顺序:
- 如果先安装Eclipse,之后需要为Eclipse配置JDK。
- 如果先安装JDK,那么在安装Eclipse时,JDK通常会被自动检测到。
你可以访问官方网站(例如 codingdein.com)的B部分和Java版块,那里提供了安装IntelliJ、Eclipse和JDK的详细步骤说明。这些步骤也能帮助你在先安装Eclipse后配置JDK。
验证安装
安装完成后,你可以在命令提示符中验证Java版本。输入以下命令:

java --version
此命令将显示你机器上安装的Java版本。例如,输出可能显示为Java 19。对于学习和开发目的,这没有问题。但如果是进行项目开发,建议使用长期支持版本,目前是JDK 17。
总结
本节课我们一起学习了如何为Java设置开发环境。我们了解了所需的工具(IDE和JDK),探讨了Eclipse IDE的安装步骤,以及如何选择和安装合适的JDK版本。现在,你的开发环境应该已经准备就绪,可以开始使用Java编程语言,并通过编写第一个“Hello World”程序来熟悉它了。


我们下节课再见。
009:本课你将学到什么 🎯
在本节课中,我们将学习构成Java编程的各个核心组件。你将了解Java虚拟机、Java运行时环境和Java开发工具包,并理解它们如何协同工作,使Java程序能够在不同平台上运行。最后,我们将通过编写一个简单的“Hello World”程序来实践,理解Java程序的基本语法和执行流程。
上一段我们概述了本课的目标,接下来,我们来详细看看Java编程的几个核心组件。

Java的核心组件
以下是构成Java生态系统的三个关键部分:
-
Java虚拟机
- JVM是一个抽象的计算机器,它负责执行Java字节码。
- 它的核心作用是提供平台无关性,实现“一次编写,到处运行”。
-
Java运行时环境
- JRE是运行Java程序所需的环境集合,它包含了JVM和Java核心类库。
- 用户只需要安装JRE,就可以运行已编译好的Java程序。
-
Java开发工具包
- JDK是供开发者使用的工具包,它包含了JRE以及编译器、调试器等开发工具。
- 开发者使用JDK来编写、编译和调试Java程序。
这些组件协同工作:开发者使用JDK编写和编译源代码(.java文件)为字节码(.class文件)。然后,JRE中的JVM加载并执行这些字节码,通过调用JRE中的核心类库来完成程序功能。
了解了理论部分后,我们将通过实践来加深理解。本节中,我们将动手编写你的第一个Java程序。
编写“Hello World”程序
我们将创建一个简单的程序来理解Java的基本语法和结构。
-
创建Java源文件
- 使用文本编辑器创建一个新文件,命名为
HelloWorld.java。 - 在文件中输入以下代码:
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } }
- 使用文本编辑器创建一个新文件,命名为
-
编译程序
- 打开命令提示符或终端,导航到
HelloWorld.java文件所在的目录。 - 使用JDK中的
javac编译器进行编译,命令为:javac HelloWorld.java - 如果编译成功,将生成一个
HelloWorld.class的字节码文件。
- 打开命令提示符或终端,导航到
-
运行程序
- 在同一个命令提示符中,使用
java命令运行程序,命令为:java HelloWorld - 此时,JVM会加载
HelloWorld.class文件,执行其中的main方法,你将在屏幕上看到输出:Hello, World!。
- 在同一个命令提示符中,使用
通过这个过程,你可以清晰地看到:我们使用JDK的 javac 工具编译源代码,然后由JRE中的 java 命令启动JVM来执行生成的字节码文件。
本节课中,我们一起学习了Java编程的基础架构。我们首先明确了JVM、JRE和JDK这三个核心组件的定义、区别与协作关系。随后,我们通过编写、编译并运行一个“Hello World”程序,实践了Java程序从源代码到执行的全过程,直观地理解了这些组件是如何各司其职,共同完成“一次编写,到处运行”这一目标的。掌握这些基础知识,是后续深入学习Java全栈开发的重要第一步。
010:JVM、JRE与JDK详解 🚀

在本节课中,我们将学习Java平台的三个核心组件:JVM、JRE和JDK。理解它们之间的区别与联系,是掌握Java程序如何运行的关键。
概述
Java程序的运行依赖于一个完整的软件栈。我们将逐一剖析Java虚拟机(JVM)、Java运行时环境(JRE)和Java开发工具包(JDK),明确各自的职责与包含关系。

JRE:Java运行时环境
上一节我们概述了Java平台,本节中我们来看看JRE。JRE是Java Runtime Environment的缩写,它为Java应用程序的运行提供了必要的环境。
JRE包含运行已编译的Java程序(即字节码)所需的所有核心库和Java虚拟机(JVM)。它不包含任何开发工具,如编译器或调试器。
其核心构成可以用一个简单的公式表示:

JRE = JVM + 核心类库
以下是JRE的主要组成部分:
- Java虚拟机(JVM):负责执行字节码。
- 核心类库:提供Java API,如
java.lang,java.util,java.io等。
简单来说,如果你只想运行别人写好的Java程序,安装JRE就足够了。
JVM:Java虚拟机
了解了运行环境后,我们深入看看其核心——JVM。JVM是Java Virtual Machine的缩写,它是实现Java“一次编写,到处运行”这一跨平台特性的基石。

当我们编写Java源代码(.java文件)后,编译器会将其编译成一种中间格式,称为字节码(.class文件)。字节码是平台无关的。JVM则充当了字节码与底层操作系统和硬件之间的翻译官。它在不同平台上提供统一的运行环境,负责加载、验证、解释或编译(JIT编译)并执行字节码。
其工作流程可以概括为:
- 编写
HelloWorld.java - 编译为
HelloWorld.class(字节码) - JVM加载并执行
HelloWorld.class
JDK:Java开发工具包
前面我们介绍了运行环境JRE和执行引擎JVM,本节中我们来看看开发者的工具箱——JDK。JDK是Java Development Kit的缩写,它是功能最完整的Java套件。
JDK不仅包含了运行Java程序所需的JRE,还额外提供了一系列用于开发、调试、监控和文档化Java应用程序的工具。
其包含关系可以用以下公式清晰地描述:
JDK = JRE + 开发工具
因此,JDK的构成可以进一步展开为:
JDK = (JVM + 核心类库) + 开发工具
以下是JDK包含的主要开发工具:
- 编译器 (
javac):将.java源代码文件编译成.class字节码文件。 - 调试器 (
jdb):用于调试Java程序。 - 打包工具 (
jar):将类文件打包成JAR归档文件。 - 文档生成器 (
javadoc):从源代码注释生成API文档。 - 其他工具:如
java(启动器)、jps(进程状态工具)、jstack(堆栈跟踪工具)等。
对于任何想要编写Java程序的人来说,安装JDK是第一步。


总结
本节课中我们一起学习了Java平台的三大核心组件:
- JVM:Java程序的执行引擎,负责解释/编译并运行字节码,是实现跨平台的关键。
- JRE:Java程序的运行环境,包含JVM和核心类库。用户只需JRE即可运行Java程序。
- JDK:Java程序的开发套件,包含JRE以及全套开发工具(如编译器、调试器)。开发者必须安装JDK。


它们三者的关系可以总结为:JDK ⊃ JRE ⊃ JVM。理解这个层次结构,有助于你更好地搭建开发环境和排查运行时问题。在下一课中,我们将利用JDK编写并运行我们的第一个Java程序。
011:第一个Java程序 - Hello World 👋

在本节课中,我们将学习如何在Java中编写一个简单的“Hello World”程序。这是一个经典的入门程序,用于向初学者介绍一门新编程语言的基本结构和运行方式。


概述
“Hello World”程序是一个简单的程序,其功能是在屏幕上输出“Hello World”字样。由于其结构简单,它常被用作学习新编程语言的第一步。接下来,我们将一步步学习如何在Eclipse集成开发环境中创建并运行这个程序。

创建Java项目
上一节我们介绍了课程目标,本节中我们来看看如何设置开发环境并创建项目。
我将使用Eclipse IDE进行整个Java开发课程。我已经为这个批次新启动了Eclipse。
以下是创建新Java项目的步骤:
- 点击菜单栏的 File -> New。
- 在弹出的窗口中,选择 Other... 选项。
- 在搜索框中输入“Java”,然后选择 Java Project。
- 将项目命名为
Java Fundamentals。 - Eclipse会自动检测并选择已安装的Java标准版环境(在我的机器上是JDK 19)。你可以根据需要更改。
- 点击 Next,然后点击 Finish。
- 系统会询问是否创建
module-info.java文件来存储项目的元信息。我们选择 Don‘t Create,然后点击 Open Perspective。


至此,名为“Java Fundamentals”的项目已成功创建。展开项目后,可以看到一个 src 文件夹,所有Java程序都需要写在这个文件夹下。
创建Java类
现在项目已经建立,我们来创建一个用于编写“Hello World”程序的Java类。
由于这是一个“Hello World”程序,我将在 src 文件夹下创建一个新的类文件。
- 右键点击
src文件夹,选择 New -> Class。 - 将类命名为
HelloWorld。 - 在包(Package)输入框中,填写
basics。包名本质上是一个文件夹,它创建在src下,用于将不同概念的代码分隔开。包有助于模块化代码结构并控制代码的可访问性,这些细节我们将在后续课程中逐一讲解。 - 点击 Finish。
现在,你可以在项目结构中看到一个名为 basics 的文件夹(包),里面有一个 HelloWorld.java 文件。
编写并运行代码
我们已经创建了类文件,接下来需要编写具体的程序代码并执行它。
以下是编写和运行“Hello World”程序的步骤:
- 在
HelloWorld类中,我们需要添加一个main方法。main方法是程序的入口点。你可以使用快捷键:输入main然后按Ctrl + Space来自动生成方法框架。 - 在
main方法内部,编写输出语句:System.out.println("Hello World");。 - 在代码编辑区域的任意位置右键点击,选择 Run As -> Java Application。
程序运行后,你将在控制台看到输出的“Hello World”消息。
代码解析
程序已经成功运行,现在我们来分析一下这段代码的各个部分。
这是一个Java的“Hello World”程序。我们从 HelloWorld 类开始。在Java中,每个应用程序都始于一个类定义。HelloWorld 是类的名称,花括号 {} 内是类的定义体。
在类定义中,我们有一个 main 方法。这是Java应用程序必须包含的主方法,被称为程序的入口点。Java编译器从 main 方法开始编译和执行程序。
主方法的签名是 public static void main(String[] args)。你将在后续课程中学习这些关键字的具体含义。目前只需知道,static 意味着它不需要创建类的实例即可调用,void 表示它不返回任何值,public 使得Java虚拟机(JVM)能够访问并加载这个类。
程序中有一行关键语句:
System.out.println("Hello World");
System 是一个类,out 是它的一个对象,println 是 out 对象的一个方法,用于将参数(这里是“Hello World”消息)打印到标准输出(屏幕)。
关键要点
最后,我们来总结一下从“Hello World”程序中学到的核心概念。
本节课中我们一起学习了:
- 每个有效的Java应用程序都必须有一个与文件名匹配的类定义。例如,如果公共类名是
HelloWorld,那么文件名也必须是HelloWorld.java。 main方法必须位于类定义内部,并且该类必须是公共的(public)。- 编译器只从
main方法开始执行代码。

我希望这个概念对大家都已清晰。请继续关注,以学习更多关于Java的实践内容。我们下节课再见!
012:Java代码的执行过程 🚀

在本节课中,我们将要学习Java代码是如何在计算机上被执行的。我们将详细探讨从编写源代码到最终输出结果的全过程,并理解Java虚拟机(JVM)、Java开发工具包(JDK)和Java运行时环境(JRE)在其中扮演的关键角色。

概述:Java程序的执行之旅
Java程序的执行并非一步到位,而是一个涉及多个组件的流程。这个过程确保了Java“一次编写,到处运行”的特性。接下来,我们将分步拆解这个流程。
第一步:从源代码到字节码
首先,我们编写一个以 .java 为扩展名的程序,这被称为Java源代码。计算机无法直接理解这种源代码。
因此,我们需要使用编译器将源代码编译成字节码。字节码文件的扩展名是 .class。这个过程可以用以下伪代码表示:
源代码 (.java) -> 编译器 (javac) -> 字节码 (.class)
生成的字节码同样无法被计算机硬件直接理解,这就需要Java虚拟机(JVM)登场。
第二步:JVM加载与执行字节码
JVM是一个平台特定的软件,这意味着每个操作系统都有其对应的JVM版本。JVM会加载你的 .class 字节码文件。
JVM内部包含一个Java解释器,它的职责是将字节码逐步转换为目标计算机能够理解的机器码,从而产生程序输出。简单来说:
字节码 (.class) -> JVM(含解释器)-> 机器码 -> 程序输出
由于JVM的存在,只要目标机器安装了正确的JVM,任何Java字节码都可以在其上运行。这实现了Java的跨平台可移植性。
第三步:字节码验证与类加载
字节码通过网络或本地方式被传送到运行时环境(JRE)后,JVM会首先对其进行验证。JVM内置了一个字节码验证器,它在类被加载到运行时环境之后工作。
这个验证器确保字节码是有效且可访问的,同时保护计算机免受各种病毒和不安全网站的威胁。JVM内部还包含了程序运行所需的各种库。
第四步:程序执行与JIT编译
一旦类被成功加载到JVM上,Java程序的执行就正式开始了。JVM作为解释器,会逐步解释执行你的代码。
现代JVM为了提高执行速度,采用了即时编译(JIT)技术。JVM可以同时执行多项任务,JIT编译器也被称为“热点编译器”,因为它能找出字节码中被频繁执行的“热点”部分,并将其直接编译为高效的机器码。
关于编译与解释的常见问题
一个常见的问题是:Java是编译型语言还是解释型语言?
- 编译器:指将源代码转换为字节码的工具(例如
javac)。 - 解释器:指将字节码转换为机器码的编程工具(例如JVM中的解释器)。
因此,我们可以得出结论:Java既是编译型语言,也是解释型语言。因为源代码首先被编译成字节码(编译过程),然后相同的字节码在JVM上被解释执行(解释过程)。
总结


本节课我们一起学习了Java代码的执行过程。我们了解到,这个过程始于 .java 源代码,经过编译成为 .class 字节码,最后由平台特定的JVM加载、验证并解释执行(或通过JIT编译优化)。正是JVM的存在,使得Java具备了强大的跨平台能力。现在,你已经准备好编写自己的Java程序,并让它们按照预期运行了。
013:原始类型、引用类型与类型转换
在本节课中,我们将学习Java中的两种数据类型:原始类型和引用类型。我们还将探讨如何从用户那里读取输入,以及如何在Java中进行类型转换。这些是构建Java程序的基础知识。
原始类型与引用类型
Java的数据类型主要分为两大类:原始类型和引用类型。
原始类型是Java语言内置的基本数据类型。它们直接存储值,并且不是对象。以下是Java中主要的原始类型:
- 整数类型:用于存储整数。
byte:8位有符号整数。short:16位有符号整数。int:32位有符号整数(最常用)。long:64位有符号整数。
- 浮点类型:用于存储带小数点的数字。
float:单精度32位浮点数。double:双精度64位浮点数(最常用)。
- 字符类型:用于存储单个字符。
char:16位Unicode字符。
- 布尔类型:用于存储逻辑值。
boolean:值为true或false。
引用类型则更为复杂,它们是通过类创建的对象。引用变量存储的是对象在内存中的地址(引用),而不是对象本身的值。常见的引用类型包括:
- 数组:用于存储同一类型的多个元素。
- 字符串:
String类,用于存储文本。 - 以及所有其他自定义或Java内置的类。
从用户读取输入

上一节我们介绍了数据类型,本节中我们来看看如何让程序与用户交互。在Java中,我们可以使用 Scanner 类来从键盘读取用户的输入。
要使用 Scanner,首先需要导入它,然后创建一个 Scanner 对象,并将其与标准输入流(通常是键盘)关联。以下是基本步骤:
- 导入
java.util.Scanner包。 - 创建
Scanner对象。 - 使用
Scanner对象的方法(如nextInt(),nextDouble(),nextLine())读取特定类型的输入。
以下是一个简单的代码示例:
import java.util.Scanner; // 1. 导入Scanner类
public class UserInputExample {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); // 2. 创建Scanner对象
System.out.print("请输入一个整数:");
int number = scanner.nextInt(); // 3. 读取一个整数
System.out.println("你输入的数是:" + number);
scanner.close(); // 使用完毕后关闭scanner
}
}
类型转换
了解了如何获取用户输入后,我们有时需要处理不同类型数据之间的转换。类型转换是将一个数据类型的值转换为另一个数据类型的过程。Java中的类型转换主要分为两种:隐式转换和显式转换。
隐式转换(自动类型转换):当将一个小范围类型的值赋给一个大范围类型的变量时,Java会自动进行转换,因为这种转换是安全的,不会造成数据丢失。例如,将 int 赋值给 double。
int myInt = 9;
double myDouble = myInt; // 自动将int转换为double
// 现在 myDouble 的值为 9.0
显式转换(强制类型转换):当需要将一个大范围类型的值赋给一个小范围类型的变量时,必须进行显式转换。这需要在值前加上目标类型并用括号括起来。这种转换可能导致数据精度丢失或溢出。
double myDouble = 9.78;
int myInt = (int) myDouble; // 手动将double转换为int
// 现在 myInt 的值为 9(小数部分被截断)
总结
本节课中我们一起学习了Java编程的几个核心概念。我们首先区分了原始类型(如 int, double, boolean)和引用类型(如 String, 数组)。接着,我们掌握了如何使用 Scanner 类从用户那里读取输入,使程序具备交互能力。最后,我们探讨了类型转换,包括由编译器自动完成的隐式转换和需要程序员手动指定的显式转换。理解这些基础知识对于编写功能完整的Java程序至关重要。
014:变量与数据类型


在本节课中,我们将学习Java编程语言中的两个核心概念:变量和数据类型。理解这些基础知识是编写任何Java程序的起点。
变量概述
变量本质上是一个用于存储值的内存位置。这些值需要在程序执行过程中被使用。为了标识这个存储区域,每个变量都必须被赋予一个唯一的名称。通过这个名称,可以在整个程序执行过程中使用该值。
在使用变量之前,必须用特定的数据类型声明它。
变量的类型
Java中有三种类型的变量:
- 局部变量:在方法、构造函数或代码块内部声明的变量。
- 实例变量:在类内部但在任何方法、构造函数或代码块外部声明的变量(非静态)。
- 类变量(静态变量):使用
static关键字声明的变量,可以通过类名直接访问。
本节课我们将主要关注局部变量。
变量的声明与初始化
以下是声明变量的语法:首先定义数据类型,然后是变量名,接着使用等号 = 为其赋值。
语法:
数据类型 变量名 = 值;
例如,要存储一个整数值,可以这样写:
int a = 10;
这里,int 是数据类型,a 是变量名,10 是存储的值。
你可以在一行语句中声明多个变量,用逗号分隔:
int x, y, z;
也可以先声明变量,稍后再赋值:
int age;
age = 25;
在声明变量的同时赋值,这个过程称为变量的初始化。
数据类型介绍
上一节我们介绍了变量的声明,本节中我们来看看数据类型的细节。数据类型指定了可以存储在变量中的数据的种类。Java中的每个变量都有一个特定的类型,它决定了需要为该变量分配多大的内存空间。
Java的数据类型主要分为两大类:基本数据类型和引用数据类型。
基本数据类型
基本数据类型是Java语言中预定义的。它们直接存储值,而不是存储对值的引用。基本数据类型的大小不随操作系统的改变而改变,这得益于Java的平台无关性。
Java共有8种基本数据类型,以下是它们的分类和说明:
整型:用于存储没有小数部分的整数值。
byte:1字节,范围从 -128 到 127。short:2字节,范围从 -32,768 到 32,767。int:4字节,范围从 -2^31 到 2^31-1。long:8字节,范围从 -2^63 到 2^63-1。
浮点型:用于存储包含小数部分的数值。
float:4字节,单精度浮点数。double:8字节,双精度浮点数。
字符型:
char:2字节,用于存储单个字符。Java基于Unicode标准,因此char占2字节。
布尔型:
boolean:1位(通常按1字节处理),只能存储true或false值。
声明基本数据类型变量时,类型关键字应使用小写字母。
引用数据类型
除了基本数据类型,我们还有引用数据类型。引用数据类型不是预定义的,它们通常指向对象(实例)在内存中的地址。
以下是引用数据类型的主要类别:
- 类:包括用户自定义的类和Java预定义的类(如
String)。 - 接口
- 数组
例如,String 是最常用的引用类型之一。它的声明方式看起来与基本类型类似,但本质上它是一个类。
String name = "Java Learner";
引用类型没有固定的内存大小,其内存分配取决于存储的具体数据。
总结


本节课中我们一起学习了Java的变量和数据类型。我们了解了变量是存储数据的内存位置,以及如何声明和初始化变量。我们详细探讨了Java的8种基本数据类型(byte, short, int, long, float, double, char, boolean)及其特点,并简要介绍了引用数据类型(如 String 和数组)的概念。掌握这些基础知识是进行后续Java编程的关键。
015:数据类型实战指南


在本节课中,我们将学习如何在Java中实际操作基本数据类型和非基本数据类型。我们将通过代码示例来理解它们的声明、赋值、使用方式以及核心区别。

概述
Java中的数据类型分为两大类:基本数据类型和引用数据类型。基本数据类型直接存储值,而引用数据类型存储的是对象的引用(地址)。理解这两者的区别对于编写正确的Java程序至关重要。
基本数据类型操作
上一节我们概述了数据类型,本节中我们来看看如何声明和使用基本数据类型。
基本数据类型包括byte、int、long、float、double、char、boolean。以下是声明和初始化这些类型的示例:
byte age = 25; // 声明一个byte类型变量存储年龄
int viewCount = 123_456_789; // 使用下划线增强大数字的可读性
float price = 10.99F; // 声明float类型时,数字后需加‘F’
char gender = 'M'; // 声明char类型存储单个字符
boolean isValid = true; // 声明boolean类型存储真/假值
要打印这些变量的值,可以使用System.out.println()方法:
System.out.println(age);
System.out.println(viewCount);
System.out.println(gender);
System.out.println(price);
System.out.println(isValid);
运行上述代码,控制台将成功输出所有基本数据类型的值。
非基本数据类型操作
上一节我们操作了基本数据类型,本节中我们来看看非基本数据类型,它们也被称为引用类型。
String是最常见的非基本数据类型,它用于存储一串字符。虽然其语法看起来简单,但它属于引用类型。
String name = "John Doe";
System.out.println(name);
除了String,Java中还有许多预定义的类(如Date)和我们自定义的类,它们都属于非基本数据类型。
以下是使用java.util.Date类的示例:
import java.util.Date; // 首先需要导入Date类
Date currentDate = new Date(); // 创建Date对象
System.out.println(currentDate); // 打印当前日期
基本类型与引用类型的核心区别
理解了各自的操作后,现在我们来探讨它们最根本的区别:值传递与引用传递。
对于基本数据类型,变量直接存储值。将一个基本类型变量赋值给另一个变量时,传递的是值的副本。
对于引用数据类型,变量存储的是对象在内存中的地址(引用)。将一个引用类型变量赋值给另一个变量时,传递的是引用的副本,这意味着两个变量指向内存中的同一个对象。
以下是一个演示区别的示例,使用java.awt.Point类:
import java.awt.Point;
Point point1 = new Point(10, 20); // 创建第一个Point对象
Point point2 = point1; // 将point1的引用赋值给point2
// 打印两个点的坐标,此时它们相同
System.out.println(point1.x + " " + point1.y); // 输出:10 20
System.out.println(point2.x + " " + point2.y); // 输出:10 20
// 通过point1修改y坐标
point1.y = 25;
// 再次打印,两个点的y坐标都变成了25
System.out.println(point1.x + " " + point1.y); // 输出:10 25
System.out.println(point2.x + " " + point2.y); // 输出:10 25
关键点:当修改point1.y时,point2.y也随之改变,因为point1和point2持有的是同一个Point对象的引用。如果这是基本数据类型,改变一个变量的值不会影响另一个变量。
总结

本节课中我们一起学习了:
- 基本数据类型:包括
byte、int、float、char、boolean等,直接存储数据值。 - 非基本(引用)数据类型:如
String、Date和自定义类,变量存储的是对象的引用。 - 核心区别:基本类型赋值是复制值,引用类型赋值是复制引用。因此,通过一个引用修改对象会影响到所有指向该对象的其他引用。


理解这一区别是避免程序中潜在错误的关键。在接下来的课程中,我们将继续探索更多Java的实际应用概念。
016:从用户读取输入 👨💻

在本节课中,我们将学习如何在Java程序中读取用户的输入。这是任何程序不可或缺的一部分,因为只有用户能够根据自己的需求输入数据,程序才能满足其要求。Java提供了多种读取输入的方式,我们将重点介绍最常用的Scanner类。
为什么需要读取用户输入?🤔
读取用户输入是程序与用户交互的基础。它允许程序根据用户提供的数据执行不同的操作,从而使程序更加动态和实用。
Java中读取输入的三种方式

Java提供了三种主要方式来读取输入:
- BufferReader类
- Scanner类
- Console类
本节我们将详细探讨Scanner类,因为它是最简单、最广泛使用的方法。

认识Scanner类 📖

Scanner类位于java.util包中,它扩展了Object类。这个类为我们提供了许多便捷的方法,可以将用户输入的数据解析为特定的数据类型。
默认情况下,Scanner将输入视为字符串。如果你需要其他类型的数据,可以使用其特定的方法进行转换,无需手动从字符串解析。
Scanner类的常用方法
以下是Scanner类中一些用于读取不同数据类型的方法:
next(): 读取下一个字符串(以空格为分隔符)。nextLine(): 读取一整行字符串(包括空格)。nextInt(): 读取一个整数。nextDouble(): 读取一个双精度浮点数。nextBoolean(): 读取一个布尔值。next().charAt(0): 读取一个字符(通常结合next()使用)。
正如你所见,对于每种基本数据类型,Scanner都有对应的方法,这使得输入处理变得非常简单。
动手实践:使用Scanner类
现在,让我们通过一个实际的例子来看看如何使用Scanner类。我们将创建一个程序,读取用户的名字、年龄、性别和联系方式。
首先,我们需要导入Scanner类并创建它的一个实例。
import java.util.Scanner; // 导入Scanner类
public class UserInputDemo {
public static void main(String[] args) {
// 创建Scanner对象,System.in表示从标准输入(键盘)读取
Scanner scanner = new Scanner(System.in);
}
}
接下来,我们将提示用户输入信息,并使用Scanner的不同方法来读取这些数据。
// 提示用户输入姓名
System.out.print("Enter your name: ");
String name = scanner.nextLine(); // 读取整行,包括空格
// 提示用户输入年龄
System.out.print("Enter your age: ");
int age = scanner.nextInt(); // 读取整数
// 提示用户输入性别(M/F)
System.out.print("Enter your gender (M/F): ");
char gender = scanner.next().charAt(0); // 读取第一个字符
// 提示用户输入联系方式
System.out.print("Enter your contact number: ");
double contactNumber = scanner.nextDouble(); // 读取双精度数
// 打印用户详情
System.out.println("\nUser Details:");
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Gender: " + gender);
System.out.println("Contact Number: " + contactNumber);
scanner.close(); // 关闭scanner
运行这个程序,你将能够输入你的信息,并看到程序将它们打印出来。例如:
Enter your name: King Kocher
Enter your age: 23
Enter your gender (M/F): M
Enter your contact number: 1234567890
User Details:
Name: King Kocher
Age: 23
Gender: M
Contact Number: 1.23456789E9
Scanner类的优势与总结 🎯
本节课我们一起学习了如何使用Scanner类从用户读取输入。Scanner类的主要优势在于:
- 易于实现:语法简单,易于学习和使用。
- 类型安全:提供了直接读取基本数据类型的方法,避免了手动转换的麻烦。
- 功能强大:它也支持使用正则表达式来解析更复杂的输入模式。
因此,Scanner类不仅是初学者的最佳选择,也因其便捷性常用于解决算法竞赛中的输入问题。它同样可以用于从数据文件中读取记录。


希望你现在对如何在Java中读取用户输入有了清晰的理解。记住,与用户交互是让程序变得有用的关键一步。
017:Java中的类型转换



在本节课中,我们将学习Java编程语言中的一个基础且重要的概念——类型转换。类型转换是将一种数据类型转换为另一种数据类型的过程。在编写程序时,有时需要将特定类型的数据转换为另一种类型,这个概念就是类型转换。
类型转换的类型
类型转换主要分为两种:隐式类型转换和显式类型转换。
隐式类型转换也称为拓宽转换,由编译器自动完成。显式类型转换也称为窄化转换,需要程序员手动指定。
隐式类型转换(拓宽转换)
上一节我们介绍了类型转换的基本概念,本节中我们来看看隐式类型转换。当我们将一个较小范围数据类型的值赋给一个较大范围数据类型的变量时,Java编译器会自动进行类型转换,这被称为隐式类型转换。因为目标类型能容纳更多数据,所以没有数据丢失的风险。
以下是隐式类型转换的常见方向(箭头表示可自动转换的方向):
byte->shortshort->intint->longlong->floatfloat->double
让我们通过一个例子来理解。假设有一个整数值:
int intValue = 100;
如果要将这个整数值存储到一个长整型变量中:
long longValue = intValue; // 隐式类型转换:int -> long
这里,int类型的值在赋值给long变量前,被自动转换成了long类型。同样地,将这个long值赋给double变量:
double doubleValue = longValue; // 隐式类型转换:long -> double
这个过程由编译器在内部自动完成,因此称为隐式类型转换。
显式类型转换(窄化转换)
上一节我们了解了自动完成的拓宽转换,本节中我们来看看需要手动指定的窄化转换。当我们将一个较大范围数据类型的值赋给一个较小范围数据类型的变量时,由于可能存在数据丢失的风险,编译器不会自动转换。此时,必须使用显式类型转换。
以下是需要显式类型转换的常见情况:
double->floatfloat->longlong->intint->shortshort->byte
让我们看一个例子。假设有一个双精度浮点值:
double doubleVal = 100.56;
如果尝试将其直接赋给一个整型变量,编译器会报错,因为整数类型无法存储小数部分,会导致数据丢失(0.56)。因此,必须手动进行类型转换:
int intVal = (int) doubleVal; // 显式类型转换:double -> int
System.out.println("double值: " + doubleVal); // 输出: 100.56
System.out.println("int值: " + intVal); // 输出: 100
可以看到,小数部分 .56 在转换过程中丢失了。这就是数据丢失的风险。
另一个例子,将一个较大的整数值赋给byte类型:
int largeInt = 200;
byte byteVal = (byte) largeInt; // 显式类型转换:int -> byte
System.out.println("byte值: " + byteVal); // 输出可能不是200,因为byte范围是-128到127
由于byte的范围远小于int,强制转换后得到的值可能与原值完全不同,这同样是数据丢失的体现。
总结
本节课中我们一起学习了Java中的类型转换。我们了解到类型转换分为两种:隐式类型转换(拓宽转换)和显式类型转换(窄化转换)。隐式转换在将小范围类型赋值给大范围类型时自动发生,安全且无数据丢失。显式转换在将大范围类型赋值给小范围类型时需要手动进行,使用(目标类型)的语法,但存在数据丢失的风险。理解这两种转换对于进行数值计算、比较和后续编程至关重要。




Java全栈开发:第18课:Java运算符详解
在本节课中,我们将学习Java编程语言中不同类型的运算符。运算符是用于对操作数执行各种操作的特殊符号。我们将涵盖算术运算符、赋值运算符、关系运算符、逻辑运算符以及三元运算符。同时,我们也将学习运算符的优先级,即运算符被求值的顺序。掌握这些知识对于编写有效的Java程序至关重要。
算术运算符
上一节我们介绍了运算符的基本概念,本节中我们来看看最基础的算术运算符。算术运算符用于执行数学运算,例如加法、减法、乘法和除法。
以下是Java中主要的算术运算符:
+:加法-:减法*:乘法/:除法%:取模(求余数)
代码示例:
int a = 10;
int b = 3;
int sum = a + b; // 结果为 13
int difference = a - b; // 结果为 7
int product = a * b; // 结果为 30
int quotient = a / b; // 结果为 3
int remainder = a % b; // 结果为 1
算术赋值运算符
了解了基本的算术运算后,我们来看一种更简洁的写法。算术赋值运算符用于一步完成算术运算并将结果赋值给变量。

以下是常见的算术赋值运算符:
+=:加后赋值-=:减后赋值*=:乘后赋值/=:除后赋值%=:取模后赋值
代码示例:
int x = 5;
x += 3; // 等同于 x = x + 3; 现在 x 的值为 8
x *= 2; // 等同于 x = x * 2; 现在 x 的值为 16
关系运算符
接下来,我们将学习如何比较值。关系运算符用于比较两个值,并返回一个布尔结果(true 或 false)。
以下是主要的关系运算符:
==:等于!=:不等于>:大于<:小于>=:大于或等于<=:小于或等于
代码示例:
int p = 7;
int q = 5;
boolean isEqual = (p == q); // false
boolean isGreater = (p > q); // true
boolean isNotEqual = (p != q); // true
逻辑运算符
当我们有多个条件需要判断时,就需要逻辑运算符。逻辑运算符用于组合多个布尔表达式,并返回一个布尔结果。
以下是核心的逻辑运算符:
&&:逻辑与(AND),当两边都为真时结果为真。||:逻辑或(OR),当至少一边为真时结果为真。!:逻辑非(NOT),用于反转布尔值。
公式/规则:
true && true结果为truetrue && false结果为falsefalse || true结果为true!true结果为false
代码示例:
boolean a = true;
boolean b = false;
boolean resultAnd = a && b; // false
boolean resultOr = a || b; // true
boolean resultNot = !a; // false
三元运算符
最后,我们来学习一种简洁的条件赋值方法。三元运算符是编写 if-else 语句的一种简写方式。
它的语法结构如下:
variable = (condition) ? expressionIfTrue : expressionIfFalse;
代码示例:
int score = 75;
String result = (score >= 60) ? "及格" : "不及格";
// 因为 score >= 60 为 true,所以 result 的值为 "及格"
运算符优先级
在包含多种运算符的表达式中,运算顺序由运算符优先级决定。优先级高的运算符先被计算。
常见的优先级顺序(从高到低)为:
- 括号
() - 一元运算符(如
!,++) - 算术运算符(
*,/,%高于+,-) - 关系运算符(
>,<,>=,<=) - 相等运算符(
==,!=) - 逻辑与
&& - 逻辑或
|| - 三元运算符
? : - 赋值运算符(
=,+=等)
当不确定时,使用括号可以明确指定运算顺序。
总结
本节课中我们一起学习了Java编程中的核心运算符。
我们首先介绍了用于基本数学计算的算术运算符,然后学习了简化运算和赋值过程的算术赋值运算符。
接着,我们探讨了用于比较值的关系运算符和组合多个条件的逻辑运算符。
最后,我们掌握了简洁的三元运算符以及决定运算执行顺序的运算符优先级规则。
理解并熟练运用这些运算符,是构建程序逻辑和实现复杂计算的基础。
Java全栈开发:19:Java中的运算符


在本节课中,我们将学习Java中的运算符。运算符是编程语言的基础,用于执行各种计算和逻辑判断。理解运算符是掌握Java编程的关键一步。
运算符是一种能够对操作数的值执行特定操作的符号。这些符号用于执行比较、计算或运算等操作,并返回一个结果。Java中的运算符大多借鉴自其他编程语言,其行为符合普遍预期。大多数编程语言或脚本语言都包含这些通用的运算符。


Java中的运算符可以根据其功能分为多个大类。以下是主要的运算符类别:
- 算术运算符:用于执行基本的数学运算。
- 赋值运算符:用于为变量赋值。
- 关系运算符:用于比较两个值。
- 位运算符:用于对整数类型的数据位进行操作。
- 逻辑运算符:用于连接布尔表达式。
- 三元(条件)运算符:一种简洁的条件判断表达式。
instanceof运算符:用于检查对象是否属于特定类。
此外,运算符还可以根据所需操作数的数量进行分类:
- 一元运算符:只需要一个操作数,例如
-a(取负)或++i(自增)。 - 二元运算符:需要两个操作数,这是最常见的类型,例如
a + b(加法)或x > y(大于比较)。
运算符在进行任何计算、比较或运算时都扮演着至关重要的角色。


本节课我们一起学习了Java运算符的基本概念、主要分类(包括算术、赋值、关系、位、逻辑、三元和instanceof运算符)以及根据操作数数量划分的一元和二元运算符。理解这些运算符是编写有效Java代码的基础。在接下来的课程中,我们将深入探讨每一类运算符的具体用法和示例。
020:算术运算符 🧮


在本节课中,我们将要学习Java中的算术运算符。算术运算符用于对基本数据类型执行简单的数学运算和计算。
上一节我们介绍了Java的基础概念,本节中我们来看看如何使用这些运算符进行数学运算。
算术运算符概述
算术运算符用于执行加法、减法、乘法和除法等基本运算。此外,还有一个取余运算符,用于获取除法运算后的余数。虽然递增(++)和递减(--)运算符在分类上属于一元运算符,但由于其常见性,通常也放在算术运算符中讨论。
以下是主要的算术运算符及其功能:
- 加法运算符 (
+): 将两个数值相加。 - 减法运算符 (
-): 将两个数值相减。 - 乘法运算符 (
*): 将两个数值相乘。 - 除法运算符 (
/): 将两个数值相除。 - 取余运算符 (
%): 返回除法运算后的余数。 - 递增运算符 (
++): 将变量的值增加1。 - 递减运算符 (
--): 将变量的值减少1。
运算符的代码实现
让我们通过具体的代码示例来理解这些运算符的用法。假设我们有两个变量:
int number1 = 100;
int number2 = 50;
以下是使用这些运算符的示例:
- 加法:
int sum = number1 + number2;// 结果为150 - 减法:
int difference = number1 - number2;// 结果为50 - 乘法:
int product = number1 * number2;// 结果为5000 - 除法:
int quotient = number1 / number2;// 结果为2 - 取余:
int remainder = number1 % number2;// 结果为0
运算可以直接在输出语句中进行,也可以先存储到变量中再打印。例如:
System.out.println("加法结果: " + (number1 + number2));
// 或者
int result = number1 + number2;
System.out.println("加法结果: " + result);
运行上述代码,我们可以得到预期的计算结果:加法得150,减法得50,乘法得5000,除法得2,取余得0。
递增与递减运算符详解
递增和递减运算符有前置和后置之分,它们对变量值的改变和使用的时机有所不同。
以下是关于递增和递减运算符的要点:
- 后置递增 (
number++): 先使用变量当前的值,然后再将其值增加1。 - 前置递增 (
++number): 先将变量的值增加1,然后再使用这个新值。 - 后置递减 (
number--): 先使用变量当前的值,然后再将其值减少1。 - 前置递减 (
--number): 先将变量的值减少1,然后再使用这个新值。
让我们通过一个例子来理解。假设 number1 的初始值为100:
// 后置递增
System.out.println(number1++); // 输出100,然后number1变为101
// 前置递增
System.out.println(++number1); // number1先变为102,然后输出102
// 后置递减
System.out.println(number1--); // 输出102,然后number1变为101
// 前置递减
System.out.println(--number1); // number1先变为100,然后输出100
运行这段代码,可以清晰地看到前置与后置操作在输出顺序上的区别。
总结


本节课中我们一起学习了Java中的算术运算符。我们了解了用于基本数学计算的加(+)、减(-)、乘(*)、除(/)、取余(%)运算符,并重点探讨了递增(++)和递减(--)运算符的前置与后置区别及其执行顺序。这些运算符是构建更复杂程序逻辑的基础。在实际编程中,你可以根据需求让用户输入数据,然后灵活运用这些运算符进行计算。
021:算术赋值运算符 🧮

在本节课中,我们将要学习Java中的算术赋值运算符。这些运算符是赋值运算符与算术运算符的结合,用于简化代码并提高效率。
上一节我们介绍了基本的算术运算符,本节中我们来看看如何将它们与赋值操作结合起来使用。
概述

算术赋值运算符,有时也被称为复合赋值运算符,它将一个算术运算和赋值操作合并为一个步骤。其基本形式是 变量 op= 表达式,这等价于 变量 = 变量 op (表达式)。
基本语法与示例
让我们通过一个具体的例子来理解其工作原理。假设我们有两个变量:
int number1 = 10;
int number2 = 20;
1. 基本赋值运算符
首先,我们看一个简单的赋值操作:
int number3;
number3 = number1; // 将 number1 的值赋给 number3
System.out.println(number3); // 输出:10
这行代码只是将 number1 的值(10)赋给了新变量 number3。
2. 算术赋值运算符
以下是各种算术赋值运算符的用法。它们都会将运算结果存回左边的变量(本例中是 number1)。
以下是每种运算符的演示:
- 加法赋值 (
+=): 等价于number1 = number1 + number2number1 += number2; // number1 变为 30 - 减法赋值 (
-=): 等价于number1 = number1 - number2number1 -= number2; // number1 变回 10 - 乘法赋值 (
*=): 等价于number1 = number1 * number2number1 *= number2; // number1 变为 200 - 除法赋值 (
/=): 等价于number1 = number1 / number2number1 /= number2; // number1 变回 10 - 取模赋值 (
%=): 等价于number1 = number1 % number2number1 %= number2; // number1 变为 10 (10除以20的余数)
运行上述所有操作后,程序的输出将依次为:10, 30, 10, 200, 10, 10。
总结

本节课中我们一起学习了Java的算术赋值运算符。我们了解到,运算符如 +=、-= 等是赋值与算术运算的快捷方式,它们能使代码更加简洁。记住其通用形式 变量 op= 表达式,并在需要简化 变量 = 变量 op 表达式 这类语句时使用它们。
022:关系运算符详解 🔍


在本节课中,我们将要学习Java中的关系运算符。关系运算符用于比较两个值之间的关系,例如判断是否相等、大于或小于。它们被广泛用于循环语句和条件判断中。本节课将通过简单的代码示例,帮助你理解这些运算符的工作原理。
概述
关系运算符用于比较两个值,并返回一个布尔值(true 或 false)。这个结果可以用于控制程序的流程,例如决定是否执行某段代码。接下来,我们将逐一介绍六种主要的关系运算符。
关系运算符列表
以下是Java中六种主要的关系运算符:
- 等于 (
==): 检查两个值是否相等。 - 不等于 (
!=): 检查两个值是否不相等。 - 大于 (
>): 检查左侧值是否大于右侧值。 - 大于或等于 (
>=): 检查左侧值是否大于或等于右侧值。 - 小于 (
<): 检查左侧值是否小于右侧值。 - 小于或等于 (
<=): 检查左侧值是否小于或等于右侧值。
代码示例与解析
上一节我们列出了所有关系运算符,本节中我们来看看它们在实际代码中如何工作。我们将通过一个具体的例子来演示每个运算符的用法和输出结果。
考虑以下三个整数变量:
int number1 = 10;
int number2 = 20;
int number3 = 7;
现在,我们使用 System.out.println 来输出各个关系运算的结果:
// 检查 number1 是否大于 number2
System.out.println(number1 > number2); // 输出: false
// 检查 number1 是否小于 number2
System.out.println(number1 < number2); // 输出: true
// 检查 number1 是否大于或等于 number2
System.out.println(number1 >= number2); // 输出: false
// 检查 number1 是否小于或等于 number2
System.out.println(number1 <= number2); // 输出: true
// 检查 number1 是否等于 number3
System.out.println(number1 == number3); // 输出: false
// 检查 number1 是否不等于 number3
System.out.println(number1 != number3); // 输出: true
运行结果分析
根据上面的代码,运行程序后会得到一系列布尔值输出。这些结果直观地展示了每个比较操作的真假情况。例如,因为10不大于20,所以 number1 > number2 的结果是 false。因为10不等于7,所以 number1 != number3 的结果是 true。
总结


本节课中我们一起学习了Java的关系运算符。我们了解了六种基本的比较操作:等于、不等于、大于、大于或等于、小于、小于或等于。这些运算符通过比较两个值并返回布尔结果,是构建程序逻辑判断的基础。掌握它们对于编写条件语句和循环至关重要。
Java全栈开发:23:逻辑运算符 🧠


在本节课中,我们将要学习Java中的逻辑运算符。逻辑运算符用于组合多个布尔条件,以构建更复杂的逻辑判断,这在条件语句和循环中非常有用。
上一节我们介绍了关系运算符,本节中我们来看看如何将多个条件组合起来进行判断。
逻辑运算符主要用于执行逻辑“与”和逻辑“或”操作。当需要同时检查多个条件时,就可以使用逻辑运算符。如果你了解数字电子学中的“与门”或“或门”,可以很容易地将其与逻辑运算符对应起来。
需要记住的一点是:在使用逻辑“与”运算符时,如果第一个条件为假,Java将不会评估第二个条件。这种特性被广泛用于测试多个条件以做出决策。Java还提供了逻辑“非”运算符。
以下是Java中提供的三种逻辑运算符:
&&:条件“与”运算符。||:条件“或”运算符。!:逻辑“非”运算符。
接下来,让我们通过一个简单的例子来理解它们的工作原理。
假设我们有两个布尔变量:
boolean x = true;
boolean y = false;
现在,我们使用逻辑运算符对它们进行操作:
x && y:当使用“与”运算符时,只有两个条件都为真,结果才为真。这里x为真,但y为假,所以结果为false。x || y:当使用“或”运算符时,只要有一个条件为真,结果就为真。这里x为真,所以结果为true。!x:当使用“非”运算符时,它会返回条件的相反值。x为真,所以!x的结果为false。
正如前面提到的,这里只是解释了它们的基本工作原理。在后续关于条件语句和循环的课程中,我们将通过更多实际案例来演示如何在程序中灵活运用这些运算符。


本节课中我们一起学习了Java的三种逻辑运算符:&&(与)、||(或)和!(非)。它们是将简单条件组合成复杂逻辑判断的基础工具。记住“与”运算符的短路特性(第一个条件为假则不再判断第二个),这在编写高效代码时很重要。我们将在后续的课程中看到它们的实际应用。
Java全栈开发:24:三元运算符

在本节课中,我们将要学习Java中的三元运算符。三元运算符是if-else语句的一种简洁写法,它包含三个操作数,能够根据条件判断返回两个表达式中的一个结果。

上一节我们介绍了条件判断语句,本节中我们来看看如何使用三元运算符来简化代码。
三元运算符的基本语法如下:
条件 ? 表达式1 : 表达式2
其中,条件是一个布尔表达式。如果条件为true,则运算符返回表达式1的结果;如果条件为false,则返回表达式2的结果。
为了更好地理解其用法,我们来看一个具体的例子。
假设我们有一个布尔变量isAuthenticated,用于记录用户是否已通过身份验证。我们的目标是:如果用户已认证,则显示“登录成功”的消息;否则,显示“未登录”的消息。
以下是使用三元运算符实现此逻辑的代码示例:
boolean isAuthenticated = true;
String result = isAuthenticated ? "You are logged in successfully." : "You are not logged in.";
System.out.println(result);
在这段代码中:
isAuthenticated是条件。- 如果
isAuthenticated为true,则result变量被赋值为"You are logged in successfully."。 - 如果
isAuthenticated为false,则result变量被赋值为"You are not logged in."。 - 最后,打印出
result变量的值。
运行这段代码,由于isAuthenticated被设置为true,因此控制台将输出“You are logged in successfully.”。


本节课中我们一起学习了三元运算符。它是一种非常实用的语法糖,能够将简单的if-else判断浓缩为一行代码,使程序更加简洁易读。记住其语法 条件 ? 表达式1 : 表达式2 并在合适的场景应用即可。
025:运算符优先级 🧮

在本节课中,我们将要学习Java中运算符优先级的概念。运算符优先级是一组规则,它决定了当一个表达式中包含多个运算符时,哪个运算符会先被计算。理解这个概念对于编写正确的数学和逻辑表达式至关重要。
概述

在小学阶段,我们学习过“先乘除后加减”的运算规则。在Java编程中,也存在类似的规则,称为运算符优先级。它规定了不同运算符的执行顺序,优先级高的运算符会先被计算。
运算符优先级表
以下是Java中主要运算符的优先级和结合性规则。结合性决定了当优先级相同的运算符连续出现时,是从左向右计算(左结合)还是从右向左计算(右结合)。
- 一元运算符(如
++,--,+,-):优先级最高,从右向左结合。 - 算术运算符(乘法、除法、取模):优先级次高,从左向右结合。
- 算术运算符(加法、减法):优先级再次之,从左向右结合。
- 移位运算符(如
<<,>>):从左向右结合。 - 关系运算符(如
<,>):从左向右结合。 - 相等运算符(如
==,!=):从左向右结合。 - 位运算符(如
&,^,|):从左向右结合。 - 逻辑运算符(如
&&,||):从左向右结合。 - 条件(三元)运算符(如
? :):从右向左结合。 - 赋值运算符(如
=,+=):优先级最低,从右向左结合。
通过不断练习,你会更清楚地掌握哪个运算符的优先级高于另一个。
示例演示

上一节我们介绍了优先级规则,本节中我们通过一个具体的代码示例来看看这些规则是如何应用的。
考虑以下代码,我们定义了三个变量并进行计算:
int number1 = 50;
int number2 = 100;
int number3 = 30;
int result = number1 + number2 * number3 / 10;
System.out.println(result);
在这个表达式 number1 + number2 * number3 / 10 中:
- 乘法(
*)和除法(/) 的优先级高于 加法(+),因此它们会先被计算。 - 乘法和除法优先级相同,根据从左向右的结合性,先计算
number2 * number3。 - 然后将上一步的结果除以
10。 - 最后,将这个结果与
number1相加。
所以计算过程分解如下:
- 第一步:
100 * 30 = 3000 - 第二步:
3000 / 10 = 300 - 第三步:
50 + 300 = 350
因此,程序运行后的输出结果是 350。
总结
本节课中我们一起学习了Java的运算符优先级。我们了解到,运算符优先级决定了复杂表达式中各部分的运算顺序,而结合性则解决了相同优先级运算符的运算方向问题。掌握这些规则,能帮助我们在开发大型或实时应用程序时,正确编写和理解涉及复杂运算符的计算逻辑。



我们下节课再见。
026:数组与字符串操作入门指南
在本节课中,我们将学习Java中数组和字符串的基础知识。我们将从数组的基本概念开始,了解一维和多维数组,并学习如何创建数组以及对数组执行的各种操作。之后,我们将转向Java中的字符串操作,学习Java提供的不同字符串处理方法,以及如何使用它们。我们还将了解用于高效字符串操作的StringBuffer和StringBuilder。最后,我们将探讨Java中的字符串池及其在内存管理中的重要性。通过本课的学习,你将牢固掌握如何处理数组和字符串,并能在自己的编程项目中应用它们。
数组基础
上一节我们概述了本课内容,本节中我们来看看数组的基础知识。数组是Java中用于存储多个相同类型数据的容器。
一维与多维数组
数组可以分为一维数组和多维数组。一维数组是线性的数据列表,而多维数组(如二维数组)则可以看作是“数组的数组”,常用于表示表格或矩阵结构。

以下是创建一维数组的代码示例:
int[] myArray = new int[5]; // 声明一个可容纳5个整数的数组
数组的创建与操作
创建数组后,我们可以对其进行多种操作。以下是数组的一些基本操作:
- 初始化:可以在声明时直接赋值,例如
int[] arr = {1, 2, 3};。 - 访问元素:通过索引访问,例如
int x = arr[0];(索引从0开始)。 - 修改元素:通过索引赋值,例如
arr[1] = 20;。 - 获取长度:使用
数组名.length属性,例如int len = arr.length;。
字符串操作
在了解了数组之后,本节我们将重点转向Java中的字符串。字符串在Java中是不可变的对象,Java提供了丰富的API来操作它们。
字符串处理方法
Java的String类包含许多用于操作字符串的方法。以下是常用的字符串操作方法:
- 获取长度:
str.length() - 连接字符串:
str1.concat(str2)或使用+运算符 - 比较字符串:
str1.equals(str2) - 提取子串:
str.substring(beginIndex, endIndex) - 查找字符或子串:
str.indexOf(‘a’)
StringBuffer与StringBuilder
当需要进行大量字符串修改时,使用String类可能效率较低,因为每次修改都会创建新对象。此时应使用StringBuffer(线程安全)或StringBuilder(非线程安全,但更快)。
以下是使用StringBuilder的示例:
StringBuilder sb = new StringBuilder(“Hello”);
sb.append(” World”); // 高效地修改字符串
String result = sb.toString();
字符串池
字符串池是Java堆内存中的一个特殊区域,用于存储字符串字面量。它的主要目的是优化内存使用。当创建一个字符串字面量时,JVM会首先检查池中是否已存在相同内容的字符串。如果存在,则返回该引用,避免创建重复对象,从而节省内存。
例如:
String s1 = “Hello”;
String s2 = “Hello”; // s2将指向字符串池中与s1相同的对象
System.out.println(s1 == s2); // 可能输出 true,因为它们引用池中同一对象
总结
本节课中我们一起学习了Java中数组和字符串的核心知识。我们首先介绍了数组,包括一维和多维数组的创建与基本操作。接着,我们深入探讨了字符串,学习了String类的各种操作方法,认识了用于高效字符串处理的StringBuffer和StringBuilder类,并了解了字符串池在内存管理中的重要作用。掌握这些内容是进行Java编程的基础,希望你能在后续的项目中熟练运用它们。
027:数组基础


概述
在本节课中,我们将要学习Java中的数组。数组是一种数据结构,用于存储相同数据类型的多个元素。通过使用数组,我们可以将大量数据高效地组织在单个变量中,并通过索引快速访问每个元素。
为什么需要数组?
在编程中,存储信息是一项至关重要的任务。每个信息片段都需要保存在内存中供未来使用。为了存储成百上千个值,如果为每个值都创建单独的变量,代码将变得冗长且难以管理。
数组解决了这个问题。它允许我们将所有值存储在单个变量中,从而优化代码并提高数据访问效率。
什么是数组?
数组是一种同质的、非原始的数据类型,用于将多个元素保存到一个特定的变量中。
“同质”意味着数组中的所有元素必须是相同的数据类型。这使得通过将偏移量加到基值上来计算每个元素的位置变得更容易。
数组中的每个元素可以通过其索引号直接访问。假设数组的大小为 n,则索引从 0 开始,到 n-1 结束。
例如,一个大小为5的数组,其索引范围为 0 到 4。
数组的优势
以下是使用数组相比使用多个独立变量的几个优势:
- 代码优化:减少代码量,使代码更简洁。
- 随机访问:借助索引可以快速访问任何元素。
- 易于遍历:使用循环可以轻松地遍历所有元素。
- 易于操作和排序:便于对数据进行批量处理和排序。
数组的类型
Java中主要有两种类型的数组:
-
一维数组:用于在单行或单列中存储数据。
- 例如:存储五个学生的分数或五个学生的姓名。
- 声明和初始化示例:
int[] studentMarks = new int[5]; // 声明一个可容纳5个整数的数组 String[] studentNames = {"Alice", "Bob", "Charlie"}; // 声明并初始化一个字符串数组
-
多维数组:用于存储更复杂的数据结构,可以理解为“数组的数组”。
- 例如:存储多个学生对象,每个对象(数组中的元素)又包含多个属性(如姓名、分数、年龄)。这在概念上类似于一个表格或矩阵。
- 例如:在进行依赖于矩阵表或向量的计算时,就会用到多维数组。
- 声明示例:
int[][] matrix = new int[3][3]; // 一个3x3的二维整数数组(矩阵)
总结
本节课我们一起学习了Java数组的基础知识。我们了解了数组的定义——它是一种用于存储同类型元素集合的数据结构。我们探讨了使用数组的优势,包括代码优化和高效的数据访问。最后,我们介绍了一维数组和多维数组两种主要类型,并了解了它们各自适用的场景。


在接下来的课程中,我们将通过实际代码示例来深入学习如何在Java中声明、初始化和操作数组。
028:一维数组 📚

在本节课中,我们将要学习Java编程中的一个基础且重要的数据结构:一维数组。我们将了解它的定义、声明方式、初始化方法以及如何遍历数组中的元素。
什么是数组? 📦

上一节我们介绍了变量的概念,本节中我们来看看如何高效地管理一组相同类型的数据。数组是一种可以存储多个相同类型数据元素的数据结构。一个只包含一个下标或一个维度的数组,被称为一维数组。它本质上是一个相同数据类型或值的列表。
我们可以将一维数组(也称为单维数组)想象成:
- 具有一行和多列的结构。
- 或者具有多行和一列的结构。
例如,一名学生在五门科目中的成绩就可以用一个一维数组来表示。
如何声明与初始化数组? 🛠️
我们使用方括号 [] 来指示数组的大小和维度。声明数组主要有两种方式。
方式一:先声明,后分配内存
首先声明数组变量,然后使用 new 关键字为其分配内存空间。具体选择哪种方式完全取决于你。
以下是声明数组的语法,先放置方括号,稍后通过指定大小(例如5)来分配空间:
数据类型[] 数组名;
数组名 = new 数据类型[大小];
你也可以将这两个语句合并为一行:
数据类型[] 数组名 = new 数据类型[大小];
方式二:声明的同时初始化

在声明数组的同时直接为其赋值,这被称为初始化数组。
如果你在声明时直接给出值,可以省略指定数组大小:
数据类型[] 数组名 = new 数据类型[]{值1, 值2, 值3, ...};
// 或者更简洁的写法:
数据类型[] 数组名 = {值1, 值2, 值3, ...};
如果你想先声明数组并指定大小,然后再逐个赋值,可以这样做:
数据类型[] 数组名 = new 数据类型[大小];
数组名[索引0] = 值1;
数组名[索引1] = 值2;
// ... 以此类推
正如之前所说,每个元素通过索引访问,索引从 0 开始,直到 n-1(其中 n 是数组大小)。对于一个大小为5的数组,有效索引是0到4。
实践:在Java中实现一维数组 💻
现在,让我们在Java中实际编写代码来使用一维数组。
1. 声明数组
假设我想声明一个名为 marks 的数组,并打算稍后分配内存:
int[] marks;
marks = new int[5];
也可以合并成一行:
int[] marks = new int[5];
2. 初始化数组
在声明数组的同时初始化值:
int[] marks = new int[]{10, 20, 30, 40, 50};
// 或者
int[] marks = {10, 20, 30, 40, 50};
如果你分配了值,可以跳过指定大小。
如果你想先声明再逐个赋值:
int[] marks = new int[5];
marks[0] = 100;
marks[1] = 60;
marks[2] = 78;
marks[3] = 80;
marks[4] = 98;
可以看到,索引从0开始,对于大小为5的数组,最后一个索引是4(即 n-1)。
如何遍历数组? 🔄
现在,你可以遍历这个数组。有两种主要方法。
使用传统的 for 循环
使用传统的 for 循环,循环变量 i 通常从0开始(因为我们在处理数组索引),直到 i < marks.length。如果 marks.length 是5,循环将从0执行到4。
for (int i = 0; i < marks.length; i++) {
System.out.println(marks[i]);
}
使用 for-each 循环
你也可以使用 for-each 循环来打印数组元素。for-each 循环的语法更简洁。
for (int value : marks) {
System.out.println(value);
}
for-each 循环会从 marks 数组中依次取出每个值并赋值给变量 value,然后打印出来。两种循环方式都将执行五次,打印出数组中的所有值。
运行你的程序,它将打印两次成绩:一次借助传统循环,一次借助 for-each 循环。
总结 📝
本节课中我们一起学习了Java一维数组的核心知识。我们了解了数组是一个存储同类型数据的列表,掌握了声明数组(int[] arr;)、分配内存(new int[5])以及初始化({1,2,3})的方法。关键点在于数组索引从0开始,通过 数组名[索引] 访问元素。我们还学习了使用传统 for 循环和 for-each 循环来遍历数组。数组是组织数据的强大工具,为学习更复杂的数据结构打下了基础。
请继续关注后续课程,我们将学习多维数组以及数组相关的各种方法。

下次课再见!🎬
029:多维数组


在本节课中,我们将要学习多维数组的概念、声明方式以及如何通过嵌套循环来访问和操作其中的元素。多维数组是处理表格、矩阵等结构化数据的有效工具。
上一节我们介绍了数组的基础知识,本节中我们来看看当数据需要以表格或矩阵形式存储时,我们该如何处理。
并非所有情况都使用一维数组。例如,当需要以表格、矩阵或向量的形式存储数据时,就需要用到多维数组。
多维数组至少包含两个维度。它看起来像一个矩形数组,因为每一行具有相同的长度,但列数可以不同。它可以是二维数组、三维数组或更多维度的数组。
我们使用逗号分隔多个维度来声明多维数组。为了存储和访问多维数组中的元素,需要使用嵌套循环,其中外层循环处理行,内层循环处理列。
以下是多维数组的声明方式:
int[][] arrayName = new int[3][3];
你可以看到这里我定义了两个维度。第一个维度是行,有三行。第二个维度是列,在我的例子中,有三列。所以这是一个3行3列的数组。
在第一行中,行索引为0,列索引不断变化。在第二行中,行索引为1,列索引不断变化,依此类推。
接下来,让我们尝试实际实现一个多维数组。这里我想存储一个成绩数组。
我希望存储三个学生、每个学生五门科目的成绩。因此,我以这种方式创建多维数组。
我也可以在不预先分配内存的情况下,直接根据这些值分配数据,内存会自动分配。这里我创建了三行,每行有五门科目的成绩:{67, 78, 87, 89, 98}, {76, 77, 56, 65, 90}, {67, 79, 92, 63, 55}。
正如我所说,这将是3行5列。我需要一个嵌套的for循环。第一个for循环将遍历行,第二个for循环将遍历列。
以下是遍历数组的代码结构:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
System.out.print(marks[i][j] + "\t");
}
System.out.println();
}
外层循环变量i从0迭代到2,对应三行。内层循环变量j从0迭代到4,对应五列。通过打印marks[i][j],可以访问每个元素。
如果不想以简单列表形式打印,而希望以表格格式打印,则不要在每打印一个元素后就换行。只有在行改变时才换行。我同时使用了制表符\t作为转义字符,以便在每个行的元素之间看到空格。
在表格格式中,所有五门科目的成绩都会被清晰地打印出来。
我希望多维数组的概念对你来说已经清晰,你已了解如何处理数组。除了基本类型数组,我们还有对象数组。在java.util包中有一个Arrays类,它包含许多预定义的方法,我们将在后续课程中讨论它们。

本节课中我们一起学习了多维数组的定义、声明、初始化以及如何使用嵌套循环进行遍历和格式化输出。掌握多维数组是处理复杂结构化数据的重要一步。我们下节课再见。
030:字符串处理



在本节课中,我们将学习Java编程中最重要的部分之一:字符串。字符串是编程不可或缺的组成部分,Java提供了String类来创建和操作字符串。
什么是字符串?
字符串是一个表示字符序列的对象。虽然字符串在内部由字符数组实现,但它是使用最频繁的数据类型。它的语法看起来像基本类型,但它实际上是一个引用类型。
String类是核心库的一部分,提供了多种处理字符串的方法。
字符串的特性:不可变性
字符串是不可变的。这意味着一旦为字符串创建了对象,其值就无法更改。当你试图改变其值时,系统会为你创建一个新的字符串对象。
你可以通过对现有字符串执行各种操作,或使用字符串方法来创建新的字符串。
字符串的初始化方式如下:
String myString = "Hello";
在内部,它会将字符串创建为一个字符数组,例如第一个字符是H,第二个是e,依此类推。
创建字符串的两种方式
在Java中,有两种定义字符串的方式。
1. 使用字符串字面量
字符串字面量是一种用于表示值的符号。你可以直接将一个值赋给字符串变量。
2. 使用new关键字
使用new关键字时,你实例化或创建了一个String对象,并将字符串作为参数传递给String类的构造函数,从而初始化字符串。
我们也可以通过复制的方式来创建字符串,这将在后面讨论。
所有使用new关键字创建的字符串对象都会在堆内存中分配空间,无论堆内存中是否已存在相同值的字符串,因为其内存地址不同。
字符串的类型:不可变与可变
字符串有两种类型:不可变字符串和可变字符串。
- 不可变字符串:无法更改。这是使用
new关键字或字符串字面量创建的普通字符串。 - 可变字符串:可以使用
StringBuilder类。如果你不希望每次需要频繁修改时都创建新的字符串对象,可以选择StringBuilder类。
在接下来的实践演示中,我将向你展示这两种类型。
总结


本节课我们一起学习了Java中字符串的基础知识。我们了解了字符串的本质是一个对象,它有两种创建方式,并且具有不可变的特性。我们还简单介绍了可变字符串StringBuilder的概念,它适用于需要频繁修改字符串的场景。在下一节中,我们将进行实际操作来巩固这些概念。
031:字符串操作与方法 🧵

在本节课中,我们将学习Java中字符串的声明方式以及常用的字符串操作方法。我们将通过字符串字面量、字符串对象以及内置方法来创建和操作字符串。

字符串的声明
首先,我们来看看如何声明字符串。在Java中,主要有两种方式:使用字符串字面量和使用new关键字创建字符串对象。
以下是两种声明方式的示例:

String str1 = "Hello"; // 字符串字面量
String str2 = new String("World"); // 字符串对象
你可以使用System.out.println()来打印这些字符串:
System.out.println(str1);
System.out.println(str2);
此外,你还可以将字符串字面量和字符串对象连接起来:
String str3 = str1 + str2;
System.out.println(str3);
运行上述代码,你将看到连接后的字符串输出。

常用的字符串方法
上一节我们介绍了如何声明字符串,本节中我们来看看Java提供的强大字符串方法。这些方法可以帮助我们执行各种操作,例如分割字符串、比较字符串、计算长度、替换字符等。
以下是几个最常用的字符串方法及其用途:
length():计算字符串的长度(包括空格)。charAt(index):获取字符串中指定索引位置的字符。concat(string):连接两个字符串。substring(startIndex, endIndex):获取字符串的子串。equals(string):比较两个字符串是否相等。contains(sequence):检查字符串是否包含指定的字符序列。toUpperCase()/toLowerCase():将字符串转换为大写或小写。trim():去除字符串首尾的空白字符。
方法使用示例
让我们通过代码来演示这些方法的具体用法。假设我们有一个字符串str3,其值为"HelloWorld"。
// 计算字符串长度
int length = str3.length(); // 结果为 10
// 获取特定索引的字符
char firstChar = str3.charAt(0); // 结果为 'H'
// 连接字符串 (与使用 + 号效果相同)
String concatenated = str1.concat(str2); // 结果为 "HelloWorld"
// 获取子串
String sub = str3.substring(0, 5); // 结果为 "Hello"
// 比较字符串是否相等
boolean isEqual = str1.equals(str2); // 结果为 false
// 检查是否包含特定字符序列
boolean containsHello = str3.contains("Hello"); // 结果为 true
// 转换大小写
String upperCase = str3.toUpperCase(); // 结果为 "HELLOWORLD"
String lowerCase = str3.toLowerCase(); // 结果为 "helloworld"
// 去除首尾空格
String stringWithSpaces = " Hello ";
String trimmed = stringWithSpaces.trim(); // 结果为 "Hello"
运行这些代码,你可以在控制台看到每个方法对应的输出结果,从而直观地理解它们的功能。



本节课中我们一起学习了Java中字符串的两种创建方式以及一系列核心的字符串操作方法。掌握这些基础方法是进行有效字符串处理的关键。在后续的课程中,我们将继续探索Java编程的其他重要概念。
Java全栈开发:07:StringBuffer与StringBuilder详解 🧵

在本节课中,我们将要学习Java中两个重要的类:StringBuffer和StringBuilder。我们将探讨它们与普通String类的区别,理解“可变性”的概念,并通过代码示例比较它们的性能与使用场景。

在上一节中,我们讨论了String及其方法,并了解到String对象是不可变的。这意味着每次修改字符串内容时,实际上都会创建一个新的String对象。

然而,StringBuffer是一个可变的类,这意味着它的内容可以被修改。它提供了一种在Java中创建可变字符串的方式,并且是线程安全的,允许多个线程同时安全地使用和修改它。
为了实现线程安全的优势,StringBuffer的实现相对较重。与之类似但不同的另一个类是StringBuilder。

StringBuffer拥有诸如append、insert、delete和reverse等方法,这些方法允许直接修改字符串内容。接下来,让我们通过实践来理解它。
以下是创建和使用StringBuffer的基本步骤:
- 首先,需要实例化
StringBuffer对象,它位于java.lang包中。 - 可以为其分配一个初始字符串,例如“Hello”。
- 使用
append方法可以向现有内容追加新的字符串。 - 可以打印出最终的字符串内容。
StringBuffer buffer = new StringBuffer("Hello");
buffer.append(" World");
System.out.println(buffer.toString()); // 输出:Hello World
此外,你还可以检查StringBuffer的初始容量。如果初始化时不指定内容,它将有一个默认的初始容量。
StringBuffer buffer = new StringBuffer();
System.out.println(buffer.capacity()); // 输出默认容量,例如 16
当你向其中添加内容时,容量会根据需要自动增加。这就是StringBuffer的基本工作原理。
接下来,我们将StringBuffer与StringBuilder进行比较。StringBuilder同样提供可变字符串的功能,但它缺乏线程安全性,因此不能被多个线程同时安全使用。这是两者之间的主要区别。
让我们也实现一个StringBuilder的示例:
StringBuilder builder = new StringBuilder("Hello");
builder.append(" World");
System.out.println(builder.toString()); // 输出:Hello World
从表面上看,两者的操作和结果是相同的。但正如之前提到的,它们在性能上存在差异。为了展示这一点,我们可以进行一个简单的性能测试。
我们将分别使用StringBuffer和StringBuilder执行大量追加操作,并计算各自所需的时间:
// 测试 StringBuffer
long startTime = System.currentTimeMillis();
StringBuffer buffer = new StringBuffer("Hello");
for (int i = 0; i < 10000; i++) {
buffer.append("World");
}
long timeTakenByBuffer = System.currentTimeMillis() - startTime;
System.out.println("Time taken by StringBuffer: " + timeTakenByBuffer + " ms");
// 测试 StringBuilder
startTime = System.currentTimeMillis();
StringBuilder builder = new StringBuilder("Hello");
for (int i = 0; i < 10000; i++) {
builder.append("World");
}
long timeTakenByBuilder = System.currentTimeMillis() - startTime;
System.out.println("Time taken by StringBuilder: " + timeTakenByBuilder + " ms");
运行上述代码后,你会发现StringBuilder所花费的时间通常少于StringBuffer。
🎼 这是因为StringBuilder虽然不保证线程安全,但正因如此,它的实现更轻量,在单线程环境下执行修改操作的速度比StringBuffer更快。
本节课中我们一起学习了StringBuffer和StringBuilder。关键点总结如下:
String是不可变的,而StringBuffer和StringBuilder是可变的。StringBuffer是线程安全的,但性能相对较低。StringBuilder不是线程安全的,但在单线程环境下性能更高。- 选择使用哪个类取决于你的具体需求:需要线程安全时用
StringBuffer,追求单线程性能时用StringBuilder。


033:控制语句入门指南
在本节课中,我们将学习Java中的控制语句。控制语句是编程的核心,它们允许程序根据不同的条件做出决策,或者重复执行某些任务。掌握它们对于编写高效、灵活的程序至关重要。
什么是控制语句? 🤔
控制语句用于根据特定条件或循环来控制程序的执行流程。

简单来说,它们决定了代码“何时”以及“如何”运行。没有控制语句,程序只能从上到下顺序执行,无法处理复杂的逻辑。
你将学到什么 📚
上一节我们介绍了控制语句的基本概念,本节中我们来看看具体包含哪些内容。以下是本节课的核心知识点列表:
- 条件结构:学习如何使用
if-else语句和switch-case语句,让程序能够根据不同情况执行不同的代码块。 - 循环结构:掌握
for、while和do-while循环,使你能够重复执行代码,直到满足特定条件。 - 流程控制关键字:理解
break和continue语句在循环和条件结构中的用法,以更精细地控制循环过程。 - 增强型循环:探索
for-each循环在处理数组时的便捷应用。 - 实践练习:课程最后将通过一个名为“Fsbu”的趣味练习,帮助你巩固和应用所学概念。
核心概念详解 🔍
1. 条件结构
条件结构让程序具备判断能力。其核心思想是:如果某个条件成立,就执行A代码;否则,执行B代码。
if-else语句:这是最基础的条件判断。if (condition) { // 条件为真时执行的代码 } else { // 条件为假时执行的代码 }switch-case语句:适用于基于一个变量有多个确定值需要分支的情况。switch (variable) { case value1: // 代码块1 break; case value2: // 代码块2 break; default: // 默认代码块 }
2. 循环结构
循环用于自动化重复任务。想象一下,你需要打印数字1到100,手动写100行打印语句是不可行的,而循环只需几行代码。
for循环:通常用于已知循环次数的场景。for (初始化; 条件; 更新) { // 循环体 }while循环:只要条件为真,就持续执行循环。可能一次都不执行。while (condition) { // 循环体 }do-while循环:先执行一次循环体,再检查条件。至少会执行一次。do { // 循环体 } while (condition);
3. 流程控制关键字 break 与 continue
在循环中,有时我们需要提前退出或跳过某次迭代。
break:立即终止整个循环。continue:跳过当前循环的剩余语句,直接进入下一次迭代。
4. 增强型 for-each 循环
这是遍历数组或集合的一种简洁语法。你不需要管理索引,循环会自动处理每个元素。
for (元素类型 变量名 : 数组或集合) {
// 使用变量名操作当前元素
}
总结与展望 🎯
本节课中,我们一起学习了Java控制语句的核心内容。我们从控制程序流程的基本概念出发,详细探讨了实现条件判断的 if-else 和 switch-case 语句,以及实现重复执行的 for、while 和 do-while 循环。此外,我们还了解了用于精细控制循环的 break 和 continue 语句,以及遍历数组的便捷工具 for-each 循环。
通过理解这些控制语句,你将能够编写逻辑更复杂、效率更高的Java程序。接下来的“Fsbu”练习是检验学习成果的好机会,请务必动手实践。我们下个视频再见。
034:Java中的条件构造


在本节课中,我们将学习Java中的条件语句。条件语句是Java控制结构的一部分,用于根据特定条件控制程序的执行流程。我们将介绍几种主要的条件构造,包括if、if-else、else-if(嵌套if)以及switch-case语句。

什么是条件语句?🤔
条件语句是可执行的代码块,其执行与否取决于特定条件的真假。条件通过我们使用的运算符返回true或false。顾名思义,它们控制着程序的执行流程,用于基于条件做出决策。
条件语句的类型
以下是Java中主要的条件语句类型:
if语句:最基本的条件语句,根据单一条件决定是否执行代码块。if-else语句:提供两个分支,根据条件真假执行不同的代码块。else-if语句(嵌套if):用于处理一系列连续的测试条件,有多个条件和多个备选执行路径。switch-case语句:适用于多选一的操作场景,也属于条件构造。
上一节我们概述了条件语句的类型,接下来让我们逐一深入了解它们的工作原理。
if语句详解
if语句是最基础的条件构造,它根据特定条件决定是否执行一组语句。执行与否取决于该条件的返回值是true还是false。
下面的流程图清晰地展示了if语句的执行逻辑:首先检查if条件,如果条件为true,则执行if代码块;如果条件为false,则跳过该代码块,继续执行后续程序。

if-else语句详解
if-else语句是一种控制结构,它根据条件在两组语句中选择一组执行。当你有多个条件和一个备选方案时使用它。如果条件为true,则执行if块;如果条件为false,则执行else块。
else-if(嵌套if)语句详解
else-if语句,也称为嵌套if语句,用于需要按顺序执行一系列测试的情况。它包含多个条件和所有条件的一个最终备选方案(else)。
其工作原理如下:如果第一个if条件满足,则执行对应的代码块。如果不满足,则检查下一个else if条件。如果某个else if条件为true,则执行其对应的代码块。此过程会持续检查所有else if条件。即使最后一个else if条件也不为true,最终将执行else代码块,然后控制流退出整个条件结构。
switch-case语句简介
switch-case语句也属于条件构造,它专门用于处理多选一的场景,根据一个表达式的值,跳转到匹配的case分支执行代码。
了解了各种条件语句的结构后,我们将在下一节通过实际代码示例来演示它们的用法。
总结📝
本节课我们一起学习了Java中的条件构造。我们了解到条件语句是控制程序流程的关键,它们基于条件的真假(true/false)来决定执行哪部分代码。我们介绍了四种主要的条件语句:基础的if语句、提供备选方案的if-else语句、处理多条件序列的else-if语句,以及适用于多路选择的switch-case语句。在接下来的课程中,我们将通过实践来巩固这些概念。




035:条件结构实战指南

在本节课中,我们将学习Java中条件结构的使用方法。我们将通过实际例子,了解if、if-else、if-else-if以及嵌套条件语句的工作原理,并探讨如何将条件语句与逻辑运算符结合使用。

简单的 if 语句
首先,我们来看一个简单的if语句。假设我们有一个布尔变量isAuthenticated,它存储用户是否已通过身份验证。
boolean isAuthenticated = true;
if (isAuthenticated == true) {
System.out.println("Logged in");
}
如果isAuthenticated的值为true,程序将输出“Logged in”。否则,程序将跳过这个代码块,不执行任何操作。
if-else 语句
上一节我们介绍了简单的if语句,本节中我们来看看if-else语句。当我们需要处理一个条件及其相反情况时,if-else结构非常有用。
以下是使用if-else重写的例子:
boolean isAuthenticated = true;
if (isAuthenticated == true) {
System.out.println("Logged in");
} else {
System.out.println("Not logged in");
}
在这个结构中,如果条件为真,则执行if块;如果条件为假,则执行else块。
if-else-if 阶梯语句
接下来,我们探讨更复杂的情况。假设我们需要根据商品的售价和成本价判断盈利状况。
以下是使用多个独立if语句的实现方式:
float sellingPrice = 1200;
float costPrice = 1000;
if (sellingPrice > costPrice) {
System.out.println("Profit");
}
if (costPrice > sellingPrice) {
System.out.println("Loss");
}
if (sellingPrice == costPrice) {
System.out.println("No profit, no loss");
}
然而,这种方法存在性能问题,因为即使第一个条件为真,程序仍会检查所有后续的if语句。
为了优化,我们可以使用if-else-if阶梯语句:
float sellingPrice = 1200;
float costPrice = 1000;
if (sellingPrice > costPrice) {
System.out.println("Profit");
} else if (costPrice > sellingPrice) {
System.out.println("Loss");
} else {
System.out.println("No profit, no loss");
}
在这种结构中,一旦某个条件为真,执行相应的代码块后,程序就会跳出整个条件判断,不再检查后续条件,从而提高了效率。
嵌套的 if-else 语句
我们也可以使用嵌套的if-else语句来实现相同的逻辑:
float sellingPrice = 1200;
float costPrice = 1000;

if (sellingPrice > costPrice) {
System.out.println("Profit");
} else {
if (costPrice > sellingPrice) {
System.out.println("Loss");
} else {
System.out.println("No profit, no loss");
}
}
嵌套if-else和if-else-if阶梯语句的输出结果是相同的,你可以根据代码的可读性和逻辑清晰度来选择使用哪一种。
结合逻辑运算符
在之前的模块中,我们学习了运算符。现在,我们来看看如何将条件语句与逻辑运算符结合使用。
假设有三个布尔变量,分别表示用户是否登录、邮箱是否验证、支付卡信息是否有效。只有当所有条件都为真时,才允许用户进行购买。
以下是使用逻辑运算符&&的实现方式:
boolean isLoggedIn = true;
boolean isEmailVerified = false;
boolean isCardInfoValid = true;
if (isLoggedIn && isEmailVerified && isCardInfoValid) {
System.out.println("You are allowed to make a purchase.");
} else {
System.out.println("You are not allowed to make a purchase.");
}
这种方法简洁明了,使用&&运算符确保所有条件必须同时为真。
使用嵌套条件实现
同样的逻辑也可以使用嵌套的if语句来实现:
boolean isLoggedIn = true;
boolean isEmailVerified = false;
boolean isCardInfoValid = true;
if (isLoggedIn) {
if (isEmailVerified) {
if (isCardInfoValid) {
System.out.println("You are allowed to make a purchase.");
} else {
System.out.println("Card info invalid. Purchase not allowed.");
}
} else {
System.out.println("Email not verified. Purchase not allowed.");
}
} else {
System.out.println("Not logged in. Purchase not allowed.");
}
嵌套方式可以让你为每个失败的条件提供更具体的错误信息,但代码结构会更复杂。选择哪种方式取决于你的具体需求和逻辑清晰度。
总结
本节课中我们一起学习了Java中条件结构的使用。
- 我们了解了基础的
if语句。 - 我们学习了如何处理两种对立情况的
if-else语句。 - 我们探讨了用于处理多个互斥条件的
if-else-if阶梯语句及其性能优势。 - 我们看到了使用嵌套
if-else实现相同逻辑的另一种方式。 - 最后,我们结合逻辑运算符,学习了如何高效地检查多个条件。


掌握这些条件结构是进行Java编程决策和流程控制的基础。
Java全栈开发:P36:Switch-Case语句详解 🔄


在本节课中,我们将学习Java中的switch-case语句。与if-else语句不同,switch-case提供了多条执行路径,它根据表达式的值来匹配不同的情况,非常适合处理多选一的场景。
概述
switch语句会评估一个表达式,并根据其值执行相应的代码块。它支持多种数据类型,包括byte、char、short、int以及String类。当有多个条件分支时,使用switch可以使代码结构更清晰。
Switch语句的基本结构
一个典型的switch语句包含一个表达式、多个case分支、可选的break语句以及一个可选的default分支。其基本语法如下:
switch (expression) {
case value1:
// 代码块1
break;
case value2:
// 代码块2
break;
...
default:
// 默认代码块
}
当expression的值与某个case的值匹配时,程序会执行该case下的代码,直到遇到break语句或switch语句结束。如果没有任何case匹配,则执行default分支的代码。
实践示例:用户权限管理
为了更好地理解switch-case的工作原理,我们来看一个具体的例子。假设我们有一个系统,根据用户类型(如管理员、子管理员、测试员、普通用户)来分配不同的访问权限。
以下是实现此功能的代码:
import java.util.Scanner;
public class UserAccess {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("1. Admin");
System.out.println("2. SubAdmin");
System.out.println("3. TestPrep");
System.out.println("4. User");
System.out.print("Enter your choice: ");
String userType = scanner.nextLine();
switch (userType) {
case "Admin":
System.out.println("Gets Full access.");
break;
case "SubAdmin":
System.out.println("Gets access to create or delete the courses.");
break;
case "TestPrep":
System.out.println("Gets access to create or delete the test.");
break;
case "User":
System.out.println("You can only get access to consume the content.");
break;
default:
System.out.println("You cannot access any content. You are a trial user.");
}
scanner.close();
}
}
在这个示例中,程序首先提示用户输入选择。然后,根据输入的userType,switch语句会匹配相应的case并执行对应的代码块。如果输入不匹配任何case,则执行default分支。
关键点说明
- 表达式类型:
switch的表达式可以是byte、char、short、int、String或枚举类型。 break语句:break用于终止当前case的执行,防止代码“穿透”到下一个case。如果省略break,程序会继续执行后续case的代码,直到遇到break或switch结束。default分支:default分支是可选的,用于处理所有case都不匹配的情况,类似于if-else中的else。
总结


本节课我们一起学习了Java中switch-case语句的用法。我们了解到,switch语句通过匹配表达式的值来选择执行路径,适用于多条件选择的场景。通过一个用户权限管理的示例,我们实践了如何编写和运行switch-case代码。记住合理使用break和default分支,可以使你的程序逻辑更清晰、更健壮。
037:Java中的循环结构 🔄
在本节课中,我们将要学习Java编程语言中的核心概念之一:循环结构。循环是一种允许我们重复执行一段代码块直到特定条件不再满足的强大工具。理解循环对于编写高效、简洁的程序至关重要。

概述

循环是一种编程特性,它促使一组指令被重复执行,直到某个条件变为假。任何需要被重复执行的代码块,无论需要重复多少次,都可以通过循环来实现。循环语句会持续执行,直到特定的表达式或条件评估为假。
循环的类型
在编程中,主要有三种类型的循环:for循环、while循环和do-while循环。此外,for-each循环(或称增强型for循环)是专门为遍历集合(如数组、列表)而设计的,我们通常将其视为第四种类型。
1. For循环
上一节我们介绍了循环的基本概念,本节中我们来看看for循环。for循环包含三个部分:索引声明、维持布尔表达式的条件以及更新语句。我们可以更清晰地将其理解为三个语句:迭代变量的初始化、对该迭代变量的条件判断,以及对该变量的递增或递减。
任何循环,无论是for、while还是do-while,都包含三个要素:初始化、条件和递增/递减。for循环在你确切知道需要重复多少次(例如5次、10次、50次)时非常有用。只要条件满足,for循环体就会继续执行,否则循环终止。
以下是for循环的基本语法结构:
for (初始化; 条件; 更新) {
// 要重复执行的代码块
}
2. While循环
接下来,我们探讨while循环。while循环也是一种扩展性循环,用于在特定条件下重复执行代码块。它会执行一个语句或语句块,直到指定的表达式评估为假。
与for循环类似,while循环也包含三个要素:初始化、条件和递增/递减。通常,当你不确定确切的迭代次数时,while循环非常有用。
以下是while循环的基本语法结构:
初始化;
while (条件) {
// 要重复执行的代码块
更新;
}
3. Do-While循环
现在,我们来看do-while循环。do-while循环与while循环非常相似,但有一个关键区别:无论条件是否为真,do-while循环的循环体至少会执行一次。从第二次开始,它才根据条件来决定是否继续执行。
这类似于使用ATM机的过程:第一次你刷卡后可以进行交易,但之后它会询问你是否要继续。如果你选择继续,菜单会再次弹出;否则,你的流程就结束了。因此,当你确定代码至少需要执行一次,而后续执行取决于某个条件时,do-while循环就很有用。
以下是do-while循环的基本语法结构:
初始化;
do {
// 要重复执行的代码块
更新;
} while (条件);

4. For-Each循环(增强型For循环)

最后,我们介绍for-each循环,也称为增强型for循环。这种循环专门用于按递增顺序遍历集合(如数组或ArrayList)中的元素。它简化了集合遍历的语法,使代码更易读。
以下是for-each循环的基本语法结构,用于遍历数组:
for (元素类型 变量名 : 数组或集合) {
// 使用变量的代码块
}
总结

本节课中我们一起学习了Java中的四种主要循环结构:for循环、while循环、do-while循环以及for-each循环。我们了解了每种循环的适用场景、基本语法和工作原理。for循环适用于已知迭代次数的情况;while循环适用于条件驱动且迭代次数未知的情况;do-while循环确保循环体至少执行一次;而for-each循环则简化了集合的遍历操作。掌握这些循环结构是构建逻辑复杂程序的基础。请继续关注后续课程,我们将通过实际代码示例来深入探讨每种循环的具体实现。
038:循环实战演练


在本节课中,我们将通过实际代码演示,学习如何在Java中实现和使用循环结构。
循环是编程中用于重复执行代码块的核心工具。Java提供了几种循环结构,我们将逐一探讨它们的工作原理和适用场景。
循环结构概述
循环允许我们重复执行一段代码,直到满足特定条件。Java中主要有三种循环:for循环、while循环和do-while循环。每种循环都有其特定的语法和最佳使用场景。
for循环详解
上一节我们介绍了循环的基本概念,本节中我们来看看for循环的具体实现。
for循环通常用于已知循环次数的情况。其语法结构包含三个关键部分:初始化语句、循环条件和迭代语句。
以下是for循环的基本语法:
for (初始化; 条件; 迭代) {
// 循环体
}
现在,让我们通过一个例子来打印“Hello World”10次。
for (int i = 1; i <= 10; i++) {
System.out.println("Hello World " + i);
}
在这个例子中:
int i = 1;是初始化语句,将循环变量i设置为1。i <= 10;是循环条件,只要i小于或等于10,循环就会继续。i++是迭代语句,每次循环结束后将i的值增加1。System.out.println("Hello World " + i);是循环体,每次循环都会执行。
程序执行流程如下:
- 首次进入循环时,执行初始化语句
int i = 1。 - 检查条件
i <= 10。如果为真,则执行循环体。 - 循环体执行完毕后,执行迭代语句
i++。 - 再次检查条件,重复步骤2和3,直到条件为假,循环结束。
运行此程序,控制台将依次输出“Hello World 1”到“Hello World 10”。
while循环详解
了解了for循环后,我们来看看while循环。while循环更适合在循环次数未知,或循环条件基于复杂逻辑时使用。
以下是while循环的基本语法:
while (条件) {
// 循环体
}
首先,我们用while循环实现同样的功能:打印“Hello World”10次。
int i = 1;
while (i <= 10) {
System.out.println("Hello World " + i);
i++;
}
可以看到,while循环同样包含三个要素:初始化(int i = 1)、条件(i <= 10)和迭代(i++),但它们被分散在代码的不同位置。对于简单的固定次数循环,使用for循环更简洁,出错概率更低。
接下来,我们看一个更典型的while循环案例:持续接收用户输入并打印,直到用户输入“quit”。
import java.util.Scanner;
Scanner scanner = new Scanner(System.in);
String input = "";
while (!input.equals("quit")) {
System.out.print("Enter message: ");
input = scanner.nextLine().toLowerCase(); // 转换为小写以便比较
System.out.println(input);
}
在这个例子中:
- 循环条件是
!input.equals("quit"),即输入不是“quit”时继续循环。 - 程序会不断提示用户输入,并将输入打印出来。
- 一旦用户输入“quit”(不区分大小写),循环条件变为假,循环终止。
do-while循环详解
最后,我们来学习do-while循环。它与while循环的关键区别在于,do-while循环会至少执行一次循环体,然后再判断条件。
以下是do-while循环的基本语法:
do {
// 循环体
} while (条件);
我们使用do-while循环改写上面的用户输入案例。
import java.util.Scanner;
Scanner scanner = new Scanner(System.in);
String input;
do {
System.out.print("Enter message: ");
input = scanner.nextLine().toLowerCase();
System.out.println(input);
} while (!input.equals("quit"));
程序执行流程如下:
- 首先,无条件地执行一次
do块内的代码:提示输入、获取输入并打印。 - 然后,检查
while后的条件!input.equals("quit")。 - 如果条件为真,则跳回
do块开头继续执行;如果为假,则循环结束。
因此,即使用户第一次就输入“quit”,程序也会先打印出“quit”,然后再结束循环。这是do-while与while的主要区别。
总结
本节课中我们一起学习了Java中三种循环结构的实战应用:
for循环:最适合在循环次数明确已知时使用。其初始化、条件和迭代语句集中在一行,结构清晰。while循环:适用于循环次数未知,或循环条件需要复杂逻辑判断的情况。务必注意在循环体内更新条件变量,以防无限循环。do-while循环:适用于需要至少执行一次循环体的场景。它先执行,后判断,保证了代码块至少运行一次。

在实际项目和案例研究中,你可以根据具体需求选择最合适的循环结构。理解每种循环的特点,是编写高效、正确代码的关键。
Java全栈开发:第39课:For-Each循环详解 🚀


在本节课中,我们将学习Java中的for-each循环。这是一种用于遍历数组和集合元素的便捷方式,也称为增强型for循环。我们将了解它的语法、工作原理、适用场景以及与传统for循环的区别。
for-each循环是遍历数组和集合元素的另一种方式,与for循环、while循环和do-while循环类似。
它允许你声明一个与数组或集合基类型相同的变量,而不是声明和初始化循环计数器变量。该变量会依次从数组或集合中获取每个元素的值,供循环体使用。
以下是其基本语法格式:
for (元素类型 变量名 : 数组或集合名) {
// 使用变量名进行操作
}
例如,对于一个字符串数组,你可以这样使用:
String[] names = {"King", "Kocher", "Sara", "Baling"};
for (String name : names) {
System.out.println(name);
}
上一节我们介绍了for-each循环的基本概念,本节中我们来看看它的具体应用示例。
假设我们有一个字符串数组,包含几个名字。我们可以使用传统for循环来遍历并打印它们:
for (int i = 0; i < names.length; i++) {
System.out.println(names[i]);
}
或者,我们可以使用更简洁的for-each循环来实现同样的功能:
for (String name : names) {
System.out.println(name);
}
两种方式都会输出相同的结果:King, Kocher, Sara, Baling。
然而,理解在何时使用for-each循环、何时不使用它非常重要。
以下是for-each循环的一些局限性:
- 无法修改数组:
for-each循环主要用于遍历或迭代元素,而不是基于索引进行操作。如果你想修改数组中的元素,需要操作索引,而for-each循环不提供索引。 - 无法获取索引:
for-each循环不跟踪当前元素的索引,因此你无法直接通过循环变量获取数组下标。 - 只能向前迭代:
for-each循环只能按顺序从第一个元素迭代到最后一个元素。而传统for循环可以轻松实现反向遍历(例如for (int i = names.length - 1; i >= 0; i--))。 - 无法处理两个决策语句:在某些需要同时处理两个条件或决策的复杂迭代场景中,传统
for循环可能更灵活。
因此,for-each循环最适合用于简单的、只读的遍历场景。在遍历集合或数组时,它通常被推荐使用,因为代码更简洁易读。但请注意,与简单的迭代相比,for-each循环可能有一些轻微的性能开销。

本节课中我们一起学习了Java的for-each循环。我们了解了它的语法,并通过示例看到了它如何简化数组的遍历。同时,我们也明确了它的适用边界:它非常适合简单的顺序遍历,但在需要索引、反向遍历或修改元素时,应选择传统的for循环。在后续关于集合的课程和实际应用中,我们会经常使用到这个结构。
040:非条件跳转语句


在本节课中,我们将学习Java中的非条件跳转语句,即 break 和 continue 语句。这些语句用于在循环中控制程序的执行流程,帮助我们跳过某些迭代或提前终止循环。
跳转语句概述
跳转语句是允许你控制程序执行流程的关键字。它们用于将程序控制权从程序中的一个点转移到另一个点。例如,在 switch 语句中,我们使用 break 关键字来终止一个 case 的执行并跳出 switch 块。
break 和 continue 是两种跳转语句,用于在循环中跳过某些语句或借助 break 关键字终止循环。
Break语句详解
break 语句用于根据条件立即终止循环。当在循环内部遇到 break 语句时,循环的迭代会立即停止,控制权会立即从循环返回到循环后的第一条语句。
break 语句通常用于我们不确定实际迭代次数的场景。
其工作原理如下:循环体开始执行,检查是否满足跳出循环的条件。如果找到 break 关键字,它将中断执行。如果条件为假,则继续执行循环体。
Continue语句详解
与 break 不同,continue 语句不是终止循环,而是跳过循环的某次迭代。只有当前迭代被跳过,循环会继续进行。
假设循环开始,条件被执行。如果条件为真且存在 continue 语句,它将跳过当前迭代,并检查下一个条件,而不是退出循环本身。
案例演示
以下是一个具体的案例,用于演示 break 和 continue 的用法。
考虑这样一个案例:用户输入一条消息,程序将持续打印这条消息,直到用户输入“quit”为止。循环会一直进行。
实现逻辑如下:在获取输入并执行循环迭代之前,我可以先进行检查。如果输入的内容等于“quit”,我需要中断循环。否则,如果输入的内容等于“pass”,则执行 continue。如果输入既不是“quit”也不是“pass”,则正常打印输入内容。
在“pass”的情况下,该次迭代的执行将被跳过。例如,如果打印消息“Hello, This is the message”,当输入“pass”时,输入将不会被打印,因为 continue 跳过了该次迭代。当遇到 continue 时,它会再次回到循环条件处,循环体内剩余的语句将被跳过。但如果输入“quit”,它将中断循环本身的执行。
这就是Java中 break 和 continue 语句的工作方式。
总结


本节课我们一起学习了Java中的非条件跳转语句 break 和 continue。break 用于立即终止循环,而 continue 用于跳过当前迭代并继续下一次循环。理解这两个语句对于控制循环流程至关重要。
Java全栈开发:专项课程(上):第41讲:条件与循环综合练习 - FizzBuzz


在本节课中,我们将通过一个经典的编程练习——FizzBuzz问题,来总结和巩固之前学习的条件判断与循环结构知识。我们将编写一个程序,根据特定规则输出一系列结果。
在上一节中,我们讨论了Java中可用的各种条件判断和循环结构。本节我们将通过一个实际案例来应用这些知识。
FizzBuzz是一个常见的编程练习,它要求我们根据一组数字的特定属性来打印不同的输出。具体规则如下:
- 给定一个数字范围(例如1到20)。
- 遍历这个范围内的每一个数字。
- 如果数字能被3整除,则打印“Fizz”。
- 如果数字能被5整除,则打印“Buzz”。
- 如果数字能同时被3和5整除,则打印“FizzBuzz”。
- 如果数字不满足以上任何条件,则直接打印该数字本身。
例如,对于数字1到5,输出应为:1, 2, Fizz, 4, Buzz。数字15能同时被3和5整除,所以会输出“FizzBuzz”。
现在,让我们开始动手编写代码来实现这个逻辑。
首先,我们需要创建一个Scanner对象来接收用户输入,以确定我们想要检查的数字范围上限。

Scanner scanner = new Scanner(System.in);
System.out.println("请输入一个数字:");
int number = scanner.nextInt();
接下来,我们将使用一个for循环来遍历从1到用户输入数字number之间的所有整数。
for (int i = 1; i <= number; i++) {
// 在这里编写条件判断逻辑
}
在循环内部,我们需要按照规则进行条件判断。以下是实现逻辑的步骤:
- 首先检查数字是否能同时被3和5整除(即能被15整除),这是最高优先级的条件。
- 如果不满足,则检查是否能被5整除。
- 如果还不满足,则检查是否能被3整除。
- 如果以上条件都不满足,则打印数字本身。
以下是实现这些判断的代码:
if (i % 3 == 0 && i % 5 == 0) {
System.out.print("FizzBuzz ");
} else if (i % 5 == 0) {
System.out.print("Buzz ");
} else if (i % 3 == 0) {
System.out.print("Fizz ");
} else {
System.out.print(i + " ");
}
让我们运行程序并输入数字20进行测试。程序将输出从1到20的结果。
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz
观察输出结果:
- 数字3、6、9、12、18被替换为“Fizz”。
- 数字5、10、20被替换为“Buzz”。
- 数字15被替换为“FizzBuzz”。
- 其余数字则直接打印。
这个练习清晰地展示了如何将if-else if-else条件链与for循环结合使用,来解决一个具体的逻辑问题。


本节课中,我们一起学习了如何通过FizzBuzz问题来综合运用条件判断和循环。我们使用for循环进行遍历,并使用if-else语句链根据不同的整除条件输出相应的结果。这类练习是巩固编程基础、训练逻辑思维的有效方式。希望你能通过这个案例更好地理解如何在实际编程中组织你的代码逻辑。我们下节课再见。
042:本课你将学到什么
在本节课中,我们将深入学习面向对象编程的核心概念以及Java中的其他重要主题。我们将从基础开始,逐步构建对Java高级特性的理解,最终目标是让你能够编写更高效、可复用且可扩展的代码。
🧱 面向对象编程基础
上一段我们介绍了本课的整体目标,本节中我们来看看面向对象编程的基石。面向对象编程是一种围绕“对象”组织代码的范式,每个对象都包含数据和操作数据的方法。
以下是其核心原则:
- 封装:将数据(属性)和操作数据的方法(行为)捆绑在一个单元(即类)中,并限制对对象内部数据的直接访问。这通常通过使用
private访问修饰符和提供公共的getter/setter方法来实现。public class BankAccount { private double balance; // 数据被封装,设为私有 public double getBalance() { // 提供公共方法访问数据 return balance; } public void deposit(double amount) { if (amount > 0) { balance += amount; } } } - 继承:允许一个类(子类)基于另一个类(父类)来构建,继承其属性和方法,从而实现代码复用和建立层次关系。
public class Vehicle { // 父类 String brand; void honk() { System.out.println("Beep!"); } } public class Car extends Vehicle { // 子类继承Vehicle int wheels = 4; } - 多态:指同一个接口或方法在不同的对象中具有不同的行为。这允许我们使用父类引用来指向子类对象,并在运行时决定调用哪个方法。
Vehicle myVehicle = new Car(); // 多态:父类引用指向子类对象 myVehicle.honk(); // 调用的是Car类继承或重写的方法

我们将学习如何在Java中定义类和创建对象,以及如何构建符合业务逻辑的自定义类。
🏗️ 抽象类与接口
理解了面向对象的基本支柱后,我们进一步探讨用于实现更高层次抽象的两种工具:抽象类和接口。
- 抽象类:使用
abstract关键字声明,不能直接实例化。它可以包含抽象方法(只有声明,没有实现)和具体方法。抽象类用于定义子类的通用模板。public abstract class Animal { public abstract void makeSound(); // 抽象方法 public void sleep() { // 具体方法 System.out.println("Zzz"); } } - 接口:使用
interface关键字声明,定义了一组方法契约(在Java 8之前全部是抽象方法)。一个类可以实现多个接口,从而获得多重“行为”继承的能力。public interface Drawable { void draw(); // 接口方法(隐式为 public abstract) } public class Circle implements Drawable { public void draw() { System.out.println("Drawing a circle"); } }
⚠️ 异常处理与文件操作
掌握了代码的结构设计后,我们需要学习如何让程序更健壮地处理运行时错误和与外部系统交互。本节将介绍异常处理和文件操作。
我们将学习如何使用try-catch块来捕获和处理程序执行中可能出现的异常,防止程序意外崩溃。同时,你也会了解如何创建自定义的异常类来表示特定的错误情况。
try {
int result = 10 / 0; // 可能抛出 ArithmeticException
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero!");
}
此外,我们将学习Java中文件处理的基础知识,即如何使用java.io包中的类来从文件中读取数据以及将数据写入文件。
🔄 泛型
最后,我们将深入探讨泛型这一强大概念。泛型允许我们在定义类、接口或方法时使用类型参数,从而创建可处理多种数据类型的通用代码,同时保证编译时的类型安全。
我们将学习如何创建泛型类和泛型方法,这能极大地提高代码的可复用性和灵活性。
// 一个简单的泛型类
public class Box<T> {
private T content;
public void setContent(T content) { this.content = content; }
public T getContent() { return content; }
}
// 使用
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
我们还将了解泛型的一些高级特性,例如:
- 有界类型参数:限制泛型参数可以接受的类型范围,例如
<T extends Number>。 - 通配符:使用
?表示未知类型,增加泛型使用的灵活性,例如List<?>。 - 类型擦除:理解Java泛型在编译后的实现机制。
本节课中我们一起学习了Java面向对象编程的核心原则(封装、继承、多态)、抽象类与接口的使用、异常处理与文件操作的基础,以及泛型编程的概念与高级特性。掌握这些知识将为你构建复杂、健壮且易于维护的Java应用程序打下坚实的基础。
043:面向对象编程 🧱


在本节课中,我们将讨论面向对象编程方法。这种方法有助于通过实时实现来解决问题和编写代码,这些代码与现实世界的问题相关联。
面向对象编程是一种使用面向对象概念(如类、对象、封装、继承、抽象和多态)来设计程序的方法论或范式。它通过提供一些概念来简化软件开发和维护。正如之前提到的,面向对象编程有六个主要组成部分。其主要目的是将现实世界中的想法付诸实践。一些最流行的面向对象编程语言包括 Java、.NET、C++ 和 Python。
既然我们已经有了面向过程编程和命令式编程,为什么还要讨论面向对象编程呢?面向对象编程概念为程序提供了清晰的模块化结构。作为程序员,我们需要编写解决现实世界问题的代码。假设你需要编写一个围绕航班管理系统展开的程序。如果我们决定使用基于线性步骤(称为过程)的面向过程编程,就必须编写诸如预订、登机、值机等功能,这些功能描述了内部工作。这变成了一项繁琐的任务。如果我们以后想对其进行修改,修改这样的代码会变得很困难。此外,重用相同的代码将不可能。但是,借助面向对象编程及其特性,代码可以无冗余地重用。面向对象编程旨在减轻面向过程方式的缺点,我们更多地关注数据,例如对象中的数据。
对象是现实世界的实体,它们要么具有状态,要么具有行为。例如,在航班管理系统中,乘客、预订、出发地、目的地、行李容量都成为了对象。这些对象代表了现实世界的实体,使得理解和可视化现实世界问题变得更加容易。
与其他面向过程或编程范式相比,使用面向对象编程有一些好处。例如,面向对象编程执行快速且更容易,为程序提供了清晰的结构。它专注于“不要重复自己”的原则。我并不是说其他方法不遵循这个规则,但面向对象编程具有强大的程序重用能力,使代码更容易维护、修改和调试。面向对象编程使得创建可重用的应用程序成为可能,从而减少了程序员的时间和精力。
以上是本节课要讨论的要点,我非常期待通过实际实现来教授大家所有这些内容。


本节课中,我们一起学习了面向对象编程的基本概念、其相对于面向过程编程的优势,以及它在解决现实世界问题中的应用。
044:类与对象



在本节课中,我们将学习面向对象编程中的核心概念:类与对象。它们是任何面向对象编程语言中最常见的组成部分。
🏗️ 什么是类?
类是一个蓝图,它包含了所有数据的定义。蓝图意味着类是一种用户自定义的数据类型,它是相似类型对象的集合。你可以将其视为一个模板,用于描述特定对象的状态和行为。
在Java或任何面向对象编程语言中,类只是一个逻辑实体。例如,你可以将现实世界中的任何实体视为对象。一只具体的猫就是一个对象,那么与“猫”这个实体相关的所有属性、状态和行为,都将在这个类中进行描述。
你可以举任何例子。例如,我们可以创建一个名为 Plane 的类。这个类将包含每架飞机的属性和方法。属性可能包括飞机编号、总座位数、航空公司、飞行员姓名等。而方法则可能包括检查座位可用性、选择舱位、预订机票等功能。
定义一个类的基本格式如下:
访问修饰符 class 类名 {
// 属性(变量)
// 方法
}
🧱 什么是对象?
如果你想访问特定类的成员,你需要创建该类的一个实例,这个实例就是对象。对象包含了类的变量和方法。每个对象都有其独特的身份和行为。
例如,一只猫有状态,如颜色、大小、性别和年龄。而它的行为则包括睡觉、觅食或四处奔跑。
在Java中,对象既是物理实体也是逻辑实体,它可以存在于类内部或外部,但始终代表一个特定的类。我们在之前的演示中已经创建了一个名为 Main 的类,我们可以用它来创建对象。
🔗 类与对象的关系
例如,我们可以将 Vehicle 视为一个类。那么,汽车、卡车和自行车就是该类的对象,它们都代表交通工具。汽车将拥有自己的属性和行为,卡车和自行车也是如此。
如果它们有共同的行为,就可以在 Vehicle 类中定义。如果它们有自己独特的行为,则可以在它们各自的类(如 Car、Truck、Cycle)中继承 Vehicle 类的基本功能,并通过对象来具体表现。


在本节课中,我们一起学习了面向对象编程的基础:类与对象。我们了解到,类是定义对象属性和行为的蓝图,而对象是类的具体实例。下一节课程中,我们将学习类和对象的实际代码实现。
045:创建类与对象

在本节课中,我们将学习如何定义一个类,以及如何通过创建该类的对象(实例化)来访问其成员。这是面向对象编程的核心基础。

概述
类是创建对象的蓝图。它定义了对象的属性(数据成员)和行为(成员函数)。我们将创建一个简单的Student类,包含ID、姓名和年龄属性,并实现输入和显示这些信息的方法。
创建Student类
首先,我们来定义一个名为Student的类。这个类将包含描述学生的数据成员和操作这些数据的成员函数。
以下是Student类的基本结构:
public class Student {
// 数据成员
int studentID;
String studentName;
int studentAge;
// 成员函数
public void acceptDetails() {
// 用于接受用户输入
}
public void displayDetails() {
// 用于显示学生信息
}
}
实现成员函数
上一节我们定义了类的结构,本节中我们来看看如何实现具体的功能。我们需要在acceptDetails方法中获取用户输入,并在displayDetails方法中打印信息。
为了在多个方法中使用输入功能,我们可以在类级别创建一个Scanner对象。
以下是acceptDetails方法的实现:
import java.util.Scanner;
public class Student {
// 数据成员
int studentID;
String studentName;
int studentAge;
Scanner scanner = new Scanner(System.in); // 类级别的Scanner对象
public void acceptDetails() {
System.out.println("Enter student ID:");
studentID = scanner.nextInt();
scanner.nextLine(); // 消耗换行符
System.out.println("Enter student name:");
studentName = scanner.nextLine();
System.out.println("Enter student age:");
studentAge = scanner.nextInt();
}
}
接下来是displayDetails方法的实现。请注意,我们不需要传递任何参数,因为方法可以直接访问类级别的数据成员。
public void displayDetails() {
System.out.println("Student ID is: " + studentID);
System.out.println("Student name is: " + studentName);
System.out.println("Student age is: " + studentAge);
}
实例化对象与访问成员
现在我们已经定义了一个完整的类,接下来需要在主方法中创建它的对象并调用其方法。
理解下面这行代码非常重要:
Student student = new Student();
这行代码包含两部分:
Student student:声明一个名为student的引用变量,其类型为Student。这还不是对象本身。new Student():使用new关键字在内存中实际创建一个Student对象。此时,对象的数据成员会获得内存空间。
创建对象后,我们可以通过引用变量.(点)操作符来访问其成员函数。
public class Main {
public static void main(String[] args) {
// 创建第一个Student对象
Student student = new Student();
student.acceptDetails();
student.displayDetails();
}
}
运行程序,它将提示你输入学生信息,然后将其打印出来。
创建多个对象
一个类可以创建多个独立的实例。每个对象都有自己的内存空间,通过其引用变量进行访问。
以下是创建和使用多个对象的示例:
public class Main {
public static void main(String[] args) {
// 创建第一个对象
Student student = new Student();
System.out.println("Enter details for first student:");
student.acceptDetails();
student.displayDetails();
// 创建第二个对象
Student student1 = new Student();
System.out.println("\nEnter details for second student:");
student1.acceptDetails();
student1.displayDetails();
}
}
程序将依次为两个学生对象获取并显示信息,每个对象都维护着自己独立的数据。
总结

本节课中我们一起学习了面向对象编程的基础操作:
- 定义类:使用
class关键字创建蓝图,包含数据成员和成员函数。 - 实现方法:在方法内部编写逻辑,可以直接访问类级别的变量。
- 实例化对象:使用
new关键字创建类的具体实例,理解引用变量与对象的区别。 - 访问成员:通过对象引用后跟
.操作符来调用方法或访问属性。 - 多个对象:一个类可以创建多个独立的对象,每个对象都有自己的状态。

希望你现在清楚了如何创建类、编写其成员,以及如何通过创建对象来使用它们。每个对象都拥有自己独立的上下文环境。在接下来的课程中,我们将继续学习更多面向对象编程的实际应用。
Java全栈开发:第46课:Java访问修饰符详解 🔒


在本节课中,我们将学习Java中的访问修饰符。我们将了解其类型、作用范围,并通过概念讲解来理解如何在不同场景下使用它们。
访问修饰符用于设置类、接口、变量、方法、构造函数和数据成员的可访问性。通过改变访问级别,我们可以控制程序中的哪些部分能够访问特定的属性和成员。

上一节我们介绍了访问修饰符的基本概念,本节中我们来看看Java中四种主要的访问修饰符及其具体区别。
Java中有四种主要的访问修饰符:
- 默认 (Default,即不写任何修饰符)
- 公共 (Public)
- 私有 (Private)
- 受保护 (Protected)
以下是每种修饰符的详细说明:
- 默认:声明仅在同一个包(或特定文件夹)内可见。
- 私有:仅在定义它的类内部可访问。
- 受保护:在继承的情况下,用于子类中访问。
- 公共:具有最广的作用域,可以从任何地方访问,无论是类内、类外、父类、子类、包内还是包外。

为了更清晰地理解它们的访问范围,我们可以参考以下对比:
| 修饰符 | 同类内 | 同包内 | 不同包子类 | 不同包非子类 |
|---|---|---|---|---|
private |
✔️ | ❌ | ❌ | ❌ |
default |
✔️ | ✔️ | ❌ | ❌ |
protected |
✔️ | ✔️ | ✔️ | ❌ |
public |
✔️ | ✔️ | ✔️ | ✔️ |
如上表所示,所有修饰符都允许在定义它们的类内部访问。但private成员即使在同一个包内也不可用。在涉及子类(即子类)的情况下,public和protected成员可以被访问。而只有public修饰符允许在包外的任何地方被访问。
这就是访问修饰符如何定义数据访问范围的方式。正如前面提到的,你可以将这些访问修饰符放在类、方法、变量、构造函数和接口等前面。


本节课中我们一起学习了Java的四种访问修饰符:public、protected、default和private。我们了解了它们各自的作用域,以及如何通过它们来控制类成员的可见性,这是封装这一面向对象核心原则的重要体现。
047:访问修饰符演示

在本节课中,我们将通过一个具体的程序示例,学习如何在Java程序中使用访问修饰符。我们将了解不同访问修饰符的作用范围,并通过修改代码来观察其效果。

概述
访问修饰符用于控制类、变量、方法和构造函数的访问权限。理解它们对于编写安全、结构清晰的Java代码至关重要。本节将通过一个学生类的例子,演示private、default和protected修饰符的实际应用。
默认访问修饰符
首先,我们来看一个没有显式声明访问修饰符的类。在下面的代码中,类、数据成员和方法都没有指定修饰符。
class Student {
int studentID;
String name;
int age;
void acceptDetails() {
// 接受输入细节的代码
}
void displayDetails() {
// 显示细节的代码
}
}
即使我移除acceptDetails和displayDetails方法前的访问修饰符,程序依然可以正常运行。这是因为在Java中,如果一个类成员没有指定任何访问修饰符,它就拥有默认(或称包私有)的访问权限。
默认访问权限意味着该成员可以在同一个包内的任何地方被访问。例如,在同一个包内的另一个类中,我可以这样访问:
// 在同一个包内的另一个类中
Student student1 = new Student();
student1.age = 23; // 可以访问,因为age是默认修饰符
使用私有访问修饰符
然而,我可能不希望类的数据成员(如studentID和name)在类定义之外被直接访问和修改。为了实现更好的封装,我可以将它们声明为private。
class Student {
private int studentID;
private String name;
int age; // 保持为默认访问权限
// ... 方法定义
}
现在,如果我在另一个类中尝试直接访问studentID或name,编译器将会报错。
// 在另一个类中
Student student1 = new Student();
student1.studentID = 10; // 编译错误:studentID 在 Student 中是 private 访问控制
student1.name = "King"; // 编译错误:name 在 Student 中是 private 访问控制
student1.age = 23; // 可以访问,因为age是默认修饰符
private修饰符将成员的访问范围限制在声明它的类内部。这是实现数据隐藏和保护的关键。
公共访问修饰符与类
对于包含main方法的类,它通常需要被Java虚拟机(JVM)加载和执行,因此这个类本身必须是public的。
public class ClassesAndObjectsExample {
public static void main(String[] args) {
// 程序入口
}
}
如果你的项目结构中有不同的文件夹(包),并且你需要从其他包访问Student类,那么Student类也必须声明为public。
package com.example.model;
public class Student {
// ... 成员定义
}
受保护的访问修饰符
protected访问修饰符主要用于继承的场景。当一个成员被声明为protected时,它可以在同一个包内以及任何子类中被访问,即使子类位于不同的包。
package com.example.base;
public class Parent {
protected int protectedField; // 受保护的成员
}
package com.example.child;
import com.example.base.Parent;
public class Child extends Parent {
void accessProtected() {
protectedField = 10; // 可以访问,因为Child是Parent的子类
}
}
关于protected修饰符在继承中的具体应用,我们将在后续讲解继承概念的课程中详细讨论。
总结

本节课我们一起学习了Java中访问修饰符的使用:
- 默认修饰符允许在同一个包内访问。
private修饰符将访问权限严格限制在类内部,是实现封装的核心。public修饰符允许在任何地方访问,常用于需要被广泛调用的类和方法。protected修饰符主要用于继承体系,允许子类访问父类的成员。

理解并正确使用这些访问修饰符,是构建健壮、可维护Java应用程序的基础。在下一节关于继承的课程中,我们将更深入地探讨protected修饰符的应用。
048:封装与抽象


在本节课中,我们将讨论Java中的封装与抽象这两个核心概念,并通过实现场景来理解它们。
概述
封装与抽象是面向对象编程的两大支柱。封装关注于数据的隐藏和保护,而抽象则关注于向用户展示必要的细节,隐藏复杂的内部实现。理解它们对于编写安全、清晰和可维护的代码至关重要。
封装:数据隐藏 🛡️
上一节我们介绍了面向对象的基础,本节中我们来看看封装。封装被定义为数据隐藏或将数据包装在一个单元内。用一句话概括,封装就是数据隐藏。
它防止外部类访问和修改一个类的字段和方法。我们使用访问修饰符来实现数据的封装。
例如,如果我们有一个类,并将其成员设为public,那么这些成员可以在类外部被访问。但如果你想隐藏数据,可以将其设为限制性最强的private。之后,你可以根据需要将其扩展为default或protected。如果你不希望隐藏数据,则可以将其保持为public。
在Java中,封装帮助我们将相关的字段和方法保持在一起,这使得我们的代码更清晰、更易于阅读。
考虑一个Student类的例子,它包含以下成员:
以下是Student类的数据成员和方法:
- 数据成员:
firstName,middleName,lastName,fullName,courses - 方法:
save(),describe(),subscribe(),getSubscribedCourses()
如果你希望这些数据成员仅在类内部的方法中使用,可以将它们设为private,从而将其隐藏在类定义之外,防止外部访问。
抽象:隐藏实现细节 🎭
理解了如何隐藏数据后,我们再来看看抽象。抽象是指抽象出类的成员,只向用户展示必要的细节。
抽象可以通过抽象类或接口来实现,同样也可以借助访问修饰符。
- 抽象类是一个受限的类,不能用于直接创建对象。
- 子类必须通过自己的对象来访问需要从父类继承的数据成员或成员函数。
- 抽象方法是没有方法体的方法。子类必须实现(重写)父类中的抽象方法,并提供方法体。
考虑一个现实世界的例子:ATM机。用户只知道操作步骤:首先插入银行卡,然后输入密码,接着输入想要提取的金额,最后就能拿到钱。用户并不知道ATM内部的工作机制。用户只知道如何操作ATM,但不知道其内部如何运作,这就是抽象。
总结
本节课中我们一起学习了Java封装与抽象的核心概念。封装通过访问修饰符(如private)将数据和行为捆绑在一起并隐藏内部细节,从而保护数据并提高代码内聚性。抽象则通过抽象类、接口和访问修饰符,向外部世界只暴露必要的功能,隐藏复杂的实现逻辑。两者共同作用,帮助我们构建出更加健壮、安全和易于理解的面向对象程序。


在接下来的演示中,我们将探讨Getter和Setter方法如何帮助封装数据,以及如何从类外部访问这些被封装的数据。敬请关注。
049:Getter与Setter方法详解 🔧


在本节课中,我们将学习面向对象编程中一个核心概念——封装,并重点讲解如何通过Getter和Setter方法来实现数据隐藏。我们将了解为何需要这些方法,以及如何利用它们来控制对类中私有属性的访问。
封装与数据隐藏
上一节我们介绍了类的基本结构,本节中我们来看看如何保护类中的数据。在Java中,类的字段(变量)在类的实例中成为对象属性。为了增加控制,我们可以将访问修饰符设置为private。
核心概念:private修饰符意味着该数据成员只能在定义它的类内部被访问,这被称为“数据隐藏”。如果需要在类定义之外使用私有成员,就必须提供公共方法。
Getter与Setter方法的作用
Getter和Setter方法(也称为访问器和修改器)是访问和修改私有属性的桥梁。它们不仅允许外部代码与私有属性交互,还能让你灵活地控制属性的访问权限。

以下是访问权限的三种类型:
- 只读属性:仅提供Getter方法。
- 只写属性:仅提供Setter方法。
- 读写属性:同时提供Getter和Setter方法。
实践:创建Person类

为了演示,我们创建一个Person类,它包含两个私有成员:firstName和lastName。我们将为它们创建Getter和Setter方法,以便从类外部访问。
public class Person {
// 私有成员变量
private String firstName;
private String lastName;
private int age;
// Getter 方法用于读取firstName
public String getFirstName() {
return this.firstName;
}
// Setter 方法用于设置firstName
public void setFirstName(String firstName) {
this.firstName = firstName;
}
// Getter 方法用于读取lastName
public String getLastName() {
return this.lastName;
}
// Setter 方法用于设置lastName
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
现在,我们可以创建Person类的实例,并通过其公共方法来访问私有属性:
Person person = new Person();
person.setFirstName("King");
person.setLastName("Coer");
System.out.println(person.getFirstName() + " " + person.getLastName());
// 输出:King Coer
在Setter方法中添加验证逻辑
Getter和Setter方法的另一个强大功能是可以在其中添加业务逻辑或验证。例如,对于age属性,我们可以确保设置的值是合理的。
public class Person {
// ... 其他属性 ...
private int age;
public int getAge() {
return this.age;
}
public void setAge(int age) {
// 添加验证:年龄必须大于等于18岁
if (age >= 18) {
this.age = age;
} else {
System.out.println("Invalid age.");
// 可以选择抛出异常或设置默认值
this.age = 0;
}
}
}
使用带验证的Setter方法:
Person person = new Person();
person.setAge(16); // 输出:Invalid age.
System.out.println(person.getAge()); // 输出:0
person.setAge(32); // 验证通过
System.out.println(person.getAge()); // 输出:32
控制访问权限
你可以根据需求决定为属性提供哪些方法:
- 若希望属性只读,则仅实现Getter方法。
- 若希望属性只写,则仅实现Setter方法。
- 若希望属性可读写,则同时实现Getter和Setter方法。
总结
本节课中我们一起学习了封装的核心实践——使用Getter和Setter方法。我们了解到:
private修饰符用于实现数据隐藏。- Getter方法用于安全地读取私有属性。
- Setter方法用于安全地修改私有属性,并可在其中加入验证逻辑。
- 通过选择性地提供Getter或Setter方法,可以精确控制属性的访问权限(只读、只写或读写)。

一个拥有完整Getter和Setter方法的类(常被称为POJO或模型类)是构建Java应用程序实体的基础。掌握这个概念对后续学习至关重要。
050:构造器入门指南
在本节课中,我们将学习Java中构造器的概念、类型及其使用方法。构造器是面向对象编程中用于初始化新创建对象的核心工具。
什么是构造器?🤔
构造器是一种特殊类型的方法,用于初始化类的对象。当创建对象时,构造器会被自动调用。构造器的名称必须与它所属的类名完全相同。
例如,一个名为 Car 的类,其构造器也命名为 Car。
public class Car {
// 这是一个构造器
public Car() {
// 初始化代码
}
}

构造器的类型
上一节我们介绍了构造器的基本定义,本节中我们来看看构造器的两种主要类型:默认构造器和参数化构造器。
默认构造器
默认构造器不接受任何参数。如果类中没有显式定义任何构造器,Java编译器会自动提供一个默认构造器。
以下是默认构造器的一个示例:
public class Book {
public Book() {
// 默认构造器
System.out.println("一个Book对象被创建了。");
}
}
参数化构造器
参数化构造器允许在创建对象时传入参数,用于以不同的初始状态初始化对象。
以下是参数化构造器的一个示例:
public class Student {
String name;
int age;
// 参数化构造器
public Student(String studentName, int studentAge) {
name = studentName;
age = studentAge;
}
}
构造器链
理解了基本构造器后,我们来看看如何通过构造器链来简化代码。构造器链指的是一个构造器调用同一个类中的另一个构造器,这通常使用 this() 关键字实现。
以下是构造器链的一个示例:
public class Rectangle {
int length;
int width;
// 默认构造器调用参数化构造器
public Rectangle() {
this(1, 1); // 调用下面的构造器
}
// 参数化构造器
public Rectangle(int l, int w) {
length = l;
width = w;
}
}
私有构造器与单例模式
最后,我们来探讨一种特殊的构造器用法——私有构造器。私有构造器通常用于实现单例模式,确保一个类在程序中只有一个实例。
以下是使用私有构造器实现单例模式的一个示例:
public class Singleton {
private static Singleton instance;
// 私有构造器,防止外部通过new创建实例
private Singleton() {
}
// 提供全局访问点
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
总结📚
本节课中我们一起学习了Java构造器的核心知识。我们了解了构造器是一种用于初始化对象的特殊方法,其名称与类名相同。我们探讨了默认构造器和参数化构造器的区别与用法,学习了如何使用构造器链来优化代码结构,并介绍了私有构造器在实现单例设计模式中的应用。掌握这些概念是进行有效Java面向对象编程的基础。
051:Java中的构造函数


在本节课中,我们将要学习Java中一个非常重要的概念——构造函数。我们将了解什么是构造函数,它的特点,以及它与普通方法的区别。
概述
构造函数是Java中用于创建对象并初始化其数据成员的一种特殊方法。它在对象创建时被自动调用,无需手动触发。
什么是构造函数?
构造函数是一种特殊的代码块,用于在创建对象时初始化该对象的状态。它看起来像一个方法,但其行为和目的与普通方法不同。
构造函数的特点
以下是构造函数的一些核心特点:
- 自动调用:构造函数在对象创建时被隐式调用。你不需要手动调用它。
Student s = new Student(); // 此时构造函数被自动调用
- 默认构造函数:如果一个类没有定义任何构造函数,Java编译器会提供一个默认的无参构造函数。这个默认构造函数来自Java的根类
Object,用于为数据成员设置默认值(如数字为0,对象引用为null)。 - 命名规则:构造函数的名称必须与它所在的类名完全相同。
- 例如,如果类名是
Student,那么构造函数名也必须是Student。
- 例如,如果类名是
- 无返回类型:构造函数不能有任何返回类型,包括
void。- 正确:
public Student() { ... } - 错误:
public void Student() { ... }或public Student Student() { ... }
- 正确:
- 访问修饰符:构造函数的访问修饰符可以是
private、default(包私有)、protected或public。private构造函数可以限制外部创建该类的实例,常用于单例模式。
- 不能被声明为 final 或 synchronized:构造函数不能被
final或synchronized修饰。 - 构造函数链:一个构造函数可以调用同一个类中的另一个构造函数,这被称为构造函数链,通常使用
this()关键字实现。
构造函数与方法的区别
上一节我们介绍了构造函数的特点,本节中我们来看看构造函数与普通方法的主要区别。

以下是构造函数与方法的对比列表:
- 返回类型:构造函数不能有返回类型。方法必须有返回类型(
void也是一种返回类型)。 - 名称:构造函数的名称必须与类名相同。方法的名称可以是任何有意义的标识符,不能与类名相同(除非是构造函数)。
- 用途:构造函数用于初始化对象的数据成员。方法用于暴露对象的行为,例如进行计算、获取详情或显示信息。
- 调用方式:构造函数在对象创建时被隐式调用。方法必须根据需求被显式调用,例如从
main方法或其他方法中调用。
构造函数如何工作?
为了更好地理解,让我们回顾一下对象创建的过程。当我们写下 Student s = new Student(); 这行代码时,具体发生了什么?
new关键字:new关键字在堆内存中为Student类的数据成员分配内存空间。- 调用构造函数:在分配内存的同时,与
Student类匹配的构造函数被自动调用,以初始化这些新分配的内存空间(即设置对象的初始状态)。
正如之前强调的,你不需要主动去“调用”构造函数,这个过程由Java运行时自动完成。
总结


本节课中我们一起学习了Java构造函数的核心知识。我们了解到构造函数是一种在创建对象时自动执行的特殊方法,用于初始化对象。它的名称必须与类名相同且没有返回类型。我们还对比了构造函数与普通方法的区别,并理解了 new 关键字如何触发构造函数的执行。掌握构造函数是理解Java对象生命周期和面向对象编程的基础。
Java全栈开发:52:构造函数的类型 🏗️

在本节课中,我们将要学习Java中构造函数的两种主要类型:默认构造函数和参数化构造函数。理解这两种构造函数对于创建和初始化对象至关重要。

概述
构造函数是一种特殊的方法,用于在创建对象时初始化新对象。Java中的构造函数主要分为两大类:默认构造函数和参数化构造函数。本节将详细介绍这两种类型及其用法。
默认构造函数(无参构造函数)
上一节我们介绍了构造函数的基本概念,本节中我们来看看第一种类型——默认构造函数。
默认构造函数,也称为无参构造函数,其特点是不接受任何参数。如果程序员没有为类显式定义任何构造函数,Java编译器会自动提供一个默认构造函数。它的主要作用是将对象的数据成员初始化为默认值(例如,数值类型为0,对象引用为null)。
语法如下:
public ClassName() {
// 初始化代码(可选)
}
以下是默认构造函数的一个关键点列表:
- 构造函数名称必须与类名完全相同。
- 它没有返回类型,甚至不是void。
- 当没有显式定义任何构造函数时,Java会自动提供它。
参数化构造函数
了解了无需输入即可工作的默认构造函数后,我们接下来看看功能更灵活的参数化构造函数。
参数化构造函数允许在创建对象时接收特定的参数。通过传递不同的参数值,我们可以在对象诞生时就为其数据成员赋予我们期望的初始值,而不是固定的默认值。
语法如下:
public ClassName(Type param1, Type param2, ...) {
// 使用参数初始化成员变量
this.dataMember = param1;
// ... 其他初始化代码
}
以下是参数化构造函数的核心要点:
- 它包含一个或多个参数。
- 在创建类的实例时,必须提供与构造函数参数列表匹配的实参。
- 构造函数内部的代码利用传入的参数来初始化对象的成员变量。
例如,若有一个 Language 类,其参数化构造函数可能接收一个字符串参数来设置语言名称:
public Language(String langName) {
this.name = langName; // 使用参数初始化成员变量
}
在main方法中创建对象时,就需要提供相应的参数:
Language la = new Language("Java"); // 传递参数"Java"

总结

本节课中我们一起学习了Java构造函数的两种主要类型。默认构造函数(无参构造函数)由Java自动提供,用于执行默认初始化。参数化构造函数则允许我们通过传递参数来灵活地初始化对象的状态。理解这两种构造函数的区别和用途,是掌握Java对象创建过程的基础。
053:默认与带参构造函数 🏗️


在本节课中,我们将学习Java中两种重要的构造函数:默认构造函数和带参构造函数。我们将通过一个学生类的例子,理解它们如何工作、如何创建以及如何使用。


概述
构造函数是类的一种特殊方法,用于在创建对象时初始化该对象的数据成员。Java提供了默认构造函数,也允许我们创建自定义的带参构造函数,以便更灵活地初始化对象。
默认构造函数
上一节我们介绍了类的基本概念,本节中我们来看看如何初始化一个类的对象。首先,我们创建一个Student类,它包含studentID、name和age属性。
public class Student {
int studentID;
String name;
int age;
}
如果我们在创建Student对象时不提供任何初始值,并调用一个显示详情的方法,这些属性仍然会有值。这是因为每个类都隐式地继承自Object类,而Object类有一个特殊的构造函数(默认构造函数),它会为数据成员提供默认初始值。
以下是默认初始值的规则:
int类型的默认值是0。String类型的默认值是null。float类型的默认值是0.0。boolean类型的默认值是false。
当我们使用new Student()创建对象时,这个默认构造函数会被自动调用,从而完成初始化。
创建自定义默认构造函数
我们可以创建自己的默认构造函数来覆盖系统提供的默认行为。构造函数没有返回类型,且名称必须与类名完全相同。
public class Student {
int studentID;
String name;
int age;
// 自定义默认构造函数
public Student() {
studentID = 100;
name = "Unknown";
age = 80;
}
}
现在,每当创建Student对象时,都会调用这个自定义的构造函数,将studentID初始化为100,name初始化为"Unknown",age初始化为80。
带参构造函数
使用自定义默认构造函数的问题是,所有创建的对象都会有相同的初始值。为了解决这个问题,我们可以创建带参构造函数,在创建对象时传入特定的值。
以下是创建带参构造函数的步骤:
- 构造函数名与类名相同。
- 在括号内定义参数列表。
- 在构造函数体内,将参数值赋给类的数据成员。
public class Student {
int studentID;
String name;
int age;
// 带参构造函数
public Student(int sID, String sName, int sAge) {
studentID = sID;
name = sName;
age = sAge;
}
}
创建对象时,我们可以这样传递参数:
Student student1 = new Student(101, "King Kocher", 23);
此时,student1对象的studentID、name和age将分别被初始化为101、"King Kocher"和23。
使用this关键字
当构造函数的参数名与类的数据成员名相同时,编译器会产生混淆。为了明确指定,我们需要使用this关键字。this代表当前对象的引用。
public class Student {
int studentID;
String name;
int age;
public Student(int studentID, String name, int age) {
this.studentID = studentID; // 将参数studentID赋给当前对象的studentID
this.name = name;
this.age = age;
}
}
this.studentID指向当前对象的studentID属性,而等号右边的studentID指的是构造函数的参数。这是一种常见的编码规范,可以增强代码的可读性。
构造函数重载
一个类中可以同时存在多个构造函数,只要它们的参数列表(参数类型、数量或顺序)不同。这被称为构造函数重载。
public class Student {
int studentID;
String name;
int age;
// 默认构造函数
public Student() {
studentID = 100;
name = "Unknown";
age = 80;
}
// 带参构造函数1
public Student(int id, String n) {
studentID = id;
name = n;
age = 18; // 给age一个默认值
}
// 带参构造函数2
public Student(int id, String n, int a) {
studentID = id;
name = n;
age = a;
}
}
根据创建对象时提供的参数,Java会自动调用匹配的构造函数。
总结


本节课中我们一起学习了Java构造函数的核心概念。我们了解到,默认构造函数由Java隐式提供,用于初始化默认值。我们可以创建自定义的默认构造函数和带参构造函数,以便更精确地控制对象的初始化状态。使用this关键字可以解决参数与属性同名时的歧义。最后,通过构造函数重载,我们可以为一个类提供多种初始化方式,使代码更加灵活。掌握这些知识是进行面向对象编程的重要基础。


🎼 敬请关注后续课程,学习更多内容。下次见!
Java全栈开发:05:构造方法重载 🏗️


在本节课中,我们将要学习Java中的构造方法重载。我们将了解它的概念、与普通方法重载的区别,并通过一个具体的例子来掌握其用法。
概述
构造方法重载允许一个类拥有多个构造方法,这些构造方法具有不同的参数列表。这与方法重载的概念类似,但主要用于对象的初始化。通过构造方法重载,开发者可以用不同的初始状态创建对象,而无需为每种状态创建单独的类,这提高了代码的复用性并减少了重复代码。
构造方法重载的概念
上一节我们介绍了构造方法的基本概念,本节中我们来看看构造方法重载。
构造方法重载与普通方法重载非常相似。核心区别在于:普通方法需要显式地使用方法名来调用,而构造方法是在创建对象时由编译器隐式调用的,我们只需要传递相应的参数即可。
构造方法重载通过创建具有不同参数列表的构造方法来实现。请记住,构造方法的名称必须与类名相同,并且其唯一目的是初始化对象。

参数列表的区别可以体现在以下方面:
- 参数的数量不同。
- 参数的类型不同。
- 参数的顺序不同。
当创建一个对象时,编译器会根据提供的参数自动选择匹配的构造方法进行初始化。

以下是使用构造方法重载的好处:
- 允许以不同初始状态初始化对象:可以根据需要灵活地创建对象。
- 提高代码复用性:无需为每种初始化场景编写单独的类。
- 减少代码重复:避免了在多个地方编写相似的初始化逻辑。
- 简化对象创建:使对象的创建过程更加直观和简洁。
构造方法重载 vs. 方法重载
为了更好地理解构造方法重载,我们来比较一下它与普通方法重载的主要区别。
以下是两者的核心差异:
- 调用方式:构造方法在对象创建时由编译器隐式调用;普通方法需要根据方法名显式调用。
- 定义目的:构造方法专门用于初始化数据成员;方法重载可以用于执行各种特定的计算、比较和操作。
- 返回类型:构造方法没有返回类型;普通方法必须有一个返回类型,并且每个重载的方法可以有不同的返回类型。
- 名称要求:构造方法重载要求多个方法名称必须与类名相同;方法重载要求多个方法名称相同但参数列表不同。
构造方法重载示例
现在,让我们通过一个具体的代码示例来看看构造方法重载是如何工作的。
假设我们有一个Student类,它有三个数据成员:studentID、studentName和studentAge。我们将为它创建三个构造方法。
public class Student {
int studentID;
String studentName;
int studentAge;
// 1. 默认构造方法
public Student() {
this.studentID = 0;
this.studentName = "Unknown";
this.studentAge = 0;
}
// 2. 带两个参数的构造方法
public Student(int id, int age) {
this.studentID = id;
this.studentName = "Not Provided";
this.studentAge = age;
}
// 3. 带三个参数的构造方法
public Student(int id, String name, int age) {
this.studentID = id;
this.studentName = name;
this.studentAge = age;
}
// 用于显示学生详情的方法
public void displayDetails() {
System.out.println("ID: " + studentID + ", Name: " + studentName + ", Age: " + studentAge);
}
public static void main(String[] args) {
// 调用默认构造方法
Student student1 = new Student();
student1.displayDetails();
// 调用带两个参数的构造方法
Student student2 = new Student(101, 23);
student2.displayDetails();
// 调用带三个参数的构造方法
Student student3 = new Student(102, "Alice", 25);
student3.displayDetails();
}
}
代码解释:
- 创建
student1对象时没有传递任何参数,因此调用了默认构造方法,所有字段被初始化为默认值。 - 创建
student2对象时传递了ID和年龄,因此调用了带两个参数的构造方法,姓名被设为默认值。 - 创建
student3对象时传递了所有三个参数,因此调用了带三个参数的构造方法,所有字段都被初始化为传入的值。
通过调用displayDetails方法,我们可以验证每个对象是否按照对应的构造方法正确初始化。

总结

本节课中我们一起学习了Java中的构造方法重载。我们了解到,它允许一个类定义多个具有不同参数列表的构造方法,从而在创建对象时提供灵活的初始化选项。我们比较了它与方法重载的关键区别,并通过一个Student类的实例演示了如何定义和使用重载的构造方法。掌握构造方法重载有助于编写更清晰、更灵活、更易于维护的代码。
055:Java中的构造器链



在本节课中,我们将学习Java中的构造器链。构造器链是指在当前对象中,一个构造器调用另一个构造器的过程。我们将探讨如何在同一类内部以及通过继承来实现构造器链,并理解其核心目的。
概述
构造器链的主要目的是通过一系列不同的构造器传递参数,并将初始化逻辑集中在一个地方完成。这可以通过两种方式实现:在同一类中使用 this 关键字,或在父子类之间使用 super 关键字。
同一类中的构造器链
首先,我们来看如何在同一类内部实现构造器链。这需要使用 this 关键字来调用当前类的其他构造器。
以下是一个示例,演示了在同一个类中,多个构造器如何相互调用:
public class Student {
int id;
String name;
int age;
// 默认构造器
public Student() {
this(101, "Carl", 23); // 调用三参数构造器
}
// 双参数构造器
public Student(int id, String name) {
this(id, name, 0); // 调用三参数构造器
}
// 三参数构造器
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public void display() {
System.out.println("ID: " + id + ", Name: " + name + ", Age: " + age);
}
public static void main(String[] args) {
Student s1 = new Student(); // 调用默认构造器
s1.display();
}
}
在上面的代码中,创建 Student 对象时,执行流程如下:
- 调用默认构造器
Student()。 - 默认构造器通过
this(101, "Carl", 23)调用三参数构造器。 - 三参数构造器完成成员变量
id、name和age的初始化。 - 控制权返回默认构造器,但此时初始化已完成,因此最终打印出
ID: 101, Name: Carl, Age: 23。
这种方式确保了无论通过哪个构造器创建对象,最终的初始化逻辑都集中在三参数构造器中完成。
通过继承实现的构造器链
上一节我们介绍了同一类中的构造器链,本节中我们来看看如何通过继承关系实现构造器链。这需要使用 super 关键字来调用父类的构造器。
在继承中,当创建子类对象时,会首先调用父类的构造器。如果父类有默认构造器,这个过程会自动发生。但如果父类只有带参数的构造器,则必须在子类构造器中显式使用 super 来调用。
以下是一个通过继承实现构造器链的示例:
// 父类
class Person {
String name;
// 父类带参数构造器
public Person(String name) {
this.name = name;
System.out.println("Person类带参数构造器被调用。");
}
}
// 子类
class Employee extends Person {
String designation;
// 子类带参数构造器
public Employee(String name, String designation) {
super(name); // 必须显式调用父类带参数构造器
this.designation = designation;
System.out.println("Employee类构造器被调用。");
}
}
public class Main {
public static void main(String[] args) {
Employee emp = new Employee("Alice", "高级经理");
}
}
在上面的代码中,创建 Employee 对象时,执行流程如下:
- 调用
Employee的构造器Employee("Alice", "高级经理")。 - 该构造器首先通过
super("Alice")调用父类Person的带参数构造器。 - 父类构造器初始化
name并打印信息。 - 控制权返回子类构造器,初始化
designation并打印信息。
如果子类构造器中没有显式写出 super(...),编译器会自动添加一个无参数的 super() 来调用父类的默认构造器。如果父类没有默认构造器,则会导致编译错误。
总结


本节课中我们一起学习了Java中的构造器链。我们了解到,构造器链是让多个构造器协同工作、集中初始化逻辑的有效方式。在同一类中,我们使用 this(参数列表) 来调用本类的其他构造器。在继承关系中,我们使用 super(参数列表) 来调用父类的构造器,并且这是初始化父类状态的必要步骤。掌握这两种方式,有助于你编写出更清晰、更易维护的Java代码。
056:继承与面向对象编程核心概念
在本节课中,我们将讨论面向对象编程中的继承及其重要概念。继承允许我们通过继承现有类的特性来创建新类,这有助于代码复用并促进了代码层次结构的概念。通过本节课的学习,你将掌握Java中继承的基础知识、不同类型以及关键实现技巧。
什么是继承?

上一节我们介绍了本节课的总体目标,本节中我们来看看继承的基本定义。
继承是面向对象编程的一个核心特性。它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。
其核心公式可表示为:
子类 extends 父类
这意味着子类自动拥有父类的非私有成员(字段和方法),从而无需重复编写相同代码。
继承的类型
理解了继承的基本概念后,接下来我们探讨Java中不同类型的继承。
Java主要支持以下几种继承类型:
- 单继承:一个子类只继承自一个父类。这是Java类继承中最直接的形式。
- 多重继承:一个子类继承自多个父类。需要注意的是,Java类不支持通过
extends关键字实现多重继承,但可以通过接口(implements)来模拟。 - 层次继承:一个父类被多个子类继承。
- 混合继承:以上多种继承类型的组合。同样,Java类不直接支持混合继承,需借助接口。
实现继承的规则
了解了继承的类型,在具体实现时需要遵循一些重要规则。
以下是实现类继承时需要遵循的关键规则:
- 使用
extends关键字建立继承关系。 - 子类可以访问父类的
public和protected成员。 - 子类不能直接访问父类的
private成员。 - 子类可以重写父类的方法以提供特定实现。
- 构造方法不会被继承,但子类构造方法必须调用父类构造方法(显式或隐式通过
super())。
向上转型与向下转型
最后,我们将深入探讨继承中两个关键概念:向上转型和向下转型,它们对于处理对象引用至关重要。
- 向上转型:将子类对象引用赋值给父类类型变量。这是安全的,并且会自动进行。
Parent obj = new Child(); // 向上转型 - 向下转型:将父类类型变量强制转换回子类类型。这需要显式转换,且存在运行时风险,应使用
instanceof进行检查。if (obj instanceof Child) { Child childObj = (Child) obj; // 向下转型 }
本节课中我们一起学习了Java继承的核心知识。我们明确了继承的定义与优势,探讨了单继承、多重继承(通过接口)、层次继承等不同类型,列出了实现继承的关键规则,并深入讲解了向上转型和向下转型的概念与用法。掌握这些内容将帮助你在程序中编写出更高效、更简洁的代码。
057:Java中的继承 🧬


在本节课中,我们将学习Java继承的概念及其类型,并通过实际实现来加深理解。
概述
继承是面向对象编程的关键特性之一,它允许我们基于现有类创建新类。继承的核心是代码的可重用性,父类的代码可以在子类中直接使用。继承体现了“是一个”的关系,这通常也被称为父子关系。
继承的基本概念
在继承中,会涉及多个类。一个是父类,也称为超类。我们创建的新类被称为子类,它需要在其内部派生自现有的超类。
当你从现有类继承时,你可以在子类中重用父类的方法和字段。此外,你还可以在当前子类中添加新的方法和字段。
Java中继承最重要的用途是实现代码重用,并减少程序员的时间和精力。同时,借助继承,我们可以在Java中实现多态,因为在方法重写的情况下,多个类会通过继承关联起来。
一个简单的例子
想象一下,你已经构建了一个标准计算器应用程序,它具备加法、减法、乘法、除法和平方根功能。现在,你需要构建一个科学计算器,它需要标准计算器的所有基本功能,但还需要添加幂运算、对数运算、三角函数运算等。
你会如何编写这个新的科学计算器应用程序?你会为所有标准操作重新编写代码吗?不会,因为你已经编写过那些代码了。你会希望在不重写所有代码的情况下,在这个新应用中使用它。在这种情况下,使用继承就可以实现这个目标。
另一个例子:车辆
我们可以考虑另一个例子:我们有一个“车辆”类。我们可以将车辆的一些基本功能放在Vehicle类中。但是,自行车、汽车、公共汽车和卡车都有各自不同的属性和行为。因此,你可以从Vehicle类继承一些基本属性,然后在子类中添加特定车辆类型的剩余属性和行为。
在这种情况下,Vehicle将是父类,而Bike、Car、Bus和Truck将是子类。
继承的实现:extends关键字
当我们实现继承时,会涉及“是一个”关系和“有一个”关系。当我们谈论“是一个”关系时,这通常指父子关系,父类需要借助extends关键字被子类扩展。
extends基本上是一个用于执行继承的关键字。它用于表示一个新类(子类)是通过继承从基类(父类)派生而来的。
代码示例:
class ParentClass {
// 父类的属性和行为
}
class ChildClass extends ParentClass {
// 子类可以重用父类的属性和行为
// 子类也可以添加新的属性和行为
}
记住extends的用法非常重要:当你的父实体是类,子实体也是类时,需要使用extends。但是,如果你要将一个接口继承到子类中,则需要使用implements,因为实现是针对契约的,而接口就是一种契约。
总结

本节课我们一起学习了Java继承。我们了解了继承是面向对象编程中实现代码重用的核心机制,它通过extends关键字建立类之间的“是一个”关系。我们探讨了继承的基本概念,并通过计算器和车辆的示例说明了其实际应用。最后,我们学习了如何使用extends关键字来实现继承,并区分了类继承与接口实现(implements)的不同。
058:继承的类型 🧬
在本节课中,我们将学习Java中继承的不同类型。继承是面向对象编程的核心概念之一,它允许一个类获取另一个类的属性和方法。我们将探讨五种主要的继承类型,并理解它们之间的区别。
概述 📋
继承是代码复用和建立类之间关系的重要机制。Java支持多种继承形式,但每种形式都有其特定的规则和适用场景。理解这些类型有助于我们设计出更清晰、更高效的类结构。
继承的类型
以下是Java中五种主要的继承类型。
1. 单继承
单继承是最简单的继承形式。在这种类型中,一个子类只从一个父类继承属性和方法。
公式:class B extends A
在单继承中,类B(子类)从类A(父类)派生。类B可以访问和使用类A中所有根据访问权限允许的属性和行为。这种关系是直接的“一对一”继承。
2. 多级继承
多级继承是单继承的扩展。在这种类型中,一个类继承自另一个类,而它自身又可以被其他类继承,从而形成多个层级。
公式:class C extends B 且 class B extends A
在多级继承中,类C继承类B,而类B继承类A。虽然类C不直接继承类A,但它间接获得了类A的属性和方法。这实际上是多个单继承的组合。
3. 层次继承
层次继承具有树状结构。在这种类型中,一个父类被多个子类继承。
公式:
class B extends A
class C extends A
class D extends A
层次继承创建了一种“一对多”的关系。例如,一个“员工”父类可以被“小时工”和“月薪工”等多个子类继承。方法重写是层次继承中的一个典型应用,子类可以重新定义从父类继承的方法以实现特定功能。
4. 多重继承

在Java中,类不支持多重继承。这意味着一个类不能同时继承多个类。

限制:class C extends A, B // 这在Java中是不允许的
这是因为多重继承可能引发歧义,例如当两个父类有同名方法时(“菱形问题”)。如果需要在Java中实现类似多重继承的功能,必须借助接口。一个类可以实现多个接口,从而获得多种行为约定。
5. 混合继承
混合继承是两种或多种继承类型的组合。
概念:混合继承 = 类型A + 类型B + ...
例如,它可以是多级继承和层次继承的组合。然而,在Java中,如果组合中包含了通过类实现的多重继承,那么这种混合继承同样是不可能的,因为Java类始终只能直接继承一个父类。
总结 🎯
本节课我们一起学习了Java中五种主要的继承类型:
- 单继承:一个子类继承一个父类。
- 多级继承:形成链式继承关系。
- 层次继承:一个父类被多个子类继承,形成树状结构。
- 多重继承:Java类本身不支持,需通过接口实现。
- 混合继承:多种继承类型的组合。

理解这些类型是掌握Java面向对象设计的关键。在下一节课中,我们将继续深入探讨继承的其他特性和应用。
059:实现类之间的继承 👨💻


在本节课中,我们将要学习如何在Java中实现类之间的继承。我们将通过一个具体的例子,从创建一个基础的Student类开始,逐步扩展其功能,最终构建一个包含学生成绩和体育分数的多级继承体系,并计算总分和平均分。
概述
继承是面向对象编程的核心概念之一,它允许我们基于现有类创建新类,从而实现代码的重用和扩展。本节将通过一个学生信息管理的示例,演示如何创建父类、子类,以及如何处理多级继承和方法重写。


创建父类:Student类
首先,我们创建一个基础的Student类。这个类包含一些基本的数据成员(如学号、姓名)和成员函数(如录入和显示详细信息)。
class Student {
private int rollNumber;
private String name;
public void acceptDetail() {
// 代码示例:使用Scanner录入rollNumber和name
}
public void displayDetail() {
// 代码示例:打印rollNumber和name
}
}
上一节我们介绍了基础的Student类,本节中我们来看看如何扩展它。
创建子类:Marks类
为了存储学生的客观题和主观题分数,我们不直接修改Student类,而是创建一个新的Marks类来继承它。这样做的好处是保持了Student类的稳定性,同时增加了新功能。
class Marks extends Student {
protected float objectiveMarks;
protected float subjectiveMarks;
public void acceptDetail() {
super.acceptDetail(); // 调用父类方法
// 代码示例:录入objectiveMarks和subjectiveMarks
}
public void displayDetail() {
super.displayDetail(); // 调用父类方法
// 代码示例:打印objectiveMarks和subjectiveMarks
}
}
以下是关键点说明:
extends关键字用于建立继承关系。- 子类可以拥有自己的数据成员(如
objectiveMarks)。 - 子类可以重写父类的方法(如
acceptDetail)。 - 使用
super关键字可以调用父类中被重写的方法。
当创建子类对象时,内存会同时分配给父类和子类的数据成员。通过子类对象,我们可以调用父类和子类的方法。
多级继承:Sports类
现在,我们进一步扩展功能,创建一个Sports类来继承Marks类,用于记录学生的体育分数。这形成了多级继承链:Student -> Marks -> Sports。
class Sports extends Marks {
protected float score;
public void acceptDetail() {
super.acceptDetail();
// 代码示例:录入score
}
public void displayDetail() {
super.displayDetail();
// 代码示例:打印score
}
}
通过这种方式,Sports类间接继承了Student和Marks的所有属性和方法。

访问控制:protected关键字

在尝试从Result类中访问Marks类的分数时,我们发现如果数据成员是private的,则无法直接访问。为了解决跨继承层次的访问问题,我们将这些成员改为protected。
访问修饰符区别:
private:仅在本类内可访问。protected:在本类、同一包内以及所有子类中可访问。public:在任何地方都可访问。
因此,我们将Marks类中的分数变量改为protected,以便在继承链下游的类中访问。
最终计算:Result类
最后,我们创建一个Result类来继承Sports类,并计算总分和平均分。
class Result extends Sports {
private float totalMarks;
private float averageMarks;
public void calculate() {
totalMarks = objectiveMarks + subjectiveMarks + score;
averageMarks = totalMarks / 3;
System.out.println("Total Marks: " + totalMarks);
System.out.println("Average Marks: " + averageMarks);
}
}
以下是程序执行的步骤:
- 创建
Result类的对象。 - 调用
acceptDetail()方法,由于多级继承和方法重写,会依次调用整个继承链上的录入方法。 - 调用
displayDetail()方法,显示所有信息。 - 调用
calculate()方法,计算并输出总分和平均分。
运行示例:
输入:学号101,姓名King,客观分43,主观分89,体育分97。
输出:总分273,平均分91。
总结

本节课中我们一起学习了Java继承的核心实践。我们从创建一个简单的Student类开始,通过继承依次构建了Marks、Sports和Result类,演示了单继承、方法重写、使用super关键字调用父类方法、protected访问控制符的作用,以及多级继承的概念。这个示例清晰地展示了如何通过继承来分层组织代码,增强功能,同时保持代码的可维护性和可扩展性。
060:Java中的向上转型与向下转型 🧱

在本节课中,我们将要学习Java继承中的一个重要概念:类型转换。具体来说,我们将探讨向上转型和向下转型,理解它们如何工作、有何区别以及在实际编程中的应用。

概述
类型转换是将一种数据类型转换为另一种数据类型的概念。在Java中,这是处理对象时的一个核心概念,它允许我们隐式或显式地转换数据类型,这有时也被称为窄化和宽化。
在面向对象编程中,有两种类型的对象类型转换:向上转型和向下转型。理解这两种转换对于有效利用多态性和继承至关重要。
向上转型
上一节我们介绍了类型转换的基本概念,本节中我们来看看向上转型。
向上转型是指将子类类型转换为父类类型。这种转换是隐式进行的,意味着你不需要编写额外的代码来指定父类,Java会自动处理。
向上转型的好处是,它允许我们访问父类的所有成员和变量。然而,在向上转型后,我们无法访问子类特有的所有变量和方法,因为此时对象的引用更优先地指向了父类。
向下转型
了解了向上转型后,我们再来看看另一种形式:向下转型。
向下转型是另一种面向对象的类型转换,其中父类引用被指向给定的子类对象。在Java中,直接将父类引用对象赋予子类是不可能的。如果尝试进行向下转型,虽然编译时可能不会报错,但在运行时可能会引发 ClassCastException 异常。
因此,向下转型需要你根据需求进行显式的类型转换。这意味着你必须明确地告诉编译器,要将父类引用转换为特定的子类类型。
以下是进行向下转型时需要注意的关键点:
- 显式转换:必须使用强制类型转换语法,例如
(ChildClass) parentReference。 - 运行时风险:如果父类引用实际指向的对象不是目标子类的实例,转换将失败并抛出
ClassCastException。 instanceof检查:为了安全地进行向下转型,通常建议先使用instanceof运算符检查对象的实际类型。
总结

本节课中我们一起学习了Java继承中的向上转型与向下转型。我们了解到,向上转型是从子类到父类的隐式转换,它简化了代码但限制了对于类特有成员的访问。而向下转型是从父类到子类的显式转换,它提供了访问子类特有功能的能力,但需要谨慎处理以避免运行时异常。掌握这两种转换是理解Java多态性的关键。
Java全栈开发:06:向上转型与向下转型详解 🚗

在本节课中,我们将通过实际代码演示,学习Java中向上转型与向下转型的概念、区别与具体实现。理解这两个概念对于掌握继承和多态至关重要。

上一节我们介绍了继承的基本概念,本节中我们来看看类型转换在继承关系中的具体应用。
考虑以下场景:我们有一个Vehicle(车辆)基类和一个继承自它的Car(汽车)子类。
class Vehicle {
void drive() {
System.out.println("Driving the vehicle.");
}
}
class Car extends Vehicle {
@Override
void drive() {
System.out.println("Driving a car.");
}
void speedUp() {
System.out.println("Speeding up a car.");
}
}
向上转型详解
向上转型是将子类引用转换为父类引用的过程。这个过程是安全的,并且可以由Java编译器隐式完成。
以下是向上转型的步骤:
- 创建子类对象。
- 将其引用赋值给父类类型的变量。
- 通过父类引用调用方法,实际执行的是子类重写的方法。
public class Main {
public static void main(String[] args) {
// 向上转型:Car对象被赋值给Vehicle引用
Vehicle vehicle = new Car();
vehicle.drive(); // 输出: Driving a car.
}
}
在这个例子中,vehicle引用是Vehicle类型,但它指向一个Car对象。调用drive()方法时,执行的是Car类中重写的版本。这就是多态的体现。
向下转型详解
向下转型是将父类引用显式转换回子类引用的过程。这个过程不是类型安全的,必须由程序员显式进行,并且可能在运行时引发ClassCastException异常。
以下是向下转型的必要条件和步骤:
- 前提是父类引用实际指向的是一个子类对象。
- 使用强制类型转换语法
(子类名)进行转换。 - 转换成功后,可以访问子类特有的成员。
public class Main {
public static void main(String[] args) {
// 首先进行向上转型
Vehicle vehicle = new Car();
// 向下转型:将Vehicle引用显式转换回Car引用
Car car = (Car) vehicle;
car.drive(); // 输出: Driving a car.
car.speedUp(); // 输出: Speeding up a car.
}
}
注意:如果vehicle引用最初指向的不是Car对象(例如指向另一个Vehicle子类或Vehicle本身),那么向下转型将会失败。
核心区别与总结
本节课中我们一起学习了向上转型与向下转型。
- 向上转型:子类 -> 父类。安全,可隐式进行。用于实现多态,让代码更通用。
- 向下转型:父类 -> 子类。不安全,必须显式进行。用于访问子类特有功能,使用前需确保类型正确。

简单来说,向上转型是“看作更通用的类型”,而向下转型是“还原回具体的类型”。理解并正确运用这两种转型,是灵活使用Java面向对象特性的关键。
062:多态性入门
在本节课中,我们将要学习Java中的多态性。多态性是面向对象编程的核心特性之一,它允许我们以统一的方式处理不同类型的对象,从而编写出更灵活、更可复用的代码。
什么是多态性?🤔
多态性,英文为Polymorphism,是面向对象编程的一个基本概念。它允许开发者编写能够处理不同数据类型的代码。简单来说,多态性意味着“一个接口,多种实现”。

上一节我们介绍了多态性的基本概念,本节中我们来看看实现多态性的两种主要技术:方法重载和方法重写。
方法重载 (Method Overloading) 🔄
方法重载允许你在同一个类中使用相同的方法名,但参数列表(参数的类型、数量或顺序)必须不同。这提高了代码的可读性和可用性。
以下是方法重载的核心规则:
- 方法名必须相同。
- 参数列表必须不同(类型、数量或顺序)。
- 返回类型可以相同也可以不同。
- 仅返回类型不同不足以构成重载。
我们可以通过一个简单的代码示例来理解:
public class Calculator {
// 重载方法:两个整数相加
public int add(int a, int b) {
return a + b;
}
// 重载方法:三个整数相加(参数数量不同)
public int add(int a, int b, int c) {
return a + b + c;
}
// 重载方法:两个浮点数相加(参数类型不同)
public double add(double a, double b) {
return a + b;
}
}
方法重写 (Method Overriding) ✍️
与方法重载不同,方法重写发生在继承关系中。它允许子类为父类中已定义的方法提供特定的实现。
以下是方法重写的核心规则:
- 方法名、参数列表和返回类型必须与父类方法完全相同。
- 重写方法的访问权限不能比父类方法更严格(例如,父类是
protected,子类可以是public或protected,但不能是private)。 - 只能重写非
static、非final的方法。
我们可以通过以下代码示例来理解方法重写:
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override // 注解,表明此方法重写了父类方法
public void makeSound() {
System.out.println("汪汪!");
}
}
重载与重写的区别 ⚖️
理解了两种技术的基本概念后,我们来明确一下它们之间的关键区别。
以下是重载与重写的主要区别:
- 发生位置:重载发生在同一个类内;重写发生在具有继承关系的不同类(子类与父类)之间。
- 参数列表:重载要求参数列表必须不同;重写要求参数列表必须完全相同。
- 返回类型:重载对返回类型没有强制要求;重写要求返回类型必须兼容(相同或是其子类)。
- 作用:重载是编译时多态,用于提供处理不同数据的同名方法;重写是运行时多态,用于实现子类的特定行为。
构造函数重载 🏗️
除了普通方法,构造函数也可以重载。这允许我们以多种方式初始化一个对象。
以下是构造函数重载的示例:
public class Book {
private String title;
private String author;
// 构造函数1:接受两个参数
public Book(String t, String a) {
title = t;
author = a;
}
// 构造函数2:只接受书名,作者设为“未知”
public Book(String t) {
title = t;
author = "未知";
}
// 构造函数3:无参构造函数,提供默认值
public Book() {
title = "未命名";
author = "未知";
}
}
总结 📚
本节课中我们一起学习了Java多态性的两大支柱:方法重载和方法重写。
- 方法重载:在同一类中,允许同名方法根据不同的参数列表执行不同的操作,增强了方法的灵活性。
- 方法重写:在继承体系中,允许子类重新定义父类已有的方法,以实现特定的行为,这是实现运行时多态的关键。
通过有效运用重载和重写,你可以构建出结构清晰、易于扩展和维护的Java应用程序。掌握这些技巧是成为一名熟练的Java开发者的重要一步。
063:Java中的多态性 🎭


在本节课中,我们将要学习面向对象编程的核心概念之一:多态性。我们将探讨多态性的含义、类型以及如何在Java中实现它。
概述
多态性(Polymorphism)一词源自希腊语,由“poly”(意为“多”)和“morph”(意为“形态”)组成。简单来说,它意味着“一个接口,多种功能”。在编程中,这表示一个实体可以以多种形式存在。
多态性的含义
一个实体可以呈现多种形态。以人类为例,一个人在生活中扮演着多种角色:女儿、姐妹、母亲等。但在任何特定时刻,只扮演其中一种角色。因此,一个实体以多种形式存在。
在Java中,我们可以通过方法重载和方法重写来实现多态性。
另一个例子是碳元素。碳可以以多种形式存在,如钻石、石墨和煤炭。我们可以说,碳根据情况同时展现出不同的特性。
多态性的类型
为了实现多态性,存在两种主要类型:编译时多态性和运行时多态性。

编译时多态性可以通过方法重载实现,而运行时多态性可以通过方法重写实现。我将在后续课程中详细讨论这些概念。
多态性的实例
另一个例子是人的身份。一个人可以是学生、雇员或百万富翁。学生和百万富翁都需要支付账单,但学生支付的是教育账单,而百万富翁支付的是信用卡账单。他们的实现、需求、属性和行为可能不同。
因此,一个人具有Person类中的共同特征,而作为Student和Millionaire的子类可以重写它们的行为。
多态性的现实类比
多态性的一个现实类比是人体。人体有不同的器官,每个器官执行不同的功能。心脏负责血液流动,肺负责呼吸,大脑负责认知活动,肾脏负责排泄。
我们必须理解,方法或功能根据身体器官的不同而执行不同的操作。
编译时多态性
编译时多态性也称为静态多态性或静态方法分派。它可以通过方法重载实现。在这个过程中,重载的方法在编译时解析,而不是在运行时解析。
Java不支持运算符重载,但在C++中,运算符重载也可以实现多态性。
运行时多态性
运行时多态性也称为动态多态性或动态方法分派。它不是编译时解析重写的方法,而是在运行时解析。通过父类引用调用子类重写的方法,然后根据对象的类型调用相应的方法。
我已经演示了为此进行的向下转型和向上转型。JVM会识别对象类型以及你创建的引用变量所属的方法。
在Java中,当存在两个或更多通过继承相互关联的类时,就会发生运行时多态性。为了实现运行时多态性,我们必须在类之间建立“is-a”关系并重写一个方法。

总结
本节课中,我们一起学习了Java中的多态性。我们探讨了多态性的基本概念、两种主要类型(编译时多态性和运行时多态性)以及它们的实现方式。我们还通过现实世界的例子加深了对多态性的理解。


在接下来的课程中,我将通过实际演示详细介绍方法重载和方法重写。敬请关注,我们下节课再见!
064:方法重载详解 🧩

在本节课中,我们将要学习Java中一个非常重要的概念——方法重载。这是多态性的核心组成部分之一。我们将探讨如何创建同名但参数列表和定义不同的方法,以及如何在实际编程中应用这一特性。
概述
方法重载允许在同一个类中声明多个同名但参数不同的方法。当调用一个被重载的方法时,Java会根据传入参数的类型、数量或顺序来决定具体执行哪个版本的方法定义。
什么是方法重载?

上一节我们介绍了方法的基本概念,本节中我们来看看如何让方法“一名多用”。
方法重载是指在同一个类中,可以定义多个方法名相同,但参数列表不同的方法。这里的“不同”可以体现在参数的数量、类型或顺序上。
以下是方法重载的核心规则:
- 方法名必须相同。
- 参数列表必须不同(数量、类型或顺序)。
- 方法的返回类型、访问修饰符可以不同,但这些不构成方法签名的一部分,因此不能仅凭它们来区分重载方法。
方法重载的原理与应用场景
当不同的对象需要执行一组相似的任务,但处理不同的输入参数时,就需要使用方法重载。
例如,在一个电子商务平台中,支付功能可能有多种模式,如信用卡支付、UPI支付或银行转账。我们可以为processPayment方法创建多个重载版本,每个版本处理一种特定的支付参数。
当调用重载方法时,Java首先匹配方法名,然后根据调用时提供的参数数量和类型,精确匹配到对应的定义并执行。
以下是实现方法重载的几种方式:
- 改变参数数量:例如,
add(int a, int b)和add(int a, int b, int c)。 - 改变参数类型:例如,
add(int a, int b)和add(double a, double b)。 - 改变参数顺序:当参数数量和类型都相同时,可以改变参数的顺序来区分。例如,
add(int a, float b)和add(float a, int b)。
方法重载的优势
在实时项目或应用程序中使用方法重载概念,具有以下优势:
- 提高代码可读性:使用直观、统一的方法名执行相关操作。
- 保持应用一致性:相似功能使用相同入口,逻辑清晰。
- 促进代码复用:核心逻辑可以封装,通过不同参数适配多种情况。
- 提供向后兼容性和灵活性:添加新功能时,可以创建新的重载方法而不影响旧代码。
实战:在Java中实现方法重载
理解了理论之后,让我们通过一个具体的例子来实践方法重载。
假设我们有一个Calculation类,其中包含多个执行加法操作的addition方法。
class Calculation {
// 版本1:两个整数相加
public static int addition(int number1, int number2) {
return number1 + number2;
}
// 版本2:三个整数相加(改变参数数量)
public static int addition(int number1, int number2, int number3) {
return number1 + number2 + number3;
}
// 版本3:两个浮点数相加(改变参数类型)
public static float addition(float number1, float number2) {
return number1 + number2;
}
// 版本4:一个整数和一个浮点数相加
public static float addition(int number1, float number2) {
return number1 + number2;
}
// 版本5:一个浮点数和一个整数相加(改变参数顺序)
public static float addition(float number1, int number2) {
return number1 + number2;
}
}
现在,我们来调用这些重载的方法:
public class Main {
public static void main(String[] args) {
// 调用两个整数相加的版本
System.out.println(Calculation.addition(100, 200));
// 调用三个整数相加的版本
System.out.println(Calculation.addition(100, 200, 300));
// 调用两个浮点数相加的版本
System.out.println(Calculation.addition(100.50f, 200.30f));
// 调用(整数,浮点数)顺序的版本
System.out.println(Calculation.addition(100, 200.54f));
// 调用(浮点数,整数)顺序的版本
System.out.println(Calculation.addition(200.54f, 100));
}
}
运行此程序,控制台将依次输出各个重载方法的计算结果,清晰地展示了Java如何根据我们传入的参数来选择合适的addition方法执行。
总结

本节课中我们一起学习了Java中的方法重载。我们了解到,方法重载是实现编译时多态的一种方式,它允许我们使用同一个方法名来执行操作相似但输入参数不同的任务。关键在于保持方法名相同,而通过参数列表(数量、类型、顺序)的差异来区分不同的方法实现。正确使用方法重载可以极大地提升代码的清晰度、灵活性和可维护性。
065:方法重写(Method Overriding)🚀

在本节课中,我们将要学习Java面向对象编程中的一个核心概念——方法重写。这是实现动态多态的关键技术之一。我们将通过对比方法重载,并借助一个具体的代码示例,来清晰地理解方法重写的定义、规则和应用场景。

概述:什么是方法重写?

上一节我们介绍了多态的基本概念,本节中我们来看看实现动态多态的一种具体方式:方法重写。
当子类拥有一个与父类方法签名完全相同(方法名、返回类型、参数列表均相同)的方法时,就发生了方法重写。在程序运行时,Java虚拟机会根据实际创建的对象类型来决定调用哪个方法,这个过程就是方法重写。
方法重写 vs. 方法重载
为了更好地理解方法重写,我们将其与方法重载进行对比。以下是两者的核心区别:
- 发生位置:方法重载发生在同一个类中;方法重写发生在具有继承关系的父子类之间。
- 方法签名:方法重载要求方法名相同,但参数列表必须不同;方法重写要求方法名、返回类型和参数列表必须完全相同。
- 目的:方法重载是为了提供处理不同数据的同名方法,实现编译时多态;方法重写是子类对父类方法的重新定义,以实现运行时多态和功能扩展。
简单来说,重载是“一个类,多个同名但参数不同的方法”;重写是“父子类,签名完全相同的两个方法,子类覆盖父类”。
方法重写的代码示例
理论需要实践来巩固。接下来,我们通过一个具体的例子来演示方法重写是如何工作的。
首先,我们创建一个Person(人)基类,它包含姓名、年龄属性和一个打印详细信息的方法。
class Person {
String name;
int age;
// 构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 打印信息的方法
public void printDetails() {
System.out.println("Name is: " + name);
System.out.println("Age is: " + age);
}
}
现在,我们创建一个Employee(员工)类来继承Person类。员工除了姓名和年龄,还有职位和薪水。我们需要重写printDetails方法,以打印员工特有的信息。
class Employee extends Person {
String designation;
double salary;
// 构造方法,使用super调用父类构造方法
public Employee(String name, int age, String designation, double salary) {
super(name, age); // 初始化从父类继承的属性
this.designation = designation;
this.salary = salary;
}
// 重写父类的printDetails方法
@Override
public void printDetails() {
super.printDetails(); // 首先调用父类方法打印基础信息
System.out.println("Designation is: " + designation); // 然后打印子类特有信息
System.out.println("Salary is: " + salary);
}
}
最后,我们创建一个测试类来观察方法重写的效果。
public class TestOverriding {
public static void main(String[] args) {
// 创建Employee对象
Employee emp = new Employee("Alice", 30, "Software Engineer", 80000.0);
// 调用printDetails方法
emp.printDetails();
}
}
运行以上代码,输出结果将是:
Name is: Alice
Age is: 30
Designation is: Software Engineer
Salary is: 80000.0
可以看到,当我们通过Employee类的对象调用printDetails()方法时,执行的是子类中重写后的版本。子类方法通过super.printDetails()先调用了父类的逻辑,然后添加了自己的逻辑,从而扩展了父类的功能。
总结

本节课中我们一起学习了Java中的方法重写。我们明确了方法重写是实现运行时多态(动态多态)的机制,它发生在继承体系中,要求子类方法与父类方法具有完全相同的方法签名。通过重写,子类可以重新定义从父类继承来的方法行为,实现功能的专业化扩展。记住,方法重写(Override)与方法重载(Overload)是两种完全不同的概念,前者关乎继承与多态,后者关乎同一类中的方法命名。


提示:方法重写也可以与抽象类和接口结合使用,我们将在后续课程中详细探讨。
066:抽象类与接口详解
在本节课中,我们将学习Java编程中两个至关重要的概念:抽象类和接口。它们是实现多态和代码结构化的核心工具,理解它们的区别与适用场景对于设计健壮的Java程序至关重要。
抽象类概述
上一节我们介绍了面向对象的基础,本节中我们来看看抽象类。抽象类是一种不能被直接实例化的类,它通常包含一个或多个抽象方法。抽象方法是一种只有声明而没有具体实现的方法。
抽象类的语法如下:
abstract class AbstractClassName {
// 可以包含普通方法和变量
abstract void abstractMethod(); // 抽象方法,没有方法体
}
以下是抽象类的几个关键点:
- 无法实例化:不能使用
new关键字创建抽象类的对象。 - 包含抽象方法:抽象类可以声明没有方法体的抽象方法,这些方法的具体实现由其子类提供。
- 与普通类的区别:抽象类可以像普通类一样拥有构造器、成员变量和已实现的方法,但它不能被直接创建为对象。
接口概述
理解了抽象类后,我们再来看看接口。接口在概念上与抽象类相似,都是用于定义规范,但它们之间存在一些关键差异。
接口的语法如下:
interface InterfaceName {
void methodToImplement(); // 接口方法默认是 public abstract
}
以下是接口的核心特性:
- 定义契约:接口定义了一组方法签名,形成了一种“契约”。
- 强制实现:任何实现该接口的类,都必须提供接口中所有声明的具体实现。
- 创建与实现:在Java中,使用
interface关键字定义接口,使用implements关键字让类实现接口。
抽象类与接口的核心区别及选择
现在我们已经分别了解了抽象类和接口,接下来我们总结一下它们之间的主要区别,并讨论如何在实际开发中选择使用。
以下是它们的关键差异点:
- 方法实现:抽象类可以包含已实现的方法和抽象方法;接口在Java 8之前只能包含抽象方法,之后可以包含默认方法和静态方法。
- 变量类型:抽象类中的成员变量可以是各种类型;接口中的变量默认是
public static final的常量。 - 继承关系:一个类只能继承一个抽象类,但可以实现多个接口。
- 设计理念:抽象类通常用于表示“是一个(is-a)”的关系,关注类的本质;接口用于表示“具有某种能力(can-do)”的关系,关注行为契约。
何时选择抽象类:当需要为一些紧密相关的类提供公共的代码实现和模板,并且这些类共享一个共同的基类概念时。
何时选择接口:当需要定义一种行为规范,让可能毫无关联的类都能具备这种能力,或者需要实现多重“能力”继承时。
总结
本节课中我们一起学习了Java中的抽象类与接口。我们明确了抽象类作为不能被实例化的基类,如何通过抽象方法定义子类必须完成的任务。我们也探讨了接口如何作为一种严格的契约,强制实现类提供特定行为。最后,我们对比了二者的核心区别,并给出了在不同场景下的选用指南。掌握这些概念将帮助你更好地运用多态性,设计出更灵活、更可维护的Java程序。
067:抽象类与抽象方法 🧩

在本节课中,我们将通过示例学习Java中的抽象类和抽象方法。这也是Java中抽象的概念,它是面向对象编程中最重要的支柱或组成部分之一。


什么是抽象类?
上一节我们提到了面向对象编程的支柱,本节中我们来看看其中一个核心概念——抽象类。
抽象类是一个不能直接实例化的类。使用 abstract 关键字声明的类被称为抽象类。😊
我们使用抽象类来创建一个契约。之所以称之为契约,是因为我没有为写在抽象类内部的抽象方法提供具体定义,这些方法必须在子类中被重写或实现。
“抽象”一词意味着一个简短的描述,它能让你对概念有一个想象性的理解,就像一个摘要。😊 就像一小段文字能给你整本书的简要描述或解释一样。同样地,Java中的抽象类只是声明了那些需要由继承该抽象类的类来实现的方法。
为什么需要抽象类?
那么,为什么要把一个类设为抽象类呢?实际上,如果任何一个特定的类中包含了任何抽象方法,那么这个类必须被声明为抽象类。这就是原因。它完全持有着契约,同时它也可以包含非抽象方法。但我们将其视为契约,因此我们不允许创建它的对象。
考虑一个现实生活中的例子:我们需要一个项目中所需功能的蓝图,比如加法、减法、乘法和除法。所有这些方法都将被视为抽象方法,因为我们尚未考虑它们的实现。我们只是在收集我们认为创建计算器应用程序所需的基本功能。无论你创建哪种类型的计算器,我们都可以在这些子类中重新定义它们的行为。
抽象类的特性
在深入之前,你需要理解为什么我们需要抽象类。如前所述,抽象类有助于创建契约或模板,有助于实现低耦合、代码复用和抽象。
以下是抽象类的一些关键特性:
- 抽象类不能被实例化。
- 抽象类至少包含一个纯虚函数(即抽象方法)。
- 抽象类可以同时包含抽象方法和非抽象方法。
- 如果需要,抽象类也可以有构造函数和析构函数。
- 抽象类也可以有成员变量。
- 抽象类可以被用作基类。
总结
本节课中,我们一起学习了Java抽象类与抽象方法。我们了解到抽象类是一种不能被直接实例化的类,它通过声明抽象方法来定义一个必须由子类实现的“契约”。这有助于实现代码的抽象、复用和低耦合。抽象类可以包含抽象方法和具体方法,也可以拥有构造器和成员变量。

在下一节中,我们将继续学习如何在实践中实现这种契约,敬请关注。
068:抽象类实战演示 🏦

在本节课中,我们将通过一个银行账户的实例,学习抽象类和抽象方法的实际应用。我们将创建一个抽象类作为基础模板,并让具体的子类来实现其定义的功能。

概述
抽象类用于定义一种通用的结构或契约,它包含抽象方法(只有声明,没有实现)和具体方法(有实现)。任何继承抽象类的具体类都必须实现其所有的抽象方法。本节我们将创建一个银行账户系统来演示这一概念。
创建抽象基类
首先,我们创建一个名为 BankAccount 的抽象类。这个类将定义所有银行账户都应具备的共同操作,但具体的实现细节将留给子类。
public abstract class BankAccount {
// 抽象方法:存款
public abstract void deposit();
// 抽象方法:取款
public abstract void withdraw();
// 抽象方法:获取余额
public abstract void getBalance();
}
请注意,如果一个类包含一个或多个抽象方法,那么这个类本身必须声明为 abstract。
实现具体子类:储蓄账户
上一节我们介绍了抽象基类的结构,本节中我们来看看如何创建一个具体的子类。我们将创建一个 SavingAccount 类来继承 BankAccount 抽象类。
当一个具体类继承一个抽象类时,它必须实现该抽象类中定义的所有抽象方法。
以下是创建 SavingAccount 类的步骤:
- 创建类并继承
BankAccount。 - 使用IDE(如Eclipse或IntelliJ IDEA)的快捷功能自动生成需要实现的方法框架。
- 在这些方法框架内添加具体的实现代码。


public class SavingAccount extends BankAccount {
@Override
public void deposit() {
System.out.println("Deposit in Saving Account.");
}
@Override
public void withdraw() {
System.out.println("Withdraw from Saving Account.");
}
@Override
public void getBalance() {
System.out.println("Balance in Saving Account.");
}
}
现在,我们可以创建 SavingAccount 的对象并调用其方法:
SavingAccount sa = new SavingAccount();
sa.deposit();
sa.withdraw();
sa.getBalance();
实现更多具体子类:活期账户
同样地,我们可以创建另一个具体类,例如 CurrentAccount。这展示了抽象类如何作为多个不同但相关的类的蓝图。
以下是 CurrentAccount 类的实现:
public class CurrentAccount extends BankAccount {
@Override
public void deposit() {
System.out.println("Deposit in Current Account.");
}
@Override
public void withdraw() {
System.out.println("Withdraw from Current Account.");
}
@Override
public void getBalance() {
System.out.println("Balance in Current Account.");
}
}
运行程序后,我们将看到来自储蓄账户和活期账户的不同输出信息。
抽象类的继承链
抽象类本身也可以继承另一个抽象类。这允许我们构建更复杂的层次结构。让我们创建一个更顶层的抽象类 ABCBank。
public abstract class ABCBank {
// 新的抽象方法
public abstract void openAccount();
public abstract void closeAccount();
// 具体方法
public void message() {
System.out.println("Welcome to ABC Bank.");
}
}
然后,我们让 BankAccount 类继承 ABCBank。由于Java不支持多继承,一个类只能直接继承一个父类,所以我们建立这样的链:SavingAccount -> BankAccount -> ABCBank。

修改后的 BankAccount 类:

public abstract class BankAccount extends ABCBank {
// ... 原有的 deposit, withdraw, getBalance 抽象方法 ...
}
现在,SavingAccount 类不仅需要实现 BankAccount 的抽象方法,还需要实现从 ABCBank 继承来的 openAccount() 和 closeAccount() 方法。
以下是更新后的 SavingAccount 类部分代码:
public class SavingAccount extends BankAccount {
// ... 原有的 deposit, withdraw, getBalance 实现 ...
@Override
public void openAccount() {
System.out.println("Open Account in Saving Account.");
}
@Override
public void closeAccount() {
System.out.println("Close Account in Saving Account.");
}
}
我们可以通过子类对象调用所有方法,包括来自顶层抽象类的具体方法:
SavingAccount sa = new SavingAccount();
sa.message(); // 调用继承自 ABCBank 的具体方法
sa.openAccount();
sa.closeAccount();
另外,如果我们将 ABCBank 中的 message() 方法声明为 static,则可以直接通过类名调用,无需创建对象:ABCBank.message();。
抽象类中的具体方法
重要的一点是,抽象类并非只能包含抽象方法。它可以包含带有完整实现的具体方法。正如我们在 ABCBank 类中看到的 message() 方法。这些具体方法可以被所有子类继承和使用,为代码复用提供了便利。
然而,我们不能直接实例化一个抽象类。例如,ABCBank abc = new ABCBank(); 这样的代码是错误的。我们只能通过创建其具体子类的对象来访问这些方法。
总结
本节课中我们一起学习了抽象类的核心概念和实战应用。我们了解到:
- 抽象类使用
abstract关键字定义,可以包含抽象方法和具体方法。 - 抽象方法只有声明,没有实现,格式为:
public abstract void methodName();。 - 任何继承抽象类的具体类必须实现其所有的抽象方法。
- 抽象类不能被直接实例化。
- 抽象类之间可以形成继承关系,构建出清晰的类层次结构。
- 抽象类中的具体方法提供了代码复用的能力。

通过银行账户的例子,我们看到了抽象类如何作为一种“契约”或“模板”,强制不同的具体类(如储蓄账户、活期账户)实现一组共同的核心功能,同时允许它们拥有各自独特的行为细节。这是面向对象编程中实现多态和代码规范化的强大工具。
069:Java中的接口 🧩


在本节课中,我们将要学习Java中一个非常重要的概念——接口。接口是定义类行为的一种方式,它规定了类必须实现哪些方法,但不提供这些方法的具体实现。通过接口,我们可以实现更灵活、更松耦合的程序设计。
什么是接口?
上一节我们介绍了抽象类,本节中我们来看看接口。接口本质上是一组我们希望类去实现的抽象和公共方法。它是一个类的蓝图,其中包含静态常量、抽象方法以及默认方法。
接口也包含变量和方法,但默认情况下,接口中的所有方法都是公共和抽象的。这意味着接口中只包含抽象方法的声明。
接口与抽象类的区别
如果你将接口与抽象类进行比较,接口是一个更严格的契约,每一个具体的类都必须遵循这个契约。接口有助于实现松耦合的架构或设计模式。
此外,通过接口,我们可以支持多重继承的功能。正如我在之前的课程中提到的,一个类在某一时刻只能继承一个类,但可以同时实现多个接口。接口也代表了一种“是一种”的关系。
接口的声明与语法
以下是使用接口时需要注意的一些声明和语法规则。
接口使用 interface 关键字声明。它提供了抽象性,意味着接口中的所有方法都只声明了空的方法体,并且默认是抽象的,你不需要额外写上 abstract 关键字。
接口中的所有字段默认都是 public、static 和 final 的。一个类要实现一个接口,必须实现该接口中声明的所有方法。这意味着在继承时,我们使用 implements 关键字,而不是 extends。
这就是创建接口的基本方式:
public interface MyInterface {
// 常量(默认 public static final)
int CONSTANT_VALUE = 10;
// 抽象方法(默认 public abstract)
void myMethod();
}
为什么使用接口?

那么,我们为什么要使用接口呢?
首先,接口有助于封装不受限制的数据,并抽象出受限制的数据。它帮助我们实现方法重写,提供了多态性的灵活性,便于测试和未来扩展。还有一个这里没有明确提到但非常重要的原因,那就是实现多重继承。
总结
本节课中我们一起学习了Java接口的核心概念。我们了解到接口是一种定义类行为的契约,它只包含抽象方法的声明,并且支持多重继承。接口是实现松耦合设计和程序灵活性的关键工具。在接下来的课程中,我们将通过实际代码来深入理解接口的应用。
敬请期待,学习更多关于接口的实践应用。

我们下节课再见。 👋
Java全栈开发:05:接口实战演示与对比

在本节课中,我们将通过实际编码来学习接口的实现,并比较接口与抽象类的异同。

概述
我们将创建一个银行账户的接口,并实现它。同时,我们会探讨接口如何支持多重继承,以及它与抽象类的关键区别。
创建接口与实现类
首先,我们创建一个名为 IBankAccount 的接口。在接口中,我们只声明方法的返回类型和名称,不写方法体。默认情况下,接口中的方法都是 public abstract 的。
interface IBankAccount {
void deposit();
void withdraw();
void balance();
}
接下来,我们创建一个具体的 SavingAccount 类来实现这个接口。实现接口时,必须重写接口中所有的抽象方法。
class SavingAccount implements IBankAccount {
@Override
public void deposit() {
System.out.println("Deposit in saving account.");
}
@Override
public void withdraw() {
System.out.println("Withdraw from saving account.");
}
@Override
public void balance() {
System.out.println("Balance in saving account.");
}
}
现在,我们可以创建 SavingAccount 类的对象,并调用其方法。
public class Main {
public static void main(String[] args) {
SavingAccount saving = new SavingAccount();
saving.deposit();
saving.withdraw();
saving.balance();
}
}
接口与多重继承
上一节我们介绍了如何实现一个简单的接口。本节中我们来看看接口如何实现多重继承。这是抽象类无法做到的。
我们创建另一个接口 IABCBank。
interface IABCBank {
void openAccount();
void closeAccount();
}
现在,我们的 SavingAccount 类可以同时实现 IBankAccount 和 IABCBank 两个接口。
class SavingAccount implements IBankAccount, IABCBank {
// ... 之前重写的方法
@Override
public void openAccount() {
System.out.println("Open account.");
}
@Override
public void closeAccount() {
System.out.println("Close account.");
}
}
这种一个类实现多个接口的能力,被称为 多重继承。
接口与抽象类的关键区别
以下是接口与抽象类的一些核心区别:
- 实例化:和抽象类一样,不能直接创建接口的对象。尝试
new IBankAccount()会导致编译错误。 - 默认方法:在接口中,如果尝试为一个方法直接提供实现体,编译器会报错。但自 Java 8 起,接口允许使用
default关键字来定义具有默认实现的方法。
实现类可以直接使用或重写这个默认方法。interface IABCBank { default void welcomeMessage() { System.out.println("Welcome to ABC Bank!"); } void openAccount(); void closeAccount(); }saving.welcomeMessage(); // 输出:Welcome to ABC Bank! - 设计目的:抽象类用于建立一种严格的“是一个(is-a)”关系,而接口更侧重于定义一种“能做什么(can-do)”的契约。接口的契约性比抽象类更强。
总结
本节课中我们一起学习了接口的创建与实现。我们了解到:
- 接口使用
interface关键字定义,方法默认是public abstract。 - 类使用
implements关键字来实现接口,并必须重写所有抽象方法。 - 一个类可以实现多个接口,从而实现多重继承。
- 接口不能直接实例化。
- 从 Java 8 开始,接口可以使用
default方法提供默认实现。 - 接口主要用于定义行为契约,在大型应用(如三层架构中的数据访问层)中广泛使用,以实现松耦合设计。

希望这些概念对您有所帮助。我们下节课再见。
071:抽象类与接口的区别 🧩
在本节课中,我们将要学习Java中抽象类与接口的核心区别。理解这两者的不同是掌握面向对象设计的关键一步。

概述
在Java中,抽象类和接口都可以用来声明一个“契约”,实现类必须满足这个契约。它们都用于定义一组方法,但具体的使用场景和特性有所不同。接下来,我们将详细探讨它们之间的差异。


核心差异详解
上一节我们介绍了抽象类和接口的共同点,本节中我们来看看它们的具体区别。
1. 方法定义
抽象类可以包含抽象方法和非抽象方法(即具体实现的方法)。而接口在默认情况下,其所有方法都是抽象的(在Java 8之后,接口也可以包含默认方法和静态方法,但核心区别依然存在)。
抽象类示例:
abstract class Animal {
abstract void makeSound(); // 抽象方法
void sleep() { // 非抽象方法
System.out.println("Sleeping...");
}
}
接口示例:
interface Animal {
void makeSound(); // 默认是抽象方法
}
2. 变量定义
抽象类可以拥有静态变量、非静态变量、final变量和非final变量。相比之下,接口中声明的变量默认是final和static的,即常量。
抽象类变量:
abstract class Example {
int normalVar; // 非静态、非final
final int finalVar = 10; // final变量
static int staticVar; // 静态变量
}
接口变量:
interface Example {
int CONSTANT = 10; // 默认是 public static final
}
3. 继承与实现
一个类在某一时刻只能继承一个抽象类,这意味着抽象类不支持多重继承。然而,一个类可以实现多个接口,因此接口支持多重继承。
继承抽象类:
class Dog extends Animal { // 只能继承一个抽象类
// ...
}
实现接口:
class Dog implements Animal, Pet { // 可以实现多个接口
// ...
}
4. 关键字与成员访问权限
使用extends关键字来继承一个抽象类,而使用implements关键字来实现一个接口。
抽象类可以拥有类成员,例如private和protected修饰的成员。而接口的成员默认都是public的。
抽象类成员:
abstract class MyClass {
private int privateVar;
protected int protectedVar;
}
接口成员:
interface MyInterface {
void myMethod(); // 默认是 public
}
使用场景对比
以下是抽象类和接口各自适用的典型场景:
- 抽象类:当多个相同类型的实现共享共同的行为时使用。此外,如果你需要一个包含部分实现的基类,抽象类是合适的选择。
- 接口:当各种实现仅共享方法签名(即行为规范)时使用。当你的类需要从多个来源获得额外行为或依赖时,接口是一个很好的选择。
总结

本节课中我们一起学习了抽象类与接口在Java中的核心区别。我们了解到,抽象类更侧重于为相关类提供一个包含部分共同实现的模板,而接口则专注于定义一套纯粹的行为规范,并支持多重继承。理解这些差异有助于你在实际开发中做出更合适的设计选择。
072:认识你的讲师 👨🏫
在本节课中,我们将认识本课程的讲师,了解他的专业背景和教学经验,为后续的学习旅程做好准备。
讲师的名字是Navni Chan。他拥有多年的软件工程经验,并且主要专注于全栈开发领域。
在职业生涯中,Navni曾与多个不同领域的公司合作,例如科技、金融科技和健康科技等领域。
多年来,他使用诸如JavaScript、React、Angular、Node.js、AWS等主流技术,从零开始构建并参与了众多面向网页和移动设备的项目。
作为一名教育者,Navni已经教授过超过1000名学生软件开发。他理解学习软件开发可能是一项艰巨的任务,但请不要担心。在本课程中,他将作为向导,陪伴你完成这段旅程,为你提供掌握Web开发所必需的知识和工具。
我们将在下一个视频中相见。
本节课中,我们一起认识了讲师Navni Chan,了解了他丰富的全栈开发经验和教学背景,为开启Java全栈开发的学习奠定了信心基础。
073:本课学习目标 🎯
在本节课中,我们将要学习构成网页开发基础的HTML标签。我们将探索各种格式化标签,了解如何创建交互式页面,并学习如何通过语义化标签优化网页设计。
概述
HTML是构建网页的基石。本节将系统介绍多种核心HTML标签,包括用于内容结构的格式化标签、用于用户交互的表单标签、用于嵌入多媒体资源的标签,以及用于提升网页可访问性和搜索引擎优化的语义化标签。
格式化标签
上一节我们介绍了本课的整体目标,本节中我们来看看如何用HTML标签来组织和格式化网页内容。这些标签决定了文本的层次结构与基本布局。
以下是常用的文本格式化标签:
- 标题标签:用于定义标题,从
<h1>到<h6>,重要性依次递减。 - 段落标签:
<p>标签用于定义一个段落。 - 换行标签:
<br>标签用于插入一个简单的换行符。
表单与框架标签
掌握了内容格式化后,我们来看看如何让网页与用户互动。表单标签用于收集用户输入,而框架标签则用于划分页面区域。
以下是相关的标签:
- 表单输入标签:例如
<input>、<textarea>、<select>,它们允许创建文本框、按钮、下拉菜单等交互元素。 - 框架标签:如
<iframe>,它允许在当前页面中嵌入另一个独立的HTML文档。
媒体与图形标签
一个丰富的网页离不开图片和多媒体。本节将学习如何在网页中嵌入图像、矢量图形、音频和视频。
以下是用于媒体内容的标签:
- 图像标签:
<img src="image.jpg" alt="描述">用于显示图片。 - SVG标签:
<svg>用于创建可缩放的矢量图形。 - 音频标签:
<audio controls>用于嵌入音频内容。 - 视频标签:
<video controls>用于嵌入视频内容。
列表与表格标签
当需要展示条目化数据或结构化信息时,列表和表格标签就派上用场了。
以下是用于数据组织的标签:
- 列表标签:
- 有序列表使用
<ol>和<li>标签。 - 无序列表使用
<ul>和<li>标签。
- 有序列表使用
- 表格标签:使用
<table>、<tr>、<td>、<th>等标签来展示行列结构的表格数据。
语义化、头部与元标签
最后,我们来探讨对现代网页开发至关重要的高级标签。语义化标签使网页结构对浏览器和开发者更清晰,而头部和元标签则承载了网页的关键元信息。
以下是相关的重要标签:
- 语义化标签:如
<header>、<nav>、<main>、<article>、<footer>,它们定义了内容的具体角色,有助于提升可访问性和搜索引擎优化。 - 非语义化标签:如
<div>和<span>,是通用的内容容器,本身不携带特定含义。 - 头部标签:
<head>标签包含了文档的元数据,如标题、字符集声明等。 - 元标签:
<meta>标签用于定义页面的描述、关键词、作者等信息,例如设置字符集:<meta charset="UTF-8">。
总结

本节课中我们一起学习了HTML的核心标签体系。我们从基础的文本格式化标签开始,逐步深入到表单、多媒体、数据展示标签,最后了解了语义化标签和元数据标签的重要性。掌握这些标签是进行Web开发的第一步,它们共同构成了网页内容和结构的基础。
准备好迎接一场知识盛宴吧,我们下一个视频见。
074:Web开发入门 🚀
在本节课中,我们将学习Web开发的基础知识,特别是前端开发的核心组成部分。我们将从HTML开始,了解它是如何构建网页结构的,并介绍创建第一个HTML页面所需的工具。
在上一节中,我们介绍了Web开发的概念。本节中,我们来看看前端开发。
对于前端开发,我们首先需要了解其构建模块,其中之一就是HTML。
如今,任何现代网站都直接或间接地使用HTML。进一步了解HTML,我们知道它代表超文本标记语言,由蒂姆·伯纳斯-李于1993年提出。
可以说,HTML与互联网本身一样古老。
它是与互联网一同发展起来的初始语言之一,用于在互联网上提供可供消费的内容。它是一种标准的标记语言,用于设计在Web浏览器中显示的文档。
HTML是一种标记语言,而不是编程语言。
这意味着它用于管理文档的内容,决定其应如何结构化以及应如何呈现。当我们的Web浏览器消费这些HTML文档时,它就能知道应如何在屏幕上呈现文档中的项目。
目前我们使用的是HTML5版本。
从HTML1到HTML5,我们看到了巨大的进步。为了举例说明,让我展示一些内容。
如果我们查看这个页面,这是亚马逊早期的页面之一。你可以看到它的样子。如果将其与当前的亚马逊网站进行比较,变化是巨大的。你可以看到技术的进步以及它如何发展。
它如何帮助一个特定的网站改变自身。谈到任何现代网站,我们可以说它主要由三种技术构成。
以下是构成现代网站的三种核心技术:
- HTML:用于管理结构。
- CSS:用于设计。
- JavaScript:主要用于为网站提供额外的功能。
因此,可以说HTML提供了基本结构,而CSS和JavaScript等技术则对其进行增强和修改。CSS主要控制呈现、格式和布局,即事物在网页上应如何显示。而JavaScript则提供不同元素的行为。
如果你想举个例子,假设你将鼠标悬停在某个菜单项上,它会默认显示该菜单项下可用的特定项目列表。这之所以发生,是因为JavaScript。因为你触发了一个称为“悬停”的事件,该事件为你的操作提供了某种输出。
现在我们已经了解了很多关于普通网页的知识,接下来看看需要哪些工具。
如果你想开始HTML开发,最初需要的工具之一是一个简单的文本编辑器。

我使用的是Visual Studio Code,它是互联网上可用的主流IDE之一。你也可以将其用于你的开发。
但这并非必须使用,取决于你。
HTML开发所需的第二样东西是一个Web浏览器。我使用的是Chrome,你可以选择任何你喜欢的浏览器。
这是一个典型的VS Code屏幕的样子。
我建议你也下载一个特定的插件,因为它会对你有所帮助。
这个插件或扩展叫做“Live Server”。我已经安装了它,安装后在这里看起来是这样的。
现在,你可以返回并为自己创建一个新的HTML页面。
我将使用 Ctrl + N,然后选择语言,在我的场景中是HTML。现在我使用一个快捷方式,它会为我编写一个模板。

这是我的初始模板或初始HTML模板。让我保存它。我可以将其命名为 first.html。

让我在这里写点东西:“我的第一个HTML页面”。如果我保存它并点击“Go Live”,我想你们可以看到这个。我们能够看到我们的第一个HTML页面。
如果我们进一步讨论这个特定的屏幕,你们可以看到这个页面主要由多个项目或HTML标签构成。
它们大多数都有开始和结束标签。你可以看到这是一个特定的标签,它在这里结束。这是另一个特定的标签,它在这里结束。这些标签被称为HTML标签。
通常总有一个开始标签和一个结束标签。
你可以在其中放置项目或内容。你也可以有嵌套的标签。
不同的标签有不同的含义。我们今天不会深入讨论标签。如果这对你来说听起来像天书,不用担心,我们将在以后深入讨论它们。
目前,请专注于我们如何创建一个普通的HTML页面,以及创建这个HTML页面需要哪些工具。


在本节课中,我们一起学习了Web开发的基础,特别是前端开发的起点——HTML。我们了解了HTML的历史、作用及其与CSS、JavaScript的关系。我们还实践了如何设置开发环境(使用VS Code和浏览器)并创建了第一个HTML页面。记住,HTML负责网页的结构,是构建一切网页内容的基石。
075:什么是HTML? 🌐
在本节课中,我们将要学习前端开发的基础构建块之一:HTML。我们将了解HTML的定义、历史、作用,以及它与CSS和JavaScript的关系。最后,我们将动手创建一个简单的HTML页面,并了解所需的开发工具。
上一节我们介绍了Web开发,本节中我们来看看前端开发。
对于前端开发,我们首先需要了解其一些基础构建块,其中之一就是HTML。
如今,我们看到的任何现代网站都直接或间接地使用HTML。进一步了解HTML,我们知道HTML代表超文本标记语言,由蒂姆·伯纳斯-李于1993年创建。
可以说,HTML与互联网本身一样古老。
它是随着互联网一同发展起来的初始语言之一,用于在互联网上提供可供消费的内容。它是一种标准的标记语言,用于设计在Web浏览器中显示的文档。
HTML是一种标记语言,而不是编程语言。
这意味着它用于管理文档的内容,规定其应如何结构化以及应如何呈现。当我们的Web浏览器消费这些HTML文档时,它就能知道应如何在屏幕上呈现文档中的项目。
关于HTML的版本,我们目前使用的是HTML5。
从HTML1到HTML5,我们看到了巨大的进步。为了举例说明,让我展示一些内容。
如果我们查看这个页面,这是亚马逊早期的页面之一。你可以看到它的样子。如果将其与当前的亚马逊网站进行比较,变化是巨大的。你可以看到这里技术的进步及其发展,以及它如何帮助一个特定的网站改变自身。
谈到任何现代网站,我们可以说它主要由三种技术构成。
以下是构成现代网站的三种核心技术:
- HTML:用于管理结构。
- CSS:用于设计。
- JavaScript:通常用于为网站提供额外的功能。
因此,可以说HTML提供了基本结构,而CSS和JavaScript等技术则对其进行增强和修改。CSS主要控制呈现、格式和布局,即事物在网页上应如何显示。而JavaScript则提供不同元素的行为。
如果你想举个例子,假设你将鼠标悬停在某个菜单项上,它会默认显示该菜单项下可用的特定项目列表。这之所以发生,是因为JavaScript。因为你触发了一个称为“悬停”的事件,而JavaScript为该事件提供了某种输出。

现在我们已经了解了很多关于普通网页的知识,接下来看看需要哪些工具。
如果你想开始HTML开发,最初需要的工具之一是一个简单的文本编辑器。
我使用的是Visual Studio Code,它是互联网上可用的主流集成开发环境之一,你也可以用它进行开发。但使用它并非必需,这取决于你。
HTML开发所需的第二样东西是一个Web浏览器。我使用的是Chrome,你可以选择任何你喜欢的浏览器。
这是一个典型的VS Code屏幕界面。我建议你也下载一个特定的插件,因为它会很有帮助。
这个插件或扩展叫做“Live Server”。我已经安装了它,安装后在这里的侧边栏会显示。现在,你可以返回并为自己创建一个新的HTML页面。
我将使用 Ctrl + N,然后选择语言为HTML。接着,我使用一个快捷方式来为我生成一个模板。

这就是我的初始HTML模板。让我保存它。我可以将其命名为 first.html。让我在这里写点东西:“我的第一个HTML页面”。

保存后,点击“Go Live”。我想你们可以看到这个。我们能够看到我们的第一个HTML页面。
如果我们进一步讨论这个特定的屏幕,你们可以看到这个页面主要由多个项目或HTML标签构成。
它们大多数都有开始和结束标签。你可以看到这是一个特定的标签,它在这里结束。这是另一个标签,它在这里结束。这些标签被称为HTML标签。
通常,总有一个开始标签和一个结束标签。你可以在它们内部放置内容或项目。你也可以有嵌套的标签。
不同的标签有不同的含义。我们今天不会深入讨论标签,如果这对你来说听起来很混乱,不用担心,我们将在后续课程中深入讨论它们。
目前,请专注于我们如何创建一个普通的HTML页面,以及创建这个HTML页面需要哪些工具。


本节课中我们一起学习了HTML的基础知识。我们了解到HTML是用于构建网页结构的标记语言,它与CSS和JavaScript共同构成了现代网页开发的三大核心技术。我们还实践了使用VS Code编辑器和浏览器创建并查看了一个简单的HTML页面。在接下来的课程中,我们将深入学习HTML的具体标签和语法。
076:HTML标签详解 🏷️

在本节课中,我们将要学习HTML文档中的核心组成部分——标签。我们将详细解析上节课编写的HTML文档中出现的各种标签,并学习如何在文档主体中使用标题和段落标签来组织内容。
文档结构标签
上一节我们介绍了HTML的基本概念和工具,本节中我们来看看构成HTML文档骨架的核心标签。
首先,我们回顾上节课编写的HTML文档及其在浏览器中的输出效果。文档中包含多个标签,我们将逐一进行解析。
从文档顶部开始,我们首先看到的是 <!DOCTYPE html> 声明。这个标签是在HTML5中引入的,用于标识文档类型。它告知浏览器当前文档是一个HTML文档,以便浏览器能正确地渲染它。
接下来是 <html> 标签。这是HTML中最基础的标签之一,用于标识HTML文档的开始与结束。如你所见,文档以 <html> 开始,以 </html> 结束,分别代表文档的开端和结尾。

在 <html> 标签内部,还有两个重要的标签用于定义文档的头部和主体:
<head>标签:用于提供关于文档的信息,例如文档的标题和一些元数据(如描述和关键词)。<body>标签:定义了文档中可见的内容。

我们可以通过浏览器来验证这一点。例如,之前写在 <body> 标签内的“My first HTML page”文本,确实显示在了浏览器窗口中。而写在 <head> 内 <title> 标签中的内容(例如“My first document”),则会显示在浏览器标签页的顶部。关于 <meta> 标签,我们将在本系列课程后续部分详细讨论。


现在,你应该对HTML标签有了基本了解,并且知道所有在 <body> 标签内编写的内容都会呈现在HTML页面上。


内容组织标签
理解了文档的基本结构后,现在让我们看看如何在 <body> 标签内使用更多HTML标签,使我们的文档在视觉上更具吸引力。
以下是两个最常用的内容组织标签:
标题标签

标题标签通常用于定义网页的标题和副标题。HTML提供了六个级别的标题标签,从 <h1> 到 <h6>,其中 <h1> 是最大的标题,<h6> 是最小的。


例如,使用 <h1> 标签可以创建最大的标题。在浏览器中,<h1> 标签内的文本会变得粗体并且字号更大。你可以尝试使用从 <h1> 到 <h6> 的所有标题标签来观察它们的不同。
段落标签

段落标签用于在HTML文档中定义文本段落。这个标签通常用于博客文章、新闻或其他需要大段文本的内容。


在HTML中,我们使用 <p> 标签来创建段落。只需将你的文本内容放在 <p> 和 </p> 之间即可。在浏览器中,每个段落通常会独占一块区域,并自动换行。
如果你想在文档中添加另一个段落并让它从新的一行开始,只需创建另一个 <p> 标签并将内容放入其中即可。




本节课中我们一起学习了HTML文档的核心结构标签(<!DOCTYPE>、<html>、<head>、<body>、<title>)以及用于组织页面内容的标题标签(<h1> - <h6>)和段落标签(<p>)。这些是构建任何网页的基础,希望你能够在自己的下一个项目中尝试使用它们。
077:HTML格式化标签 🎨
在本节课中,我们将要学习HTML中的格式化标签。这些标签用于改变网页上文本的外观,例如加粗、倾斜或添加下划线。通过掌握这些标签,你可以更好地控制网页内容的视觉呈现。

上一节我们介绍了一些基础的HTML标签及其用例。本节中,我们来看看专门用于文本格式化的标签。
加粗标签


首先,我们介绍加粗标签。加粗标签用于使文本以粗体显示。使用此标签时,只需将需要加粗的文本包裹在开始和结束的加粗标签中。

其语法结构如下:
<b>需要加粗的文本</b>

在编辑器中,如果将段落标签<p>替换为加粗标签<b>,可以观察到文本的字体粗细发生了变化。加粗标签也可以嵌套在段落标签内部使用,以实现段落内部分文本的加粗效果。


倾斜标签

接下来是倾斜标签。倾斜标签用于使文本以斜体显示。使用此标签时,只需将需要倾斜的文本包裹在开始和结束的倾斜标签中。

其语法结构如下:
<i>需要倾斜的文本</i>


在浏览器中查看,被包裹的文本会呈现为斜体。与加粗标签类似,倾斜标签也可以嵌套在段落标签内部使用。

下划线标签
现在,我们来看下划线标签。下划线标签用于为文本添加下划线。使用此标签时,只需将需要添加下划线的文本包裹在开始和结束的下划线标签中。


其语法结构如下:
<u>需要添加下划线的文本</u>

在HTML中,下划线标签使用<u>表示。保存并刷新页面后,可以看到指定文本已被添加下划线。

删除线标签
另一个重要的标签是删除线标签。删除线标签用于在文本上添加一条横线,表示文本被划掉或删除。使用此标签时,只需将需要划掉的文本包裹在开始和结束的删除线标签中。


其语法结构如下:
<s>需要划掉的文本</s>

在HTML中,删除线标签通常使用<s>表示。保存后,可以看到指定文本被成功划掉。

插入文本标签
插入文本标签用于指示被插入的文本,例如更正或补充内容。它常与删除线标签配合使用,以显示内容的更新。

其语法结构如下:
<ins>插入的文本</ins>
例如,可以先使用删除线标签划掉旧文本,然后紧接着使用插入标签添加新文本,从而清晰地展示内容的修改。

上标与下标标签
以下是用于处理数学和化学公式的标签。

上标标签使文本显示在基线之上,常用于数学公式中的指数。
其语法结构如下:
<sup>上标内容</sup>
例如,要书写“2的平方”,可以写为 2<sup>2</sup>。



下标标签使文本显示在基线之下,常用于化学分子式。
其语法结构如下:
<sub>下标内容</sub>
例如,要书写水的化学式“H₂O”,可以写为 H<sub>2</sub>O。

小型文本标签
小型文本标签用于使文本显示得比周围文本更小,通常用于脚注或免责声明。

其语法结构如下:
<small>小型文本</small>
例如,可以在页面底部使用此标签添加一行小字说明。

标记文本标签

标记文本标签用于高亮显示文本,类似于荧光笔的效果。

其语法结构如下:
<mark>需要高亮的文本</mark>
使用此标签包裹的文本会在页面上以高亮背景色显示。

强调与着重标签
最后,我们介绍两个具有语义含义的标签。
着重标签在视觉上与加粗标签相似,但它告知浏览器此文本在文档中具有重要性。
其语法结构如下:
<strong>重要的文本</strong>

强调标签在视觉上与倾斜标签相似,但它告知浏览器应以强调的语气(如加重音)来朗读此文本。
其语法结构如下:
<em>需要强调的文本</em>

虽然<strong>和<b>、<em>和<i>的视觉效果可能相同,但它们在语义上传达了不同的含义,对辅助阅读工具(如屏幕阅读器)更为友好。



本节课中我们一起学习了HTML中常用的格式化标签。你了解了如何使文本加粗、倾斜、添加下划线或添加删除线,也学会了如何标注插入内容、书写数学公式如22和H2O、添加小型文本、高亮关键内容,以及使用具有语义的<strong>和<em>标签。这些标签可以相互嵌套组合使用,为你提供了丰富的文本格式化能力。
078:表单与输入标签 📝


在本节课中,我们将要学习HTML中用于收集用户数据的核心元素:表单(<form>)和输入标签(<input>)。我们将了解如何创建表单,探索不同类型的输入控件,并学习如何通过属性来增强它们的功能。
上一节我们介绍了HTML的格式化标签,本节中我们来看看如何与用户进行交互。
表单标签(<form>)


HTML中的表单用于从用户那里收集数据并将其发送到服务器。<form>标签是网页上表单的容器,内部可以包含输入字段、按钮和下拉菜单等元素。



要使用<form>标签,需要指定action和method属性:
action:决定表单数据将被发送到哪个URL。method:指定数据如何通过请求从客户端发送到服务器(通常是GET或POST)。



以下是创建表单标签的基本结构:





<form action="/submit-data" method="POST">
<!-- 各种输入控件将放在这里 -->
</form>




输入标签(<input>)



<input>标签是一个自闭合标签,用于创建各种类型的交互式表单控件。它拥有多个属性,如type、name、value、placeholder等,我们将在本节中学习它们。


以下是HTML中一些最常见的输入类型:
- 文本 (
type="text"): 创建可接受文本输入的表单字段。<input type="text"> - 邮箱 (
type="email"): 创建专门用于接收邮箱地址的输入字段。 - 数字 (
type="number"): 创建仅允许输入数字的字段。<input type="number"> - 密码 (
type="password"): 创建用于输入密码的字段,输入内容会被掩码(显示为圆点或星号)以保护隐私。<input type="password"> - 日期 (
type="date"): 提供一个日期选择器,让用户选择日期。<input type="date"> - 文件 (
type="file"): 允许用户选择并上传文件。 - 隐藏 (
type="hidden"): 创建一个对用户不可见的输入字段,用于在表单中传递不需要用户填写的数据。 - 提交 (
type="submit"): 创建一个提交按钮,用于将表单数据发送到服务器。<input type="submit" value="提交"> - 单选按钮 (
type="radio"): 创建单选按钮,用于让用户从多个选项中选择一项。<input type="radio" id="minor" name="age"> <label for="minor">您是未成年人吗?</label> - 复选框 (
type="checkbox"): 创建复选框,允许用户选择多个选项。<input type="checkbox" id="terms"> <label for="terms">您接受条款和条件吗?</label>


为了使表单布局更清晰,可以使用<br>(换行)标签将元素放置到新的一行。


输入标签的常用属性


输入标签的属性为其提供了额外的功能和约束。以下是几个关键属性:



value: 设置输入控件的默认值。<input type="text" value="初始值">placeholder: 在输入字段中显示提示文本(占位符),描述期望的输入内容。当用户开始输入时,提示文本会消失。<input type="text" placeholder="请输入您的姓名">readonly: 使输入字段变为只读,用户可以看到其值但无法编辑。<input type="text" value="只读文本" readonly>required: 指定在提交表单之前必须填写此输入字段,确保收集到必要的数据。name: 用于在表单提交时标识输入控件,这对服务器端处理表单数据至关重要。<input type="text" name="username">max/min: 对于type="number"的输入,设置允许输入的最大值和最小值。<input type="number" min="5" max="10">




其他表单元素:下拉列表 (<select>)


除了<input>,表单中还可以使用<select>标签来创建下拉列表。它内部包含多个<option>标签来定义各个选项。




<select>
<option value="red">红色</option>
<option value="blue">蓝色</option>
<option value="green">绿色</option>
</select>






本节课中我们一起学习了HTML表单的基础知识。我们了解了如何用<form>标签创建表单,探索了多种<input>类型(如文本、密码、日期、单选按钮等)及其关键属性(如placeholder、required、name),还简单介绍了<select>下拉列表。建议你在自己的项目中尝试使用这些元素来构建交互式表单。
079:iframe标签详解 🖼️
在本节课中,我们将要学习HTML中的<iframe>标签。上一节我们介绍了不同的输入类型,本节中我们来看看如何使用<iframe>标签在网页中嵌入其他网页或内容。
<iframe>标签是一个强大的工具,用于将来自另一个网页或网站的内容嵌入到你自己的网页中。在本视频中,你将探索<iframe>的各种用途,以及它如何使你的网站受益。
什么是 <iframe> 标签?
让我们从讨论<iframe>标签是什么以及它如何工作开始。
<iframe>标签代表内联框架,用于在网页内创建一个窗口或容器,该容器可以显示来自另一个来源的内容。<iframe>标签有几个属性,包括src属性,该属性用于指定要在<iframe>内显示的内容的URL。

<iframe>标签的主要用途是将一个网站的内容嵌入到你自己的网站中。例如,你可能希望将YouTube视频或Google地图嵌入到你的网站中。通过使用<iframe>标签,你可以轻松地将其他网页的内容嵌入到你自己的网页中。
如何创建和使用 <iframe>
让我向你展示如何做到这一点。
以下是创建<iframe>的基本代码结构:

<iframe src="URL"></iframe>

现在,我们有了代码编辑器,我将向你们展示如何创建一个<iframe>。要创建一个<iframe>,我们只需要放置<iframe>标签。如果我现在保存并在这里显示输出,这就是我们的<iframe>。
为了在这个<iframe>中打开一个网站,我们需要使用src属性。我们可以在这个src属性中放入一个URL,该URL将显示在这里。

例如,我复制了URL https://www.bing.com 并粘贴到src属性中。现在你们可以看到,我们能够在当前网页中打开它。

我们还有一个名为width的属性,我可以调整<iframe>的宽度。例如,设置width="50%"。你可以看到它变成了50%的宽度。

现在我们已经增加了宽度,我们也可以为<iframe>增加高度。我们可以通过放置height属性来实现。你可以看到高度增加了。让我们尝试增加更多。
嵌入视频示例
这里我们已经使用了<iframe>。让我也展示一下如何使用<iframe>嵌入视频。
为了做到这一点,让我打开YouTube。这是一个YouTube视频。我可以点击“分享”,然后选择“嵌入”。YouTube默认会为我们提供一个带有其嵌入URL的<iframe>代码,以便在我们的任何网站上显示。
如果我把这段代码复制粘贴到这里,你可以看到我们能够在这里观看这个视频。
<iframe> 的常见用例
现在,我们已经看到了如何使用<iframe>标签,让我们看看它的一些用例。
以下是<iframe>标签的一些用例:
- 创建独立区域或页面:你可以使用
<iframe>标签在你自己的网站内创建一个独立的区域或页面。如果你想创建一个专门针对特定主题的页面,或者想创建一个为特定受众设计的页面,这会很有帮助。 - 展示广告:你可以使用
<iframe>来展示来自第三方广告网络的广告。 - 创建图片库或幻灯片:你可以使用
<iframe>在你自己的网页内创建图片库或幻灯片。这是展示你的产品或服务,或为用户创建交互式演示的好方法。 - 嵌入社交媒体动态:你可以使用
<iframe>嵌入来自不同社交媒体平台(如Twitter、Facebook或Instagram)的动态。这是在你的网站上展示社交媒体存在的好方法。
<iframe> 的潜在问题与安全考量


现在我们已经看到了一些用例,但<iframe>也存在一些潜在问题。出于安全原因,一些公司可能会选择在其网站上禁用<iframe>。


<iframe>可用于显示来自其他域的内容,这可能会带来安全风险。通过禁用<iframe>,公司可以降低安全漏洞的风险,并确保其网站上有更好的用户体验。
让我用一个例子来展示。如果我访问https://www.google.com,复制URL,并将其放入<iframe>的src属性中,那么你将能够看到我们无法访问它。这就是为什么一些公司已禁用从其网站通过<iframe>访问的原因。
在本系列课程的后面,我们将学习如何在我们自己的网站上实现同样的限制。
总结

本节课中我们一起学习了<iframe>标签。在本视频中,我们看了一些关于如何使用<iframe>的例子以及它的一些潜在风险。凭借其多功能性和灵活性,有无数种方法可以使用<iframe>标签来增强你网站的功能和用户体验。希望你能尝试在你自己的HTML代码中使用它们。
080:图像标签详解 📸
在本节课中,我们将要学习HTML中的图像标签(<img>)。图像是网站的重要组成部分,它允许你向用户展示视觉内容。我们将探讨图像标签的不同用法及其如何使你的网站受益。
上一节我们介绍了<iframe>标签,本节中我们来看看如何在网页中嵌入图像。
什么是图像标签?
图像标签,也称为<img>标签,用于将图像嵌入到网页中。它是一个自闭合标签,这意味着它不需要单独的结束标签。
图像标签有几个重要的属性,主要包括:
src属性:用于指定图像的URL(来源)。alt属性:用于提供图像的描述性文本。
图像标签的一个主要用途是在电子商务网站上展示产品图片。通过使用图像标签,你可以轻松展示高质量的产品图像,这有助于增加销量并改善用户体验。

如何使用图像标签嵌入图像?
以下是使用图像标签将图像嵌入网页的基本步骤。
- 编写基础标签:使用
<img>标签,它是一个自闭合标签。 - 指定图像源:通过
src属性设置图像的URL。这个URL可以是在线图片的链接。 - 添加替代文本:通过
alt属性为图像添加描述文本。当图像无法加载时,浏览器会显示这段文本。
让我们通过一个例子来具体操作。假设我们想显示一张在线图片,其URL为 https://example.com/image.jpg。
<img src="https://example.com/image.jpg" alt="用户个人资料图片">

将这段代码放入HTML文件后,刷新页面,图像就会显示出来。

图像标签的其他重要属性
除了src和alt属性,图像标签还有其他有用的属性来控制图像的显示。
以下是两个常用的属性:
width:用于设置图像的显示宽度(单位通常是像素)。height:用于设置图像的显示高度(单位通常是像素)。
你可以这样使用它们:


<img src="dog.jpg" alt="一只小狗" width="300" height="200">
这段代码会将名为“dog.jpg”的图片显示为300像素宽、200像素高,并在图片无法加载时显示“一只小狗”。
如何加载本地图像?
我们不仅可以加载网络上的图片,也可以加载存储在自己电脑上的本地图片。
加载本地图像的步骤如下:
- 确保图像文件与你的HTML文件在同一个目录(文件夹)下,或者你知道它的相对路径。
- 在
src属性中,使用相对路径来指向该图像文件。
例如,如果你的HTML文件旁边有一个名为 cat.jpg 的图片,你可以这样嵌入它:
<img src="./cat.jpg" alt="一只猫">
这里的 ./ 表示当前目录。
如果图片在一个名为 images 的子文件夹中,路径应写为 src=“images/cat.jpg”。

alt属性的重要性

让我们再次强调alt属性的作用。当图片链接失效(src的URL被篡改或错误)或者用户使用屏幕阅读器时,alt属性提供的文本就至关重要。
例如,对于上面的代码,如果cat.jpg文件被移动或删除,浏览器将无法加载图片,并在其位置显示“一只猫”这段文字,从而告知用户这里本应有什么内容。


本节课中我们一起学习了HTML图像标签(<img>)的核心用法。我们了解了如何使用src属性嵌入在线和本地图片,如何用width和height控制尺寸,以及为什么alt替代文本对于可访问性和用户体验必不可少。请务必在你的下一个项目中尝试使用这些知识。
081:HTML音频标签详解 🎵
在本节课中,我们将要学习HTML中的<audio>标签。这个标签允许你在网页中嵌入音频内容,就像上一节我们介绍的<video>标签用于嵌入视频一样。本节中我们来看看如何使用<audio>标签及其相关属性来增强网站的多媒体体验。
什么是音频标签? 🎧
<audio>标签,也称为音频元素,用于将音频内容嵌入到网页中。它是一个强大的工具,可以为你的网站添加背景音乐、音效或音频描述,从而提升用户体验和参与度。
音频标签的用途
以下是<audio>标签的主要用途:

- 播放音乐或音频内容:你可以轻松地为网站添加背景音乐或音频片段。
- 提供音频描述:为视频或其他多媒体内容提供音频描述,这能使你的内容对视障用户更友好,并改善整体用户体验。
如何嵌入音频

现在,让我们看看如何在HTML页面中嵌入音频。核心方法是使用<audio>标签,并在其中嵌套<source>标签来指定音频文件的来源。


以下是基本的HTML代码结构:
<audio controls>
<source src="音频文件的URL" type="audio/文件格式">
</audio>

<audio>:定义音频播放器。controls:此属性用于显示播放控件,如播放、暂停和音量调节。<source>:此标签用于指定音频源。src:此属性包含你想要嵌入网页的音频文件的URL或路径。

音频标签的关键属性


上一节我们介绍了基本结构,本节中我们来看看<audio>标签支持的一些重要属性,它们能实现不同的功能。

以下是<audio>标签的主要属性:
controls:显示播放器的控制面板(播放/暂停按钮、进度条、音量控制等)。这是最常用的属性。muted:设置音频初始状态为静音。loop:设置音频播放结束后自动重新开始,实现循环播放。autoplay:设置页面加载后自动开始播放音频(注意:许多现代浏览器出于用户体验考虑,会阻止带声音的自动播放)。
加载本地音频文件
除了从网络URL加载音频,你还可以从本地文件系统加载音频。

以下是加载本地音频文件的步骤:
- 在
<source>标签的src属性中,使用相对路径或绝对路径指向你的音频文件。 - 例如,如果你的HTML文件、视频文件夹、音频文件夹和音频文件的结构如下,路径可以这样写:
当前目录/ ├── 你的HTML文件.html └── video/ └── audio/ └── your-audio-file.mp3 - 对应的
src属性值应为:src="./video/audio/your-audio-file.mp3"。这里的./表示当前HTML文件所在的目录。
总结

本节课中我们一起学习了HTML的<audio>标签。它是一个功能强大的工具,允许你将音频内容嵌入网站,为用户创造更具沉浸感的体验。通过有效使用<audio>标签及其属性(如controls、loop、autoplay),你可以创建更具吸引力和表现力的网页。希望你现在已经理解了如何将音频标签应用到自己的HTML页面中。
082:HTML视频标签详解 🎬
在本节课中,我们将要学习HTML中的<video>标签。这是一个用于在网页中嵌入视频内容的强大工具。我们将探讨它的基本语法、核心属性以及如何从不同来源加载视频。
上一节我们介绍了如何使用<img>标签嵌入图片,本节中我们来看看如何在网页中嵌入和控制视频播放。
什么是视频标签?
<video>标签,也称为视频元素,用于将视频内容嵌入到网页中。它拥有多个属性,可以控制视频的播放、尺寸和交互方式。

视频标签的核心属性
以下是<video>标签的一些关键属性及其作用:
src:此属性用于指定视频文件的URL。它通常用在嵌套的<source>标签内。controls:此属性用于在视频下方显示播放控制条,包括播放/暂停按钮、音量控制和进度条。width与height:这两个属性用于设置视频播放器的宽度和高度。其值可以是像素值(如300)或百分比(如50%)。loop:此属性使视频在播放结束后自动重新开始播放。muted:此属性设置视频在加载时默认处于静音状态。autoplay:此属性使视频在页面加载后自动开始播放(注意:许多现代浏览器会阻止带声音的自动播放)。


在HTML页面中集成视频

现在,让我们看看如何将视频集成到我们的HTML页面中。

首先,我们使用<video>标签。在这个标签内部,我们通常放置一个<source>标签来指定视频源。

基础代码结构如下:
<video controls>
<source src="视频文件的URL或路径" type="video/mp4">
</video>


示例1:嵌入网络视频

假设我们有一个在线视频的URL,可以按以下方式嵌入:

<video controls width="600">
<source src="https://example.com/path/to/your-video.mp4" type="video/mp4">
</video>
添加controls属性后,页面上将显示视频播放器,用户可以进行播放、暂停、调整音量等操作。

示例2:嵌入本地视频
如果视频文件存储在本地项目文件夹中,我们可以使用相对路径来引用它。
假设项目结构如下:
你的项目文件夹/
├── index.html
└── videos/
└── test-video.mp4

在index.html中,可以这样嵌入本地视频:
<video controls width="600" height="400">
<source src="./videos/test-video.mp4" type="video/mp4">
</video>
这里的./代表当前文件夹(即index.html所在的目录),./videos/test-video.mp4则指向具体的视频文件。

使用其他属性
我们可以组合使用上述属性来增强视频体验。例如,创建一个自动播放、循环播放且默认静音的视频背景:
<video autoplay loop muted width="100%">
<source src="./videos/background-loop.mp4" type="video/mp4">
</video>


本节课中我们一起学习了HTML <video>标签的用法。<video>标签是网页开发者和设计师的强大工具,它允许你将视频内容无缝集成到网站中。通过有效地使用controls、loop、autoplay等属性,你可以为用户创造更具吸引力、沉浸感更强的视觉体验,从而有效提升用户参与度和网站的整体表现。
083:列表标签详解 📋
在本节课中,我们将学习HTML中的列表标签。列表标签是一个强大的工具,它允许你以结构化和视觉上吸引人的方式组织和呈现信息。我们将探讨列表标签的不同属性、功能以及它如何使你的网站受益。
在上一节视频中,我们了解了音频标签及其在HTML页面中的嵌入方法。本节中,我们来看看列表标签。
列表标签,也称为列表元素,用于在网页上创建项目列表。列表标签有几个属性,包括用于定义列表类型(有序或无序)的 type 属性,以及用于定义有序列表起始编号的 start 属性。
列表标签的一个主要用途是在网页上创建项目列表,例如产品、服务或功能列表。通过使用列表标签,你可以创建一个结构清晰、视觉美观的列表,这有助于提升用户体验和参与度。
HTML中可以创建不同类型的列表标签。让我们逐一了解它们。
有序列表
第一种列表类型是有序列表,我们也可以称之为 OL 元素。这种列表用于创建具有特定顺序或序列的项目列表。有序列表标签通常用于创建带编号的信息列表,例如分步指南或教程。
让我们创建一个有序列表,看看它在HTML页面中的样子。
以下是一个HTML文档示例,我们将创建一个有序列表:

<h2>有序列表</h2>
<ol>
<li>苹果</li>
<li>芒果</li>
<li>橙子</li>
</ol>
在浏览器中,这个列表的显示效果如下:

正如我们所见,这是一个有序列表,我们可以看到数字序列:1, 2, 3。
以上就是关于有序列表的内容。现在让我们看看无序列表。

无序列表
第二种列表标签类型是无序列表,或者可以称之为 UL 元素。这种列表用于创建没有特定顺序或序列的项目列表。无序列表标签通常用于创建带项目符号的信息列表,例如功能或优势列表。
让我们看看 UL 标签在我们的HTML页面中是如何工作的。

在我们的HTML文档中,紧接在有序列表下方,我们将创建一个无序列表:

<h2>无序列表</h2>
<ul>
<li>知识渊博</li>
<li>简明扼要</li>
<li>从基础到进阶</li>
</ul>
现在,如果在浏览器中显示,你可以看到:

总结


本节课中,我们一起学习了HTML中的列表标签。我们首先了解了列表标签的基本概念和作用,然后详细探讨了两种主要的列表类型:有序列表(<ol>)和无序列表(<ul>)。通过具体的代码示例,我们看到了如何使用 <li> 标签在两种列表中创建列表项,以及它们在浏览器中的最终呈现效果。列表是组织网页内容的有效工具,掌握它们对构建结构清晰的页面至关重要。
084:表格标签详解
在本节课中,我们将要学习HTML中的表格标签。表格是网页上以结构化、有组织的方式呈现数据和信息的重要工具。
上一节我们介绍了HTML列表,本节中我们来看看如何使用HTML创建和定制表格。
表格的基本结构
要创建HTML表格,需要使用<table>标签。在<table>标签内部,可以定义多个其他标签来构建表格的结构。

以下是构成表格的核心标签:
<thead>:定义表格的头部,通常包含列标题。<tbody>:定义表格的主体,包含实际的数据行。<tfoot>:定义表格的页脚,通常包含汇总信息。<tr>:在<thead>、<tbody>或<tfoot>内部使用,用于定义一行。<td>:在<tr>内部使用,用于定义标准的数据单元格。<th>:在<tr>内部使用,用于定义表头单元格,通常替代<td>用于标题列,浏览器会默认将其内容加粗并居中显示。
创建一个简单的表格
现在,让我们使用这些标签来创建一个HTML页面上的表格。
首先,我们使用<table>标签。在其内部,我们创建表格的三个主要部分:头部(<thead>)、主体(<tbody>)和页脚(<tfoot>)。
<table>
<thead>
</thead>
<tbody>
</tbody>
<tfoot>
</tfoot>
</table>
此时保存并查看浏览器,不会看到任何内容,因为我们还没有在表格中添加行和列。


让我们从头部开始。在<thead>内部,我们使用<tr>创建一行。假设我们的表格要展示“国家”和“识字率”,因此需要两个列标题。我们使用<th>标签来创建这两个标题单元格。
<table>
<thead>
<tr>
<th>国家</th>
<th>识字率</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
</tfoot>
</table>
现在,我们为表格主体添加数据。在<tbody>内部,我们创建多个<tr>行,每行包含两个<td>数据单元格。


<tbody>
<tr>
<td>印度</td>
<td>80%</td>
</tr>
<tr>
<td>美国</td>
<td>75%</td>
</tr>
<tr>
<td>埃及</td>
<td>60%</td>
</tr>
</tbody>
最后,我们在页脚(<tfoot>)添加一行汇总信息。
<tfoot>
<tr>
<td>世界</td>
<td>76%</td>
</tr>
</tfoot>


使用属性定制表格

除了基本标签,还可以使用属性来定制表格的外观和行为。

例如,border属性可以为表格添加边框。

<table border="1">


cellspacing属性可以控制单元格之间的间距。将其设置为0可以合并边框间的空隙,设置更大的值则会增加间距。

<table border="1" cellspacing="10">


align属性可以设置单元格内内容的水平对齐方式(如左对齐、居中、右对齐)。注意,<th>单元格的内容默认是居中的。


<td align="center">印度</td>

我们可以为所有<td>标签添加此属性,使其内容也居中显示。

<tr>
<td align="center">印度</td>
<td align="center">80%</td>
</tr>

总结
本节课中我们一起学习了HTML表格。表格是在网站上结构化展示数据和信息的强大工具。
我们掌握了表格的基本结构,包括<table>、<thead>、<tbody>、<tfoot>、<tr>、<td>和<th>标签的用法。我们还学习了如何使用border、cellspacing和align等属性来初步调整表格的样式。

通过组合使用表格标签及其属性,你可以创建出符合特定需求的表格。后续我们还可以使用CSS来进一步美化和定制表格的外观。希望你能理解表格的构成,并在自己的网页中实践应用。
085:语义化与非语义化标签 🏷️
在本节课中,我们将学习HTML中的语义化标签与非语义化标签。我们将探讨它们各自的定义、用途、区别以及在实际开发中的重要性。
概述
在HTML中,我们见过不同类型的标签,也了解了它们各自的用例。本节我们将从讨论在网页开发中使用语义化标签的重要性及其优势开始。我们也将探讨非语义化标签的概念,并区分两者。最后,我们将提供一些语义化和非语义化标签的常见示例。
语义化标签
语义化标签旨在为其包含的内容提供意义和上下文。它们帮助搜索引擎和辅助技术理解内容的结构和目的,从而提升网站的可访问性和搜索引擎优化。
以下是一些常用的语义化标签:
<header>: 定义文档或区域的页眉。<nav>: 定义导航链接部分。<main>: 定义文档的主要内容。<article>: 定义独立、完整的内容块。<section>: 定义文档中的节或段。
这些标签传达了其所包含内容的目的和角色,使得开发者、浏览器和用户更容易理解和与网页交互。

非语义化标签
上一节我们介绍了语义化标签,本节中我们来看看非语义化标签。非语义化标签不传达任何特定的含义或上下文。它们通常用于样式设计或作为通用容器。
以下是一些常见的非语义化标签示例:
<div>: 通用块级容器。<span>: 通用行内容器。<p>: 段落标签。


虽然它们在组织和格式化内容方面有其用途,但它们缺乏语义化标签所提供的内在含义和结构。
标签实例解析
现在,让我们通过一些实例来看看语义化和非语义化标签的实际应用。
语义化标签示例:<form>
<form> 标签用于在HTML页面中创建表单。它内部通常包含特定的输入标签。
<form>
<input type="text" placeholder="用户名">
<input type="password" placeholder="密码">
<button type="submit">提交</button>
</form>


当浏览器或搜索引擎的爬虫程序访问这个页面时,<form> 标签能明确告知它们:“这是一个表单区域”。这有助于技术理解页面结构。同理,<h1> 到 <h6> 等标题标签能清晰地标示内容的层级关系。
非语义化标签示例:<div> 与 <span>
<div> 是一个通用的容器标签,其内部可以包含任何内容,如图片、文本甚至整个表单。
<div>
<img src="image.jpg" alt="示例图片">
<p>这是一段文字。</p>
</div>
<div> 标签本身不提供任何关于其内部内容的语义信息,它仅仅表示“这是代码的一个区块”。类似地,<span> 是一个行内容器。即使是 <p> 标签,也主要表示这是一个段落,但段落的具体重要性或内容类型需要借助其他标签(如 <strong> 或 <em>)来进一步明确。
<p>这是一个非常重要的<strong>概念</strong>。</p>

总结

本节课中,我们一起学习了HTML中语义化标签与非语义化标签的核心区别。语义化标签(如 <header>、<nav>、<form>)为内容赋予明确含义,有助于SEO和可访问性。而非语义化标签(如 <div>、<span>)主要用作通用容器,用于布局和样式控制。理解并恰当地使用这两类标签,将使你的网页结构更清晰,更易于维护和被机器理解。希望你能在下一个项目中合理地运用它们。
086:CSS基础入门
在本节课中,我们将要学习CSS的基础知识。CSS全称为层叠样式表,用于为HTML文档添加样式和格式,使其更具视觉吸引力且更易于阅读。我们将了解CSS是什么、它如何工作以及其基本语法。课程还将涵盖CSS选择器,这些选择器用于定位特定的HTML元素并对其应用样式。我们将学习基于元素类型、类和ID进行选择的简单选择器,以及基于元素间关系进行选择的组合选择器。
什么是CSS?
CSS代表层叠样式表。它是一种样式表语言,用于描述HTML或XML文档的呈现方式,包括颜色、布局和字体等。CSS旨在将文档的内容与其表现形式分离,从而提高内容的可访问性,并提供更多的灵活性和控制力。
CSS如何工作?
CSS通过规则集来工作。每个规则集由一个选择器和一个声明块组成。选择器指定规则将应用于哪些HTML元素。声明块包含一个或多个用分号分隔的声明。每个声明包括一个CSS属性和一个值,用冒号分隔。
基本语法示例:
选择器 {
属性: 值;
}
CSS选择器
选择器是CSS的基石,用于“选择”你想要样式化的HTML元素。以下是主要的选择器类型。
简单选择器
简单选择器基于元素的名称、ID或类直接选择元素。
- 元素选择器:根据元素名称选择HTML元素。
- 例如:
p { color: blue; }会选择所有<p>段落元素并将其文本颜色设置为蓝色。
- 例如:
- ID选择器:使用HTML元素的
id属性来选择特定元素。ID在一个页面中应是唯一的。- 例如:
#header { background-color: gray; }会选择id="header"的元素。
- 例如:
- 类选择器:选择具有特定
class属性的元素。类可以被多个元素共享。- 例如:
.center { text-align: center; }会选择所有class="center"的元素。
- 例如:
组合选择器
组合选择器用于基于元素之间的特定关系来选择元素,提供了更精确的控制。
- 后代选择器(空格):选择位于指定元素内部的所有后代元素。
- 例如:
div p { background-color: yellow; }会选择所有在<div>元素内部的<p>元素。
- 例如:
- 子元素选择器(
>):选择作为指定元素直接子元素的所有元素。- 例如:
div > p { background-color: yellow; }只会选择作为<div>直接子元素的<p>元素。
- 例如:
- 相邻兄弟选择器(
+):选择紧接在另一指定元素后的元素,且二者有相同的父元素。- 例如:
h1 + p { font-weight: bold; }会选择紧跟在<h1>元素后的第一个<p>元素。
- 例如:
- 通用兄弟选择器(
~):选择指定元素之后的所有兄弟元素。- 例如:
h1 ~ p { color: red; }会选择所有在<h1>元素之后且与其同级的<p>元素。
- 例如:

应用CSS到HTML
要将样式应用到HTML文档,主要有三种方法:
- 内联样式:直接在HTML元素的
style属性中定义样式。此方法优先级最高,但不利于维护和复用。- 示例:
<p style="color: red;">这是一段红色文字。</p>
- 示例:
- 内部样式表:在HTML文档的
<head>部分使用<style>标签定义样式。适用于单个页面。- 示例:
<head> <style> body { background-color: lightblue; } h1 { color: navy; } </style> </head>
- 示例:
- 外部样式表:将CSS规则保存在一个独立的
.css文件中,然后在HTML文档中通过<link>标签链接。这是最推荐的方法,可以实现样式与结构的完全分离,并方便多个页面共享样式。- 在
styles.css文件中:p { font-family: Arial; } - 在HTML文件中链接:
<link rel="stylesheet" href="styles.css">
- 在
总结
本节课中,我们一起学习了CSS的基础知识。我们了解了CSS是一种用于美化HTML文档的样式表语言。我们掌握了CSS的基本语法结构,即由选择器和声明块组成的规则集。我们重点学习了两种核心选择器:用于直接选择元素的简单选择器(元素、ID、类选择器),以及用于根据元素关系进行选择的组合选择器(后代、子元素、相邻兄弟、通用兄弟选择器)。最后,我们了解了将CSS应用到HTML的三种主要方式。现在,你已经能够使用CSS选择器为你的HTML文档应用简单的样式了。
Java全栈开发:P15-02:什么是CSS 🎨
在本节课中,我们将学习CSS(层叠样式表)的基础知识,包括其定义、重要性、工作原理以及最佳实践。CSS是网页开发中用于控制网页视觉表现的核心技术。
在上一节中,我们介绍了HTML及其在网页开发中的重要性。本节中,我们将探讨网页开发的另一个关键元素——CSS。
CSS,全称层叠样式表,是一种样式表语言,用于描述HTML或XML文档的呈现方式。它定义了网页的视觉布局、格式和设计,例如字体、大小、颜色、边距和整体布局。CSS是网页开发的关键部分,因为它允许开发者将网页的内容和结构与它的表现形式分离开来。
这意味着你可以改变网站的布局和样式,而无需改动其底层的HTML结构。这使得网站的维护和更新变得更加容易,同时也提升了其可访问性和用户体验。
CSS的工作原理是通过选择器将样式与HTML元素关联起来。选择器用于识别应应用样式的HTML元素。样式则通过属性来定义,例如 font-size 或 color。
示例代码:
p {
color: blue;
font-size: 16px;
}

CSS也支持使用类和ID来为特定元素或元素组应用样式。
示例代码:
.highlight {
background-color: yellow;
}
#header {
font-weight: bold;
}
了解了CSS的基本概念后,以下是使用CSS时可以遵循的一些最佳实践。
- 使用外部CSS文件:将样式与HTML代码分离。这将改善你的文件管理。
- 使用一致的命名规范:为类和ID使用一致的命名约定。这将帮助你在整个HTML/CSS项目中保持标准。
- 使用CSS预处理器:例如SASS或LESS,以使编写和维护复杂的样式表变得更加容易。
- 使用CSS框架:例如Bootstrap或Foundation,以加速开发并确保不同网页间的一致性,因为它们能让你在短时间内轻松开发复杂功能。
本节课中,我们一起学习了CSS。CSS是网页开发的关键部分,它允许开发者定义网页的视觉布局和设计。通过将内容和结构与表现形式分离,CSS使网站的维护和更新更加容易,同时也提升了其可访问性和用户体验。通过遵循最佳实践并使用CSS框架和预处理器,开发者可以高效地创建美观且功能强大的网页。


希望你能在你的下一个项目中使用CSS。我们下一个视频再见。
088:CSS语法详解 🎨
在本节课中,我们将深入学习CSS的语法结构。我们将了解CSS规则的基本组成部分,并通过一个具体的例子来演示如何将样式应用到HTML元素上。
概述
在上一节视频中,我们讨论了CSS是什么以及它在网页开发中的重要性。本节中,我们将深入探讨CSS,并详细讲解其语法规则。
CSS语法结构
CSS使用简单的语法来为HTML元素定义样式。一条CSS规则主要由三个部分组成:选择器、属性和值。
- 选择器:用于标识需要应用样式的HTML元素。
- 属性:定义要更改的样式属性,例如字体大小或颜色。
- 值:为属性指定具体的设置。
其基本语法格式如下:
selector {
property: value;
}
CSS选择器类型
CSS支持多种类型的选择器,用于精确地定位页面元素。
以下是几种常见的选择器:
- 元素选择器:将样式应用于特定类型的所有元素,例如所有的段落标签(
p)。 - 类选择器:将样式应用于所有具有特定
class属性的元素。 - ID选择器:将样式应用于具有特定
id属性的单个元素。 - 属性选择器:将样式应用于具有特定属性的元素,例如所有包含
target属性的锚点标签(a)。
在本系列课程后续内容中,我们将详细学习这些不同的选择器。

CSS属性与值
CSS支持非常广泛的属性,用于控制元素的各个方面。
以下是一些常用的CSS属性示例:
font-size: 控制字体大小。color: 设置文本颜色。background-color: 设置背景颜色。margin: 控制元素的外边距。padding: 控制元素的内边距。
每个属性都有其对应的值,例如一个具体的像素值(如16px)或颜色代码(如blue或#0000FF)。
实践示例:为链接添加背景色
现在,让我们通过一个例子来实践所学的CSS知识。假设我们想为页面中所有的锚点链接(<a>标签)添加一个蓝色的背景。
以下是如何在HTML页面中实现这一效果:
- 我们有一个包含一些标题(
<h1>)和链接(<a>)的HTML页面。 - 为了添加样式,我们在HTML的
<head>部分或元素内部使用<style>标签。 - 在
<style>标签内,我们编写CSS规则。由于目标是所有<a>标签,我们使用元素选择器a。 - 我们设置属性
background-color的值为blue。

具体的CSS代码如下:
a {
background-color: blue;
}
这段代码的含义是:我们使用了选择器a,并在花括号{}内定义了属性background-color及其值blue。这将把所有锚点标签的背景颜色改为蓝色。
应用此样式后,页面中所有链接的背景都将显示为蓝色。你可以尝试在自己的HTML页面中实现各种CSS属性,以加深理解。
总结

本节课中,我们一起学习了CSS的核心语法结构,包括选择器、属性和值。我们了解了不同类型的选择器及其用途,并通过一个为链接添加背景色的实例,掌握了编写和应用基础CSS规则的方法。建议你亲自动手尝试,为页面元素应用不同的样式。我们下节课再见。
089:CSS 简单选择器 🎯
在本节课中,我们将要学习 CSS 中的简单选择器。选择器是 CSS 的核心,它决定了样式规则将应用到哪些 HTML 元素上。通过掌握简单选择器,你将能够精确地控制网页的外观。
上一节我们介绍了 CSS 的基本语法,本节中我们来看看如何通过不同的选择器来定位元素。
概述

简单选择器是一种根据元素的标签名、类名或 ID 来匹配单个元素的选择器。它们是最基础也是最常用的选择器类型。我们将逐一探讨四种主要的简单选择器:标签选择器、类选择器、ID 选择器和通用选择器。

1. 标签选择器

标签选择器通过 HTML 元素的标签名称来匹配所有该类型的元素。
其语法非常简单,直接使用标签名作为选择器即可。例如,要为所有段落 <p> 设置样式,选择器就是 p。


以下是一个使用标签选择器的代码示例:
a {
background-color: blue;
}
在上一个视频中,我们创建了一个 HTML 页面,其中包含 <h1> 和 <h2> 标签。当我们使用 a 作为选择器并应用上述 CSS 时,所有 <a>(锚点)标签的背景色都会变为蓝色。

我们也可以将此规则应用于其他标签。例如,将选择器改为 h2,那么所有 <h2> 标签的背景色会改变。如果改为 body,则整个页面的背景色会发生变化。

2. 类选择器
类选择器用于匹配所有具有特定 class 属性的 HTML 元素。类(Class)代表一组元素,因此一个类可以包含多个元素。对该类应用的样式会作用于所有使用该类的元素。

类选择器的语法是在类名前加上一个点 .。
以下是如何使用类选择器的示例。假设我们有一些 <h2> 标签,我们希望其中特定的几个拥有红色背景。
首先,为这些 <h2> 标签添加一个类名,例如 red-background:

<h2 class="red-background">标题1</h2>
<h2 class="red-background">标题2</h2>

然后,在 CSS 中这样定义样式:
.red-background {
background-color: red;
}
这样,所有具有 class="red-background" 的元素的背景色都会变为红色。类选择器的优势在于它可以轻松地为一组元素统一样式。

3. ID 选择器
ID 选择器用于匹配具有特定 id 属性的单个 HTML 元素。id 是元素的唯一标识符,每个元素的 id 在文档中应该是独一无二的。因此,ID 选择器用于定位并样式化某个特定的元素。
ID 选择器的语法是在 ID 名前加上一个井号 #。
让我们看一个例子。假设我们有一个额外的 <h2> 标签,并且只想改变它的文字颜色。
首先,为该标签赋予一个唯一的 ID,例如 change-color:

<h2 id="change-color">这个标题颜色会变</h2>

然后,在 CSS 中针对这个 ID 设置样式:

#change-color {
color: white;
}

这样,只有这个特定的 <h2> 标签的文字颜色会变为白色。ID 选择器提供了最精确的定位方式。
4. 通用选择器

通用选择器可以匹配页面上的所有 HTML 元素。它使用星号 * 表示。

通用选择器通常用于设置一些全局的、基础的样式。例如,我们可以设置整个文档的默认文字颜色:

* {
color: brown;
}

这会将页面上所有元素的文字颜色初始设置为棕色。需要注意的是,其他更具体的选择器(如 ID 选择器或类选择器)的样式规则会覆盖通用选择器的规则,因为 CSS 遵循层叠和优先级规则。
总结
本节课中我们一起学习了 CSS 的四种简单选择器:
- 标签选择器:通过标签名匹配元素,例如
p {}。 - 类选择器:通过类名匹配一组元素,例如
.className {}。 - ID 选择器:通过唯一 ID 匹配单个元素,例如
#idName {}。 - 通用选择器:匹配所有元素,例如
* {}。


简单选择器是精确控制网页样式的基础工具。通过组合使用它们,开发者可以轻松创建美观且功能完善的网页。建议你亲自动手实践这些选择器,以加深理解。我们下节课再见!
090:CSS组合选择器(第一部分)🎨
概述
在本节课中,我们将要学习CSS中的组合选择器。组合选择器是一种通过组合多个简单选择器来更精确地定位网页元素的方法,它允许开发者创建更具体、更有针对性的样式。
从简单选择器到组合选择器
上一节我们介绍了CSS中的简单选择器。本节中,我们来看看如何组合它们以创建更强大的选择规则。
组合选择器,也称为关系选择器,通过描述元素之间的层级或相邻关系来定位目标元素。当单个选择器无法轻松选中所需元素时,组合选择器就非常有用。

以下是几种主要的组合选择器类型,我们将逐一探讨。
后代选择器
后代选择器用于匹配作为另一个元素后代的元素。这意味着,只要目标元素嵌套在指定的祖先元素内部,无论嵌套多深,都会被选中。
语法公式:
祖先元素 后代元素 {
样式声明;
}


让我们通过代码示例来理解。假设我们有以下HTML结构:
<div>
<a href="#">我是div内的链接1</a>
<section>
<a href="#">我是div内的链接2(嵌套更深)</a>
</section>
</div>
<a href="#">我是div外的链接</a>
如果我们只想改变<div>内部所有<a>标签的颜色,而不影响外部的链接,可以使用后代选择器。
CSS代码示例:
div a {
color: red;
}

应用此规则后,只有位于<div>内部的两个链接会变成红色,而外部的链接保持不变。这证明了后代选择器会选择所有层级的后代元素。
子选择器
子选择器与后代选择器类似,但更为严格。它只匹配作为另一个元素直接子元素的元素。

语法公式:
父元素 > 子元素 {
样式声明;
}

为了理解区别,让我们修改之前的例子。考虑以下结构:
<div>
<a href="#">直接子链接1</a>
<a href="#">直接子链接2</a>
<section>
<a href="#">孙子链接(非直接子元素)</a>
</section>
</div>
如果我们使用子选择器:
div > a {
color: blue;
}
应用此规则后,只有前两个作为<div>直接子元素的<a>标签会变成蓝色。嵌套在<section>内的第三个链接不会被选中,因为它不是<div>的直接子元素。

本节总结

本节课中我们一起学习了CSS组合选择器的前两种类型:后代选择器和子选择器。
- 后代选择器(空格):选择指定祖先元素内的所有后代元素,无论嵌套深度。
- 子选择器(
>):仅选择指定父元素的直接子元素。


理解这两种选择器的区别对于编写精确的CSS规则至关重要。在下一节视频中,我们将继续探讨另外两种组合选择器。
091:CSS组合选择器(第二部分)👨💻
在本节课中,我们将要学习CSS中另外两种组合选择器:相邻兄弟选择器和通用兄弟选择器。我们将通过实例来理解它们的工作原理和应用场景。
在上一节中,我们介绍了什么是组合选择器,并学习了后代选择器和子选择器。本节中,我们来看看另外两种基于兄弟元素关系的选择器。
相邻兄弟选择器
相邻兄弟选择器用于选择紧接在另一个元素之后的同级元素。其语法是:第一个选择器 + 第二个选择器。它只会选择第一个选择器之后紧接着出现的那个匹配第二个选择器的兄弟元素。

以下是其工作原理的代码描述:
h1 + h2 {
color: red;
}
这段代码的意思是:选择所有紧跟在 h1 元素之后的 h2 元素,并将它们的文字颜色设置为红色。


让我们通过一个例子来理解。假设我们的HTML结构如下:
<h1>标题1</h1>
<h2>标题2-1</h2>
<h2>标题2-2</h2>
<h1>另一个标题1</h1>
<h2>标题2-3</h2>
<h2>标题2-4</h2>
应用 h1 + h2 { color: red; } 规则后,只有“标题2-1”和“标题2-3”会变成红色,因为它们是各自紧跟在 h1 后面的第一个 h2 元素。

通用兄弟选择器


通用兄弟选择器用于选择某个元素之后的所有同级元素。其语法是:第一个选择器 ~ 第二个选择器。它会选择第一个选择器之后所有匹配第二个选择器的兄弟元素。
以下是其工作原理的代码描述:
h1 ~ h2 {
color: blue;
}
这段代码的意思是:选择所有在 h1 元素之后出现的 h2 兄弟元素,并将它们的文字颜色设置为蓝色。
使用与上面相同的HTML结构,应用 h1 ~ h2 { color: blue; } 规则后,“标题2-1”、“标题2-2”、“标题2-3”和“标题2-4”都会变成蓝色。因为它选择了每个 h1 之后的所有 h2 兄弟元素。

以下是两种选择器的核心区别总结:
- 相邻兄弟选择器 (
+):只选择紧邻的、下一个匹配的兄弟元素。 - 通用兄弟选择器 (
~):选择之后所有匹配的兄弟元素。


总结
本节课中我们一起学习了CSS组合选择器的另外两个重要成员。我们探讨了相邻兄弟选择器,它用于精确选择紧跟在特定元素后的第一个兄弟元素;也学习了通用兄弟选择器,它可以选择特定元素之后的所有指定兄弟元素。


通过组合使用后代选择器、子选择器、相邻兄弟选择器和通用兄弟选择器,开发者可以创建出高度精准、易于维护的网页样式。希望你能在下一个项目中灵活运用它们。
092:伪类与伪元素选择器
在本节课中,我们将学习CSS中两种强大的选择器:伪类选择器和伪元素选择器。伪类选择器允许你根据元素的特定状态(如悬停、点击或获得焦点)来应用样式。伪元素选择器则让你能够选择和样式化元素的特定部分,例如段落的首字母或在元素前后插入内容。掌握这些选择器将极大地提升你网页的用户界面和功能。
伪类选择器
上一节我们介绍了CSS选择器的基本概念,本节中我们来看看伪类选择器。伪类选择器用于定义元素的特殊状态。它们通常以冒号(:)开头。
以下是常见的伪类选择器及其应用场景:
:hover:当用户将鼠标指针悬停在元素上时应用样式。- 代码示例:
a:hover { color: red; }
- 代码示例:
:active:当元素(如链接或按钮)被激活(例如被点击)时应用样式。- 代码示例:
button:active { background-color: blue; }
- 代码示例:
:focus:当元素获得焦点(例如通过键盘Tab键选中或鼠标点击输入框)时应用样式。- 代码示例:
input:focus { border-color: green; }
- 代码示例:
:first-child:选择作为其父元素第一个子元素的元素。- 代码示例:
li:first-child { font-weight: bold; }
- 代码示例:
:nth-child(n):选择作为其父元素第n个子元素的元素。- 公式示例:
tr:nth-child(2n) { background-color: #f2f2f2; }(选中所有偶数行)
- 公式示例:
通过组合使用这些选择器,你可以创建出响应式的、交互性强的用户界面。
伪元素选择器
了解了如何根据状态选择元素后,我们再来看看如何选择元素的某个部分。伪元素选择器用于样式化元素的特定部分。它们以双冒号(::)开头,但单冒号(:)也被广泛支持用于向后兼容。
以下是核心的伪元素选择器及其功能:

::before:在选定元素的内容之前插入生成的内容。- 代码示例:
p::before { content: "“"; color: gray; }
- 代码示例:
::after:在选定元素的内容之后插入生成的内容。- 代码示例:
.note::after { content: " (重要)"; font-size: smaller; }
- 代码示例:
::first-letter:选择块级元素(如段落)文本内容的第一个字母进行样式化。- 代码示例:
p::first-letter { font-size: 200%; float: left; }
- 代码示例:
::first-line:选择块级元素文本内容的第一行进行样式化。- 代码示例:
p::first-line { font-weight: bold; }
- 代码示例:
::selection:改变用户用鼠标选中的文本的样式。- 代码示例:
::selection { background-color: yellow; color: black; }
- 代码示例:
伪元素选择器 ::before 和 ::after 必须与 content 属性一起使用,即使 content 的值为空字符串(content: "";)。
总结
本节课中我们一起学习了CSS伪类与伪元素选择器。伪类选择器(如 :hover, :focus)让你能根据用户交互或元素在文档树中的位置来应用样式。伪元素选择器(如 ::before, ::first-letter)则让你能够深入到元素内部,对其特定部分进行精细的样式控制。将这些概念应用到你的网页项目中,你将能充分利用CSS强大的样式化能力,创造出更具动态感和视觉吸引力的用户体验。
093:CSS伪类选择器(第一部分)
在本节课中,我们将要学习CSS伪类选择器。伪类选择器是CSS中一个强大的工具,它允许我们根据元素的状态或特定条件来应用样式。
在上一节视频中,我们介绍了简单选择器和组合选择器。本节中,我们来看看CSS伪类选择器。
伪类选择器用于根据元素的当前状态或特定条件来选择和设置样式。例如,可以根据链接是否被访问过,或者输入框是否被鼠标悬停来设置不同的样式。
伪类选择器以冒号(:)开头,可以附加在任何元素选择器之后。CSS中有许多伪类选择器,以下是一些最常用的。
以下是几个核心伪类选择器的介绍和示例。

:hover 伪类

:hover 伪类用于在用户将鼠标悬停在元素上时设置样式。这可以用来创建交互效果,例如改变颜色或增大字体。

让我们在HTML中看看它的效果。这是一个简单的HTML页面,我们创建一个按钮。
<button>Pick me</button>
目前,在浏览器中悬停按钮没有任何效果。现在,我们希望当鼠标悬停在按钮上时,其背景色发生变化。

我们使用标签名作为选择器,并添加 :hover 伪类。

button:hover {
background-color: red;
color: white;
}

保存后,当我们悬停在按钮上时,可以看到背景颜色和文字颜色都改变了。这就是 :hover 的工作原理。
:active 伪类

在了解了悬停效果后,我们来看 :active 伪类。:active 伪类用于在元素被激活时(例如用户点击按钮或链接时)设置样式。这可以为用户提供视觉反馈。
在我们的HTML页面中,已经有了一个应用了 :hover 的按钮。现在,我们为它添加 :active 样式。

button:active {
background-color: blue;
}
应用更改后,当我们悬停按钮时,颜色变为红色。此时点击按钮,按钮被激活,CSS样式生效,背景色变为蓝色。这就是 :active 伪类。


:visited 伪类
现在让我们继续学习第三个伪类::visited。:visited 伪类用于设置用户已访问过的链接的样式。这可以用来指示用户已经浏览过哪些页面。

让我们看看它的实际应用。在我们的HTML中,我们将使用一个锚点标签(<a>),因为它用于在页面间导航。
<a href="#">Click here</a>
接下来,我们为它编写CSS。我们选择 a 标签并添加 :visited 伪类。
a:visited {
color: green;
}

保存后,我们点击“Click here”链接。点击之后,可以看到链接变成了绿色,这表示该链接指向的位置已被访问过。这就是 :visited 伪类。
:focus 伪类
现在让我们进入下一个伪类::focus。:focus 伪类用于设置当前获得焦点的元素的样式,例如通过键盘选中的输入框或按钮。

让我们看看 :focus 的实际效果。在我们的HTML中添加一个输入框。
<input type="text">

现在,我们将 :focus 伪类应用到这个输入框上。
input:focus {
background-color: yellow;
color: green;
}
这是我们的输入标签。让我们看看当它获得焦点时会发生什么。可以看到背景变成了黄色,如果我们开始输入文字,文字颜色会变成绿色。

本节课中我们一起学习了几个常用的CSS伪类选择器::hover、:active、:visited 和 :focus。它们分别用于响应鼠标悬停、元素激活、链接访问和元素获得焦点这些状态。希望你能在下一个项目中尝试使用它们。我们下个视频再见。
094:伪类选择器(第二部分)🎯
概述
在本节课中,我们将继续学习CSS伪类选择器。上一节我们介绍了 :hover、:active、:visited、:focus 等常见伪类选择器。本节中,我们将深入探讨基于元素位置进行选择的伪类选择器,特别是 :nth-child()、:first-child 和 :last-child。

使用 :nth-child() 伪类选择器
:nth-child() 伪类选择器用于根据元素在其父元素中的位置来选择元素。这可以用来为每隔一个的元素设置样式,或者基于特定位置选择元素。
以下是 :nth-child() 的基本语法:
selector:nth-child(expression) {
/* 样式规则 */
}
让我们通过一个HTML示例来看看如何使用它。假设我们有以下包含多个段落的文档:


<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
<p>5</p>
<p>6</p>
<p>7</p>
<p>8</p>
现在,我们使用 :nth-child() 为这些段落标签应用样式。

选择特定位置的元素
如果我们想只对第一个段落应用样式,可以这样写:
p:nth-child(1) {
background-color: red;
}
这样,只有标有“1”的段落背景会变成红色。

同理,选择第五个段落:
p:nth-child(5) {
background-color: red;
}
选择偶数或奇数位置的元素

:nth-child() 的强大之处在于可以使用公式。
为所有偶数位置的元素设置样式:
p:nth-child(2n) {
background-color: red;
}
参数 2n 表示选择所有第 2、4、6、8... 个元素。
为所有奇数位置的元素设置样式:
p:nth-child(2n+1) {
background-color: red;
}
参数 2n+1 表示选择所有第 1、3、5、7... 个元素。
使用更复杂的公式
我们还可以使用更复杂的表达式。例如:
p:nth-child(2n+3) {
background-color: red;
}
这个公式会选择第 3、5、7... 个元素。因为当 n=0 时,是第3个元素;n=1 时,是第5个元素,依此类推。


使用关键字

:nth-child() 也接受 odd(奇数)和 even(偶数)关键字,使代码更易读。

选择所有奇数元素:
p:nth-child(odd) {
background-color: red;
}
选择所有偶数元素:
p:nth-child(even) {
background-color: red;
}

选择间隔元素
我们还可以每隔几个元素选择一次。例如,每隔两个元素选择第三个:
p:nth-child(3n) {
background-color: red;
}
这会对第 3、6、9... 个元素应用样式。

使用 :first-child 伪类选择器

在了解了 :nth-child() 之后,我们来看看 :first-child。这个选择器用于选择父元素下的第一个子元素,可以用来将第一个元素的样式设置得与其他元素不同。
其语法很简单:
selector:first-child {
/* 样式规则 */
}
让我们在之前的HTML上应用它。注释掉之前的 :nth-child 样式,添加:
p:first-child {
background-color: yellow;
}
你会发现,所有 <p> 标签中的第一个子元素(即标有“1”的段落)背景变成了黄色。

理解作用域

:first-child 的选择是基于其父元素上下文(作用域)的。考虑以下HTML结构:
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
<div>
<p>5</p>
<p>6</p>
<p>7</p>
<p>8</p>
</div>
再次应用 p:first-child { background-color: yellow; }。
现在,标有“1”和“5”的段落背景都变成了黄色。这是因为:
- 在整个文档中,第一个
<p>是“1”。 - 在
<div>这个新的父元素作用域内,第一个<p>子元素是“5”。

使用 :last-child 伪类选择器

与 :first-child 相对应的是 :last-child 选择器,它用于选择父元素下的最后一个子元素。
语法如下:
selector:last-child {
/* 样式规则 */
}
让我们在上面的HTML示例中添加这个样式:
p:last-child {
background-color: red;
}
现在,你会发现最后一个 <p> 标签(在示例中是“8”)的背景变成了红色。它选择了所有 <p> 标签中作为其父元素最后一个子元素的那一个。


总结

本节课我们一起学习了三种基于位置的CSS伪类选择器:
:nth-child(expression):通过数字、公式(如2n+1)或关键字(odd,even)选择特定位置或模式的子元素。:first-child:选择父元素下的第一个子元素。:last-child:选择父元素下的最后一个子元素。

这些选择器能让你更精准、更高效地控制页面元素的样式,尤其是在处理列表、表格或任何重复性内容时。希望你能在下一个项目中灵活运用它们。
095:伪元素选择器 🎨
概述
在本节课中,我们将要学习 CSS 伪元素选择器。我们将了解什么是伪元素,它与伪类有何不同,并学习如何使用 ::before、::after、::first-line 和 ::first-letter 等伪元素来为网页元素的特定部分添加样式。


伪元素简介
上一节我们介绍了伪类选择器及其在 HTML 中的应用。本节中我们来看看伪元素选择器。
伪元素用于设置元素特定部分的样式,例如段落的第一个字母或第一行文本。伪元素通过双冒号 :: 来表示。
其基本语法为:
selector::pseudo-element {
property: value;
}
::before 伪元素
::before 伪元素允许你在元素内容之前插入内容。这对于添加装饰性元素(如图标或线条)非常有用。



以下是 ::before 伪元素的一个应用示例。假设我们有一个段落标签,内容为“Active”。

<p>Active</p>
我们可以通过 CSS 在其前面添加一个绿色圆点:

p::before {
content: ''; /* 内容为空,用于创建纯样式元素 */
display: inline-block;
width: 20px;
height: 20px;
background-color: green;
border: none;
border-radius: 50px;
}



代码解释:
content: '';:这是::before和::after伪元素的必需属性。即使内容为空,也需要声明。display: inline-block;:使伪元素以行内块级元素显示,可以设置宽高。width和height:定义了圆点的大小。background-color:设置圆点的颜色。border-radius: 50px;:将方形变为圆形。
应用后,你会在“Active”文本前看到一个绿色圆点。这个圆点实际上是插入在 <p> 标签内部,位于其内容之前。


::after 伪元素
顾名思义,::after 伪元素用于在元素内容之后插入内容。
我们继续在上一个段落的基础上,使用 ::after 添加一个“新消息”提示:
p::after {
content: 'New Message'; /* 插入文本内容 */
background-color: gray;
color: black;
opacity: 0.5;
margin-left: 10px;
padding: 2px;
}
效果:在“Active”文本后面,会出现一个半透明的灰色背景,上面写着“New Message”的标签。这模拟了聊天应用中常见的未读消息提示效果。




::first-line 与 ::first-letter 伪元素
除了插入内容,伪元素还可以用于修饰元素内已有的内容。
::first-line 伪元素
::first-line 用于设置元素内第一行文本的样式。
假设我们有一个长段落:
<p id="chat">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>


我们可以用以下 CSS 高亮其第一行:
#chat::first-line {
background-color: yellow;
}
应用后,无论浏览器窗口如何调整,只有该段落的第一行文本会显示黄色背景。


::first-letter 伪元素
::first-letter 用于设置元素内第一个字母的样式。
使用同一个段落,我们可以这样设置:
#chat::first-letter {
font-size: 2em;
font-weight: bold;
color: red;
}
应用后,段落的第一个字母“L”会变得更大、更粗且为红色,常用于创建首字下沉效果。




重要注意事项
需要记住的关键点是:伪元素并不会在页面文档对象模型(DOM)中实际添加新的 HTML 内容,它们只是通过 CSS 修改了现有内容的视觉呈现。
总结
本节课中我们一起学习了 CSS 伪元素选择器。
- 伪元素用于样式化元素内容的特定部分,使用双冒号
::表示。 ::before和::after允许在元素内容的前后插入装饰性内容。::first-line和::first-letter用于修饰元素内已有的第一行或第一个字符。- 伪元素通过 CSS 改变视觉表现,而不改变 HTML 结构。


希望你能在下一个项目中运用这些强大的样式工具。我们下个视频再见!
096:属性选择器
概述
在本节课中,我们将要学习CSS属性选择器。属性选择器是一种强大的工具,它允许我们根据HTML元素的属性或其属性值来精确地选择并应用样式。
课程内容
回顾与引入
在之前的视频中,我们已经学习了不同类型的CSS选择器,例如简单选择器、组合器、伪类和伪元素,并在我们的项目中使用了它们。

本节中,我们来看看属性选择器。属性选择器用于根据元素属性的存在或其值来选择元素。它们由方括号 [] 表示,并且可以与其他选择器结合使用,以创建更具体的样式规则。
属性选择器示例一:链接样式
以下是属性选择器的一个典型应用场景。假设我们有一个网站,其中包含多个链接。其中一些链接指向外部网站,而另一些则指向内部页面。我们希望将指向外部网站的链接显示为不同的颜色。我们可以借助属性选择器来实现这一点。

让我们看看如何实现。这是我们的HTML页面:

<a href="internal1.html">内部链接1</a>
<a href="internal2.html">内部链接2</a>
<a href="internal3.html">内部链接3</a>
<a href="https://youtube.com">YouTube</a>
<a href="https://google.com">Google</a>

页面上显示如下:

内部链接1 内部链接2 内部链接3 YouTube Google
我们希望YouTube和Google链接的颜色与众不同。为了实现这个效果,我们首先添加 <style> 标签,并在其中编写CSS。这次我们将使用属性选择器。
我们将首先写入选择器,即锚点标签 a,然后使用方括号 [] 来指定属性条件。我们知道这些链接的地址属性是 href。
a[href^="https://"] {
background-color: red;
}

这段CSS代码的意思是:选择所有 href 属性值以 "https://" 开头的 <a> 元素,并将它们的背景色设置为红色。保存后,我们可以看到YouTube和Google链接的背景色发生了变化。

这是一个属性选择器的应用示例。
属性选择器示例二:表单输入样式
属性选择器还可以用于根据特定属性的值来选择元素。让我们看另一个例子。
假设我们有一些输入框:

<input type="text">
<input type="number">
<input type="email">
<input type="password">

页面上显示为:
文本输入框 数字输入框 邮箱输入框 密码输入框
我们希望邮箱输入框看起来与众不同。我们不想将这个样式应用于所有输入框,而只应用于邮箱输入框。当然,我们可以给它添加一个不同的ID或类,但这不是我们本节要讨论的方法。我们希望使用属性选择器。
我们可以这样写CSS:

input[type="email"] {
background-color: yellow;
color: red;
}
这段代码选择了所有 type 属性值等于 "email" 的 <input> 元素,并为它们应用了黄色背景和红色文字样式。保存后,可以看到只有邮箱输入框的样式发生了变化。

这个选择器选中了所有类型为邮箱的输入元素,并对其应用了样式。
总结
本节课中,我们一起学习了CSS属性选择器。属性选择器用于根据元素属性的存在或其值来选择元素,由方括号 [] 表示。它们可以与其他选择器结合,以创建更精确的样式规则,对于定位页面上的特定元素非常有用。
希望你已经学会了如何在你的项目中使用它们,并且一定会尝试借助属性选择器来实现各种不同的功能。


我们下个视频再见。
097:CSS字体属性 🎨

在本节课中,我们将要学习CSS字体属性。文本是任何网页上的重要元素,使用CSS字体属性可以增强文本的设计感和可读性。通过恰当地使用字体,你可以使内容更具吸引力、更易于阅读,从而帮助用户在你的页面上停留更长时间。学完本视频后,你将很好地理解如何使用这些属性来提升文本的视觉效果。
上一节我们介绍了CSS定位,本节中我们来看看如何为HTML文档中的文本应用样式。
字体大小
首先,我们可以调整字体的大小。这通过 font-size 属性实现。
以下是可用的不同值:
smaller:使字体变小。large:使字体变大。x-large:使字体变得更大。
除了这些关键词,你还可以使用具体的单位,例如 50px 表示50像素。我们将在本系列课程后续部分详细讨论这些单位。
代码示例:
p {
font-size: large; /* 使用关键词 */
font-size: 20px; /* 使用像素单位 */
}

字体样式

接下来,我们看看如何调整字体样式。font-style 属性允许你指定文本是否应以斜体显示。
代码示例:
p {
font-style: italic; /* 将文本设置为斜体 */
}
字体粗细


调整字体粗细也很重要。font-weight 属性决定文本是以粗体还是正常粗细显示。
以下是可用的不同值:
400:正常粗细。700或bold:粗体。900或bolder:更粗的字体。
代码示例:
p {
font-weight: bold; /* 设置为粗体 */
font-weight: 900; /* 设置为更粗的字体 */
}


文本转换
我们还可以控制文本的大小写显示。text-transform 属性可以实现这一点。


以下是可用的不同值:


capitalize:将每个单词的首字母大写。uppercase:将所有字母转换为大写。lowercase:将所有字母转换为小写。
代码示例:
p {
text-transform: capitalize; /* 首字母大写 */
text-transform: uppercase; /* 全部大写 */
}


字体系列

最后,我们可以更改文本的字体系列。默认情况下,浏览器使用其预设字体。通过 font-family 属性,我们可以指定其他字体。

例如,设置为 monospace(等宽字体)会改变文本的外观。你可以尝试多种字体,如 Arial、Georgia、"Times New Roman" 等,以找到最适合你设计的字体。
代码示例:
p {
font-family: monospace; /* 使用等宽字体 */
font-family: Arial, sans-serif; /* 指定首选和备用字体 */
}
本节课中我们一起学习了如何使用 font-family、font-size、font-style、font-weight 和 text-transform 等CSS属性来控制网页上文本的外观,使其最符合你的需求。尝试不同的字体样式和大小,看看哪种最适合你的设计。


我们下个视频再见。🎼
098:CSS定位详解 🎯
在本节课中,我们将要学习CSS定位。CSS定位允许你控制网页上元素的位置,决定了元素如何被放置在页面上以及如何呈现给用户。

上一节我们介绍了属性选择器,并在HTML文档中使用了它们。本节中,我们来看看CSS定位的不同类型。

静态定位


静态定位是所有HTML元素的默认定位方式。具有静态定位的元素按照文档的正常流进行定位,无法通过CSS移动或重新定位。
以下是如何静态定位一个元素:


div {
position: static;
}


相对定位

相对定位允许你相对于元素的正常位置进行定位。你可以使用 top、bottom、left 和 right 属性分别向上、下、左、右移动元素。

以下是相对定位的示例:


#second {
position: relative;
top: 10px;
left: 20px;
}

绝对定位


绝对定位允许你相对于其最近的非静态定位的祖先元素进行定位。如果没有这样的祖先元素,则相对于初始包含块(通常是 <body> 元素)定位。



以下是绝对定位的示例:
#absolute-element {
position: absolute;
top: 50px;
left: 100px;
}
固定定位

固定定位与绝对定位类似,但元素是相对于浏览器窗口的视口定位的。这意味着即使用户滚动页面,元素也会保持在相同的位置。


以下是固定定位的示例:


#fixed-element {
position: fixed;
top: 0;
left: 0;
}


核心概念总结




以下是四种主要CSS定位方式的对比:

- 静态定位:默认值,元素处于正常文档流中。
- 相对定位:相对于自身正常位置偏移,原空间保留。
- 绝对定位:相对于最近的非静态定位祖先元素偏移,脱离文档流。
- 固定定位:相对于浏览器视口偏移,脱离文档流,滚动时位置固定。
本节课中我们一起学习了CSS的四种主要定位方式:静态定位、相对定位、绝对定位和固定定位。通过使用这些定位方式,你可以根据设计需求,以最佳方式在网页上定位元素。建议尝试不同的定位方式,看看哪种最适合你的设计。


下节课再见。🎼
099:CSS浮动(Float)属性详解 🎨
在本节课中,我们将要学习CSS中的float属性。这是一个非常强大且灵活的工具,它允许我们创建多列布局,并实现文字环绕图片或其他元素的效果。
上一节我们介绍了如何使用不同的CSS字体属性来美化HTML文档中的文字。本节中我们来看看如何使用float属性来控制元素的排列方式。
概述:什么是CSS浮动?
float是一个CSS属性,它允许你将一个元素定位在其容器的左侧或右侧。这会为其他内容在其周围流动创造空间,这对于创建多列布局或将图片与文字并排非常有用。
float属性通常与clear属性结合使用,后者用于控制浮动元素之后其他元素的行为。
核心概念与属性
以下是本节课将涵盖的核心内容:
float属性:用于设置元素的浮动方向。- 代码:
float: left;或float: right;
- 代码:
clear属性:用于清除浮动,防止后续元素受到前面浮动元素的影响。- 代码:
clear: left;,clear: right;, 或clear: both;
- 代码:
实践:创建文字环绕效果
让我们通过一个HTML文档示例来学习如何实现浮动效果。

首先,我们有一个包含多个段落的简单HTML页面。
<p>这是第一个段落,包含一些示例文字。</p>
<p>这是第二个段落,包含更多示例文字。</p>
现在,我们在段落之间添加一张图片。
<img src="example.jpg" alt="示例图片">
<p>这是第一个段落,包含一些示例文字。</p>
<p>这是第二个段落,包含更多示例文字。</p>

默认情况下,图片会作为一个块级元素显示,文字会出现在它的下方。
为了让图片浮动到右侧,并使文字环绕它,我们为图片添加CSS样式。

img {
float: right;
width: 200px;
height: 200px;
}
应用此样式后,图片会移动到容器的右侧,周围的文字会自动调整并环绕在图片的左侧。

控制浮动:使用Clear属性
有时,我们可能希望某些内容不受前面浮动元素的影响。例如,我们希望第二个段落不从图片右侧开始,而是从新的一行开始。
这时,我们可以使用clear属性。

首先,我们为不希望被浮动影响的段落创建一个CSS类。
.clear-right {
clear: right;
}
然后,将这个类应用到对应的HTML段落标签上。
<img src="example.jpg" alt="示例图片">
<p>这是第一个段落,包含一些示例文字。</p>
<p class="clear-right">这是第二个段落,它将从新的一行开始,不会环绕图片。</p>

应用clear: right;后,第二个段落将清除右侧的浮动,因此它会从图片下方开始显示,而不会环绕图片。

最佳实践与注意事项

在使用float时,需要注意以下几点:
- 父元素高度塌陷:当一个父元素内部的所有子元素都浮动时,父元素的高度可能会变为0,因为它不再包含任何常规流中的内容。解决方法是使用“清除浮动”技术,例如在父元素末尾添加一个带有
clear: both;的空元素,或者使用overflow: hidden;等现代方法。 - 响应式布局:虽然
float可以用于创建布局,但对于复杂的响应式设计,现代CSS技术如Flexbox和Grid通常是更好的选择。float更适合于实现简单的文字环绕效果。 - 谨慎使用Clear:确保只在需要阻止元素与浮动元素相邻时才使用
clear属性。
总结
本节课中我们一起学习了CSS的float和clear属性。我们了解了float如何让元素向左或向右浮动,并让其他内容环绕它。我们还学习了如何使用clear属性来精确控制哪些元素应该避开浮动元素。
掌握这些知识后,你应该能够在网页中创建灵活的文字环绕布局。请记住谨慎使用clear属性,并始终在不同设备上测试你的布局,以确保它们在所有屏幕上都能良好显示。

希望本教程对你有帮助,我们下节课再见!
100:CSS display属性详解
在本节课中,我们将要学习CSS中一个核心的布局属性——display。我们将了解它如何控制HTML元素的显示类型,并学习其三个最常用的值:block、inline和inline-block。通过本教程,你将能够使用display属性来控制网页元素的布局方式。
课程回顾与引入
上一节我们介绍了CSS的float属性,并利用它为HTML页面创建了多列布局,调整了文本和图像的排列。本节中,我们来看看另一个强大的布局属性——display。
display属性用于控制HTML元素的盒子类型,从而直接影响元素在网页上的呈现方式。我们将探讨如何使用display属性及其不同的值,并了解一些最佳实践和常见用例。
理解display属性
CSS display属性用于指定HTML元素所使用的盒子类型,这决定了元素在网页上的渲染方式。
display属性最常用的三个值是:
block(块级元素)inline(行内元素)inline-block(行内块元素)
以下是这三个值的核心区别:
block元素会独占一行,并占据其容器的全部可用宽度。inline元素会与文本流同行排列,只占据其内容所必需的宽度。inline-block元素是前两者的混合体,它允许元素像行内元素一样水平排列,但同时可以像块级元素一样设置宽度、高度等属性。

让我们通过代码示例来观察这三个值的实际效果。

代码示例与实践
首先,我们创建一个包含三个<div>元素的简单HTML页面,并为其应用一些基础CSS样式。
<!DOCTYPE html>
<html>
<head>
<style>
div {
width: 100px;
height: 100px;
background-color: red;
margin: 5px;
}
</style>
</head>
<body>
<div>1</div>
<div>2</div>
<div>3</div>
</body>
</html>

在浏览器中查看,你会发现这三个红色的方块垂直堆叠排列。这是因为<div>标签的默认display属性值就是block,所以它们会各自占据一整行。
接下来,我们看看<span>标签的默认行为。我们将上面的<div>替换为<span>,并将颜色改为蓝色。

<span>1</span>
<span>2</span>
<span>3</span>
span {
width: 100px; /* 注意:这对inline元素通常无效 */
height: 100px; /* 注意:这对inline元素通常无效 */
background-color: blue;
margin: 5px;
}
此时,你会看到数字“1”、“2”、“3”水平排列在一行。这是因为<span>标签的默认display属性值是inline。

使用CSS改变display属性

元素的默认显示类型并非一成不变,我们可以通过CSS轻松地改变它。

例如,我们可以将<div>的display属性改为inline:

div {
display: inline;
}

应用此样式后,原本垂直排列的<div>方块会立刻变为水平排列,表现得像行内元素一样。
同理,我们也可以将<span>的display属性改为block:

span {
display: block;
}

这样,原本水平排列的<span>就会各自独占一行,表现得像块级元素一样。
深入inline-block的妙用
现在,让我们关注一个更实用的值:inline-block。回顾之前的例子,当我们为<span>(display: inline)设置height和width时,这些属性并未生效。这是因为标准的inline元素不支持设置高度、上边距(margin-top)、上内边距(padding-top)等盒模型属性。
这时,inline-block就派上用场了。它兼具两者的优点:
- 像
inline元素一样,可以在水平方向与其他元素并列。 - 像
block元素一样,可以设置宽度(width)、高度(height)、内边距(padding)和外边距(margin)。

让我们将<div>的显示类型改为inline-block:

div {
display: inline-block;
}
现在,这些<div>既能水平排列,又能完美地应用我们之前设置的height: 100px属性,呈现出规整的方块效果。这解决了纯inline元素在布局上的诸多限制。
课程总结
本节课中我们一起学习了CSS display属性的核心知识。我们了解了block、inline和inline-block这三种基本显示类型的特性与区别,并通过实践掌握了如何使用CSS来改变元素的默认显示行为。

掌握这些知识和技巧后,你现在应该能够运用display属性来控制网页的布局,为创建更复杂、更动态的网页设计打下基础。希望你能在接下来的项目中灵活运用它们。

我们下个视频再见!
101:CSS单位 📏
在本节课中,我们将要学习CSS单位。它们是网页设计中用于定义元素尺寸(如字体大小、边距、内边距和边框)的关键工具。理解不同的CSS单位及其适用场景,对于创建响应式、跨设备兼容的网页至关重要。
上一节我们介绍了CSS的display属性及其不同取值。本节中,我们来看看如何精确控制元素的尺寸,这就要用到CSS单位。
什么是CSS单位?🤔
在网页开发中,CSS单位对于设计视觉吸引人且用户友好的网站至关重要。它们允许你指定元素的尺寸,例如字体、边距、内边距和边框。它们确保你的设计在不同浏览器和设备上看起来保持一致。
CSS单位主要分为两大类:绝对单位和相对单位。
绝对单位与相对单位
- 绝对单位是固定的,例如厘米(cm)、毫米(mm)、英寸(in)、像素(px)。这意味着无论屏幕如何变化,我们定义的尺寸将保持不变。
- 相对单位则与绝对单位非常不同。相对单位的长度取决于屏幕尺寸。当屏幕尺寸改变时,这些长度也会随之改变。

让我们通过一个HTML页面来查看这两种单位的例子。
实践示例:像素(px)、视口单位(vw/vh)和百分比(%)
假设我们有一个简单的HTML文档,其中包含一个<div>元素。
<div>这是一些文本。</div>
现在,让我们使用像素(px)来改变字体大小。像素是CSS中最常用的单位之一。

div {
font-size: 100px;
}
使用像素单位时,即使我们改变屏幕尺寸,字体大小也始终保持为100像素。


接下来,我们看看相对单位。首先是百分比(%)。

div {
font-size: 10%;
}


百分比单位会基于其父元素的尺寸进行计算。但有时我们更希望基于视口(浏览器窗口)的尺寸。这时可以使用视口宽度单位(vw)和视口高度单位(vh)。
div {
font-size: 10vw; /* 字体大小为视口宽度的10% */
}
div {
font-size: 10vh; /* 字体大小为视口高度的10% */
}
10vw意味着字体大小是当前视口宽度的10%,10vh则是视口高度的10%。当调整浏览器窗口大小时,字体大小会动态变化。
深入理解:em 与 rem 单位
em和rem是另外两个非常重要的相对单位。为了更好地演示,我们创建几个段落。


<p id="r1">这是 rem 示例文本。</p>
<p id="r2">这是另一个 rem 示例文本。</p>
<p id="e1">这是 em 示例文本。</p>
<p id="e2">这是另一个 em 示例文本。</p>
然后为它们应用样式:
#r1 { font-size: 1rem; }
#r2 { font-size: 2rem; }
#e1 { font-size: 1em; }
#e2 { font-size: 2em; }


初始状态下,1rem和1em看起来大小相同。它们的区别在于参考基准不同:
rem单位基于根元素(通常是<html>)的字体大小。em单位基于其直接父元素的字体大小。

让我们通过设置<body>的字体大小来观察区别:

body {
font-size: 50px;
}
应用此规则后,你会发现使用em单位的段落(#e1, #e2)字体变得非常大,因为它们继承了父元素<body>的50px大小(1em = 50px, 2em = 100px)。而使用rem单位的段落大小可能变化不大(除非根元素<html>的字体大小被改变),因为它们始终参考根元素的默认字体大小(通常是16px)。

如果注释掉body的样式,em单位也会回退到参考根元素的大小,因此1rem和1em又会看起来相同。
总结与最佳实践 🎯

本节课中我们一起学习了CSS的核心单位。
- 我们了解了绝对单位(如
px)的固定特性。 - 我们探索了相对单位,包括基于视口的
vw/vh,基于父元素的em,以及基于根元素的rem。 - 我们通过实例看到了
em和rem在继承关系上的关键区别。


通过理解不同类型的单位并有效地使用它们,你可以创建在所有设备上都看起来很棒且加载快速的设计。请记住,务必在不同的屏幕尺寸上测试你的设计,以确保它们能为所有用户提供良好的体验。希望你能在下一个HTML/CSS项目中运用这些知识。
102:本课你将学到什么
在本节课中,我们将学习如何使用CSS实现各种设计元素和功能。我们将从理解CSS中颜色和背景的使用开始,逐步深入到布局、文本样式、图标以及更高级的界面组件。
颜色与背景 🎨
首先,我们将学习CSS中颜色和背景的应用。颜色和背景是网页视觉设计的基础,用于定义元素的填充色、背景图像及其表现方式。
CSS盒模型 📦
上一节我们介绍了颜色与背景,本节中我们来看看CSS盒模型。盒模型是CSS布局的核心概念,它描述了每个元素所占用的空间。
盒模型由以下部分组成:
- 内容区:显示实际内容的区域。
- 内边距:内容区周围的透明区域。
- 边框:围绕内边距和内容的边框。
- 外边距:边框外的透明区域,用于与其他元素隔开。
布局属性 🔧
理解了盒模型后,我们需要掌握控制布局的关键属性。以下是影响元素布局的几个核心属性:
box-sizing:此属性定义如何计算元素的总宽度和高度。例如,box-sizing: border-box;会让元素的宽度和高度包含内边距和边框。position:控制元素的定位方式(如静态、相对、绝对、固定)。float:使元素向左或向右浮动,允许文本和内联元素环绕它。display:规定元素应生成的框的类型(如块级、内联、弹性盒)。
文本与表格样式 ✍️
掌握了布局,我们来看看如何美化页面内容。CSS提供了丰富的属性来设置文本的字体、大小、颜色、对齐方式等。同时,我们也可以使用CSS来为HTML表格添加边框、背景色,改善其外观和可读性。
图标与高级布局 🖼️
除了文本,图标也是现代网页设计的重要组成部分。我们将学习如何引入和使用CSS图标库(如Font Awesome)来增强界面的视觉效果。
接下来,我们将探索更强大的布局工具。CSS Grid布局允许你轻松创建复杂的二维布局系统,通过行和列来排列元素。
界面组件 🧩
利用所学知识,我们可以构建常见的网页界面组件。以下是本课将涵盖的几个实用组件:
- 导航栏:创建水平或垂直的网站导航菜单。
- 下拉菜单:实现鼠标悬停或点击时展开的菜单项。
- 图片画廊:设计用于展示多张图片的布局。
- CSS精灵图:一种将多个小图标合并到一张大图中的技术,用于减少HTTP请求。
表单与文字特效 📝
最后,我们将学习如何运用CSS来美化表单元素(如输入框、按钮),使其与网站设计风格一致。同时,我们也会探索一些CSS文字特效,如阴影、渐变文字等,以增加文本的视觉吸引力。
本节课中,我们一起学习了CSS从基础到进阶的多个方面,包括颜色背景、盒模型、关键布局属性、文本表格样式、图标使用、Grid高级布局,以及导航栏、表单等常见组件的实现方法。掌握这些知识将使你能够为网页创建视觉上吸引人且结构良好的设计。
103:CSS颜色
概述
在本节课中,我们将要学习CSS中颜色的使用方法。颜色是网页设计的基础,能极大地影响网站的整体观感。我们将探讨在CSS中指定颜色的多种方式,包括颜色名称、RGB值和十六进制代码等。
从单位到颜色
上一节我们介绍了CSS单位,学习了如何为HTML文本选择字体大小。本节中,我们来看看CSS颜色。
在CSS中,有许多不同的方式来指定颜色,从基本的命名颜色到更高级的技术,如渐变和透明度。本视频将探讨在CSS中指定颜色的各种方法,包括命名颜色、RGB值等。

应用颜色到HTML页面
以下是应用颜色的基本步骤。我们有一个包含四个段落标签的HTML页面。



在浏览器中显示如下:

现在,让我们看看如何为它们应用颜色。

我们可以看到,不同的段落标签有不同的ID。我们可以借助ID选择其中一个,并使用名为 color 的属性。我们可以放入颜色名称,例如,为第一个放入红色,为下一个放入蓝色,然后为第三个放入绿色。

保存后,效果如下:

第一个显示为红色,第二个为蓝色,第三个为绿色。


使用命名颜色的限制
当我们使用这些颜色的名称时,我们限制了自己只能使用有限数量的颜色。因为每种颜色都有多种色调,例如红色有多种色调。但我们称所有这些状态为红色。如果我们想使用那些多种色调,可以借助RGB来实现。

使用RGB值
RGB代表红色、绿色和蓝色。我们可以在其中放入这些颜色的值,它将为我们形成颜色。例如,如果我们放入 rgb(0, 0, 0),它将形成黑色。
第一个段落变成了黑色。

要改变它,例如使用 rgb(255, 35, 10),颜色可能会改变。我们可以从这里选择RGB值。
当我们增加这些值时,颜色发生了变化。这为我们打开了一个广阔的视野,现在我们可以选择任何颜色。通过放入这些不同的值,我们可以创建不同的颜色。

使用十六进制代码
与RGB类似,我们还有另一种放置颜色的方式。例如,代替蓝色,我们放入 #FFFFFF。


现在文本变成了白色。这就是我们所说的十六进制代码。

十六进制代码是HTML中颜色的十六进制表示,我们可以将其分配给 color 属性,该颜色将显示给你。
例如,如果我们想找到其他颜色的十六进制代码,我们可以放入 #00FF00 代表绿色。
如果我们把它放在这里,文本变成了绿色。
我们可以点击这个网站,找到不同颜色的不同十六进制代码。
你可以看到所有这些颜色都是绿色,但它们有不同的十六进制代码和不同的RGB值。这是使用命名颜色无法实现的。
总结
本节课中,我们一起学习了CSS颜色的来龙去脉。通过使用这些技术,你将能够创建具有美观和影响力设计的网站,同时确保它们对所有用户都可访问。请记住,颜色只是网页设计的一个方面,但它可以在整体用户体验中产生巨大差异。希望你在下一个HTML CSS项目中使用这些颜色属性。下个视频见。


104:CSS背景颜色 🎨
在本节课中,我们将要学习CSS中的background-color属性。我们将探讨如何为网页设置背景颜色,以及使用不同颜色表示方法(如颜色名称、RGB、RGBA和十六进制)来丰富你的设计。
上一节我们介绍了CSS的color属性,本节中我们来看看如何设置元素的背景颜色。作为网页开发者,为网站选择合适的背景色对于创造引人入胜的用户体验至关重要。背景色为整个页面定下基调,并极大地影响网站的可读性和整体美观度。
设置背景颜色
在CSS中,我们使用background-color属性来设置元素的背景颜色。其基本语法如下:
selector {
background-color: value;
}


以下是几种常用的颜色值指定方法。

1. 使用颜色名称
CSS预定义了一系列颜色名称,例如red、blue、coral等。这种方法简单直接,但可选颜色有限。


body {
background-color: coral;
}
2. 使用RGB值
RGB颜色模型通过混合红(Red)、绿(Green)、蓝(Blue)三种光的分量来定义颜色。每个分量的取值范围是0到255。
body {
background-color: rgb(100, 149, 237);
}


3. 使用RGBA值

RGBA在RGB的基础上增加了一个Alpha通道,用于控制颜色的不透明度。Alpha值的范围是0.0(完全透明)到1.0(完全不透明)。

body {
background-color: rgba(100, 149, 237, 0.4); /* 40%不透明度 */
}


4. 使用十六进制值
十六进制颜色代码以#开头,后跟六位数字或字母,每两位分别代表红、绿、蓝的分量。
body {
background-color: #ffffff; /* 白色 */
background-color: #6495ed; /* 与 rgb(100, 149, 237) 相同 */
}

实践与应用
选择互补的颜色并使用对比度来提升可读性非常重要。浅色背景搭配深色文字通常具有良好的可读性,反之亦然。你可以尝试不同的颜色组合,找到最适合你网站主题和风格的方案。
本节课中我们一起学习了CSS的background-color属性。我们掌握了使用颜色名称、RGB、RGBA和十六进制代码来设置背景色的方法,并了解了透明度控制。请花时间尝试不同的颜色,利用对比度提升可读性,从而创建视觉上突出且能吸引用户的网站。

我们下个视频再见。🎼
105:CSS盒模型 📦
概述
在本节课中,我们将要学习CSS盒模型。盒模型是CSS布局的核心概念,它描述了网页上每个元素如何被渲染为一个矩形盒子,并控制其尺寸和间距。
上一节我们介绍了CSS背景颜色,本节中我们来看看CSS盒模型。对于CSS初学者,盒模型可能看起来有些复杂,但我们会将其分解,用简单的方式解释清楚。
什么是CSS盒模型?
简单来说,盒模型是一种描述网页元素布局方式的概念。每个HTML元素都被表示为一个矩形盒子,这个盒子由四个部分组成:内容区、内边距、边框和外边距。
以下是盒模型四个部分的图示说明:

让我们逐一了解每个部分:
-
内容区
内容区是HTML元素的实际内容,例如文本或图像。 -
内边距
内边距是内容区与边框之间的空间,用于在元素内部增加间距。 -
边框
边框是围绕元素和内边距的边界线,可以自定义其颜色、宽度和样式。 -
外边距
外边距是边框与相邻元素之间的空间,用于在元素外部增加间距。

理解盒模型对于创建CSS布局至关重要,因为它允许你控制页面上元素的尺寸和间距。通过使用width、height、padding、border和margin等CSS属性,你可以调整盒模型以实现期望的设计。
盒模型实战演示
让我们通过一个HTML和CSS文档的例子来具体看看。
这是我们的HTML文档,其中创建了两个<div>元素,并为它们编写了一些CSS样式。

* {
margin: 0;
padding: 0;
}
div {
height: 50px;
background-color: lightblue;
}
默认情况下,我们已将整个HTML文档的外边距和内边距设置为0。同时,为两个<div>设置了相同的高度和一些背景色。
在浏览器中,它目前看起来是这样的:


如果我们检查元素,可以看到当前这个<div>元素的盒模型。目前,该<div>没有任何内边距、边框或外边距,因此我们看不到这些部分。内容区的尺寸是宽度(视口宽度)乘以高度50px。

现在,让我们看看如何设置这些值,以及它们如何影响盒模型。

设置内边距
首先,为第一个<div>添加内边距。
.div1 {
padding: 20px;
}
现在你可以看到,内容被20px的内边距从四周包围。



有时,我们可能需要为盒子的每个边设置不同的内边距。以下是实现方法:
我们可以分别为每个边设置属性:
.div1 {
padding-left: 10px;
padding-top: 5px;
padding-right: 10px;
padding-bottom: 5px;
}
这样,我们为顶部和底部设置了5px内边距,为左侧和右侧设置了10px内边距。



但是,有一种更简洁的写法。padding属性可以接受多个值,按上、右、下、左的顺序(顺时针)分别设置各边的内边距。
.div1 {
/* 顺序:上 右 下 左 */
padding: 10px 50px 5px 100px;
}
- 第一个值(10px)应用于顶部。
- 第二个值(50px)应用于右侧。
- 第三个值(5px)应用于底部。
- 第四个值(100px)应用于左侧。

设置边框
接下来,添加边框。我们将为所有边设置统一的边框。


.div1 {
padding: 50px;
border: 10px solid black;
}
现在,你可以看到一个10px宽的黑色实线边框出现在内边距的外围。


我们可以比较两个<div>的盒模型。第二个<div>的盒模型值仍然是空的,而第一个<div>的盒模型已经填充了内边距和边框的值。
设置外边距
最后,添加外边距。

.div1 {
padding: 50px;
border: 10px solid black;
margin: 20px;
}
现在,元素周围出现了20px的外边距,它将第一个<div>与第二个<div>分隔开来。


与padding类似,margin属性也可以为每个边设置不同的值。

.div1 {
/* 顺序:上 右 下 左 */
margin: 10px 20px 30px 50px;
}



我们也可以仅为第二个<div>添加外边距。


.div2 {
margin: 50px;
}
现在可以看到,外边距也添加到了第二个元素上。你不必同时设置内边距、边框和外边距,可以根据需要直接添加任何一个属性。


检查元素时,我们会发现第一个元素的外边距延伸到这里,第二个元素的外边距延伸到这里。实际上,相邻元素的外边距有时会发生重叠(外边距合并),但每个元素始终会根据其周围的元素计算自己的外边距。
总结
本节课中我们一起学习了CSS盒模型。我们了解到,每个HTML元素都可以看作一个由内容区、内边距、边框和外边距组成的盒子。通过padding、border和margin属性,我们可以精确控制元素的内部空间、边界和外部间距,这是进行网页布局的基础。希望你能够在接下来的项目中运用这些知识。
我们下个视频再见。


106:CSS box-sizing 属性详解 📦
在本节课中,我们将要学习 CSS 的 box-sizing 属性。这个属性控制着元素宽度和高度的计算方式,对于精确布局至关重要。
上一节我们介绍了 CSS 盒模型及其如何影响网页上 HTML 元素的布局和尺寸。本节中,我们来看看 box-sizing 属性及其不同的取值。
box-sizing 属性允许你控制元素宽度和高度的计算方式,决定是否将内边距和边框包含在计算内。它主要有两个值:content-box 和 border-box。
box-sizing 的默认值是 content-box。这意味着元素的宽度和高度仅基于其内容区域计算。如果你为元素添加内边距或边框,元素的总尺寸会增加,这可能导致布局问题。
另一方面,border-box 值在计算宽度和高度时,会将内边距和边框包含在内。这意味着,如果你将一个元素的宽度设为 200 像素,并添加 10 像素的内边距和 1 像素的边框,元素的总宽度将保持为 200 像素。
以下是两种计算方式的公式对比:

-
content-box(默认)
元素总宽度 = width + padding-left + padding-right + border-left + border-right -
border-box
元素总宽度 = width(已包含 padding 和 border)

现在,让我们通过一个 HTML 文档示例来看看它的实际效果。


以下是一个演示 box-sizing 如何工作的示例步骤。

首先,我们创建一个 HTML 文档,其中包含一个主 div 容器和两个内部的 div 元素。
<div id="main">
<div class="box box1">Box 1</div>
<div class="box box2">Box 2</div>
</div>

接着,我们为这些元素添加一些基础样式。


#main {
width: 500px;
border: 10px solid black;
}
.box {
width: 100%; /* 默认继承父容器宽度 */
background-color: lightblue;
margin-bottom: 10px;
}
此时,两个内部 div 会填满主容器的宽度。现在,我们为第一个盒子添加内边距。


.box1 {
padding: 20px;
}

你会发现,第一个盒子超出了主容器的黑色边框。这是因为在默认的 content-box 模式下,width: 100% 仅指内容宽度为 500px,加上左右各 20px 的内边距后,总宽度变成了 540px。

为了解决这个问题,我们使用 box-sizing 属性。

.box1 {
padding: 20px;
box-sizing: border-box; /* 将 padding 和 border 包含在 width 计算内 */
}
应用 border-box 后,第一个盒子自动调整,其总宽度(内容+内边距)被限制在主容器设定的 500px 宽度内。即使我们再添加边框,它也会被包含在总宽度内。

.box1 {
padding: 20px;
border: 5px solid red;
box-sizing: border-box;
}
而第二个盒子保持 content-box 的默认行为。如果你将其 box-sizing 也改为 content-box,它会表现出和最初未设置时一样的向外扩张的行为。

本节课中我们一起学习了 CSS box-sizing 属性的核心概念和用法。我们了解到 content-box 是默认值,计算尺寸时不包含内边距和边框;而 border-box 则将这些部分包含在设定的宽度和高度内,使得布局控制更加直观和稳定。在实际开发中,使用 border-box 通常能避免许多意外的布局问题。
107:CSS样式化表格 🎨
在本节课中,我们将要学习如何使用CSS来美化HTML表格。表格是展示结构化数据的绝佳方式,但为了使其更具视觉吸引力,我们需要为其添加样式。
上一节我们介绍了CSS图标,本节中我们来看看如何为表格添加边框、颜色和响应式设计,使其看起来更专业、更易读。
概述
我们将通过一个简单的HTML表格示例,逐步应用各种CSS属性来改善其外观。核心步骤包括设置边框、合并边框、对齐文本、设置表头样式以及为表格行添加交替的背景色。
创建基础表格

首先,我们有一个基础的HTML表格结构,它目前没有任何样式,因此在浏览器中看起来只是简单的文本排列。


<table>
<tr>
<th>姓名</th>
<th>年龄</th>
<th>城市</th>
</tr>
<tr>
<td>张三</td>
<td>25</td>
<td>北京</td>
</tr>
<tr>
<td>李四</td>
<td>30</td>
<td>上海</td>
</tr>
</table>
添加基础样式
为了让表格看起来像表格,我们需要添加边框。以下是第一步CSS代码,为整个表格和每个单元格添加边框。

table {
border: 1px solid black;
width: 500px;
margin: 20px; /* 为表格添加一些外边距 */
}

td, th {
border: 1px solid black;
}

应用此样式后,表格有了边框,但每个单元格的边框是分开的,看起来不够整洁。
合并表格边框


为了解决边框分离的问题,我们可以使用 border-collapse 属性。这个属性可以将相邻单元格的边框合并为单一边框。

table {
border-collapse: collapse;
}
应用此属性后,表格的边框变得清晰、统一。

对齐文本与设置表头样式

目前,表格内的文本(特别是表头)可能没有对齐。我们可以使用 text-align 属性使其居中。同时,为了让表头更突出,我们可以为其设置背景色和文字样式。
以下是相关CSS代码:


td, th {
text-align: center; /* 使所有单元格内容居中 */
}
th {
background-color: black; /* 设置表头背景色 */
color: white; /* 设置表头文字颜色 */
font-weight: bold; /* 加粗表头文字 */
text-align: left; /* 表头文字左对齐 */
padding: 10px; /* 为表头添加内边距 */
}

为表格行添加交替颜色


为了提高长表格的可读性,一个常见的做法是为奇数行和偶数行设置不同的背景色。我们可以使用 :nth-child() 伪类选择器来实现斑马纹效果。
以下是实现代码:
tr:nth-child(even) {
background-color: #f2f2f2; /* 为偶数行设置浅灰色背景 */
}

添加悬停效果
为了提升交互体验,我们可以为表格行添加悬停效果,当鼠标悬停在某一行时,改变其背景色。

tr:hover {
background-color: yellow; /* 悬停时背景变为黄色 */
color: black; /* 悬停时文字变为黑色 */
}

总结

本节课中我们一起学习了如何使用CSS来样式化HTML表格。我们掌握了以下核心技能:
- 使用
border和border-collapse属性来创建和优化表格边框。 - 使用
text-align和padding来调整文本对齐和单元格间距。 - 使用背景色、文字颜色和字体粗细来突出显示表头。
- 利用
:nth-child()伪类为表格行创建交替的背景色,增强可读性。 - 使用
:hover伪类为表格添加简单的交互效果。

通过这些步骤,你可以将任何朴素的HTML表格转变为结构清晰、视觉美观的数据展示组件。尝试将这些技巧应用到你的项目中,以提升用户体验。
108:CSS图标 🎨
在本节课中,我们将要学习如何在网页中使用CSS图标。图标能够使网页更加生动、直观,并提升用户体验。
上一节我们介绍了CSS中与文本相关的样式属性,如文本装饰、文本转换和文本阴影等。本节中,我们来看看如何为网页添加图标。
概述
CSS本身没有提供内置的图标功能。为了在HTML网页中使用图标,我们需要借助第三方图标库,例如Font Awesome、Bootstrap Icons、Google Icons或W3.CSS Icons。使用这些库需要先将它们导入到项目中,然后即可使用它们提供的丰富图标资源。
导入图标库
首先,我们从一个空白的HTML页面开始。当前页面仅移除了默认的边距和内边距。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS Icons</title>
<style>
* {
margin: 0;
padding: 0;
}
</style>
</head>
<body>

</body>
</html>
为了添加图标,我们需要导入第三方库。以W3.CSS图标库为例,以下是导入步骤。

我们需要在HTML文档的<head>部分添加两个链接来导入该库。
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
添加后,我们的HTML文档结构如下。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS Icons</title>
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
* {
margin: 0;
padding: 0;
}
</style>
</head>
<body>

</body>
</html>
使用图标

导入库之后,就可以在页面中使用图标了。图标通常通过<i>标签并添加特定的CSS类来呈现。

例如,要添加一个“主页”图标,可以在<body>标签内插入以下代码。
<i class="fa fa-home"></i>
保存并刷新页面,你将看到一个主页图标显示出来。
自定义图标样式
我们可以为图标添加自定义样式,例如改变其大小和颜色。这可以通过内联样式或内部样式表来实现。
首先,尝试使用内联样式。

<i class="fa fa-home" style="font-size: 50px; color: blue;"></i>

然而,你可能会发现图标大小没有改变。这是因为图标库自带的CSS类优先级更高,覆盖了我们的内联样式。
为了强制应用我们的样式,需要使用CSS的!important规则。以下是使用内部样式表的示例。
在<style>标签内添加以下规则。
i {
font-size: 50px !important;
color: blue !important;
}

现在保存并刷新页面,图标的大小和颜色都已按照我们的设定改变了。
探索更多图标
W3.CSS图标库提供了丰富的图标选择。要查看所有可用的图标,可以访问其官方文档。在那里,你可以找到图标的名称和对应的CSS类,然后根据需求在项目中使用。

除了W3.CSS,还有其他流行的图标库可供选择,例如Font Awesome、Google Material Icons和Bootstrap Icons。它们的导入和使用方式类似,通常都是通过链接引入CSS文件,然后使用特定的类名。
以下是使用不同图标库的通用步骤。

- 在图标库官网找到CDN链接或下载指令。
- 将链接添加到HTML文档的
<head>部分。 - 在需要图标的地方使用
<i>或<span>标签,并添加库指定的CSS类名。
总结

本节课中我们一起学习了如何在网页中使用CSS图标。我们了解到CSS本身不提供图标,需要借助第三方图标库。我们以W3.CSS为例,演示了如何导入图标库、在页面中插入图标以及如何通过!important规则自定义图标的样式。掌握这些知识后,你就可以在未来的项目中灵活运用各种图标来增强界面的视觉效果和交互性了。
109:CSS Grid 布局(第一部分)📐
在本节课中,我们将要学习 CSS Grid 布局系统的基础知识。CSS Grid 是 CSS 中最强大的布局工具之一,它是一个二维布局系统,允许你使用行和列来创建复杂的布局结构。
概述
上一节我们介绍了如何使用 CSS 来样式化表格。本节中,我们来看看 CSS Grid。我们将涵盖 CSS Grid 的基础知识,包括如何设置一个网格、如何定义列、以及如何在网格内放置内容。

什么是 CSS Grid?
CSS Grid 是一个二维布局系统,允许你使用行和列来创建复杂的布局。与 CSS 中的其他布局工具(如 Flexbox 和浮动)不同,CSS Grid 是专门为创建基于网格的布局而设计的。


使用 CSS Grid,你可以轻松创建能够适应不同屏幕尺寸和设备的响应式、灵活的布局。你可以将 HTML 元素排列成行和列,从而完全控制每个元素的位置和大小,例如将其布局为页眉、主体内容区、空白区域和侧边栏等有意义的区域。
实践 CSS Grid


让我们在自己的 HTML 文档中实现 CSS Grid。
以下是一个 HTML 文档示例,我们创建了一个网格容器,其中包含多个网格项,并应用了一些基础样式。


<div class="container">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
<div class="item">7</div>
<div class="item">8</div>
</div>



.container {
background-color: blue;
padding: 20px;
}
.item {
background-color: lightgray;
margin-bottom: 10px;
text-align: center;
font-size: 24px;
}

现在,为了对这个容器应用网格布局,我们将使用 display 属性,并将其值设置为 grid。



.container {
display: grid;
/* 其他样式保持不变 */
}


仅仅设置 display: grid 并不会立即产生可见的网格效果,因为我们还需要定义网格的结构。


定义网格列


要定义网格的列,我们使用 grid-template-columns 属性。

以下是定义列的方法:

- 定义固定宽度列:
grid-template-columns: 100px;会创建一个 100 像素宽的单一列。 - 定义多列:
grid-template-columns: 100px 100px;会创建两个 100 像素宽的列。 - 使用
repeat()函数:当需要多列时,可以使用repeat()函数简化代码。例如,grid-template-columns: repeat(4, 100px);会创建四个 100 像素宽的列。 - 使用弹性单位
fr:fr单位代表“分数”,用于创建按比例分配可用空间的列。例如:grid-template-columns: 1fr;创建一个占据全部可用空间的列。grid-template-columns: 1fr 1fr;创建两个等宽的列。grid-template-columns: 2fr 1fr;创建两列,第一列的宽度是第二列的两倍。
定义网格行


定义网格行与定义列类似。
以下是定义行的方法:
- 使用
grid-template-rows:此属性用于显式定义行的高度。例如,grid-template-rows: 200px 200px;会创建两个 200 像素高的行。 - 使用
grid-auto-rows:此属性用于定义所有隐式创建的行(即未在grid-template-rows中定义的行)的高度。例如,grid-auto-rows: 200px;会使所有行的高度为 200 像素。 - 结合使用:可以同时使用
grid-template-rows和grid-auto-rows。例如:.container { grid-template-rows: 300px; /* 第一行高 300px */ grid-auto-rows: 200px; /* 后续所有行高 200px */ } - 使用
minmax()函数实现动态行高:为了让行高根据内容动态调整,可以使用minmax(min, max)函数。例如,grid-auto-rows: minmax(100px, auto);表示行高最小为 100 像素,最大根据内容自动扩展。
设置网格间隙
网格项默认是紧密排列的。我们可以使用以下属性来添加间隙:



- 列间隙:使用
column-gap属性。例如,column-gap: 10px;。 - 行间隙:使用
row-gap属性。例如,row-gap: 10px;。 - 统一间隙:使用
gap属性可以同时设置行和列的间隙。例如,gap: 20px;会设置行和列的间隙都为 20 像素。


总结

本节课中我们一起学习了 CSS Grid 布局的基础部分。我们了解了什么是 CSS Grid,它是一个强大的二维布局工具。我们实践了如何通过 display: grid 将一个容器设置为网格,并使用 grid-template-columns 和 grid-template-rows(或 grid-auto-rows)来定义网格的行和列结构。我们还学习了如何使用 fr 单位创建弹性布局,使用 minmax() 函数让行高自适应内容,以及使用 gap 系列属性为网格项之间添加间距。


掌握这些基础知识后,你已经可以在项目中开始使用 CSS Grid 来创建结构清晰、响应灵活的页面布局了。
110:CSS Grid布局(第二部分)📐


在本节课中,我们将继续深入学习CSS Grid布局。上一节我们介绍了如何使用grid-template-columns和grid-template-rows来定义网格的列与行。本节中,我们将探索另一种强大的布局方式:网格模板区域,并学习如何精确控制网格项的位置与对齐方式。

概述:网格模板区域


grid-template-areas属性允许我们通过为网格区域命名来直观地定义布局结构。这是一种声明式的布局方法。
以下是定义网格模板区域的步骤:


- 在网格容器中,使用
grid-template-areas属性定义区域布局。 - 为每个网格项分配一个
grid-area名称,使其填充对应的区域。


例如,我们可以这样定义一个包含页眉、侧边栏和内容区的布局:
.container {
display: grid;
grid-template-columns: 1fr 2fr;
grid-template-rows: auto 1fr;
grid-template-areas:
"header header"
"sidebar content";
}
.item1 { grid-area: header; }
.item2 { grid-area: sidebar; }
.item3 { grid-area: content; }
通过这种方式,我们可以清晰地看到页眉横跨两列,侧边栏和内容区分别占据第二行的两列。


精确控制网格项位置

除了使用命名区域,我们还可以通过指定网格线的起始和结束位置来精确控制网格项的尺寸和位置。

以下是相关的CSS属性:
grid-column-start: 定义网格项从哪条垂直网格线开始。grid-column-end: 定义网格项到哪条垂直网格线结束。grid-row-start: 定义网格项从哪条水平网格线开始。grid-row-end: 定义网格项到哪条水平网格线结束。

这些属性可以简写为:
grid-column: <start-line> / <end-line>;grid-row: <start-line> / <end-line>;

重要提示:网格线编号从1开始,并且包括每条列/行之间的隐性格线。例如,一个3列的网格有4条垂直网格线。
我们也可以使用span关键字来指定网格项跨越的轨道数量,这通常更为直观:
.item {
/* 从第1条网格线开始,跨越3列 */
grid-column: 1 / span 3;
/* 从第2条网格线开始,跨越2行 */
grid-row: 2 / span 2;
}
网格内容的对齐
CSS Grid提供了强大的属性来控制网格容器内所有网格项的对齐方式。

以下是控制对齐的核心属性:

- 水平对齐:使用
justify-content属性。其常用值包括:start: 将网格项对齐到容器的起始边缘(左对齐)。end: 将网格项对齐到容器的结束边缘(右对齐)。center: 将网格项在容器内水平居中。space-between: 在网格项之间均匀分配空间,首尾项紧贴容器边缘。space-around: 在每个网格项周围分配相等的空间。space-evenly: 在网格项之间以及项与容器边缘之间分配相等的空间。


- 垂直对齐:使用
align-content属性。其取值与justify-content相同,但作用于垂直方向。start: 顶部对齐。end: 底部对齐。center: 垂直居中。space-between、space-around、space-evenly: 在垂直方向上均匀分配空间。

例如,要使网格内容在容器内完全居中:
.container {
display: grid;
height: 100vh; /* 使容器有足够高度以观察垂直对齐效果 */
justify-content: center; /* 水平居中 */
align-content: center; /* 垂直居中 */
}




总结


本节课中我们一起学习了CSS Grid布局的进阶技巧。我们掌握了如何使用grid-template-areas进行直观的布局规划,以及如何通过grid-column和grid-row属性(结合网格线编号或span关键字)来精确定位网格项。最后,我们探讨了justify-content和align-content属性,它们能帮助我们在网格容器内轻松实现各种复杂的对齐需求。结合这些强大的工具,你可以创建出既灵活又响应迅速的网页布局。
111:CSS导航栏 🧭
在本节课中,我们将学习如何使用CSS创建一个功能完整、样式美观的导航栏。导航栏是网站的关键组成部分,它帮助用户在网站的不同页面或区域间进行导航。
上一节我们介绍了CSS的网格布局,本节中我们来看看如何利用CSS的其他属性来构建一个导航栏。

创建基础HTML结构
首先,我们需要创建导航栏的HTML骨架。导航栏通常由一个无序列表构成,列表项内包含用于跳转的链接。
<ul>
<li><a class="active" href="#">首页</a></li>
<li><a href="#">新闻</a></li>
<li><a href="#">联系</a></li>
<li><a href="#">关于</a></li>
</ul>
使用CSS进行基础样式设置
初始的HTML列表看起来并不像导航栏。我们需要使用CSS来改变它的外观。


以下是应用于无序列表的样式,用于重置默认样式并设置背景:
ul {
list-style-type: none; /* 移除列表项前的圆点 */
margin: 0;
padding: 0;
overflow: hidden; /* 防止内容溢出 */
background-color: #333; /* 设置背景色为深色 */
}

排列列表项并设置链接样式

接下来,我们需要让列表项水平排列,并设置链接的样式,使其看起来像可点击的按钮。
以下是设置列表项和链接样式的代码:
li {
float: left; /* 使列表项水平排列 */
}

li a {
display: block; /* 将链接变为块级元素,方便设置尺寸 */
color: white; /* 文字颜色 */
text-align: center; /* 文字居中 */
padding: 14px 16px; /* 内边距 */
text-decoration: none; /* 移除下划线 */
}

添加交互效果

一个优秀的导航栏需要有视觉反馈。我们将为当前活动页面和鼠标悬停状态添加特殊样式。

以下是添加活动状态和悬停效果的样式:


/* 活动页面链接的样式 */
.active {
background-color: #04AA6D; /* 绿色背景 */
color: white;
}
/* 鼠标悬停时的样式 */
li a:hover {
background-color: #ddd; /* 浅灰色背景 */
color: black;
}
实现固定定位导航栏

在许多网站中,导航栏会固定在页面顶部,不随页面滚动而移动。我们可以使用 position: fixed 属性来实现这个效果。


以下是使导航栏固定在顶部的CSS:

ul {
position: fixed; /* 固定定位 */
top: 0; /* 距离顶部为0 */
width: 100%; /* 宽度占满整个视口 */
}

当导航栏固定后,页面内容可能会被其遮挡。我们需要为页面主体内容添加一个上边距,将其向下推。

以下是调整页面内容位置的CSS:


body {
margin-top: 50px; /* 根据导航栏高度调整 */
}

总结


本节课中我们一起学习了如何使用HTML和CSS创建一个完整的导航栏。我们首先构建了基础的HTML列表结构,然后逐步使用CSS设置了列表样式、链接样式、交互效果,并最终实现了固定在页面顶部的导航栏功能。通过组合使用 float、display、position 等属性,我们可以灵活地控制导航栏的布局和外观。
112:CSS下拉菜单教程 🎨
在本节课中,我们将学习如何使用HTML和CSS创建一个基础的下拉菜单。下拉菜单是网页设计中常见的交互元素,当用户点击或悬停在特定区域时,会显示一个选项列表供用户选择。

上一节我们介绍了如何使用CSS创建导航栏,本节中我们来看看如何为其添加一个下拉菜单功能。
什么是下拉菜单?
下拉菜单是一种当用户点击按钮或将鼠标悬停在特定区域时出现的菜单。它常用于展示一系列选项供用户选择。
创建基础HTML结构
首先,我们需要构建下拉菜单的HTML骨架。以下是创建基础下拉菜单所需的HTML代码:
<div class="dropdown">
<button class="dropdown-btn">下拉菜单</button>
<div class="dropdown-content">
<a href="#">选项一</a>
<a href="#">选项二</a>
<a href="#">选项三</a>
</div>
</div>
添加基础CSS样式
有了HTML结构后,我们需要用CSS来控制其外观和行为。初始时,下拉内容应该是隐藏的。

以下是初始的CSS样式,用于设置下拉容器和隐藏下拉内容:

.dropdown {
position: relative;
display: inline-block;
}
.dropdown-content {
display: none;
position: absolute;
z-index: 1;
}
代码解释:
position: relative;为下拉容器建立定位上下文。display: none;确保下拉内容默认是隐藏的。position: absolute;使下拉内容脱离文档流,可以精确定位。z-index: 1;确保下拉菜单显示在其他元素之上。
实现悬停显示功能

接下来,我们需要实现核心交互:当鼠标悬停在下拉按钮上时,显示隐藏的菜单选项。

这可以通过CSS的 :hover 伪类选择器来实现:
.dropdown:hover .dropdown-content {
display: block;
}

公式解释:父元素:hover 子元素 { 样式 }
这条规则意味着:当鼠标悬停在 .dropdown 元素上时,将其子元素 .dropdown-content 的显示方式改为 block,从而使其可见。

美化下拉菜单
功能实现后,我们可以添加更多样式来美化按钮和下拉选项框,使其更美观。


以下是用于美化按钮和下拉内容框的CSS样式:
/* 美化按钮 */
.dropdown-btn {
border: none;
padding: 20px;
background-color: #3498db;
color: black;
font-weight: 900;
margin: 20px;
}



/* 美化下拉选项框 */
.dropdown-content {
/* 之前的样式保持不变 */
left: 30px; /* 微调位置 */
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); /* 添加阴影 */
padding: 20px;
width: 200px; /* 设置宽度 */
background-color: #f9f9f9; /* 设置背景色 */
}

最终效果与总结
本节课中我们一起学习了如何使用HTML和CSS创建一个完整的下拉菜单。我们首先构建了HTML结构,然后通过CSS设置了其基本布局和隐藏状态,接着使用 :hover 选择器实现了鼠标悬停显示菜单的交互逻辑,最后对按钮和菜单进行了视觉上的美化。


你可以根据实际项目需求,调整颜色、尺寸、阴影等样式属性,并将菜单链接(#)替换为真实的URL,从而将这个基础的下拉菜单应用到你的网站导航栏或其他交互组件中。
113:CSS图片画廊与精灵图 🖼️

概述
在本节课中,我们将学习如何使用CSS创建图片画廊和精灵图。图片画廊能有效展示多张图片,而精灵图技术则能优化网页性能。
CSS图片画廊
上一节我们介绍了如何使用CSS创建下拉按钮。本节中,我们来看看如何创建一个图片画廊。
图片画廊是排列成网格或行的图片集合。它是展示图片并为浏览者提供便捷浏览方式的好方法。在CSS中,我们可以使用float属性并设置图片的宽度和高度来创建画廊,也可以使用其他CSS属性实现。
以下是创建一个基础图片画廊的步骤:
-
HTML结构:首先,创建一个主容器
<div>,并为其设置一个类名(例如my-gallery)。在这个容器内,为每张图片创建单独的<div>或直接使用<img>标签。<div class="my-gallery"> <img src="dog1.jpg" alt="Dog 1"> <img src="dog2.jpg" alt="Dog 2"> <img src="dog3.jpg" alt="Dog 3"> </div> -
CSS样式:使用CSS Grid布局来排列图片。为画廊容器设置
display: grid,并定义列数。同时,可以添加间隙使布局更美观。.my-gallery { display: grid; /* 创建3列,每列等宽 */ grid-template-columns: repeat(3, 1fr); /* 设置图片之间的间隙为10像素 */ grid-gap: 10px; }

-
图片样式:控制图片本身的尺寸,使其适应网格布局。通常将宽度设为100%,高度设为自动。
.my-gallery img { width: 100%; height: auto; }
应用这些样式后,图片将整齐地排列在一个三列的网格中。你可以通过调整grid-template-columns的值来改变列数。

CSS精灵图
了解了图片画廊的创建后,我们接下来探讨一种优化技术:CSS精灵图。
精灵图是一种用于减少网页HTTP请求次数的技术。它将多个小图标或图片合并到一张大图中,然后使用CSS背景定位来显示所需的特定部分。
以下是使用精灵图的步骤:
-
创建合并图像:首先,需要使用图像编辑软件(如Photoshop)或在线工具,将所有小图标合并到一张图片中。例如,一张图里可能包含主页图标、左箭头和右箭头图标。
-
HTML结构:在HTML中,为每个需要显示图标的地方创建一个元素(如
<div>或<span>)。由于实际图像将通过CSS背景引入,src属性可以放置一个透明的占位图。<div id="home"></div> <div id="back"></div>


-
CSS背景定位:为每个元素设置相同的背景图像(即合并后的精灵图),然后通过
background-position属性调整位置,以显示正确的图标部分。同时需要精确设置元素的宽度和高度,以匹配目标图标的大小。#home { width: 46px; height: 44px; background: url(‘sprites.gif’) 0 0; } #back { width: 43px; height: 44px; background: url(‘sprites.gif’) -91px 0; }在
#back的样式中,background-position: -91px 0;意味着将背景图像向左移动91像素,从而显示出左箭头图标。




通过这种方式,浏览器只需加载一张合并后的图片,而不是多个单独的小图片,从而显著提升了页面加载性能。


总结
本节课中我们一起学习了两个实用的CSS技巧。我们首先学习了如何利用CSS Grid布局创建整洁的图片画廊。接着,我们探讨了CSS精灵图技术,它通过合并图片和背景定位来减少HTTP请求,优化网站性能。掌握这些技能将有助于你构建更美观、更高效的网页。
114:CSS 图像样式与倒影
在本节课中,我们将学习如何使用 CSS 为图像添加各种样式效果,包括边框、圆角、阴影、滤镜,以及如何创建图像倒影。这些技巧能让网页中的图片更具视觉吸引力。
上一节我们介绍了如何创建图像画廊和使用 CSS 雪碧图。本节中,我们来看看如何对单个图像进行美化和添加特殊效果。
图像样式基础
图像是提升网站视觉吸引力的绝佳方式。通过 CSS,你可以为图像应用不同的样式,使其脱颖而出。

我们可以从多个方面着手美化图像,例如添加边框、设置圆角、添加内边距、应用阴影或使用各种滤镜。接下来,我们将逐一查看这些属性。
以下是一个简单的 HTML 文档,其中包含一张待处理的图片。


<img src="dog.jpg" alt="A dog">
居中与边框

首先,我们使用 CSS 选择器选中这张图片,并将其在页面中居中显示。


img {
display: block;
margin-left: auto;
margin-right: auto;
}
接着,为图像添加一个边框。

img {
display: block;
margin-left: auto;
margin-right: auto;
border: 20px solid black;
}
圆角与阴影
我们可以使用 border-radius 属性将图片的直角变为圆角。
img {
/* ... 其他样式 ... */
border-radius: 50%;
}
现在,图片变成了圆形。我们还可以尝试制作一个宝丽来(拍立得)相片风格的效果。

以下是实现宝丽来效果的步骤:

- 将图片和描述文字包裹在一个
div容器中。 - 为容器添加阴影和背景色。
- 为文字设置居中对齐。
<div class="polaroid">
<img src="dog.jpg" alt="A dog">
<p id="text">A dog</p>
</div>

.polaroid {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
width: 500px;
padding: 10px;
background-color: white; /* 通常宝丽来相纸是白色的 */
}

#text {
text-align: center;
}
应用滤镜
CSS 滤镜(filter)属性可以为图像添加各种视觉效果,如模糊、亮度调整、对比度调整等。

让我们为图像应用一个模糊滤镜。


img {
/* ... 其他样式 ... */
filter: blur(4px);
}

我们也可以调整图像的亮度。
img {
/* ... 其他样式 ... */
filter: brightness(200%);
}

或者调整对比度。
img {
/* ... 其他样式 ... */
filter: contrast(180%);
}
类似地,你还可以尝试其他滤镜,如 hue-rotate(色相旋转)、invert(反色)、opacity(透明度)、saturate(饱和度)、sepia(怀旧色)、drop-shadow(投影)、grayscale(灰度)等。

图像倒影

现在,让我们来看看第二个主题:图像倒影。
图像倒影可以创建图像的一个镜像副本,从而产生反射效果。需要注意的是,该属性主要通过 -webkit-box-reflect 实现,主要被 Chrome、Safari 和 Opera 浏览器支持,而 Firefox 则不支持。

要为图像添加倒影,我们可以使用 -webkit-box-reflect 属性。

img {
-webkit-box-reflect: below;
}

上述代码会在图像下方创建一个倒影。你也可以将方向改为 right(右侧)、left(左侧)或 above(上方,但可能因超出视图而不可见)。


本节课中,我们一起学习了如何使用 CSS 为图像添加边框、圆角、阴影和宝丽来效果,探索了多种滤镜的应用,并了解了如何为图像创建倒影效果。这些技能将帮助你显著提升网页中图像的视觉表现力。希望你能在下一个项目中尝试使用它们。我们下节课再见。
115:CSS表单样式设计 🎨

在本节课中,我们将学习如何使用CSS来设计和美化HTML表单。表单是网页与用户交互的关键组件,良好的样式设计能提升用户体验。我们将从创建一个基础表单开始,逐步应用CSS样式,使其外观更加美观和专业。
上一节我们介绍了如何使用CSS处理图片,本节中我们来看看如何将CSS应用于表单元素。
创建基础HTML表单结构
首先,我们需要创建一个包含基本输入字段的HTML表单。以下是表单的HTML结构代码:
<div>
<form>
<label for="fname">First Name</label>
<input type="text" id="fname" name="firstname" placeholder="Your name..">
<label for="lname">Last Name</label>
<input type="text" id="lname" name="lastname" placeholder="Your last name..">
<label for="country">Country</label>
<select id="country" name="country">
<option value="aus">Australia</option>
<option value="can">Canada</option>
<option value="usa">USA</option>
</select>
<input type="submit" value="Submit">
</form>
</div>
此代码创建了一个包含姓名输入框、国家下拉菜单和提交按钮的简单表单。
为输入框和下拉菜单添加样式
接下来,我们将使用CSS为文本输入框和下拉菜单添加统一的样式。目标是让它们具有一致的宽度、内边距和边框。
以下是应用于输入框和下拉菜单的CSS规则:
input[type=text], select {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
代码解释:
width: 100%;使元素宽度占满其容器。padding: 12px 20px;设置元素内部的内容与边框之间的上下间距为12像素,左右间距为20像素。margin: 8px 0;设置元素外部的上下边距为8像素,左右边距为0。border: 1px solid #ccc;定义一个1像素宽、灰色的实线边框。border-radius: 4px;为边框添加4像素的圆角。box-sizing: border-box;确保元素的宽度和高度包含内边距和边框,防止布局错乱。
设计提交按钮样式
提交按钮是表单交互的终点,需要设计得醒目且友好。我们将改变其背景色、文字颜色,并添加鼠标悬停效果。
以下是提交按钮的基础样式和悬停效果样式:
/* 基础样式 */
input[type=submit] {
width: 100%;
background-color: #4CAF50;
color: white;
padding: 14px 20px;
margin: 8px 0;
border: none;
border-radius: 4px;
cursor: pointer;
}
/* 悬停效果 */
input[type=submit]:hover {
background-color: #45a049;
}
代码解释:
background-color和color分别设置按钮的背景色和文字颜色。border: none;移除了浏览器为按钮添加的默认边框。cursor: pointer;将鼠标指针在按钮上时变为手形,提示用户此处可点击。:hover是一个伪类选择器,用于定义当用户鼠标悬停在元素上时的样式。
美化表单容器
最后,为了让整个表单区域看起来更完整,我们可以为包裹表单的<div>容器添加一些样式,例如背景色、内边距和圆角。
以下是表单容器的CSS样式:


div {
border-radius: 5px;
background-color: #f2f2f2;
padding: 20px;
}
应用以上所有样式后,我们的表单将从朴素的默认样式转变为具有现代感的、风格统一的设计。


本节课中我们一起学习了如何使用CSS来美化HTML表单。我们掌握了为文本输入框、下拉菜单设置统一样式的方法,设计了醒目且具有交互反馈的提交按钮,并通过美化表单容器使整个表单区域更加协调。这些技能是前端开发中构建用户友好界面的基础。
116:CSS文本效果
在本节课中,我们将学习CSS中一系列用于控制文本样式和布局的属性。这些属性可以帮助我们美化网页中的文字,实现对齐、装饰、间距调整以及阴影效果。
在上一节视频中,我们了解了box-sizing属性的工作原理及其如何帮助避免HTML项目中的布局问题。本节中,我们将深入探讨一些与CSS文本相关的属性。
我们已经学习了font-family、font-weight和font-style等属性如何帮助我们装饰HTML文档中的字体。本视频将介绍其他一些文本相关属性,例如text-align、text-decoration、text-transform、letter-spacing、line-height等。这些属性将帮助我们对齐文本、装饰文本、转换文本大小写,以及在单词和行之间添加间距。
现在,让我们在ST项目中实际操作这些属性。
文本对齐:text-align


以下是如何使用text-align属性来对齐文本。
text-align属性有不同的取值。默认值是left。
text-align: left; /* 默认值,文本左对齐 */
如果我们将值改为right,文本将向右对齐。
text-align: right;

类似地,如果设置为center,文本将居中对齐。

text-align: center;

最后,我们还可以使用justify值。它会自动调整文本,使其左右两端都对齐。
text-align: justify;

当文本左对齐时,文本靠左排列,如果单词在当前行放不下,会自动换行。而使用justify时,它会尝试调整整个文本,使其左右两边都对齐。
文本装饰:text-decoration

接下来,我们看看text-decoration属性。它用于为文本添加装饰线。


如果我们设置text-decoration: overline;,它会在所有文本上方添加一条线。
text-decoration: overline;

如果设置值为line-through,文本中间会有一条贯穿线。
text-decoration: line-through;

如果设置值为underline,文本下方会有一条下划线。
text-decoration: underline;


最后,overline underline值会在文本的上下方都添加线条。
text-decoration: overline underline;

这些是text-decoration属性的一些取值及其用例。
文本转换:text-transform
现在,让我们看看text-transform属性,它用于转换文本的大小写。

如果我们设置text-transform: capitalize;,每个单词的首字母会变成大写。
text-transform: capitalize;
如果设置text-transform: uppercase;,整个文本会变成大写。

text-transform: uppercase;
类似地,如果设置text-transform: lowercase;,整个文本会变成小写。

text-transform: lowercase;

这些是text-transform属性的一些应用。
单词与字母间距:word-spacing 与 letter-spacing
接下来,我们看看word-spacing属性,它用于设置单词之间的间距。
我们可以设置一个值作为单词之间的空间单位。例如,设置10px会在每个单词之间产生10像素的间隙。

word-spacing: 10px;
我们也可以将其设置为负值,例如-1px,这会减少单词间距。如果设置为-10px,单词甚至会重叠。


类似地,letter-spacing属性用于设置每个字母之间的间距。

letter-spacing: 5px;
这会在文本中的每个字母之间产生5像素的间距。

行高:line-height

现在,让我们看看line-height属性,它用于设置文本行之间的高度或间距。
例如,设置line-height: 10px;会将行高减小到10像素。
line-height: 10px;
如果增加到200px,行高会显著增大。
line-height: 200px;
这就是line-height属性及其在HTML/CSS页面中的用法。
文本阴影:text-shadow
最后,我们介绍text-shadow属性,用于为文本添加阴影效果。
应用文本阴影时,我们需要设置四个值:
- 水平阴影偏移量
- 垂直阴影偏移量
- 模糊半径
- 阴影颜色
例如:
text-shadow: 2px 2px 5px red;
2px:水平阴影偏移量2px:垂直阴影偏移量5px:模糊半径red:阴影颜色

我们可以调整这些值来改变阴影效果。增加水平偏移量会使阴影水平方向更明显;增加垂直偏移量会使阴影垂直方向更明显;增加模糊半径会使阴影边缘更模糊。
本节课中,我们一起学习了CSS中一系列重要的文本相关属性,包括对齐、装饰、大小写转换、间距调整和阴影效果。掌握这些属性将极大地增强你美化网页文本的能力。希望你能理解并在下一个CSS项目中应用它们。

我们下个视频再见。
117:CSS object-fit与object-position属性详解 🖼️
在本节课中,我们将学习如何使用CSS的 object-fit 和 object-position 属性来控制HTML元素内图像的显示方式。这两个属性对于处理不同尺寸的图片、保持图片比例以及在容器内精确定位图片至关重要。
在上一节中,我们介绍了如何使用CSS来美化HTML表单。本节中,我们来看看如何精确控制图片在其容器内的显示。
理解 object-fit 属性
object-fit 属性定义了图片(或视频)应如何调整自身以适应其容器。其默认值为 fill。

以下是 object-fit 属性的主要取值及其含义:
fill: 这是默认值。图片会被拉伸以完全填满容器,这可能导致图片变形。contain: 图片会保持其原始宽高比,并缩放至完全被容器容纳。这通常会在图片周围留下空白区域。cover: 图片会保持其原始宽高比,并缩放至完全覆盖容器。图片的某些部分可能会被裁剪掉。none: 图片不会被调整大小,将保持其原始尺寸显示。如果图片比容器大,则只有部分图片可见。scale-down: 图片会缩小到none或contain中尺寸更小的那个版本。


在CSS中,其基本语法为:
img {
object-fit: contain; /* 或其他值 */
}

理解 object-position 属性

object-position 属性用于指定图片在其容器内的位置。它通常与 object-fit 属性(尤其是 cover 或 none)结合使用,以控制图片的哪一部分被显示。


其默认值为 50% 50%,表示图片在水平和垂直方向上都居中。

在CSS中,其基本语法为:
img {
object-fit: cover;
object-position: 25% 75%; /* 调整图片的显示焦点 */
}


属性应用实例

假设我们有一个固定尺寸为 300px 宽、200px 高的容器,并放入一张图片。


以下是不同属性值组合的效果演示:
object-fit: fill- 图片被拉伸以填满整个容器,完全无视原始宽高比,导致变形。

object-fit: contain- 图片保持宽高比,并缩放至能完整放入容器。由于容器比例与图片不同,上下或左右会出现空白区域。

object-fit: cover- 图片保持宽高比,并缩放至完全覆盖容器。由于容器比例与图片不同,图片的边缘部分会被裁剪。

-
object-fit: none- 图片保持原始尺寸。由于容器较小,我们只能看到图片的中心部分。
-
结合
object-position- 当我们使用
object-fit: cover或none时,可以配合object-position来调整图片的显示焦点。 - 例如,
object-position: 20% 80%会将图片水平方向20%、垂直方向80%的点对准容器的对应位置,从而改变被裁剪或显示的区域。
- 当我们使用

本节课中,我们一起学习了CSS的 object-fit 和 object-position 属性。object-fit 控制图片如何适应容器,避免拉伸变形;object-position 则用于精确定位图片在容器内的显示区域。掌握这两个属性,能让你在网页开发中更自如地处理各类图像布局问题。希望你能在接下来的项目中灵活运用它们。
118:课程介绍与JavaScript入门
在本节课中,我们将学习JavaScript的基础介绍,包括如何设置开发环境以及创建你的第一个JavaScript程序。课程结束时,你将理解JavaScript是什么,以及它如何为网页添加动态和交互功能。
🛠️ 开发环境设置
上一节我们介绍了课程目标,本节中我们来看看如何搭建一个基础的JavaScript开发环境。这主要包括安装必要的软件工具。
以下是设置开发环境所需的两个核心组件:
- 代码编辑器:用于编写和编辑JavaScript代码的工具,例如Visual Studio Code、Sublime Text或Atom。
- 网页浏览器:用于运行和测试JavaScript程序的工具,例如Google Chrome、Mozilla Firefox或Microsoft Edge。
📝 编写第一个JavaScript程序
环境准备就绪后,我们就可以开始编写代码了。本节将引导你创建第一个JavaScript程序,并介绍其基本语法。
以下是编写第一个程序时涉及的核心概念:
- 基本语法:JavaScript代码由一系列语句构成,每条语句通常以分号
;结束。 - 常见数据类型:
- 数字:用于表示数值,例如
let age = 25; - 字符串:用于表示文本,需要用引号包裹,例如
let name = "Alice"; - 布尔值:用于表示真或假,即
true或false
- 数字:用于表示数值,例如
一个简单的程序示例如下:
// 这是一个注释,控制台输出一条消息
console.log("Hello, World!");
🌐 JavaScript与网页交互
JavaScript的核心能力在于操作网页内容并响应用户行为。本节我们将探索JavaScript如何与HTML文档互动。
通过JavaScript,我们可以:
- 操纵HTML元素:使用文档对象模型接口来查找、修改或创建页面上的元素。
- 响应用户交互:为网页元素绑定事件监听器,例如点击、鼠标移动或键盘输入,从而执行相应的代码。
📚 课程总结
本节课中我们一起学习了JavaScript的入门知识。我们了解了如何设置包含代码编辑器和浏览器的开发环境,编写了第一个包含基本语法和数据类型的JavaScript程序,并初步认识了JavaScript在操纵HTML和响应用户交互方面的强大功能。
119:JavaScript简介 🚀


在本节课中,我们将学习JavaScript的历史和基本介绍。
概述
JavaScript是一种编程语言,用于为网站添加交互功能。它通常与HTML和CSS一起使用,使网站更具动态性和吸引力。简单来说,JavaScript允许您为网页添加功能。
什么是JavaScript?
JavaScript是一种编程语言,用于为网站添加交互功能。它通常与HTML和CSS一起使用,使网站更具动态性和吸引力。
简单来说,JavaScript允许您为网页添加功能。例如,您可以使用JavaScript创建交互式表单、添加动画,并在用户与网页交互时创建动态效果。
JavaScript的一个优点是它在网络浏览器中运行,这意味着几乎任何带有网络浏览器的设备都可以使用它。这使其成为创建跨平台应用程序的强大工具,可在台式电脑、平板电脑和智能手机上使用。
为什么叫JavaScript?
JavaScript最初由Netscape Communications的Brendan Eich创建。这门语言最初被称为Mocha,但后来更名为LiveScript。
将语言更名为JavaScript的决定主要是Netscape出于营销考虑做出的。当时,该公司正与微软进行激烈的浏览器大战,他们希望通过让语言听起来与Java相似来利用Java的流行度。这有助于形成一种观念,即JavaScript是Java的补充技术,尽管这两种语言有很大不同。
JavaScript已成为世界上使用最广泛的编程语言之一,并且是当今Web开发人员的重要工具。事实上,根据一些估计,互联网上超过95%的网站都使用JavaScript。
JavaScript可以在浏览器之外执行吗?
JavaScript是一种多功能的编程语言,可用于创建前端和后端应用程序。
它主要以在Web开发中的使用而闻名,用于为网页添加交互性和动态效果。然而,JavaScript也可用于构建在Web服务器上运行的服务器端应用程序,以及桌面和移动应用程序,甚至可以在物联网设备上运行。
这种多功能性是JavaScript的最大优势之一,因为它允许开发人员使用单一编程语言在各种平台和设备上构建应用程序。这不仅使开发更高效,还使开发人员能够创建交互式和响应式的用户界面,可以实时响应用户输入。
浏览器中的JavaScript引擎
Web浏览器使用不同的JavaScript引擎来解释和执行JavaScript代码。例如,Chrome使用V8,Firefox使用SpiderMonkey,Internet Explorer使用Chakra。每个引擎都有其独特的功能和性能特点。
浏览器中JavaScript可以执行哪些任务?
以下是浏览器中JavaScript可以执行的一些主要任务:
- 操作HTML和CSS:JavaScript可以更改网页上元素的样式和内容,例如,通过隐藏或显示元素,或更改其颜色、大小,或者为其添加动画。
- 处理用户事件:JavaScript可以响应用户与页面的交互,例如点击按钮、提交表单或滚动,并触发相应的操作,例如显示消息或更新页面内容。
- 验证表单:JavaScript可以检查Web表单中的用户输入,例如,确保必填字段不为空、电话号码有效或密码符合特定标准。
- 添加交互性:JavaScript可以向网页添加交互元素,例如下拉菜单、图像滑块或弹出窗口,以增强用户体验并吸引用户关注内容。
- 发起HTTP请求:JavaScript可以使用HTTP请求从服务器获取数据或将数据发送到服务器,例如,动态更新页面内容或在不重新加载页面的情况下提交表单。
- 存储和操作数据:JavaScript可以将数据存储在变量、数组和对象中,例如,记住用户偏好或跟踪游戏分数,并使用内置函数(如排序或筛选)来操作数据。
总结
本节课中我们一起学习了JavaScript的基础知识。JavaScript是一种高级动态编程语言,用于创建交互式网页和Web应用程序。它最初被称为Mocha,后来更名为LiveScript,最终出于营销目的更名为JavaScript。虽然它主要与Web开发相关,但JavaScript也可以在服务器和其他拥有JavaScript引擎的设备上使用。凭借其多功能性和普及度,JavaScript已成为现代Web开发的基础语言。


本视频内容到此结束。在下一个视频中,我们将学习如何设置您的开发环境。下个视频再见。谢谢。
120:搭建开发环境 🛠️
在本节课中,我们将学习如何为JavaScript开发搭建一个完整的开发环境。一个合适的开发环境是高效编写、测试和调试代码的基础。
在上一节中,我们介绍了JavaScript的基本概念。本节中,我们来看看搭建开发环境所需的工具和步骤。
开发环境的核心组件
要搭建你的开发环境,你需要三样东西:一个代码编辑器、一个网页浏览器和一个JavaScript引擎。
- 代码编辑器:它使我们能够编写和组织代码,并提供诸如语法高亮、自动补全和代码格式化等功能。它还为我们提供了调试工具,以识别和修复代码中的错误。
- 网页浏览器:它对于在真实环境中测试我们的JavaScript代码是必要的。它允许我们查看代码如何与网页上的HTML和CSS交互,并测试用户交互和API请求等功能。
- JavaScript引擎:它使我们能够在网页浏览器之外运行JavaScript代码,这对于构建服务器端应用程序、命令行工具以及其他不依赖网页浏览器的应用程序非常有用。
搭建步骤
以下是搭建开发环境的基本步骤。
第一步:安装代码编辑器
你可以从多种免费或付费的代码编辑器中选择,例如Visual Studio Code、Sublime Text或Atom。这些编辑器提供了语法高亮、自动补全和调试工具等功能,使编写和调试JavaScript代码更加容易。
你可以访问搜索引擎,例如Google。如果你想安装Visual Studio Code,只需搜索“Visual Studio Code下载”,然后根据你的操作系统进行安装。


第二步:安装网页浏览器
你可以选择流行的网页浏览器,例如Google Chrome、Mozilla Firefox或Microsoft Edge。这些浏览器内置了开发者工具,使你能够实时检查和调试你的JavaScript代码。
第三步:安装JavaScript引擎
大多数现代网页浏览器都自带JavaScript引擎,但你也可以安装独立的引擎,例如Node.js或Rhino。这些引擎允许你在网页浏览器之外执行JavaScript代码,例如用于构建服务器端应用程序或命令行工具。
一旦你设置好这些工具,你就可以开始在代码编辑器中编写和运行JavaScript代码,并在你的网页浏览器或JavaScript引擎中进行测试。
总结
本节课中,我们一起学习了搭建JavaScript开发环境所需的三个核心组件:代码编辑器、网页浏览器和JavaScript引擎,并了解了安装它们的基本步骤。一个配置良好的开发环境是后续所有编程工作的起点。

在下一节中,我们将编写我们的第一个JavaScript程序。
121:编写你的第一个JavaScript程序 🚀
在本节课中,我们将学习如何编写并运行你的第一个JavaScript程序。我们将从打开代码编辑器开始,逐步完成编写代码、保存文件以及运行程序的完整流程。
上一节我们介绍了如何搭建开发环境。本节中,我们来看看如何实际编写并执行一段JavaScript代码。
以下是编写第一个JavaScript程序的基本步骤:
- 打开代码编辑器:选择一个你喜欢的代码编辑器,例如Visual Studio Code、Sublime Text或Atom。创建一个新文件,并使用
.js作为文件扩展名。 - 编写代码:在新文件中,你可以编写你的第一个JavaScript程序。例如,你可以写一个简单的“Hello World”程序。
- 保存文件:为你的文件起一个有意义的名称,并确保扩展名为
.js。例如,hello.js。 - 运行程序:打开终端或命令提示符,导航到保存程序文件的目录。然后输入
node hello.js来运行你的程序。你应该能在控制台中看到输出的结果。
现在,让我们实际跟随这些步骤来编写我们的第一个JavaScript程序。
我将前往桌面,创建一个新文件夹并将其重命名为JavaScript。


现在,我们可以打开Visual Studio Code。你也可以使用任何其他代码编辑器。然后,我将打开这个JavaScript文件夹。
目前这个JavaScript文件夹是空的。所以,我们需要创建一个文件,让我们将其命名为hello.js,这是JavaScript文件的扩展名。
接下来,让我们编写我们的第一个程序。在这里,你可以简单地输入:
console.log("Hello World");
这段代码的作用是在控制台中打印“Hello World”。就这么简单,这就是第一段JavaScript代码。
关于如何运行它,有几种方法:
第一种方法是使用Node.js。你可以在VS Code中打开终端。确保你当前所在的目录是正确的,你可以看到这里的目录是这个JavaScript文件夹。然后我可以输入node hello.js。一旦我按下回车,你将在控制台中看到输出结果:Hello World。这是一种方法。
第二种方法是,你可以直接打开Google Chrome浏览器,右键点击并选择“检查”,这会打开Chrome开发者工具。在这里,你可以直接粘贴或输入代码。例如,输入console.log("Hello World");,一旦我按下回车,它就会运行代码,并将输出打印在这里。
第三种方法是使用一个HTML文件。我们可以创建一个新的index.html文件。你需要使用一个基础模板。你可以使用感叹号!作为快捷方式,它会生成一个基础的HTML模板代码。
我们需要做的是,要么通过<script>标签将hello.js文件链接到这个index.html文件中,要么直接在<script>标签内编写JavaScript代码。例如,输入console.log("Hello World");。
如何在浏览器中检查它呢?你可以直接点击index.html文件,复制其路径。将路径粘贴到浏览器的地址栏中。这是一个空白屏幕,因为此时没有任何HTML标签内容。但只要你点击“检查”,你将在控制台中看到打印出的“Hello World”。






恭喜你!你已经编写并执行了你的第一个JavaScript程序。这是一个简单的例子,但它演示了JavaScript程序的基本结构和语法。
本节课中,我们一起学习了编写第一个JavaScript程序的完整步骤,包括在代码编辑器中创建.js文件、使用console.log()函数输出信息,以及通过Node.js、浏览器控制台和HTML文件三种方式来运行JavaScript代码。掌握这些基础是开启JavaScript编程之旅的关键。


122:JavaScript 基础概念入门
在本节课中,我们将学习 JavaScript 的核心基础概念,包括变量、数据类型、运算符以及控制结构。这些是构建任何 JavaScript 程序的基石。
变量与数据类型
上一节我们介绍了本课程的学习目标,本节中我们来看看 JavaScript 中存储和表示信息的基础:变量与数据类型。
变量是用于存储数据值的容器。在 JavaScript 中,你可以使用 let、const 或 var 来声明变量。
JavaScript 的数据类型主要分为两大类:原始类型和复杂类型。
以下是主要的原始数据类型:

- 字符串:用于表示文本,用单引号或双引号包裹。例如:
let name = "Alice"; - 数字:用于表示整数或浮点数。例如:
let age = 25;或let price = 19.99; - 布尔值:表示逻辑真或假,只有两个值:
true和false。例如:let isLoggedIn = true;
除了原始类型,JavaScript 还有更复杂的引用类型,例如数组和对象,它们用于存储集合或键值对数据。
运算符
了解了如何存储数据后,我们需要学习如何操作这些数据。运算符允许我们对值进行计算和比较。
算术运算符用于执行基本的数学运算。
以下是常用的算术运算符:
- 加法:
+ - 减法:
- - 乘法:
* - 除法:
/ - 取余:
%
比较运算符用于比较两个值,并返回一个布尔值(true 或 false)。
以下是常用的比较运算符:
- 等于:
==(值相等) - 严格等于:
===(值和类型都相等) - 不等于:
!= - 大于:
> - 小于:
< - 大于等于:
>= - 小于等于:
<=
控制结构与函数
掌握了数据和运算符,下一步是控制代码的执行流程。这通过条件语句和循环结构来实现。
条件语句(如 if...else)允许你根据不同的条件执行不同的代码块。
循环结构(如 for 循环和 while 循环)让你能够重复执行一段代码,直到满足特定条件。
函数是可重复使用的代码块,用于执行特定任务。定义函数可以提高代码的复用性和组织性。
一个简单的函数定义如下:
function greet(name) {
return "Hello, " + name;
}
字符串操作
字符串是编程中最常用的数据类型之一。JavaScript 提供了丰富的方法来操作字符串,例如连接字符串、获取子字符串、转换大小写等。
本节课中我们一起学习了 JavaScript 的基础概念。你了解了变量如何存储数据,认识了字符串、数字、布尔值等数据类型,学会了使用算术和比较运算符进行计算与判断,并初步接触了条件语句、循环和函数来控制程序逻辑。这些知识为你进一步深入学习 JavaScript 和全栈开发奠定了坚实的基础。
123:理解变量 📦
在本节课中,我们将学习JavaScript中的变量。变量是编程中存储和管理数据的基础工具,理解它们对于编写任何程序都至关重要。
概述
想象你有一盒蜡笔,并希望按颜色整理它们。你可以使用盒子里的不同隔间来分别存放每种颜色。每个隔间就像一个变量,而蜡笔就是存储在这些变量中的数据。例如,可以有一个隔间放红色蜡笔,另一个放蓝色蜡笔。每个隔间里的蜡笔数量会根据你拥有的每种颜色的数量而变化。
在JavaScript中,变量的工作方式与此类似。你可以将变量视为一个容器,它保存着一份数据,就像蜡笔一样。变量有一个你可以选择的名称,就像蜡笔盒隔间上的标签,标明它存放的蜡笔颜色。然后,你可以使用这些变量进行计算或操作其中存储的数据,就像你可以将蜡笔从一个隔间移到另一个隔间一样。大多数时候,JavaScript应用程序都需要处理一些信息。
以下是几个例子:
- 社交媒体应用:信息可能包括用户的姓名、年龄和粉丝数量。
- 音乐应用:信息可能包括用户喜欢的歌曲、播放列表和艺术家。
变量就是用来存储这些信息的。
创建变量

在JavaScript中,你可以使用 let、var 和 const 关键字来创建变量。让我们通过一些例子来理解何时该使用哪一个。
上一节我们介绍了变量的基本概念,本节中我们来看看如何在代码中实际创建和使用它们。
使用 let 声明变量
let 是声明一个在代码后续可以被重新赋值的变量的首选方式。它是块级作用域的,这意味着它只存在于定义它的代码块内部。
以下是创建变量的两个步骤:
- 声明变量:例如
let name; - 初始化变量:例如
name = “John”;
你也可以在同一行完成声明和初始化:
let name = “John”;
让我们看一个 let 的例子:
let count = 0;
count = 1;
console.log(count); // 输出:1
使用 const 声明常量
const 是声明一个不能被重新赋值的变量的首选方式。它也是块级作用域的,通常用于存储常量值。
const PI = 3.14;
console.log(PI); // 输出:3.14
// PI = 3.14159; // 这行代码会报错:Assignment to constant variable.
使用 var 声明变量(传统方式)
var 是JavaScript中声明变量的较旧关键字。虽然在现代JavaScript中仍可使用,但通常被认为是过时的,因为它不是块级作用域的。这意味着用 var 声明的变量在整个函数或全局作用域内都可用,这可能使代码更难理解和避免错误。
var count = 0;
if (true) {
var count = 1;
console.log(count); // 输出:1
}
console.log(count); // 输出:1 (注意:外部的count也被修改了)

总结
本节课中我们一起学习了JavaScript中的变量。变量是在代码中存储和管理数据的一种方式。它们允许你通过名称来引用数据,这使你的代码更有组织性且更易于阅读。
在JavaScript中创建变量,可以使用 let、var 和 const 关键字。let 和 const 是现代JavaScript中声明变量的首选关键字,因为它们是块级作用域的,并且能更好地控制变量赋值。虽然 var 仍然可以使用,但通常被认为是过时的,在新代码中应尽可能避免使用。

下一节视频中,我们将学习JavaScript中的数据类型。下节课见。
124:理解数据类型 📊
在本节课中,我们将要学习 JavaScript 中的数据类型。数据类型定义了程序中可以存储和操作的数据种类。理解数据类型是编写有效、无错误代码的基础。
上一节我们介绍了 JavaScript 中的变量,本节中我们来看看变量可以存储哪些不同类型的数据。
什么是数据类型?
在 JavaScript 中,数据类型代表了可以在程序中存储和操作的数据种类。
以下是 JavaScript 中一些常见的数据类型。

1. 数字类型 (Number)
数字类型用于表示整数和浮点数。在 JavaScript 中,数字使用 Number 类型表示。

让我们通过一个例子来理解。在代码中,可以这样创建一个数字类型的变量:
let x = 5;
这创建了一个名为 x 的变量,其类型为数字,值为 5。
2. 大整数类型 (BigInt)

对于大多数用途,数字类型的范围(从 -(2^53 - 1) 到 2^53 - 1)已经足够。但有时我们需要处理任意长度的超大整数,例如在密码学或微秒级时间戳的场景中。
BigInt 类型是最近添加到语言中的,用于表示任意长度的整数。一个大整数值通过在整数末尾附加 n 来创建。
以下是创建 BigInt 的示例:
const bigInt = 1234567890123456789012345678901234567890n;
如果你想检查任何变量的类型,可以使用 typeof 操作符:

console.log(typeof bigInt); // 输出: bigint
大整数类型很少需要,但在必要时可以像这样使用。

3. 字符串类型 (String)
字符串类型用于表示一个字符序列。在 JavaScript 中,字符串使用 String 类型表示。
让我们看一个例子。要创建一个字符串,可以这样写:
let name = “John”;

这创建了一个名为 name 的变量,其类型为字符串,值为 “John”。同样,你可以使用 typeof 操作符来检查其类型。
4. 布尔类型 (Boolean)

布尔类型表示一个逻辑值,它只能是 true(真)或 false(假)。在 JavaScript 中,布尔值使用 Boolean 类型表示。
看一个例子。要创建一个持有布尔值的变量,可以这样写:
let isTrue = true;

这创建了一个名为 isTrue 的变量,其类型为布尔型,值为 true。
5. 空值类型 (Null)
空值类型用于表示一个对象值的故意缺失。它使用 null 关键字表示,是一个独立的数据类型。



要为一个变量赋予空值,可以这样写:


let emptyValue = null;




本节课中我们一起学习了 JavaScript 中的五种基本数据类型:数字 (Number)、大整数 (BigInt)、字符串 (String)、布尔 (Boolean) 和 空值 (Null)。理解这些类型是掌握变量操作和后续学习更复杂概念的关键。在下一节中,我们将探讨其他数据类型,如未定义 (Undefined) 和对象 (Object)。
125:使用算术与比较运算符 🧮
在本节课中,我们将要学习 JavaScript 中的算术运算符与比较运算符。它们是进行数学计算和逻辑判断的基础工具。
上一节我们介绍了 JavaScript 的数据类型,本节中我们来看看如何对数据进行运算和比较。

算术运算符
算术运算符用于对数值执行数学计算。
以下是 JavaScript 中主要的算术运算符及其符号:
- 加法:
+ - 减法:
- - 乘法:
* - 除法:
/ - 取模(求余):
% - 指数运算:
**
让我们通过代码示例来理解它们。
let x = 10;
let y = 5;
// 加法
let sum = x + y;
console.log(sum); // 输出:15
// 减法
let difference = x - y;
console.log(difference); // 输出:5
// 乘法
let product = x * y;
console.log(product); // 输出:50
// 除法
let quotient = x / y;
console.log(quotient); // 输出:2
// 取模(求余)
let remainder = x % y;
console.log(remainder); // 输出:0
// 指数运算
let result = x ** y; // 10的5次方
console.log(result); // 输出:100000

比较运算符
比较运算符用于比较两个值,并根据比较结果返回一个布尔值(true 或 false)。
以下是 JavaScript 中的比较运算符:

- 等于:
== - 不等于:
!= - 严格等于:
=== - 严格不等于:
!== - 大于:
> - 大于或等于:
>= - 小于:
< - 小于或等于:
<=
让我们通过示例来理解这些运算符。请记住,它们的输出总是布尔值。
let a = 10;
let b = 5;
// 等于
console.log(a == b); // 输出:false
// 不等于
console.log(a != b); // 输出:true
// 严格等于(同时比较值和类型)
console.log(a === b); // 输出:false
// 严格不等于
console.log(a !== b); // 输出:true
// 大于
console.log(a > b); // 输出:true
// 大于或等于
console.log(a >= b); // 输出:true
// 小于
console.log(a < b); // 输出:false
// 小于或等于
console.log(a <= b); // 输出:false

总结 📝
本节课中我们一起学习了 JavaScript 中的两种核心运算符。
- 算术运算符用于执行数学计算,如加法、减法、乘法、除法等。
- 比较运算符用于比较值,并根据比较结果返回布尔值,例如等于、不等于、大于、小于等。

这些运算符是 JavaScript 编程的基础,理解并掌握它们的使用对于后续学习至关重要。下一节,我们将学习 JavaScript 中的数组。
126:JavaScript 中的数组 📚
在本节课中,我们将要学习 JavaScript 中的一个核心概念——数组。数组是一种用于存储多个值的数据结构,理解它对于编写高效的程序至关重要。
概述
上一节我们介绍了 JavaScript 中的算术和比较运算符。本节中,我们来看看如何存储和操作一组数据,也就是数组。
在编程中,数组是一个存储在单个变量名下的值的集合。数组可以容纳任何类型的值,包括数字、字符串,甚至其他数组。
数组的基本概念
数组中的每个值都通过其索引来访问。索引从 0 开始,对应数组中的第一个值。
让我们通过一个例子来理解。假设我们有一个数字数组,它通常以方括号 [] 开始。
let myArray = [10, 20, 30, 40, 50];

在这个例子中,我们有一个名为 myArray 的数组,它包含 5 个值:10, 20, 30, 40, 50。每个值存储在数组的一个独立“槽位”中,并通过其索引号来标识。
- 第一个值
10存储在槽位0,即索引0。 - 第二个值
20存储在索引1。 - 依此类推,索引分别为
2,3,4。
你可以将 JavaScript 中的数组想象成一个带有多个隔层的抽屉。每个隔层都有一个标签,你可以在每个隔层里放置物品。
例如,假设你有一个标签为“水果”的抽屉。在抽屉内部,有标签为“苹果”、“橙子”、“香蕉”的隔层。你可以在每个隔层里放入一种或多种水果。
类似地,在 JavaScript 中,你可以创建一个名为 fruits 的数组,并在其中存储多个值。数组可以包含不同类型的值,如字符串、数字,甚至其他数组。
你可以通过使用索引号来访问数组中的值,就像打开抽屉中特定的隔层一样。
创建与访问数组
以下是创建和访问数组的示例:
// 创建一个字符串数组
let fruits = ["苹果", "橙子", "香蕉"];
// 访问数组中的第一个元素(索引从0开始)
console.log(fruits[0]); // 输出:苹果
// 访问数组中的第二个元素
console.log(fruits[1]); // 输出:橙子
数组的内置方法
数组有一些内置的方法,可以用来操作其中的数据。以下是两个最常用的方法:
1. push 方法
push 方法用于在数组的末尾添加一个新值。
let fruits = ["苹果", "橙子", "香蕉"];
fruits.push("葡萄"); // 在数组末尾添加“葡萄”
console.log(fruits); // 输出:["苹果", "橙子", "香蕉", "葡萄"]
2. pop 方法
pop 方法用于移除数组中的最后一个值。
let fruits = ["苹果", "橙子", "香蕉", "葡萄"];
fruits.pop(); // 移除最后一个元素“葡萄”
console.log(fruits); // 输出:["苹果", "橙子", "香蕉"]
数组还有其他内置方法,但 push 和 pop 是最常用的。

数组语法总结
以下是数组语法的几种形式:
// 1. 数字数组
let numbers = [1, 2, 3, 4, 5];
// 2. 字符串数组
let strings = ["a", "b", "c"];
// 3. 混合数据类型数组
let mixed = [1, "hello", true, null];
创建数组的关键点:
- 使用方括号
[]。 - 用逗号
,分隔各个值。 - 通过索引访问值,例如
fruits[1]。 - 可以通过为特定索引赋值来修改数组中的值。
总结
本节课中我们一起学习了 JavaScript 中的数组。我们了解到:
- 数组是存储在单个变量名下的值的集合。
- 数组可以容纳任何数据类型的值。
- 通过从
0开始的索引可以访问数组元素。 - 可以使用
push和pop等内置方法来操作数组。
理解数组是编程中非常重要的概念,因为它允许你在单个变量名下存储和访问多个值。

下一节视频,我们将学习 JavaScript 中的字符串和字符串操作。下节课见。
127:字符串处理 📝
概述
在本节课中,我们将要学习 JavaScript 中的字符串。字符串是用于存储文本数据的基本数据类型。我们将了解如何创建字符串、获取字符串长度、访问其中的字符,并理解字符串的一个重要特性:不可变性。

字符串基础
上一节我们介绍了数组,本节中我们来看看 JavaScript 中的字符串。
在 JavaScript 中,文本数据以字符串的形式存储。没有专门用于存储单个字符的数据类型。字符串是由引号括起来的字符序列,引号可以是单引号、双引号或反引号。
以下是字符串的示例代码:
let str1 = "Hello, world";
let str2 = 'This is a string';
let str3 = `123`;
可以使用 typeof 操作符来检查变量的类型:
console.log(typeof str1, typeof str2, typeof str3);
// 输出:string string string
单引号和双引号在功能上基本相同。然而,反引号(模板字符串)允许在字符串中嵌入表达式,这提供了扩展功能。
console.log(`1 + 2 = ${1 + 2}`);
// 输出:1 + 2 = 3
表达式通过 ${} 语法嵌入,并会被计算后转换为字符串。
字符串长度
要获取字符串的长度,可以使用 .length 属性。

以下是 .length 属性的使用示例:

let str2 = 'This is a string';
console.log(str2.length);
// 输出:16
请注意,.length 是一个属性,而不是一个方法,因此使用时不需要括号。

访问字符串中的字符
可以像访问数组元素一样,使用方括号表示法来访问字符串中的单个字符。
以下是访问字符串字符的方法:

- 要访问字符串中特定位置的字符,可以使用方括号
[]并提供索引位置(索引从 0 开始)。 - 也可以使用一些内置方法来访问字符。
例如,访问第一个字符:
console.log(str2[0]);
// 输出:T
你可以通过索引访问更多的字符。
字符串的不可变性
需要特别注意的一个重要概念是:字符串在 JavaScript 中是不可变的。这意味着字符串一旦创建,其内容就无法被改变。不可能直接修改字符串中的某个字符。
以下代码演示了尝试修改字符串字符的结果:

let str2 = 'This is a string';
str2[0] = 'H'; // 尝试将索引 0 的字符改为 'H'
console.log(str2); // 再次输出字符串
// 输出:This is a string (字符 'T' 并未改变)
可以看到,即使尝试赋值,原字符串也没有发生任何变化,这证明了字符串的不可变性。
总结
本节课中我们一起学习了 JavaScript 中的字符串。
- 字符串用于存储文本数据,是由引号(单引号、双引号或反引号)括起来的字符序列。
- 反引号(模板字符串)允许使用
${}语法将表达式嵌入字符串中。 - 字符串的
.length属性可以返回其长度。 - 可以使用方括号
[]或内置方法来访问字符串中特定位置的字符。 - 字符串在 JavaScript 中是不可变的,这意味着无法直接修改字符串中的某个字符。

下一节视频中,我们将学习 JavaScript 中的字符串操作。
128:字符串操作入门
在本节课中,我们将学习JavaScript中的字符串操作。字符串操作是指以某种方式修改或处理字符序列,以获得期望的输出。我们将从最基础的概念开始,逐步介绍几种最常用的字符串操作方法。
概述
在上一节视频中,我们学习了字符串的基本概念。本节中,我们将深入探讨如何在JavaScript中操作字符串。字符串是由引号包围的字符序列,例如 "Hello World"。字符串操作涉及对这些字符序列进行各种处理和转换。

以下是几种最常见的JavaScript字符串操作技术。
字符串连接
字符串连接是将两个或多个字符串合并以创建新字符串的过程。在JavaScript中,你可以使用加号(+)运算符或 concat() 方法来实现连接。
让我们通过代码示例来看这两种方法。
let str1 = "Hello";
let str2 = "World";
// 使用加号 (+) 运算符连接字符串
let result1 = str1 + " " + str2;
console.log(result1); // 输出: Hello World

// 使用 concat() 方法连接字符串
let result2 = str1.concat(" ", str2);
console.log(result2); // 输出: Hello World


在上面的例子中,我们创建了两个字符串,然后分别使用 + 运算符和 concat() 方法将它们连接起来,两者都得到了相同的结果。
提取子字符串
子字符串是从一个字符串中提取出来的一部分。在JavaScript中,你可以使用 slice() 方法或 substring() 方法来获取子字符串。
让我们看看这两种方法的具体用法。
let str = "Hello World";
// 使用 slice() 方法提取子字符串
let result1 = str.slice(6, 11);
console.log(result1); // 输出: World
// 使用 substring() 方法提取子字符串
let result2 = str.substring(6, 11);
console.log(result2); // 输出: World
slice() 和 substring() 方法都用于提取字符串的一部分,并返回一个新的字符串,而不会修改原始字符串。它们都可以接受一个或两个参数。当传递两个参数时,方法将返回从第一个索引开始到第二个索引(但不包括第二个索引)之间的所有字符。

总结
本节课中,我们一起学习了JavaScript中的字符串操作。字符串操作是对字符串执行各种操作的过程,例如连接字符串、提取子字符串,以及其他如替换字符、改变字母大小写等。
我们重点介绍了最常用的两种操作:
- 连接:可以使用加号(
+)运算符或concat()方法实现。 - 提取子字符串:可以使用
slice()方法或substring()方法实现。
此外,你还可以探索其他有用的字符串方法,例如:
replace()方法用于替换字符串中的字符。toUpperCase()和toLowerCase()方法用于改变字母的大小写。trim()方法用于移除字符串两端的空白字符。

需要注意的是,所有这些字符串方法都会返回一个新的字符串,而不会修改原始字符串。这就是本节关于字符串操作的全部内容,我们下节课再见。
129:if-else 与 switch 语句
概述
在本节课中,我们将学习 JavaScript 中的条件语句。条件语句允许你根据不同的条件执行不同的操作。我们将重点介绍两种最常见的条件语句:if...else 语句和 switch 语句。

if...else 语句
上一节我们介绍了条件语句的基本概念,本节中我们来看看 if...else 语句。if...else 语句允许你根据一个条件是真是假来执行不同的代码块。
以下是 if...else 语句的基本语法:
if (condition) {
// 条件为真时执行的代码
} else {
// 条件为假时执行的代码
}
现在,让我们通过一个例子来理解 if...else 条件。
let age = 18;
if (age >= 18) {
console.log("你是一个成年人。");
} else {
console.log("你还不是一个成年人。");
}

在这个例子中,if 语句检查变量 age 的值是否大于或等于 18。如果条件为真,则执行 if 后面花括号内的代码。如果条件为假,则执行 else 后面花括号内的代码。
else 条件是可选的,你可以只使用 if 条件。但使用 else 可以更轻松地处理边界情况。
switch 语句

接下来,我们学习 switch 语句。switch 语句是 JavaScript 中另一种条件语句,它允许你将一个变量与一系列值进行比较,并根据匹配情况执行不同的代码块。
以下是 switch 语句的基本语法:
switch (expression) {
case value1:
// 当 expression 等于 value1 时执行的代码
break;
case value2:
// 当 expression 等于 value2 时执行的代码
break;
default:
// 当没有匹配的 case 时执行的代码
break;
}
现在,让我们创建一个 switch 语句的例子。
let day = 2;
switch (day) {
case 1:
console.log("星期一");
break;
case 2:
console.log("星期二");
break;
// 可以继续添加 case 3 到 case 5 对应星期三到星期五
default:
console.log("无效的日期");
break;
}
在这个例子中,switch 语句检查变量 day 的值,并执行与匹配的 case 标签关联的代码块。如果 day 的值是 2,它将输出“星期二”。如果 day 的值是 10(或其他不匹配的值),它将输出“无效的日期”。

总结
本节课中我们一起学习了 JavaScript 中的条件语句。条件语句允许你根据条件是真还是假来执行不同的代码块。
if...else 语句用于测试单个条件,并根据其真假值执行不同的代码块。而 switch 语句用于将一个变量与一系列值进行比较,并根据匹配情况执行不同的代码块。
一个重要的注意事项是,switch 语句通常用作一长串 if...else 语句的替代方案,以使代码更具可读性,尤其是在需要检查许多情况时。

在下一节课中,我们将学习 JavaScript 中的循环结构。
130:循环结构
在本节课中,我们将要学习 JavaScript 中的循环结构。循环是编程中用于重复执行一段代码块的重要工具。
上一节我们介绍了条件语句,本节中我们来看看如何使用循环来高效地处理重复性任务。
什么是循环结构? 🔄
循环结构用于在程序中多次重复执行一个代码块。

在 JavaScript 中,主要有三种循环结构:for 循环、while 循环和 do...while 循环。接下来我们将详细讨论每一种。
for 循环
for 循环是最常用的循环,适用于你明确知道代码块需要执行多少次的情况。
其语法如下:
for (初始化; 条件; 增量) {
// 要执行的代码
}
- 初始化语句 在循环开始前执行,通常用于声明和初始化一个计数器变量。
- 条件 在每次循环迭代开始时被评估。如果条件为
true,则执行循环体内的代码。 - 增量语句 在每次循环迭代结束时执行,通常用于更新计数器变量的值。
让我们看一个在 VS Code 中的例子。以下是一个打印数字 1 到 10 的 for 循环:

for (let i = 1; i <= 10; i++) {
console.log(i);
}
首先,i 被初始化为 1。然后检查条件 i <= 10,由于 1 小于等于 10,条件为真,因此执行 console.log(i) 打印出 1。接着执行 i++ 将 i 增加到 2。这个过程会重复,直到 i 变为 11,此时条件 i <= 10 为假,循环结束。
运行此程序,输出结果为:1, 2, 3, ..., 10。
while 循环

while 循环适用于当你不知道代码块需要执行多少次,但希望在某个条件为真时持续执行的情况。
其语法如下:
while (条件) {
// 要执行的代码
}
条件在每次循环迭代开始时被评估。如果条件为 true,则执行循环体内的代码。循环会持续执行,直到条件变为 false。
现在,我们使用 while 循环来实现同样的功能(打印 1 到 10):

let i = 1; // 在循环外初始化变量
while (i <= 10) {
console.log(i);
i++; // 在循环体内更新变量
}
这里,我们在循环开始前将 i 初始化为 1。只要 i <= 10 为真,就会打印 i 的值并将其增加 1。运行此程序,会得到与 for 循环相同的输出。
do...while 循环
do...while 循环与 while 循环类似,但有一个关键的不同点。
其语法如下:
do {
// 要执行的代码
} while (条件);
在 do...while 循环中,循环体内的代码至少会执行一次,因为条件是在代码块执行之后才被检查的。代码块执行后,再评估条件。如果条件为 true,循环继续执行;如果为 false,则退出循环。

让我们用 do...while 循环来打印 1 到 10:
let i = 1;
do {
console.log(i);
i++;
} while (i <= 10);
运行此程序,输出结果依然是 1 到 10。
为了展示其“至少执行一次”的特性,我们修改一下条件:
let i = 1;
do {
console.log(i);
i++;
} while (i <= 0); // 条件初始即为假
即使初始条件 i <= 0 为假,循环体 console.log(i) 也会执行一次,因此输出结果为 1。

总结
本节课中我们一起学习了 JavaScript 的三种主要循环结构。
for循环:当你明确知道循环需要执行的次数时非常有用。while循环:当你不确定循环次数,但希望在某条件为真时持续执行时非常有用。do...while循环:与while循环类似,但它保证循环体内的代码至少会执行一次,然后再检查条件。
通过使用这些循环结构,你可以在 JavaScript 程序中遍历数据集合或执行重复性任务。

在下一节视频中,我们将学习函数及其作用域。
131:函数与作用域 🧩
在本节课中,我们将学习 JavaScript 中的两个核心概念:函数和作用域。函数是组织代码、实现复用的关键,而作用域则决定了变量在程序中的可访问性。理解它们对于编写清晰、高效的代码至关重要。
函数的定义与语法
上一节我们介绍了循环结构,本节中我们来看看如何通过函数来封装和复用代码。
函数是 JavaScript 的基本组成部分。它是一个执行特定任务的代码块。你可以使用函数将代码组织成可重用的片段,这些片段可以在程序的不同部分被调用。
在 JavaScript 中,你可以使用 function 关键字来定义函数。让我们看一下函数的语法:
function functionName(parameter1, parameter2) {
// 要执行的代码块
return value;
}

以下是语法的组成部分:
function关键字:用于在 JavaScript 中定义函数。- 函数名:你为函数起的名字。它应具有描述性,能表明函数的功能。
- 参数:函数接受的输入值。参数是可选的,你可以根据需要定义任意数量,它们之间用逗号分隔。
- 代码块:包含需要执行的 JavaScript 代码。当你调用函数时,这部分代码会执行。它可以包含任何有效的 JavaScript 代码,包括其他函数、循环和条件语句。
- 返回值:
return语句用于从函数返回一个值。如果函数没有返回值,则默认返回undefined。
函数示例
让我们通过一个例子来具体理解。以下是一个简单的加法函数:
function addNumbers(number1, number2) {
let sum = number1 + number2;
return sum;
}
let result = addNumbers(5, 10); // 函数调用
console.log(result); // 输出:15
在这个例子中:
number1和number2是函数的参数。5和10是调用函数时传递的实参。- 函数计算两数之和并通过
return语句返回结果。 addNumbers(5, 10)是函数调用(或函数调用),它执行函数内的代码。

作用域的概念
理解了如何创建和使用函数后,我们需要了解变量在何处可以被访问,这就是作用域。
作用域是决定变量和函数在程序中可访问性的一组规则。在 JavaScript 中,主要有两种作用域:全局作用域和局部作用域。

以下是两种作用域的区别:
- 全局作用域:在任何函数或代码块之外声明的变量。它们可以在程序的任何地方被访问。
- 局部作用域:在函数或代码块内部声明的变量。它们只能在其被声明的函数或代码块内部被访问。
作用域示例
让我们通过代码来观察全局变量和局部变量的不同行为:
// 全局作用域
let globalVariable = “我是全局变量”;
function demonstrateScope() {
// 局部作用域
let localVariable = “我是局部变量”;
console.log(globalVariable); // 可以访问全局变量
console.log(localVariable); // 可以访问局部变量
}
demonstrateScope(); // 调用函数
console.log(globalVariable); // 可以访问全局变量
console.log(localVariable); // 报错:ReferenceError: localVariable is not defined
运行这段代码,你会看到:
- 在函数
demonstrateScope内部,可以成功打印全局变量和局部变量。 - 在函数外部,可以打印全局变量。
- 在函数外部尝试打印局部变量
localVariable会导致ReferenceError错误,因为它只在函数内部的局部作用域中有效。
这个例子清晰地展示了局部变量的“封装”特性:它在函数内部创建和使用,外部代码无法直接访问它。

课程总结
本节课中我们一起学习了 JavaScript 的函数与作用域。
- 函数是可调用和执行的代码块,它们可以接收参数并返回值,是实现代码复用的核心工具。
- 作用域决定了变量在程序中的可访问位置。
- 全局作用域的变量在任何函数或代码块外声明,可在程序任何地方访问。
- 局部作用域的变量在函数或代码块内声明,仅在其内部可访问。
理解作用域对于编写整洁、高效且无错误的代码非常重要。通过合理使用函数和作用域,你可以更好地组织代码结构,避免变量冲突。

本节内容到此结束,我们下节课再见。
132:课程概述
在本节课中,我们将学习文档对象模型(DOM),了解如何使用 JavaScript 访问和操作 DOM 元素、修改样式与属性,并初步接触事件与事件监听器。
什么是 DOM?
上一节我们介绍了课程的整体目标,本节中我们来看看 DOM 的核心概念。文档对象模型(Document Object Model,简称 DOM)是 HTML 和 XML 文档的编程接口。它将文档表示为一个由节点和对象组成的结构化树,允许编程语言(如 JavaScript)与页面内容进行交互。
简单来说,当浏览器加载一个网页时,它会创建该页面的 DOM 表示。这个 DOM 树由各种类型的节点构成,例如元素节点、文本节点和属性节点。
课程核心内容
以下是本课程将涵盖的主要知识点:
- 访问与操作 DOM 元素:学习如何使用 JavaScript 选择页面上的特定元素,并对其进行修改。
- 修改样式和属性:了解如何动态地改变元素的视觉外观(如颜色、大小)及其属性(如
id、class、src)。 - 事件与事件监听器:探索如何响应用户的操作(如点击、鼠标移动、键盘输入),使网页具有交互性。
学习目标
在本节课结束时,你将达成以下目标:
- 对文档对象模型及其结构有透彻的理解。
- 掌握使用 JavaScript 访问和操作 DOM 的方法。
- 学会利用事件创建交互式用户界面。
- 能够动态更改网页上的内容。
- 了解如何验证用户输入。
本节课中,我们一起学习了 DOM 的基本概念、本课程的核心内容以及最终的学习目标。接下来,我们将深入每个部分,从如何访问 DOM 元素开始我们的实践之旅。
133:理解文档对象模型(DOM)
在本节课中,我们将学习文档对象模型(DOM)及其与HTML的关系。DOM是Web开发中用于动态操作网页内容的核心技术。
概述
DOM代表文档对象模型。它是一个用于Web文档的编程接口。DOM将HTML或XML文档表示为一个树状结构,树中的每个节点对应文档的不同部分。DOM是Web开发者使用JavaScript与网页内容交互的一种方式,允许他们动态地添加、移除或修改元素。
什么是DOM?
你可以将网页想象成一棵树,其中每个分支代表网页中的一个元素(如段落或图像),每个叶子代表该元素的属性或特性(如段落内的文本或图像的源URL)。这本质上就是DOM。
JavaScript可以通过选择节点并更改其属性,或完全添加和移除节点来操作DOM。
例如,你可以使用JavaScript来:
- 更改段落标签内的文本。
- 向页面添加新的段落或图像。
- 从页面完全移除一个元素。
- 更改元素的属性,如大小、颜色或位置。
DOM的树状结构
以下是一个树状结构的示例,它代表了给定网页DOM的层次结构。


树中的每个节点对应一个HTML元素或该元素内的一段文本。节点以父子关系排列,父节点位于树的上层,子节点从其父节点下方分支出来。
例如,document 是 HTML 的父节点,HTML 是 document 的子节点。
在这个特定的图中:
- 根节点是
document对象,它代表整个网页。 - 第一级子节点对应顶层的HTML元素,如
HTML,然后我们有head和body。 - 这些节点中的每一个都有一个或多个子节点,具体取决于网页的结构。
head有进一步的子节点,body也有进一步的子节点。
例如,body 节点可能具有对应于标题部分、主要内容区域和页脚部分的子节点。这些子节点中的每一个可能还有进一步的子节点,如段落、图像或其他元素。
例如,这里我们在 body 中有一个 H1,而这个 H1 有一个进一步的子节点,即文本“welcome to my page”。
这个树状图有助于可视化网页的结构,并理解不同元素之间是如何相互关联的。通过遍历树并选择不同的节点,开发者可以使用JavaScript修改网页内容,并创建动态和交互式的效果。
如何操作DOM?
上一节我们介绍了DOM的树状结构,本节中我们来看看JavaScript如何与之交互。开发者通过访问和操作这些节点来实现动态效果。

以下是操作DOM的基本思路:
- 选择节点:使用JavaScript方法(如
document.getElementById)找到树中的特定元素。 - 修改属性:更改选中节点的属性,例如
element.textContent或element.style.color。 - 改变结构:添加新节点(
document.createElement,appendChild)或移除现有节点(removeChild)。
总结
本节课中我们一起学习了文档对象模型(DOM)。DOM是一个用于Web文档的编程接口,它以节点树的形式表示HTML或XML文档。树中的每个节点对应文档的不同部分。JavaScript可以通过选择节点并更改其属性,或完全添加和移除节点来操作DOM。DOM是Web开发中的一个强大工具,它使开发者能够创建动态、交互式的网页。
在下一个视频中,我们将学习如何使用JavaScript访问和实际操作DOM。下个视频见。谢谢。


134:使用 JavaScript 访问 DOM 元素 🎯
在本节课中,我们将学习如何使用 JavaScript 访问网页中的 DOM 元素。这是实现动态网页交互的基础。

概述
上一节我们介绍了 DOM 的概念。本节中,我们来看看如何通过 JavaScript 代码来访问和选取 DOM 树中的特定元素。掌握这些方法是操作网页内容的第一步。


访问 DOM 元素的方法
以下是 JavaScript 中几种常用的 DOM 选择器。

1. 通过 ID 获取元素

document.getElementById() 方法返回拥有指定 ID 的第一个元素。由于 ID 在 HTML 中应是唯一的,此方法通常返回单个元素。

代码示例:
let element = document.getElementById('hello');
console.log(element);
在 HTML 中,你需要有一个 ID 为 hello 的元素,例如 <div id="hello">Hello World</div>。执行上述代码后,控制台将输出这个 <div> 元素。
2. 通过类名获取元素
document.getElementsByClassName() 方法返回一个包含所有指定类名的元素的集合(HTMLCollection)。请注意方法名中的 Elements 是复数形式。


代码示例:
let items = document.getElementsByClassName('green');
console.log(items);
此代码会选取所有 class 属性包含 green 的元素。即使只有一个元素匹配,返回的也是一个集合。

3. 通过标签名获取元素
document.getElementsByTagName() 方法返回一个包含所有指定标签名的元素的集合。


代码示例:
let listItems = document.getElementsByTagName('li');
console.log(listItems);
这段代码会选取文档中所有的 <li> 列表项元素。

4. 使用 CSS 选择器获取单个元素

document.querySelector() 方法返回文档中匹配指定 CSS 选择器的第一个元素。它非常强大,可以使用任何有效的 CSS 选择器。
代码示例:
let firstGreenItem = document.querySelector('.green');
console.log(firstGreenItem);
即使有多个元素拥有 green 类,此方法也只会返回第一个匹配的元素。

5. 使用 CSS 选择器获取所有元素


document.querySelectorAll() 方法返回一个包含文档中匹配指定 CSS 选择器的所有元素的 NodeList。
代码示例:
let allGreenItems = document.querySelectorAll('.green');
console.log(allGreenItems);
与 querySelector 不同,此方法会返回所有匹配的元素。
总结

本节课中我们一起学习了访问 DOM 元素的五种核心方法。我们了解到,DOM 是一个代表网页 HTML 元素的树状结构。JavaScript 提供了 getElementById、getElementsByClassName、getElementsByTagName、querySelector 和 querySelectorAll 等方法来选取这些元素。一旦获得对元素的引用,你就可以修改其属性、内容、样式,或者创建新元素,从而构建出能够响应用户输入并实时更新的动态交互网页。在接下来的视频中,我们将学习如何使用 JavaScript 来操作 DOM。
135:使用 JavaScript 操作 DOM 元素 🛠️
在本节课中,我们将学习如何使用 JavaScript 来操作网页上的 DOM 元素。上一节我们介绍了如何访问 DOM 元素,本节中我们来看看如何动态地修改它们。
概述
使用 JavaScript 操作 DOM,意味着可以在不重新加载整个页面的情况下,动态地修改网页上的元素。例如,一个网页上有一个按钮和一个段落元素,你可以使用 JavaScript 在按钮被点击时,改变段落内的文本,而无需刷新页面。
要实现这一点,首先需要使用 DOM 选择器选中你想要修改的元素。DOM 选择器是一种允许你选择网页上一个或一组元素的方法。我们在之前的视频中已经介绍了几种不同的 DOM 选择器。
一旦选中了目标元素,你就可以访问并修改它的属性。属性是元素的可变特征,例如其文本内容、属性和样式。

实践操作

现在,让我们通过一些例子来具体看看如何操作。

访问文档属性

首先,我们可以访问 document 对象的各种属性。以下是一些基本示例:
// 获取并打印当前页面的域名
console.log(document.domain);

// 获取并打印当前页面的完整 URL
console.log(document.URL);

// 获取并修改页面标题
document.title = "DOM 操作";
console.log(document.title);

// 获取整个文档的 body 元素
console.log(document.body);


// 获取整个文档的 head 元素
console.log(document.head);
执行上述代码,你可以在浏览器的开发者工具控制台中看到相应的输出,例如域名、URL 以及修改后的页面标题。

向页面添加文本


要向页面添加文本,你需要先选中一个元素,然后使用相应的方法。以下是向 body 添加文本的步骤:


- 选中
body元素。 - 使用
append或appendChild方法添加内容。

// 1. 选中 body 元素
const body = document.body;

// 2. 向 body 追加文本
body.append("Hello");

执行后,单词 “Hello” 会出现在网页上。
创建并添加新元素

除了添加文本,我们还可以创建全新的 HTML 元素并将其添加到文档中。以下是创建一个 <div> 元素并添加文本的步骤:
// 1. 创建一个新的 div 元素
const div = document.createElement('div');

// 2. 为这个 div 元素设置文本内容
div.innerText = "这是一个用 JavaScript 创建的 div";
// 3. 将这个 div 元素添加到 body 中
body.appendChild(div);

执行上述代码后,页面上会出现一个包含指定文本的新 <div> 元素。
从页面中移除元素

同样,我们也可以移除已存在的元素。以下是两种移除元素的方法:

方法一:通过父元素移除
// 假设 ‘div’ 是要移除的元素,且 ‘body’ 是其父元素
body.removeChild(div);

方法二:元素自行移除
// 直接调用元素自身的 remove 方法
div.remove();

两种方法都可以成功将指定的 <div> 元素从页面中删除。
核心方法总结

在本节实践中,我们使用了以下几个核心方法:
document.createElement(tagName):用于创建一个新的指定类型的 HTML 元素。element.innerText:用于获取或设置元素的文本内容。parentElement.appendChild(childElement):将一个元素节点添加到指定父节点的子节点列表末尾。parentElement.removeChild(childElement):从父元素中移除一个指定的子元素。element.remove():将元素自身从它所属的 DOM 树中移除。
总结

本节课中我们一起学习了如何使用 JavaScript 操作 DOM 元素。通过 DOM 选择器选中元素后,我们可以修改其文本内容、创建并添加新元素,或者移除现有元素。这些操作使得网页能够实现动态、交互式的用户体验,而无需重新加载。在下一节视频中,我们将进一步学习如何使用 JavaScript 修改元素的样式和属性。
136:使用JavaScript修改样式与属性 🎨
概述
在本节课中,我们将学习如何使用JavaScript动态修改网页元素的样式和属性。通过掌握这些方法,你可以让网页元素根据用户交互或程序逻辑实时改变外观和行为。
在上一节中,我们学习了如何使用JavaScript操作DOM元素。本节中,我们来看看如何具体修改这些元素的样式和属性。
修改样式与属性的基本概念
使用JavaScript修改样式和属性,可以让你动态地改变网页元素的外观和行为。

若要修改样式,可以使用元素的 style 属性来访问和修改其CSS样式。
若要修改属性,可以使用 setAttribute 方法来添加或修改元素上的属性。
让我们通过一些示例来理解这些方法和属性。
实践示例
以下是一个HTML模板,其中包含一个<div>和两个<span>元素,我们将在其基础上编写JavaScript代码。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>修改样式与属性</title>
</head>
<body>
<div>
<span id="one" name="first">这是第一个span。这是一些文本。</span>
<span id="two" name="second">第二个span。</span>
</div>
<script>
// 使用DOM选择器选中元素
const div = document.querySelector('div');
const span1 = document.getElementById('one');
const span2 = document.getElementById('two');
</script>
</body>
</html>
操作属性
以下是操作元素属性的几种方法。
首先,使用 getAttribute 方法获取属性值。


console.log(span1.getAttribute('name')); // 输出:first
其次,使用 setAttribute 方法设置或修改属性。

span1.setAttribute('name', 'ABCDE');
// 执行后,span1的name属性值从“first”变为“ABCDE”
第三,使用 removeAttribute 方法移除属性。

span1.removeAttribute('name');
// 执行后,span1的name属性被移除


此外,对于某些标准属性(如id),可以直接通过元素对象进行赋值。
span1.id = 'newUniqueId';
// 直接修改了span1的id属性

操作类名
使用 classList 属性可以方便地添加或移除CSS类。

以下是添加和移除类的方法。

// 添加类
span2.classList.add('new-class');
// 执行后,span2会拥有一个名为“new-class”的CSS类

// 移除类
span2.classList.remove('new-class');
// 执行后,“new-class”类被从span2上移除
操作样式
使用元素的 style 属性可以直接修改其内联样式。


以下是修改颜色和背景色的示例。
// 修改文本颜色
span1.style.color = 'red';
// 执行后,span1的文本颜色变为红色


// 修改背景颜色
span2.style.backgroundColor = 'green';
// 执行后,span2的背景颜色变为绿色
总结
本节课中我们一起学习了如何使用JavaScript动态修改网页元素的样式和属性。
style属性可用于访问和修改元素的CSS样式。classList属性可以添加或移除CSS类,从而应用预定义的样式。setAttribute方法可以添加或更改元素的属性。getAttribute方法可以获取属性的值。removeAttribute方法可以从元素上移除指定的属性。
这些功能使开发者能够创建可以响应用户输入并实时变化的交互式网页。

在下一节视频中,我们将学习JavaScript中的事件与事件监听器。
137:理解事件与事件监听器


在本节课中,我们将要学习JavaScript中的事件与事件监听器。它们是实现网页交互功能的核心,能够响应用户的点击、输入等操作。
什么是事件?🤔
上一节我们介绍了如何使用JavaScript修改样式和属性。本节中,我们来看看如何让网页“动起来”。
事件指的是在网页上发生的动作或事情,例如鼠标点击、键盘按键或页面加载完成。JavaScript提供了一种方式来监听这些事件,并通过事件监听器对它们做出响应。
事件在JavaScript中至关重要,因为它们允许我们创建动态和交互式的网页。没有事件,网页将是静态且无响应的。事件为用户与网页交互以及网页对用户输入做出反应提供了途径。
事件监听器的工作原理 ⚙️
为了监听JavaScript中的事件,我们使用事件监听器。事件监听器是在特定事件被触发时调用的函数。我们可以使用 addEventListener 方法将事件监听器附加到DOM元素上。当事件发生时,事件监听器函数被调用,我们便可以执行一些操作来响应该事件。
以下是其核心代码结构:
element.addEventListener('事件类型', function() {
// 事件触发时执行的代码
});
常见事件类型示例 📝
以下是几种常见的事件类型及其在JavaScript中的典型用途:
- 点击事件:当用户点击页面上的元素(如按钮或链接)时触发。我们可以使用点击事件来执行诸如显示或隐藏内容、提交表单或导航到不同页面的操作。
- 输入事件:当用户在输入字段中键入内容或在下拉菜单中选择时触发。我们可以使用输入事件来验证用户输入或实时更新页面。
- 加载事件:当页面完成加载时触发。我们可以使用加载事件来执行依赖于页面内容完全加载的操作,例如从API获取数据或初始化插件。
- 鼠标悬停事件:当用户将鼠标悬停在页面上的元素(如图像或链接)上时触发。我们可以使用鼠标悬停事件来添加视觉特效,如工具提示或悬停状态。
这些只是我们可以在JavaScript中使用的众多事件类型中的几个例子。通过使用事件监听器,我们可以使网页更具交互性,并能更好地响应用户操作。
总结 📚
本节课中我们一起学习了JavaScript中的事件与事件监听器。
- JavaScript中的事件指的是在网页上发生的动作,如鼠标点击、键盘按键或页面加载。
- 事件之所以重要,是因为它们使我们能够创建动态和交互式的网页。
- 为了监听事件,我们使用事件监听器,它是在特定事件触发时被调用的函数。
- 我们可以使用
addEventListener方法将事件监听器附加到DOM元素上。
总而言之,事件是使用JavaScript创建交互式网页的基础部分,它使我们能够为用户创造动态且响应迅速的网页体验。


在下一节视频中,我们将学习如何响应用户输入,例如点击或按键事件。
138:响应用户输入(点击与按键)🎯
在本节课中,我们将学习如何使用 JavaScript 来响应用户的输入,例如鼠标点击和键盘按键。这是构建交互式应用和网站的核心技能。
上一节我们介绍了 JavaScript 中的事件和事件监听器。本节中,我们来看看如何具体响应两种常见的用户输入:点击事件和按键事件。
响应用户输入概述

构建交互式应用或网站时,响应用户输入(如点击或按键)至关重要,它能提供更好的用户体验。这需要使用事件监听器来检测用户何时与应用交互,并执行特定代码来响应这些事件。
以下是两种主要的用户输入响应方式。

1. 点击事件响应 🖱️

你可以使用 JavaScript 为 HTML 元素添加事件监听器,以检测它们何时被点击。例如,可以创建一个按钮,当点击时触发一个弹出窗口。
以下是一个在 VS Code 中创建点击事件响应的示例。
首先,创建一个带有最小化 HTML 模板的 index.html 页面,其中包含一个脚本标签。

<button id="myButton">点击我</button>

接下来,在脚本标签内编写 JavaScript 代码。

const button = document.querySelector('#myButton');
button.addEventListener('click', function() {
alert('Hello World!');
});
在这个例子中,我们使用 addEventListener 函数为按钮附加了一个点击事件监听器。当事件被触发时,传入的回调函数就会执行,弹出一个显示“Hello World!”的警告框。

2. 按键事件响应 ⌨️
你也可以检测用户何时在键盘上按下按键,并执行代码作为响应。例如,可以创建一个搜索框,在用户输入时自动更新搜索结果。
以下是如何实现按键事件响应的示例。
首先,在 HTML 中创建一个输入框。

<input id="searchBox" type="text">
然后,在脚本标签内编写 JavaScript 代码。

const searchBox = document.querySelector('#searchBox');
searchBox.addEventListener('keyup', function(event) {
console.log(event);
// 此处可以添加更新搜索结果的代码
});

在这个例子中,addEventListener 函数为搜索框输入元素附加了一个 keyup 事件监听器。每当用户在搜索框中输入并释放一个按键时,传入的匿名函数就会执行。函数内部的代码可以用于更新搜索结果,目前我们只是将事件对象打印到控制台。

总结
本节课中我们一起学习了如何响应用户输入,如点击和按键,这是构建交互式应用或网站的重要组成部分。这涉及使用事件监听器来检测用户交互,并执行特定代码来响应这些事件。
我们通过两个示例演示了如何响应用户输入:
- 使用点击事件来触发弹出窗口。
- 使用按键事件来在用户输入时更新搜索结果。
要实现这些功能,需要使用 HTML 元素来触发事件,并使用 JavaScript 代码来附加事件监听器并执行所需的功能。

在下一节视频中,我们将使用事件来创建交互式用户界面。下节课见!
139:使用事件创建交互式用户界面
在本节课中,我们将学习如何使用JavaScript事件来创建交互式的用户界面。我们将通过构建一个简单的待办事项列表应用,来演示如何响应用户的点击操作,并动态改变页面元素的样式。
概述

在上一节视频中,我们学习了如何响应用户输入,例如点击和按键事件。本节中,我们将利用事件来创建一个交互式的用户界面。事件是浏览器中发生的动作,例如用户点击按钮或滚动页面,它们可以用来触发应用程序中的特定功能。通过事件,我们可以创建能够实时响应用户操作的交互式界面。
创建交互式界面
现在,让我们开始创建一个交互式界面。假设你正在构建一个待办事项列表应用,并希望允许用户通过点击项目来将其标记为已完成。

首先,我们需要一个基本的HTML结构。在<body>标签内,我们创建一个标题和一个无序列表,其中包含几个待办事项。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>交互式待办列表</title>
<style>
.completed {
text-decoration: line-through;
color: gray;
}
</style>
</head>
<body>
<h1>待办事项列表</h1>
<ul>
<li>购买杂货</li>
<li>洗衣服</li>
<li>打扫房间</li>
</ul>
<script>
// JavaScript 代码将写在这里
</script>
</body>
</html>
添加JavaScript交互逻辑
接下来,我们将在<script>标签内编写JavaScript代码,为列表项添加点击事件监听器。
以下是实现步骤:
- 使用
document.querySelectorAll选择所有的列表项(<li>元素)。 - 遍历这些列表项。
- 为每个列表项添加一个
click事件监听器。 - 在事件处理函数中,切换一个名为
completed的CSS类。
const listItems = document.querySelectorAll('li');
listItems.forEach(item => {
item.addEventListener('click', () => {
item.classList.toggle('completed');
});
});
代码解释:
document.querySelectorAll('li'):获取页面中所有的<li>元素。.forEach():遍历每一个获取到的列表项。addEventListener('click', ...):为每个列表项添加点击事件监听。classList.toggle('completed'):在点击时,切换completed类的状态(如果存在则移除,不存在则添加)。
定义样式
我们在<style>标签中定义了.completed类的样式,它会给文本添加删除线并将颜色变为灰色,以直观地表示任务已完成。

.completed {
text-decoration: line-through;
color: gray;
}
效果演示

保存文件并在浏览器中打开。当你点击“购买杂货”这个列表项时,它会立即被划掉并变成灰色。再次点击,样式会被移除,表示任务未完成。
这个简单的例子展示了如何将HTML、CSS和JavaScript结合起来,通过事件驱动的方式创建出交互式的用户体验。

总结
本节课中,我们一起学习了使用事件创建交互式用户界面的核心方法。事件是Web应用实现交互性的关键,它可以用来检测用户的点击、按键等操作,并触发相应的功能来响应。
通过为待办列表项添加点击事件监听器,并在回调函数中切换CSS类,我们实现了一个动态标记任务完成状态的功能。这体现了如何利用事件监听器处理用户输入,从而构建出吸引人且动态的应用程序,提供流畅的用户体验。

我们通过一个现实中的应用示例(待办列表应用)具体展示了事件的用法。掌握事件处理是成为前端开发者的重要一步。
140:课程概述与学习目标
在本节课中,我们将学习如何使用 JavaScript 动态地创建和移除 DOM 元素,以及如何利用 AJAX 技术动态加载内容。这些技能是构建现代、交互式网页应用的基础。
🎯 本课你将学到什么

在本课中,你将学习如何使用 JavaScript 创建和移除 DOM 元素,如何使用 AJAX 动态加载内容,以及其他相关知识。

📚 课程学习成果
在本课结束时,你将理解如何使用 JavaScript 创建和移除 DOM 元素,从而能够动态修改网页的结构。你还将学会如何使用 AJAX 动态加载内容,使你能够从服务器获取数据并更新页面,而无需重新加载整个页面。
总结
本节课我们一起学习了本课程的核心目标:掌握动态修改网页结构的 DOM 操作方法,以及实现无刷新数据交互的 AJAX 技术。在接下来的章节中,我们将深入探讨这些概念的具体实现。
141:使用 JavaScript 创建与移除 DOM 元素
在本节课中,我们将详细学习如何使用 JavaScript 动态地创建和移除网页中的 DOM 元素。这是 Web 开发中动态修改页面内容的一项基础技术。

JavaScript 提供了多种方法来创建和操作 DOM 元素,包括 createElement、appendChild、append、remove 和 removeChild 等。

上一节我们介绍了 DOM 的基本概念,本节中我们来看看如何通过代码实践这些操作。
实践示例:动态食物列表
让我们通过一个具体的例子来理解。首先,我们准备一个基础的 HTML 模板。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM 操作示例</title>
</head>
<body>
<h1>我最喜欢的食物</h1>
<ul id="foodList">
<li>Pizza</li>
<li>Sushi</li>
</ul>
<button id="addButton">添加 Tacos</button>
<button id="removeButton">移除 Sushi</button>
<script src="script.js"></script>
</body>
</html>

接下来,我们编写 JavaScript 代码来实现动态添加和移除列表项的功能。
以下是实现此功能的核心步骤:
- 选择 DOM 元素:使用
querySelector方法获取页面上的按钮和列表。 - 添加事件监听器:为“添加”和“移除”按钮绑定点击事件。
- 创建新元素:在点击“添加”按钮时,使用
createElement和createTextNode方法创建一个新的列表项。 - 将元素加入 DOM:使用
appendChild方法将新创建的元素添加到列表中。 - 从 DOM 中移除元素:在点击“移除”按钮时,使用
removeChild方法将指定的列表项从 DOM 中删除。
// 1. 选择 DOM 元素
const addButton = document.querySelector('#addButton');
const removeButton = document.querySelector('#removeButton');
const foodList = document.querySelector('#foodList');
// 2. 为“添加”按钮添加事件监听器
addButton.addEventListener('click', function() {
// 3. 创建新的列表项元素和文本节点
const newListItem = document.createElement('li');
const newListItemText = document.createTextNode('Tacos');
// 4. 将文本节点附加到列表项元素
newListItem.appendChild(newListItemText);
// 5. 将新的列表项添加到食物列表中
foodList.appendChild(newListItem);
});
// 6. 为“移除”按钮添加事件监听器
removeButton.addEventListener('click', function() {
// 7. 选择要移除的列表项(第二个li,即Sushi)
const sushiItem = document.querySelector('#foodList li:nth-child(2)');
// 8. 检查元素是否存在,然后从DOM中移除
if (sushiItem) {
foodList.removeChild(sushiItem);
}
});


运行上述代码后,页面将显示一个包含“Pizza”和“Sushi”的列表,以及两个按钮。点击“添加 Tacos”按钮,列表末尾会新增一个“Tacos”项。点击“移除 Sushi”按钮,列表中的“Sushi”项将被删除。
核心方法总结

在本示例中,我们使用了几个关键的 DOM 操作方法:

document.createElement(tagName):创建一个指定标签名的 HTML 元素。document.createTextNode(text):创建一个包含指定文本的文本节点。parentElement.appendChild(childElement):将一个节点附加到指定父节点的子节点列表末尾。parentElement.removeChild(childElement):从 DOM 中移除父节点下的一个指定子节点。document.querySelector(selector):返回文档中与指定选择器匹配的第一个元素。

事件监听器使用 querySelector 方法访问相应的 DOM 元素,然后使用 appendChild 或 removeChild 方法来创建或移除元素。

课程总结
本节课中我们一起学习了如何使用 JavaScript 创建和移除 DOM 元素。在 Web 开发中,这是一项用于动态修改网页内容的基础技术。通过使用 createElement、appendChild、removeChild 等方法,开发者可以创建动态且响应式的网页,为用户提供丰富而吸引人的体验。

在下一节视频中,我们将学习如何使用 Ajax 动态加载内容。
142:使用Ajax动态加载内容 🚀
在本节课中,我们将学习如何使用Ajax技术,在不刷新整个页面的情况下,动态地从服务器加载并显示内容。上一节我们介绍了如何使用JavaScript创建和移除DOM元素,本节中我们来看看如何通过Ajax实现数据的异步获取与动态展示。
Ajax(异步JavaScript和XML)是一种用于Web开发的技术,它允许网页在后台与服务器交换数据并更新部分页面内容,而无需打断用户当前的操作或重新加载整个页面。

在网页中使用Ajax,我们通常利用JavaScript中的 XMLHttpRequest 对象向服务器发送HTTP请求,然后使用JavaScript处理服务器的响应。通过这种方式,我们可以从服务器加载内容并将其显示在网页上,而无需重新加载整个页面。
让我们通过一个具体的例子,来看看如何使用Ajax从服务器动态加载内容。
构建示例:动态加载用户列表
以下是实现动态加载用户列表的步骤。
首先,我们需要创建一个基本的HTML文件结构。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ajax 示例</title>
</head>
<body>
<h1>用户列表</h1>
<ul id="userList"></ul>
<script>
// JavaScript代码将写在这里
</script>
</body>
</html>
接下来,我们将在 <script> 标签内编写JavaScript代码来实现Ajax请求。
以下是实现Ajax请求的核心代码逻辑:
// 1. 获取用于显示用户列表的DOM元素
const userList = document.querySelector('#userList');

// 2. 创建新的 XMLHttpRequest 对象
const xhr = new XMLHttpRequest();


// 3. 定义当请求状态改变时的回调函数
xhr.onreadystatechange = function() {
// 检查请求是否完成
if (xhr.readyState === XMLHttpRequest.DONE) {
// 检查HTTP状态码是否为200(成功)
if (xhr.status === 200) {
// 解析从服务器返回的JSON数据
const userData = JSON.parse(xhr.responseText);
// 遍历用户数据,为每个用户创建列表项
userData.forEach(function(user) {
// 创建新的 <li> 元素
const newUser = document.createElement('li');
// 创建包含用户姓名的文本节点
const newUserText = document.createTextNode(user.name);
// 将文本节点添加到 <li> 元素中
newUser.appendChild(newUserText);
// 将 <li> 元素添加到用户列表中
userList.appendChild(newUser);
});
} else {
// 如果请求失败,在控制台输出错误信息
console.log('请求过程中出现问题。');
}
}
};


// 4. 初始化一个GET请求,目标URL为提供测试数据的API
xhr.open('GET', 'https://jsonplaceholder.typicode.com/users');
// 5. 发送请求
xhr.send();
在这个例子中,我们使用Ajax从JSONPlaceholder API加载了一个用户列表,并将其显示在网页上。我们首先使用 XMLHttpRequest 对象创建了一个新的HTTP请求,然后定义了一个回调函数来处理从服务器接收到的响应。当响应成功返回时,我们解析响应数据,遍历用户数组,并为每个用户动态创建 <li> 元素,最后将这些元素添加到页面的用户列表中。
总结

本节课中我们一起学习了如何使用Ajax动态加载内容。通过 XMLHttpRequest 对象,我们可以向服务器发送异步请求,获取数据并在不刷新页面的情况下更新网页的特定部分。这是一种强大的技术,能显著提升网页的用户体验,使其更加流畅和响应迅速。

在下一节视频中,我们将探讨如何在JavaScript中处理错误和异常。我们下节课再见。
143:处理错误与异常 🛡️
在本节课中,我们将学习如何在JavaScript中处理错误与异常。这是编写健壮、可靠代码的重要部分。
上一节我们介绍了如何使用Ajax动态加载内容。本节中,我们来看看如何通过try...catch语句来优雅地捕获和处理程序运行中可能出现的错误。
错误处理的基本语法

在JavaScript中,我们使用try和catch关键字来共同处理代码中的错误与异常。
以下是try...catch语句的基本语法:
try {
// 可能抛出错误的代码
} catch (error) {
// 处理错误的代码
}
try块:包含可能抛出错误或异常的代码。catch块:如果try块中发生错误,JavaScript会跳出try块并进入catch块。catch块包含用于处理错误的代码,例如记录错误信息或采取纠正措施。
在catch块中,你会注意到一个error变量。这个变量代表了被抛出的错误对象。你可以使用这个变量来访问关于错误的信息,例如它的消息、堆栈跟踪以及可能附加到它的任何其他数据。
通过示例理解
让我们通过一个具体的例子来理解这个概念。
我们将创建一个函数来执行除法运算,并处理除数为零的情况。
// 定义一个除法函数
function divide(numerator, denominator) {
// 检查分母是否为0
if (denominator === 0) {
// 如果为0,则抛出一个新的错误对象
throw new Error('Division by 0');
}
// 否则,返回正常的除法结果
return numerator / denominator;
}
// 使用 try...catch 来调用函数并处理潜在错误
try {
const result = divide(10, 0); // 这里会触发错误
console.log(result);
} catch (error) {
// 捕获到错误,并打印错误信息
console.log('Error:', error.message);
}
在这个例子中:
- 我们定义了一个
divide函数,它对两个数字(分子和分母)执行除法运算。 - 在执行除法之前,我们检查分母是否不为0。如果是0,我们使用
throw new Error()抛出一个带有自定义错误消息的新错误对象。 - 然后,我们使用参数10和0调用
divide函数,并将其包裹在try块中。 - 由于分母为0,这将导致“除以零”错误。JavaScript会跳出
try块并进入catch块。 - 在
catch块中,我们使用console.log(error.message)将错误消息记录到控制台,从而得到“Error: Division by 0”的输出。

总结
本节课中我们一起学习了JavaScript中错误处理的核心机制。
try和catch语句用于协同处理代码中的错误与异常。try块包含可能抛出错误的代码。catch块包含用于处理这些错误的代码。catch块中的error变量代表了被抛出的错误对象,可用于获取错误的详细信息。

使用try...catch块可以帮助我们编写更可靠、更易维护的JavaScript代码,因为它能让我们优雅地处理错误和异常,防止程序意外崩溃。
144:Angular 框架入门指南
在本节课中,我们将学习 Angular,一个用于构建 Web 应用程序的流行框架。我们将从 Angular 的简介开始,然后深入探讨其核心概念:组件和模块。通过本课,你将掌握 Angular 应用的基本构建方式。

Angular 简介

Angular 是一个由 Google 维护的开源前端框架,用于构建高效、复杂的单页面应用程序(SPA)。它提供了一套完整的工具和结构,帮助开发者管理应用的数据、逻辑和用户界面。
上一节我们介绍了 Angular 框架的概况,本节中我们来看看 Angular 的核心构成部分:组件和模块。
组件与模块
在 Angular 中,组件和模块是构建应用程序的基础单元。组件控制屏幕上被称为视图的一小片区域,而模块则用于将相关的组件、指令、管道和服务组织在一起。
以下是关于组件和模块的核心要点:
- 组件:每个 Angular 应用都至少有一个根组件。组件由三部分组成:
- 模板:定义组件渲染的 HTML 视图。
- 类:包含组件的数据和逻辑,通常用 TypeScript 编写。
- 元数据:通过装饰器(如
@Component)定义,告诉 Angular 如何处理这个类。
- 模块:模块(使用
@NgModule装饰器)将应用组织成功能块。每个 Angular 应用都有一个根模块(通常是AppModule),用于引导启动应用。
Angular 生命周期
组件具有从创建、更新到销毁的生命周期。Angular 提供了生命周期钩子,允许开发者在关键时刻(如组件初始化后、视图检查后、组件销毁前)插入自己的代码。
常见的生命周期钩子包括 ngOnInit, ngOnChanges, ngOnDestroy 等。理解生命周期有助于管理资源、响应数据变化。
Angular 装饰器
装饰器是一种特殊的声明,可以附加到类、方法、属性或参数上,以修改其行为。在 Angular 中,装饰器是框架语法的核心。
我们将介绍不同类型的装饰器及其用途。以下是主要的 Angular 装饰器:
@Component:将一个类标记为 Angular 组件,并提供相关的元数据。@NgModule:将一个类标记为 Angular 模块。@Injectable:标记一个类可以被 Angular 的依赖注入系统管理。@Input和@Output:用于组件间的数据通信。
例如,一个组件的基本结构可以通过以下代码描述:
@Component({
selector: 'app-example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css']
})
export class ExampleComponent implements OnInit {
// 组件逻辑写在这里
constructor() { }
ngOnInit(): void { }
}
构建 Angular 应用
了解了组件和模块的基础后,我们来看看它们如何协同工作以构建完整的应用。这涉及到几个关键主题:
- 依赖注入:Angular 的核心设计模式,用于为类提供其依赖的服务或对象,提高了代码的可测试性和模块化。
- 路由:允许在单页面应用的不同视图之间导航。Angular 路由器根据 URL 的变化来加载相应的组件。
- 模板语法:Angular 扩展了 HTML,提供了数据绑定、指令(如
*ngFor,*ngIf)、管道等强大功能,用于在模板中动态渲染内容。
通过组合使用组件、模块、服务以及路由,你可以构建出结构清晰、功能强大的现代化 Web 应用程序。
本节课中我们一起学习了 Angular 框架的基础知识,包括其核心的组件与模块概念、生命周期钩子、装饰器的用法,以及构建应用所必需的依赖注入、路由和模板语法。掌握这些内容是成为一名 Angular 开发者的第一步。我们下个视频再见。
145:Angular 组件生命周期 🔄
在本节课中,我们将学习 Angular 组件的生命周期。理解生命周期对于开发 Angular 应用至关重要。
Angular 生命周期由一系列预定义的阶段组成,也可以称为钩子。这些钩子允许你在组件生命周期的不同时间点执行特定的操作。
为了帮助你形象化理解,我们可以把组件想象成一个生命体,经历从诞生到消亡的不同阶段。
生命周期阶段详解
上一节我们介绍了生命周期的概念,本节中我们来看看其具体的阶段和钩子。
以下是 Angular 组件生命周期的重要阶段:
-
创建阶段
- 构造函数:这是组件被创建时调用的第一个方法,用于初始化组件及其依赖项。此时,组件的模板和输入属性尚不可用。
-
初始化阶段
ngOnChanges:当组件的输入属性发生变化时调用此钩子。它提供有关已更改属性的信息。ngOnInit:在首次ngOnChanges之后调用一次。用于初始化组件、发起 API 调用和执行其他设置任务。
-
内容投影阶段
ngAfterContentInit:在内容子项(即投影内容)初始化后被调用。
-
视图初始化阶段
ngAfterViewInit:在内容子项初始化并可在组件视图中使用后被调用。

-
视图更新阶段
ngAfterViewChecked:在组件的视图和子视图初始化后被调用。用于执行需要访问组件视图的额外设置任务。
-
运行时阶段
ngDoCheck:在每个变更检测周期中被调用。用于执行自定义的变更检测并对组件状态的变化做出反应。
-
销毁阶段
ngOnDestroy:在组件即将被销毁时调用。用于取消订阅、清除计时器以及释放组件持有的任何资源。
代码示例与实践
理解了各个阶段后,我们通过一个简单的例子来看看这些钩子是如何工作的。
以下是演示 constructor、ngOnInit 和 ngOnDestroy 钩子的基本组件代码:
import { Component, OnInit, OnDestroy } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h2>生命周期示例</h2>
<p *ngIf="initialized">组件已初始化</p>
<p *ngIf="destroyed">组件已销毁</p>
`,
})
export class AppComponent implements OnInit, OnDestroy {
initialized: boolean = false;
destroyed: boolean = false;
constructor() {
console.log('构造函数被调用');
}
ngOnInit() {
this.initialized = true;
console.log('组件已初始化');
}
ngOnDestroy() {
this.destroyed = true;
console.log('组件已销毁');
}
}
运行此代码,在浏览器控制台中你将看到以下输出顺序:
构造函数被调用组件已初始化
这演示了生命周期钩子的调用流程:首先是创建阶段(构造函数),然后是初始化阶段(ngOnInit)。如果组件被销毁,ngOnDestroy 将在最后被调用。



重要注意事项
了解钩子的调用顺序后,还需要注意以下几点。
以下是关于 Angular 生命周期钩子的关键点:
- 并非所有钩子都会在每个组件中使用。
- 生命周期钩子是可选的,你可以根据组件的行为或用例选择实现必要的钩子。
- 理解 Angular 组件生命周期有助于你管理组件初始化、执行清理操作,并通过在生命周期的每个阶段使用适当的钩子来优化性能。

本节课中我们一起学习了 Angular 组件的生命周期,包括其各个阶段、对应的钩子函数以及一个简单的实践示例。理解这些概念是构建健壮、高效 Angular 应用的基础。在下一节课中,我们将了解 Angular 装饰器。
146:Angular装饰器详解 🎯

在本节课中,我们将要学习Angular框架中的装饰器。装饰器是Angular中用于增强和修改类、方法、属性及参数行为的重要特性。它们通过提供元数据,帮助Angular理解如何创建、配置和运行应用程序的各个部分。
在上一节我们介绍了Angular的生命周期,本节中我们来看看Angular中不同类型的装饰器。
Angular中有多种类型的装饰器,用于增强和修改类、方法、属性及参数的行为。以下是Angular中一些主要的装饰器类型。
类装饰器
以下是Angular中主要的类装饰器:
@Component:用于定义一个Angular组件。@Directive:用于定义一个Angular指令。@Injectable:用于将一个类声明为可注入的,以便进行依赖注入。@NgModule:用于定义一个Angular模块。@Pipe:用于定义一个Angular管道。


对于以上所有功能,我们都有不同类型的类装饰器。
接下来,我们来看一种叫做属性装饰器的类型。
属性装饰器
以下是一些重要的属性装饰器:
@Input:用于在组件或指令上定义一个输入属性。@Output:用于在组件或指令上定义一个输出属性。@HostBinding:用于将宿主元素的属性绑定到指令的属性上。@HostListener:用于在宿主元素上定义一个事件监听器。
除了属性装饰器,我们还有方法装饰器。
方法装饰器

以下是方法装饰器的例子:

@ViewChild:用于查询子组件或元素。@ContentChild:用于查询内容子组件或元素。@Host:用于指定注入依赖时要使用的提供者。@HostListener:同样可用于在宿主元素上定义事件监听器。
以上这些装饰器可能看起来令人困惑,但它们只是针对不同使用场景的工具。
最后,我们还有参数装饰器。
参数装饰器

以下是参数装饰器的类型:
@Inject:用于指定需要注入的依赖项。@Optional:用于表明某个依赖项是可选的。@Self:用于将依赖项的搜索范围限制在当前注入器中。@SkipSelf:用于跳过当前注入器,在父级注入器中搜索依赖项。@Attribute:用于获取元素上某个属性的值。
现在,让我们通过代码示例来看看如何在实践中使用一些重要的装饰器。
装饰器使用示例
在Visual Studio Code中,我们有一个基础的Angular模板。当前位于 app.component.ts 文件。
import { Component, Input, Output } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
@Input() inputData: string;
@Output() outputEvent = new EventEmitter<string>();
}
如你所见,我们使用了 @Component 装饰器,这是一个类装饰器。在类内部,我们还可以添加更多装饰器,例如 @Input 和 @Output。这就是你根据不同的用例来包含装饰器的方式。
如果你查看 app.module.ts 文件,你会看到我们也使用了 @NgModule 装饰器,它也可以接收自己的元数据。
分析这里的模式,所有Angular中的装饰器都以 @ 符号表示,例如 @Component、@NgModule、@Input 和 @Output。我们可以定义类装饰器、输入装饰器等,具体取决于使用场景。此外,还有许多事件装饰器,如 @HostListener 等。

装饰器本质上是向Angular应用程序传递元数据的一种方式。
总结
本节课中我们一起学习了Angular装饰器。所有这些装饰器可以组合使用,以定制Angular元素的行为、提供元数据、配置依赖注入等。它们在构建Angular应用程序和启用框架强大功能方面扮演着至关重要的角色。


在下一节视频中,我们将学习Angular组件。
147:Angular 组件 🧩
在本节课中,我们将要学习 Angular 框架中的一个核心概念:组件。组件是构建 Angular 应用的基础单元,它负责管理应用的一部分视图及其相关逻辑。
概述
在上一节中,我们学习了 Angular 的安装与项目初始化。本节中,我们将深入探讨 Angular 组件,了解其构成、作用以及如何创建和使用它。
Angular 组件是应用的基本构建块。它代表用户界面的一个特定部分,并封装了相关的逻辑、数据和 UI 模板。换句话说,一个组件将模板、类和元数据组合在一起,定义了一个可重用且自包含的单元。

组件的构成
一个 Angular 组件主要由以下三部分构成:
1. 模板
模板代表组件的视觉部分,定义了组件 UI 应如何渲染。它使用 HTML 标记和 Angular 模板语法定义。模板可以包含数据绑定、事件处理、指令和其他 Angular 特性,使其具有动态性和交互性。
2. 类
类代表组件的逻辑部分,包含与之相关的代码和业务逻辑。它使用 TypeScript 编写,通常遵循面向对象编程原则。类负责定义属性、方法以及处理组件特定的行为。
3. 元数据
元数据向 Angular 提供关于组件的信息。它通过装饰器定义。用于组件的主要装饰器是 @Component 装饰器。@Component 装饰器接收一个对象作为参数,并提供诸如选择器、模板、样式等元数据。
代码示例:一个组件的基本结构
import { Component } from '@angular/core';
@Component({
selector: 'app-example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css']
})
export class ExampleComponent {
// 类的属性和方法定义在这里
}

创建组件实践


理解了理论之后,让我们通过一个实践例子来巩固知识。我们将使用 Angular CLI 工具创建一个新组件。

以下是创建并使用一个名为 test 的组件的步骤:
- 打开终端:在 Angular 项目根目录下打开终端。
- 运行生成命令:使用 Angular CLI 的
generate(或g)命令来创建组件。
或者使用简写:ng generate component testng g c test - 观察生成的文件:CLI 会自动创建以下文件并更新根模块:
test.component.ts(组件类与元数据)test.component.html(组件模板)test.component.css(组件样式)test.component.spec.ts(测试文件,可忽略)
- 在应用中使用组件:生成组件后,我们需要在父组件的模板中通过其选择器来使用它。例如,在根组件
AppComponent的模板 (app.component.html) 中插入:<app-test></app-test> - 查看结果:运行应用 (
ng serve),你将在浏览器中看到新组件渲染的内容。
通过以上步骤,我们成功创建并集成了一个 Angular 组件。组件是可重用、模块化的,通过组合和嵌套它们,可以构建出复杂的 UI 结构。它们封装了自身的逻辑、数据和 UI,使得管理和维护应用代码库变得更加容易。

总结
本节课中,我们一起学习了 Angular 组件的核心概念。我们了解到组件是 Angular 应用的基石,由模板、类和元数据三部分组成。我们通过实践演示了如何使用 Angular CLI 快速生成组件,并将其集成到应用中。掌握组件的创建和使用,是构建 Angular 应用的第一步。

在下一节中,我们将探讨 Angular 的另一个重要概念:模块。我们下次见。
148:Angular模块 🧩
在本节课中,我们将要学习Angular模块。模块是Angular应用组织代码的核心方式,它帮助我们将应用划分为清晰、可管理的功能块。
上一节我们介绍了Angular组件,本节中我们来看看如何将组件和其他功能组织到模块中。
什么是Angular模块?
在Angular中,模块是一种组织和打包相关组件、服务、指令及其他应用构建块的方式。它就像一个容器,将实现特定功能或一组紧密相关功能所需的代码组织在一起。

模块的类型
Angular主要有两种类型的模块:根模块和特性模块。

根模块
根模块通常命名为 AppModule,是应用的入口点。它负责引导启动主组件,并导入其他特性模块和Angular库。根模块协调着应用的初始化过程。
在Angular项目中,根模块通常位于 app.module.ts 文件中。
特性模块
特性模块封装了用于特定功能或特性的一组相关组件、服务和其他代码。特性模块可以独立开发、测试,并在应用的不同部分复用。

创建模块的优势

创建Angular模块能带来诸多好处,包括:
- 模块化:将应用分解为独立的功能单元。
- 封装性:隐藏模块内部的实现细节。
- 依赖管理:清晰地声明模块间的依赖关系。
- 惰性加载:提升大型应用的初始加载速度。
- 代码组织:使代码结构更清晰,易于维护。
如何创建模块
要创建模块,可以使用Angular提供的 @NgModule 装饰器。这个装饰器用于定义模块的元数据,例如其声明(组件、指令、管道)、导入、导出和提供者。
以下是创建一个模块的基本代码结构:
import { NgModule } from ‘@angular/core’;
import { CommonModule } from ‘@angular/common’;
@NgModule({
declarations: [
// 在此声明属于本模块的组件、指令、管道
],
imports: [
// 在此导入本模块所需的其他模块
CommonModule
],
exports: [
// 在此导出希望其他模块能使用的组件、指令、管道
],
providers: [
// 在此提供本模块的服务
]
})
export class YourModuleName { }
实践:创建模块与组件
让我们通过一个简单的例子来实践。假设我们要创建一个用户认证相关的特性模块。
-
生成模块:使用Angular CLI命令生成一个名为
user-auth的特性模块。ng generate module user-auth这将在项目中创建
user-auth.module.ts文件。 -
在模块内生成组件:接下来,我们在
user-auth模块内创建一个登录组件。ng generate component user-auth/login执行后,CLI会自动在
user-auth模块的declarations数组中声明这个新创建的LoginComponent。

-
导出组件:如果希望其他模块(如根模块)能使用这个登录组件,需要在
user-auth模块的exports数组中将其导出。// user-auth.module.ts @NgModule({ declarations: [LoginComponent], imports: [CommonModule], exports: [LoginComponent] // 导出组件 }) export class UserAuthModule { } -
导入特性模块:在根模块
AppModule中导入我们创建的UserAuthModule。// app.module.ts import { UserAuthModule } from ‘./user-auth/user-auth.module’; @NgModule({ imports: [ BrowserModule, UserAuthModule // 导入特性模块 ], ... }) export class AppModule { }


- 使用组件:现在,我们可以在根模块的组件模板(如
app.component.html)中使用导出的登录组件了。
页面上将显示 “login works!”。<!-- app.component.html --> <app-login></app-login>
总结
本节课中我们一起学习了Angular模块。我们了解了模块是组织Angular应用的基础,区分了根模块和特性模块的作用,并通过实践步骤创建了一个特性模块及其内部的组件,最后将其集成到主应用中。

通过利用Angular模块的概念,你可以构建出模块化、可扩展且易于维护的应用程序,这有助于促进代码复用、关注点分离和高效的依赖管理。
149:Angular 数据绑定详解 🎯
在本节课中,我们将学习 Angular 框架中最重要且强大的功能之一:数据绑定。我们将从理解数据绑定的基本概念开始,逐步探索其不同类型,并学习如何在 Angular 应用中有效地使用它们。
概述 📋
数据绑定是现代 Web 开发中的核心概念,它允许开发者在组件类(TypeScript 代码)和模板(HTML 视图)之间建立动态连接。Angular 提供了多种数据绑定方式,使应用能够响应用户交互并实时更新界面。
上一节我们介绍了 Angular 框架的基础,本节中我们来看看其数据绑定机制。
什么是数据绑定? 🔗
数据绑定是 Angular 框架中连接组件逻辑与用户界面的机制。它确保当组件中的数据发生变化时,视图会自动更新;反之,当用户在界面上进行操作时,组件中的数据也能相应更新。
Angular 数据绑定的类型 📊
Angular 主要支持两种数据绑定模式:单向数据绑定和双向数据绑定。以下是具体的实现方式。
1. 单向数据绑定
单向数据绑定意味着数据只从一个方向流动:要么从组件到视图,要么从视图到组件。
插值语法
插值语法用于将组件类的数据绑定到模板的文本内容中。其语法是使用双花括号 {{ }}。
例如,在组件类中定义变量:
export class MyComponent {
title = 'Hello Angular';
}
在模板中绑定:
<h1>{{ title }}</h1>
渲染结果为:<h1>Hello Angular</h1>。
属性绑定
属性绑定用于将数据从父组件传递到子组件的属性,或设置 HTML 元素的属性。其语法是使用方括号 [ ]。
例如,将组件中的图片 URL 绑定到 img 标签的 src 属性:
<img [src]="imageUrl">
事件绑定
事件绑定允许你响应用户输入(如点击、键盘输入),并触发组件类中的方法。其语法是使用圆括号 ( )。
例如,绑定一个按钮的点击事件:
<button (click)="onSave()">保存</button>
当按钮被点击时,组件类中的 onSave 方法将被调用。
2. 双向数据绑定
双向数据绑定结合了属性绑定和事件绑定,实现了数据在组件类和模板之间的双向流动。Angular 使用 [(ngModel)] 语法来实现这一功能。
首先,需要在模块中导入 FormsModule:
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [ FormsModule ],
...
})
export class AppModule { }
然后,在模板中使用:
<input [(ngModel)]="userName">
<p>你输入的名字是:{{ userName }}</p>
当用户在输入框中键入内容时,userName 的值会同步更新,并且段落中的显示也会实时变化。
总结 🏁
本节课中我们一起学习了 Angular 数据绑定的核心概念。我们了解了什么是数据绑定及其重要性,并详细探讨了单向数据绑定(包括插值、属性绑定和事件绑定)与双向数据绑定的具体用法和语法。
通过掌握这些数据绑定技术,你将能够构建出动态、交互性强的现代 Web 应用程序,高效地管理应用状态与用户界面之间的同步。
150:Angular 数据绑定 - 单向与双向绑定 🎯
在本节课中,我们将学习 Angular 数据绑定的概念,并具体了解单向数据绑定和双向数据绑定的工作原理与实现方式。
Angular 数据绑定是一项在应用程序数据和用户界面之间建立连接的功能。它允许组件(TypeScript 代码)和视图(HTML 模板)之间的数据自动同步,确保一方的更改能即时反映在另一方。
数据绑定的类型
Angular 中的数据绑定主要分为两种类型。
单向数据绑定
在单向数据绑定中,数据仅沿单一方向流动。这意味着数据要么从组件流向视图,要么从视图流向组件。单向数据绑定又包含三种具体形式:

以下是单向数据绑定的三种形式:
- 插值:使用双花括号
{{ }}将组件属性或变量直接绑定到模板中。 - 属性绑定:使用方括号
[ ],根据组件属性的值来设置 HTML 元素的属性。 - 属性绑定:使用
attr.前缀和方括号[ ]来设置 HTML 元素的属性值。
双向数据绑定


在双向数据绑定中,数据在组件和视图之间双向同步。它使用 ngModel 指令将输入元素的值绑定到组件属性上。
上一节我们介绍了数据绑定的基本类型,本节中我们来看看如何在项目中实际应用它们。

实践:单向数据绑定

首先,我们聚焦于单向数据绑定。假设我们需要在页面上显示一个项目状态。
-
在组件的 TypeScript 文件中(例如
app.component.ts),创建一个变量。status = 'pending'; -
在对应的 HTML 模板文件(例如
app.component.html)中,使用插值语法来显示这个变量。<p>项目状态是:{{ status }}</p>
保存后,页面将显示“项目状态是:pending”。此时,数据从组件(TS)流向了视图(HTML),这就是单向数据绑定。
实践:双向数据绑定


接下来,我们看看双向数据绑定。假设我们有一个输入框,允许用户输入国家名称,并且我们希望在用户输入时,组件中的变量能同步更新,同时触发一个函数。


-
首先,在组件的 TypeScript 文件中创建变量和函数。
country = 'US'; displayCountry() { console.log(this.country); } -
在 HTML 模板中,使用
ngModel指令实现双向绑定,并绑定(ngModelChange)事件来调用函数。<input type="text" [(ngModel)]="country" (ngModelChange)="displayCountry()">

重要提示:要使用 ngModel,必须在 AppModule 中导入 FormsModule。
```typescript
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [
// ... 其他模块
FormsModule
],
// ...
})
export class AppModule { }
```

保存后,页面输入框会显示默认值“US”。当你在输入框中修改内容时,控制台会实时打印出当前的国家名。这演示了数据在视图(输入框)和组件(country 变量)之间的双向同步。

总结
本节课中我们一起学习了 Angular 数据绑定的核心概念。通过利用数据绑定,你可以轻松地在应用程序的 UI 中更新和反映数据的变化,反之亦然。这简化了动态和交互式应用程序的开发,使得在 Angular 应用中管理和操作数据变得更加容易。

我们了解了单向数据绑定(包括插值、属性绑定和属性绑定)以及使用 ngModel 实现的双向数据绑定。在接下来的课程中,我们将更深入地学习 Angular 插值。
151:Angular 插值
在本节课中,我们将要学习 Angular 中的插值。这是一种单向数据绑定技术,允许你将组件属性或变量直接嵌入到模板标记中。
上一节我们介绍了 Angular 的数据绑定,特别是单向和双向数据绑定。本节中我们来看看其中一种单向绑定的具体实现:插值。
插值是 Angular 中的一种单向数据绑定,它允许你将组件属性或变量直接绑定到模板标记中。其语法由双花括号 {{ }} 表示。

插值可以通过多种方式工作。
以下是插值的第一种工作方式:绑定组件属性。
在这种情况下,你可以使用插值将组件属性直接绑定到模板中。属性值将被求值并显示在模板中。
让我们创建一个示例。
// app.component.ts
import { Component } from '@angular/core';

@Component({
selector: 'app-root',
template: `
<h1>{{ title }}</h1>
<p>{{ message }}</p>
`,
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Welcome to Angular';
message = 'This is an example of interpolation.';
}


在这个例子中,title 和 message 属性在组件中定义。在模板中,这些属性被包裹在双花括号 {{ }} 中。这些属性的值将被动态地插值并显示在 HTML 页面上。

接下来,我们看看插值的第二种类型:表达式求值。

在这种情况下,插值允许你在模板中对表达式进行求值。你可以在插值语法中执行简单的操作或包含方法调用。
例如:
// app.component.ts
import { Component } from '@angular/core';

@Component({
selector: 'app-root',
template: `
<p>The length of the message is: {{ message.length }}</p>
<p>The result of 2 + 2 is: {{ 2 + 2 }}</p>
`,
styleUrls: ['./app.component.css']
})
export class AppComponent {
message = 'Welcome to Angular';
}


在这个例子中,我们再次使用插值来执行算术运算和访问 message 字符串的长度。插值内的这些表达式会被求值,结果将显示在模板中。

插值是在模板中显示动态数据的一种便捷方式。它允许你将组件属性、变量和表达式无缝地整合到用户界面中,使你的应用程序更具交互性和响应性。
需要记住的是,插值是一种单向数据绑定。这意味着它只会在用户界面中显示组件的数据。如果用户在界面上做出更改,它不会更新组件中的数据。
本节课中我们一起学习了 Angular 插值的概念、语法和两种主要应用方式:绑定组件属性和进行表达式求值。这是一种强大的单向数据绑定工具,用于动态渲染内容。

在下一节视频中,我们将学习 Angular 的属性绑定。
152:Angular 属性绑定 🎯
在本节课中,我们将要学习 Angular 中的属性绑定。这是一种单向数据绑定技术,允许你根据组件属性或表达式来动态设置 HTML 元素属性的值。
上一节我们介绍了 Angular 插值,本节中我们来看看属性绑定。
什么是属性绑定?

属性绑定是 Angular 中一种单向数据绑定,它允许你基于组件属性或某个表达式来设置 HTML 元素属性的值。它使用方括号 [] 将组件属性绑定到元素属性。
以下是属性绑定的几种主要工作方式。
1. 绑定组件属性到元素属性

在这种情况下,你可以使用属性绑定直接将组件属性绑定到 HTML 元素的属性。组件属性的值将被赋给元素的属性。

让我们通过一个例子来理解。
<!-- app.component.html -->
<button [disabled]="isButtonDisabled">Click me</button>

// app.component.ts
export class AppComponent {
isButtonDisabled = true;
}
在这个例子中,我们将按钮元素的 disabled 属性绑定到了组件中的 isButtonDisabled 属性。组件属性决定了按钮是否被禁用。如果 isButtonDisabled 为 true,按钮的 disabled 属性将被设置为 true,从而禁用按钮;如果为 false,按钮将被启用。

2. 表达式求值
属性绑定也允许你在模板中对表达式进行求值。你可以在属性绑定语法中执行逻辑运算或包含方法。
让我们看另一个例子。

<!-- app.component.html -->
<p>The result of 10 - 5 is: {{ 10 - 5 }}</p>
<input [value]="getInitialValue()" />
// app.component.ts
export class AppComponent {
getInitialValue() {
return 'Initial Value';
}
}
在这个例子中,我们再次使用属性绑定来设置输入框的 value 属性。其值是通过调用组件中定义的 getInitialValue() 方法获得的。方法调用的结果被赋给了输入框元素的 value 属性。


属性绑定的特点
属性绑定允许你基于组件数据或表达式动态设置和更新 HTML 元素的属性,为你的应用提供了灵活性和交互性。
需要注意的一点是,属性绑定是单向数据绑定。这意味着它只根据组件属性或表达式来设置元素属性的值。如果用户界面发生变化,它不会更新组件属性。

本节课中我们一起学习了 Angular 的属性绑定,包括如何将组件属性绑定到元素属性,以及如何在绑定中使用表达式和方法。在下一节视频中,我们将学习 Angular 的事件绑定。
153:Angular 事件绑定
在本节课中,我们将要学习 Angular 中的事件绑定。事件绑定允许你的应用响应用户的交互操作,例如点击按钮、移动鼠标或键盘输入。
上一节我们介绍了属性绑定,本节中我们来看看如何通过事件绑定来处理用户交互。
什么是事件绑定? 🤔
事件绑定在 Angular 中用于响应用户交互,例如按钮点击、鼠标移动、键盘输入等。它建立了用户操作与组件类中一个方法之间的连接,使你能够处理并响应这些事件。

如何使用事件绑定
以下是使用事件绑定的几种主要方式。
1. 绑定事件处理器
你可以使用事件绑定,直接将一个事件绑定到组件类中的一个方法。当指定的事件发生时,相关联的方法就会被执行。

语法格式:
<button (click)="onButtonClick()">点击我</button>
让我们通过一个例子来理解。
首先,在组件类中创建一个方法:
onButtonClick() {
console.log('按钮被点击了');
}

然后,在模板中创建一个按钮并绑定点击事件:
<button (click)="onButtonClick()">点击我</button>

当按钮被点击时,onButtonClick 方法将被执行,并在控制台输出信息。
你可以根据应用需求,绑定各种事件,例如 mouseover、keydown、submit、input 等。

2. 访问事件数据
事件绑定还可以提供关于所发生事件的额外信息。你可以在事件处理器方法中,通过传递特殊的 $event 对象来访问这些数据。
语法格式:
<input (keyup)="onKeyUp($event)">
让我们看一个访问键盘事件的例子。
在组件类中创建方法:
onKeyUp(event: any) {
console.log('按下的键是:', event.key);
}

在模板中绑定 keyup 事件:
<input (keyup)="onKeyUp($event)">

当在输入框中释放一个按键时,onKeyUp 方法会被执行,并通过 event.key 将按下的键名输出到控制台。$event 对象提供了与事件相关的各种属性和方法,例如 target、key、keyCode 等。
事件绑定的作用
事件绑定通过响应用户操作,使你的应用变得具有交互性。你可以执行各种操作、更新组件数据、调用方法,并基于事件触发应用逻辑。

通过将事件绑定与其他类型的数据绑定(如属性绑定)结合使用,你可以在 Angular 应用中创建动态且响应迅速的用户界面。
总结
本节课中我们一起学习了 Angular 的事件绑定。我们了解了如何将用户界面事件(如点击和按键)绑定到组件类中的方法,以及如何通过 $event 对象访问事件的详细信息。掌握事件绑定是构建交互式 Web 应用的关键一步。
在下一节视频中,我们将理解 Angular 的数据绑定,特别是双向数据绑定。

🎼 我们下节课再见。谢谢。
154:Angular 双向数据绑定 - ngModel 🔄
在本节课中,我们将要学习 Angular 中的双向数据绑定,特别是如何使用 ngModel 指令来实现组件属性与用户界面输入元素之间的双向同步。
上一节我们介绍了事件绑定,本节中我们来看看如何将数据绑定从单向扩展到双向,从而更高效地处理用户输入。
概述
双向数据绑定是 Angular 的一项强大功能,它允许你在组件属性和用户界面的输入元素之间建立双向同步。这确保了无论在组件中还是在用户界面中做出的更改,都会自动反映到另一方。你可以使用 ngModel 指令来实现双向数据绑定。它结合了属性绑定和事件绑定,将组件属性绑定到输入元素并使它们保持同步。
实现步骤
以下是使用 ngModel 实现双向数据绑定的关键步骤。
1. 导入 FormsModule
在使用 ngModel 之前,你需要在你的 Angular 模块中从 @angular/forms 导入 FormsModule。这个模块提供了处理表单和数据绑定所需的指令和服务。

代码示例:
// 在 app.module.ts 中
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [
// ... 其他模块
FormsModule // 导入 FormsModule
],
// ...
})
export class AppModule { }
2. 将组件属性绑定到输入元素
要建立双向数据绑定,你可以在输入元素内部使用 ngModel 指令,并将其绑定到一个组件属性。
代码示例:
<!-- 在组件模板中 -->
<input type="text" [(ngModel)]="userName" placeholder="请输入您的名字">
<p>欢迎,{{ userName }}!</p>
3. 更新组件属性与输入元素

在双向数据绑定中,无论是在组件中还是在输入元素中做出的更改,都会自动反映在两个地方。当用户在输入框中键入时,组件属性会更新;反之,如果通过编程方式更新组件属性,输入框也会显示更新后的值。

实践示例
让我们通过一个具体的例子来理解这个过程。

假设我们有一个 Angular 项目,在 AppComponent 中:

- 在
app.module.ts中导入FormsModule。 - 在
app.component.ts中定义一个属性(例如name),并初始化为空字符串。 - 在
app.component.html模板中,创建一个输入框,使用[(ngModel)]将其与name属性绑定,同时使用插值表达式{{ name }}来实时显示该属性的值。
当用户在输入框中输入“John”时,下方的文本会实时更新为“欢迎,John!”。这演示了 ngModel 如何同步组件数据和用户界面。

总结
本节课中我们一起学习了 Angular 的双向数据绑定。我们了解到,使用 ngModel 指令可以简化用户输入处理和组件数据同步,减少保持组件与用户界面同步所需的样板代码。关键点是记住在你的 Angular 模块中导入 FormsModule 以有效使用 ngModel。

通过掌握双向数据绑定,你将能更流畅地构建交互式的 Angular 应用。下一节课我们将继续探索 Angular 的其他核心功能。
155:了解指令
在本节课中,我们将要学习 Angular 框架中的一个核心概念——指令。指令是 Angular 应用的基础构建块,它们允许你操作 DOM 并为元素添加自定义行为,对于创建可复用的组件至关重要。
什么是 Angular 指令?
上一节我们介绍了课程目标,本节中我们来看看指令的具体定义。Angular 指令是一种可以附加到 DOM 元素上的标记,它能够扩展元素的行为或改变其外观。你可以将指令理解为赋予 HTML 元素新能力的工具。
Angular 指令的类型
了解了指令的基本概念后,接下来我们探索 Angular 中不同类型的指令。Angular 主要提供了两种内置指令:结构型指令和属性型指令。
以下是两种主要指令类型的简要说明:
- 结构型指令:这类指令通过添加、移除或替换 DOM 元素来改变布局结构。它们通常以星号 (
*) 为前缀。 - 属性型指令:这类指令用于改变元素、组件或其他指令的外观或行为。它们看起来就像普通的 HTML 属性。
内置指令的应用
我们将学习如何使用 Angular 提供的一些内置指令。例如,*ngIf 是一个结构型指令,用于根据条件显示或隐藏元素;[ngStyle] 是一个属性型指令,用于动态设置元素的样式。
以下是一个简单的代码示例:
<!-- 使用 *ngIf 根据条件显示段落 -->
<p *ngIf="user.isLoggedIn">欢迎回来!</p>
<!-- 使用 [ngStyle] 动态绑定样式 -->
<div [ngStyle]="{'color': textColor, 'font-size.px': fontSize}">动态样式的文本</div>
创建自定义指令
除了使用内置指令,你还可以创建自己的自定义指令来封装特定的行为。这能极大地提升代码的复用性和可维护性。创建自定义指令涉及使用 @Directive 装饰器并定义其选择器与逻辑。
课程总结
本节课中我们一起学习了 Angular 指令的核心知识。我们了解了指令是什么,区分了结构型指令和属性型指令,并介绍了如何使用 *ngIf 和 [ngStyle] 这样的内置指令来动态操作 DOM。掌握指令将使你能够创建出更动态、更可复用的 Angular 组件,从而增强应用的功能与用户体验。
156:什么是Angular指令


在本节课中,我们将学习Angular指令是什么。Angular指令是强大的工具,允许你扩展HTML元素的功能、操作DOM并为你的Angular应用添加动态行为。指令是Angular的基本构建块,在创建可复用组件和增强用户体验方面起着至关重要的作用。
🧩 指令的三种类型
Angular中有三种类型的指令,下面我们来逐一讨论。
组件指令
组件是Angular中最常见的指令类型。它们用于创建具有自己的模板、样式和行为的可复用UI组件。组件由HTML元素、CSS样式和定义组件逻辑与外观的TypeScript代码组成。它们封装了自己的数据和功能,使其在应用中自成一体且可复用。
组件在Angular中使用 @Component 装饰器声明,并可以通过将其放置在其它组件的HTML模板中或直接在路由配置中复用来使用。
让我们看一个例子。假设你正在构建一个电子商务应用,需要创建一个名为 ProductCardComponent 的可复用组件。该组件将显示产品信息,如产品名称、价格和一个“加入购物车”按钮。它封装了自己的样式和行为,使其可以在整个应用中复用。然后,你可以在应用的各个部分使用这个组件,例如产品列表页、购物车页面,甚至侧边栏小部件中。
属性指令
属性指令允许你通过操作元素的属性或添加/移除元素来改变元素的外观或行为。属性指令通过将其作为属性应用到现有的HTML元素上来使用。它们可以修改所应用元素的行为或外观。
Angular提供了一些内置的属性指令,例如 ngClass、ngStyle 和 ngIf。这些指令允许你根据组件数据或条件动态修改元素的类、样式和可见性。此外,你也可以创建自己的自定义属性指令来满足特定的应用需求。
让我们看一个例子。假设你想根据产品的可用性,在产品列表页中高亮显示某些产品。你可以创建一个名为 highlight 的自定义属性指令,当它应用到一个产品元素上时,会改变其背景颜色以指示其可用性状态。你可以根据每个产品的可用性数据动态应用此指令,使其在视觉上对用户突出显示。
结构指令
结构指令使你能够根据特定条件,通过添加或移除元素来修改DOM的结构。它们在语法上以星号 * 为前缀,提供了一种在模板中有条件地渲染或重复元素的方式。
让我们看一个例子。假设你想显示一个产品及其价格的列表,但你只想显示价格高于某个特定金额的产品。你可以使用内置的 *ngFor 结构指令遍历产品列表,并使用 *ngIf 指令有条件地渲染元素。这样,只有满足指定条件的产品才会显示在DOM中。
Angular提供了几个内置的结构指令,例如 *ngIf、*ngFor 和 *ngSwitch。这些指令允许你根据组件数据有条件地在DOM中显示或重复元素。这些指令可以动态改变DOM结构,以反映应用状态的变化。
📝 总结
本节课中,我们一起学习了Angular指令。指令对于构建动态和交互式的Angular应用至关重要。它们允许你操作DOM、应用条件渲染、控制元素行为以及封装可复用组件。自定义属性和结构指令也可以被创建,以扩展Angular的功能并满足特定的应用需求。
在下一节课中,我们将学习Angular结构指令。下节课见。

🎼





157:Angular 结构型指令 🏗️

在本节课中,我们将要学习 Angular 中的结构型指令。结构型指令是 Angular 中用于动态修改 DOM 结构的重要工具,它们允许我们根据条件来显示、隐藏或重复元素。
上一节我们介绍了什么是 Angular 指令,本节中我们来看看结构型指令的具体内容。
什么是结构型指令?
Angular 中的结构型指令是一种允许你根据特定条件添加或移除元素,从而修改 DOM 结构的指令。它们是强大的工具,允许你基于动态条件有条件地渲染或重复 DOM 中的元素。它们在语法上以星号 * 为前缀,提供了一种有条件地渲染或重复 DOM 元素的方法。
内置的结构型指令
Angular 提供了几个内置的结构型指令,你可以直接使用。以下是其中一些核心指令的介绍。
*ngIf 指令
*ngIf 指令用于根据条件有条件地渲染一个元素。它计算一个表达式,如果表达式为真,就在 DOM 中渲染该元素;如果表达式为假,则将该元素从 DOM 中移除。
示例场景:假设你的应用程序中有一个用户认证功能,你只想在用户登录时显示欢迎信息。你可以使用 *ngIf 指令,根据组件中 isLoggedIn 属性的值来有条件地渲染欢迎信息元素。
代码示例:
<div *ngIf="isLoggedIn">欢迎回来!</div>
*ngFor 指令
*ngFor 指令用于遍历一个集合,并为集合中的每个项目渲染一个模板。它会为每个项目创建模板的一个独立实例,并将项目的数据绑定到该模板上。
示例场景:假设你有一个产品数组,你想显示一个产品名称列表。你可以使用 *ngFor 指令遍历产品数组,并为每个产品名称生成对应的列表项元素。
代码示例:
<ul>
<li *ngFor="let product of products">{{ product.name }}</li>
</ul>
*ngSwitch 指令
*ngSwitch 指令用于根据多个条件(情况)有条件地渲染元素。它计算一个表达式,并将其与一系列 *ngSwitchCase 语句进行比较。匹配的 *ngSwitchCase 内的模板会被渲染。
示例场景:假设你有一个带有不同颜色选项的下拉菜单,你想根据所选颜色显示特定的消息。你可以使用 *ngSwitch 指令来评估 color 属性,并使用 *ngSwitchCase 语句有条件地渲染不同的消息。
代码示例:
<div [ngSwitch]="selectedColor">
<p *ngSwitchCase="'red'">你选择了红色。</p>
<p *ngSwitchCase="'blue'">你选择了蓝色。</p>
<p *ngSwitchCase="'green'">你选择了绿色。</p>
<p *ngSwitchDefault>请选择一个颜色。</p>
</div>
结构型指令的作用总结

结构型指令有助于创建动态模板,并在 Angular 应用程序中构建交互式用户界面。它们允许你控制 DOM 结构,有条件地渲染元素,并允许你根据数据或条件重复元素。通过利用这些结构型指令,你可以创建更灵活、响应更快的组件,这些组件能够适应不断变化的数据或用户交互,从而在你的 Angular 应用程序中提供动态和交互式的用户体验。


本节课中我们一起学习了 Angular 的三种核心结构型指令:*ngIf、*ngFor 和 *ngSwitch,了解了它们的基本概念和使用场景。在下一节视频中,我们将深入学习 Angular 的 *ngIf 指令。
158:条件渲染指令 *ngIf 🎬
在本节课中,我们将要学习 Angular 中的一个核心结构指令:*ngIf。我们将了解它的作用、语法,并通过一个简单的例子来演示如何使用它动态控制页面元素的显示与隐藏。
概述

上一节我们介绍了 Angular 的结构指令。本节中,我们来看看 *ngIf 指令。*ngIf 用于根据特定条件,有条件地在 DOM 中渲染或移除元素。
*ngIf 指令详解
*ngIf 指令会计算一个表达式。如果表达式的值为 真值,则对应的元素会被渲染到 DOM 中;如果表达式的值为 假值,则该元素会从 DOM 中被移除。
其核心逻辑可以用以下伪代码描述:
if (condition) {
// 渲染元素
} else {
// 从DOM中移除元素
}

接下来,我们通过一个实例来理解它的用法。

实战:创建条件渲染示例
首先,我们需要在组件类中定义控制变量和方法。
1. 定义组件逻辑
打开 app.component.ts 文件,进行如下设置:
- 创建一个布尔类型的变量
showMessage,并初始化为true。 - 创建一个方法
toggleMessage(),用于切换showMessage的值。
具体代码如下:
export class AppComponent {
showMessage: boolean = true;
toggleMessage() {
this.showMessage = !this.showMessage;
}
}



2. 编写组件模板

接下来,在 app.component.html 模板文件中使用 *ngIf 指令。
以下是模板内容:
<h2>*ngIf 指令示例</h2>
<!-- 使用 *ngIf 根据 showMessage 的值决定是否渲染此段落 -->
<p *ngIf="showMessage">
这段信息会根据条件渲染。
</p>

<!-- 点击按钮触发 toggleMessage 方法 -->
<button (click)="toggleMessage()">切换信息显示</button>

示例运行原理
在这个例子中:
<p>元素:其显示与否由*ngIf="showMessage"控制。初始时showMessage为true,因此段落会显示。- 按钮:绑定了
(click)事件到toggleMessage()方法。点击按钮会调用该方法,将showMessage的值在true和false之间切换。 - 动态效果:每次点击按钮,
showMessage的值改变,*ngIf指令会重新评估条件,从而立即显示或隐藏段落内容。
通过这种方式,*ngIf 指令让你能够基于动态条件来控制内容的可见性,为 Angular 应用提供了强大的视图控制能力。
总结

本节课我们一起学习了 *ngIf 指令。我们了解到它是一个用于条件渲染的结构指令,能够根据表达式的结果动态地向 DOM 中添加或移除元素。我们通过一个包含按钮切换的完整示例,实践了如何定义控制变量、编写切换逻辑,并在模板中应用 *ngIf 来实现交互式的显示/隐藏功能。

在下一节视频中,我们将学习另一个实用的指令:*ngStyle 指令。敬请期待,我们下节课再见!
159:NgStyle 指令详解 🎨
在本节课中,我们将要学习 Angular 中的 NgStyle 指令。这个指令允许我们根据组件中的数据动态地为 HTML 元素应用内联 CSS 样式。

上一节我们介绍了 NgIf 指令,本节中我们来看看如何使用 NgStyle 来动态控制样式。
NgStyle 指令简介
NgStyle 指令用于根据组件中的表达式,动态地将内联 CSS 样式应用到一个元素上。它允许你基于特定条件或数据值来设置元素的样式属性。
其基本语法是使用方括号将指令绑定到一个表达式,该表达式会计算出一个包含 CSS 样式属性的对象。
<div [ngStyle]="{ 'font-size': fontSize + 'px', 'color': fontColor }">
动态样式的文本
</div>
动手实践:创建一个动态样式示例
让我们通过一个具体的例子来理解它的工作原理。我们将创建一个可以实时调整字体大小和颜色的文本区域。
首先,在组件的 TypeScript 文件中定义控制样式的属性。
// app.component.ts
export class AppComponent {
fontSize: number = 16;
fontColor: string = 'black';
}
接下来,在组件的 HTML 模板中,我们使用 NgStyle 指令将这些属性绑定到 div 元素的样式上。
<!-- app.component.html -->
<div [ngStyle]="{ 'font-size': fontSize + 'px', 'color': fontColor }">
这段文本的样式是动态的。
</div>

增强交互:添加输入控件

为了让用户能够动态调整样式,我们可以添加输入框。以下是实现步骤:
- 为字体大小和颜色分别创建标签和输入框。
- 使用
NgModel指令实现输入框与组件属性的双向数据绑定。
确保你的 AppModule 中已经导入了 FormsModule 以支持 NgModel。
// app.module.ts
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [
// ... 其他模块
FormsModule
],
})
export class AppModule { }

现在,在模板中添加输入控件:
<!-- app.component.html -->
<label>字体大小:</label>
<input type="number" [(ngModel)]="fontSize">

<label>字体颜色:</label>
<input type="text" [(ngModel)]="fontColor">
当用户在“字体大小”输入框中输入数字,或在“字体颜色”输入框中输入有效的 CSS 颜色值(如 red, #00ff00, rgb(0, 0, 255))时,上方 div 元素的样式会立即更新。
示例解析
在这个例子中:
div元素通过[ngStyle]指令被动态地赋予样式。- 字体大小和颜色样式由组件中的
fontSize和fontColor属性决定。 - 两个输入字段允许用户实时修改这些属性值。
- 我们使用了
NgModel指令来实现输入框与组件属性之间的双向数据绑定,确保用户输入能即时反馈到样式上。

总结

本节课中我们一起学习了 Angular 的 NgStyle 指令。我们了解到它如何通过绑定一个样式对象来动态控制元素的内联样式,并且通过结合 NgModel 指令,创建了一个允许用户实时交互、修改样式的生动示例。掌握 NgStyle 是构建动态、响应式用户界面的重要一步。
160:管道与表单 🚀
在本节课中,我们将深入学习 Angular 中的管道和表单。管道允许我们在数据展示到视图之前对其进行转换。我们将学习如何使用内置管道,并探索如何创建自定义管道。此外,我们还将深入研究 Angular 表单,了解其两种主要类型。
什么是 Angular 管道? 🔄
上一节我们介绍了本课的学习目标,本节中我们来看看 Angular 管道的核心概念。管道是一种用于在模板中转换数据的简单方式。其基本语法是在模板表达式中使用竖线 | 后跟管道名称。
公式:{{ 数据 | 管道名称:参数 }}
例如,我们可以使用内置的 uppercase 管道将文本转换为大写:
<p>{{ ‘hello world’ | uppercase }}</p>
<!-- 输出:HELLO WORLD -->
内置管道与自定义管道 🛠️
Angular 提供了一系列开箱即用的内置管道来处理常见的数据转换需求。
以下是三种常用的内置管道示例:
currency管道:用于格式化货币值。例如,{{ 1234.56 | currency:’USD’ }}会输出$1,234.56。date管道:用于格式化日期。例如,{{ today | date:’yyyy-MM-dd’ }}会按照指定格式输出日期。uppercase/lowercase管道:用于转换文本的大小写。
除了使用内置管道,我们还可以创建自定义管道来满足特定业务逻辑。创建自定义管道需要使用 @Pipe 装饰器并实现 PipeTransform 接口。
管道与指令的结合使用 ⚙️
我们已经了解了管道的基本用法,现在来看看如何将管道与 Angular 指令结合使用,以实现更灵活的数据展示。
通过将管道嵌入到 *ngFor 或 *ngIf 等结构性指令中,我们可以在循环或条件判断的同时对数据进行格式化。这使得我们能够以多种方式动态地呈现和转换数据。
Angular 表单:模板驱动与响应式 📝
在掌握了管道之后,本节我们将进入 Angular 表单的学习。表单是收集用户输入的核心组件,Angular 主要支持两种构建表单的方式。
1. 模板驱动表单
模板驱动表单依赖于指令(如 ngModel)在模板中直接构建表单结构。它利用双向数据绑定来同步视图与组件中的数据模型。
代码示例:<input [(ngModel)]=”user.name” name=”name”>
2. 响应式表单
响应式表单则在组件类中显式地创建和管理表单控件对象(如 FormControl, FormGroup)。这种方式提供了对数据验证和处理流程更精细的控制。
课程总结 🎯
本节课中,我们一起学习了 Angular 的两个重要特性:管道和表单。
我们了解了管道如何转换数据,并实践了内置管道与自定义管道的用法。
接着,我们探讨了 Angular 的两种表单构建策略:基于模板的模板驱动表单和基于模型的响应式表单,理解了它们各自的适用场景。
掌握这些知识将帮助你在项目中更有效地处理数据展示和用户交互。
161:Angular管道(Pipes)🚀
在本节课中,我们将学习Angular中的管道(Pipes)。管道是Angular中用于在模板中转换和格式化数据的一种方式,常用于执行过滤、排序和格式化值等常见的数据处理任务。
概述

Angular管道通过在模板语法中使用 | 字符来标记,能够轻松地将原始数据转换为更友好的显示格式。接下来,我们将通过一个具体的例子来理解管道的使用方法。
创建示例项目
首先,我们进入一个基础的Angular项目。当前位于 app.component.ts 文件中。我们将在这个组件中创建几个变量。
// app.component.ts
export class AppComponent {
value = 'hello Angular';
currentDate = new Date();
amount = 99.99;
}
在上面的代码中,我们创建了三个属性:
value:一个字符串。currentDate:一个日期对象。amount:一个数字。
在模板中使用管道
接下来,我们需要修改对应的HTML模板文件 app.component.html,以展示这些数据并应用管道进行转换。
<!-- app.component.html -->
<p>原始值:{{ value }}</p>
<p>大写转换:{{ value | uppercase }}</p>
<p>日期格式化:{{ currentDate | date:'medium' }}</p>
<p>货币格式化:{{ amount | currency:'USD':true }}</p>
在上面的模板中:
- 第一行展示了原始的
value值。 - 第二行使用
uppercase管道将value转换为大写。 - 第三行使用
date管道,并指定'medium'格式来格式化currentDate。 - 第四行使用
currency管道,将amount格式化为美元货币格式,并显示货币符号。
查看运行结果

保存更改后,我们可以在浏览器中看到以下输出结果:

- 原始值:hello Angular
- 大写转换:HELLO ANGULAR
- 日期格式化:(显示为中等格式的日期,例如:Jun 15, 2023, 10:30:00 AM)
- 货币格式化:$99.99
通过这个例子,我们可以看到:
value属性通过uppercase管道被转换成了大写。currentDate属性通过date管道以中等格式显示。amount属性通过currency管道以美元货币形式显示。

总结
本节课我们一起学习了Angular管道的基本概念和使用方法。管道是Angular中一个强大的工具,能够帮助我们在模板中轻松地转换和格式化数据,提升用户体验。在下一节课中,我们将学习Angular表单的相关知识。

感谢学习,我们下节课再见!
162:Angular 表单 📝
在本节课中,我们将要学习 Angular 框架中处理表单的两种主要方式:模板驱动表单和响应式表单。表单是许多 Web 应用的核心组成部分,用于收集和提交用户数据。理解这两种方法的区别和适用场景,对于构建高效、可维护的应用至关重要。
上一节我们介绍了 Angular 管道,本节中我们来看看 Angular 表单。
表单是许多 Web 应用的重要组成部分,允许用户输入和提交数据。Angular 为处理表单提供了强大的支持,包括模板驱动表单和响应式表单。
接下来,让我们详细讨论它们。
模板驱动表单
模板驱动表单依赖于 HTML 模板中的指令来创建和管理表单。表单的结构直接在模板中定义。Angular 根据使用的指令(如 ngModel 用于双向数据绑定,ngForm 用于表单处理)来推断表单控件。
模板驱动表单语法更简单,与响应式表单相比需要更少的代码。它们非常适合具有基本验证需求的简单表单。表单逻辑主要在模板本身中定义,使其更具声明性。
模板驱动表单通常用于需要快速实现表单,或表单结构相对简单直接的场景,例如联系表单或登录表单。对于较小、不太复杂的表单,它们是一个不错的选择。
以下是模板驱动表单的核心概念:
ngModel:用于在表单控件和组件属性之间建立双向数据绑定。ngForm:用于将 HTML<form>元素包装成一个 Angular 表单对象。
响应式表单
响应式表单提供了一种更灵活、更程序化的方式来处理表单。它们是使用 Angular 表单模块提供的 FormControl、FormGroup 和 FormArray 类构建的。
在响应式表单中,表单结构是在组件代码中程序化定义的,而不是直接在模板中。这使您可以对表单结构和行为进行细粒度的控制。您可以动态添加或删除表单控件、应用复杂的验证规则并以编程方式处理表单提交。
响应式表单非常适合需要更高级功能的复杂表单,例如动态表单控件、条件验证、跨字段验证或具有复杂数据依赖关系的表单。对于大型表单或需要大量验证和自定义的表单,它们提供了更好的可扩展性和可维护性。
以下是响应式表单的核心概念:
FormControl:用于跟踪单个表单控件的值和验证状态。FormGroup:用于跟踪一组FormControl实例的值和状态。FormArray:用于跟踪一个表单控件数组的值和状态。
如何选择表单类型
重要的是要注意,模板驱动表单和响应式表单都提供了强大的功能,如表单验证、数据绑定和处理表单提交。
让我们讨论在哪些情况下使用哪种表单。
以下是适合使用模板驱动表单的场景:
- 联系表单:这些是具有基本验证要求的简单表单,例如姓名、电子邮件和消息字段。
- 登录表单:这些是具有标准登录凭据验证的表单,例如电子邮件和密码字段。
- 快速数据录入表单:这些是字段数量少、不需要复杂数据处理或验证的表单。
以下是适合使用响应式表单的场景:
- 多步骤表单:这些是具有多个步骤或部分、需要条件验证和复杂导航的表单。
- 具有动态控件的表单:这些表单中控件的数量或类型可能根据用户输入或数据条件而变化。
- 自定义表单验证:这些是具有自定义验证规则的表单,涉及多个字段或复杂的数据依赖关系。
- 高级表单处理:这些是需要以编程方式控制表单提交、处理表单事件或进行复杂数据处理的表单。
模板驱动表单和响应式表单之间的选择取决于表单的复杂性和自定义需求。模板驱动表单适用于较简单的表单,而响应式表单为复杂表单提供了更好的可扩展性、灵活性和控制力。
因此,在选择模板驱动表单和响应式表单时,您需要考虑表单的具体要求、应用程序的复杂性以及所需的控制和自定义级别。
本节课中我们一起学习了 Angular 的两种表单构建方法。模板驱动表单适合快速构建简单表单,而响应式表单则为复杂、动态的表单场景提供了强大的程序化控制能力。理解它们的特点将帮助您在实际项目中做出合适的技术选型。


在下一个视频中,我们将学习模板驱动表单的具体实现。下个视频见。谢谢。
163:模板驱动表单入门教程 🚀
概述
在本节课中,我们将学习 Angular 中的模板驱动表单。这是一种通过 HTML 模板中的指令来创建和管理表单的简单方法。我们将通过一个具体示例,逐步了解如何构建一个包含基本验证和提交功能的表单。
模板驱动表单简介
上一节我们介绍了 Angular 表单的基本概念。本节中,我们来看看模板驱动表单的具体实现方式。
模板驱动表单提供了一种声明式的方法来处理表单。它依赖于 HTML 模板中的指令来创建和管理表单控件。
以下是创建模板驱动表单的主要步骤:
第一步:导入必要模块
首先,需要在模块中导入 FormsModule。这个模块提供了模板驱动表单所需的核心指令。
// 在 AppModule 中
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [
FormsModule // 导入 FormsModule
]
})
第二步:在模板中创建表单结构
接下来,在组件的 HTML 模板中构建表单。使用 <form> 元素包裹所有表单控件,并使用 ngModel 等指令将控件绑定到组件属性。

第三步:添加表单验证
可以为表单控件添加验证规则。可以使用 HTML 原生属性(如 required、minlength)或 Angular 指令来定义验证。
第四步:处理表单提交
将表单的 ngSubmit 事件绑定到组件中的一个方法,以处理表单提交逻辑。
第五步:访问表单数据和验证状态
通过 ngModel 指令,可以在组件中访问和操作表单数据,以及检查控件的验证状态。
实战示例:创建一个用户信息表单
现在,让我们通过一个具体示例来实践上述步骤。我们将创建一个包含“姓名”和“邮箱”字段的简单表单。
1. 准备项目与模块
确保已在项目的根模块(通常是 AppModule)中正确导入了 FormsModule。
2. 编写 HTML 模板
在组件的 HTML 文件中,我们构建表单结构。
<form (ngSubmit)="submitForm()">
<!-- 姓名字段 -->
<label for="name">姓名</label>
<input type="text" id="name" name="name" [(ngModel)]="user.name" required>
<!-- 邮箱字段 -->
<label for="email">邮箱</label>
<input type="email" id="email" name="email" [(ngModel)]="user.email" required>
<!-- 提交按钮 -->
<button type="submit">提交</button>
</form>

代码解释:
(ngSubmit)=“submitForm()”:将表单的提交事件绑定到组件的submitForm方法。[(ngModel)]=“user.name”:使用“双向数据绑定”将输入框的值与组件中的user.name属性同步。required:HTML5 属性,表示该字段为必填项。
3. 编写组件逻辑
在组件的 TypeScript 文件中,定义数据模型和处理提交的方法。

import { Component } from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
// 定义用户对象,用于绑定表单数据
user = {
name: '',
email: ''
};
// 处理表单提交的方法
submitForm() {
console.log('表单已提交', this.user);
// 这里可以添加将数据发送到服务器的逻辑
}
}
4. 运行与测试
保存所有文件后,应用将自动重新加载。在浏览器中:
- 你会看到一个包含两个输入框和一个按钮的表单。
- 尝试不填写任何内容直接点击“提交”,浏览器会阻止提交并提示必填字段。
- 填写“姓名”(如:John)和“邮箱”(如:john@email.com)后点击“提交”。
- 打开浏览器的开发者工具(Console 标签页),你将看到打印出的消息:“表单已提交”以及包含输入数据的
user对象。


核心概念总结
FormsModule:必须导入此模块才能使用模板驱动表单功能。ngModel指令:实现表单控件与组件属性之间的双向数据绑定。语法为[(ngModel)]=“propertyName”。ngSubmit事件:用于捕获表单提交事件,并触发组件中指定的处理方法。- 模板驱动表单的特点:逻辑主要写在 HTML 模板中,适合结构简单、验证逻辑不复杂的表单场景。
总结
本节课中,我们一起学习了 Angular 模板驱动表单的创建过程。我们从导入 FormsModule 开始,然后在模板中使用 form、input 标签及 ngModel、ngSubmit 等指令构建了一个具有基本验证功能的表单,最后在组件中定义了数据模型和处理提交的方法。模板驱动表单以其直观、声明式的方式,为构建简单表单提供了极大的便利。

在下一节视频中,我们将了解另一种更强大、更适合复杂场景的表单处理方式——响应式表单。
164:Angular响应式表单 🚀
在本节课中,我们将要学习Angular中的响应式表单。上一节我们介绍了模板驱动表单,本节中我们来看看响应式表单,它提供了一种更灵活、更程序化的方式来构建和管理表单。
概述

响应式表单使用Angular Forms模块提供的 FormControl、FormGroup 和 FormArray 类来构建。它们特别适合处理复杂的表单场景,例如动态控件、条件验证或自定义验证规则。
创建基本响应式表单
让我们通过一个例子来理解和创建一个基本的响应式表单。
首先,我们需要在项目中设置好基础环境。以下是创建表单的步骤:
1. 模板设置
在组件的HTML模板中,我们构建一个表单结构。表单使用 [formGroup] 指令绑定到组件中的一个 FormGroup 实例,并使用 (ngSubmit) 事件绑定提交方法。
<form [formGroup]="myForm" (ngSubmit)="submitForm()">
<label for="name">Name</label>
<input type="text" id="name" formControlName="name" required>
<label for="email">Email</label>
<input type="email" id="email" formControlName="email" required>
<button type="submit" [disabled]="!myForm.valid">Submit</button>
</form>
2. 组件逻辑设置
在组件的TypeScript文件中,我们需要导入必要的模块,并在 ngOnInit 生命周期钩子中初始化表单。
首先,在 AppModule 中导入 ReactiveFormsModule:
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [
ReactiveFormsModule
]
})
export class AppModule { }
接着,在组件类中创建表单:
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
myForm: FormGroup;
constructor(private formBuilder: FormBuilder) {}
ngOnInit() {
this.myForm = this.formBuilder.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]]
});
}
submitForm() {
console.log('Form submitted');
// 这里可以添加表单提交的逻辑,例如发送数据到服务器
}
}
3. 表单验证与提交

在模板中,提交按钮通过 [disabled]="!myForm.valid" 绑定到表单的有效性状态。只有当表单所有控件都通过验证时,按钮才会启用。
当用户点击提交按钮时,会触发 submitForm() 方法,执行表单提交逻辑。
核心概念解析


以下是响应式表单中的几个核心概念:
FormControl:代表表单中的一个单独输入控件,用于跟踪其值、验证状态和用户交互。FormGroup:代表一组FormControl实例的集合,用于管理多个相关控件的值和状态。FormBuilder:一个服务,用于简化FormGroup和FormControl的创建过程。Validators:提供了一系列内置的验证器函数,如required、email等,用于验证表单控件的输入。

总结
本节课中我们一起学习了Angular的响应式表单。我们了解了如何通过 FormBuilder 服务创建 FormGroup 和 FormControl,如何在模板中绑定表单和控件,以及如何实现表单验证和提交。

响应式表单通过其程序化的方式,为处理复杂表单逻辑提供了强大的控制和灵活性。掌握响应式表单是构建现代Angular应用的重要一步。
Java全栈开发专项课程(下):0.01:讲师介绍
在本节课中,我们将认识本课程的讲师,了解他的专业背景和教学经验。
大家好,我是Pabna Gwani,很高兴担任本课程的讲师。
我拥有多年的软件工程经验,主要专注于使用微软技术进行后端开发。
我曾与多家公司合作,为他们提供技术和企业培训,并协助他们的项目开发。
多年来,我精通了C#、.NET、Python、MEAN和MERN等技术栈。
我单独或组合使用这些技术来创建可扩展且健壮的系统。
这些系统为数百万用户提供了有价值的服务。作为一名教育者,我已向超过一千名学生教授软件开发。
因此,我理解学习软件开发并非易事。
但请放心,我将在此旅程中担任您的向导,为您提供掌握全栈开发所需的所有必要知识和工具。
非常感谢。
本节课中,我们一起认识了讲师Pabna Gwani,了解了他丰富的技术背景和教学经验,为后续的学习建立了良好的开端。
Java全栈开发:02:Java中的数组 📚


在本节课中,我们将学习Java中的数组。数组是一种用于存储多个相同类型数据的数据结构。通过使用数组,我们可以高效地管理和访问数据,避免为每个数据项单独创建变量。
在数字世界中,作为程序员,存储信息是一项至关重要的任务。每一小段信息都需要保存在内存位置中以备将来使用。为此,我们使用数组。数组是一种数据结构,用于存储相同数据类型的元素,而不是将成百上千个值存储在不同的变量中。
通过数组,我们可以将所有值存储在单个变量中,以便频繁使用数据。我们必须为这个变量分配一个名称。在数组中,每个元素都可以通过其索引号直接访问。假设数组的大小为5,那么索引从0开始,直到4(即5-1)。数组是一种同质的、非原始的数据类型,用于将多个元素保存到一个特定的变量中。
其核心思想是在数组中存储多个相同类型的项,这就是我们称之为“同质”的原因。通过将一个偏移量加到基值上,可以更容易地计算每个元素的位置。
假设你需要编写一个代码,用于记录五名学生的五门科目成绩。与其创建五个不同的变量,不如将数据存储并访问自一个名为 marks 的数组变量中。
与使用多个独立变量相比,使用数组有几个优点:
- 它有助于减少代码量,实现代码优化。
- 借助索引实现随机访问。
- 易于遍历数据。
- 易于操作和排序数据。
数组主要有两种类型:一维数组和多维数组。
如果你想将数据存储在特定的行中,那就是一维数组。例如,五名学生的成绩或五名学生的姓名。
如果你想存储多维数据,例如数组中的每个元素是一个学生对象,而每个对象又关联着多个属性,这种数据可以存储在多维数组中。同样,如果你需要处理任何依赖于矩阵表或向量的计算,也可以使用多维数组。


本节课我们一起学习了Java中数组的基本概念、优势以及主要类型。数组是组织和高效访问同类型数据集合的强大工具。在接下来的课程中,我们将通过实际实现来深入了解数组的更多细节。敬请关注,下节课见。
003:Java中的一维数组 📚

在本节课中,我们将要学习Java中的一维数组。一维数组是Java编程中最基础的数据结构之一,它用于存储一系列相同类型的数据。理解一维数组的声明、初始化和使用,是学习更复杂数据结构(如多维数组)的重要基础。
什么是单维数组? 📖

上一节我们介绍了数组的基本概念,本节中我们来看看一维数组的具体定义。
一个只有一个下标或一个维度的数组被称为一维数组。它本质上是一个由相同数据类型或值组成的列表。
我们使用一维数组,有时也称之为单维数组。它可以有两种表现形式:一行多列 或 多行一列。例如,一个学生在五门科目中的成绩就构成一个一维数组。
如何声明一维数组? ✍️
了解了定义后,我们来看看如何创建一维数组。我们使用方括号 [] 来指示数组的大小和维度。
声明数组主要有两种方式。第一种是先声明数组,然后使用 new 关键字为其分配内存空间。第二种方式是在声明的同时进行初始化。选择哪种方式完全取决于你的需求。
以下是声明数组的语法,我们稍后通过指定大小(例如5)来分配空间:
dataType[] arrayName;
arrayName = new dataType[size];
或者,你也可以在声明的同时初始化值。如果你直接给出了值,甚至可以省略指定大小。
如何初始化和访问数组? 🔢

声明了数组之后,我们需要知道如何给它赋值以及如何获取其中的值。
正如之前提到的,数组中的每个元素都通过索引访问。索引从 0 开始,一直到 n-1,其中 n 是数组的大小。对于一个大小为5的数组,有效索引是0到4。
让我们在Java中实际实现一个一维数组。
1. 声明数组
如果你想先声明数组,稍后再分配内存或赋值,可以这样做:
int[] marks;
marks = new int[5];
你也可以将这两个语句合并为一行:
int[] marks = new int[5];
2. 初始化数组
在声明数组的同时进行赋值,这被称为初始化数组。
int[] marks = new int[] {10, 20, 30, 40, 50};
或者,更简洁的写法(如果你直接给出值,可以省略 new int[]):
int[] marks = {10, 20, 30, 40, 50};
3. 逐个元素赋值
你也可以先声明数组并指定大小,然后逐个元素进行赋值:
int[] marks = new int[5];
marks[0] = 100;
marks[1] = 60;
marks[2] = 78;
marks[3] = 80;
marks[4] = 98;
可以看到,索引从0开始,到 n-1(即4)结束。
如何遍历一维数组? 🔄
创建并填充数组后,我们通常需要遍历它以访问所有元素。以下是两种常用的遍历方法。
使用传统的 for 循环
你可以使用一个从0开始的索引变量 i,循环条件为 i < marks.length。这样,循环将从0执行到4。
for (int i = 0; i < marks.length; i++) {
System.out.println(marks[i]);
}
使用 for-each 循环
Java还提供了更简洁的 for-each 循环来遍历数组或集合。
for (int value : marks) {
System.out.println(value);
}
for-each 循环会从 marks 数组中依次取出每个元素,赋值给变量 value,然后执行循环体。两种循环最终都会打印出数组中所有的值。
运行你的程序,你将看到数组中的值被打印两次:一次通过传统 for 循环,一次通过 for-each 循环。
总结 📝
本节课中我们一起学习了Java中的一维数组。我们首先了解了一维数组的定义,它是由相同类型数据组成的线性列表。接着,我们掌握了声明数组的两种主要方式,以及如何初始化和访问数组元素。最后,我们探讨了使用传统 for 循环和 for-each 循环遍历数组的方法。理解这些基础知识对于后续学习多维数组和各种数组方法至关重要。

请继续关注接下来的课程,我们将学习更多关于多维数组和数组方法的知识。下节课见! 🎼
Java全栈开发:04:多维数组详解 📚


在本节课中,我们将要学习Java中的多维数组。我们将了解什么是多维数组,为什么需要它,以及如何声明、初始化和遍历多维数组。课程内容将尽可能简单直白,确保初学者能够理解。
并非所有情况下我们都使用一维数组。例如,当你需要以表格、矩阵或向量的形式存储数据时,就需要用到多维数组。
多维数组,或称二维及更多维度的数组,其结构看起来像一个矩形数组,因为它的每一行长度相同,但列数可以不同。它可以是二维数组、三维数组或更多维。但当我们称之为多维数组时,至少需要是二维的。
我们使用逗号分隔多个维度来声明数组。为了存储和访问多维数组中的元素,需要使用嵌套循环,其中外层循环处理行,内层循环处理列。
以下是声明多维数组的方式:
int[][] arrayName = new int[3][3];
你可以看到这里我定义了两个维度。第一个维度是行,有三行;第二个维度是列,在我的例子中,有三列。所以这是一个3行3列的数组。
这里,第一行的行索引是0,列索引会不断变化;第二行的行索引是1,列索引同样会变化,依此类推。
接下来,让我们尝试实际实现一个多维数组。这里,我想存储一个成绩数组。我希望存储三个学生、每个学生五门科目的成绩。
我可以这样创建多维数组。我也可以不预先分配内存,而是直接根据这些值分配数据,内存会自动分配。
int[][] marks = {
{67, 78, 87, 89, 98},
{76, 77, 56, 65, 90},
{67, 79, 92, 63, 55}
};
如上所述,这将是一个3行5列的数组。我需要一个嵌套的for循环。第一个for循环将遍历行,第二个for循环将遍历列。
以下是遍历代码:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
System.out.print(marks[i][j] + " ");
}
System.out.println();
}
这样,你就可以处理多维数组了。
如果你不希望这样打印,而是希望以表格格式输出,只需在行变化时换行。我还会使用制表符转义字符 \t,以便在每个行的元素之间看到空格。
以表格格式打印的代码如下:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
System.out.print(marks[i][j] + "\t");
}
System.out.println();
}
这样,所有五门科目的成绩就会以整齐的表格形式打印出来。
我希望多维数组的概念对你来说已经清晰了。我们还有对象数组,并且在 java.util 包中有一个 Arrays 类,它包含许多预定义的方法。我们将在后续课程中讨论它们。

本节课中,我们一起学习了多维数组的基本概念、声明、初始化及遍历方法。掌握这些是处理更复杂数据结构的基础。我们下节课再见。
005:Java中的字符串


在本节课中,我们将学习Java编程中最重要的部分之一:字符串。字符串是编程不可或缺的组成部分,Java提供了String类来创建和操作字符串。
什么是字符串?
字符串本质上是一个表示字符序列的对象。虽然字符串在内部以字符数组的形式存储,但正如我在之前的课程中提到的,它是最常用、最频繁使用的数据类型。因此,它的语法看起来更像原始类型,但实际上它是一个引用类型。
String类是核心库的一部分,并提供了多种处理字符串的方法,稍后我将为您演示。
字符串的不可变性
字符串是不可变的。这意味着一旦为字符串创建了对象,其值就无法更改。因为当您更改其值时,系统会为您创建一个新的字符串。您可以通过对现有字符串执行各种操作或使用字符串方法来创建新的字符串。
字符串的初始化方式如下,但在内部,它会将字符串创建为一个字符数组,其中第一个字符是h,第二个是e,依此类推。
String myString = "hello";
创建字符串的两种方式
在Java中,有两种定义字符串的方式:一种是使用字符串字面量,另一种是使用new关键字创建字符串对象。
以下是两种方式的说明:
- 字符串字面量:字面量是用于表示值的符号。您可以直接将值赋给字符串变量。
- 使用
new关键字:当使用new关键字时,您实例化或创建了一个字符串对象,并将字符串作为参数传递给String类的构造函数,从而初始化字符串。
我们也可以借助复制来创建字符串,这一点稍后会讨论。
所有使用new关键字创建的字符串对象都会在堆内存中分配空间,无论堆内存中是否已存在相同值的字符串,因为其内存地址是不同的。
字符串的类型
字符串有两种类型:不可变字符串和可变字符串。
- 不可变字符串:正如之前所说,不可变意味着无法更改。这是您可以使用
new关键字或字符串字面量创建的普通字符串。 - 可变字符串:如果您不希望每次需要频繁更改时都创建新的字符串,可以使用
StringBuilder类。我将在后续的实践演示中向您展示这两种方式。
总结


本节课我们一起学习了Java中字符串的基础知识。我们了解了字符串的本质、其不可变性、两种创建方式(字面量与new关键字)以及两种类型(不可变与可变)。理解这些核心概念是有效使用Java字符串的关键。在接下来的课程中,我们将通过实践来深入掌握这些方法。
006:字符串操作 📝

在本节课中,我们将学习如何在Java中声明和操作字符串。我们将使用字符串字面量、字符串对象以及各种字符串方法来完成常见的任务。

字符串的声明
首先,我们来看看如何声明字符串。在Java中,主要有两种方式:使用字符串字面量和使用new关键字创建字符串对象。
以下是两种声明方式的示例:
// 使用字符串字面量声明
String str1 = "Hello";

// 使用字符串对象声明
String str2 = new String("World");
你可以通过System.out.println()方法来打印这些字符串,以验证它们的内容。
字符串的拼接
上一节我们介绍了如何声明字符串,本节中我们来看看如何将多个字符串连接在一起。你可以使用加号+运算符来拼接字符串字面量和字符串对象。
以下是拼接字符串的示例:
// 拼接字符串字面量和字符串对象
String str3 = str1 + str2;
System.out.println(str3);
运行这段代码,你将看到输出结果为HelloWorld。

常用的字符串方法
Java提供了许多内置的字符串方法,用于执行各种操作。以下是几个最常用的方法:
length():计算字符串的长度,包括空格。charAt(index):获取字符串中指定索引位置的字符。concat(string):将当前字符串与另一个字符串连接。substring(startIndex, endIndex):提取字符串中从startIndex到endIndex-1的子串。equals(string):比较两个字符串是否相等。contains(substring):检查字符串是否包含指定的子串。toUpperCase():将字符串转换为大写。toLowerCase():将字符串转换为小写。trim():移除字符串两端的空白字符。
字符串方法示例
让我们通过一些代码示例来演示这些方法的具体用法:
// 计算字符串长度
int length = str3.length();
System.out.println("Length: " + length);
// 获取指定索引的字符
char firstChar = str3.charAt(0);
System.out.println("First character: " + firstChar);
// 使用concat方法拼接字符串
String concatenated = str1.concat(str2);
System.out.println("Concatenated: " + concatenated);
// 提取子串
String substring = str3.substring(0, 5);
System.out.println("Substring: " + substring);
// 比较字符串是否相等
boolean isEqual = str1.equals(str2);
System.out.println("Are they equal? " + isEqual);
// 检查是否包含子串
boolean containsHello = str3.contains("Hello");
System.out.println("Contains 'Hello'? " + containsHello);
// 转换为大写
String upperCase = str3.toUpperCase();
System.out.println("Uppercase: " + upperCase);
// 转换为小写
String lowerCase = str3.toLowerCase();
System.out.println("Lowercase: " + lowerCase);
// 移除空白字符
String stringWithSpaces = " Hello World ";
String trimmed = stringWithSpaces.trim();
System.out.println("Trimmed: '" + trimmed + "'");
运行上述代码,你将看到每个方法的输出结果,从而更好地理解它们的功能。

总结


本节课中我们一起学习了Java中字符串的基本操作。我们首先介绍了如何使用字符串字面量和字符串对象来声明字符串,然后探讨了如何拼接字符串。接着,我们详细讲解了多个常用的字符串方法,并通过代码示例演示了它们的用法。掌握这些基础知识将帮助你在实际编程中更有效地处理字符串数据。
007:StringBuffer与StringBuilder详解 🔧

在本节课中,我们将要学习Java中两个重要的类:StringBuffer和StringBuilder。我们将探讨它们与普通String类的区别,理解“可变性”的概念,并通过代码示例比较它们的性能与线程安全性。
概述

在之前的课程中,我们讨论了String类及其方法。我们了解到,String对象是不可变的。这意味着一旦创建,其内容就无法被更改或修改。每次需要修改字符串时,都需要创建一个新的String对象。
StringBuffer:线程安全的可变字符串
上一节我们介绍了不可变的String,本节中我们来看看StringBuffer。StringBuffer是一个可变的字符串类,这意味着你可以轻松地修改其内部的字符串值。它提供了一种在Java中使字符串可变的方法。这些字符串可以安全地被多个线程同时使用,多个线程也可以修改它。
为了提供这种线程安全的优势,StringBuffer的实现相对较重。它提供了诸如append(追加)、insert(插入)、delete(删除)和reverse(反转)等方法,这些方法允许字符串被修改。
以下是StringBuffer的基本使用方法:
// 实例化StringBuffer
StringBuffer buffer = new StringBuffer("Hello");
// 使用append方法追加字符串
buffer.append(" World");
// 打印结果
System.out.println(buffer.toString());
你还可以检查StringBuffer的初始容量。如果初始化一个空的StringBuffer,其默认容量为16个字符。
StringBuffer buffer = new StringBuffer();
System.out.println(buffer.capacity()); // 输出:16
当你添加内容时,容量会根据需要自动增加。
StringBuilder:非线程安全的可变字符串
接下来,我们将StringBuffer与StringBuilder进行比较。StringBuilder也是一个提供可变字符串功能的类,但它缺乏线程安全性,因此不能被多个线程同时使用。这是它与StringBuffer的主要区别。
StringBuilder的使用方法与StringBuffer非常相似:
// 实例化StringBuilder
StringBuilder builder = new StringBuilder("Hello");
// 使用append方法追加字符串
builder.append(" World");
// 打印结果
System.out.println(builder.toString());
性能比较:StringBuffer vs. StringBuilder
为了理解性能差异,我们可以通过一个循环追加操作来测试两者所花费的时间。
以下是测试代码示例:
// 测试StringBuffer性能
long startTimeBuffer = System.currentTimeMillis();
StringBuffer buffer = new StringBuffer("Hello");
for (int i = 0; i < 10000; i++) {
buffer.append("World");
}
long timeTakenByBuffer = System.currentTimeMillis() - startTimeBuffer;
System.out.println("Time taken by StringBuffer: " + timeTakenByBuffer + " ms");
// 测试StringBuilder性能
long startTimeBuilder = System.currentTimeMillis();
StringBuilder builder = new StringBuilder("Hello");
for (int i = 0; i < 10000; i++) {
builder.append("World");
}
long timeTakenByBuilder = System.currentTimeMillis() - startTimeBuilder;
System.out.println("Time taken by StringBuilder: " + timeTakenByBuilder + " ms");
运行此代码后,你会发现StringBuilder所花费的时间通常少于StringBuffer。
这是因为StringBuilder虽然不适合多线程环境,但由于它不需要处理线程同步的开销,因此在单线程环境下是所有可变字符串类中速度最快的。而StringBuffer为了保证线程安全,允许同时进行多个线程操作,因此速度相对较慢。
总结
本节课中我们一起学习了StringBuffer和StringBuilder这两个关键的Java类。我们明确了以下核心概念:
String是不可变的。StringBuffer是可变且线程安全的,适用于多线程环境,但性能开销较大。StringBuilder是可变但非线程安全的,适用于单线程环境,是三者中性能最高的。

选择使用哪个类取决于你的具体需求:如果需要线程安全,请使用StringBuffer;如果追求单线程下的最高性能,请使用StringBuilder。
Java全栈开发:08:Java集合框架入门 🗂️

在本节课中,我们将要学习Java集合框架的基础知识。集合框架是Java中用于存储和操作一组对象的强大工具,它提供了一套标准化的接口和类,使得数据处理更加高效和便捷。

概述

Java集合框架是一个用于存储和帮助操作一组对象的框架。它是一个由接口和类组成的层次结构,旨在以高效、关联的方式管理对象组,并为数据结构和算法提供支持。
为了便于理解,可以想象一个存钱罐的例子。在我们童年时期,我们都曾使用存钱罐来存放硬币。这里的“存钱罐”就相当于一个集合,而“硬币”就是存储在其中的对象。因此,集合是一个可以存储一组其他对象的容器或对象。

集合框架的组成
Java集合框架主要包括以下三个核心部分:接口、类和算法。
上一节我们介绍了集合框架的基本概念,本节中我们来看看它的具体构成。
接口
接口是框架层次结构中的顶层。在通俗意义上,接口是一组具有空方法体的相关方法的集合。你也可以将其视为一种抽象,即向用户隐藏实现细节,仅提供功能。
在集合框架中,最顶层的接口是 Iterable 接口,它被 Collection 接口扩展。Collection 接口又有几个重要的子接口,如 List、Queue 和 Set。
类
类是相似类型对象的集合。在集合框架中,类是集合接口的具体实现。例如,ArrayList 和 LinkedList 是 List 接口的实现类。
算法
算法是执行有用计算、比较和操作的方法,例如搜索和排序。这些算法作用于实现了集合接口的对象上。你可以将其理解为解决问题的不同迭代方式,并根据其时间复杂度和空间复杂度选择最优方案。
集合框架的层次结构
正如前面提到的,集合框架是一个层次化的接口结构。其顶层是接口(图中通常用蓝色表示),下层是具体的实现类(图中通常用绿色表示)。
基础接口是 Iterable,它被 Collection 接口扩展。然后,Collection 接口又派生出 List、Queue 和 Set 等接口。每个接口都有特定的子类来实现接口中定义的行为、契约或抽象方法。
在接下来的课程中,我们将详细探讨 List、Queue 和 Set,学习如何在这些结构中存储特定数据,以及如何根据需求遍历对象列表。

总结
本节课中,我们一起学习了Java集合框架的基础。我们了解到集合框架是一个用于管理对象组的接口和类的层次结构,它包含接口、类和算法三个核心部分。接口定义了规范,类提供了具体实现,而算法则提供了高效的数据操作方法。

请继续关注后续课程,我们将通过更多实际迭代和示例,深入探讨集合框架的具体应用,实现其在实时项目中的价值。


我们下节课再见。
Java全栈开发:09:集合框架与Collection接口 🎯

在本节课中,我们将要学习Java集合框架的基础,特别是其根接口——Collection接口。我们将了解它的作用、基本操作以及它在整个框架中的位置。

上一节我们介绍了集合框架的整体概念,它是一个由接口和类组成的层次化结构。本节中我们来看看这个结构的根基:Collection接口。
Collection接口是整个集合框架构建的基石。它是一个通用接口,本身没有具体的实现声明。在定义时,我们使用泛型来指定该集合将要持有的对象类型。例如,如果集合要存储整数或字符串,我们需要在初始化实现此接口的类时传递相应的类型。
它提供了一系列基础操作,用于管理集合中的元素。以下是Collection接口定义的一些核心方法:
- 添加元素:
boolean add(E e) - 移除元素:
boolean remove(Object o) - 清空集合:
void clear() - 检查是否为空:
boolean isEmpty() - 获取大小:
int size() - 检查是否包含某元素:
boolean contains(Object o)
List、Queue和Set是三个重要的组件,它们都扩展(extends)了Collection接口。而集合框架中的所有具体类(如ArrayList, LinkedList, HashSet等)则实现了这些子接口。
因此,主要的层次关系是:Iterable接口被Collection接口扩展,然后List、Queue、Set等接口又扩展了Collection接口。这些子接口最终由具体的类来实现,例如LinkedList、PriorityQueue、HashSet等等。

本节课中我们一起学习了Collection接口的核心地位与基本功能。它是操作所有集合类型的通用契约,为添加、删除、检查元素等操作提供了统一的方法定义。理解它是深入学习具体集合类(如List, Set, Queue)的关键第一步。在接下来的课程中,我们将继续探讨这些扩展接口及其实现类。
Java全栈开发:10:List接口详解 📚
在本节课中,我们将要学习Java集合框架中的List接口。List接口扩展了Collection接口,用于存储有序的数据集合,并且允许包含重复元素。我们将了解其基本概念、语法以及实现它的几个主要类。

什么是List接口? 📋

List接口用于存储有序的数据集合,并且可以包含重复的元素。这里的“有序”指的是元素被插入的顺序会被保留。
List中的元素可以通过它们在列表中的位置(即索引)来访问或插入。Java使用基于0的索引,这意味着索引从0开始计数。
这个接口位于 java.util 包中。
List接口的实现类 🏗️
Java中有多个类实现了List接口,主要包括以下三个:
- ArrayList
- LinkedList
- Vector
这三个类允许我们通过迭代来操作一组特定类型的对象。我们只需要理解这个接口默认声明了哪些抽象方法以及它们的功能,例如 add、remove、clear、size、iterator、contains 等等。
如何使用List接口? 💻
现在,让我们从语法开始,看看如何通过初始化这些实现类来创建List接口的引用变量。
你可以在主方法中这样写:List 是一个接口,它位于 java.util 包中。我们需要指定要存储的数据类型,这里我们以包装类 Integer 为例。
首先,我创建一个 LinkedList 的引用变量。需要再次强调,你不能直接实例化List接口。你可以看到,如果尝试 new List<>(),编译器会报错并要求提供具体实现,因为List是一个接口。我们必须使用它的具体实现类,如 LinkedList、Vector 或 ArrayList。
以下是如何创建不同List实现类的示例:
// 创建一个LinkedList
List<Integer> linkedList = new LinkedList<>();
// 创建一个ArrayList
List<Integer> arrayList = new ArrayList<>();
// 创建一个Vector
List<Integer> vectorList = new Vector<>();
请注意,在实例化这些具体类时,我指定了数据类型 Integer。正如之前提到的,List是一个泛型接口,它的实现类也是泛型的。你需要根据你想要操作或存储的对象类型,在实例化时传入相应的数据类型。
总结 📝
本节课我们一起学习了Java中的List接口。我们了解到List是一个用于存储有序、可重复元素集合的接口。它位于 java.util 包中,并且不能被直接实例化。我们介绍了三个主要的实现类:ArrayList、LinkedList 和 Vector,并学习了如何通过它们来创建List类型的引用变量。关键在于理解List的泛型特性,并在创建时指定要存储的数据类型。

在下一节中,我们将更深入地探讨 ArrayList 的具体用法。敬请期待,我们下节课再见!
011:ArrayList详解


在本节课中,我们将要学习一个实现了List接口的重要类——ArrayList。我们将了解它的特性、常用方法以及如何在代码中实际使用它。
概述
ArrayList是Java集合框架中的一个核心类。它类似于数组,但提供了动态调整大小的能力,因此也被称为动态数组。它实现了List接口,这意味着它支持有序的元素集合,并允许重复元素。
上一节我们介绍了List接口,本节中我们来看看它的一个具体实现——ArrayList。
ArrayList的特性
ArrayList的核心特性在于其动态性。与普通数组不同,ArrayList在创建时无需指定固定大小。其容量会根据元素的添加或删除自动增长或缩减。
以下代码展示了如何声明和初始化一个ArrayList:
import java.util.ArrayList;
ArrayList<String> list = new ArrayList<>();
需要注意的是,ArrayList不能用于存储基本数据类型(如int, char)。正如在List接口中讨论的,必须使用对应的包装类(如Integer, Character)。
ArrayList的常用方法
以下是ArrayList类中一些非常常用和重要的方法。掌握这些方法对于有效操作集合至关重要。
- add(E e): 将指定元素追加到列表的末尾。
- add(int index, E element): 将元素插入到列表中的指定位置。
- clear(): 移除列表中的所有元素。
- get(int index): 返回列表中指定位置的元素。
- indexOf(Object o): 返回指定元素在列表中首次出现的索引,如果不存在则返回-1。
- lastIndexOf(Object o): 返回指定元素在列表中最后一次出现的索引。
- remove(Object o): 移除列表中首次出现的指定元素。
- remove(int index): 移除列表中指定位置的元素。
- size(): 返回列表中的元素数量。
- trimToSize(): 将此ArrayList实例的容量调整为列表的当前大小。
实践:使用ArrayList
现在,让我们通过一个简单的例子来理解ArrayList在实际编程中如何工作。
首先,我们创建一个存储字符串的ArrayList,并向其中添加几个元素。
import java.util.ArrayList;
public class ArrayListDemo {
public static void main(String[] args) {
// 初始化一个ArrayList
ArrayList<String> nameList = new ArrayList<>();
// 使用add方法添加元素
nameList.add("King");
nameList.add("Sia");
nameList.add("Sarah");
// 方法1:直接打印整个列表
System.out.println("整个列表: " + nameList);
// 方法2:使用增强for循环遍历列表
System.out.println("遍历列表:");
for (String name : nameList) {
System.out.println(name);
}
// 获取列表大小
System.out.println("列表大小: " + nameList.size());
// 移除指定索引的元素(索引0,即"King")
nameList.remove(0);
System.out.println("移除第一个元素后: " + nameList);
// 再次检查大小
System.out.println("新列表大小: " + nameList.size());
}
}
运行上述代码,你将看到列表的初始内容、遍历结果,以及在移除一个元素后列表发生的变化。这演示了如何使用add、size和remove等基本方法。
总结


本节课中我们一起学习了Java集合框架中的ArrayList。我们了解到它是一个动态数组,可以自动调整大小。我们学习了其核心特性、常用方法(如add, remove, size),并通过一个完整的代码示例实践了如何创建、填充、遍历和修改一个ArrayList。掌握ArrayList是进行Java集合操作的重要基础。
Java 全栈开发:12:LinkedList 数据结构详解 📚

在本节课中,我们将要学习 Java 集合框架中的 LinkedList 类。我们将了解它的基本概念、如何创建和使用它,以及一些常用的操作方法。

LinkedList 是一个实现了 List 和 Deque 接口的类,它构成了一个链表数据结构,其中的元素被称为节点。LinkedList 类默认被包含在集合框架中。
上一节我们介绍了集合框架的基本概念,本节中我们来看看如何具体使用 LinkedList。
首先,我们需要初始化一个 LinkedList 对象。你可以创建一个 List 类型的引用变量来指向它,但通常直接使用 LinkedList 类型。
以下是初始化 LinkedList 的代码示例:
LinkedList<String> linkedList = new LinkedList<>();
初始化后,我们可以使用 size() 方法来查看链表的初始大小。
System.out.println("初始大小: " + linkedList.size());
接下来,我们向链表中添加一些元素。LinkedList 提供了 add 方法来添加元素。
以下是向链表添加元素的步骤:
- 使用
add方法添加第一个元素 “Java”。 - 继续添加 “Python”。
- 添加 “JavaScript”。
- 添加 “C#”。
添加完成后,我们可以打印整个链表来查看内容。
linkedList.add("Java");
linkedList.add("Python");
linkedList.add("JavaScript");
linkedList.add("C#");
System.out.println("添加元素后: " + linkedList);
如果你想在链表的特定索引位置插入一个元素,可以使用 add(int index, E element) 方法。
例如,在索引 2 的位置插入 “C++”:
linkedList.add(2, "C++");
如果你想从链表中移除元素,可以使用 remove 方法。它有两种方式:通过索引移除,或者直接指定要移除的对象。
以下是移除元素的方法:
- 通过索引移除:
linkedList.remove(0)会移除第一个元素(“Java”)。 - 通过对象移除:
linkedList.remove(“JavaScript”)会移除值为 “JavaScript” 的元素。
在添加和移除了若干元素后,我们可以再次打印链表,并检查其最终大小。
linkedList.remove(0); // 移除索引0的元素
linkedList.remove("JavaScript"); // 移除“JavaScript”
System.out.println("操作后链表: " + linkedList);
System.out.println("最终大小: " + linkedList.size());
除了 add、remove 和 size,LinkedList 还提供了许多其他方法,例如查找元素的索引(indexOf)、检查是否包含某元素(contains)等。这些方法与 ArrayList 中的方法非常相似。


本节课中我们一起学习了 LinkedList 的基本用法,包括如何创建链表、添加元素到特定位置、移除元素以及获取链表大小。LinkedList 的操作方法与 ArrayList 类似,但底层数据结构不同,这会在特定场景下影响性能。
013:Vector详解 🧮

在本节课中,我们将要学习Java集合框架中的Vector类。我们将了解它的定义、特性、与ArrayList的区别,并通过一个简单的编程示例来掌握其基本用法。

概述
与ArrayList和LinkedList类似,Vector也用于实现动态数组。Vector就像一个可以动态增长或缩小的数组。它扩展了AbstractList类并实现了List接口。


Vector的核心特性
上一节我们介绍了Vector的基本概念,本节中我们来看看它的一个核心特性。
Vector是同步的。这意味着在任意给定时间,只有一个线程可以访问代码。如果一个线程正在操作Vector,其他线程就无法获取它。因此,对Vector的操作一次只能执行一个。例如,如果一个线程正在执行添加操作,其他操作必须等待该操作完成。
Vector扩展了AbstractList类并实现了List接口。

访问与使用Vector
我们可以通过索引来访问Vector对象中的元素。

正如之前提到的,它类似于ArrayList,但存在一些关键区别。Vector是同步的,并且包含许多不属于集合框架接口的遗留方法。
以下是使用Vector的主要原因:
- 它具有动态大小,与
ArrayList或LinkedList一样。 - 它提供了遗留类支持。
- 它的同步特性是一个附加优势,可以限制多个线程同时对列表进行操作或修改。
Vector提供了一些特定的方法,例如addAll、addElement、capacity、contains、equals、get、indexOf。你可以使用来自集合接口的方法,也可以使用Vector自身的一些方法来遍历列表。
Vector 与 ArrayList 的比较
大多数时候,我们会将Vector与ArrayList进行比较。
以下是它们的主要区别:
ArrayList是Java集合框架的一部分,而Vector是Java的遗留类。- 当容量达到上限时,
Vector的容量会翻倍增长,而ArrayList的容量则增长一半。 Vector的方法是同步的,因此不允许在给定时间点由多个线程进行修改或操作,而ArrayList是不同步的。Vector使用Enumeration和Iterator来遍历元素列表,而ArrayList只使用Iterator。- 由于同步机制,
Vector的操作速度较慢,而ArrayList更快。 Vector有一个增量大小(capacityIncrement)属性,可以用来控制容量增长,而ArrayList不提供此功能。Vector是线程安全的,这意味着允许多个线程安全地使用它。而ArrayList不是线程安全的。
编程实现
让我们尝试在编程中实现Vector。
以下是创建一个Vector并演示其基本操作的示例代码:
import java.util.Vector;
public class VectorDemo {
public static void main(String[] args) {
// 创建一个存储字符串的Vector
Vector<String> vector = new Vector<>();
// 首先,显示Vector的初始大小
System.out.println("初始Vector大小: " + vector.size());
// 向Vector中添加元素
vector.add("Programming");
vector.add("Networking");
vector.add("Database");
vector.add("Deployment");
vector.add("Cloud Services");
// 显示Vector的内容
System.out.println("添加元素后的Vector: " + vector);
// 检查Vector的大小
System.out.println("当前Vector大小: " + vector.size());
// 如果想移除某个元素,使用remove方法并传入要移除的索引
vector.remove(1); // 移除索引为1的元素("Networking")
System.out.println("移除一个元素后的Vector大小: " + vector.size());
// 如果想移除所有元素,使用clear方法
vector.clear();
System.out.println("清空所有元素后的Vector大小: " + vector.size());
}
}
执行这段代码,我们可以看到输出结果:
初始Vector大小是0。
添加五个字符串后,大小变为5。
移除一个元素后,大小变为4。
清空所有元素后,大小变回0。
这就是Vector在实际中的一个简单应用示例。在后续的示例中,我还会演示如何像使用ArrayList或LinkedList一样,使用Vector来存储对象或类类型的实例。
总结
本节课中我们一起学习了Java中的Vector类。我们了解了它是同步的动态数组,是Java的遗留类。我们探讨了它与ArrayList在多线程支持、性能、容量增长策略等方面的主要区别。最后,我们通过一个编程示例实践了Vector的创建、添加、移除和清空等基本操作。理解这些集合类的特性和适用场景,对于构建高效、稳定的Java应用程序至关重要。
我们下节课再见。保持关注,谢谢!🎼


014:栈(Stack)数据结构详解

在本节课中,我们将要学习Java集合框架中的栈(Stack)数据结构。我们将了解它的定义、核心原理、实现方式以及实际应用场景。

上一节我们介绍了向量、列表和链表。本节中我们来看看栈,因为Stack类扩展了Vector类,是其子类。
栈是一种线性数据结构,其中元素的插入和删除只能在一端进行,这一端被称为栈顶。
实际上,操作栈时需要遵循一个原则,即LIFO(后进先出)。
栈也实现了诸如List、Collection、Iterable、Cloneable和Serializable等接口。
为了将一个对象放入栈顶,我们使用push方法。
为了移除并返回栈顶的元素,我们使用pop方法。
以下是基于现实场景的更多例子:当我们堆叠书本时,我们总是将最新的书放在最上面,然后一本接一本地从顶部取走。因此,只有一个端点用于元素的插入和删除。
栈的实例化方式如下所述。如前所述,Stack扩展了Vector类,而Vector在多级继承中实现了Iterable、Collection和List接口。简而言之,栈拥有来自Iterable、Collection、List接口以及Vector类的方法实现。
如果你基于实际应用来比较,以下是栈可以执行的技术应用:
- 浏览器历史记录遍历:当你点击特定链接时会发生,但当你想要返回上一页时,你需要逐一点击浏览器的后退按钮,无法直接跳转到第二个或第三个链接。
- 表达式求值。
- 树遍历(如二叉树)。
- 编辑器中的撤销操作。
- 递归。
- 编译器中的语法分析。


本节课中我们一起学习了栈数据结构的基本概念、LIFO原理、核心方法(push和pop)以及其广泛的应用场景。理解栈是掌握更复杂算法和数据结构的基础。
015:栈操作详解 🥞


在本节课中,我们将学习Java中栈(Stack)数据结构的基本操作。栈遵循“后进先出”(LIFO)的原则,所有操作都只发生在一端,即栈顶。我们将通过代码示例来理解栈的创建、元素添加、删除以及状态检查等核心操作。
栈的工作原理
上一节我们介绍了栈的基本概念,本节中我们来看看栈的具体工作原理。栈基于“后进先出”原则运行。这意味着所有操作都只从一端进行,这一端被称为栈顶。
当我们向栈中压入元素时,这被称为从栈顶进行插入。例如,依次压入10、20、30和40。当执行弹出操作时,它会移除栈顶的元素。
如果你想检查哪个元素是下一个准备被删除或弹出的,可以使用名为peek的方法。如果你想查看栈是否为空,则可以使用isEmpty方法。
以下是栈的核心操作方法:
push:向栈顶添加一个元素。pop:移除并返回栈顶的元素。peek:返回栈顶的元素但不移除它。isEmpty:检查栈是否为空。search:搜索栈中是否存在某个元素。
栈的状态与索引
通过操作栈,你需要根据栈顶的值来了解栈的状态。如果栈为空,则返回-1。如果栈中有一个元素,则栈顶索引值为0。如果栈已满,则其索引为n-1。当索引值恰好等于n时,表示栈溢出。这里的n是数组的大小或数组中已有的元素数量。
实践操作:Java栈示例
现在,让我们通过一个实际的Java程序来理解这些操作。
首先,我将创建一个栈对象。栈类位于java.util包中,它是一个泛型类。这里我将其指定为Integer类型。
import java.util.Stack;
public class StackDemo {
public static void main(String[] args) {
// 创建栈对象
Stack<Integer> stack = new Stack<>();
// 打印栈的初始大小
System.out.println("初始栈大小: " + stack.size());
// 向栈中压入元素
stack.push(10);
stack.push(20);
stack.push(30);
stack.push(40);
// 打印栈中的元素
System.out.println("栈元素: " + stack);
// 打印当前栈的大小
System.out.println("压入元素后栈大小: " + stack.size());
// 弹出栈顶元素并打印
int poppedElement = stack.pop();
System.out.println("弹出的元素: " + poppedElement);
// 查看栈顶元素(不弹出)
int topElement = stack.peek();
System.out.println("当前栈顶元素(peek): " + topElement);
// 再次打印栈的大小和元素
System.out.println("弹出一次后栈大小: " + stack.size());
System.out.println("弹出一次后栈元素: " + stack);
}
}
运行这段代码,你将看到以下输出:
- 初始栈大小为0。
- 压入四个元素后,栈大小变为4。
- 栈元素按顺序打印为
[10, 20, 30, 40]。 - 执行
pop操作后,弹出的元素是40。 - 使用
peek方法查看当前栈顶元素,结果是30(注意,peek不会移除元素)。 - 最后,栈中剩余三个元素:10、20和30,栈大小变为3。
重要注意事项
有一点需要注意:Java标准库中的Stack类是线程安全的。在不需要线程安全特性的环境中,使用它可能会带来额外的开销。在这种情况下,可以考虑使用ArrayDeque来替代Stack。
总结


本节课中我们一起学习了Java栈的基本操作。我们了解了栈的LIFO特性,并通过代码实践了如何使用push、pop、peek和size等方法。我们还讨论了栈的状态判断以及在实际开发中关于线程安全的考量。掌握这些操作是理解更复杂数据结构和算法的基础。
016:使用栈实现字符串反转练习 🧱

在本节课中,我们将学习如何使用栈(Stack)这种数据结构来实现一个字符串反转的操作。栈的“后进先出”(LIFO)特性使其非常适合完成此类任务。
概述

我们将创建一个名为 StringReversal 的类,其中包含一个 reverse 方法。该方法接收一个字符串作为输入,利用栈来反转字符的顺序,并返回反转后的字符串。最后,我们将在主方法中测试这个功能。
实现步骤
以下是实现字符串反转功能的具体步骤。
1. 创建类与方法
首先,我们创建一个类和一个用于反转字符串的方法。
public class StringReversal {
public String reverse(String input) {
// 反转逻辑将在这里实现
return "";
}
}
2. 初始化栈
在 reverse 方法内部,我们需要一个栈来存储字符。我们使用 Stack<Character>。
Stack<Character> stack = new Stack<>();
3. 将字符压入栈
接下来,我们需要遍历输入字符串,并将每个字符依次压入栈中。这利用了栈的“后进先出”特性。
for (char ch : input.toCharArray()) {
stack.push(ch);
}
4. 弹出字符以构建反转字符串
当所有字符都入栈后,我们通过依次弹出栈顶元素来构建反转后的字符串。
StringBuilder reversed = new StringBuilder();
while (!stack.isEmpty()) {
reversed.append(stack.pop());
}
return reversed.toString();
5. 测试程序
最后,我们在 main 方法中实例化类,传入测试字符串,并打印原始字符串与反转后的字符串进行对比。
public static void main(String[] args) {
String str = "ABCD";
StringReversal reverser = new StringReversal();
String result = reverser.reverse(str);
System.out.println("原始字符串: " + str);
System.out.println("反转后字符串: " + result);
}
核心概念与代码
本节的核心是理解栈的操作。主要涉及两个操作:
push(element):将元素压入栈顶。pop():移除并返回栈顶元素。
完整的 StringReversal 类代码如下:
import java.util.Stack;
public class StringReversal {
public String reverse(String input) {
Stack<Character> stack = new Stack<>();
for (char ch : input.toCharArray()) {
stack.push(ch);
}
StringBuilder reversed = new StringBuilder();
while (!stack.isEmpty()) {
reversed.append(stack.pop());
}
return reversed.toString();
}
public static void main(String[] args) {
String str = "ABCD";
StringReversal reverser = new StringReversal();
String result = reverser.reverse(str);
System.out.println("原始字符串: " + str);
System.out.println("反转后字符串: " + result);
}
}
运行结果
运行上述程序,控制台将输出:
原始字符串: ABCD
反转后字符串: DCBA
可以看到,输入字符串 “ABCD” 被成功反转为 “DCBA”。

总结

本节课中,我们一起学习了如何利用栈的“后进先出”特性来实现字符串反转。我们创建了一个完整的Java类,定义了反转方法,并进行了测试。通过这个练习,你不仅掌握了栈的基本操作(push 和 pop),也理解了如何将数据结构应用于解决实际问题。
017:队列详解


在本节课中,我们将要学习Java中的队列数据结构。队列是一种遵循特定顺序存储数据的线性数据结构,其核心原则是“先进先出”。我们将探讨队列的基本概念、工作原理、实现方式以及其在实际场景中的应用。
队列的基本概念
上一节我们介绍了集合框架,本节中我们来看看队列。队列是一个接口,它继承自集合接口。队列是一种以有序形式存储数据的数据结构,也可以看作是一个待存储项目的线性序列。
与栈的“后进先出”原则不同,队列遵循的是“先进先出”原则。你可以联想任何现实生活中的排队场景:第一个进入队列的元素将首先被处理,后续元素按顺序依次处理。
队列有两个端点:队尾和队头。元素从队尾插入,并且只能从队头移除。因此,队列本质上是一个顺序序列。
队列的表示与实现
以下是队列的图形化表示:
[ 队头 ] -> [ 元素1 ] -> [ 元素2 ] -> ... -> [ 元素N ] <- [ 队尾 ]
如之前所述,队列数据结构可以从其两端进行访问:队头用于删除操作,队尾用于插入操作。
队列可以通过多种方式实现,包括数组、链表或向量。为了便于理解,本教程将使用一维数组来实现队列,这样你可以更专注于队列的各种操作迭代。
队列的操作
以下是队列支持的核心操作:
- 入队:将一个元素添加到队列的队尾。
- 出队:从队列的队头移除一个元素。
- 查看队头:获取队头的元素但不移除它。
- 检查队列是否为空:判断队列中是否没有元素。
- 获取队列大小:返回队列中当前元素的数量。
队列的应用场景
队列在现实生活和计算机科学中有广泛的应用。以下是一些常见的例子:
- 任务调度:操作系统使用队列来管理等待CPU执行的进程。
- 消息队列:在分布式系统中,用于在不同服务之间异步传递消息。
- 数据缓冲:例如,在打印任务中,打印作业被放入队列等待打印机处理。
- 广度优先搜索:在图算法中,队列用于按层级遍历节点。
总结


本节课中我们一起学习了Java队列的核心知识。我们了解到队列是一种“先进先出”的线性数据结构,元素从队尾插入,从队头移除。我们探讨了其基本操作,并通过现实世界的例子理解了它的应用场景。掌握队列对于理解更复杂的算法和系统设计至关重要。
018:队列操作详解 🚶♂️➡️🚶♀️
在本节课中,我们将学习Java中队列(Queue)的基本概念、常见操作及其实现。队列是一种遵循“先进先出”(FIFO)原则的数据结构,类似于现实生活中的排队场景。
概述
队列在编程中应用广泛,例如处理任务调度、消息传递等。本节将介绍队列的基本操作,并通过Java代码演示如何实现这些操作。
队列的现实应用
以下是队列在现实生活中的一些应用场景,这些场景都遵循“先到先得”的原则:
- 自动扶梯上的乘客依次上下。
- 商店收银台前的排队。
- 银行柜台前的排队。
- 公交车站的候车队伍。
- 洗车店的车辆排队。
队列的基本操作
上一节我们了解了队列的应用场景,本节中我们来看看队列支持哪些核心操作。以下是队列最常用的几种方法:
- enqueue:向队列尾部添加一个元素。在Java的
PriorityQueue中,我们使用add()方法。queue.add(element); - dequeue:从队列头部移除一个元素。在Java的
PriorityQueue中,我们使用poll()方法。queue.poll(); - peek:查看队列头部的元素,但不移除它。
queue.peek(); - initialize:创建一个空队列。
Queue<Integer> queue = new PriorityQueue<>(); - isFull / isEmpty:检查队列是否已满或为空。
isEmpty()返回一个布尔值。queue.isEmpty(); // 返回 true 或 false

代码实践:实现队列操作
现在,让我们通过具体的Java代码来实践上述操作。我们将使用Java内置的PriorityQueue类。
首先,我们创建一个PriorityQueue实例并添加一些元素:
// 创建一个优先级队列实例
PriorityQueue<Integer> queue = new PriorityQueue<>();
// 添加元素到队列
queue.add(10);
queue.add(20);
queue.add(30);
queue.add(40);
// 打印队列
System.out.println(queue);
接下来,我们检查队列的大小并查看队首元素:
// 检查队列大小
System.out.println("Size: " + queue.size());
// 查看队首元素(不删除)
System.out.println("Element ready to remove (peek): " + queue.peek());
然后,我们从队列中移除元素并再次检查大小:
// 移除队首元素
System.out.println("Element removed (poll): " + queue.poll());
// 再次检查队列大小
System.out.println("Size after removal: " + queue.size());
最后,我们可以检查队列是否为空:
// 检查队列是否为空
System.out.println("Is queue empty? " + queue.isEmpty());
运行以上代码,输出结果将演示队列的FIFO行为:第一个添加的元素(10)会首先被移除。
总结

本节课中我们一起学习了Java队列的核心操作。我们了解了队列“先进先出”的特性,认识了enqueue(添加)、dequeue(移除)和peek(查看)等基本方法,并通过PriorityQueue类进行了代码实践。记住,poll()方法会移除元素,而peek()方法仅查看不移除。掌握这些操作为处理需要顺序管理的任务打下了基础。


敬请关注后续课程,以深入学习更多相关概念。我们下节课再见。
019:使用队列反转字符串

在本节课中,我们将学习如何使用队列(Queue)来反转一个字符串。在上一节中,我们介绍了如何使用栈(Stack)完成同样的操作,因此通过对比,您将对栈和队列的操作有更清晰的理解。
概述
本节课的目标是演示一个反转队列中元素顺序的静态方法。我们将创建一个方法,它接收一个队列作为参数,并使用一个辅助栈来反转队列中元素的顺序。最后,我们将通过一个简单的示例来验证方法的正确性。

实现步骤
以下是实现反转队列功能的核心步骤。
首先,我们创建一个静态方法,它接受一个泛型队列作为参数。这意味着该方法可以处理整数、字符串或任何其他类型的队列。
public static <T> void reverse(Queue<T> queue) {
// 反转逻辑将在这里实现
}
上一节我们介绍了栈的基本操作,本节中我们来看看如何结合栈来反转队列。我们将在方法内部创建一个栈,用于临时存储队列中的元素。
Stack<T> stack = new Stack<>();
接下来,我们需要将队列中的所有元素依次移出并压入栈中。由于栈的“后进先出”特性,元素的顺序将被反转。
while (!queue.isEmpty()) {
stack.push(queue.remove());
}
当队列中所有元素都转移到栈中后,我们再将栈中的元素依次弹出并添加回队列。此时,队列中的元素顺序就是反转后的顺序。
while (!stack.isEmpty()) {
queue.add(stack.pop());
}
完整代码示例
为了清晰地展示整个过程,以下是将上述步骤整合后的完整静态方法,以及一个用于测试的 main 方法。
import java.util.*;
public class QueueReversalDemo {
// 反转队列的静态方法
public static <T> void reverse(Queue<T> queue) {
Stack<T> stack = new Stack<>();
// 将队列元素转移到栈中
while (!queue.isEmpty()) {
stack.push(queue.remove());
}
// 将栈元素转移回队列(此时顺序已反转)
while (!stack.isEmpty()) {
queue.add(stack.pop());
}
}
public static void main(String[] args) {
// 创建一个整数队列并添加元素
Queue<Integer> queue = new ArrayDeque<>();
queue.add(20);
queue.add(30);
queue.add(40);
System.out.println("原始队列: " + queue);
// 调用反转方法
reverse(queue);
System.out.println("反转后队列: " + queue);
}
}
运行结果分析
运行上述程序,您将看到类似以下的输出:
原始队列: [20, 30, 40]
反转后队列: [40, 30, 20]
输出结果明确显示,队列中元素的顺序已经从 [20, 30, 40] 成功反转为 [40, 30, 20]。
总结

本节课中我们一起学习了如何使用一个辅助栈来反转队列中的元素顺序。我们实现了一个通用的静态方法,并通过一个具体的例子验证了其功能。这个方法的核心思想是利用栈的“后进先出”特性来逆转队列的“先进先出”顺序。您也可以尝试使用其他数据结构,如 LinkedList 或 Vector 来实现相同的逻辑。希望这个概念对您来说已经非常清晰。
Java全栈开发:20:Java Map接口详解 🗺️


在本节课中,我们将要学习Java中的Map接口。Map是一种基于键值对存储数据的集合,类似于字典。我们将了解其核心概念、实现类、常用方法以及需要注意的异常。
概述
Map接口用于存储键值对,其中每个键都是唯一的。它提供了基于键进行搜索、更新和删除元素的高效方法。Java提供了多个实现Map接口的类,如HashMap、LinkedHashMap和TreeMap。
Map的核心概念
Map包含基于键的值,即键值对。就像字典一样,每个键值对被称为一个条目,并且Map中的键始终是唯一的。您可以通过Map接口中提供的一些方法来单独访问键和条目。如果您需要基于键来搜索、更新或删除元素,Map会非常有用。
在Java中,有两个接口用于实现Map:Map和SortedMap。此外,还有三个主要的实现类:HashMap、LinkedHashMap和TreeMap。
Map不允许重复的键。正如前面提到的,Map中的键始终是唯一的。HashMap和LinkedHashMap允许空键和空值,这意味着空键和空值的迭代是非重复的。但TreeMap不允许任何空值和空键本身。
以下是Map的层次结构:
有两个接口,一个是Map,另一个是SortedMap。NavigableMap是SortedMap的子接口。它们被HashMap、LinkedHashMap和TreeMap类扩展或实现。
常用Map方法
以下是允许我们基于键及其值来实现和操作Map的一些常用方法:
put(K key, V value):在Map中插入一个条目。putAll(Map<? extends K, ? extends V> m):将指定Map中的所有条目插入当前Map。putIfAbsent(K key, V value):检查插入的值是否存在;如果不存在,则插入;否则跳过。remove(Object key):删除指定键对应的条目。get(Object key):返回指定键对应的值。containsKey(Object key):检查Map集合中是否存在特定的键。entrySet():返回Map中所有条目的集合。keySet():仅获取所有键的集合。values():仅获取所有值的集合。
Map的常见异常
在使用Map时,您可能会遇到以下异常:
NoSuchElementException:当调用的Map或条目中不存在项目时抛出。ClassCastException:当尝试将对象分配给Map中不兼容的元素时发生。NullPointerException:如果尝试使用空对象,并且在您使用的特定Map类型中不允许空值时发生。UnsupportedOperationException:当尝试更改不可修改的Map时抛出。
因此,在使用Map时,您需要根据这些异常来调整您的操作。
总结

本节课我们一起学习了Java中的Map接口。我们了解了Map基于键值对存储数据的核心概念,认识了其主要的实现类(HashMap、LinkedHashMap、TreeMap)以及它们之间的区别。我们还介绍了Map接口中常用的方法,并指出了在使用过程中可能遇到的几种异常情况。掌握这些知识是有效使用Java集合框架进行数据管理的基础。在接下来的课程中,我们将继续深入探讨Map接口及其子类的更多细节。
021:HashMap详解 🗺️
在本节课中,我们将要学习Java集合框架中的一个重要组成部分——HashMap。HashMap是Map接口的一个基础实现,它用于存储键值对数据。我们将了解其基本概念、工作原理、常用方法,并通过示例代码演示其基本操作。
概述
HashMap实现了Map接口,以键值对的形式存储数据。要访问一个值,必须知道其对应的键。HashMap内部使用了一种称为“哈希”的技术,该技术能将一个大的字符串转换成一个代表相同字符串的较短值,这有助于实现更快的索引和搜索。
HashMap的创建与泛型
上一节我们介绍了HashMap的基本概念,本节中我们来看看如何创建和使用泛型HashMap。
HashMap可以使用泛型来指定键和值的类型。其创建语法如下:
HashMap<K, V> map = new HashMap<>();
其中,K代表键的类型,V代表值的类型。HashMap在创建时具有初始容量,随着插入更多键值对,其容量会自动增加。
常用方法
以下是HashMap中一些常用的方法,我们将在示例中具体演示它们的用法。

clear(): 移除映射中的所有映射关系。isEmpty(): 检查映射中是否包含键值对。clone(): 创建HashMap的一个浅拷贝。entrySet(): 返回映射中包含的映射关系的Set视图(即键值对集合)。keySet(): 返回映射中包含的键的Set视图。put(K key, V value): 将指定的值与此映射中的指定键关联。get(Object key): 返回指定键所映射的值。
示例演示

现在,让我们通过一个具体的例子来看看这些方法是如何工作的。
首先,我们创建一个HashMap,其键为Integer类型,值为String类型,并插入几个键值对。
HashMap<Integer, String> map = new HashMap<>();
map.put(100, "King");
map.put(101, "Kocher");
map.put(102, "Gotham");
遍历HashMap
如果你想以任意顺序遍历所有条目,可以使用for-each循环配合entrySet()方法。
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
根据键获取值
如果你想根据特定的键获取对应的值,可以使用get()方法。
String value = map.get(101);
System.out.println(value); // 输出: Kocher
获取所有键或值
你可以分别获取映射中所有的键或所有的值。
System.out.println(map.keySet()); // 输出所有键,如 [100, 101, 102]
System.out.println(map.values()); // 输出所有值,如 [King, Kocher, Gotham]
移除元素
要移除映射中的某个元素,可以使用remove()方法。
map.remove(101); // 移除键为101的映射
清空映射
要清空整个映射,可以使用clear()方法。
map.clear(); // 清空所有映射
执行clear()后,再次打印映射将为空。


总结


本节课中我们一起学习了Java HashMap的核心知识。我们了解了HashMap通过键值对存储数据,并利用哈希技术实现高效访问。我们掌握了如何使用泛型创建HashMap,以及如何使用put、get、remove、clear、keySet、values和entrySet等核心方法进行数据的增删改查和遍历操作。HashMap是Java编程中处理键值对数据的强大工具,在后续课程中我们还将探讨Map接口的其他实现。
022:LinkedHashMap详解 📚

在本节课中,我们将学习Java集合框架中的LinkedHashMap。我们将了解它是什么、它与普通HashMap的区别、如何创建和使用它,并通过代码示例演示其核心方法。

概述
LinkedHashMap是Java集合框架中的一个类,它实现了Map接口。它结合了哈希表的快速访问和链表的顺序维护能力。简单来说,它就像一个能记住你添加元素顺序的HashMap。
上一节我们介绍了HashMap,本节中我们来看看它的一个有序版本——LinkedHashMap。
LinkedHashMap 是什么? 🤔
LinkedHashMap类提供了哈希表和链表的双重实现。HashMap的优势在于快速的插入、搜索和删除操作,但它从不维护元素的插入顺序。而LinkedHashMap则提供了按插入顺序访问元素的能力。
Java的LinkedHashMap接口扩展了HashMap类,在哈希表中存储条目,并在内部维护一个所有条目的双向链表来排序这些条目。
LinkedHashMap包含基于键的唯一元素,并且可以有一个null键和多个null值。它维护插入顺序,初始默认容量为16,负载因子为0.75。

如何创建 LinkedHashMap
以下是创建LinkedHashMap的语法,其中Key和Value是关联的。你也可以根据需要修改容量和负载因子。
LinkedHashMap<Key, Value> variableName = new LinkedHashMap<>();
Key 是用于关联映射中每个元素的唯一标识符。
Value 是与该键关联的值。
大多数方法与HashMap通用,例如get、clear、getOrDefault、put。但它也有一些更具体的方法,如接受BiConsumer接口的forEach和containsValue。

代码示例与实践
以下是如何创建和使用LinkedHashMap的示例。
我将创建一个键为String、值为Integer的LinkedHashMap。这里我添加几个偶数。
LinkedHashMap<String, Integer> evenNumbers = new LinkedHashMap<>();
evenNumbers.put("Two", 2);
evenNumbers.put("Four", 4);
// ... 以此类推
System.out.println(evenNumbers);
我们也可以在初始化时添加键值对:
LinkedHashMap<String, Integer> numbers = new LinkedHashMap<>(evenNumbers);
现在,如果你想添加更多数字,可以使用put方法:
numbers.put("Six", 6);
numbers.put("Eight", 8);
特定方法演示
除了put,LinkedHashMap还提供了putIfAbsent方法。这个方法只在键不存在时才添加元素。
以下是使用示例:
numbers.putIfAbsent("Six", 6); // 不会添加,因为"Six"已存在
numbers.putIfAbsent("Eight", 8); // 会添加,因为"Eight"不存在
这样,我们可以确保集合中不会出现重复的键。
此外,还有其他常用方法如clear、containsKey等,这里不再赘述。
总结
本节课中,我们一起学习了LinkedHashMap。我们了解到它是HashMap的一个有序版本,通过内部的双向链表维护了元素的插入顺序。我们学习了如何创建LinkedHashMap,如何使用put和putIfAbsent等方法添加元素,并通过代码示例加深了理解。

掌握LinkedHashMap对于需要保持元素顺序的场景非常有用,希望本教程能帮助你更好地理解和使用它。
Java全栈开发:23:Java WeakHashMap详解

在本节课中,我们将学习Java中的WeakHashMap,通过示例了解其操作,并阐明它与普通HashMap之间的区别。

概述
WeakHashMap是基于哈希表实现的Map接口,其特点是拥有弱键。当某个键不再被普通使用时,其在WeakHashMap中的条目会自动被移除。它支持null值和null键,性能特征与HashMap类似,并具有相同的初始容量和负载因子参数。默认初始容量为16,默认负载因子为0.75。
上一节我们介绍了Map接口的基本概念,本节中我们来看看一个特殊的实现——WeakHashMap。
创建WeakHashMap
以下是创建WeakHashMap的示例代码:

WeakHashMap<String, Integer> numbers = new WeakHashMap<>();
numbers.put("One", 1);
numbers.put("Two", 2);
numbers.put("Three", 3);
在以上代码中,String类型的键是唯一标识符,Integer类型的值通过键关联。我们也可以指定初始容量和负载因子:
WeakHashMap<String, Integer> map = new WeakHashMap<>(8, 0.6f);
此映射的容量为8,意味着它可以存储8个条目,负载因子为0.6。
WeakHashMap的特性演示
现在,我们通过一个具体示例来演示WeakHashMap的核心特性,特别是其弱键和垃圾回收行为。
以下是操作步骤:
- 插入元素并打印:首先,我们插入几个键值对并打印映射内容。
- 使用新对象作为键:创建一个新的
String对象作为键插入,并打印映射。 - 将键引用置为null并触发垃圾回收:将该键的引用置为
null,然后显式调用垃圾回收,再次打印映射以观察变化。
public class WeakHashMapDemo {
public static void main(String[] args) {
// 1. 创建并初始化WeakHashMap
WeakHashMap<String, Integer> numbers = new WeakHashMap<>();
numbers.put("One", 1);
numbers.put("Two", 2);
numbers.put("Three", 3);
System.out.println("初始WeakHashMap: " + numbers);
// 2. 使用新创建的String对象作为键
String keyFour = new String("Four");
numbers.put(keyFour, 4);
System.out.println("插入‘Four’后: " + numbers);
// 3. 将键引用置为null并触发垃圾回收
keyFour = null;
System.gc(); // 建议JVM进行垃圾回收
// 等待一下,让GC有机会运行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("GC后,键‘Four’被移除: " + numbers);
}
}
代码解析与输出:
- 初始打印会显示包含
"One","Two","Three"的映射。 - 插入
keyFour(值为"Four"的新对象)后,映射会包含四个条目。 - 当我们将
keyFour引用置为null并调用System.gc()后,由于"Four"这个键除了WeakHashMap内部的弱引用外已无其他强引用,它便成为垃圾回收的候选对象。垃圾回收后,对应的条目("Four" -> 4)会从WeakHashMap中自动移除。因此,最后的输出将不再包含键"Four"。
这个例子清晰地展示了弱键如何工作:一旦键对象失去所有强引用,它就可以被垃圾回收器回收,同时WeakHashMap会自动清理对应的映射条目。
WeakHashMap与HashMap的区别
理解了基本操作后,我们来总结一下WeakHashMap与普通HashMap的核心区别。
以下是主要差异点:
- 键的引用强度:
- HashMap:使用强引用持有键。只要HashMap实例存在,其内的键即使在其他地方已无引用,也不会被垃圾回收。
- WeakHashMap:使用弱引用持有键。如果某个键对象在WeakHashMap之外没有其他强引用指向它,那么当垃圾回收器运行时,该键对象可以被回收,同时WeakHashMap中的对应条目会被自动移除。
- 内存管理:WeakHashMap的这种特性使其适用于需要缓存数据,但又不想阻止键对象被垃圾回收的场景(例如缓存大型对象的映射)。而HashMap可能造成内存泄漏,如果不再使用的键被长期保留在映射中。
- 允许null键:两者都允许
null键和null值。
常用方法
WeakHashMap继承了AbstractMap并实现了Map接口,因此它包含了Map的标准方法。以下是一些关键方法:
put(K key, V value): 将指定值与此映射中的指定键关联。get(Object key): 返回指定键所映射的值。remove(Object key): 如果存在,则从此映射中移除键的映射关系。containsKey(Object key): 如果此映射包含指定键的映射关系,则返回true。keySet(): 返回此映射中包含的键的Set视图。values(): 返回此映射中包含的值的Collection视图。entrySet(): 返回此映射中包含的映射关系的Set视图。
这些方法的用法与HashMap中一致。
总结

本节课中我们一起学习了Java WeakHashMap。我们了解了它是一个基于弱键实现的Map,当键不再被外部强引用时,其条目会被自动清理。我们通过代码示例演示了这一核心特性,并对比了WeakHashMap与普通HashMap在键引用强度和适用场景上的主要区别。掌握WeakHashMap有助于在特定场景下(如实现缓存)进行更优雅的内存管理。
Java全栈开发:24:Java EnumMap详解 🗺️


在本节课中,我们将要学习Java集合框架中的EnumMap类。我们将了解它的定义、特性、创建方法以及基本操作,并通过示例代码帮助初学者理解其用法。
概述
EnumMap是Java集合框架中一个特殊的Map实现,它专门设计用于使用枚举(Enum)类型作为键。与HashMap相比,它具有更高的性能,并且能保证键的自然顺序。
EnumMap的定义与特性
上一节我们介绍了EnumMap的基本概念,本节中我们来看看它的具体定义和核心特性。
EnumMap类位于java.util包中,它继承自AbstractMap类并实现了Map接口。其核心特性如下:
- 键的类型固定:所有键必须来自同一个枚举类型。
- 不允许空键:尝试插入
null键会抛出NullPointerException。 - 高性能:由于键在枚举中预先定义,内部使用数组实现,因此访问速度非常快。
- 保持顺序:键的顺序与它们在枚举中定义的顺序一致。
其类继承关系可以表示为:
EnumMap → AbstractMap → 实现 Map 接口。
创建EnumMap
要使用EnumMap,首先需要导入相应的包。以下是创建EnumMap的步骤。
首先,定义一个枚举类型作为键:
enum Size {
SMALL, MEDIUM, LARGE, EXTRA_LARGE
}
然后,在main方法中创建EnumMap实例:
import java.util.EnumMap;
public class EnumMapExample {
public static void main(String[] args) {
EnumMap<Size, Integer> sizes = new EnumMap<>(Size.class);
}
}
代码new EnumMap<>(Size.class)创建了一个键为Size枚举类型、值为Integer类型的EnumMap。
EnumMap的基本操作
创建EnumMap后,我们可以向其中添加元素并进行各种操作。以下是常用的方法示例。
在创建好的sizes映射中添加键值对:
sizes.put(Size.SMALL, 20);
sizes.put(Size.MEDIUM, 30);
sizes.put(Size.LARGE, 40);
sizes.put(Size.EXTRA_LARGE, 50);
现在,我们可以使用不同的方法来查看映射中的内容:
- 获取所有键:使用
keySet()方法。System.out.println(sizes.keySet()); // 输出: [SMALL, MEDIUM, LARGE, EXTRA_LARGE] - 获取所有值:使用
values()方法。System.out.println(sizes.values()); // 输出: [20, 30, 40, 50] - 获取所有条目:使用
entrySet()方法,或直接打印整个映射。System.out.println(sizes.entrySet()); // 或直接 System.out.println(sizes); // 输出: {SMALL=20, MEDIUM=30, LARGE=40, EXTRA_LARGE=50} - 获取特定键的值:使用
get()方法。System.out.println(sizes.get(Size.MEDIUM)); // 输出: 30
此外,像replace()、remove()、clear()这些在集合中通用的方法,在EnumMap中同样适用,大家可以自行尝试。
总结


本节课中我们一起学习了EnumMap。我们了解到EnumMap是一个专为枚举键设计的高性能Map实现,它要求所有键来自同一枚举类型且不允许空键。我们学习了如何创建EnumMap,以及如何通过put、get、keySet、values等方法对其进行基本操作。由于其内部基于数组实现且键的顺序固定,EnumMap在涉及枚举键的场景下是比HashMap更高效的选择。
025:SortedMap接口详解 📚

在本节课中,我们将要学习Java中的SortedMap接口。SortedMap是Map接口的一个子接口,它能够根据键的自然顺序或指定的比较器对映射进行排序。我们将了解其核心概念、特性、常用方法,并通过一个简单的示例来掌握其基本用法。

概述
SortedMap接口是Java集合框架的一部分,它扩展了Map接口,并保证其条目按照键的升序排列。它本身是一个接口,不能直接实例化,通常通过实现类TreeMap来使用其功能。SortedMap提供了高效操作映射子集的能力。
SortedMap的核心特性
SortedMap的主要特性是它能够根据键的自然顺序或通过特定的比较器来对键进行排序。这意味着存入SortedMap的条目会自动按照键的顺序进行组织。
核心概念公式/代码表示:
- 接口继承关系:
SortedMap<K, V>extendsMap<K, V> - 常用实现类:
TreeMap<K, V> - 排序依据:键的自然顺序 或 指定的Comparator
上一节我们介绍了SortedMap的基本概念,本节中我们来看看它的具体实现和适用场景。
当您需要一个满足以下条件的映射时,可以考虑使用TreeMap(SortedMap的实现类):
- 不允许使用null键或null值(取决于具体实现和比较器)。
- 需要键按照自然顺序或特定比较器排序。
下图展示了SortedMap在集合框架中的位置:
Map 接口
↑
SortedMap 接口
↑
TreeMap 类 / ConcurrentSkipListMap 类
SortedMap的优势
使用SortedMap主要带来以下优势:
- 元素有序排列:数据始终按照键的顺序存储。
- 易于搜索:有序结构使得范围查询和特定顺序的搜索性能更佳。
- 可预测的迭代顺序:遍历映射时,条目会按照键的顺序依次出现,非常高效。
SortedMap的常用方法
除了继承自Map接口的所有方法(如put, get, remove)外,SortedMap还定义了一些自身特有的方法,用于处理有序的键。
以下是SortedMap接口中新增的一些重要方法:
Comparator<? super K> comparator(): 返回用于对此映射中的键进行排序的比较器;如果此映射使用键的自然顺序,则返回null。K firstKey(): 返回此映射中当前的第一个(最低)键。K lastKey(): 返回此映射中当前的最后一个(最高)键。SortedMap<K, V> headMap(K toKey): 返回此映射的部分视图,其键严格小于toKey。SortedMap<K, V> tailMap(K fromKey): 返回此映射的部分视图,其键大于等于fromKey。SortedMap<K, V> subMap(K fromKey, K toKey): 返回此映射的部分视图,其键的范围从fromKey(包括)到toKey(不包括)。
实践示例
现在,让我们通过一个简单的代码示例来演示SortedMap(通过TreeMap)的基本使用。
// 1. 创建SortedMap引用,并实例化TreeMap
SortedMap<String, Integer> numbers = new TreeMap<>();
// 2. 向映射中添加键值对
numbers.put("Two", 2);
numbers.put("One", 1);
numbers.put("Three", 3);
// 3. 打印整个有序映射。输出将按键(字符串)的字母顺序排列。
System.out.println("Sorted Map: " + numbers); // 输出: {One=1, Three=3, Two=2}
// 4. 获取第一个和最后一个键
String firstKey = numbers.firstKey(); // "One"
String lastKey = numbers.lastKey(); // "Two"
System.out.println("First Key: " + firstKey);
System.out.println("Last Key: " + lastKey);
// 5. 移除一个键值对
numbers.remove("One");
System.out.println("After removal: " + numbers); // 输出: {Three=3, Two=2}
此外,您还可以使用headMap, tailMap, subMap等方法来获取映射的子集视图,并进行迭代等操作。SortedMap继承了Map接口的所有方法,并在此基础上增加了针对有序键的操作方法,您可以据此进行更多练习。
总结


本节课中我们一起学习了Java的SortedMap接口。我们了解到SortedMap是一个保证条目按键排序的Map子接口,通常通过TreeMap类来使用。我们探讨了它的优势,如有序存储和高效的范围查询,并重点介绍了其特有的方法,如firstKey(), lastKey(), headMap()等。最后,通过一个简单的代码示例,我们实践了如何创建、填充和操作一个SortedMap。掌握SortedMap对于需要处理有序键值对数据的场景非常有帮助。
Java全栈开发:26:NavigableMap接口详解 🧭


在本节课中,我们将学习Java中的NavigableMap接口,并通过示例了解其方法。
NavigableMap接口是Java集合框架的一部分,它提供了在映射条目之间导航的功能,被视为SortedMap的一种类型。升序操作的性能和行为通常比降序操作更快、更高效。此外,它还提供了在现有映射中创建子映射的方法。
NavigableMap拥有多种实现特性,如headMap、tailMap和subMap,这些方法使我们能够更专注于导航操作。
以下是NavigableMap的语法:
NavigableMap<K, V> numbers = new TreeMap<>();
其中,K是用于关联每个元素的唯一标识符(键),V是与键关联的值元素。

在继承层次上,Map接口扩展了SortedMap接口,而SortedMap接口又扩展了NavigableMap接口。NavigableMap主要由TreeMap类实现。简而言之,如果你想使用NavigableMap,通常需要使用TreeMap类来进行操作。
以下是NavigableMap特有的一些方法:
ceilingEntrydescendingKeySetdescendingMapfirstEntryfloorEntrylastEntrypollFirstEntrypollLastEntry
上一节我们介绍了NavigableMap的基本概念和方法,本节中我们来看看如何通过代码实践来实现它。
以下是一个简单的实现示例:
import java.util.NavigableMap;
import java.util.TreeMap;
public class NavigableMapExample {
public static void main(String[] args) {
// 创建一个NavigableMap,键为String类型,值为Integer类型
NavigableMap<String, Integer> numbers = new TreeMap<>();
// 向映射中添加元素
numbers.put("Two", 2);
numbers.put("One", 1);
numbers.put("Three", 3);
// 打印整个NavigableMap
System.out.println("NavigableMap: " + numbers);
// 获取第一个条目(不删除)
System.out.println("First Entry: " + numbers.firstEntry());
// 获取最后一个条目(不删除)
System.out.println("Last Entry: " + numbers.lastEntry());
// 获取并移除第一个条目
System.out.println("Polled First Entry: " + numbers.pollFirstEntry());
// 获取并移除最后一个条目
System.out.println("Polled Last Entry: " + numbers.pollLastEntry());
// 打印移除元素后的映射
System.out.println("Updated NavigableMap: " + numbers);
}
}
运行上述代码,输出结果可能如下:
NavigableMap: {One=1, Three=3, Two=2}
First Entry: One=1
Last Entry: Two=2
Polled First Entry: One=1
Polled Last Entry: Two=2
Updated NavigableMap: {Three=3}
可以看到,firstEntry返回映射的第一个条目(One=1),lastEntry返回最后一个条目(Two=2)。而pollFirstEntry方法会返回并移除第一个条目,pollLastEntry方法会返回并移除最后一个条目。这就是这些方法在实现中的迭代方式。

本节课中我们一起学习了Java的NavigableMap接口。我们了解了它是SortedMap的扩展,提供了强大的导航功能,如获取首尾条目、创建降序视图以及获取并移除条目。通过TreeMap的示例,我们实践了firstEntry、lastEntry、pollFirstEntry和pollLastEntry等核心方法的使用。掌握NavigableMap有助于更高效地处理需要排序和导航的键值对集合。
027:Java TreeMap详解 🌳
在本节课中,我们将要学习Java集合框架中的TreeMap类。我们将探讨它的定义、特性、继承层次结构以及基本操作方法。通过简单的示例,你将学会如何创建和使用TreeMap来存储和管理有序的键值对数据。


概述
TreeMap是Java集合框架中提供的一种基于红黑树(Red-Black tree)实现的Map接口。它能够高效地存储键值对,并按照键的自然顺序或自定义比较器进行排序。与HashMap不同,TreeMap保证了元素的有序性。
上一节我们介绍了不同的Map实现,本节中我们来看看TreeMap特有的操作和属性。
TreeMap的继承层次
TreeMap类实现了NavigableMap接口,而NavigableMap又扩展了SortedMap接口,最终SortedMap扩展了基础的Map接口。其类声明如下:
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, Serializable
TreeMap的核心特性
以下是TreeMap的一些关键特性:
- 有序存储:
TreeMap中的所有条目(键值对)都根据键的顺序进行排序。默认按键的自然升序排列,也可以通过提供的Comparator进行自定义排序。 - 不允许
null键:TreeMap不允许使用null作为键。尝试插入null键会抛出NullPointerException。 - 允许
null值:TreeMap允许多个null值与不同的键关联。 - 非线程安全:与大多数集合类一样,
TreeMap不是线程安全的。如果在多线程环境下使用,需要进行外部同步。 - 视图方法返回快照:由
TreeMap的方法(如keySet()、entrySet())返回的集合视图,代表了方法调用时映射关系的一个快照。它们不支持结构修改操作(例如,通过迭代器直接移除元素)。
基本操作示例
让我们通过一个简单的例子来了解如何创建和使用TreeMap。
首先,我们创建一个键为String、值为Integer的TreeMap,并插入一些数据。
import java.util.TreeMap;
public class TreeMapExample {
public static void main(String[] args) {
// 创建一个TreeMap
TreeMap<String, Integer> numbers = new TreeMap<>();
// 使用put方法添加键值对
numbers.put("One", 1);
numbers.put("Two", 2);
numbers.put("Three", 3);
// 使用putIfAbsent方法:仅当键不存在时才插入
numbers.putIfAbsent("Three", 33); // 键"Three"已存在,此操作被忽略
numbers.putIfAbsent("Four", 4); // 键"Four"不存在,成功插入
// 打印整个TreeMap
System.out.println("完整的TreeMap: " + numbers);
}
}
运行上述代码,输出将显示有序的键值对:
完整的TreeMap: {Four=4, One=1, Three=3, Two=2}
可以看到,键是按照字母顺序("Four", "One", "Three", "Two")升序排列的。
遍历TreeMap
你可以通过多种方式遍历TreeMap中的元素。以下是几种常见的方法:
- 遍历所有键:使用
keySet()方法获取所有键的集合。 - 遍历所有值:使用
values()方法获取所有值的集合。 - 遍历所有条目(键值对):使用
entrySet()方法获取所有条目的集合,这是最常用的方式。
以下是遍历的示例代码:
// 遍历所有键
System.out.println("所有键:");
for (String key : numbers.keySet()) {
System.out.println(key);
}
// 遍历所有值
System.out.println("\n所有值:");
for (Integer value : numbers.values()) {
System.out.println(value);
}
// 遍历所有条目(键值对)
System.out.println("\n所有条目(键值对):");
for (Map.Entry<String, Integer> entry : numbers.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
性能与内存管理提示
TreeMap基于红黑树实现,因此其put、get、remove等操作的时间复杂度为O(log n),这比HashMap的O(1)要慢,但换来了有序性。
在长时间运行且频繁修改TreeMap的程序中,为了优化内存,可以在批量操作后建议垃圾回收器运行。但这只是一个建议,并不保证立即执行。
// 在可能进行大量增删操作后
System.gc(); // 建议JVM进行垃圾回收


总结

本节课中我们一起学习了Java中的TreeMap。我们了解到TreeMap是一个基于红黑树实现的有序映射,它不允许null键但允许多个null值。我们探讨了它的继承层次、核心特性,并通过代码示例演示了如何创建TreeMap、插入数据以及遍历其内容。记住,在选择使用HashMap还是TreeMap时,关键在于你是否需要元素保持有序。
028:Set接口详解 🎯

在本节课中,我们将学习Java集合框架中的Set接口及其实现。Set接口提供了数学集合的特性,并且不允许包含重复元素。
概述

Set接口是Java集合框架的一部分,它扩展了Collection接口。与List接口不同,Set不能包含重复的元素。由于Set是一个接口,我们不能直接创建它的对象。为了使用Set的功能,我们需要借助实现了Set接口的类。
Set接口的层次结构
上一节我们介绍了Set的基本概念,本节中我们来看看它的具体实现类。
Set接口由Collection接口扩展而来。它本身也被其他子接口扩展,例如SortedSet和NavigableSet。
为了使用Set接口的功能,我们可以使用以下几个类:
HashSetLinkedHashSetTreeSetEnumSet
这些类都定义在Java集合框架中,并提供了Set接口的具体实现。
Set接口的常用方法
Set接口继承了Collection接口的所有方法。以下是其中一些核心方法:
add(E e): 向集合中添加指定元素。如果元素已存在,则添加失败。addAll(Collection<? extends E> c): 将指定集合中的所有元素添加到此集合中(求并集)。remove(Object o): 移除集合中指定的单个元素。removeAll(Collection<?> c): 移除此集合中那些也包含在指定集合中的所有元素(求差集)。retainAll(Collection<?> c): 仅保留此集合中那些也包含在指定集合中的元素(求交集)。contains(Object o): 判断集合是否包含指定元素。containsAll(Collection<?> c): 判断此集合是否包含指定集合中的所有元素。iterator(): 返回在此集合元素上进行迭代的迭代器。toArray(): 返回一个包含此集合所有元素的数组。
集合运算
类似于数学中的集合,Set也支持基本的集合运算,包括并集、交集和子集判断。
- 并集 (Union): 将两个集合的所有元素合并,重复元素只出现一次。可以通过
addAll()方法实现。 - 交集 (Intersection): 获取两个集合中共有的元素。可以通过
retainAll()方法实现。 - 子集 (Subset): 判断一个集合的所有元素是否都包含在另一个集合中。可以通过
containsAll()方法实现。
实践:创建与遍历Set
让我们通过一个简单的例子来实践如何创建Set和遍历其中的元素。
首先,我们创建一个HashSet并添加一些整数元素。
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class SetExample {
public static void main(String[] args) {
// 创建一个HashSet
Set<Integer> set1 = new HashSet<>();
// 向集合中添加元素
set1.add(100);
set1.add(200);
set1.add(300);
set1.add(400);
// 打印整个集合
System.out.println("Set elements: " + set1);
}
}
运行上述代码,输出结果将显示集合中的四个元素。注意,HashSet不保证元素的顺序。
接下来,我们使用迭代器(Iterator)来遍历集合中的每个元素。
// 获取迭代器
Iterator<Integer> iterator = set1.iterator();
// 使用while循环遍历
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
// 输出可能为:400 100 200 300 (顺序可能不同)
你也可以使用增强型for循环(for-each)来更简洁地遍历集合:
for (Integer number : set1) {
System.out.print(number + " ");
}
总结


本节课中我们一起学习了Java中的Set接口。我们了解了Set不包含重复元素的特性,认识了它的主要实现类HashSet、LinkedHashSet、TreeSet和EnumSet。我们介绍了Set接口的核心方法,并演示了如何创建Set、添加元素以及使用迭代器和for-each循环进行遍历。关于Set更深入的操作和不同实现类之间的区别,我们将在后续课程中继续探讨。
029:Java HashSet 教程 🎼

在本节课中,我们将学习 Java 中的 HashSet 类。我们将通过一个示例来了解 HashSet 的不同方法和操作。

概述
Java 集合框架中的 HashSet 类提供了哈希表数据结构的功能。它继承自 AbstractSet 类并实现了 Set 接口。HashSet 使用一种称为“哈希”的机制来存储元素,并且只包含唯一的元素。
HashSet 的特性

以下是 HashSet 的一些关键特性:
- 唯一元素:
HashSet只存储唯一的元素,重复项会被自动忽略。 - 初始容量与负载因子:默认初始容量为 16,默认负载因子为 0.75。
- 允许空值:
HashSet允许存储null值。 - 高效查找:由于其哈希表实现,
HashSet在执行搜索操作时性能优异。

HashSet 的声明
HashSet 的声明结构如下,它扩展了 AbstractSet 并实现了 Set 接口:
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable

常用方法
上一节我们介绍了 HashSet 的基本概念,本节中我们来看看它提供的一些常用方法。这些方法大多继承自 Collection 和 Set 接口。
以下是 HashSet 中一些核心的操作方法:
add(E e):向集合中添加元素。clear():移除集合中的所有元素。contains(Object o):判断集合是否包含指定元素。remove(Object o):从集合中移除指定元素。iterator():返回一个迭代器,用于遍历集合中的元素。isEmpty():判断集合是否为空。clone():返回此HashSet实例的浅拷贝。
实践操作
现在,让我们通过代码示例来实践这些方法。
首先,我们创建一个 HashSet 并添加一些元素。
HashSet<Integer> numbers = new HashSet<>();
numbers.add(100);
numbers.add(200);
numbers.add(300);
numbers.add(400);
System.out.println(numbers);
接下来,我们学习如何使用迭代器来访问集合中的元素。
Iterator<Integer> iterate = numbers.iterator();
while(iterate.hasNext()) {
System.out.println(iterate.next());
}
然后,我们尝试移除一个元素。
System.out.println(numbers.remove(300)); // 返回 true,表示移除成功
集合运算
HashSet 也支持集合之间的运算,如并集、交集和差集。我们创建两个 HashSet 来进行演示。
HashSet<Integer> set1 = new HashSet<>();
set1.add(100);
set1.add(200);
set1.add(300);
HashSet<Integer> set2 = new HashSet<>();
set2.add(200);
set2.add(300);
set2.add(400);
set2.add(500);
首先,我们进行并集操作,使用 addAll 方法。
set1.addAll(set2); // set1 现在包含 set1 和 set2 的所有元素(去重后)
System.out.println(set1); // 输出: [400, 100, 500, 200, 300]
接着,我们进行交集操作,使用 retainAll 方法。
set1.retainAll(set2); // set1 现在只保留与 set2 共有的元素
System.out.println(set1); // 输出: [400, 200, 300] (假设set1在执行交集前已恢复原状)
最后,我们进行差集操作,使用 removeAll 方法。
set2.removeAll(set1); // 从 set2 中移除所有也存在于 set1 中的元素
System.out.println(set2); // 输出: [500]
总结


本节课中我们一起学习了 Java HashSet 类。我们了解了它的特性,如存储唯一元素和高效的搜索性能。我们实践了其核心方法,包括添加、移除、遍历元素。最后,我们通过示例演示了如何使用 HashSet 进行并集、交集和差集等集合运算。HashSet 是处理无需唯一元素集合的强大工具。
030:Java EnumSet类详解 🎯


在本节课中,我们将学习Java集合框架中的EnumSet类。我们将通过示例了解其定义、特性以及各种常用方法。EnumSet是一种专为枚举类型设计的高性能集合实现。
概述
EnumSet是Java集合框架中一个特殊的Set实现,它专门用于存储单个枚举类型的元素。它继承了AbstractSet类并实现了Set接口。与HashSet或TreeSet相比,EnumSet在性能上更具优势,因为它内部使用位向量实现,JVM预先知道所有可能的枚举值。
EnumSet的特性
上一节我们介绍了EnumSet的基本概念,本节中我们来看看它的核心特性。

- 单一枚举类型:EnumSet中的所有元素必须来自创建时指定的同一个枚举类型。
- 高性能:内部使用位向量(bit vector)实现,因此速度非常快。
- 无公共构造器:不能使用
new关键字创建EnumSet实例,必须使用其提供的静态工厂方法。 - 故障安全迭代器:使用迭代器遍历时,如果集合被修改,不会抛出
ConcurrentModificationException。 - 继承体系:其类声明为
EnumSet<E extends Enum<E>>,其中E表示具体的枚举类型。

创建EnumSet
了解了EnumSet的特性后,我们来看看如何创建它。EnumSet提供了多个静态方法来创建实例。
以下是创建EnumSet的几种常用方法:
EnumSet.allOf(Class<E> elementType):创建一个包含指定枚举类型所有值的EnumSet。EnumSet<Size> allSizes = EnumSet.allOf(Size.class);EnumSet.noneOf(Class<E> elementType):创建一个指定枚举类型的空EnumSet。EnumSet<Size> noSizes = EnumSet.noneOf(Size.class);EnumSet.range(E from, E to):创建一个包含从from到to范围内(包括两端)所有枚举值的EnumSet。EnumSet<Size> rangeSizes = EnumSet.range(Size.MEDIUM, Size.EXTRA_LARGE);EnumSet.of(E e1, E e2, ...):创建一个包含一个或多个指定枚举值的EnumSet。EnumSet<Size> specificSizes = EnumSet.of(Size.SMALL, Size.LARGE);
常用操作示例
现在我们已经知道如何创建EnumSet,接下来通过一个完整的代码示例来演示其常用操作。
// 1. 定义一个枚举类型
enum Size {
SMALL, MEDIUM, LARGE, EXTRA_LARGE
}
public class EnumSetDemo {
public static void main(String[] args) {
// 2. 创建包含所有枚举值的集合
EnumSet<Size> sizes1 = EnumSet.allOf(Size.class);
System.out.println("All sizes: " + sizes1); // 输出: [SMALL, MEDIUM, LARGE, EXTRA_LARGE]
// 3. 创建空集合
EnumSet<Size> sizes2 = EnumSet.noneOf(Size.class);
System.out.println("Empty set: " + sizes2); // 输出: []
// 4. 使用range方法
EnumSet<Size> sizes3 = EnumSet.range(Size.MEDIUM, Size.EXTRA_LARGE);
System.out.println("Range (MEDIUM to XL): " + sizes3); // 输出: [MEDIUM, LARGE, EXTRA_LARGE]
// 5. 使用of方法指定元素
EnumSet<Size> sizes4 = EnumSet.of(Size.SMALL, Size.LARGE);
System.out.println("Specific sizes: " + sizes4); // 输出: [SMALL, LARGE]
// 6. 添加元素
sizes2.add(Size.MEDIUM);
sizes2.addAll(sizes4); // 合并集合
System.out.println("After add operations: " + sizes2); // 输出: [SMALL, MEDIUM, LARGE]
// 7. 使用迭代器遍历
System.out.print("Iterating: ");
Iterator<Size> iterator = sizes1.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
// 输出: Iterating: SMALL MEDIUM LARGE EXTRA_LARGE
System.out.println();
// 8. 移除元素
boolean isRemoved = sizes1.remove(Size.MEDIUM);
System.out.println("Removed MEDIUM? " + isRemoved); // 输出: true
System.out.println("After removal: " + sizes1); // 输出: [SMALL, LARGE, EXTRA_LARGE]
// 9. 清空集合
sizes1.clear();
System.out.println("After clear: " + sizes1); // 输出: []
}
}
总结

本节课中我们一起学习了Java的EnumSet类。我们了解到EnumSet是一个专为枚举类型设计的高性能Set实现,它要求所有元素来自同一个枚举。我们重点掌握了如何使用allOf、noneOf、range和of等静态工厂方法创建EnumSet,并演示了添加、遍历、移除等常规集合操作。由于其内部基于位向量的实现,在处理枚举集合时,EnumSet通常是比HashSet更高效的选择。
031:Java LinkedHashSet 类详解

在本节课中,我们将通过示例学习 Java 中的 LinkedHashSet 类及其方法。

概述
LinkedHashSet 是 Java 集合框架中的一个类,它结合了哈希表和链表数据结构的特性。当需要维护元素的迭代顺序时,就会使用这个类。它是有序版本的 HashSet。
LinkedHashSet 的工作原理
LinkedHashSet 的元素存储在一个哈希表中,这一点与 HashSet 类似。但不同之处在于,LinkedHashSet 内部为所有元素维护了一个双向链表。这个链表定义了元素插入哈希表的顺序。
因此,当使用迭代器遍历 LinkedHashSet 时,元素将按照它们被插入的顺序返回。
类的声明
LinkedHashSet 的类声明如下,它扩展了 HashSet 并实现了 Set 接口:

public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, Serializable

常用方法
以下是 LinkedHashSet 中一些常用的方法:
equals(Object o)hashCode()removeAll(Collection<?> c)addAll(Collection<? extends E> c)containsAll(Collection<?> c)
容量与负载因子
LinkedHashSet 的默认初始容量是 8,默认负载因子是 0.75。它位于 java.util 包中。

如果你想修改初始容量和负载因子,可以在创建 LinkedHashSet 实例时,通过构造函数传入这些参数。
示例演示
现在,让我们通过一个示例来演示 LinkedHashSet 的用法。
首先,我们创建一个包含偶数的 ArrayList:
ArrayList<Integer> evenNumbers = new ArrayList<>();
evenNumbers.add(2);
evenNumbers.add(4);
evenNumbers.add(6);
System.out.println("ArrayList: " + evenNumbers);
接下来,我们创建一个 LinkedHashSet,并将上面列表中的所有偶数添加进去:
LinkedHashSet<Integer> numbers = new LinkedHashSet<>(evenNumbers);
System.out.println("LinkedHashSet: " + numbers);
如果你没有现成的列表,也可以先创建一个空的 LinkedHashSet,然后使用 addAll 方法或多次调用 add 方法来添加元素:
LinkedHashSet<Integer> numbers = new LinkedHashSet<>();
numbers.addAll(evenNumbers);
numbers.add(8);
System.out.println("New LinkedHashSet: " + numbers);
遍历与删除
要遍历 LinkedHashSet,可以使用迭代器:
Iterator<Integer> iterate = numbers.iterator();
while(iterate.hasNext()) {
System.out.print(iterate.next());
System.out.print(", ");
}
要删除特定元素或清空集合:
numbers.remove(2); // 删除元素 2
numbers.removeAll(numbers); // 删除所有元素
集合运算
LinkedHashSet 支持集合运算,如并集、交集和差集。
首先,创建两个 LinkedHashSet:
LinkedHashSet<Integer> set1 = new LinkedHashSet<>();
set1.add(2);
set1.add(4);
LinkedHashSet<Integer> set2 = new LinkedHashSet<>();
set2.add(2);
set2.add(4);
set2.add(6);
1. 并集 (Union)
使用 addAll 方法可以将一个集合的所有元素添加到另一个集合中,实现并集操作:
set1.addAll(set2);
System.out.println("Union: " + set1); // 输出: [2, 4, 6]
2. 交集 (Intersection)
使用 retainAll 方法可以保留两个集合中共有的元素,实现交集操作:
set1.retainAll(set2);
System.out.println("Intersection: " + set1); // 输出: [2, 4]
3. 差集 (Difference)
使用 removeAll 方法可以从一个集合中移除另一个集合中包含的所有元素,实现差集操作。注意,通常将较大的集合放在前面:
set2.removeAll(set1);
System.out.println("Difference: " + set2); // 输出: [6]
在上面的差集操作中,set2 中与 set1 相同的元素(2和4)被移除,只剩下6。
总结
本节课我们一起学习了 LinkedHashSet 类。我们了解到它是 HashSet 的一个有序版本,通过内部维护的双向链表来保证元素的插入顺序。我们学习了它的声明、常用方法、默认容量和负载因子,并通过详细的代码示例演示了如何创建、遍历、删除元素以及执行并集、交集和差集等集合运算。掌握 LinkedHashSet 对于在需要维护元素顺序的场景下使用集合非常有帮助。


请继续关注后续课程,以了解更多关于 Set 接口及其其他实现的内容。我们下节课再见。
032:SortedSet接口详解 🎯

在本节课中,我们将学习Java中的SortedSet接口及其方法。SortedSet是Set接口的一个扩展,它保证集合中的元素以排序后的顺序存储。我们将通过示例来理解其工作原理和常用方法。
概述

SortedSet接口位于java.util包中,它实现了数学上的集合概念。此接口继承了Set接口的所有方法,并增加了一个核心特性:所有元素都以排序后的方式存储。
SortedSet接口的继承关系如下:Set接口扩展为SortedSet,SortedSet再扩展为NavigableSet接口,最终由TreeSet类实现。因此,要使用SortedSet的功能,我们需要使用TreeSet类。TreeSet是基于自平衡二叉搜索树(红黑树)的实现,这使得该接口提供了在树结构中导航的能力。
以下是SortedSet中一些常用的方法:
comparator(): 返回用于排序的比较器。first(): 返回集合中的第一个(最低)元素。last(): 返回集合中的最后一个(最高)元素。headSet(toElement): 返回此集合中严格小于toElement的部分视图。subSet(fromElement, toElement): 返回此集合中从fromElement(包含)到toElement(不包含)的部分视图。tailSet(fromElement): 返回此集合中大于等于fromElement的部分视图。
上一节我们介绍了Set接口的基础,本节中我们来看看它的有序版本SortedSet。
实践示例
现在,让我们通过代码示例来实际理解SortedSet及其方法在Java中是如何工作的。
首先,我们创建一个SortedSet实例并添加一些元素。
SortedSet<String> sortedSet = new TreeSet<>();
sortedSet.add("India");
sortedSet.add("Australia");
sortedSet.add("South Africa");
sortedSet.add("Japan");
// 尝试添加重复元素
sortedSet.add("India");

当我们打印这个集合时,会观察到两个现象:
- 重复的“India”只出现一次,体现了Set的去重特性。
- 元素按字母升序排列输出:Australia, India, Japan, South Africa。
接下来,我们使用迭代器遍历集合,可以看到顺序依然保持。
Iterator<String> iterator = sortedSet.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
常用方法演示
以下是SortedSet其他一些实用方法的演示。
检查元素是否存在
使用contains方法可以检查集合中是否包含特定元素。
boolean containsIndia = sortedSet.contains("India"); // 返回 true
获取首尾元素
使用first和last方法可以快速获取排序后集合的第一个和最后一个元素。
String firstElement = sortedSet.first(); // 返回 "Australia"
String lastElement = sortedSet.last(); // 返回 "South Africa"
移除元素
与普通Set一样,可以使用remove方法移除单个元素。
boolean isRemoved = sortedSet.remove("Japan"); // 返回 true,元素被移除
如果要清空整个集合,可以使用clear方法或removeAll方法。
sortedSet.clear(); // 清空所有元素
// 或者
sortedSet.removeAll(sortedSet); // 同样清空所有元素
总结
本节课中我们一起学习了Java的SortedSet接口。我们了解到SortedSet是Set的一个有序版本,它自动对元素进行排序并去除重复项。其核心实现类是TreeSet,它基于红黑树数据结构。我们通过代码示例实践了如何创建SortedSet、添加元素、遍历集合,并演示了first、last、contains、remove等关键方法的使用。SortedSet为需要有序且唯一元素集合的场景提供了便利。


Java集合框架中还有其他的Set实现,敬请关注后续课程以了解更多内容。
Java全栈开发:33:NavigableSet接口详解 🧭

在本节课中,我们将学习Java集合框架中的NavigableSet接口,并通过一个示例来了解其方法。

NavigableSet接口提供了在集合元素间导航的功能。它类似于SortedSet,但除了排序机制外,还额外提供了导航方法。为了使用NavigableSet接口的功能,我们需要使用实现了该接口的TreeSet类。NavigableSet可以按升序或降序存在和遍历,具体取决于你的导航需求。
以下是NavigableSet接口在集合框架中的层次结构:SortedSet接口扩展了NavigableSet接口,而NavigableSet则由TreeSet类实现。
上一节我们介绍了接口的基本概念,本节中我们来看看如何通过具体示例来迭代和实现NavigableSet。

以下是一个使用NavigableSet的代码示例:
import java.util.NavigableSet;
import java.util.TreeSet;

public class NavigableSetExample {
public static void main(String[] args) {
// 创建一个NavigableSet
NavigableSet<Integer> set = new TreeSet<>();
set.add(10);
set.add(20);
set.add(30);
set.add(40);
set.add(50);
// 打印原始集合
System.out.println("Original Set: " + set);
// 获取逆序视图
NavigableSet<Integer> reverseSet = set.descendingSet();
System.out.println("Reversed Set: " + reverseSet);
}
}
执行此程序,输出结果为:原始集合是[10, 20, 30, 40, 50],其逆序视图是[50, 40, 30, 20, 10]。
接下来,我们看看NavigableSet提供的其他一些核心方法。
以下是tailSet、lower、pollFirst和pollLast方法的示例:
// 获取大于等于3的元素视图(此处exclusive参数为false,表示包含下限)
NavigableSet<Integer> threeOrMore = set.tailSet(3, false);
System.out.println("Elements >= 3: " + threeOrMore);
// 获取小于给定元素的最大元素
Integer lowerOfThree = set.lower(3);
System.out.println("Greatest element less than 3: " + lowerOfThree); // 输出null,因为集合中所有元素都大于3
// 检索并移除第一个(最低)元素
Integer first = set.pollFirst();
System.out.println("Polled First Element: " + first);
System.out.println("Set after pollFirst: " + set);
// 检索并移除最后一个(最高)元素
Integer last = set.pollLast();
System.out.println("Polled Last Element: " + last);
System.out.println("Set after pollLast: " + set);
执行这些操作后,集合中的元素10和50被依次移除。
此外,你还可以使用contains方法来检查集合是否包含特定元素。
boolean containsFirst = set.contains(set.first());
System.out.println("Set contains its first element: " + containsFirst);
如果集合不为空,此检查将返回true。
这就是NavigableSet的基本实现方式。在实际开发中,你可以将其用于更复杂的面向对象场景,例如存储Employee对象或其他实体,并进行相应的操作。



本节课中我们一起学习了Java的NavigableSet接口。我们了解了它作为SortedSet的扩展,所提供的导航功能,并通过TreeSet示例实践了descendingSet、tailSet、lower、pollFirst、pollLast和contains等核心方法的使用。掌握这些方法有助于你在需要有序且可双向导航的数据集合时进行高效操作。
034:TreeSet详解 🌳


在本节课中,我们将要学习Java集合框架中的一个重要成员——TreeSet。TreeSet是SortedSet接口的一个关键实现,它使用树形结构来存储元素,并自动维护元素的排序。
概述
TreeSet继承自AbstractSet类,并实现了NavigableSet接口。这意味着,当我们使用NavigableSet接口中的方法时,需要实例化TreeSet。TreeSet会维护元素的自然排序,无论是否提供了显式的比较器。这种排序必须与equals方法保持一致,这是Set接口正确实现的要求。
由于TreeSet基于树形结构实现,其访问和检索速度非常快。具体来说,TreeSet使用了一种自平衡的二叉搜索树(如红黑树)来实现。因此,搜索、删除和添加等操作的时间复杂度为O(log n)。这是因为在自平衡树中,树的高度永远不会超过log n,从而保证了操作的高效性。
TreeSet的特点


以下是TreeSet的一些核心特点:
- 有序性:TreeSet中的元素总是处于排序状态。
- 唯一性:与所有Set实现一样,TreeSet不允许存储重复元素。
- 高效性:基于红黑树实现,使得添加、删除和查找操作都非常高效。
- 导航方法:实现了NavigableSet接口,提供了如
first()、last()、higher()、lower()等导航方法。
核心操作与代码示例
上一节我们介绍了TreeSet的基本概念,本节中我们来看看如何在实际代码中使用它。
以下是一个简单的TreeSet使用示例:
import java.util.TreeSet;
import java.util.NavigableSet;
public class TreeSetDemo {
public static void main(String[] args) {
// 创建一个TreeSet
TreeSet<String> set = new TreeSet<>();
// 向集合中添加元素
set.add("E");
set.add("B");
set.add("C");
set.add("A");
set.add("C"); // 重复元素不会被添加
// 打印TreeSet,元素将按自然顺序(字母顺序)排序
System.out.println("TreeSet内容: " + set); // 输出: [A, B, C, E]
// 检查元素是否存在
System.out.println("集合是否包含'D'? " + set.contains("D")); // 输出: false
// 获取第一个和最后一个元素
System.out.println("第一个元素: " + set.first()); // 输出: A
System.out.println("最后一个元素: " + set.last()); // 输出: E
// 使用NavigableSet的导航方法
NavigableSet<String> navigableSet = set;
System.out.println("大于'B'的最小元素: " + navigableSet.higher("B")); // 输出: C
System.out.println("小于'B'的最大元素: " + navigableSet.lower("B")); // 输出: A
// 移除并返回第一个和最后一个元素
System.out.println("移除的第一个元素: " + navigableSet.pollFirst()); // 输出: A
System.out.println("移除的最后一个元素: " + navigableSet.pollLast()); // 输出: E
System.out.println("操作后的集合: " + navigableSet); // 输出: [B, C]
}
}
方法详解
以下是TreeSet中一些特定方法的说明,这些方法与其继承或实现的接口中的方法一同构成了TreeSet的功能集。
add(E e): 将指定元素添加到集合中(如果尚未存在)。remove(Object o): 从集合中移除指定元素。contains(Object o): 如果集合包含指定元素,则返回true。first(): 返回集合中当前第一个(最低)元素。last(): 返回集合中当前最后一个(最高)元素。higher(E e): 返回集合中严格大于给定元素的最小元素,如果没有这样的元素则返回null。lower(E e): 返回集合中严格小于给定元素的最大元素,如果没有这样的元素则返回null。pollFirst(): 检索并移除第一个(最低)元素,如果集合为空则返回null。pollLast(): 检索并移除最后一个(最高)元素,如果集合为空则返回null。
总结
本节课中我们一起学习了TreeSet。TreeSet是Java中一个基于树结构实现的有序集合,它高效地结合了元素的唯一性和自动排序功能。其核心实现是红黑树,这保证了基本操作(添加、删除、查找)的时间复杂度为O(log n)。通过实现NavigableSet接口,它还提供了丰富的导航方法。StringBuffer等类内部也利用了TreeSet的特性来去重和排序。希望通过本次讨论,你能理解在何种实际场景下应该选择使用TreeSet。


下次再见!
035:Spring框架入门指南
在本节课中,我们将学习Spring框架。Spring是一个流行的开源应用框架,用于构建企业级Java应用程序。我们将从了解Spring框架及其优势开始,逐步探索其架构的各个组成部分,并最终学习如何使用Maven构建工具来开发一个Spring应用。
什么是Spring框架?🤔
上一节我们介绍了本课程的目标,本节中我们来看看Spring框架的定义。Spring框架是一个为Java平台提供全面基础设施支持的开源应用框架。它简化了企业级应用的开发。

Spring框架的主要优势包括:
- 轻量级:核心容器非常小巧。
- 控制反转(IoC):通过依赖注入管理对象生命周期和依赖关系。
- 面向切面编程(AOP):支持将横切关注点(如日志、事务)模块化。
- 集成性:可以轻松与其他流行框架和技术集成。
Spring框架架构剖析 🏗️
了解了Spring的基本概念后,本节我们将深入其架构。Spring框架采用模块化设计,其核心架构包含以下几个关键层:
以下是Spring框架的主要组件:
- 核心容器(Core Container):提供IoC和依赖注入功能,是框架的基础。
- 数据访问/集成层(Data Access/Integration):包含JDBC、ORM、事务管理等模块,用于简化数据库操作。
- Web层(Web):包括Spring MVC、WebSocket等模块,用于构建Web应用程序。
- AOP模块(AOP):提供面向切面编程的实现。
- 测试模块(Test):支持使用JUnit或TestNG进行单元测试和集成测试。
搭建Spring开发环境 ⚙️
要开始使用Spring进行开发,首先需要配置相应的环境。这通常包括安装Java开发工具包(JDK)、集成开发环境(IDE)以及构建工具。
以下是设置Spring开发环境的基本步骤:
- 确保已安装合适版本的JDK。
- 安装一个IDE,如Eclipse或IntelliJ IDEA。
- 在IDE中创建一个新的动态Web项目。
- 配置项目以支持Spring框架。
使用Maven构建Spring应用 🛠️
环境准备就绪后,我们可以使用Maven来管理和构建Spring项目。Maven能帮助我们自动处理项目依赖。
以下是通过Maven创建Spring应用的关键步骤:
- 在IDE中创建一个Maven项目。
- 在项目的
pom.xml文件中添加Spring框架的依赖项。例如,添加Spring核心容器的依赖:<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.x.x</version> </dependency> - 编写Spring配置文件和Java代码。
- 使用Maven命令(如
mvn clean install)来构建项目。
总结 📚
本节课中,我们一起学习了Spring框架的基础知识。我们首先了解了Spring框架是什么以及它的主要优势。接着,我们剖析了其模块化的架构,包括核心容器、数据访问层、Web层等关键组件。然后,我们介绍了如何搭建Spring开发环境并创建动态Web项目。最后,我们讲解了如何使用Maven构建工具来开发一个基础的Spring应用程序。掌握这些内容后,你已经为开始构建自己的Spring应用做好了准备。
Java全栈开发:P36:什么是Spring框架


在本节课中,我们将学习Spring框架的基础知识。Spring是一个为Java应用程序开发提供全面基础设施支持的平台。我们将了解它的核心特性、优势以及它为何在Java开发中如此重要。
Spring框架本质上是一个Java平台,它为开发Java应用程序提供了全面的基础设施支持。Spring负责处理底层基础设施,使得应用程序开发者可以专注于业务逻辑。它是一个现成的框架,最初由Rod Johnson编写,并于2003年6月在Apache 2.0许可证下首次发布。从大小和透明度来看,它是一个非常轻量级的框架。
Spring框架有一些核心特性,可用于开发任何Java应用程序。虽然我们主要关注Spring MVC或Spring Boot应用,但它还有扩展功能,可用于在Java EE平台或企业级平台上构建更多类型的应用。
使用Spring框架有一些优势。它是一个框架,因此提供了一个方便的预定义模板供使用,有助于实现松耦合架构。它支持Spring MVC框架,而MVC本身就是一种松耦合的设计模式。它易于测试,是一个轻量级的IOC容器,借助事务管理技术(这些技术你在数据库课程中也学过)提供稳定的事务管理。同时,它有助于快速开发,因为它是一个模板化的框架,各种库和功能(如AOP、事务管理、核心上下文、Bean以及最重要的依赖注入)都是现成可用的。

选择Spring而非其他实现框架有许多原因。它简单、轻量,并能构建安全的Web应用,因为它也支持Spring Security。它支持MVC模式,易于与数据库通信。虽然你可以讨论任何数据库集成(如Hibernate或JDBC),但我们主要讨论与Hibernate这类ORM框架结合,用于构建大型可扩展应用。它有助于实现模块化设计模式,并且可以与其他框架集成。
依赖注入在其中扮演着非常重要的角色。我们创建Bean,在XML或基于Java注解的文件中配置它们,然后可以将它们注入到我们的MVC模式中。同时,由于集成了测试功能,测试也变得非常容易。


本节课中,我们一起学习了Spring框架的定义、核心特性、主要优势以及依赖注入等关键概念。它是一个强大且灵活的框架,能显著提升Java应用开发的效率和质量。请继续关注,以了解更多关于Spring框架及其核心组件与架构的知识。
037:Spring框架架构 🏗️


在本节课中,我们将学习Spring框架的架构。理解其模块化设计和各个核心组件,对于开发快速、可扩展的应用程序至关重要。
概述
Spring框架是一个模块化的框架。它并非作为一个包含所有模块的单一包提供,而是由多个独立的模块组成。这种设计提供了灵活性,允许开发者根据需求选择使用特定模块,从而使应用程序保持轻量级和专注。

Spring框架架构详解

接下来,我们详细看看Spring框架的高层架构图,它主要包含核心容器、数据访问、Web、测试等部分。
核心容器:Spring的心脏 ❤️
整个Spring框架都构建在核心容器之上。核心容器是Spring的心脏,包含基础框架类和工具。
- Spring Core:该模块包含Spring框架的基本类,包括依赖注入和IoC容器。无论构建何种类型的Spring应用,都会直接或间接地依赖此模块。
- Spring Bean:此模块管理Bean的生命周期。在Spring框架中,Bean是任何注册到Spring并由Spring管理的Java类。Bean工厂负责创建这些Bean类的实例。
- Spring Context:Spring Bean被定义在称为Spring上下文的容器中。此模块允许我们访问这个上下文,它也被称为应用Bean上下文。
- Spring Expression Language (SpEL):这是一种强大的表达式语言,用于在运行时解析表达式。它可以查询对象图,并可用于XML配置或Java注解中。
Web模块 🌐
如其名称所示,Spring Web组件用于实现Web应用程序,可以构建完整的Servlet应用。
- Web/Portlet:Servlet和Portlet提供了构建Web应用的许多功能。
- Spring MVC:这是Spring Web的一个组件,提供了一种构建基于模型-视图-控制器(MVC)模式应用的机制。
- WebSocket:为WebSocket提供支持。WebSocket是在服务和Web应用消费者之间建立的一种隧道,不同于需要客户端轮询服务器更新的传统HTTP连接。
数据访问与集成 💾
数据访问层涉及与数据库和消息系统的交互,主要包括以下组件:
- JDBC:在Java JDBC API之上提供了一层抽象,简化了从数据库访问数据时处理语句、查询和结果集的操作。
- ORM:支持对象关系映射(ORM)实现,例如Hibernate。
- JMS:代表Java消息服务,定义了发布者和订阅者通信的规范。
- OXM:提供对象-XML编组的抽象,定义了以XML形式传输和访问数据的方式。
- Transaction Management:提供统一的方式来管理数据对象以及数据库的事务。
其他组件 🔧
Spring框架还包含一些其他重要的组件:
- AOP:面向切面编程(AOP)的实现。切面是指对象需要执行的任何次要任务(如日志、事务)。
- Messaging:提供与消息传递系统和服务的简化、统一的交互方式。
- Instrumentation:提供类检测支持,用于监控应用程序性能,帮助测试和诊断应用问题。
总结


本节课中,我们一起学习了Spring框架的模块化架构。我们了解到,Spring由核心容器、Web模块、数据访问层等多个独立模块组成,这种设计使得应用开发更加灵活和高效。核心容器是框架的基础,管理着Bean的生命周期和依赖注入。Web模块支持构建各种Web应用,而数据访问模块则简化了与数据库和消息系统的交互。掌握这些架构知识是有效使用Spring框架进行开发的第一步。
038:Spring框架环境搭建指南 🛠️
在本节课中,我们将学习如何为Spring框架开发搭建必要的本地环境。这包括验证和安装所需的软件,以及了解如何获取Spring框架库。

环境准备概述
在开始使用Spring框架之前,必须在您的计算机上安装一些必备软件,并清楚如何使用它们。以下是需要完成的步骤。

1. 验证JDK安装
首先,需要确认您的机器上已安装Java开发工具包(JDK)。
您可以通过命令行来检查JDK是否已安装。打开命令提示符并输入以下命令:
java -version
如果已安装,命令将返回JDK的版本信息。例如,您可能会看到类似 JDK 19 的输出。JDK通常与JRE捆绑在一起。
2. 安装Apache Tomcat服务器
接下来,您需要安装Apache Tomcat服务器。这是一个广泛使用的Java应用服务器。
您可以访问Apache Tomcat的官方网站来下载所需的版本(如8、9或10)。根据您的操作系统(例如Windows)选择相应的安装程序(如32位或64位的Windows服务安装器)。
下载完成后,运行安装程序并按照提示完成安装。建议在安装过程中选择完整安装。安装完成后,您可以在程序文件目录(如 C:\Program Files\Apache Software Foundation\Tomcat 9.0)中找到它。
3. 配置集成开发环境(IDE)
我们推荐使用Eclipse IDE进行Java开发。Eclipse支持创建各种Java项目,包括核心Java、移动应用、Web应用等。


对于Spring框架项目,我们通常使用Maven来管理依赖。在创建Maven项目时,您需要知道如何获取Spring框架库。
4. 获取Spring框架库
在Maven项目中,您可以通过Maven仓库来添加项目依赖。一个常用的仓库是 mvnrepository.com。
以下是获取Spring依赖的步骤:
- 访问
mvnrepository.com。 - 在搜索框中输入您需要的依赖名称,例如
spring core。 - 从搜索结果中选择合适的包和版本(例如
spring-core 5.3.9)。 - 页面会提供该依赖的Maven坐标。您只需复制
<dependency>...</dependency>代码块。 - 将复制的依赖粘贴到您Maven项目的
pom.xml文件中。
使用Maven管理依赖的优势在于,当您添加一个核心依赖(如 spring-core)时,Maven会自动下载并管理其所有相关的支持库,无需手动处理多个JAR文件。
对于动态Web项目,您也可以手动下载JAR文件,但使用Maven项目是更高效和推荐的方式。
课程总结
本节课我们一起学习了搭建Spring框架开发环境的核心步骤。我们首先验证了JDK的安装,然后安装了Apache Tomcat服务器,接着配置了Eclipse IDE,最后详细介绍了如何通过Maven仓库来获取和管理Spring框架的依赖库。掌握这些基础环境配置是进行Spring应用开发的第一步。

希望这些概念对您来说已经清晰。请继续关注,以学习更多关于Spring框架应用开发的知识。我们下节课再见。
039:创建动态Web项目教程 🚀

在本节课中,我们将学习如何在Eclipse集成开发环境中创建一个动态Web项目。我们将从环境准备开始,逐步完成项目创建、服务器配置,并最终运行一个简单的网页。
概述

创建动态Web项目是Java Web开发的第一步。这个过程涉及在Eclipse中设置项目结构,并将其部署到Apache Tomcat应用服务器上运行。本节教程将引导你完成所有必要步骤。
环境准备
在开始之前,请确保你的计算机上已安装以下软件:
- JDK (Java开发工具包)
- Eclipse IDE (用于Java EE开发者的版本更佳)
- Apache Tomcat (应用服务器)
上一节我们介绍了课程背景,本节中我们来看看具体的创建步骤。Apache Tomcat是运行Java Web应用常用的服务器,几乎所有的Java Web应用都会部署到类似的应用服务器上。
创建动态Web项目
以下是创建新动态Web项目的步骤。
- 打开Eclipse,点击菜单栏的 File (文件)。
- 选择 New (新建) -> Other... (其他...)。在弹出的窗口中,展开 Web 文件夹。
- 选择 Dynamic Web Project (动态Web项目),然后点击 Next (下一步)。

配置项目参数
在项目配置页面,你需要设置几个关键参数。
- 在 Project name (项目名称) 字段中,为你的项目命名,例如
DynamicWebProjectDemo。 - Target runtime (目标运行时) 需要指定为Apache Tomcat。如果下拉列表中没有可选项,请点击 New Runtime... (新建运行时...)。
- 在弹出的窗口中选择你安装的Tomcat版本(例如 Apache Tomcat v9.0),点击 Next。
- 点击 Browse... (浏览...),找到并选择你电脑上Tomcat的安装目录(例如
C:\Program Files\Apache Software Foundation\Tomcat 9.0),然后点击 Finish (完成)。
完成项目创建
设置好运行时后,返回项目创建向导。
- Dynamic web module version (动态Web模块版本) 可以选择最新的(如4.0),这对创建JSP页面没有影响。
- 连续点击 Next (下一步),直到看到 Finish (完成) 按钮,然后点击它。
- 项目创建成功后,你可以在Eclipse的 Project Explorer (项目资源管理器) 视图中看到它。
创建Web页面
项目创建完成后,需要添加一个网页文件。
- 在项目结构中,导航到
src/main/webapp目录。这是存放Web应用文件(如HTML、JSP)的标准位置。 - 右键点击
webapp文件夹,选择 New -> HTML File (HTML文件)。 - 将文件命名为
index.html。 - 在打开的编辑器中,编写简单的HTML代码,例如:
<!DOCTYPE html> <html> <head><title>My Page</title></head> <body> <h1>Hello World!</h1> </body> </html> - 保存文件 (
Ctrl + S)。
配置并启动服务器
要让项目运行起来,需要在Eclipse中配置Tomcat服务器。
- 转到 Window (窗口) -> Show View (显示视图) -> Servers (服务器)。如果“Servers”视图未显示,可以通过 Other... 在搜索框中找到它。
- 在“Servers”视图底部,点击 “No servers are available. Click this link to create a new server...” (没有可用服务器。点击此链接创建新服务器...)。
- 选择你之前配置的Apache Tomcat版本,点击 Next。
- 在下一个界面,将左侧你刚创建的项目移动到右侧的“Configured” (已配置) 列表中,点击 Finish。
运行与测试项目
服务器配置好后,就可以运行项目了。
- 在“Servers”视图中,右键点击你刚添加的Tomcat服务器,选择 Start (启动)。控制台会显示服务器启动日志。
- 服务器启动后,右键点击你的项目(例如
DynamicWebProjectDemo),选择 Run As (运行方式) -> Run on Server (在服务器上运行)。 - 选择已启动的服务器,点击 Finish。Eclipse内置的浏览器会打开并显示你的
index.html页面,你应该能看到“Hello World!”字样。

(可选)配置外部浏览器
默认情况下,Eclipse使用内置浏览器。你可以将其改为系统默认的外部浏览器(如Chrome)。
- 点击 Window (窗口) -> Preferences (首选项)。
- 在左侧导航栏中,展开 General (常规) -> Web Browser (Web浏览器)。
- 在右侧选择 Use external web browser (使用外部Web浏览器),并从列表中选择你喜欢的浏览器(如Chrome)。
- 点击 Apply and Close (应用并关闭)。
- 下次运行项目时,页面将在你选择的外部浏览器中打开。
如果更改后未生效,可以尝试重启Eclipse,或者直接从浏览器访问服务器地址(通常是 http://localhost:8080/你的项目名/)。
总结

本节课中我们一起学习了在Eclipse中创建和运行动态Web项目的完整流程。关键步骤包括:创建项目、配置Tomcat运行时、添加Web内容、配置服务器以及运行测试。你现在已经拥有了一个基础的Java Web开发环境,可以在此基础上进一步学习Servlet、JSP等更高级的Web技术。

请继续关注后续课程,以了解更多关于Web应用开发环境的知识。下次见!




040:使用Maven开发Spring应用教程 🚀

在本节课中,我们将学习如何在Eclipse中使用Maven创建一个Spring应用,并演示如何构建一个基础的Web项目。整个过程将涵盖从项目初始化、配置调整到最终在服务器上运行的完整步骤。
概述 📋
上一节我们介绍了Maven的基本概念。本节中,我们来看看如何实际运用Maven在Eclipse中搭建一个Spring Web应用项目。我们将创建一个Maven项目,配置其POM文件,并最终在Tomcat服务器上成功运行。

准备工作
首先,如果之前的项目正在Tomcat服务器上运行,需要先清理Tomcat的工作目录。在Eclipse中,右键点击服务器,选择“Clean Tomcat Work Directory”。这将停止任何与Tomcat相关的进程。
创建Maven项目
接下来,我们开始创建新的Maven项目。
在Eclipse中,点击 File -> New -> Other...,然后在搜索框中输入“Maven Project”并选择它。
创建Maven项目时,有两种主要选项:
- 简单项目:这是一个核心Java项目。选择此项后,系统不会询问项目模板,只会要求进行Maven配置。
- 使用原型(Archetype)的项目:如果不选择“简单项目”,点击“Next”后,系统会要求你选择一个项目原型(Archetype)。原型是预定义的项目模板。
对于Web应用,我们需要选择Web应用原型。
以下是创建步骤:
- 在过滤框中输入“webapp”或“Apache”来搜索原型。以“Apache Maven”开头的原型会被筛选出来。
- 从列表中选择
maven-archetype-webapp。Spring框架项目通常也需要Web应用集成。如果只想创建核心Maven项目,可以选择maven-archetype-quickstart。 - 点击“Next”。
配置项目
现在需要配置项目的基本信息。
- Group Id:这本质上是项目的基础包名。
- Artifact Id:这是项目的名称,例如可以命名为“MavenWebProject”或“SpringProject”。
填写完毕后,点击“Next”。Maven会根据所选的原型生成项目的基本骨架,这个过程可能需要一点时间。
项目生成成功后,你可以在项目目录中看到 pom.xml 文件。
调整项目配置
生成的项目可能需要一些调整才能正常工作。
首先,检查 pom.xml 文件。默认的Java编译器版本可能是1.7。建议将其至少修改为1.8。在 pom.xml 中找到 <properties> 部分,修改 <maven.compiler.source> 和 <maven.compiler.target> 的值。
修改后,右键点击项目,选择 Maven -> Update Project...。在弹出的窗口中,勾选“Force Update of Snapshots/Releases”,然后点击“OK”。更新完成后,项目的Java标准版就会变为1.8。
接下来,需要配置项目的目标运行时服务器。右键点击项目,选择 Properties。在属性窗口中,找到 Targeted Runtimes。在这里,选择你的Tomcat服务器版本(例如Tomcat 9.0)。这一步是必须的,它指明了Web应用将在哪个服务器上运行。
在项目的 src/main/webapp 文件夹下,已经存在一个 index.jsp 文件,其中包含基础的“Hello World”代码。至此,项目的基础结构已准备就绪。
构建与运行项目
现在,我们来构建并运行这个Maven Web项目。
首先,构建项目。右键点击项目,选择 Run As -> Maven build...。在“Goals”输入框中,输入 clean install,然后点击“Run”。这将清理并编译项目。控制台输出显示“BUILD SUCCESS”即表示构建成功且没有错误。
构建成功后,即可在服务器上运行。右键点击项目,选择 Run As -> Run on Server。选择配置好的Tomcat服务器,并确保要运行的是我们刚创建的Maven项目(可能需要从列表中移除其他动态Web项目)。点击“Finish”,服务器将启动。
项目运行后,默认会在Eclipse内置浏览器中打开 index.jsp 页面,显示“Hello World”。


额外配置与查看
你可以配置在外部浏览器中打开应用。在Eclipse的 Window -> Web Browser 菜单中选择“External Web Browser”并应用设置。这样下次运行时就会使用系统默认的外部浏览器。
此外,一个重要的文件是 pom.xml。在这个文件中,你可以管理项目的所有依赖。默认情况下,JUnit依赖已被添加。你可以通过访问 Maven中央仓库网站 查找其他依赖,并将其坐标(groupId, artifactId, version)添加到 pom.xml 的 <dependencies> 部分。通过这种方式,你可以轻松管理项目所需的所有库。
总结 🎯



本节课中,我们一起学习了在Eclipse中使用Maven创建和运行Spring Web应用的全过程。我们完成了从创建Maven项目、选择Web应用原型、配置POM文件和目标运行时,到最终使用 clean install 命令构建项目并在Tomcat服务器上成功运行的步骤。掌握这些是进行Java企业级开发的基础。
041:Spring应用开发入门指南 🚀
在本节课中,我们将学习如何使用Eclipse集成开发环境来开发Spring应用程序。我们将深入理解控制反转容器的概念及其在Spring框架中的应用。此外,我们还将探索Spring框架的核心特性——依赖注入技术。
环境搭建与工具介绍 💻

上一节我们概述了本课的学习目标,本节中我们来看看如何为Spring应用开发搭建环境。
我们将从在Eclipse中设置Spring应用开发环境开始。Eclipse是一个功能强大的Java集成开发环境,它提供了丰富的插件和工具来简化开发流程。
以下是设置环境的关键步骤:
- 确保已安装Java开发工具包。
- 下载并安装Eclipse IDE for Java Developers。
- 在Eclipse中安装或配置Spring相关插件,如Spring Tools Suite。
理解控制反转容器 🔄
环境准备就绪后,现在我们来深入探讨控制反转容器的概念。
控制反转是Spring框架的基石。传统上,程序代码主动创建和管理其所依赖的对象。而在IoC模式中,这个控制权被反转了——由一个外部容器来负责对象的创建、组装和管理。这个容器就是IoC容器。
Spring框架主要提供了两种类型的IoC容器:
- BeanFactory:这是最基础的容器,提供了基本的依赖注入支持。其核心接口是
org.springframework.beans.factory.BeanFactory。 - ApplicationContext:这是BeanFactory的子接口,增加了更多企业级功能,如国际化、事件传播等,是更常用的容器。其核心接口是
org.springframework.context.ApplicationContext。
探索依赖注入技术 💉
理解了IoC容器如何管理对象生命周期后,本节我们来看看容器实现控制反转的具体技术——依赖注入。
依赖注入是IoC的一种具体实现方式。它指的是由容器在运行时动态地将某个对象所依赖的其他对象注入进去,而不是由对象自己创建依赖。这极大地降低了组件间的耦合度。
Spring框架支持以下几种主要的依赖注入方式:
- 构造器注入:通过类的构造方法来注入依赖。这能保证对象在创建完成后就处于完全初始化的状态。
public class ExampleService { private final DependencyRepository repository; // 构造器注入 public ExampleService(DependencyRepository repository) { this.repository = repository; } } - Setter方法注入:通过类的setter方法来注入依赖。这种方式更为灵活。
public class ExampleService { private DependencyRepository repository; // Setter方法注入 public void setRepository(DependencyRepository repository) { this.repository = repository; } }
使用依赖注入的优势在于,它使代码更易于测试、更模块化,并且遵循了面向接口编程的原则,提升了应用程序的可维护性和可扩展性。
总结 📚
本节课中,我们一起学习了使用Eclipse开发Spring应用程序的基础。我们从环境搭建开始,逐步理解了控制反转容器的核心思想,并深入探讨了其实现手段——依赖注入的两种主要方式:构造器注入和Setter方法注入。
通过掌握这些概念,你现在已经具备了使用Eclipse进行Spring应用开发的基础知识,并对Spring框架的基本原理有了清晰的认识。这些是构建更复杂、更松耦合的Java企业级应用的重要基石。
Java全栈开发:42:Spring容器概述 🏺

在本节课中,我们将学习Spring框架的核心——IoC容器。我们将探讨它的工作原理、如何创建Spring IoC容器,以及如何从容器中检索Bean。通过本课,你将理解Spring容器如何管理对象的生命周期。

Spring容器是Spring框架的核心,它负责实例化、配置和组装Spring Bean。容器通过读取配置元数据来获取关于需要实例化、配置和组装哪些对象的指令。
配置元数据可以通过三种方式表示:XML配置文件、Java注解或Java代码。这些元数据告诉容器需要执行哪些职责。
Spring IoC容器主要有两种类型:BeanFactory容器和ApplicationContext容器。
BeanFactory容器是Spring IoC容器的根接口,它支持Spring框架中定义的依赖注入。其核心接口是 org.springframework.beans.factory.BeanFactory。
ApplicationContext容器是BeanFactory的实现,并且是BeanFactory的子接口。它提供了更多企业级特定的功能。例如,它包含像 getBean() 这样的特定方法,并且会在容器启动时立即实例化所有单例Bean,而不是等到 getBean() 方法被调用时才进行实例化。
由于ApplicationContext是BeanFactory的子接口,因此它包含了BeanFactory容器的所有功能。
上一节我们介绍了Spring容器的基本概念和类型,本节中我们来看看如何创建和使用这些容器。
以下是创建配置元数据的几种主要方式:
- XML配置:使用传统的XML文件来定义Bean及其依赖关系。
- 注解配置:在Java类中使用注解(如
@Component,@Autowired)来声明Bean和注入依赖。 - Java配置:使用
@Configuration注解的Java类,通过@Bean注解的方法来定义Bean。
在后续课程中,我们将深入学习如何使用这些不同的配置方式来创建元数据,并调用Spring容器。

本节课中我们一起学习了Spring IoC容器的基础知识。我们了解了容器负责Bean的生命周期管理,认识了两种主要的容器类型——基础的BeanFactory和功能更强大的ApplicationContext,并简要介绍了三种配置元数据的方式。理解这些是掌握Spring依赖注入和面向切面编程等高级特性的基础。
043:创建Spring IOC容器 🏗️

在本节课中,我们将学习如何创建Spring IOC容器。该容器负责实例化、配置和组装Spring Bean。我们将使用XML配置方式来完成这个演示。

概述
Spring IOC容器是Spring框架的核心,它管理着应用中所有对象的生命周期和依赖关系。配置元数据可以通过XML、Java注解或Java代码来表示。本节我们将专注于使用XML配置来创建容器。
创建Maven项目
首先,我们需要创建一个Maven项目作为基础。我们将生成一个简单的Java项目,不包含Web应用等复杂结构。
以下是创建项目的步骤:
- 选择项目骨架类型,这里我们选择“简单项目”。
- 设置Group ID为
com.panagwani.spring.ioc.container.example。 - 完成项目创建。

项目生成后,我们可以在 src/main/java 目录下存放Java文件,在 pom.xml 文件中管理依赖和配置。

配置项目与依赖

创建项目后,需要进行一些基础配置并添加Spring依赖。
首先,我们需要将项目的Java编译器版本从默认的1.5调整为至少1.8。这可以通过项目属性中的Java编译器设置来完成。修改后,需要更新Maven项目以使更改生效。
接下来,我们需要在 pom.xml 文件中添加Spring Context依赖。Spring Context模块已经包含了Spring Core,因此添加它即可。我们可以从Maven中央仓库获取依赖信息。添加依赖后保存文件,Maven会自动下载所需的库文件。
如果项目没有自动更新,我们可以在 pom.xml 的 <properties> 标签中手动指定Maven编译器的目标版本,例如设置为17。每次修改 pom.xml 后,都建议通过右键项目选择“Maven -> Update Project”来更新项目。
创建Bean类
配置好项目环境后,我们需要创建一个简单的Bean类。
在 src/main/java 下,于包 com.panagwani.spring.ioc 中创建一个名为 HelloWorld 的类。这个类包含一个私有字符串属性 message,并为其生成Getter和Setter方法。同时,我们重写 toString() 方法,以便在打印对象时显示我们想要的信息。
配置XML元数据
Bean类创建完成后,我们需要通过XML文件来配置它,告诉Spring容器如何管理这个Bean。
在 src/main/resources 目录下创建一个名为 applicationContext.xml 的配置文件。我们需要在文件中声明必要的命名空间。然后,在文件中定义一个Bean:指定其ID为 helloWorld,类路径为我们刚刚创建的 HelloWorld 类。接着,通过 <property> 标签为 message 属性注入值,例如“Hello”。
创建Spring容器并获取Bean
最后一步是编写主应用程序类,启动Spring容器并从容器中获取我们配置的Bean。
在基础包下创建一个包含 main 方法的类,例如 ApplicationLauncher。在 main 方法中,我们使用 ClassPathXmlApplicationContext 类来加载 applicationContext.xml 配置文件,从而初始化Spring IOC容器。
容器初始化后,我们通过容器的 getBean 方法,传入Bean的ID helloWorld 来获取该Bean的实例。获取到的对象需要强制转换为 HelloWorld 类型。此时,Bean已经由Spring容器完成实例化和属性注入。我们可以打印这个对象来验证配置是否成功。
运行这个Java应用程序,控制台将输出Bean的详细信息,证明Spring IOC容器已成功创建并工作。
总结

本节课我们一起学习了创建Spring IOC容器的完整流程。我们首先创建了一个Maven项目,然后配置了项目环境并添加了必要的Spring依赖。接着,我们定义了一个简单的Bean类,并通过XML文件配置了该Bean的元数据。最后,我们编写代码初始化了Spring容器,并成功从容器中获取了配置好的Bean实例。这种基于XML的配置方式是Spring IOC的基础,同样的配置也可以通过Java注解或Java代码来实现。掌握这些步骤后,你就可以在未来的Spring项目中运用Bean了。
044:Spring Bean、作用域与生命周期 🚀


在本节课中,我们将要学习Spring框架中的核心概念:Bean、Bean的作用域以及Bean的生命周期。我们将了解什么是Spring Bean,如何配置它们,以及Spring容器如何管理它们的创建、使用和销毁过程。
什么是Spring Bean? 🤔

Spring Bean是构成Spring应用程序骨架的对象,它们由Spring IoC容器进行实例化、组装和管理。简单来说,一个Bean就是一个由Spring IoC容器管理的对象。

你可以通过XML配置文件或Java注解来配置Bean的元信息。例如,在XML中,我们使用 <bean> 标签来定义一个Bean;在Java注解中,我们使用 @Bean 注解来创建或配置Bean注入。此外,还有 @Component、@Service、@Controller 和 @Repository 等注解,它们通常用在类定义之上,以参与Spring Bean的生命周期管理。
Spring Bean的作用域 🎯
在定义Bean时,你可以为其声明一个作用域。这决定了Spring容器创建Bean实例的方式。Spring支持多种作用域,主要包括以下几种:
以下是Spring Bean的几种核心作用域:
-
Singleton(单例):这是默认的作用域。在整个应用程序中,Spring容器只会创建该Bean的一个实例。这个单例实例会被缓存,所有后续对该Bean的请求都会返回这个缓存的对象。
- 配置方式:在Bean配置中设置
scope="singleton"。
- 配置方式:在Bean配置中设置
-
Prototype(原型):每次请求该Bean时,Spring容器都会创建一个新的实例。因此,对于有状态的Bean,通常使用原型作用域;对于无状态的Bean,则使用单例作用域。
- 配置方式:在Bean配置中设置
scope="prototype"。
- 配置方式:在Bean配置中设置
-
Request(请求):为每一个HTTP请求创建一个新的Bean实例。这意味着如果服务器正在处理50个请求,就会创建50个独立的Bean实例。一个实例的状态改变对其他实例不可见。
- 配置方式:在Bean配置中设置
scope="request"。
- 配置方式:在Bean配置中设置
-
Session(会话):为每一个HTTP会话创建一个新的Bean实例。如果服务器有20个活跃会话,就会创建20个实例。但在同一个会话内的所有请求,访问的是同一个Bean实例。
- 配置方式:在Bean配置中设置
scope="session"。
- 配置方式:在Bean配置中设置
-
Application(应用):也称为全局作用域。在整个Web应用程序运行时,Spring容器为每个Web应用创建一个实例。这类似于Servlet上下文(
ServletContext)的作用域。- 配置方式:在Bean配置中设置
scope="application"。
- 配置方式:在Bean配置中设置
-
WebSocket:为每个WebSocket会话创建一个Bean实例。需要注意的是,此作用域在较新版本的Spring中已被标记为过时(deprecated)。


Spring Bean的生命周期 🔄
对象的生命周期指的是它何时以及如何被创建、在其生存期间如何行为、以及何时以及如何被销毁。同样,Bean的生命周期指的是Bean何时以及如何被实例化、初始化和销毁。
Bean的生命周期由Spring容器管理。其基本流程如下:
- 启动容器:当我们运行程序时,Spring容器首先启动。
- 创建实例:容器根据请求创建Bean的实例。
- 依赖注入:容器将所需的依赖注入到Bean中。
- 初始化:如果Bean定义了初始化方法(如
init-method或使用了@PostConstruct注解),容器会调用它来执行自定义的初始化代码。 - 就绪使用:此时,Bean已准备就绪,可供应用程序使用。
- 销毁:当Spring容器关闭时,如果Bean定义了销毁方法(如
destroy-method或使用了@PreDestroy注解),容器会调用它来执行清理工作,然后销毁Bean。
为了在Bean初始化和销毁时执行特定代码,Spring提供了多种方式:
以下是控制Bean生命周期事件的几种主要方法:
- 实现
InitializingBean和DisposableBean接口:分别重写afterPropertiesSet()和destroy()方法。 - XML配置:在
<bean>标签中指定init-method和destroy-method属性,指向Bean类中的自定义方法。 - 使用注解:在方法上使用
@PostConstruct和@PreDestroy注解。


总结 📝


本节课中我们一起学习了Spring框架中关于Bean的核心知识。我们首先了解了Spring Bean是由IoC容器管理的对象。接着,我们详细探讨了Bean的几种作用域,包括单例、原型、请求、会话和应用作用域,它们决定了Bean实例的创建策略。最后,我们梳理了Bean的完整生命周期,从容器启动、实例创建、依赖注入、初始化、使用到最终销毁的各个阶段,并介绍了如何通过接口、XML配置或注解来介入生命周期的初始化和销毁环节,以执行自定义逻辑。

理解Bean的作用域和生命周期对于构建高效、可控的Spring应用程序至关重要。在接下来的课程中,我们将继续深入学习Spring依赖注入,了解Setter方法和属性如何帮助注入依赖。
045:使用构造器和Setter方法进行依赖注入


在本节课中,我们将讨论Spring中的依赖注入是什么,以及依赖注入的不同类型。
概述
依赖注入是Spring框架中的一个基本概念,它指的是一种设计模式。对象由其外部提供依赖项,而不是在内部创建它们。依赖注入通过控制反转实现,其中一个类的对象需要被注入到另一个类中,而不是由该类自行实例化。这样可以避免两个类或对象之间的紧耦合。
依赖注入的核心概念

假设有两个类A和B,如果类A使用了类B的功能,那么就意味着类A依赖于类B。依赖注入主要涉及三个角色:客户端类、服务类和注入器类。
在这个架构中,服务是作为依赖项被创建的对象,供客户端使用。注入器则是负责创建构造器或Setter方法的部分。
以下是依赖注入的主要好处:
- 提高应用程序各部分的内聚性。
- 减少应用程序各部分之间的耦合度,有助于创建松耦合架构。
- 更好地设计应用程序,因为它本身就是设计模式的一部分。
- 减少样板代码。
依赖注入的类型
依赖注入主要有两种实现方式:一种是基于构造器的依赖注入,另一种是基于Setter方法的依赖注入(有些人也称之为基于字段的依赖注入)。
基于构造器的依赖注入
在基于构造器的依赖注入中,你的Bean中需要有一个构造器,用于初始化你想要传递的属性。
代码示例:
public class MyBean {
private String message;
// 构造器注入
public MyBean(String message) {
this.message = message;
}
}
基于Setter方法的依赖注入
在基于Setter方法的依赖注入中,我们需要通过Setter方法来传递属性。为此,Bean中至少需要一个默认构造器,以及与我们想要实例化的属性相关联的Setter方法。
代码示例:
public class MyBean {
private String message;
// 默认构造器
public MyBean() {}
// Setter方法注入
public void setMessage(String message) {
this.message = message;
}
}
实践演示
上一节我们介绍了依赖注入的类型,本节我们来看看如何在实践中实现依赖注入。
在之前的演示中,我们有一个HelloWorld.java作为Bean,其中包含了属性的Getter和Setter方法。如果我们不编写任何构造器,程序应该不会报错。
当我们通过XML配置文件使用<property>标签时,我们使用的是Setter方法注入。你可以在Bean中通过System.out.println打印一条消息来验证Setter方法是否被调用。
Setter方法注入验证:
public void setMessage(String message) {
this.message = message;
System.out.println("Setting message property.");
}
如果你想通过构造器进行注入,则需要修改配置文件。在配置文件中,不使用<property>,而是使用<constructor-arg>标签。你需要定义参数的类型、名称和要赋予的值。
构造器注入配置示例 (XML):
<bean id="helloAnotherBean" class="com.example.HelloWorld">
<constructor-arg name="message" value="Hello via Constructor!" />
</bean>
当然,为了实现构造器注入,你的Bean中必须要有相应的构造器。首先,需要一个默认构造器,然后还需要一个参数化构造器。
Bean中的构造器示例:
public class HelloWorld {
private String message;
// 默认构造器
public HelloWorld() {
System.out.println("Default constructor invoked.");
}
// 参数化构造器
public HelloWorld(String message) {
this.message = message;
System.out.println("Parameterized constructor invoked.");
}
// Setter方法
public void setMessage(String message) {
this.message = message;
System.out.println("Set message property invoked.");
}
}
运行程序后,你将在控制台看到类似以下的输出,这证明了默认构造器、Setter方法和参数化构造器都被成功调用:
Default constructor invoked.
Set message property invoked.
Parameterized constructor invoked.
总结

本节课中,我们一起学习了Spring依赖注入的核心概念。我们了解了依赖注入是一种由外部提供对象依赖的设计模式,它能有效降低代码耦合度。我们重点探讨了两种主要的依赖注入方式:基于构造器的注入和基于Setter方法的注入,并通过代码示例和配置演示了它们的具体实现。现在,我们已经准备好在自己的Spring或Spring Boot应用中使用依赖注入了。
Java全栈开发:第46课:Spring Boot基础入门
在本节课中,我们将学习Spring Boot的基础知识。你将了解什么是Spring Boot,它与Spring和Spring MVC有何不同,探索其架构,并学习如何使用Spring Initializr和Maven来搭建一个Spring Boot项目。
什么是Spring Boot?
Spring Boot是一个开源框架,旨在简化基于Spring的应用程序开发。它提供了一种更快速、更直接的方式来创建独立的、生产级的应用程序。

上一节我们介绍了课程目标,本节中我们来看看Spring Boot的核心定义。
Spring Boot的核心优势
以下是Spring Boot的几个关键特性和优势:
- 简化配置:Spring Boot采用“约定优于配置”的原则,提供了大量自动配置,减少了繁琐的XML或Java配置。
- 独立运行:Spring Boot应用可以打包成可执行的JAR或WAR文件,内嵌了Tomcat、Jetty或Undertow等服务器,无需部署到外部Web容器即可直接运行。
- 生产就绪:它集成了健康检查、指标、外部化配置等特性,方便监控和管理生产环境中的应用。
- 无代码生成:Spring Boot不需要代码生成,也不要求XML配置。
Spring Boot架构概览
Spring Boot的架构建立在Spring框架之上,并进行了高度封装和自动化。其核心组件包括:
- 自动配置:根据项目中的类路径、已定义的Bean以及其他属性设置,自动配置Spring应用。
- 起步依赖:一组预定义的依赖描述符,可以轻松地将所需的功能(如Web、数据访问、安全等)添加到项目中。
- Actuator:提供了一系列生产级功能,用于监控和管理应用。
- 嵌入式Servlet容器:允许将Web服务器(如Tomcat)作为应用的一部分打包和运行。
搭建Spring Boot项目
了解了Spring Boot的基本概念和架构后,本节我们将动手实践,学习如何搭建一个Spring Boot项目。我们将使用两种主要工具:Spring Initializr和Maven。
使用Spring Initializr
Spring Initializr是一个基于Web的工具,用于生成Spring Boot项目的基本配置和结构。以下是使用步骤:
- 访问Spring Initializr网站。
- 选择项目类型(如Maven Project)、语言(Java)和Spring Boot版本。
- 填写项目的元数据(Group, Artifact)。
- 添加项目所需的依赖(如Spring Web, Spring Data JPA)。
- 点击“Generate”按钮下载项目压缩包。
使用Maven构建和管理依赖
Maven是一个构建和项目管理工具,它自动化了构建过程并管理项目依赖。在Spring Boot项目中,Maven的pom.xml文件是核心配置文件。
以下是一个典型的Spring Boot Maven项目pom.xml文件的关键部分示例:
<?xml version="1.0" encoding="UTF-8"?>
<project>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version> <!-- 示例版本号 -->
</parent>
<dependencies>
<!-- Spring Boot Web起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 其他依赖... -->
</dependencies>
<build>
<plugins>
<!-- Spring Boot Maven插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
spring-boot-starter-parent:作为父项目,它提供了默认的Maven配置和依赖管理。spring-boot-starter-*:这些是起步依赖,只需声明一个,即可引入相关功能所需的所有库。spring-boot-maven-plugin:这个插件可以将应用打包成可执行的JAR文件。
总结
本节课中,我们一起学习了Spring Boot的基础知识。我们首先明确了Spring Boot是一个用于简化Spring应用开发的框架。接着,我们探讨了它的核心优势,如自动配置和独立运行能力。然后,我们概述了Spring Boot的架构及其关键组件。最后,我们通过介绍Spring Initializr和Maven,掌握了搭建和管理一个Spring Boot项目的基本方法。这些知识为你开始使用Spring Boot进行高效开发奠定了坚实的基础。
047:Spring Boot入门指南 🚀

在本节课中,我们将学习Spring Boot框架。Spring Boot是一个用于简化Java应用程序开发的框架,特别适合基于微服务的架构。我们将了解它的核心概念、优势以及基本组成部分。

概述
Spring Boot是Spring框架的一个模块,旨在简化配置和设置过程,使开发者能更专注于编写应用程序代码。它支持快速应用开发,通常被称为RAD。
什么是Spring Boot?
Spring Boot是一个Java框架,它让创建和运行基于微服务的Java应用变得更加容易。
核心公式:Spring Boot = Spring Framework + 嵌入式服务器 + 自动配置
它通过提供一系列“启动器”依赖和自动配置,减少了传统Spring应用所需的大量XML配置工作。
为什么使用Spring Boot?
我们使用Spring Boot,主要基于以下几个原因:
以下是Spring Boot的主要优势列表:
- 开源框架:由Pivotal团队开发,用于创建微服务。
- 快速启动:提供了更快捷的方式来设置、配置并运行简单和Web应用程序。
- 独立应用:可用于运行和构建独立的应用程序,无需部署WAR文件。
- 简化配置:无需复杂的XML配置,并内置了Tomcat、Jetty或Undertow等服务器。
- 生产就绪:提供生产环境所需的特性,并易于管理和定制Spring容器中的Bean组件。
Spring Boot的核心特性

上一节我们了解了Spring Boot的优势,本节中我们来看看它的一些具体特性。
以下是Spring Boot的关键特性列表:
- 项目生成灵活:可以通过Maven项目生成,或利用Spring Initializer网站生成项目骨架。
- 命令行工具:也可以使用Spring Boot CLI命令来生成项目。
- 自动配置:项目初始化后,自动配置功能即可使用,无需手动设置。
- 外部化配置:支持将配置(如数据库连接)外移到属性文件或环境变量中,便于不同环境部署。
Spring Boot的组成部分与应用
Spring Boot提供了多种实现来支持应用开发。
以下是Spring Boot的一些核心子组件和功能:
- Spring Boot Starters:一组依赖描述符,可以轻松地将相关依赖添加到项目中。
- 灵活的配置:提供了配置Java Bean、数据库事务等的灵活方式。
- 批处理与REST端点:提供强大的批处理功能,并能方便地管理和定制REST API端点。
- 注解驱动:提供基于注解的Spring应用开发方式,注解开箱即用。
- 依赖管理:简化了大型应用中的依赖管理。
- 架构支持:Spring Boot有助于创建三层架构或仓库设计模式。
- 嵌入式Servlet容器:内置了Servlet容器(在Spring框架中即DispatcherServlet),无需单独部署。
总结

本节课中,我们一起学习了Spring Boot的基础知识。我们了解到Spring Boot是一个通过自动配置和嵌入式服务器来极大简化Spring应用开发的框架。它使开发者能够快速创建独立、生产级的Spring应用程序。在接下来的课程中,我们将继续学习如何使用Spring Initializer以及如何在Spring Boot应用中创建REST端点。
048:Spring、Spring Boot与Spring MVC的区别 🚀


在本节课中,我们将学习Spring生态中三个核心概念:Spring框架、Spring Boot和Spring MVC之间的区别。我们将逐一分析它们的设计目的、核心特性以及适用场景,帮助你理解在何种情况下应该选择哪种技术。
概述
Spring是Java领域最流行的应用程序开发框架。它的主要特性是依赖注入(Dependency Injection)或控制反转(IoC)。借助Spring框架,我们可以开发出松耦合的应用程序。当应用程序的类型或特性被明确定义时,使用Spring框架是更好的选择。
上一节我们介绍了Spring框架的基本概念,本节中我们来看看Spring Boot。
Spring Boot是Spring框架的一个模块,它允许我们以极简或零配置的方式构建独立的应用程序。当我们想要开发一个简单的基于Spring的应用程序或RESTful服务时,使用Spring Boot是更好的选择。
以下是Spring与Spring Boot之间的主要区别:
- 定位与用途:Spring是一个开源、轻量级的框架,广泛用于开发企业级Web应用程序。Spring Boot则主要用于开发REST API。
- 核心特性:Spring最重要的特性是依赖注入。而使用Spring Boot,是因为可以用最少的代码创建REST API,并享受其自动配置功能。
- 应用耦合度:Spring创建松耦合的应用程序。Spring Boot创建独立的应用程序。
- 服务器配置:运行Spring应用程序需要显式设置服务器。但Spring Boot提供了嵌入式服务器,如Tomcat或Jetty。
- 内存数据库支持:Spring不提供对内存数据库的支持,但Spring Boot支持。
- 样板代码:在Spring中,开发者需要手动集成和实现较小任务的样板代码。但在Spring Boot中,样板代码大大减少,配置本身也是极简的。
- 依赖管理:开发者需要在
pom.xml中手动为Spring项目定义依赖。但Spring Boot引入了Starter的概念。当你使用Spring Initializr生成应用程序时,依赖项会作为JAR包内部添加,以自动处理下载。

接下来,我们来比较Spring Boot与Spring MVC。

Spring Boot使得快速引导和开始开发基于Spring的REST API变得容易。它避免了大量的代码实现,隐藏了幕后的复杂性,使开发者能够快速上手并轻松开发基于Spring的应用程序。
Spring MVC是一个用于构建Web应用程序的Web MVC框架。它包含许多用于各种功能的配置文件,是一个面向HTTP的Web应用程序开发框架。
Spring Boot和Spring MVC是为不同目的而存在的,因此直接比较它们并不完全公平。但由于它们都是Spring框架的模块,我们仍可以列出一些关键差异。
以下是Spring Boot与Spring MVC之间的主要区别:
- 设计目的:Spring Boot用于打包基于Spring的应用程序以创建REST API。Spring MVC则用于创建完整的基于MVC的Web应用程序。
- 配置方式:Spring Boot为构建Spring应用程序提供了默认配置。Spring MVC则使用其内置功能开发Web应用程序,需要手动配置。
- 启动与配置:在Spring Boot中,无需手动构建配置。但在Spring MVC中,一切从DispatcherServlet开始,你需要自行配置。
- 部署描述符:Spring Boot不需要手动提供部署描述符(但你可以选择提供)。Spring MVC则始终需要一个部署描述符。
- 代码与依赖管理:Spring Boot避免了样板代码,并将依赖项打包在一个单元中。在Spring MVC中,每个依赖项都是单独处理的。
- 开发效率:Spring Boot减少了开发时间并提高了生产力。而Spring MVC需要更多时间来实现相同的功能。
总结

本节课中我们一起学习了Spring、Spring Boot和Spring MVC的核心区别。Spring框架提供了依赖注入的基础,用于构建松耦合的企业应用;Spring Boot在其基础上通过自动配置、嵌入式服务器和Starter依赖,极大地简化了独立应用和REST API的创建;而Spring MVC则是一个专注于构建传统Web应用程序的MVC框架。理解这些区别有助于你在不同的项目需求中选择最合适的工具。请继续关注后续课程,学习更多关于Spring Boot REST API开发的内容。
049:Spring Boot 架构详解 🏗️


在本节课中,我们将学习 Spring Boot 的内部架构,了解其各层如何协同工作以构建独立的生产级应用程序。
概述
Spring Boot 是 Spring 框架的一个模块,用于以最少的配置创建独立的、生产级的 Spring 应用程序。它建立在 Spring 框架核心之上,并支持分层架构,其中每一层直接与其上下层通信,形成一个类似树形的层次结构。
架构分层
Spring Boot 架构主要包含四个层次,理解这些层次是掌握其工作原理的关键。
以下是这四个层次及其功能:
-
表示层
- 处理 HTTP 请求。
- 将 JSON 参数转换为对象。
- 对请求进行身份验证。
- 将请求转发至业务层。
- 简而言之,它包含所有的视图或前端部分。
-
业务层
- 处理所有业务逻辑。
- 包含服务类。
- 使用数据访问层提供的服务。
- 协助进行授权和验证。
-
持久层
- 包含所有存储逻辑。
- 在业务对象和数据库行之间进行转换。

- 数据库层
- 这是实际的数据库。
- 负责执行 CRUD 操作,即创建、检索、更新和删除。
工作流程
上一节我们介绍了架构的静态分层,本节中我们来看看数据在这些层之间是如何流动的。
Spring Boot 的流程架构与 Spring MVC 基本相同,但有一个关键区别:在 Spring Boot 中,我们使用数据访问对象(DAO)实现类与数据库通信。
以下是 Spring Boot 应用程序的典型工作流程:
- 客户端发起一个 HTTP 请求(如 GET、POST、PUT、DELETE)。
- 该请求首先到达控制器。
- 控制器映射并处理该请求。
- 如果需要,控制器会调用服务层的业务逻辑。
- 在服务层,所有业务逻辑都在映射到 JPA 模型类的数据上执行。
- 如果没有错误,相应的 JSP 页面(或响应)将返回给用户。
Spring Boot 在内部整合了 Spring 的各个模块,如 Spring MVC、Spring Data 等,并利用验证类、视图类和工具类来实现上述流程。

总结

本节课中,我们一起学习了 Spring Boot 的四层架构:表示层、业务层、持久层和数据库层,并了解了请求从客户端发出到返回响应的完整工作流程。掌握这个架构有助于我们更好地设计和理解 Spring Boot 应用程序。
050:Spring Initializer与Maven项目创建 🚀
在本节课中,我们将学习如何使用Spring Initializer工具来快速生成一个Spring Boot项目的骨架结构。这是一个基于Web的工具,能帮助我们轻松配置项目元数据和依赖。
概述
Spring Initializer是一个由Pivotal Web Services提供的基于Web的工具。借助它,我们可以轻松生成Spring Boot项目的结构。它提供了一个可扩展的API,用于创建基于JVM的项目,并提供了多种选项。这些选项通过一个元数据模型来表达,该模型允许我们配置JVM支持的依赖项列表和平台版本。它还将元数据以标准格式提供,为第三方客户端提供必要的支持。
Spring Initializer模块包含多个组件,例如:Spring Initializer Actuator、Initializer BOM(物料清单)、Initializer Docs、Generator、Generator Test、Metadata Service Example、Version Resolver和Initializer Web。它支持多种集成开发环境接口,如STS、IntelliJ IDEA、NetBeans或Eclipse。在本教程中,我将使用Eclipse。
项目生成步骤

以下是使用Spring Initializer生成并导入一个Spring Boot项目的具体步骤。

1. 访问Spring Initializer网站
首先,你需要访问网站 start.spring.io。
2. 配置项目基本信息
在网站页面上,你需要进行以下配置选择:
- 项目类型:选择Maven或Gradle。这里我们选择Maven。
- 语言:选择Java、Kotlin或Groovy。这里我们选择Java。
- Spring Boot版本:可以选择快照版(Snapshot,表示仍在开发中)或稳定版。例如,可以选择
3.1.0。 - 项目元数据:
- Group:通常填写组织名。默认是
com.example。 - Artifact:填写项目名称。例如,
spring-boot-fundamentals。 - Name:项目名称。
- Description:项目描述。
- Package name:包名,通常由Group和Artifact组合而成。
- Packaging:选择打包方式,如JAR。
- Java版本:选择JDK版本,例如17。
- Group:通常填写组织名。默认是
3. 添加项目依赖
在“Dependencies”部分,点击“ADD DEPENDENCIES”来为项目添加所需的库。例如,对于Web项目,可以添加 Spring Web 依赖。这个依赖包含了创建RESTful API所需的注解和嵌入式Tomcat等包。
4. 生成项目
配置完成后,点击页面上的“GENERATE”按钮。浏览器会下载一个包含项目骨架的ZIP压缩包。
5. 保存并解压项目
将下载的ZIP文件保存到本地目录,例如 D:\SpringBootProjects。然后解压该文件。解压后,你可能会看到一个嵌套的文件夹结构,可以将最内层的项目文件夹剪切到目标目录(如 D:\SpringBootProjects)的根目录下。
6. 将项目导入Eclipse
打开Eclipse,可以通过以下两种方式之一导入项目:
- 点击 File -> Open Projects from File System...,然后浏览到你的项目目录。
- 或者点击 File -> Import...,在弹出的对话框中选择 Maven -> Existing Maven Projects,然后点击 Next,浏览并选择你的项目根目录(即包含
pom.xml文件的文件夹),最后点击 Finish。
如果导入后未在项目资源管理器中看到项目,可能是因为设置了工作集(Working Set)。你可以通过点击项目资源管理器右上角的倒三角图标,选择 Edit Active Working Set...,然后将新导入的项目添加到当前活动的工作集中。
总结

本节课我们一起学习了如何使用Spring Initializer在线工具快速生成一个Spring Boot Maven项目,并将其成功导入到Eclipse集成开发环境中。我们了解了配置项目基本信息、添加依赖以及导入项目的完整流程。生成的项目已经具备了基础结构,在下一节课中,我们将在此项目基础上创建第一个Web请求处理器。
Java全栈开发:51:本课学习内容概述

在本节课中,我们将探索RESTful Web服务,并学习如何使用Java和Spring框架来实现它们。
我们将从学习RESTful Web服务的基础知识开始,然后着手实现一个包含路径变量的简单“Hello”服务。
上一节我们介绍了课程目标,本节中我们来看看具体的实现路径。
以下是本课将涵盖的核心主题:
- 实现统一的方法,如GET、POST、PUT和DELETE,以创建、检索、更新和删除用户资源。
- 使用Spring Data JPA将我们的RESTful Web服务连接到数据库。
你将学习如何配置应用程序以连接到数据库,创建实体类,定义仓库接口,并使用Spring JPA实现CRUD操作。
通过本节课的学习,你将扎实掌握如何使用Spring开发和部署RESTful Web服务,并将其连接到数据库。
052:使用路径变量增强Hello World服务 🚀
在本节课中,我们将学习如何创建一个控制器,并在其中添加REST API,同时学习如何传递路径变量。我们将从创建一个基本的Spring Boot应用开始,逐步构建带有不同端点的控制器。
概述
我们将创建一个Spring Boot应用,并定义一个RestController。在这个控制器中,我们将编写两个处理HTTP GET请求的方法。之后,我们将运行应用并通过浏览器测试这些API端点。
创建项目结构与控制器
首先,我们需要建立项目的基础结构。在src/main/java目录下,创建我们的基础包。
代码示例:创建基础包
package com.pabna.guvani;
接下来,我们创建主应用类。为了清晰起见,我们将其命名为Application.java,它包含启动Spring Boot应用的main方法。
代码示例:主应用类
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
现在,我们来创建控制器。你可以在基础包下创建,也可以新建一个子包。这里我们在com.pabna.guvani包下创建一个名为controllers的子包。
操作步骤:
- 右键点击
src/main/java目录。 - 选择
New->Package。 - 输入包名:
com.pabna.guvani.controllers。
在controllers包内,我们创建一个名为HelloWorldController的类。由于这是一个Spring Boot应用,并且我们要创建REST API,我们需要使用@RestController注解来标记这个类。
代码示例:创建控制器类
package com.pabna.guvani.controllers;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloWorldController {
// 后续将在这里添加方法
}
定义REST API端点
上一节我们创建了控制器类,本节中我们来看看如何在其中定义处理请求的方法。我们将创建两个处理HTTP GET请求的端点。
@RestController注解表明这个类的所有方法返回的数据都将直接写入HTTP响应体。为了映射HTTP GET请求到特定的处理方法,我们需要使用@GetMapping注解。
以下是我们要添加的两个方法:
代码示例:定义GET请求处理方法
import org.springframework.web.bind.annotation.GetMapping;
@RestController
public class HelloWorldController {
@GetMapping("/")
public String message() {
return "Welcome to Spring Boot application";
}
@GetMapping("/hello")
public String helloWorld() {
return "Hello World";
}
}
message()方法:映射到根路径/。当访问应用首页时,会返回欢迎信息。helloWorld()方法:映射到路径/hello。当访问此路径时,会返回“Hello World”。
运行与测试应用

我们已经定义了两个API端点,现在需要启动应用并进行测试。Spring Boot应用可以通过运行主类Application中的main方法来启动。
启动后,应用默认会在8080端口运行。请确保该端口未被占用。

启动成功后,我们可以通过浏览器来测试我们的API。
以下是测试步骤:
- 打开浏览器。
- 测试根路径:在地址栏输入
http://localhost:8080/。页面应显示“Welcome to Spring Boot application”。 - 测试
/hello路径:在地址栏输入http://localhost:8080/hello。页面应显示“Hello World”。
通过以上步骤,我们验证了两个REST端点工作正常。
总结
本节课中我们一起学习了Spring Boot REST控制器的基础知识。我们创建了一个@RestController,并使用@GetMapping注解定义了两个处理HTTP GET请求的方法,分别响应根路径/和/hello的请求。最后,我们成功运行了应用并通过浏览器测试了这些API端点。

在接下来的课程中,我们将学习如何创建数据模型(Model/Bean),并将其注入到控制器中,以构建更符合实际场景的请求处理逻辑。
053:实现获取用户资源的GET服务 🚀

在本节课中,我们将学习如何在Spring Boot应用中实现一个GET服务,用于检索学生资源。我们将创建一个学生模型、一个控制器,并实现两个不同的GET端点:一个用于获取所有学生列表,另一个用于根据路径变量(Path Variable)获取特定学生。

概述 📋
我们将分步构建一个简单的学生信息API。首先,创建一个表示学生的Java Bean。然后,创建一个REST控制器,其中包含两个GET请求处理方法。第一个方法返回所有学生的列表,第二个方法根据提供的名(first name)和姓(last name)返回一个特定的学生对象。我们将使用路径变量来传递这些参数。
创建学生模型 🧱
首先,我们需要创建一个Student类来表示学生数据。这个类将作为我们应用程序中的数据模型。
在com.pavna.gvani.beans(或model)包下,右键创建名为Student的类。
以下是Student类的代码:
package com.pavna.gvani.beans;
public class Student {
private String firstName;
private String lastName;
// 默认构造函数
public Student() {}
// 带参数的构造函数
public Student(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// Getter 和 Setter 方法
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
// toString 方法,便于打印对象信息
@Override
public String toString() {
return "Student{" +
"firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
'}';
}
}
这个类定义了两个属性:firstName和lastName。我们生成了构造函数、Getter和Setter方法以及toString方法。
创建学生控制器 🎮
上一节我们创建了数据模型,本节中我们来看看如何创建控制器来处理HTTP请求。
在controller包下,创建一个名为StudentController的类。这个类将被标记为@RestController。
注意:避免让多个控制器使用默认的请求路径(如“/”),以免应用程序混淆该将请求路由到哪个控制器。
以下是StudentController的初始代码,包含一个返回学生列表的GET端点:
package com.pavna.gvani.controller;
import com.pavna.gvani.beans.Student;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
public class StudentController {
// 创建一个静态的学生列表
public static List<Student> students = new ArrayList<>();
// 构造函数,用于初始化学生列表
public StudentController() {
students.add(new Student("King", "Coachcher"));
students.add(new Student("Sya", "Sharma"));
students.add(new Student("Sarah", "Bowling"));
students.add(new Student("John", "Smith"));
students.add(new Student("Gotem", "Ella"));
}
// 处理 GET 请求,路径为 "/students"
@GetMapping("/students")
public List<Student> getStudents() {
return students;
}
}
在这个控制器中,我们创建了一个静态的students列表,并在构造函数中初始化了一些学生数据。getStudents方法映射到/students路径,当收到GET请求时,它会返回整个学生列表。
测试获取所有学生的API ✅
在测试API之前,请确保停止占用8080端口的任何其他应用程序进程,否则会导致端口冲突,无法启动当前应用。
启动Spring Boot应用程序后,你可以使用浏览器或API测试工具(如Postman)访问以下URL来测试第一个端点:

http://localhost:8080/students
该请求将返回一个包含所有学生对象的JSON数组。

实现根据路径变量获取学生 🔍
我们已经实现了获取所有学生的服务,现在我们来创建一个更具体的服务,根据学生的姓名来检索单个学生。
我们将使用@PathVariable注解来实现这个功能。在StudentController类中添加以下方法:
import org.springframework.web.bind.annotation.PathVariable;
// ... 其他导入
@RestController
public class StudentController {
// ... 之前的代码(students列表和构造函数)
// 处理 GET 请求,路径为 "/student/{firstName}/{lastName}"
@GetMapping("/student/{firstName}/{lastName}")
public Student getStudentByPath(
@PathVariable("firstName") String firstName,
@PathVariable("lastName") String lastName) {
// 目前直接返回一个新的Student对象,实际应用中通常会从数据库或列表中查询
return new Student(firstName, lastName);
}
}
这个方法映射到/student/{firstName}/{lastName}路径。{firstName}和{lastName}是路径中的占位符。当请求到达时,Spring会自动将路径中的值绑定到方法参数firstName和lastName上。
注意:当前实现为了演示,直接根据传入的参数创建并返回一个新的Student对象。在实际应用中,你通常会根据这些参数从students列表或数据库中查询并返回匹配的学生。
测试路径变量API ✅
重启应用程序后,你可以通过访问以下格式的URL来测试这个新的端点:
http://localhost:8080/student/Goham/Bla
将Goham和Bla替换为任何名和姓。API将返回一个包含这些信息的JSON对象,例如:
{
"firstName": "Goham",
"lastName": "Bla"
}

这演示了路径变量如何工作:它们允许你将数据作为URL路径的一部分进行传递。
总结 🎯
本节课中我们一起学习了如何实现一个基本的GET服务来检索资源。
- 创建模型:我们首先创建了一个
Student类作为数据模型。 - 创建控制器:接着,我们创建了一个
StudentController,并使用@RestController进行标记。 - 实现GET端点:
- 我们实现了
/students端点,用于返回所有学生的列表。 - 我们实现了
/student/{firstName}/{lastName}端点,使用@PathVariable来接收参数并返回特定的学生信息。
- 我们实现了
- 测试:我们学习了如何启动应用并测试这些API端点。

目前,根据路径变量获取学生的端点只是简单地回显输入的数据。在后续课程中,我们将学习如何使用请求参数(Request Parameter)来实现类似功能,并添加逻辑来从列表中实际查找学生。

下节预告:在下一节课中,我们将探讨如何使用@RequestParam注解来实现相同的查询功能,并完善我们的服务逻辑。敬请关注!
054:实现POST方法创建用户资源
在本节课中,我们将学习如何在Spring Boot应用中实现一个POST方法,用于创建新的学生资源。我们还将学习如何通过查询参数传递数据,并使用Postman工具测试我们的API。
概述
上一节我们介绍了如何使用GET方法和路径变量获取资源。本节中,我们来看看如何实现POST方法来创建新的学生资源,并理解查询参数与请求体的区别。
实现GET方法并传递查询参数
首先,我们回顾一下在StudentController中如何通过GET方法并利用查询参数返回学生信息。
以下是实现代码:

@GetMapping("/student")
public Student getStudentWithQueryParam(@RequestParam(name = "firstName") String firstName,
@RequestParam(name = "lastName") String lastName) {
return new Student(firstName, lastName);
}

@GetMapping: 注解表明这是一个处理HTTP GET请求的方法。@RequestParam: 注解用于从请求URL的查询字符串中获取参数值。- 方法逻辑: 该方法接收
firstName和lastName两个查询参数,并使用它们创建一个新的Student对象返回。
如何传递查询参数
理解了代码后,我们来看看如何在浏览器中测试这个带有查询参数的GET请求。
以下是测试步骤:
- 启动Spring Boot应用程序。
- 在浏览器地址栏中构造URL。格式为:
基础URL?参数1=值1&参数2=值2。 - 例如,访问
http://localhost:8080/student?firstName=John&lastName=Smith。 - 浏览器将显示一个包含
John和Smith的JSON对象,表明查询参数已成功传递并处理。
实现POST方法创建资源
接下来,我们实现核心功能:创建一个处理POST请求的方法来新增学生。
以下是实现代码:
@PostMapping("/student")
public void addStudent(@RequestBody Student student) {
// 此处通常会有将student对象保存到数据库的逻辑
// 例如:studentService.save(student);
System.out.println("学生已添加: " + student.getFirstName() + " " + student.getLastName());
}
@PostMapping: 注解表明这是一个处理HTTP POST请求的方法。@RequestBody: 注解用于将HTTP请求体中的JSON数据自动绑定到Student对象上。- 方法逻辑: 该方法接收一个
Student对象作为参数。在实际应用中,你会在这里调用服务层方法将学生数据保存到数据库。本例中,我们仅打印一条日志。
使用Postman测试POST请求
由于浏览器默认发送GET请求,我们需要使用Postman这类API测试工具来发送POST请求。
以下是测试步骤:
- 确保应用程序正在运行。
- 打开Postman,创建一个新的请求。
- 将请求方法设置为 POST。
- 输入URL:
http://localhost:8080/student。 - 在 Headers 选项卡中,添加一个键值对:
Content-Type: application/json。 - 切换到 Body 选项卡,选择 raw 和 JSON,然后输入要创建的学生数据:
{ "firstName": "Roger", "lastName": "Lee" } - 点击 Send 按钮发送请求。
- 如果看到状态码 200 OK,表示请求成功。控制台会打印出添加的学生信息。
- 为了验证,可以再次使用浏览器访问
http://localhost:8080/students(假设你有这个列表接口),检查新学生“Roger Lee”是否已在列表中。
总结

本节课中我们一起学习了Spring Boot中POST方法的实现。我们首先回顾了如何通过@RequestParam接收查询参数,然后重点实现了使用@PostMapping和@RequestBody来接收JSON请求体并创建新的学生资源。最后,我们使用Postman工具成功测试了POST API。理解如何创建和处理POST请求是构建交互式Web应用的基础。
055:实现PUT方法更新用户资源 🛠️

在本节课中,我们将学习如何使用Spring Boot的@PutMapping注解来实现一个REST API的PUT方法,用于更新学生资源。我们将通过修改控制器中的特定端点,使其能够接收路径变量和请求体,从而更新现有学生的信息。

更新学生资源的实现步骤
上一节我们介绍了如何获取学生资源,本节中我们来看看如何更新一个已有的学生信息。我们将修改StudentController,添加一个处理PUT请求的方法。
首先,在控制器中添加@PutMapping注解,并指定相应的URL路径。这个方法将接收一个路径变量(学生名)和一个包含新学生信息的请求体。
@PutMapping("/student/{firstName}")
public void updateStudent(@PathVariable String firstName, @RequestBody Student student) {
// 更新逻辑将在这里实现
}
以下是更新逻辑的具体步骤:
- 遍历学生列表:使用
for-each循环遍历现有的学生列表。 - 匹配学生:检查列表中是否有学生的
firstName属性与路径变量{firstName}的值相匹配。 - 更新信息:如果找到匹配的学生,则使用请求体
@RequestBody Student student中提供的新数据,更新该学生的firstName和lastName属性。 - 无操作:如果没有找到匹配的学生,则不执行任何更新操作。
核心的更新逻辑可以用以下代码块表示:
for (Student s : studentsList) {
if (s.getFirstName().equals(firstName)) {
s.setFirstName(student.getFirstName());
s.setLastName(student.getLastName());
break; // 找到并更新后退出循环
}
}
测试更新功能

现在,让我们运行应用程序并测试更新功能是否正常工作。

首先,通过GET请求获取所有学生的当前列表,确认目标学生(例如名为“King”的学生)存在。
接下来,构造一个PUT请求。这个请求需要:
- URL:指向特定学生,例如
/student/King。 - 请求体 (Body):一个JSON对象,包含要更新的新信息,例如将名字改为“Kashmir”,姓氏改为“Goodman”。
{
"firstName": "Kashmir",
"lastName": "Goodman"
}
发送PUT请求后,如果收到状态码 200 OK,则表示更新成功。
最后,再次发送GET请求获取学生列表。你可以验证名为“King”的学生信息已被成功更新为“Kashmir Goodman”。

本节课中我们一起学习了如何实现REST API的PUT方法。我们创建了一个端点,它通过路径变量定位资源,并通过请求体接收更新数据,最终完成了对学生信息的修改。这是构建完整CRUD功能的关键一步。
056:实现删除用户资源的DELETE方法 🗑️

在本节课中,我们将学习如何为“学生”资源实现一个DELETE方法。我们将创建一个REST API端点,允许根据学生的“名字”从列表中删除对应的学生对象。

概述

上一节我们介绍了如何创建和获取学生资源。本节中,我们来看看如何实现删除操作。我们将创建一个使用@DeleteMapping注解的端点,它接收一个路径变量(学生名字),并从内存中的学生列表里移除匹配的对象。
实现DELETE方法
首先,我们需要在控制器类中创建一个新的方法。这个方法将使用@DeleteMapping注解,并指定一个路径,例如/student/{firstName},其中{firstName}是一个路径变量。
以下是该方法的实现步骤:
- 定义方法签名:方法返回类型为
void,并接收一个String类型的参数,该参数使用@PathVariable注解绑定到URL中的{firstName}。 - 遍历学生列表:我们需要遍历存储所有学生的列表。
- 匹配并删除:在遍历过程中,检查每个学生对象的
firstName属性是否与传入的路径变量匹配。如果匹配,则将该对象从列表中移除。
以下是具体的代码实现:
@DeleteMapping(“/student/{firstName}”)
public void deleteStudent(@PathVariable String firstName) {
for (Student student : students) {
if (student.getFirstName().equals(firstName)) {
students.remove(student);
break; // 找到并删除后即可退出循环
}
}
}
代码解释:
@DeleteMapping(“/student/{firstName}”):这定义了HTTP DELETE请求的端点URL模式。@PathVariable String firstName:将URL路径中的{firstName}部分的值注入到方法参数firstName中。- 循环
for (Student student : students):遍历名为students的列表(假设这是一个在类中已定义的List<Student>)。 student.getFirstName().equals(firstName):检查当前遍历到的学生名字是否与要删除的名字一致。students.remove(student):从列表中移除这个学生对象。break:找到并删除目标对象后,立即跳出循环,提高效率。
测试删除操作
理论实现后,我们需要进行测试来验证功能是否正常工作。
以下是测试步骤:
- 重启Spring Boot应用程序。
- 打开API测试工具(如Postman)。
- 首先,使用一个GET请求查看当前所有学生,确认目标学生(例如“Kashfi”)存在。
- 然后,构造一个DELETE请求。请求的URL应为:
http://localhost:8080/student/Kashfi。 - 发送请求后,应收到状态码为
200 OK的响应,表示删除成功。 - 最后,再次发送GET请求获取所有学生列表。此时,列表中应不再包含名为“Kashfi”的学生对象,从而确认删除操作已生效。
总结
本节课中我们一起学习了如何实现一个简单的DELETE API。我们创建了一个能够根据学生名字从内存列表中删除对应资源的方法,并通过测试验证了其功能。这是对资源进行“删”操作的基础。

在下一个模块中,我们将探讨如何将这些操作(增、删、改、查)与真实的数据库集成,让数据持久化存储。
Java全栈开发:57:Hibernate入门指南
在本节课中,我们将学习Hibernate,这是一个用于Java的对象关系映射工具,它能简化与数据库交互的过程。
我们将理解Hibernate架构的基础知识、配置方法,以及使用Hibernate执行数据库操作的不同方式。

🏗️ Hibernate及其特性介绍
首先,我们来介绍Hibernate及其核心特性。Hibernate是一个开源的对象关系映射框架,它允许开发者以面向对象的方式来操作关系型数据库。其主要特性包括:
- 简化数据库操作:开发者无需编写复杂的SQL语句。
- 对象关系映射:将Java类与数据库表、对象属性与表字段进行映射。
- 事务管理:提供简单的事务管理接口。
- 缓存机制:支持一级和二级缓存,提升应用性能。
- 数据库无关性:通过配置即可切换不同的数据库。
🧱 Hibernate架构概览
上一节我们介绍了Hibernate的特性,本节中我们来看看它的核心架构。理解架构有助于我们更好地使用Hibernate。Hibernate架构主要包括以下几个关键组件:
- SessionFactory:这是一个重量级对象,是
Session的工厂。通常一个应用只有一个SessionFactory实例,它在应用启动时创建。 - Session:这是一个轻量级对象,代表与数据库的一次会话。它提供了保存、获取、删除对象等操作方法。Session对象不是线程安全的。
- Transaction:代表一次原子性的数据库操作单元。它封装了底层的事务管理。
- ConnectionProvider:管理数据库连接池。
- TransactionFactory:工厂类,用于创建
Transaction对象。
这些组件协同工作,使得Hibernate能够高效地管理数据库连接和操作。
⚙️ Hibernate配置方法
了解了架构之后,我们需要知道如何配置Hibernate以连接到数据库。Hibernate可以通过两种主要方式进行配置:XML文件或注解。
以下是配置Hibernate的核心步骤:
- 添加依赖:在项目构建文件(如Maven的
pom.xml)中添加Hibernate依赖。 - 创建配置文件:通常是一个名为
hibernate.cfg.xml的文件,用于配置数据库连接信息、映射关系等。<hibernate-configuration> <session-factory> <property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/testdb</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">password</property> <property name="hibernate.dialect">org.hibernate.dialect.MySQL8Dialect</property> <!-- 指定映射文件或带注解的实体类 --> </session-factory> </hibernate-configuration> - 建立映射关系:使用XML映射文件(
.hbm.xml)或在Java实体类上使用注解(如@Entity,@Table,@Id)来定义对象与表的映射。
📝 使用Hibernate执行CRUD操作
配置完成后,我们就可以使用Hibernate来执行核心的数据库操作了,即CRUD(创建、读取、更新、删除)。Hibernate的Session接口提供了相应的方法。
以下是使用Hibernate进行CRUD操作的基本流程:
- 创建对象并保存:使用
session.save(Object)方法将一个新对象持久化到数据库。 - 读取对象:使用
session.get(Class, id)或session.load(Class, id)方法根据主键从数据库获取对象。 - 更新对象:先获取对象,修改其属性后,使用
session.update(Object)方法更新,或让Hibernate在事务提交时自动检测更改(脏检查)。 - 删除对象:使用
session.delete(Object)方法从数据库中移除一个对象。
所有这些操作都应在事务边界内进行,以确保数据的一致性。
🎬 操作演示与总结
最后,我们将通过一个简单的演示,展示如何创建一个实体类,配置Hibernate,并完成一套完整的CRUD操作流程。这将帮助你直观地理解Hibernate如何简化数据库交互。
本节课中我们一起学习了Hibernate的基础知识。我们从Hibernate的特性和架构入手,了解了其核心组件。接着,我们学习了通过XML和注解两种方式来配置Hibernate。最后,我们详细讲解了如何使用Hibernate的Session API来执行创建、读取、更新和删除等数据库操作。掌握这些内容,你就能在Java应用中利用Hibernate更高效、更面向对象地处理数据持久化任务了。
Java全栈开发:58:ORM框架入门 🚀

在本节课中,我们将学习对象关系映射(ORM)框架的基本概念。ORM是一种编程技术,用于在关系型数据库和面向对象编程语言之间转换数据。通过使用ORM,业务逻辑能够以实体(即Java对象)的形式访问和操作数据。ORM通过处理数据库交互的各个方面,例如事务管理、脏数据检查、延迟关联、数据抓取和优化功能,帮助我们加速整体开发过程。

什么是ORM? 🤔
ORM,即对象关系映射,是一种编程技术。它的核心作用是在关系型数据库和面向对象编程语言之间进行数据转换。这使得业务逻辑能够以实体(即Java对象)的形式访问和操作数据。
核心公式:Java对象 <-> ORM框架 <-> 数据库表
ORM如何工作? ⚙️
ORM框架通过提供一系列服务来简化与关系型数据库的交互。它不仅限于Java应用,也可用于其他技术栈,如.NET。
以下是ORM工作的两个主要方向:
- 从对象到数据库:当你编写Java实体类或POJO类时,ORM框架会将这些类转换为数据库中的表。
- 从数据库到对象:当你已有数据库表时,ORM框架可以将这些表转换为Java对象,供你的Java应用程序使用。
ORM的优势 💪
ORM通过处理底层数据库交互的复杂性,为开发者带来了显著优势:
- 提高开发效率:开发者可以更专注于业务逻辑,而非繁琐的SQL语句和数据库连接管理。
- 简化数据操作:以操作对象的方式操作数据,代码更直观、更符合面向对象思维。
- 提供高级功能:ORM框架通常内置了事务管理、缓存、延迟加载等优化功能。

总结与展望 📚
本节课我们一起学习了ORM框架的基本概念、工作原理及其优势。ORM作为连接对象世界与关系数据库的桥梁,是现代企业级应用开发中的重要工具。

在接下来的课程中,我们将借助Hibernate框架,通过实践来深入了解ORM的具体实现。敬请期待,我们下节课再见!
Java全栈开发:59:Hibernate入门指南 🚀

在本节课中,我们将学习Hibernate框架的基础知识。Hibernate是一个强大的Java对象关系映射工具,它简化了Java应用程序与数据库之间的交互。我们将探讨它的核心概念、工作原理以及主要特性。

Hibernate是一个基于Java的ORM工具。它提供了一个框架,用于在面向对象的模型和关系型数据库的表之间进行映射,反之亦然。
Hibernate由Gavin King于2001年启动,作为EJB2风格实体Bean的替代方案。它允许将面向对象模型映射到关系数据库,并且是Java中最流行的ORM实现之一,也是总体上最受欢迎的Java框架之一。
它位于Java对象和数据库服务器之间。正如我在之前的课程中演示的,这是你的数据访问层,这是你的关系型数据库。无论你在Java实体框架中使用何种Java持久化API,Hibernate都会位于中间。
在内部,Hibernate基于JDBC架构工作,这是一种与数据库通信的传统方法。作为一个ORM工具,Hibernate拥有自己的语言,称为HQL。
Hibernate具有以下主要特性:
- 它没有数据库锁定。
- 它提供编译时异常转换的事务管理。
- 它支持外连接抓取和延迟初始化。
- 它支持Java EE集成以及JTA和JCA包。
- 它也支持注解和分页。
- 它提供了智能查询生成器和查询条件支持。
- 它为SessionFactory和EntityManager对象或提供者提供了更好的支持。
Hibernate支持不同类型的关系,例如一对一、一对多、多对多和多对一。它提供了诸如聚合和组合的关系,即“是一个”关系和“有一个”关系。
Hibernate还允许你将元素存储为列表、集合和映射的集合,正如我们在数据结构中讨论过的。
必须实现主键和复合键的生成。在Hibernate中,非常重要的一点是你的实体应该至少有一个主键。
它提供了继承的概念,其中一个实体可以继承到另一个实体。它还提供了两层缓存,即第一层和第二层缓存。通过注解和配置或过滤器,它提供了验证和全文搜索集成,你可以根据需求添加这些功能。

本节课中,我们一起学习了Hibernate的基本介绍、历史背景、核心架构以及其主要特性。Hibernate作为一个强大的ORM框架,极大地简化了Java应用与数据库的交互。在接下来的课程中,我们将继续深入学习Hibernate的具体实现。
060:Hibernate架构详解 🏗️

在本节课中,我们将学习Hibernate框架的核心架构。我们将了解其分层结构、关键组件以及数据操作的完整流程。理解这些概念是高效使用Hibernate进行数据库操作的基础。
架构分层

Hibernate架构主要分为四个层次。
以下是这四个层次的具体说明:

- Java应用层:这是开发者编写业务逻辑和创建持久化对象的层面。
- Hibernate框架层:这是Hibernate的核心,提供了配置、会话管理、事务处理等功能。
- 后端API层:Hibernate在此层与各种Java API(如JDBC)交互,以执行底层数据库操作。
- 数据库层:这是实际存储数据的数据库系统。
这种分层设计不仅适用于实时应用开发,也有助于实现三层架构。
核心组件与流程
上一节我们介绍了Hibernate的宏观分层,本节中我们来看看其内部的核心组件和数据流转过程。
Hibernate框架使用了多种对象,例如SessionFactory、Session、Transaction,同时也整合了现有的Java API,如JDBC、JTA、JNDI。不过,在实际项目中,最常使用的是Java数据库连接(JDBC)。
下图展示了Hibernate的高层架构和完整流程:


其工作流程可以概括为:首先,你创建的任何Java应用都需要以Java实体(Entity)的形式定义持久化对象。接着,配置Hibernate属性并创建SessionFactory对象。通过SessionFactory开启一个Session,在每个Session内可以创建多个事务(Transaction)。在事务中,你可以运行所需的查询(Query)或条件(Criteria)来完成操作。请确保你的实体类拥有一个主键(Primary Key)。至于后端API的选择,正如之前提到的,你可以使用JNDI、JTA或JDBC,但在当今的实时项目中,JDBC是最常用的,我也推荐使用它。
详细工作流
了解了核心组件后,我们来详细拆解Hibernate的具体工作步骤。
以下是Hibernate的详细工作流程:
- 创建模型类:首先,创建你希望进行持久化操作的模型类(即实体类)。
- 配置并构建SessionFactory:配置Hibernate,并基于配置构建SessionFactory对象。SessionFactory会加载数据库配置。
- 开启Session:从构建好的SessionFactory中打开一个Session。
- 开启事务:在打开的Session内部,开启一个事务。
- 执行查询:在事务中编写并执行查询。你可以选择使用SQL查询、以对象形式进行通信、编写Criteria查询或HQL查询。
- 提交或回滚事务:查询执行完毕后,如果没有错误,则提交(commit)事务以保存更改;如果出现任何错误,则回滚(rollback)事务,撤销所有更改。
- 关闭资源:最后,关闭Session和SessionFactory,完成整个请求,以便其他请求可以使用这些资源。
这就是Hibernate请求完成的完整过程。

关键接口解析
在详细工作流中,我们提到了几个关键接口,现在让我们深入理解它们各自的作用。
- SessionFactory接口:这是一个重量级对象,它是Session的工厂,并且可以持有可选的二级缓存。
- Session接口:这是一个轻量级对象,代表与数据库的一次会话。它持有强制性的一级缓存,我们在此开启事务并编写查询。
- Transaction接口:此接口负责管理事务,遵循ACID原则(原子性、一致性、隔离性、持久性)。事务在此开始,并通过提交或回滚来完成。
- TransactionFactory:这是一个可选的、用于创建Transaction对象的工厂。
- ConnectionProvider:连接提供者,管理数据库连接池。
为了完成请求并释放资源供他人使用,我们需要关闭Session和SessionFactory。
总结

本节课中,我们一起学习了Hibernate的核心架构。我们首先了解了其四层结构,然后剖析了从创建实体到提交事务的完整工作流程,最后详细介绍了SessionFactory、Session等关键接口的作用。理解这些架构知识,将帮助你更好地驾驭Hibernate,构建高效、稳定的数据访问层。

这就是Hibernate架构的工作原理。我们下节课再见。
061:使用XML或注解进行Hibernate配置 🛠️

在本节课中,我们将学习如何使用XML文件来创建Hibernate的关联映射。关联映射是Hibernate的核心,它定义了实体类与数据库表之间的关系。

概述
Hibernate中的关联映射可以是单向的或双向的,主要类型包括:一对一、一对多、多对一和多对多。这些映射可以通过Hibernate的XML配置文件或注解来实现。本节课我们将重点介绍使用XML文件进行配置的方法。
关联映射的类型
关联映射是连接Java对象与数据库表的关键。根据实体类之间的关系,映射可以分为以下几种类型:
- 一对一:一个实体实例对应另一个实体实例。
- 一对多:一个实体实例对应多个其他实体实例。
- 多对一:多个实体实例对应另一个实体实例。
- 多对多:多个实体实例与多个其他实体实例相互关联。

使用XML文件进行配置

上一节我们介绍了关联映射的基本概念,本节中我们来看看如何使用XML文件具体配置这些映射。
你需要根据想要建立的关联类型,在XML配置文件中使用相应的节点。配置文件通常命名为 类名.hbm.xml。
以下是配置时需要使用的关键节点:
- 一对一 映射使用
<one-to-one>节点。 - 一对多 映射使用
<one-to-many>节点。 - 多对多 映射使用
<many-to-many>节点。 - 多对一 映射使用
<many-to-one>节点。
配置步骤

了解了配置节点后,接下来是具体的操作步骤。你需要将配置好的XML文件放置在项目的正确位置。

- 创建对应的
.hbm.xml配置文件。 - 将所需的JAR文件或Maven依赖添加到项目中。
- 将XML配置文件放在
resources资源包的正确目录下。
完成以上步骤后,配置就基本准备好了。
总结


本节课中我们一起学习了Hibernate关联映射的配置方法。我们了解到映射可以通过XML或注解完成,并重点介绍了使用XML文件配置一对一、一对多、多对多和多对一关联的具体节点。最后,我们回顾了将配置文件集成到项目中的基本步骤。关于Hibernate映射在Spring Security中的示例,可以参考之前的演示内容。
Java全栈开发:专项课程(下):第62课:本课内容概述
在本节课中,我们将学习Java持久化API。我们将了解JPA的基础知识、其架构、如何使用Hibernate注解进行对象关系映射,以及如何执行基本的数据库操作。
Java全栈开发:专项课程(下):第62课:JPA基础与架构
在Java编程中,当需要将数据存储到应用程序生命周期之外时,就需要持久化存储。
Java持久化API是Java应用程序中进行对象关系映射时广泛使用的标准。
上一节我们介绍了本课的学习目标,本节中我们来看看JPA的基础概念和架构。
JPA是一个ORM框架的规范,它允许你将Java对象映射到关系型数据库。
以下是JPA架构中的不同层次及其协作方式:
- 持久化对象层:由实体类组成,这些类使用注解映射到数据库表。
- 实体管理器层:作为与持久化上下文交互的主要接口,用于管理实体的生命周期。
- 持久化提供者层:由JPA的实现框架(如Hibernate)构成,负责底层的数据库操作。
- 数据库层:最终存储数据的关系型数据库。
这些层次共同工作,将面向对象的Java代码与关系型数据库连接起来。

Java全栈开发:专项课程(下):第62课:Hibernate注解与JPA配置
了解了JPA的整体架构后,本节我们将聚焦于具体的实现工具——Hibernate,以及如何使用它进行配置和映射。
Hibernate是最流行的JPA提供商之一,它提供了一套丰富的注解,用于将Java对象映射到关系型数据库。
你将学习如何使用这些注解来映射Java对象到数据库表,以及如何配置JPA以与Hibernate协同工作。
以下是核心的Hibernate/JPA注解示例:
@Entity:将一个Java类声明为JPA实体,对应数据库中的一张表。@Id:指定实体的主键字段。@GeneratedValue:配置主键的生成策略,例如自增。@Column:将实体字段映射到数据库表的特定列,可指定列名等属性。
一个简单的实体类代码示例如下:
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "product_name")
private String name;
// 构造函数、Getter和Setter方法
}
JPA的配置通常通过一个名为persistence.xml的文件完成,该文件定义了数据库连接、提供商等持久化单元信息。
Java全栈开发:专项课程(下):第62课:使用JPA执行数据库操作
掌握了对象映射和配置之后,本节我们来看看如何使用JPA对数据库进行增删改查等核心操作。
最后,你将学习如何使用JPA执行操作,例如持久化、更新和删除数据库中的数据。
这些操作主要通过EntityManager接口来完成。以下是关键的操作方法:
- 持久化数据:使用
entityManager.persist(object)方法将一个新的实体对象插入数据库。 - 更新数据:通过
entityManager.merge(object)方法更新一个已存在实体的状态。 - 删除数据:调用
entityManager.remove(object)方法从数据库中删除一个实体。 - 查询数据:使用JPQL(Java持久化查询语言)或Criteria API来查询数据,例如
entityManager.createQuery("SELECT p FROM Product p", Product.class).getResultList()。
Java全栈开发:专项课程(下):第62课:课程总结
本节课中,我们一起学习了Java持久化API的核心内容。
我们从JPA解决数据持久化需求入手,了解了其作为ORM规范的基本架构。接着,我们深入探讨了如何使用Hibernate提供的注解(如@Entity, @Id)来定义实体映射,并介绍了JPA的配置文件。最后,我们学习了如何通过EntityManager执行数据的持久化、更新、删除和查询等基本操作。
通过本课的学习,你应该对如何使用JPA在Java应用程序中管理数据库持久化有了一个初步而完整的认识。
063:理解持久化存储的必要性 💾


在本节课中,我们将探讨为什么在现代应用程序开发中,持久化存储是一个不可或缺的概念。我们将了解其定义、优势以及Java中用于实现持久化的关键技术。
持久化存储是指任何在设备断电后仍能保留数据的存储设备。它通常也被称为非易失性存储介质。常见的类型包括DVD等光学介质,以及硬盘驱动器和磁带等磁性介质。这就是持久化存储的基本概念。
持久化存储的优势
理解了持久化存储是什么之后,我们来看看它能为应用程序带来哪些具体的好处。以下是持久化存储的几个关键优势:
- 数据易于维护:数据被系统地存储,便于管理和维护。
- 提供安全层:可以对存储的数据实施访问控制和加密等安全措施。
- 处理大型容器的灵活性:能够高效地存储和处理海量数据。
- 强大的可移植性:数据可以相对容易地迁移到不同的系统或环境中。
- 成本效益:长期来看,使用专门的存储方案比依赖临时存储更经济。
Java持久化API(JPA)
为了在Java应用程序中实现上述优势,我们需要一个标准化的方式来处理持久化。这就是Java持久化API(JPA)的作用。JPA,全称Java Persistence API,它提供了一套通用的规范和功能给对象关系映射(ORM)工具。
本质上,JPA是一个规范,它促进了对象关系映射,帮助我们在Java应用程序中管理关系型数据。它允许开发者直接操作对象,而不是编写复杂的SQL语句。其核心价值在于可移植性:未来如果我们想将应用程序从一个ORM框架(如Hibernate)切换到另一个,可以相对容易地实现,因为底层的持久化API(JPA)保持不变,我们只需要更改由特定ORM框架实现的配置即可。


总结:本节课我们一起学习了持久化存储的必要性。我们明确了持久化存储是一种断电后数据不丢失的存储方式,并列举了它的主要优势,如易于维护、安全性高和可移植性强。最后,我们介绍了Java Persistence API(JPA),它作为一套标准规范,允许我们通过操作对象来管理关系型数据,并保证了应用程序在不同ORM实现间的可移植性。在接下来的课程中,我们将继续深入学***A的更多细节。
Java全栈开发:64:什么是JPA?🤔
在本节课中,我们将学习Java持久化API(JPA)。JPA是Java平台的一个规范,用于在Java对象和关系型数据库之间进行数据持久化操作。我们将了解它的基本概念、工作原理以及优势。
上一节我们介绍了课程背景,本节中我们来看看JPA的核心定义。
JPA是Java Persistence API的缩写。它是一个Java规范,用于在Java对象和关系型数据库表之间持久化数据。正如我们讨论过的Hibernate一样,数据以对象形式存储和通信,但在关系型数据库中,数据以表的形式存储。JPA使得你的实体或对象能够以对象的形式与数据库表进行交互。
例如,Employee在Java中是一个类,但在关系型数据库中它是一张表。因此,JPA充当了对象关系模型和关系数据库系统之间的桥梁。
由于JPA只是一个规范,它本身不执行任何操作。它主要负责在数据库表和Java对象之间进行通信。像Hibernate、TopLink、EclipseLink这样的ORM工具实现了JPA规范,从而提供了Java持久化的具体功能。


了解了JPA的定义后,接下来我们看看在现实应用中使用JPA有哪些优势。
以下是使用JPA的主要优点:
- 降低代码复杂度:通过使用注解(如
@Entity、@Table)来映射实体和表,代码编写变得非常简单。 - 提高生产力:可以轻松地使用一个Java对象执行多种数据库操作。
- 提升性能:JPA实现通常包含缓存和优化机制。
- 数据独立性:如果需要更改数据库设置,只需修改一次配置。JPA会处理所有相关的配置变更。
- 更好的错误处理:在整个Java持久化操作中,可以利用自定义异常或JPA规范中预定义的异常进行更有效的错误处理。

本节课中,我们一起学习了Java持久化API(JPA)。我们了解到JPA是一个连接Java对象世界和关系型数据库表的规范,它本身不直接操作,而是由Hibernate等ORM工具实现。我们还探讨了JPA在降低代码复杂度、提高开发效率和维护性方面的主要优势。希望这个简要的介绍能帮助你理解JPA的基本概念。
065:JPA架构详解 🏗️


在本节课中,我们将学习Java持久化API的架构,包括其核心类、接口以及它们之间的关系。理解这些组件是掌握JPA如何将Java对象映射到关系型数据库的基础。
上一节我们介绍了JPA的基本概念,本节中我们来看看JPA规范中定义的核心类和接口架构。
Java持久化API是用于将Java对象映射到关系型数据库的Java标准。这种将Java对象与数据库表相互映射的过程,被称为对象关系映射。JPA是实现ORM的一种可能途径,开发者可以通过它来映射、存储、更新和检索数据库与Java对象之间的数据。
一个重要的点是,JPA规范既可用于Java企业版应用,也可用于标准版应用。JPA规范有多个实现,例如流行的Hibernate、EclipseLink和Apache OpenJPA等。JPA旨在将业务实体作为关系实体存储,并展示了如何将普通的旧Java对象定义为实体,以及如何管理实体之间的关系。
下图展示了JPA的类级别架构:

以下是架构中涉及的核心组件:
- EntityManagerFactory:这是一个工厂类,用于创建和管理多个EntityManager实例。
- EntityManager:这是一个接口,负责管理对象的持久化操作。它本身也充当查询实例的工厂。
- Entity:实体是持久化对象,在数据库中存储为记录。
- EntityTransaction:它与EntityManager是一对一的关系。每个EntityManager的操作都由EntityTransaction类来维护。
- Persistence:这个类包含一些静态方法,用于获取EntityManagerFactory实例。
- Query:此接口由每个JPA供应商实现,用于获取符合特定条件的关系对象。

了解了各个组件后,我们来看看它们在这个架构中的相互关系。
- EntityManagerFactory 与 EntityManager 的关系是一对多。EntityManagerFactory是EntityManager实例的工厂类。
- EntityManager 与 EntityTransaction 的关系是一对一。每个EntityManager的操作都对应一个EntityTransaction。
- EntityManager 与 Query 的关系是一对多。因为一个EntityManager实例可以执行多个查询。
- EntityManager 与 Entity 的关系是一对多。因为一个EntityManager实例可以管理多个实体。
如何创建EntityManager实例,以及如何与事务和查询进行交互,在你进入实际实现阶段时会变得更加清晰。

本节课中我们一起学习了JPA的核心架构,包括EntityManagerFactory、EntityManager、Entity、EntityTransaction等关键组件及其相互关系。下一节,我们将通过实践来更深入地理解这些概念和架构。
Java全栈开发:专项课程(下):05:Hibernate JPA 注解详解 🏷️

在本节课中,我们将学习 Hibernate JPA 中的核心注解。这些注解是连接 Java 对象与关系型数据库表的桥梁,对于构建数据持久层至关重要。我们将逐一介绍每个注解的作用和用法。
上一节我们了解了 JPA 的基本概念,本节中我们来看看具体有哪些注解可以帮助我们定义实体和映射关系。
@Entity 注解用于标记一个 Java 类,表明这个类是一个 JPA 实体,需要与数据库进行映射。被 @Entity 注解的类不需要实现任何接口或继承特定的超类。

默认情况下,每个实体类会映射到数据库默认模式(schema)中一个同名的表。@Table 注解允许我们自定义这种映射关系。
以下是 @Table 注解可以配置的主要属性:
- name:指定映射的数据库表名。
- schema:指定表所在的数据库模式。
- catalog:指定表所在的数据库目录。
JPA 和 Hibernate 要求为每个实体至少指定一个主键属性。@Id 注解用于标记实体类中作为主键的字段。
当主键的值需要由数据库自动生成时(例如使用序列或自增列),我们需要使用 @GeneratedValue 注解。
以下是 @GeneratedValue 的两种常用生成策略:
- GenerationType.SEQUENCE:使用数据库序列来生成主键值。
- GenerationType.IDENTITY:使用数据库的自增列来生成主键值。
在关系型数据库中,表与表之间通过外键关联。JPA 提供了一系列注解来定义这些关联关系。
以下是四种核心的关系映射注解:
- @OneToOne:定义一对一关联。
- @OneToMany:定义一对多关联。
- @ManyToOne:定义多对一关联。
- @ManyToMany:定义多对多关联。
这些注解使得我们的对象模型能够更精确地反映数据库中的表关系。
除了上述核心注解,JPA 还提供了一些其他有用的注解来处理特定场景。
@Lob 注解用于映射大型对象字段,例如很长的文本(String)或二进制数据(byte[])。关系型数据库通常对普通字段有长度限制,而 @Lob 类型则几乎没有限制。数据库会将其映射为 BLOB(二进制大对象)或 CLOB(字符大对象)类型。
如果你在实体中使用了 java.util.Date 或 java.util.Calendar 类型的属性,需要使用 @Temporal 注解来指定如何将其持久化到数据库。
使用 @Temporal 注解时,你需要指定 TemporalType,例如:
TemporalType.DATE:只存储日期部分。TemporalType.TIME:只存储时间部分。TemporalType.TIMESTAMP:存储日期和时间。
@Transient 注解用于标记一个属性,表明该属性不需要持久化到数据库中。它仅存在于 Java 对象层面。
在定义关联关系(如 @ManyToOne)时,JPA 会自动创建一个外键列。如果你想自定义此外键列的名称或其他属性,可以使用 @JoinColumn 注解进行手动配置。

本节课中我们一起学习了 Hibernate JPA 的核心注解。我们从定义实体本身的 @Entity 和 @Table 开始,接着学习了主键注解 @Id 和 @GeneratedValue,然后探讨了四种关系映射注解,最后介绍了一些处理特殊数据类型的注解,如 @Lob、@Temporal、@Transient 和 @JoinColumn。理解并熟练运用这些注解,是使用 JPA 进行高效、准确数据持久化开发的基础。在后续的实际编码练习中,这些概念会变得更加清晰。
067:JPA配置文件详解 📄
在本节课中,我们将学习Java持久化API中的一个核心配置文件——persistence.xml。这个文件在JPA应用中扮演着至关重要的角色,它负责管理所有的持久化配置。我们将了解它的作用、标准位置以及如何在一个Spring Boot项目中正确配置它。
上一节我们介绍了JPA的基本概念,本节中我们来看看如何通过配置文件来具体管理这些持久化设置。

配置文件的位置与作用

persistence.xml文件是JPA的核心配置文件。它定义了持久化上下文,而持久化上下文是一组实体实例的集合。在这个上下文中,每个持久化实体都有一个唯一的标识和对应的实体实例,并由实体管理器(Entity Manager)进行管理。
该文件通常位于应用程序的 META-INF 目录下。在基于Maven或Gradle的现代项目中,你也可以将其放在 src/main/resources 资源包中。
项目结构示例
为了更好地理解,让我们来看一个Spring Boot集成JPA的项目示例。这个项目名为springboot-mysql-jpa。
以下是该项目的主要目录结构:
application.java: 包含主方法的应用程序入口类。controller文件夹: 存放控制器类。exceptions文件夹: 存放自定义异常类。models文件夹: 存放实体模型类。repository文件夹: 存放数据仓库接口。services文件夹: 存放服务层类。
注意:本课程重点在于Spring Boot与JPA的基础集成,因此不会深入讲解Spring MVC等Web层细节。
创建JPA实体模型
在models文件夹中,我们创建了一个名为Employee.java的实体类。这个类使用了Jakarta Persistence API(JPA)的注解。
以下是创建实体类的关键步骤和注解说明:
@Entity注解: 将这个Java类标记为一个JPA实体。@Table注解: 指定该实体映射到数据库中的表名。例如:@Table(name = “employees”)。@Id注解: 标记该字段为实体的唯一标识符(主键)。@GeneratedValue注解: 定义主键的生成策略。例如,strategy = GenerationType.IDENTITY表示使用数据库的自增字段。@Column注解: 可选。用于指定字段映射到数据库表的列名。如果省略,则默认使用字段名作为列名。
一个完整的实体类除了上述注解,还应遵循Java Bean的规范:
- 将属性(字段)声明为
private。 - 为所有属性生成公共的
getter和setter方法。 - 提供一个无参数的默认构造函数。
- 提供一个包含所有必要字段的构造函数。
- 重写
toString()方法,便于调试和打印对象信息。
提示:在大多数集成开发环境(IDE)中,你可以通过生成代码的功能自动创建
getter、setter、构造函数和toString()方法。
总结
本节课中我们一起学习了JPA配置文件persistence.xml的基础知识,包括其作用和标准存放位置。同时,我们通过一个Spring Boot项目示例,了解了如何构建项目结构以及如何使用JPA注解来定义一个完整的实体类(Entity)。我们重点讲解了 @Entity、@Table、@Id、@GeneratedValue 和 @Column 这几个核心注解的用法。
在接下来的课程中,我们将进一步探讨如何使用Repository和Service层来实现基于JPA的数据操作。

敬请关注,谢谢。
068:使用JPA执行操作 🛠️

在本节课中,我们将学习如何使用JPA(Java Persistence API)来执行数据库的增删改查(CRUD)操作。我们将通过一个具体的例子,了解如何创建Repository、Service和Controller层,并利用JPA的便捷功能来操作数据库,而无需编写复杂的SQL语句。

创建Repository接口
上一节我们介绍了JPA的基本概念,本节中我们来看看如何创建一个Repository接口。Repository是数据访问层,它直接与数据库交互。
首先,我们创建一个名为 EmployRepository 的接口,它继承自 JpaRepository。JpaRepository 是一个泛型接口,需要指定两个类型参数:实体类(Entity)和该实体主键(ID)的类型。
public interface EmployRepository extends JpaRepository<Employee, Long> {
}
通过继承 JpaRepository,我们的 EmployRepository 接口自动获得了许多用于数据操作的方法,例如 save(), findAll(), findById(), deleteById() 等。这些方法由JPA框架隐式实现,我们无需编写任何SQL代码。
定义Service层
Repository负责数据访问,而Service层则包含业务逻辑。接下来,我们定义一个Service接口,声明需要实现的操作。
以下是 EmployeeService 接口中定义的五个核心方法:
public interface EmployeeService {
Employee saveEmploy(Employee employee);
List<Employee> getAllEmployees();
Employee getEmployById(Long id);
Employee updateEmploy(Long id, Employee employeeDetails);
void deleteEmploy(Long id);
}
这些方法分别对应创建、读取、更新和删除(CRUD)操作。
实现Service层
定义好接口后,我们需要实现它。在实现类中,我们将注入之前创建的 EmployRepository。
@Service
public class EmployeeServiceImpl implements EmployeeService {
private final EmployRepository employRepository;
// 通过构造函数注入依赖
public EmployeeServiceImpl(EmployRepository employRepository) {
this.employRepository = employRepository;
}
@Override
public Employee saveEmploy(Employee employee) {
return employRepository.save(employee);
}
@Override
public List<Employee> getAllEmployees() {
return employRepository.findAll();
}
@Override
public Employee getEmployById(Long id) {
return employRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Employee", "id", id));
}
@Override
public Employee updateEmploy(Long id, Employee employeeDetails) {
Employee employee = getEmployById(id);
// 更新employee对象的属性
employee.setName(employeeDetails.getName());
// ... 更新其他属性
return employRepository.save(employee);
}
@Override
public void deleteEmploy(Long id) {
Employee employee = getEmployById(id);
employRepository.delete(employee);
}
}
请注意 getEmployById 方法中使用的 orElseThrow。这是为了处理当根据ID找不到对应记录时的情况。我们抛出一个自定义的 ResourceNotFoundException。
自定义异常 ResourceNotFoundException 的结构如下:
public class ResourceNotFoundException extends RuntimeException {
private String resourceName;
private String fieldName;
private Object fieldValue;
public ResourceNotFoundException(String resourceName, String fieldName, Object fieldValue) {
super(String.format("%s not found with %s : '%s'", resourceName, fieldName, fieldValue));
this.resourceName = resourceName;
this.fieldName = fieldName;
this.fieldValue = fieldValue;
}
// Getter 方法
}
配置数据库连接
要让JPA知道如何连接到数据库,我们需要在 application.properties 文件中进行配置。
以下是配置示例:
# 数据库连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/training_db
spring.datasource.username=root
spring.datasource.password=root123456
# JPA & Hibernate 配置
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.ddl-auto=create
配置项说明:
spring.datasource.url: 指定数据库的JDBC连接URL。spring.datasource.username和password: 数据库登录凭证。spring.jpa.properties.hibernate.dialect: 指定Hibernate使用的数据库方言,确保SQL语句的兼容性。spring.jpa.hibernate.ddl-auto: 设置Hibernate的DDL(数据定义语言)自动更新策略。create表示每次启动应用都会删除旧表并创建新表,仅用于初次演示。在实际开发中,应改为update以保留数据。
创建Controller层
Service层实现了业务逻辑,现在我们需要通过Controller层来暴露REST API,以便客户端可以调用这些功能。
我们创建一个 EmployeeController 类,并使用 @RestController 注解标记它。
@RestController
@RequestMapping("/api/employees")
public class EmployeeController {
private final EmployeeService employeeService;
// 通过构造函数注入EmployeeService
public EmployeeController(EmployeeService employeeService) {
this.employeeService = employeeService;
}
// 创建新员工
@PostMapping
public ResponseEntity<Employee> createEmployee(@RequestBody Employee employee) {
Employee savedEmployee = employeeService.saveEmploy(employee);
return new ResponseEntity<>(savedEmployee, HttpStatus.CREATED);
}
// 获取所有员工
@GetMapping
public List<Employee> getAllEmployees() {
return employeeService.getAllEmployees();
}
// 根据ID获取员工
@GetMapping("/{id}")
public ResponseEntity<Employee> getEmployeeById(@PathVariable Long id) {
Employee employee = employeeService.getEmployById(id);
return ResponseEntity.ok(employee);
}
// 根据ID更新员工信息
@PutMapping("/{id}")
public ResponseEntity<Employee> updateEmployee(@PathVariable Long id, @RequestBody Employee employeeDetails) {
Employee updatedEmployee = employeeService.updateEmploy(id, employeeDetails);
return ResponseEntity.ok(updatedEmployee);
}
// 根据ID删除员工
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteEmployee(@PathVariable Long id) {
employeeService.deleteEmploy(id);
return ResponseEntity.ok("Employee deleted successfully.");
}
}
这个Controller定义了五个端点(Endpoint),分别映射到HTTP的POST、GET、PUT、DELETE方法,从而实现了完整的CRUD API。
总结
本节课中我们一起学习了如何使用JPA来执行数据库操作。我们构建了一个完整的三层架构(Repository, Service, Controller),并实现了以下功能:
- 创建Repository:通过继承
JpaRepository,无需编写SQL即可获得基础的数据访问方法。 - 定义和实现Service:在Service层封装业务逻辑,并处理自定义异常(如
ResourceNotFoundException)。 - 配置数据库:通过
application.properties文件连接MySQL数据库,并配置Hibernate行为。 - 暴露REST API:在Controller层创建端点,将Service方法暴露为HTTP接口。

JPA的强大之处在于其对象关系映射(ORM)能力,它允许我们以操作Java对象的方式来操作数据库表,极大地简化了数据持久化代码的编写。在下一个会话中,我们将探讨更复杂的JPA查询和关系映射。
069:Spring Security 入门与JWT认证
在本节课中,我们将学习身份验证与授权,以及如何使用Spring Security来保护你的Spring Boot应用程序。我们将从理解Spring Security的基础知识及其核心功能开始,然后学习如何利用JWT令牌实现认证。通过本课的学习,你将掌握Spring Security的核心概念,并能够将其应用到实际项目中。
理解Spring Security基础
上一节我们概述了本课的学习目标,本节中我们来看看Spring Security的基础知识。Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架,它是保护基于Spring的应用程序的事实标准。
Spring Security的核心功能包括管理用户会话、处理Cookie、以及提供表单登录和注销等标准安全操作。这些功能共同构成了应用程序安全的第一道防线。
以下是Spring Security的一些关键特性:
- 会话管理:控制用户会话的创建、维护和销毁。
- Cookie处理:安全地处理用于会话跟踪的Cookie。
- 表单登录/注销:提供现成的、可定制的登录和注销页面及流程。

实现JWT令牌认证

了解了传统基于会话的安全机制后,本节我们将探讨一种更适用于现代RESTful API和无状态架构的认证方式——JWT令牌认证。
JWT(JSON Web Token)是一种开放标准,它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息。在基于令牌的认证中,服务器在用户登录成功后生成一个JWT令牌并返回给客户端。客户端在后续请求中携带此令牌,服务器通过验证令牌来确认用户身份。
与传统的会话管理相比,JWT使得服务器无需在服务端存储会话状态,从而更容易实现扩展。一个典型的JWT令牌由三部分组成:Header.Payload.Signature。
以下是实现JWT认证的基本步骤:
- 用户登录:客户端提交凭证(如用户名和密码)。
- 验证凭证并生成令牌:服务器验证凭证,若成功则使用密钥生成JWT。
// 伪代码示例:生成JWT String jwtToken = Jwts.builder() .setSubject(username) // 设置主题(通常是用户名) .setExpiration(new Date(System.currentTimeMillis() + expirationTime)) // 设置过期时间 .signWith(SignatureAlgorithm.HS512, secretKey) // 使用密钥和算法签名 .compact(); - 返回令牌:服务器将JWT返回给客户端(通常放在HTTP响应头或体中)。
- 携带令牌请求:客户端在后续请求的
Authorization头中携带令牌(例如:Bearer <token>)。 - 验证令牌:服务器拦截请求,提取并验证JWT的签名和有效性,然后根据令牌中的信息(如用户名)授权访问资源。
课程总结
本节课我们一起学习了Spring Security的核心概念。我们首先介绍了Spring Security的基础,包括会话、Cookie和表单登录。接着,我们深入探讨了基于JWT令牌的无状态认证机制,了解了其工作原理和实现步骤。
通过本课的学习,你已经建立了Spring Security的坚实基础,并掌握了使用JWT保护应用程序的现代方法。你可以将这些知识应用于你的Spring Boot项目中,以构建安全、可靠的Web应用或API服务。
070:理解认证与授权

在本节课中,我们将学习Spring Security的核心概念:认证与授权。理解这两个概念对于构建安全的应用程序至关重要。

概述
Spring Security的核心目标是保护应用程序的内容和请求,其实现主要围绕认证和授权展开。要掌握Spring Security,必须理解三个关键概念:认证、授权和过滤器链。本节将逐一介绍前两个概念。
认证:验证用户身份
上一节我们概述了Spring Security的重要性,本节中我们来看看第一个核心概念:认证。
保护资源的一个基本方法是确保访问者是其声称的身份。在一个典型的应用程序中,你需要用户进行认证。这意味着应用程序需要验证用户是否是其声称的那个人。这通常通过用户名和密码来完成。例如,用户声称自己是“用户A”,并提供了密码。如果密码和用户名正确,则允许该用户访问,否则拒绝。
为了实现认证,Spring Security使用了认证过滤器和认证提供者。认证提供者处理特定类型的认证请求,并检查是否支持该类型的认证。
在大型或完整的应用程序中,我们通常使用仓库设计模式和数据访问对象。认证过滤器或提供者会从用户详情服务中检索用户详细信息。用户详情服务是一个核心接口,用于加载Spring框架中用户特定的数据。通过特定的验证过滤器进行认证,并判断请求是否来自正确的用户。
授权:管理用户权限
理解了如何验证用户身份后,接下来我们需要控制用户能做什么,这就是授权。
在简单的应用中,仅靠认证可能不够。一旦用户通过认证,理论上她可以访问应用的每个部分。但大多数应用程序都有权限或角色的概念,这时就需要授权。授权指的是确定用户是否拥有执行特定操作的适当权限的过程。
例如,作为管理员,我可以查看有多少用户在我的电商网站上注册,但作为客户的你则无法看到这些数据。这取决于你拥有何种权限。权限通常以角色或规则的形式出现。再举一个例子:可以访问网店前台的客户,和可以访问独立管理后台的管理员,这两种用户都需要登录,但我们需要规定认证用户或普通用户可以执行哪些活动。
以下是我们可以执行的不同类型的授权配置:
- 基于URL的配置
- 基于注解的配置
应用程序的授权检查可能很复杂,如果将所有规则定义在一个地方,会变得难以阅读和维护。因此,基于注解的配置更受青睐。
以下是你可以使用的一些注解示例:
@PreAuthorize:在授权前需要执行哪些操作。@PostAuthorize:在授权后需要执行哪些操作。@PreFilter和@PostFilter@Secured@RolesAllowed:允许特定用户执行操作。
这就是认证和授权在Spring Security中的工作方式。
总结
本节课中,我们一起学习了Spring Security的两个基石:认证与授权。认证负责验证“你是谁”,通常通过用户名和密码实现。授权则负责决定“你能做什么”,通过权限和角色来管理用户的访问范围。我们还简要了解了基于注解的授权配置方式,这种方式更清晰、更易于管理。
在接下来的课程中,我将讨论过滤器链,这是连接和协调认证与授权过程的另一个关键组件。

🎼 下次再见,敬请期待,谢谢。
071:Spring Security 入门指南 🔐
在本节课中,我们将学习一个非常重要的概念——Spring Security。这是一个为创建安全的Java企业级应用程序提供多种安全特性(如身份验证和授权)的框架。让我们开始吧。


概述 📋
Spring Security 是一个强大且高度可定制的身份验证和访问控制框架。它主要针对两个核心领域:身份验证和授权。
- 身份验证是识别并确认试图访问系统的用户身份的过程。
- 授权是确定已验证用户被允许在应用程序中执行哪些操作的过程。
Spring Security 可以应用于Web请求方法,也支持访问单个域对象。它能够与Spring MVC和Spring Boot轻松集成。

Spring Security 的优势 ✨

以下是使用Spring Security的主要好处:
- 易于集成:可以轻松与Spring MVC和Spring Boot集成。
- 可移植性:其包具有良好的可移植性。
- CSRF保护:提供跨站请求伪造保护。
- 安全会话管理:支持安全的会话集成。
- API保护:可用于保护Servlet API。
- 防御暴力攻击:帮助抵御暴力破解攻击。
- Java配置支持:支持通过最简配置进行Java配置。
Spring Security 架构解析 🏗️
上一节我们介绍了Spring Security的基本概念和优势,本节中我们来看看它的核心架构是如何工作的。
Spring Security 的架构工作流程如下:
- 用户发起请求:请求首先来自用户。
- 经过认证过滤器:请求到达认证过滤器。
- 创建认证令牌:认证过滤器将请求信息(如用户名和密码)封装成一个认证令牌。
- 认证管理器处理:令牌被传递给认证管理器。
- 委托认证提供者:认证管理器将请求委托给一个或多个认证提供者。
- 加载用户详情:认证提供者调用
UserDetailsService来加载用户的具体信息(如密码、权限等)。 - 进行认证决策:
UserDetailsService将加载的用户信息返回给认证提供者,由后者进行验证(返回true或false)。- 认证提供者可以通过多种方式验证,例如:DAO认证、CAS认证或LDAP认证。
UserDetailsService可以加载自定义用户详情或内存中的用户详情,这完全取决于你的选择。
- 返回认证结果:认证结果(成功或失败)沿调用链返回。
- 设置安全上下文:当认证成功的请求返回到认证过滤器时,安全上下文(
SecurityContext)会被建立。 - 进行授权检查:系统接着检查该用户的权限,以决定是否允许其访问请求的资源。
- 返回响应:最终,响应被返回给客户端。
这就是Spring Security架构的基本工作流程。
总结与展望 🎯
本节课我们一起学习了Spring Security的基础知识。我们了解了它是一个用于身份验证和授权的框架,探讨了其主要优势,并深入解析了其核心架构的工作流程。


在接下来的演示课程中,我们将继续学习更多关于Spring Security的内容,特别是它与内存数据库的集成。敬请期待,我们下节课再见!
072:Spring Security 过滤器链 🔗

在本节课中,我们将要学习 Spring Security 的核心组件之一:过滤器链。我们将了解它的工作原理、每个过滤器的作用,以及它如何保护你的应用程序。

概述
如果你在项目中使用 Spring Security,理解过滤器链是正确使用它的关键概念。
什么是过滤器链?
Spring Security 使用一个过滤器链来执行安全功能。这个链由一系列过滤器组成,每个过滤器负责一项特定的安全任务。
以下是过滤器链的基本工作流程:
- 客户端发起请求:用户通过浏览器访问应用程序。
- 创建过滤器链:应用容器为处理这个传入的请求创建一个过滤器链。
- 请求通过过滤器:请求会依次通过链中的每一个过滤器。
- 执行过滤器逻辑:每个过滤器执行其内部编写的安全逻辑,例如身份验证、授权等。
- 到达控制器或返回响应:如果所有过滤器都验证通过,请求最终会到达后端控制器(如
@Controller)。如果任何一个过滤器验证失败,它可以创建一个 HTTP 响应并直接返回给客户端,而无需到达控制器。
过滤器链的作用
过滤器链负责根据应用程序中定义的配置来强制执行安全规则并保护资源。
上一节我们介绍了过滤器链的基本流程,本节中我们来看看每个过滤器具体能做什么。
每个过滤器在链中执行一个特定的安全任务,例如:
- 身份验证:确认用户的身份。
- 授权:检查用户是否有权访问特定资源。
- 会话管理:管理用户会话状态。
- 处理持久化Cookie:处理与安全相关的Cookie。
如果你想为任何安全功能自定义或添加自己的逻辑,你可以编写自己的过滤器,并在链执行期间调用它。
过滤器的工作原理
过滤器主要在 Web 应用层工作,它们可以在请求到达 Spring MVC 控制器之前或之后,修改 HTTP 的请求或响应。
以下是过滤器可以执行的核心逻辑:
- 在请求到达控制器之前修改
HttpServletRequest或HttpServletResponse。 - 停止对请求的进一步处理,并直接向客户端发送一个响应。
这就是过滤器链在实时应用中的工作方式。
总结
本节课中我们一起学习了 Spring Security 的过滤器链。我们了解到,过滤器链是一系列有序的过滤器,用于处理传入的 HTTP 请求并执行安全检查。它通过依次调用每个过滤器来完成身份验证、授权等任务,是 Spring Security 保护应用程序资源的基石。


请继续关注,以了解更多关于实际实现的内容。我们下节课再见。
073:Spring Security Hello World 演示 🛡️

在本节课中,我们将学习Spring Security的基础知识,并通过一个“Hello World”级别的演示来了解其基本工作原理。我们将看到如何创建一个简单的Spring Boot应用,其中包含公开页面和受保护页面,并体验Spring Security提供的默认登录和认证流程。
概述
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它主要用于保护基于Spring的应用程序。本节课的演示将展示Spring Security的默认行为:如何自动保护应用程序端点,以及如何通过内置的登录表单进行用户认证。
项目演示与目标
首先,我们来看一下本次演示项目的预期行为。当我们运行这个Spring Security应用程序时,它会根据访问的URL路径提供不同的安全级别。
以下是项目中的两个主要端点:
- 公开页面 (
/public): 此页面无需登录即可访问,仅请求一些基本信息。 - 受保护页面 (
/secured): 此页面受到Spring Security的保护,未经认证的用户将被重定向到登录页面。
运行应用后,如果访问公开页面,可以直接看到表单并提交数据。然而,如果尝试访问受保护页面,系统会要求我们进行登录。登录成功后,才能看到受保护页面的内容。

实现步骤解析
上一节我们看到了项目的运行效果,本节中我们来看看为了实现这个效果,需要进行哪些关键配置和编码工作。由于在视频中逐行编写所有代码会非常冗长,因此这里将概述核心实现步骤,完整的代码可以参考视频描述或附件。
以下是实现此演示的主要步骤:
- 创建Spring Boot项目:使用Spring Initializr创建一个新的Spring Boot项目,并添加
Spring Web和Spring Security依赖。 - 配置安全规则:在配置类中,通过重写
SecurityFilterChainBean来定义URL的访问规则。例如,将/public路径设置为允许所有访问(permitAll),而其他所有请求(如/secured)都需要认证。 - 创建控制器:编写一个简单的控制器(
@Controller),用于处理/public和/secured页面的请求,并返回对应的视图名称。 - 创建视图模板:使用Thymeleaf等模板引擎创建
public.html和secured.html页面,用于展示内容。 - 理解默认认证:Spring Security提供了默认的用户名(
user)和一个在控制台生成的随机密码。为了演示,我们可以在application.properties文件中配置一个固定的用户名和密码,例如:spring.security.user.name=KB spring.security.user.password=KB1234
通过以上步骤,我们就构建了一个具备基础安全功能的Spring Boot应用。当用户访问受保护资源时,Spring Security会自动拦截请求并展示其内置的登录表单。
核心概念与代码
在这个简单的演示中,最核心的概念是安全过滤链的配置。它决定了哪些请求路径需要被保护,哪些可以公开访问。
以下是一个简化的配置示例,展示了如何区分公开路径和受保护路径:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) -> authz
.requestMatchers("/public").permitAll() // 公开页面,允许所有访问
.anyRequest().authenticated() // 其他所有请求都需要认证
)
.formLogin(withDefaults()); // 使用默认的登录表单
return http.build();
}
}
permitAll(): 表示匹配该路径的请求不需要任何安全约束。authenticated(): 表示匹配该路径的请求需要用户已成功认证。formLogin(withDefaults()): 启用Spring Security默认的基于表单的登录。
总结


本节课中,我们一起学习了Spring Security的入门知识。我们通过一个“Hello World”演示,直观地看到了Spring Security如何为Spring Boot应用提供开箱即用的安全保护。我们了解了如何通过简单的配置来定义公开和受保护的资源路径,并体验了其内置的认证流程。这个演示是理解更复杂安全配置(如自定义用户详情服务、角色权限控制等)的坚实基础。在接下来的课程中,我们将深入探讨如何定制这些安全特性。
074:P74 Spring Security Hello World 实现 🛡️

在本节课中,我们将学习如何实现一个简单的 Spring Security “Hello World” 应用。我们将创建一个包含公共页面和受保护页面的项目,并了解 Spring Security 的基本配置和工作流程。
概述
在上节课中,我们演示了 Spring Security 的基本概念。本节我们将动手实现一个具体的项目。这个项目是一个 Spring Maven 项目,包含一个控制器、公共页面和受保护页面,用于展示 Spring Security 如何拦截请求并引导用户登录。


项目结构
首先,我们来看一下项目的核心结构。项目中有一个控制器,用于处理不同的页面请求。

在控制器中,我们定义了公共页面和受保护页面的端点。公共页面会检查一个包含新用户信息的属性。这里我们有一个 Person 类,它具有私有的 name 和 age 属性,并通过 getter 和 setter 方法进行访问。
页面功能
以下是项目中各个页面的功能描述:
- 公共页面:当用户发起一个公共请求时,页面会简单地返回一条欢迎信息:“Hi user, great welcome to the secured homepage”。
- 受保护页面:默认情况下,在登录后会跳转到受保护的主页。这是一个安全的页面。当用户请求受保护页面时,系统会展示登录页面。
- 页面存放位置:所有的网页文件都存放在
src目录下。具体路径是:src/main/webapp/。例如,index.jsp文件就位于此。所有集成了安全代码的页面都放在这里。
安全配置
接下来,我们看看安全相关的配置。目前,我们允许任何具有“USER”角色的用户访问受保护资源。
Spring Security 会拦截每一个请求,并将其传递给 DispatcherServlet。DispatcherServlet 会扫描项目包。当你发送请求时,请求的路径必须与控制器中定义的路径匹配,这样请求才能被正确处理。
过滤器链
在请求处理过程中,会经过一系列过滤器。我们在 com.xxx.filters 包中添加了几个过滤器:
CrossScriptingFilterRequestWrapperResponseWrapperValidatingHttpRequestWrapper
这些过滤器来自特定的包,我们通常不需要修改其内部逻辑,因此这里不展开详述。
验证属性
在 validation.properties 文件中,定义了一些验证规则。例如,未来如果你需要使用邮箱、IP地址、URL、信用卡或其他评估细节的验证,可以在这里配置。目前,在这个演示项目中,我们暂时没有使用这些验证功能。
核心依赖与页面
要实现这个项目,你需要添加 Spring Security 的核心依赖包。同时,你需要在 webapp 目录下创建相应的页面。
例如,我创建了一个受保护的 home.jsp 页面。它会打印从后端 HelloWorld 控制器返回的消息。而 helloworld.jsp 页面则负责接收输入的 name 和 age 信息,并将其打印出来。
运行与输出
按照上述步骤配置和编写代码后,你就可以运行这个项目了。运行结果将与我们上节课演示的效果一致。
为了方便你的学习和参考,我将提供这个项目的完整内容。你可以直接查看和使用它。

总结

本节课我们一起实现了一个 Spring Security 的入门示例。我们创建了一个 Maven 项目,配置了安全规则,区分了公共页面和受保护页面,并了解了请求如何经过过滤器链和 DispatcherServlet 进行处理。通过这个简单的“Hello World”项目,你应该对 Spring Security 的基本集成方式有了初步的认识。我们下节课再见。

浙公网安备 33010602011771号