Repository: joeljhou/geekyspace
Branch: master
Commit: 0e92c932f72c
Files: 107
Total size: 410.9 KB
Directory structure:
gitextract_4cg6voya/
├── .github/
│ └── workflows/
│ └── deploy-docs.yml
├── .gitignore
├── DirectoryV3.xml
├── README.md
├── package.json
└── src/
├── .vuepress/
│ ├── client.ts
│ ├── config/
│ │ └── docSearchLocales.ts
│ ├── config.ts
│ ├── path/
│ │ ├── navbar.ts
│ │ └── sidebar/
│ │ ├── index.ts
│ │ ├── installation-guide.ts
│ │ ├── java-datetime.ts
│ │ ├── java-features.ts
│ │ ├── java.ts
│ │ ├── jvm.ts
│ │ └── spring-framework.ts
│ ├── public/
│ │ ├── CNAME
│ │ └── special/
│ │ └── wedding-chenzhuo.html
│ ├── styles/
│ │ ├── config.scss
│ │ ├── index.scss
│ │ └── palette.scss
│ └── theme.ts
├── README.md
├── about-me.md
├── article.md
└── md/
├── blockchain/
│ └── README.md
├── database/
│ └── mysql/
│ ├── README.md
│ └── overview/
│ └── what-is-database.md
├── docker/
│ ├── install.md
│ ├── mirror-acceleration.md
│ ├── overview.md
│ └── top20-commands.md
├── flutter/
│ └── flutter-guide.md
├── idea-tips/
│ └── activation.md
├── installation-guide/
│ ├── README.md
│ ├── base-tools/
│ │ └── Homebrew.md
│ ├── dev-env/
│ │ ├── java/
│ │ │ └── SDKMAN.md
│ │ └── nodejs/
│ │ ├── Corepack.md
│ │ ├── nrm.md
│ │ └── nvm.md
│ └── os/
│ └── windows-office-activation.md
├── java/
│ ├── basic/
│ │ ├── java-basic-oop.md
│ │ ├── java-common-classes.md
│ │ └── java-from-scratch.md
│ ├── datetime/
│ │ ├── README.md
│ │ ├── api.md
│ │ ├── compare-dates.md
│ │ ├── convert-dates.md
│ │ ├── date-diff.md
│ │ ├── datetime.md
│ │ ├── locale.md
│ │ ├── timestamp.md
│ │ └── zoneddatetime-vs-offsetdatetime.md
│ ├── features/
│ │ ├── Java10/
│ │ │ └── jep286-local-variable-type-inference.md
│ │ ├── Java11/
│ │ │ └── jep320-remove-JavaEE-CORBA.md
│ │ ├── Java14/
│ │ │ └── jep361-switch-expressions.md
│ │ ├── Java15/
│ │ │ ├── jep371-hidden-classes.md
│ │ │ └── jep378-text-blocks.md
│ │ ├── Java16/
│ │ │ ├── jep394-pattern-matching-for-instanceof.md
│ │ │ └── jep395-records.md
│ │ ├── Java17/
│ │ │ ├── jep406-pattern-matching-for-switch-preview.md
│ │ │ └── jep409-sealed-classes.md
│ │ ├── Java18/
│ │ │ ├── jep400-utf8-by-default.md
│ │ │ ├── jep408-simple-web-server.md
│ │ │ └── jep413-code-snippets-in-api-documentation.md
│ │ ├── Java19/
│ │ │ └── java19-new-features-summary.md
│ │ ├── Java20/
│ │ │ └── java20-new-features-summary.md
│ │ ├── Java21/
│ │ │ ├── jep430-string-templates.md
│ │ │ ├── jep431-sequenced-collections.md
│ │ │ ├── jep439-generational-zgc.md
│ │ │ ├── jep440-record-partterns.md
│ │ │ ├── jep441-pattern-matching-for-switch.md
│ │ │ └── jep444-virtual-threads.md
│ │ ├── Java9/
│ │ │ ├── jep222-jshell.md
│ │ │ └── jep269-convenience-factory-methods-for-collections.md
│ │ └── README.md
│ ├── jvm/
│ │ ├── README.md
│ │ ├── part1/
│ │ │ ├── compile_jdk.md
│ │ │ └── overview.md
│ │ ├── part2/
│ │ │ ├── heap-object-flow.md
│ │ │ ├── runtime-data-areas.md
│ │ │ └── visual-tools/
│ │ │ └── visualvm.md
│ │ └── part3/
│ │ ├── bytecode-instructions-set.md
│ │ ├── class-file-structure.md
│ │ └── class-loading-mechanism.md
│ ├── kotlin/
│ │ └── kotlin-quick-for-java.md
│ └── thread/
│ └── thread-concurrency.md
├── spring-boot/
│ ├── README.md
│ └── quickstart.md
├── spring-data-jpa/
│ ├── README.md
│ └── jetbrains/
│ └── getting-started.md
├── spring-framework/
│ ├── core/
│ │ ├── README.md
│ │ ├── beans-definition.md
│ │ ├── beans-lifecycle.md
│ │ ├── beans-scope.md
│ │ ├── child-bean-definitions.md
│ │ ├── dependencies/
│ │ │ ├── README.md
│ │ │ ├── factory-autowire.md
│ │ │ ├── factory-collaborators.md
│ │ │ ├── factory-dependson.md
│ │ │ ├── factory-lazy-init.md
│ │ │ ├── factory-method-injection.md
│ │ │ └── factory-properties-detailed.md
│ │ └── ioc-container.md
│ └── overview/
│ ├── README.md
│ └── quickstart.md
└── template/
└── blog template.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/deploy-docs.yml
================================================
# 全局配置参考:https://vuejs.press/zh/guide/deployment.html
name: docs
on:
# push 到 master 分支时自动部署
push:
branches: [master]
# 允许手动触发
workflow_dispatch:
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
# “最近更新时间” 等 git 日志相关信息,需要拉取全部提交记录
fetch-depth: 0
- name: 设置 pnpm
uses: pnpm/action-setup@v4
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
# 选择要使用的 node 版本
node-version: 22
# 缓存 pnpm 依赖
cache: pnpm
- name: 安装依赖
run: pnpm install --frozen-lockfile
# 运行构建脚本
- name: 构建 VuePress 站点
env:
NODE_OPTIONS: --max_old_space_size=8192
run: |
pnpm docs:build
touch src/.vuepress/dist/.nojekyll
# 查看 workflow 的文档来获取更多信息
# @see https://github.com/crazy-max/ghaction-github-pages
- name: 部署到 GitHub Pages
uses: crazy-max/ghaction-github-pages@v4
with:
# 部署到 gh-pages 分支
target_branch: gh-pages
# 部署目录为 VuePress 的默认输出目录
build_dir: src/.vuepress/dist
env:
# @see https://docs.github.com/cn/actions/reference/authentication-in-a-workflow#about-the-github_token-secret
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .gitignore
================================================
/.idea/
/**/.obsidian/
node_modules/
src/.vuepress/.cache/
src/.vuepress/.temp/
src/.vuepress/dist/
.DS_Store
================================================
FILE: DirectoryV3.xml
================================================
================================================
FILE: README.md
================================================
## 在线访问地址
https://www.geekyspace.cn/
## 博客效果图展示:
## 项目介绍
本项目是一个基于VuePress的个人博客项目,主要用于记录个人的学习和工作经验,以及一些技术分享。
## 代码仓库地址
https://github.com/joeljhou/geekyspace/
================================================
FILE: package.json
================================================
{
"name": "vuepress-theme-hope-template",
"version": "2.0.0",
"description": "A project of vuepress-theme-hope",
"license": "MIT",
"type": "module",
"scripts": {
"docs:build": "vuepress-vite build src",
"docs:clean-dev": "vuepress-vite dev src --clean-cache",
"docs:dev": "vuepress-vite dev src",
"docs:dedupe": "pnpm dedupe",
"docs:update-package": "pnpm dlx vp-update"
},
"devDependencies": {
"@vuepress/bundler-vite": "2.0.0-rc.9",
"@vuepress/plugin-copyright": "2.0.0-rc.21",
"@vuepress/plugin-docsearch": "2.0.0-rc.21",
"@vuepress/plugin-feed": "2.0.0-rc.21",
"@vuepress/plugin-google-analytics": "2.0.0-rc.21",
"@vuepress/plugin-redirect": "2.0.0-rc.21",
"mermaid": "^11.4.1",
"vue": "^3.4.27",
"vuepress": "2.0.0-rc.9",
"vuepress-plugin-comment2": "2.0.0-rc.30",
"vuepress-theme-hope": "2.0.0-rc.32"
},
"packageManager": "pnpm@10.6.3+sha512.bb45e34d50a9a76e858a95837301bfb6bd6d35aea2c5d52094fa497a467c43f5c440103ce2511e9e0a2f89c3d6071baac3358fc68ac6fb75e2ceb3d2736065e6"
}
================================================
FILE: src/.vuepress/client.ts
================================================
// client.ts ---> 客户端配置文件
// @ts-ignore
import {defineGiscusConfig} from 'vuepress-plugin-comment2/client'
// @ts-ignore
import {defineClientConfig} from "vuepress/client";
/**
* // 设置评论插件Giscus客户端选项:https://giscus.app/zh-CN
*/
defineGiscusConfig({
repo: 'joeljhou/geekyspace',
repoId: 'R_kgDOK4fo4g',
category: 'Announcements',
categoryId: 'DIC_kwDOK4fo4s4Cd4Y9',
mapping: 'title'
})
export default defineClientConfig({
// 解决百度统计单页面应用无法统计的问题
enhance({app, router, siteData}) {
router.beforeEach((to, from, next) => {
// @ts-ignore
if (typeof _hmt != "undefined") {
if (to.path && to.fullPath !== from.fullPath) {
// @ts-ignore
_hmt.push(["_trackPageview", to.fullPath]);
}
}
next();
});
},
setup() {
},
rootComponents: [],
})
================================================
FILE: src/.vuepress/config/docSearchLocales.ts
================================================
export const docSearchLocales = {
placeholder: "搜索geekyspace.cn的文档",
translations: {
button: {
buttonText: "",
buttonAriaLabel: "",
},
modal: {
searchBox: {
resetButtonTitle: "清除查询条件",
resetButtonAriaLabel: "清除查询条件",
cancelButtonText: "取消",
cancelButtonAriaLabel: "取消",
},
startScreen: {
recentSearchesTitle: "搜索历史",
noRecentSearchesText: "最近没有搜索",
saveRecentSearchButtonTitle: "保存至搜索历史",
removeRecentSearchButtonTitle: "从搜索历史中移除",
favoriteSearchesTitle: "收藏",
removeFavoriteSearchButtonTitle: "从收藏中移除",
},
errorScreen: {
titleText: "无法获取结果",
helpText: "你可能需要检查你的网络连接",
},
footer: {
selectText: "选择",
navigateText: "导航",
closeText: "关闭",
searchByText: "搜索",
},
noResultsScreen: {
noResultsText: "无法找到相关结果",
suggestedQueryText: "你可以尝试查询",
reportMissingResultsText: "你认为该查询应该有结果?",
reportMissingResultsLinkText: "点击反馈",
},
},
},
};
================================================
FILE: src/.vuepress/config.ts
================================================
// config.ts ---> 配置文件
import {defineUserConfig} from "vuepress";
import {redirectPlugin} from '@vuepress/plugin-redirect';
import {docsearchPlugin} from "@vuepress/plugin-docsearch";
import {googleAnalyticsPlugin} from '@vuepress/plugin-google-analytics';
import {commentPlugin} from "vuepress-plugin-comment2";
import {docSearchLocales} from "./config/docSearchLocales";
import theme from "./theme.js";
export default defineUserConfig({
base: "/",
lang: "zh-CN",
theme,
plugins: [
// 设置重定向
redirectPlugin({
config: {},
}),
// 搜索插件
docsearchPlugin({
appId: "PTKSWUU4JQ",
apiKey: "8cf4dc036ad5f140f40d1d97e178b0b4",
indexName: "geekyspace",
locales: {"/": docSearchLocales},
injectStyles: true
}),
// 设置谷歌分析
googleAnalyticsPlugin({
id: "G-3L19EZ1HH8",
debug: false,
}),
// 设置评论插件
commentPlugin({
// 插件选项:Artalk | Giscus | Waline | Twikoo
provider: "Giscus",
}),
],
// Enable it with pwa
// shouldPrefetch: false,
head: [
// 添加百度统计代码
[
"script",
{},
`
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?c3b455c45c9c9b349e7d28e7e13e950f";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
`
],
// 添加Google AdSense广告位
[
"script",
{
async: true,
src: "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3218450089809357",
crossorigin: "anonymous"
}
]
],
});
================================================
FILE: src/.vuepress/path/navbar.ts
================================================
import {navbar} from "vuepress-theme-hope";
export default navbar([
{text: "首页", icon: "home", link: "/"},
{text: "时间轴", icon: "list", link: "timeline/"},
{
text: "Java", icon: "java", children: [
{
text: "Java 基础", children:[
{text: "快速入门", icon: "java", link: "md/java/basic/java-from-scratch"},
{text: "面向对象编程(OOP)", icon: "java", link: "md/java/basic/java-basic-oop"},
],
},
{
text: "文章", children:[
{text: "Java 多线程与并发", icon: "thread", link: "md/java/thread/thread-concurrency"},
{text: "Java 程序员快速掌握 Kotlin", icon: "kotlin", link: "md/java/kotlin/kotlin-quick-for-java"},
],
},
{
text: "专栏", children:[
{text: "Java日期时间", icon: "java", link: "md/java/datetime/"},
{text: "Java新版本特性", icon: "java", link: "md/java/features/"},
{text: "深入理解Java虚拟机", icon: "jvm-xx", link: "md/java/jvm/"},
],
},
]
},
{
text: "DataBase", icon: "database", children: [
{
text: "专栏", children:[
{text: "MySQL必知必会", icon: "mysql", link: "md/database/mysql/"},
],
},
]
},
{
text: "Spring", icon: "spring", children: [
{text: "Spring 框架", icon: "spring", link: "md/spring-framework/core/"},
// {text: "Spring Boot 教程", icon: "spring", link: "md/spring-boot/"},
{text: "Spring Data JPA", icon: "spring", link: "md/spring-data-jpa/jetbrains/getting-started"},
]
},
{text: "安装大全", icon: "launch", link: "md/installation-guide/"},
{
text: "玩转IDEA", icon: "intellij-idea", prefix: "md/idea-tips/", children: [
{
text: "IDEA 教程",
icon: "intellij-idea",
link: "https://www.jetbrains.com/help/idea/getting-started.html"
},
{text: "正版激活码", icon: "intellij-idea", link: "activation"},
]
},
{text: "文库汇总", icon: "article", link: "article.html"},
]);
================================================
FILE: src/.vuepress/path/sidebar/index.ts
================================================
import {sidebar} from "vuepress-theme-hope";
import {java} from "./java.js";
import {springFramework} from "./spring-framework"
import {installationGuide} from "./installation-guide";
// 专栏
import {javaDatetime} from "./java-datetime";
import {javaFeatures} from "./java-features.js";
import {jvm} from "./jvm.js";
export default sidebar({
"/md/java/": java, // 1.Java
"/md/java/datetime/": javaDatetime, // 专栏:Java日期时间
"/md/java/features/": javaFeatures, // 专栏:Java新特性
"/md/java/jvm/": jvm, // 专栏:深入理解Java虚拟机
"/md/database/mysql/": [
{text: "总目录", prefix: "/md/database/mysql/", link: "/md/database/mysql/",},
{
text: "概述", prefix: "overview/", link: "overview/",
children: [
{text: "什么是数据库?", link: "what-is-database"},
],
},
],
"/md/spring-framework/": springFramework, // Spring框架
// "/md/spring-boot/": [ // SpringBoot框架
// {text: "总目录", prefix: "/md/spring-boot/", link: "/md/spring-boot/"},
// {
// text: "快速入门", children: [
// {text: "Spring Boot 入门", link: "quickstart"}
// ]
// },
// ],
"/md/spring-data-jpa/": [
{text: "总目录", prefix: "/md/spring-data-jpa/", link: "/md/spring-data-jpa/"},
{text: "快速入门", prefix: "/md/jetbrains/", link: "jetbrains/getting-started"},
],
"/md/installation-guide/": installationGuide,
"/md/docker/": [
{text: "概述", prefix: "/md/docker/overview", link: "/md/docker/overview"},
{text: "安装指南", prefix: "/md/docker/install", link: "/md/docker/install"},
{text: "镜像加速器", prefix: "/md/docker/mirror-acceleration", link: "/md/docker/mirror-acceleration"},
{text: "Top20常用命令", prefix: "/md/docker/top20-commands", link: "/md/docker/top20-commands"},
],
});
================================================
FILE: src/.vuepress/path/sidebar/installation-guide.ts
================================================
import {arraySidebar} from "vuepress-theme-hope";
export const installationGuide = arraySidebar([
{text: "开发者安装大全", prefix: "/md/installation-guide/", link: "/md/installation-guide/"},
{
text: "操作系统", prefix: "os/", children: [
{text: "Windows、Office激活", link: "windows-office-activation"},
],
},
{
text: "常用工具", prefix: "base-tools/", children: [
{text: "Homebrew", link: "Homebrew"},
],
},
{
text: "开发环境", prefix: "dev-env/", children: [
{
text: "Java", prefix: "java/", link: "java/",
collapsible: false,
children: [
{text: "SDKMAN", link: "SDKMAN"},
]
},
{
text: "Nodejs", prefix: "nodejs/", link: "nodejs/",
collapsible: false,
children: [
{text: "nvm", link: "nvm"},
{text: "nrm", link: "nrm"},
{text: "Corepack", link: "Corepack"},
]
},
],
},
]);
================================================
FILE: src/.vuepress/path/sidebar/java-datetime.ts
================================================
import {arraySidebar} from "vuepress-theme-hope";
export const javaDatetime = arraySidebar([
{text: "总目录", prefix: "/md/java/datetime/", link: "/md/java/datetime/",},
{text: "API", prefix: "/md/java/datetime/api", link: "/md/java/datetime/api",},
{
text: "1.获取当前日期和时间",
children: [
{text: "当前日期和时间", link: "datetime"},
{text: "当前时间戳", link: "timestamp"},
{text: "当前区域设置/国际化", link: "locale"},
],
},
{
text: "2.比较日期和时间",
children: [
{text: "日期比较", link: "compare-dates"},
{text: "时区与偏移量的区别", link: "zoneddatetime-vs-offsetdatetime"},
],
},
{
text: "3.转换日期和时间",
children: [
{text: "转换日期时间实例", link: "convert-dates"},
],
},
{
text: "6.日期和时间提取与处理",
children: [
{text: "日期时间差", link: "date-diff"},
],
},
]
);
================================================
FILE: src/.vuepress/path/sidebar/java-features.ts
================================================
import {arraySidebar} from "vuepress-theme-hope";
export const javaFeatures = arraySidebar([
{text: "总目录", prefix: "/md/java/features/", link: "/md/java/features/",},
{
text: "Java 21", prefix: "Java21/", link: "Java21/",
children: [
{text: "字符串模版(Preview)", link: "jep430-string-templates"},
{text: "有序集合", link: "jep431-sequenced-collections"},
{text: "分代ZGC", link: "jep439-generational-zgc"},
{text: "记录模式", link: "jep440-record-partterns"},
{text: "switch模式匹配", link: "jep441-pattern-matching-for-switch"},
{text: "虚拟线程", link: "jep444-virtual-threads"},
],
},
{
text: "Java 20", prefix: "Java20/", link: "Java20/",
children: [
{text: "新特性总结", link: "java20-new-features-summary"},
],
},
{
text: "Java 19", prefix: "Java19/", link: "Java19/",
children: [
{text: "新特性总结", link: "java19-new-features-summary"},
],
},
{
text: "Java 18", prefix: "Java18/", link: "Java18/",
children: [
{text: "指定UTF-8为默认字符集", link: "jep400-utf8-by-default"},
{text: "简单Web服务器", link: "jep408-simple-web-server"},
{text: "新增@snipppet标签", link: "jep413-code-snippets-in-api-documentation"},
],
},
{
text: "Java 17", prefix: "Java17/", link: "Java17/",
children: [
{text: "switch模式匹配(Preview)", link: "jep406-pattern-matching-for-switch-preview"},
{text: "sealed类", link: "jep409-sealed-classes"},
],
},
{
text: "Java 16", prefix: "Java16/", link: "Java16/",
children: [
{text: "instanceof模式匹配", link: "jep394-pattern-matching-for-instanceof"},
{text: "record类", link: "jep395-records"},
],
},
{
text: "Java 15", prefix: "Java15/", link: "Java15/",
children: [
{text: "隐藏类(Hidden Classes)", link: "jep371-hidden-classes"},
{text: "文本块(Text Blocks)", link: "jep378-text-blocks"},
],
},
{
text: "Java 14", prefix: "Java14/", link: "Java14/",
children: [
{text: "switch表达式增强", link: "jep361-switch-expressions"},
],
},
{
text: "Java 11", prefix: "Java11/", link: "Java11/",
children: [
{text: "移除JavaEE和CORBA模块", link: "jep320-remove-JavaEE-CORBA"},
],
},
{
text: "Java 10", prefix: "Java10/", link: "Java10/",
children: [
{text: "局部变量类型推断", link: "jep286-local-variable-type-inference"},
],
},
{
text: "Java 9", prefix: "Java9/", link: "Java9/",
children: [
{text: "交互式编程环境JShell", link: "jep222-jshell"},
{text: "不可变集合的快捷创建方法", link: "jep269-convenience-factory-methods-for-collections"},
],
}
]
);
================================================
FILE: src/.vuepress/path/sidebar/java.ts
================================================
import {arraySidebar} from "vuepress-theme-hope";
export const java = arraySidebar([
{
text: "Java 基础", prefix: "basic/", /*link: "basic/",*/
children: [
{text: "快速入门", link: "java-from-scratch"},
{text: "面向对象编程(OOP)", link: "java-basic-oop"},
{text: "常用类与工具", link: "java-common-classes"},
],
},
{
text: "文章",
children: [
{text: "Java 多线程与并发", icon: "thread", link: "thread/thread-concurrency"},
{text: "Java 程序员快速掌握 Kotlin", icon: "kotlin", link: "kotlin/kotlin-quick-for-java"},
],
},
{
text: "专栏",
children: [
{text: "Java 日期时间", icon: "java", link: "datetime/"},
{text: "Java 新版本特性", icon: "java", link: "features/"},
{text: "深入理解Java虚拟机", icon: "jvm-xx", link: "jvm/"},
],
},
]
);
================================================
FILE: src/.vuepress/path/sidebar/jvm.ts
================================================
import {arraySidebar} from "vuepress-theme-hope";
export const jvm = arraySidebar([
{text: "总目录", prefix: "/md/java/jvm/", link: "/md/java/jvm/",},
{
text: "走近Java", prefix: "part1/", link: "part1/",
children: [
{text: "JVM概述", link: "overview"},
{text: "编译JDK", link: "compile_jdk"},
],
},
{
text: "自动内存管理", prefix: "part2/", link: "part2/",
children: [
{text: "运行时数据区", link: "runtime-data-areas"},
{text: "对象分配过程", link: "heap-object-flow"},
{
text: "基础故障处理工具", prefix: "basic-tools/", link: "basic-tools/",
collapsible: true,
children: [
// {text: "依赖注入", link: "factory-collaborators"}
]
},
{
text: "可视化故障处理工具", prefix: "visual-tools/", link: "visual-tools/",
collapsible: true,
children: [
{text: "VisualVM介绍", link: "visualvm"}
]
},
],
},
{
text: "虚拟机执行子系统", prefix: "part3/", link: "part3/",
children: [
{text: "类文件结构", link: "class-file-structure"},
{text: "字节码指令集", link: "bytecode-instructions-set"},
{text: "类加载机制", link: "class-loading-mechanism"},
],
},
{
text: "程序编译与代码优化", prefix: "part4/", link: "part4/",
children: [],
},
{
text: "高效并发", prefix: "part5/", link: "part5/",
children: [],
},
]
);
================================================
FILE: src/.vuepress/path/sidebar/spring-framework.ts
================================================
import {arraySidebar} from "vuepress-theme-hope";
export const springFramework = arraySidebar([
{
text: "概述", collapsible: true, prefix: "overview/", link: "overview/",
children: [{text: "快速开始", link: "quickstart"}]
},
{
text: "核心技术", collapsible: true, prefix: "core/", link: "core/",
children: [
{text: "IoC容器", link: "ioc-container"},
{text: "Bean定义", link: "beans-definition"},
{
text: "依赖", prefix: "dependencies/", link: "dependencies/",
collapsible: true,
children: [
{text: "依赖注入", link: "factory-collaborators"},
{text: "依赖和配置详解", link: "factory-properties-detailed"},
{text: "使用depends-on", link: "factory-dependson"},
{text: "懒加载Bean", link: "factory-lazy-init"},
{text: "自动装配协作者", link: "factory-autowire"},
{text: "方法注入", link: "factory-method-injection"},
]
},
{text: "Bean作用域", link: "beans-scope"},
{text: "Bean生命周期", link: "beans-lifecycle"},
{text: "Bean定义继承", link: "child-bean-definitions"},
]
},
]
);
================================================
FILE: src/.vuepress/public/CNAME
================================================
www.geekyspace.cn
================================================
FILE: src/.vuepress/public/special/wedding-chenzhuo.html
================================================
新婚快乐
🎉🎊 囍 新婚快乐 囍 🎊🎉
亲爱的 卓哥,
今天是你人生中最重要的日子之一,
从懵懂少年到如今的新郎官,
见证你的成长,真的由衷替你高兴!🎉
愿你与 伟娜 携手同行,相知相守,
往后余生,岁月温柔,爱情长存 💕💕
💖 祝你们百年好合,白头偕老!💖
================================================
FILE: src/.vuepress/styles/config.scss
================================================
// 你可以在这里更改配置 https://theme-hope.vuejs.press/zh/config/style.html
/* Content Class */
$content-class: ".theme-hope-content";
/* responsive breakpoints */
// wide screen
$pc: 1440px !default;
// desktop
$laptop: 1280px !default;
// narrow desktop / iPad
$pad: 959px !default;
// wide mobile
$tablet: 719px !default;
// narrow mobile
$mobile: 419px !default;
/* Color list */
$colors: #cf1322, #fa541c, #f39c12, #2ecc71, #25a55b, #10a5a5, #096dd9, #aa6fe9,
#eb2f96 !default;
/* Code Block */
// only available with shiki highlighter
$code-color: (
light: #383a42,
dark: #abb2bf,
) !default;
$code-bg-color: (
light: #ecf4fa,
dark: #282c34,
) !default;
================================================
FILE: src/.vuepress/styles/index.scss
================================================
// fix:解决新版Chrome字体加粗
body {
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-size: 16px;
}
// 导航栏
#navbar {
border-bottom: 1px solid var(--border-color);
// 去掉父级标题“文本转换为大写字母”
.dropdown-subtitle {
text-transform: none;
}
}
// 侧边栏
#sidebar {
border-right: 1px solid var(--border-color);
font-size: 0.86em;
scrollbar-color: var(--border-color);
//padding-inline-start: 0;
// 父级目录字体加粗
.vp-sidebar-title {
font-weight: 700;
}
}
// 设置博客上边距
.blog-page-wrapper {
margin-top: 20px;
}
/**
* ========== Markdown 样式 ==========
*/
// 修改 > 块引用 样式
html[data-theme] blockquote {
border-left: 4px solid var(--theme-color-dark);
padding: 10px 15px;
color: var(--text-color);
background-color: var(--theme-color-mask);
}
// 修改 `标记` 样式
html[data-theme] mark {
border-radius: 2px;
padding: 2px 4px;
margin: 0 2px;
font-weight: 500;
color: var(--text-color);
background-color: var(--theme-color-mask);
}
// 修改 “表格” 样式
table {
// 默认情况下,“表格” 宽度不会占满整个屏幕,这里设置为 100%
display: inline-table;
//width: 100%;
border-radius: 1px;
margin: 0;
th {
text-align: left;
color: #fff;
background-color: var(--theme-color-dark);
}
tr:nth-child(odd) {
background-color: var(--theme-color-mask);
}
// 鼠标悬停时改变行颜色
tr:hover {
color: var(--theme-color);
html[data-theme=light] & {
background-color: #ebebeb;
}
html[data-theme=dark] & {
background-color: #30363d;
}
}
// 去除“表格”的边框,看起来更加美观
thead, tbody, tfoot, tr, td, th {
border-width: 0 0 1px 0;
}
}
// 视觉优化 “夜间模式”
html[data-theme=dark] {
--bg-color: #22272e; /*页面背景颜色*/
--navbar-bg-color: #22272e; /*导航栏背景颜色*/
--sidebar-bg-color: #22272e; /*侧边栏背景颜色*/
--bg-color-back: #22272e; /*首页页背景颜色*/
--bg-color-float: #2b313a; /*首页卡片背景颜色*/
--text-color: #e6edf3; /*页面字体颜色*/
--grey3: #e6edf3; /*首页项目卡片字体颜色*/
--dark-grey: #e6edf3; /*图标文字颜色*/
--light-grey: #e6edf3; /*图标文字颜色*/
}
html[data-theme=light] {
//--dark-grey: #2c3e50; /*图标文字颜色*/
--light-grey: #2c3e50; /*图标文字颜色*/
}
// 夜间模式下降低图片亮度
html[data-theme="dark"] img {
filter: brightness(0.7); /* 适当降低亮度 */
transition: filter 0.3s ease-in-out;
}
// 将博主信息移动至文章列表的左侧
@import "vuepress-theme-hope/presets/left-blog-info.scss";
================================================
FILE: src/.vuepress/styles/palette.scss
================================================
// 设置默认主题色
$theme-color: #3eaf7c;
// 主题色选择器
$theme-colors: #2196f3, #a266f3, #e04c8a, #fb9b5f;
================================================
FILE: src/.vuepress/theme.ts
================================================
import {hopeTheme} from "vuepress-theme-hope";
import navbar from "./path/navbar.js";
import sidebar from "./path/sidebar";
export default hopeTheme({
/**
* 信息选项
*/
// 站点图标
favicon: "/images/system/geeky.png",
// 全局默认作者
author: {
name: "流浪码客",
url: "https://github.com/joeljhou",
email: "joeljhou336@gmail.com"
},
// 全局默认协议
license: "MIT",
// 网站部署域名
hostname: "https://www.geekyspace.cn",
/**
* 外观选项
*/
// 深色模式支持选项 (switch:默认)
darkmode: "toggle",
/**
* 导航栏本地选项
*/
navbar,
// 导航栏标题
// navTitle: "极客空间",
// 导航栏图标
logo: "/images/system/geeky.png",
// logoDark: "/images/system/geeky_zh.png",
// 全屏按钮
fullscreen: false,
// 字体图标资源链接(阿里巴巴iconfont矢量图标:https://www.iconfont.cn/)
iconAssets: "//at.alicdn.com/t/c/font_4370612_ke2l7xy0pf.css",
// 页面显示信息:作者、原创、分类、日期、标签、阅读时间、字数、阅读量
// "Author", "Original", "Category", "Date", "Tag", "ReadingTime", "Word", "PageView"
pageInfo: ["Author", "Original", "Date", "Category", "Tag", "ReadingTime"],
// 是否在向下滚动时自动隐藏导航栏 "always" | "mobile" | "none"
navbarAutoHide: "mobile",
// 导航栏布局设置
navbarLayout: {
start: ["Brand"],
center: ["Links"],
end: ["Repo", "Outlook", "Search"],
},
// 是否在移动视图下隐藏站点名称
hideSiteNameOnMobile: false,
/**
* 侧边栏本地选项
*/
sidebar,
// 是否在侧边栏显示图标
sidebarIcon: true,
// 侧边栏和页面目录的标题深度, 默认值为 2
headerDepth: 2,
/**
* 页脚选项
*/
footer: "© 2023 - 至今 www.geekyspace.cn 保留所有权利",
// 默认的版权信息,设置为 false 来默认禁用它
copyright: "Copyright © 2026 流浪码客",
// 是否默认显示页脚
displayFooter: true,
/**
* 功能区域设置选项
*/
// 博客功能配置
blog: {
avatar: "/images/personal/geekyspace.png",
description: "流浪码客,分享技术,品味人生",
intro: "/about-me.html",
medias: {
GitHub: "https://github.com/joeljhou",
Gitee: "https://gitee.com/joeljhou",
BiliBili: "https://space.bilibili.com/3546587190004175",
CSDN: [
"https://blog.csdn.net/qq_40174960?type=blog",
""
],
Bokeyuan: [
"https://www.cnblogs.com/geekyspace",
""
],
Zhihu: "https://www.zhihu.com/people/joeljhou",
Juejin: [
"https://juejin.cn/user/2911162523717640",
""
],
XiaoHongShu: "https://www.xiaohongshu.com/user/profile/5dd53e0c0000000001009bf1",
WechatMP: "https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MzI4MTMwMDg4MA==",
Tieba: "https://tieba.baidu.com/home/main?un=%E6%9E%81%E5%AE%A2%E8%8B%B1%E9%9B%84",
Weibo: "https://weibo.com/u/7788864199",
Twitter: "https://twitter.com/joeljhou336",
Rss: "./rss.xml",
},
},
/*
* 文章目录加密:
* https://theme-hope.vuejs.press/zh/guide/feature/encrypt.html
*/
encrypt: {
config: {
// 这会加密整个 demo 目录,并且两个密码都是可用的
// "/idea-tips/activation.html": ["52ff"],
},
},
// 是否展示编辑此页链接
editLink: true,
// 编辑此页链接地址
docsRepo: "https://github.com/joeljhou/joeljhou.github.io",
docsDir: "src",
docsBranch: "master",
// page meta
metaLocales: {
editLink: "编辑此页",
},
plugins: {
blog: true,
// 开启git实现编辑此页面-最后更新时间-贡献者功能
git: true,
/*
* 版权信息在复制时自动附加-vuepress-plugin-copyright2
*/
copyright: {
global: false,
triggerLength: 100,
author: "GeekySpace",
license: "MIT",
},
// 代码复制功能-vuepress-plugin-copy-code2
copyCode: {
showInMobile: true,
},
// Feed生成器-vuepress-plugin-feed2
feed: {
rss: true,
},
// install @waline/client before enabling it
// WARNING: This is a test server for demo only.
// You should create and use your own comment service in production.
// comment: {
// provider: "Waline",
// serverURL: "https://waline-comment.vuejs.press",
// },
// MarkDown文件增强-vuepress-plugin-md-enhance
mdEnhance: {
align: true,
attrs: true,
// install chart.js before enabling it
// chart: true,
codetabs: true,
// insert component easily
// component: true,
demo: true,
// install echarts before enabling it
// echarts: true,
figure: true,
// install flowchart.ts before enabling it
// flowchart: true,
// gfm requires mathjax-full to provide tex support
// gfm: true,
imgLazyload: true,
imgSize: true,
include: true,
// install katex before enabling it
// katex: true,
// install mathjax-full before enabling it
// mathjax: true,
mark: true,
// install mermaid before enabling it
mermaid: true,
playground: {
presets: ["ts", "vue"],
},
// install reveal.js before enabling it
// revealJs: {
// plugins: ["highlight", "math", "search", "notes", "zoom"],
// },
stylize: [
{
matcher: "Recommended",
replacer: ({tag}) => {
if (tag === "em")
return {
tag: "Badge",
attrs: {type: "tip"},
content: "Recommended",
};
},
},
],
// 启用剧透,目前版本不支持
// spoiler: true,
// 启用下角标
sub: true,
// 启用上角标
sup: true,
tabs: true,
vPre: true,
// install @vue/repl before enabling it
// vuePlayground: true,
},
// MarkDown启用组件-vuepress-plugin-components
components: {
components: [
// 为站点提供了在MD文档中自定义颜色的徽章
"Badge",
// 为站点提供了在MD文档中加载B站视频的功能,但是不建议使用
"BiliBili",
// 为站点提供了在MD文档中加载PDF阅读器的功能,但是不建议使用
// 原因一:PDF书籍较大,上传到码云后会大量占用码云空间
// 原因二:当PDF阅读器较多的时候,将MD文档渲染成HTML页面比较耗费性能,使页面加载速度变慢
"PDF",
]
},
// uncomment these if you want a PWA
// pwa: {
// favicon: "/favicon.ico",
// cacheHTML: true,
// cachePic: true,
// appendBase: true,
// apple: {
// icon: "/assets/icon/apple-icon-152.png",
// statusBarColor: "black",
// },
// msTile: {
// image: "/assets/icon/ms-icon-144.png",
// color: "#ffffff",
// },
// manifest: {
// icons: [
// {
// src: "/assets/icon/chrome-mask-512.png",
// sizes: "512x512",
// purpose: "maskable",
// type: "image/png",
// },
// {
// src: "/assets/icon/chrome-mask-192.png",
// sizes: "192x192",
// purpose: "maskable",
// type: "image/png",
// },
// {
// src: "/assets/icon/chrome-512.png",
// sizes: "512x512",
// type: "image/png",
// },
// {
// src: "/assets/icon/chrome-192.png",
// sizes: "192x192",
// type: "image/png",
// },
// ],
// shortcuts: [
// {
// name: "Demo",
// short_name: "Demo",
// url: "/demo/",
// icons: [
// {
// src: "/assets/icon/guide-maskable.png",
// sizes: "192x192",
// purpose: "maskable",
// type: "image/png",
// },
// ],
// },
// ],
// },
// },
},
}, {custom: true});
================================================
FILE: src/README.md
================================================
---
# 博客主页配置:https://theme-hope.vuejs.press/zh/config/frontmatter/blog-home.html
home: true
layout: BlogHome
hero: false
title: 最新发布
icon: home
projects:
- icon: launch
name: 开发者安装大全
desc: 好用工具、开发环境、中间件配置等安装指南
link: /md/installation-guide/
- icon: java
name: Java新特性
desc: 从Java 8开始所有新特性解读
link: /md/java-features/
- icon: intellij-idea
name: IDEA激活
desc: 了解IDEA更多牛x功能、推荐很有意思的主题和插件
link: /md/idea-tips/activation
- icon: spring
name: Spring实战
desc: 深入理解Spring框架
link: /md/spring-framework/core/
- icon: mysql
name: MySQL教程
desc: MySQL必知必会、高性能架构、排错指南
link: /mysql/
- icon: aigc
name: AIGC新时代
desc: 提供生成式人工智能的入门和进阶教程
link: /aigc/
- icon: cloud-service
name: 云原生技术
desc: 分享云原生架构的最佳实践和使用指南
link: /cloud-native/
- icon: smart-contracts
name: 区块链智能合约
desc: 提供区块链技术和智能合约开发的指南
link: /md/blockchain/
footer: © 2023 - 至今 www.geekyspace.cn 保留所有权利
copyright: Copyright © 2026 流浪码客
---
================================================
FILE: src/about-me.md
================================================
---
# 信息 Frontmatter 配置
title: 个人简介
description: 一个专注于技术分享的博客网站
icon: circle-info
# cover: /assets/images/cover3.jpg
author: 流浪码客
isOriginal: false # 是否原创文章
date: 1998-12-14
category: 程序人生
tag: 程序人生
# 布局 Frontmatter 配置
sidebar: false # 禁用侧边栏
---
# 个人简介 | 一个专注于技术分享的博客网站
欢迎访问我的博客网站 [www.geekyspace.cn](www.geekyspace.cn),与我一起探索科技的未来!
本博客维护人周宇,在Java和相关技术领域拥有多年的专业经验。
此前,我在深圳南山 [Liquido](https://www.liquido.com/) 工作,这是一家专注于金融领域,
提供拉美地区[支付解决方案](https://mp.weixin.qq.com/s/UzPsaVvXqrvW8T8PDPnhZw)的公司。
**贴上“程序员”标签**
1. 个人兴趣:我从小就对工科方向的工作非常感兴趣,喜欢倒腾电子产品,研究它们的原理,拆了又装。
2. 家庭影响:我有一个“伢伢”,比我大10多岁,他学的就是计算机,赚钱也多。
他毕业后在一家电商公司工作,后来被京东收购,之后去了高德,又被阿里收购。在它的影响下,我最终成为了一名程序员。
**程序员的价值体现**
做程序员是幸运的,世界的本质是价值交换。
作为程序员,我最大的成就感在于能否真正给别人带来价值。如果可以,那泰裤辣。
大环境没有那么差(给自己信心),这是程序员的时代!!!
* 人生没有白走的路,每一步都算数(我要坚持坚持坚持)
**我的程序员格言**
1. 编码原则:🥇 代码简洁高效,优雅永不过时 ✨
2. 架构核心:🔧 掌握系统控制权,确保可扩展性和灵活性
3. 技术理念:🌐 技术广度是技术深度的副产品
4. 职业信仰:🚀 当坚持成为一种热爱,极致成为一种精神,那么成功就是一种必然
5. 长期主义:⏳ 注重长远发展,坚持不懈地追求卓越
================================================
FILE: src/article.md
================================================
---
home: true
icon: home
title: 文库汇总
# 指定首页背景头像
heroImage: /images/personal/geekyspace.png
heroImageDark: /images/personal/geekyspace.png
# 指定首页背景图片
#bgImage: /assets/bg/wallhaven-8586my.png
#bgImageDark: /assets/bg/wallhaven-o5762l.png
bgImageStyle:
background-attachment: fixed
heroText: 文库汇总
tagline: 极客空间,一个专注于技术分享的博客网站✨
actions:
- text: 首页
icon: home
link: /
type: primary
- text: 文章
icon: article
link: /article/
features:
- title: RabbitMQ
icon: rabbitmq
# icon: https://avatars.githubusercontent.com/u/96669?s=48&v=4
details: 高并发实战-RabbitMQ消息队列
link: https://github.com/joeljhou/RabbitMQ
- title: JVM
icon: javaJvm-xx
details: 深入理解Java虚拟机
link: /md/java/jvm/
- title: Docker
icon: docker
details: Docker容器化最佳实践
link: /md/docker/overview
- title: K8S
icon: k8s
details: Kubernetes云原生时代
- title: MySQL
icon: mysql
# icon: https://labs.mysql.com/common/logos/mysql-logo.svg
details: MySQL原理与SQL调优
- title: Redis
icon: redis
#icon: https://redis.io/wp-content/uploads/2024/04/Logotype.svg
details: Redis缓存高可用实战
- title: Elasticsearch
icon: elasticsearch
#icon: https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt36f2da8d650732a0/5d0823c3d8ff351753cbc99f/logo-elasticsearch-32-color.svg
details: Elasticsearch搜索引擎
- title: Google Istio
icon: istio_bypass_mode_s
details: Google Istio服务网格
copyright: false
footer: "© 2023 - 至今 www.geekyspace.cn 保留所有权利"
---
================================================
FILE: src/md/blockchain/README.md
================================================
---
title: 区块链基础
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2025-11-06
category: blockchain
tags:
- blockchain
---
# 区块链基础
## 注册美国/新加坡AppleID
参考:[B站视频](https://www.bilibili.com/video/BV1Tg1MBmEk4)
**注册成功后**
- 闲鱼/淘宝购买礼品卡,防止被封
- 礼品卡金额可用来购买小火箭(Shadowrocket)软件,$2.99
## 交易所实操1
1. 选择交易所
- [Binance(币安)](https://www.binance.com/zh-CN) - 新加坡、香港
- [OKX(欧易)](https://www.okx.com/zh-hans)
2. 身份认证
3. 充值(资金)
- 法币充值,得到数字货币。其中法币是指国家发行的货币。
- [Binance(币安)- C2C 快捷区](https://p2p.binance.com/zh-CN/express/buy/USDT/CNY)
- [OKX(欧易)- C2C 快捷交易](https://www.okx.com/zh-hans/p2p/express/cny/buy-usdt)
4. 划转
- 将**资产账户**的资金划转到**现货账户**,以便进行现货交易。
- 资金账户:存放充值的法币或数字资产
- 现货账户:用于现货交易(买卖币种)
- 合约账户:用于合约交易(U本位/币本位合约),如`BTC-USDT` 永续合约。
- `BTC-USDT` 是一个**交易对**,表示用 USDT 买 BTC,或卖 BTC 换 USDT。
- [Binance(币安)- 钱包总览下划转](https://www.binance.com/zh-CN/my/wallet/account/overview)
- [OKX(欧易)- 资金划转](https://www.okx.com/zh-hans/balance/transfer)
5. 买币
* 现货:购买`BNB/USDT`,可抵扣平台20%手续费
* 合约:
* **U本位合约**,使用稳定币 USDT结算保证金和盈亏。
* **币本位合约**,使用加密货币本身(比如 BTC、ETH)结算保证金和盈亏。
* 买币一定要委托**止盈/止损**策略,一定要止损!!!
6. 卖币
7. 提币
* **从币安提币(withdraw) → 到欧易充币(deposit)地址**
* 选择币种(USDT)
* 选择网络(**重要!** 需与币安提币网络一致)
* 不同网络手续费不一样,`APT(Aptos)`收费费低,欧易能看到具体费用标准
================================================
FILE: src/md/database/mysql/README.md
================================================
---
title: MySQL 必知必会
shortTitle:
description:
icon: mysql
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2025-09-18
category: DataBase
tags:
- DataBase
---
# MySQL 必知必会
开启一个新的专栏,介绍 MySQL 的一些必知必会的内容。
================================================
FILE: src/md/database/mysql/overview/what-is-database.md
================================================
---
title: 什么是数据库?
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2025-09-18
category: DataBase
tags:
- DataBase
---
# 什么是数据库?
## 数据库的定义
> 这个术语的用法很多,对于程序员来说,数据库是一个**有组织的数据集合**,由 [数据库管理系统 (DBMS)](https://www.oracle.com/cn/database/what-is-database/#WhatIsDBMS) 来管理。在计算机系统中,数据、DBMS 和相关应用程序统称为**数据库系统**,简称数据库。
- 理解数据库的一种最简单的办法是将其想象为一个**文件柜**。
- 此文件柜是一个存放数据的物理位置,不管数据是什么以及如何组织的。
## 什么是SQL?
> **SQL**(Structured Query Language,**结构化查询语言**)是一种专门**与[关系数据库](https://www.oracle.com/cn/database/what-is-database/#relational)通信的编程语言**。 可以使用 SQL 语句从数据库中存储、更新、删除、搜索和检索信息。您还可以使用 SQL 来维护和优化数据库性能。
**历史背景**
1. SQL 最初由 IBM 在 20 世纪 70 年代开发,用于关系数据库操作。
2. Oracle 是主要贡献者之一,推动了 SQL 的 ANSI 标准化。
3. 随着 SQL 的广泛应用,IBM、Oracle、Microsoft 等公司相继扩张其数据库业务。
4. 时至今日,SQL 仍是主流数据库的标准语言,但新兴语言和技术也逐渐出现。
## 数据库的类型
1. 关系数据库
2. 非关系型数据库
## 序
* [什么是数据库? | Oracle 中国](https://www.oracle.com/cn/database/what-is-database)
* [什么是数据库?- 云数据库简介 - AWS](https://aws.amazon.com/cn/what-is/database)
================================================
FILE: src/md/docker/install.md
================================================
---
title: Docker安装
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-11-15
category: Docker
tag:
- docker
---
# Docker安装&配置
您可以在多个平台上下载并安装 Docker。请参阅以下部分并选择最适合您的安装路径。
> [适用于Mac的Docker桌面](https://docs.docker.com/desktop/setup/install/mac-install/)
> [适用于Windows的Docker桌面](https://docs.docker.com/desktop/setup/install/windows-install/)
> [适用于Linux的Docker桌面](https://docs.docker.com/desktop/setup/install/linux/)
**验证是否安装成功:**
```shell
$ docker version # 查看版本
$ docker info # 查看信息
```
**运行测试镜像**
```shell
$ docker run hello-world
$ docker run -it ubuntu bash
```
可能会遇到如下网络错误,这个时候就需要配置[镜像源加速器](/md/docker/mirror-acceleration.html)
`docker: error pulling image configuration: download failed after attempts=6: EOF.`
================================================
FILE: src/md/docker/mirror-acceleration.md
================================================
---
title: Docker镜像加速器
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-11-16
category: Docker
tag:
- docker
---
# Docker镜像加速器
国内从`Docker Hub`拉取镜像有时会遇到困难,此时可以配置镜像加速器。
## 阿里云镜像加速器
> [阿里云镜像加速器](https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors),有针对`Ubuntu`,`CentOS`,`Mac`,`Windows`的操作文档

## Linux
对于使用`Ubuntu`或`CentOS`的系统,请在`/etc/docker/daemon.json`文件中写入如下内容(如果文件不存
在请新建该文件)
```shell
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://onnxqmp4.mirror.aliyuncs.com"]
}
EOF
# 之后重新启动服务
sudo systemctl daemon-reload
sudo systemctl restart docker
```
## Windows
对于使用Windows的系统,在`docker desktop`右上角**齿轮**图标,打开配置窗口后选择`Docker Engine`。
编辑JSON串,填写加速器地址:
Windows: `%USERPROFILE%\.docker\daemon.json`

**完整的`daemon.json`配置示例:**
```shell
{
"builder": {
"gc": {
"defaultKeepStorage": "20GB",
"enabled": true
}
},
"experimental": false,
"registry-mirrors": [
"https://onnxqmp4.mirror.aliyuncs.com",
"https://docker.hpcloud.cloud",
"https://docker.m.daocloud.io",
"https://docker.1panel.live",
"http://mirrors.ustc.edu.cn",
"https://docker.chenby.cn",
"https://docker.ckyl.me",
"http://mirror.azure.cn",
"https://hub.rat.dev"
]
}
```
## MacOS

================================================
FILE: src/md/docker/overview.md
================================================
---
title: Docker概述
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-11-15
category: Docker
tag:
- docker
---
# Docker概述
## 什么是Docker?
`Docker`是`dotCloud`团队在2013年发布的开源项目。 使用`Go`语言开发,是一个轻量级的虚拟机容器解决方案。
## 为什么使用Docker?
Docker跟传统虚拟机相比,具有以下优势:
* 更高效的利用系统资源
* 更快速的启动速度
* 一致的运行环境
* 持续交付和部署
* 更轻松的迁移
* 更轻松的维护和扩展
**`Docker` VS `传统虚拟机`**
| 特性 | 容器 | 虚拟机 |
|-------|-----------|--------|
| 启动 | 秒级 | 分钟级 |
| 硬盘使用 | 一般为 MB | 一般为 GB |
| 性能 | 接近原生 | 弱于 |
| 系统支持量 | 单机支持上千个容器 | 一般几十个 |
## 基本概念
* **镜像(Image)**:Docker镜像是一个只读的模板,包含了运行容器所需的所有文件。
* 本质是==文件系统==
* 基于`Union FS`设计,**分层存储**,可以叠加
* **容器(Container)**:Docker容器是可独立运行的一个或一组应用,是Docker镜像的运行实例。
* 实质是==进程==
* 拥有自己的`root`文件系统,网络配置,进程空间等
* 最佳实践:文件写入操作使用**数据卷(Volume)**,避免文件写入到容器中
* **仓库(Repository)**:Docker Registry是一个集中存储、分发镜像服务。
* [Docker Hub](https://hub.docker.com/) 官方镜像仓库
* [Google Container Registry](https://cloud.google.com/artifact-registry/docs?hl=zh-cn) K8s 镜像仓库
* [Amazon ECR](https://aws.amazon.com/cn/ecr/) AWS 镜像仓库
* [VMWare Harbor](https://github.com/goharbor/harbor) 和 [Sonatype Nexus](https://www.sonatype.com/docker)
三方软件实现了Docker Registry API
## Docker执行流程
* 客户端发指令 → 守护进程接收指令 → 检查镜像(本地/Docker Hub) → 创建容器 → 启动并运行。

## Docker架构
Docker 的架构设计基于 **客户端-服务器模型**,主要包含以下核心组件:

1. **Client(客户端)** :用户与Docker交互的界面,通过命令行或API发送指令给Docker daemon。
* `docker run`:创建并启动一个容器。
* `docker build`:根据Dockerfile构建一个新的镜像。
* `docker pull`:从注册中心拉取一个镜像。
* `docker push`:将本地镜像推送到注册中心。
2. **Docker daemon(守护进程)** :Docker引擎的核心,负责监听、处理客户端的指令,并管理Docker对象(镜像、容器等)。
* `镜像管理`:管理本地镜像,包括存储、加载和删除。
* `容器管理`:创建、启动、停止、删除容器。
* `网络管理`:为容器提供网络接口。
* `存储管理`:管理容器的数据卷。
3. **Images(镜像)** :Docker镜像是一个只读的模板,包含了运行容器所需的所有文件
* `分层结构`:镜像由多层组成,每一层代表一个构建步骤。
* `只读`:镜像是只读的,保证了镜像内容的不可变性。
4. **Containers(容器)** :Docker容器是镜像的运行实例,是应用程序的运行环境。
* `隔离性`:每个容器都有独立的文件系统、网络配置和进程空间。
* `可复用性`:基于同一个镜像可以创建多个容器实例。
5. **Registry(注册中心)** :用于存储和分发Docker镜像的仓库。
* `集中存储`:将镜像存储在集中式的仓库中,方便管理和共享。
* `版本控制`:支持镜像的版本管理。
================================================
FILE: src/md/docker/top20-commands.md
================================================
---
title: Top20常用命令
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-11-23
category: Docker
tag:
- docker
---
# Top20常用命令
作为一款领先的容器化工具,Docker 提供了强大的功能,让开发者和运维人员能够快速构建、部署和管理应用。
在这篇文章中,我们将介绍 20 条最常用的 Docker 命令,并结合详细说明,帮助大家轻松掌握 Docker 的基本操作。
* [1. Docker version](#docker-version)
* [2. Docker search](#docker-search)
* [3. Docker pull](#docker-pull)
* [4. Docker run](#docker-run)
* [5. Docker ps](#docker-ps)
* [6. Docker stop](#docker-stop)
* [7. Docker restart](#docker-restart)
* [8. Docker kill](#docker-kill)
* [9. Docker exec](#docker-exec)
* [10. Docker login](#docker-login)
* [11. Docker commit](#docker-commit)
* [12. Docker push](#docker-push)
* [13. Docker network](#docker-network)
* [14. Docker history](#docker-history)
* [15. Docker rmi](#docker-rmi)
* [16. Docker ps -a](#docker-ps--a)
* [17. Docker copy](#docker-copy)
* [18. Docker logs](#docker-logs)
* [19. Docker volume](#docker-volume)
* [20. Docker logout](#docker-logout)
**相关文档**
* [Docker 命令参考文档](https://docs.docker.com/engine/reference/commandline/cli/)
* [Dockerfile 镜像构建参考文档](https://docs.docker.com/engine/reference/builder/?spm=5176.8351553.0.0.4ef81991wFvDZm)
## Docker version
用途:显示 Docker 的版本信息。
```bash
docker version
```

## Docker search
用途: 用于搜索 Docker Hub 上的镜像。
```shell
docker search nginx
```

## Docker pull
用途:从 Docker Hub 下载镜像。
```shell
docker pull nginx
```

## Docker run
用途:运行一个镜像,并创建一个容器。
```shell
docker run -d --name my-mysql -e MYSQL_ROOT_PASSWORD=root -p 3307:3306 mysql:8.0
```
参数解释:
* `-d`:后台运行容器,并返回 Shell。
* `-e MYSQL_ROOT_PASSWORD=root`:设置环境变量,用于设置 MySQL 的 root 用户密码。
* `-p 3306:3306`:将主机的端口 3306 映射到容器的端口 3306。
* `mysql:8.0`:要下载的镜像的名称。

## Docker ps
用途:列出当前正在运行的容器。
```shell
docker ps
```
## Docker stop
用途:停止一个正在运行的容器。
```shell
docker stop
```
## Docker restart
用途:重启一个容器。
```shell
docker restart
```
## Docker kill
用途:强制停止一个容器。
```shell
docker kill
```
## Docker exec
用途:在运行中的容器中执行命令。
```shell
docker exec -it bash
```
参数解释:
* `-it`:保持 STDIN 打开并允许使用键盘输入。
* ``:要进入的容器的ID。
* `bash`:要执行的命令。

## Docker login
用途:登录 Docker Hub,用于推送私有镜像。
```shell
docker login
```

## Docker commit
用途:创建一个镜像,该镜像包含一个容器的当前状态。
```shell
docker commit
```
参数解释:
* ``:要提交为镜像的容器的ID。
* ``:新镜像的名称。
## Docker push
用途:将镜像推送到 Docker Hub。
```shell
docker push
```
## Docker network
用途:管理 Docker 网络。
```shell
docker network create
docker network inspect
docker network rm
docker network connect
docker network disconnect
```
参数解释:
* ``:要创建、查看、删除或连接的 Docker 网络的名称。
* ``:要连接到或断开连接的网络的容器的ID。
## Docker history
用途:显示镜像的创建历史。
```shell
docker history
```
参数解释:
* ``:要查看其创建历史的镜像的名称。
## Docker rmi
用途:删除镜像。
```shell
docker rmi
```
## Docker ps -a
用途:列出所有容器,包括已停止的容器。
```shell
docker ps -a
```

## Docker copy
用途:复制文件或文件夹到容器。
```shell
docker cp :
```
参数解释:
* ``:主机上的文件或文件夹的路径。
* ``:要复制文件的容器的ID。
* ``:容器内的目标路径。
## Docker logs
用途:查看容器的日志。
```shell
docker logs
```
参数解释:
* ``:要查看其日志的容器的ID。
## Docker volume
用途:管理 Docker 卷。
```shell
docker volume create
docker volume inspect
docker volume rm
docker run -v :/data nginx
```
参数解释:
* ``:要创建、查看或删除的卷的名称。
* `nginx`:要运行的镜像名称。
* `/data`:要挂载到容器内的卷的挂载点。
## Docker logout
用途:注销 Docker Hub。
```shell
docker logout
```
================================================
FILE: src/md/flutter/flutter-guide.md
================================================
---
title: Flutter入门指南
shortTitle:
description: Flutter是一个跨端的平台开发框架
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2025-09-26
category: Flutter
tags:
- Flutter
---
# Flutter入门
> Flutter 是 Google 推出的跨平台 UI 框架,使用 **Dart** 语言,可一次开发同时运行在 **Android / iOS / Web / 桌面端**。
- 核心理念:**一切皆为组件(Widget)**。
## 环境搭建
### 安装 Flutter SDK
- [SDK 归档列表](https://docs.flutter.cn/release/archive?tab=macos)
- [国内镜像加速配置](https://docs.flutter.cn/community/china/)
**解压与环境变量配置**
```shell
# 假设解压路径为 ~/Development/sdk/flutter
export PATH="$HOME/Development/sdk/flutter/bin:$PATH"
# 国内镜像
export PUB_HOSTED_URL="https://pub.flutter-io.cn"
export FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn"
# 配置生效
source ~/.zshrc
```
**检查安装是否成功**
```shell
flutter --version
dart --version
```
**环境诊断命令:`flutter doctor`**
```shell
$ flutter doctor
Doctor summary (查看详细信息,请运行 flutter doctor -v):
[✓] Flutter (稳定通道, 版本 3.35.4, 运行于 macOS 15.6.1 24G90 darwin-arm64, 语言环境 zh-Hans-CN)
[✗] Android 工具链 - 用于开发 Android 设备
✗ 无法找到 Android SDK。
请从 https://developer.android.com/studio/index.html 安装 Android Studio
首次启动会协助你安装 Android SDK 组件。
(或者访问 https://flutter.dev/to/macos-android-setup 获取详细说明)。
如果 Android SDK 已安装在自定义路径,请使用
`flutter config --android-sdk` 更新该路径。
[✗] Xcode - 用于开发 iOS 和 macOS
✗ Xcode 安装不完整;iOS 和 macOS 开发需要完整安装。
下载地址: https://developer.apple.com/xcode/
或通过 App Store 安装 Xcode。
安装完成后运行:
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -runFirstLaunch
✗ 未安装 CocoaPods。
CocoaPods 是 iOS 或 macOS 平台的包管理工具。
没有 CocoaPods,插件无法在 iOS 或 macOS 上正常工作。
更多信息请见 https://flutter.dev/to/platform-plugins
安装说明请见 https://guides.cocoapods.org/using/getting-started.html#installation
[✓] Chrome - 用于 Web 开发
[!] Android Studio (未安装)
[✓] IntelliJ IDEA Ultimate 版本 (2025.1.3)
[✓] VS Code 版本 (1.104.2)
[✓] 已连接设备 (2 个可用)
[✓] 网络资源
! Doctor 在 3 个类别中发现问题。
```
### 安装 Android Studio
==搭建安卓环境==
1. [下载 Android Studio](https://developer.android.com/studio?hl=zh-cn)
2. 安装 Dart 和 Flutter 插件
3. 安装 SDK Command-line Tools

4. 接受 Android SDK 许可证
```shell
$ flutter doctor --android-licenses
# 根据提示逐条输入 y 接受许可证
```
### 安装 XCode
==搭建iOS/macOS环境==
- 通过 [App Store安装 Xcode](https://developer.apple.com/xcode/)
- 安装完成后,在终端执行:
```shell
# 指定 Xcode 路径(支持安装多个)
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
# 首次启动初始化(确保命令行工具可用)
sudo xcodebuild -runFirstLaunch
```
### 安装 CocoaPods
CocoaPods 是 iOS/macOS 平台的依赖管理工具,用于管理 Flutter 插件的本地依赖。
```shell
# 1. 安装 ruby(Mac系统自带,但建议使用 brew 安装新版)
brew install ruby
# 配置 ruby 环境变量
echo 'export PATH="/opt/homebrew/opt/ruby/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
# 验证 ruby 是否安装成功
ruby --version
# 2. 安装 CocoaPods
gem install cocoapods
# 配置 gems 环境变量
export PATH="/opt/homebrew/lib/ruby/gems/3.4.0/bin:$PATH"
source ~/.zshrc
```
## 常用命令
```shell
# 🌟 检查环境
flutter doctor # 检查 SDK、IDE、工具链
flutter doctor -v # 显示详细信息
# 🌟 设备管理
flutter devices # 列出已连接设备/模拟器
flutter emulators # 查看可用模拟器
flutter emulators --launch # 启动指定模拟器
# 🌟 运行项目
flutter run # 在默认设备运行
flutter run -d # 指定设备运行
flutter run -d all # 所有设备同时运行
# 🌟 管理依赖
flutter pub get # 下载依赖(pubspec.yaml 中的包)
flutter pub upgrade # 升级依赖
# 🌟 清理与构建
flutter clean # 清理缓存和临时文件
flutter build apk # 构建 Android APK
flutter build apk --debug # 构建调试 APK
flutter build apk --split-per-abi # 按 CPU 架构分包,减小 APK 大小
# 🌟 代码检查与格式化
flutter analyze # 静态分析代码
flutter format . # 格式化项目代码
# 🌟 iOS 模拟器(仅 macOS)
open -a Simulator # 启动 Xcode iOS 模拟器
flutter run -d ios # 在 iOS 模拟器运行
```
## 组件(Widget)
### 理解 Flutter 应用
* [🐟代码小抄-理解 Flutter 应用](https://codecopy.cn/post/19cdjq)
- 1️⃣ StatelessWidget —— 无状态组件
- 2️⃣ StatefulWidget —— 有状态组件
- [joeljhou/hello_flutter: 一个面向初学者的 Flutter 示例项目,展示基础控件、布局和样式。适合学习 Flutter 基础知识并快速上手开发简单应用。](https://github.com/joeljhou/hello_flutter)
### StatefulWidget 生命周期

StatefulWidget 的生命周期可分为 **4 个阶段**:
1. **初始化阶段**:`createState()` → `initState()`
2. **依赖变化与首次构建阶段**:`didChangeDependencies()` → `build()`
3. **状态更新阶段**:`setState()`、`didUpdateWidget()`、`reassemble()`
4. **销毁阶段**:`deactivate()` → `dispose()`
**🧩 各阶段方法说明**
1. `createState()`
- 当 StatefulWidget 第一次被创建时调用
- 用于创建对应的 `State` 实例
2. `initState()`
- State 初始化时调用(仅执行一次)
- 常用于:
- 初始化变量
- 发起网络请求 / 初始化数据
- ⚠️ 不要在此直接调用 `BuildContext` 相关操作(如 `Provider.of`)
3. `didChangeDependencies()`
- 当依赖的对象(如 `InheritedWidget`)发生变化时调用
- 在 `initState()` 之后 **会被调用一次**
- 通常用于依赖外部数据的初始化
4. `build()`
- 返回要渲染的界面
- 可能会被调用多次(如 `setState()`、父组件重建)
- ⚠️ 避免在此执行耗时或带副作用的操作
5. `reassemble()`
- **仅在 Debug 模式下热重载(Hot Reload)** 时调用
- 用于调试时更新状态
6. `didUpdateWidget()`
- 当父组件重建并传入新的配置时调用
- Flutter 通过 `Widget.canUpdate` 判断是否需要调用此方法
- 调用后一定会触发 `build()` 重新渲染
7. `setState()`
- 用于触发状态更新并重新调用 `build()`
- 只更新当前组件,不影响父组件
8. `deactivate()`
- 当组件从 Widget 树中**暂时移除**时调用
- 有可能会再次被插入(如路由切换)
9. `dispose()`
- 当组件 **永久从树中移除** 时调用
- 用于:
- 释放资源(如 `Controller`、`Stream`、`Timer`)
- 取消订阅、关闭动画等
### Widget/UI布局/交互

## 页面跳转(Navigator)

## 网络请求和序列化数据

## 参考:
* [Flutter 开发文档](https://docs.flutter.dev/) | [Flutter 中文开发文档](https://docs.flutter.cn/)
* [手动安装Flutter](https://docs.flutter.cn/install/manual) | [安装常见问题](https://docs.flutter.cn/install/troubleshoot#cmdline-tools-component-is-missing)
* [在 Android Studio 或 IntelliJ 里开发 Flutter 应用](https://docs.flutter.cn/tools/android-studio)
* [女程序猿带你从0开始学Flutter:认识Flutter](https://www.bilibili.com/video/BV1p14y1T79R/)
* [一个面向初学者的 Flutter 示例项目](https://github.com/joeljhou/hello_flutter)
================================================
FILE: src/md/idea-tips/activation.md
================================================
---
title: 2025最新IntelliJ IDEA专业版稳定正版激活码
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: true
date: 2024-04-17
category: 开发工具
tag:
- IDEA
---
# 2025最新IntelliJ IDEA专业版稳定正版激活码
## 适用于Mac系统
> 仅支持官网专业版2020-2024的激活。不支持社区版 toolbox下载的IDEA。
### 1️⃣ 第一步:下载激活文件
[激活文件获取](https://h5.m.goofish.com/item?id=785250929165)
### 2️⃣ 第二步:打开激活文件 `macjihuo.zip`,解压缩
### 3️⃣ 第三步:"macjihuo"文件夹上右键,新建终端窗口

### 4️⃣ 第三步:运行脚本,输入对应软件的命令-回车(看下图)
==运行命令前:先打开你要激活的软件。然后关闭软件,最后输入命令激活软件哦。==
```shell
如果你要激活idea: 则输入 sh idea.sh
如果你要激活pycharm: 则输入 sh pycharm.sh
如果你要激活datagrip: 则输入 sh datagrip.sh
如果你要激活clion: 则输入 sh clion.sh
如果你要激活goland: 则输入 sh goland.sh
如果你要激活webstorm: 则输入 sh webstorm.sh
如果你要激活phpstorm: 则输入 sh phpstorm.sh
如果你要激活dataspell:则输入 sh dataspell.sh
如果你要激活rider: 则输入 sh rider.sh
```

## 适用于Windows系统
> 仅支持官网专业版2020-2024的激活。不支持社区版 toolbox下载的
### 1️⃣ 第一步:下载激活文件
[激活文件获取](https://h5.m.goofish.com/item?id=785250929165)
### 2️⃣ 第二步:解压下载的压缩包
### 3️⃣ 第三步:打开解压的“`激活码-Win系统`”文件夹,
双击要激活软件的vbs

**♦♦比如:**
* 要激活pycharm,就运行 pycharm激活.vbs
* 要激活IDEA,就运行 IDEA激活.vbs
等待出现 如下 `Success` 提示,再次打开软件即可正常使用!

================================================
FILE: src/md/installation-guide/README.md
================================================
---
title: 开发者安装大全
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-04-23
category: 软件安装
tag:
---
# 开发者安装大全
该专栏主要整理与汇总开发者常用的编程环境、中间件等工具的安装,以指导开发者快速搭建自己的需要的开发环境。
================================================
FILE: src/md/installation-guide/base-tools/Homebrew.md
================================================
---
title: macOS必备包管理工具—Homebrew
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2025-03-15
category: 包管理器
tags:
- Homebrew
---
# macOS必备包管理工具—Homebrew
主要官方来源:[Homebrew官网](https://brew.sh/zh-cn/) | [Github](https://github.com/homebrew)
> Homebrew 是 macOS 和 Linux 上的包管理器,可以帮助用户轻松安装和管理各种软件包。
## 安装Homebrew
### 官网脚本

**安装 Homebrew**
将以下命令粘贴至终端:
```shell
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```
运行此命令将安装最新版本的Homebrew,自动处理MacOS环境中`Xcode Command Tools`的安装。
- **`git`**(用于版本控制,拉取 Homebrew 软件包)
- **`gcc` / `clang`**(用于编译部分软件包)
- **`make`**(用于构建和安装依赖)
安装完成后,按照终端提示,添加 Homebrew 环境环境变量到 `.zprofile` 或 `.bash_profile`。
```shell
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
source ~/.zprofile
```
**卸载 Homebrew**
```shell
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/uninstall.sh)"
```
**验证 Homebrew 安装**
安装完成后,可以运行以下命令来检查 Homebrew 是否正确安装:
```shell
brew --version
```
### 国内脚本
由于国内网络访问 GitHub 可能较慢,可以使用国内的 Homebrew 安装脚本,加快安装速度。
**安装步骤**
1. 访问 [HomebrewCN](https://gitee.com/cunkai/HomebrewCN)。
2. 按照说明执行相应的安装命令。
### .pkg包(macOS)
如果您使用的是 macOS,并希望通过图形化方式安装 Homebrew,可以使用官方提供的 `.pkg` 安装器。
**安装步骤:**
1. 访问 [Homebrew 最新 GitHub 发行版](https://github.com/Homebrew/brew/releases/latest)。
2. 下载 `.pkg` 文件。
3. 双击安装并按照提示完成安装。
## 使用指南
以下是一些常见的 Homebrew 命令:
| 操作 | 命令 |
| ---------------- | ----------------------------------- |
| 安装命令行软件包 | `brew install ` |
| 安装图形界面软件 | `brew install --cask ` |
| 搜索软件 | `brew search ` |
| 卸载命令行软件包 | `brew uninstall ` |
| 卸载图形界面软件 | `brew uninstall --cask ` |
| 更新 Homebrew 本身 | `brew update` |
| 更新所有已安装软件 | `brew upgrade` |
| 更新具体软件 | `brew upgrade ` |
| 显示已安装的软件 | `brew list` |
| 查看软件信息 | `brew info ` |
| 查看需要更新的软件 | `brew outdated` |
| 清理不再使用的旧版本 | `brew cleanup` |
| 显示软件安装路径 | `brew --prefix ` |
| 测试软件是否正常运行 | `brew test ` |
| 检查 Homebrew 运行状态 | `brew doctor` |
| 列出所有可用的 cask | `brew list --cask` |
| 列出所有依赖项 | `brew deps ` |
| 显示详细依赖树 | `brew deps --tree ` |
| 查看软件安装日志 | `brew log ` |
## 默认安装路径
在 Apple M1 (M4) 芯片的 Mac 上,Homebrew 默认安装路径有所不同。对于 M1 芯片,Homebrew 会安装在 `/opt/homebrew` 目录下,而不是像 Intel 芯片那样安装在 `/usr/local`。
- **Homebrew** 安装路径:`/opt/homebrew`
- **Homebrew 安装的软件** 默认路径:`/opt/homebrew/Cellar/`
- **软链接** 默认路径:`/opt/homebrew/bin`(软链接会创建在这个目录中,以便更方便地在命令行中调用安装的软件)
通过以下命令确认安装路径:
```shell
which brew
```
检查某个软件包的安装路径,可以使用 `brew list <软件名>`。
================================================
FILE: src/md/installation-guide/dev-env/java/SDKMAN.md
================================================
---
title: Java生态版本管理神器—SDKMAN
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: true
date: 2025-03-15
category: 包管理器
tags:
- SDKMAN
---
# Java生态版本管理神器—SDKMAN
主要官方来源:[SDKMAN官网](https://sdkman.io/) | [Github](https://github.com/sdkman)
> SDKMAN! 是一个轻量级、支持多平台,开源的,用于管理多个 SDK(如 `Java`、`Kotlin`、`Gradle`、`Maven` 等)的工具。
## 安装SDKMAN
**安装 SDKMAN!**
* 只需启动一个新终端并输入:
```shell
curl -s "https://get.sdkman.io" | bash
```
**初始化并配置环境变量**
* 按照屏幕上的说明完成安装。然后,打开一个新终端或在同一 shell 中运行以下命令:
```shell
source "$HOME/.sdkman/bin/sdkman-init.sh"
```
最后,运行以下代码片段来确认安装成功:
```shell
sdk version
```
您应该看到包含最新脚本和本机版本的输出:
```shell
SDKMAN!
script: 5.19.0
native: 0.6.0 (macos aarch64)
```
## 使用指南
以下是基于 SDKMAN! 文档整理的 **Java SDK 管理命令及详细解释**,按照使用流程排列:
### 查看Java版本
```shell
sdk list java
```
查看所有 [Java 发行版](https://sdkman.io/jdks)(如 Zulu、AdoptOpenJDK、Amazon Corretto 等)及版本列表。

* **关键字段**:`Identifier` 是安装时使用的唯一标识符(如 `21.0.6-tem`)。
### JDK的安装与卸载
**安装指定版本的 Java**
```shell
sdk install java $Identifier
```
* 如果不输入`$Identifier`的话,会自动安装最新的稳定版本。
* Eclipse Temurin 作为默认 JDK,因为它被广泛认为是 OpenJDK 发行版的事实标准。
**卸载已安装的 Java 版本**
```shell
sdk uninstall java $Identifier
```
### JDK版本管理
**切换 Java 版本(当前会话有效)**
```shell
sdk use java $Identifier
```
**设置全局默认 Java 版本**
```shell
sdk default java $Identifier
```
**查看当前 Java 版本**
```shell
sdk current java
```
================================================
FILE: src/md/installation-guide/dev-env/nodejs/Corepack.md
================================================
---
title: Corepack核心包管理工具
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2025-03-15
category: 包管理器
tag:
- npm
- pnpm
- yarn
---
# Corepack核心包管理工具
主要官方来源:[Nodejs官网文档](https://nodejs.org/api/corepack.html) | [Github](https://github.com/nodejs/corepack)
> Corepack 作为一个内置于 Node.js 工具,为开发者解决了包管理器(`Yarn`、`npm` 和 `pnpm` )版本不一致和兼容性问题。
## 安装Corepack
从 Node.js 16.x 开始,Corepack 已经内置在 Node.js 中,所以只要安装 Node.js,就可以直接使用 Corepack。如果需要手动安装,可以参考 [Github 官方文档](https://github.com/nodejs/corepack?tab=readme-ov-file#manual-installs)。
**验证安装**
```shell
corepack -v
```
**Corepack更新**
* 用于解决 [Corepack 中的签名过时](https://github.com/nodejs/corepack/issues/612) 问题
```shell
npm install --global corepack@latest
```
## 启用/禁用 Corepack
* [Corepack](https://github.com/nodejs/corepack) 默认是实验性工具,需要手动启用。
```shell
# 启用
corepack enable
```
启用后,它会自动为你管理所需的包管理器版本。
```shell
# 禁用
corepack disable
```
## 管理包管理器的版本
Corepack 使得你能够轻松管理不同版本的包管理器。比如你可以为项目指定特定版本的 `npm`、`Yarn` 或 `pnpm`。
### 安装[pnpm](https://pnpm.io/zh/)
> 快速的,节省磁盘空间的包管理工具。pnpm 比 npm 快 2 倍。
**安装并启用:**
```shell
corepack prepare pnpm@latest --activate
```
**验证安装:**
```shell
pnpm --version
```
**固定项目使用的版本:**
```shell
corepack use pnpm@latest-10
```
这会将 `pnpm` 的版本信息添加到 `package.json` 中的 `packageManager` 字段。
### 安装[Yarn](https://yarnpkg.com/)
[Github](https://github.com/yarnpkg/yarn)
> Yarn 是 Facebook 开发的一个快速、安全、稳定的包管理器,特别适用于大型项目。
**安装并启用:**
* 稳定版本:1.22.22
```shell
corepack prepare yarn@1.22.22 --activate
```
**验证安装:**
```shell
yarn --version
```
**固定项目使用的版本:**
```shell
corepack use yarn@1.22.22
```
在 `package.json` 中,会看到类似以下内容:
```shell
{
"packageManager": "yarn@1.22.22"
}
```
这样,Corepack 会根据该配置自动管理 Yarn 的版本。
================================================
FILE: src/md/installation-guide/dev-env/nodejs/nrm.md
================================================
---
title: npm源切换加速利器—nrm
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2025-03-17
category: 包管理器
tags:
- nrm
- 镜像
---
# npm源切换加速利器—nrm
主要官方来源:[npmjs-nrm](https://www.npmjs.com/package/nrm)
> `nrm`可以帮助您轻松快速地切换不同的 npm 注册表。它支持 `npm` 、 `cnpm` 、 `taobao` 、 `yarn` 、 `tencent` 、 `npmMirror`和`huawei` 。
## 安装nrm
```shell
# npm 安装
npm install -g nrm
```
安装成功后,使用`nrm --version`查看命令,验证是否安装正常。
## 使用指南
### nrm ls
查看源列表
```shell
$ nrm ls
* npm ---------- https://registry.npmjs.org/
yarn --------- https://registry.yarnpkg.com/
tencent ------ https://mirrors.tencent.com/npm/
cnpm --------- https://r.cnpmjs.org/
taobao ------- https://registry.npmmirror.com/
npmMirror ---- https://skimdb.npmjs.com/registry/
huawei ------- https://repo.huaweicloud.com/repository/npm/
```
### nrm use
切换镜像
```shell
$ nrm use taobao
SUCCESS The registry has been changed to 'taobao'.
```
### nrm current
查看当前源
```shell
$ nrm current
You are using taobao registry.
```
### nrm test
测试所有源的响应时间
```shell
$ nrm test
npm ---------- 823 ms
yarn --------- 799 ms
tencent ------ 964 ms
cnpm --------- 1882 ms
* taobao ------- 184 ms
npmMirror ---- 871 ms
huawei ------- 845 ms
```
### nrm add
添加镜像源
```shell
$ nrm add <源名> <源URL>
```
### nrm del
删除镜像源
```shell
nrm del <源名>
```
## 统一切换npm,pnpm,yarn镜像源
使用 `nrm` 切换镜像源时,通常会修改 `.npmrc` 配置文件,这会同时影响 `npm` 和 `pnpm`。
```
nrm use huawei
```
* 验证 `npm` 镜像源:
```shell
npm config get registry
```
* 验证 `pnpm` 镜像源:
```shell
pnpm config get registry
```
为了确保`nrm use`命令, `yarn` 也同步更新镜像源,在 `~/.zshrc`中中添加以下内容:
```shell
nrm() {
if [ "$1" = "use" ] && [ -n "$2" ]; then
command nrm use "$2"
yarn config set registry $(npm config get registry)
echo "Yarn registry updated to $(yarn config get registry)"
else
command nrm "$@"
fi
}
```
执行`source ~/.zshrc`后,验证 `yarn` 镜像源:
```shell
yarn config get registry
```
================================================
FILE: src/md/installation-guide/dev-env/nodejs/nvm.md
================================================
---
title: Node.js版本管理神器—nvm
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2025-03-15
category: 包管理器
tags:
- nvm
---
# Node.js版本管理神器—nvm
主要官方来源:[nvm Github](https://github.com/nvm-sh/nvm?tab=readme-ov-file#installing-and-updating)
> `nvm`是 [node.js](https://nodejs.org/zh-cn) 的版本管理器,允许您通过命令行快速安装和使用不同版本的 node。
## 安装nvm
手动下载并运行 [安装脚本](https://github.com/nvm-sh/nvm/blob/v0.40.2/install.sh),或使用**命令行安装**(`cURL` 或 `Wget`):
```shell
# 使用 curl 安装
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash
# 使用 wget 安装
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash
```
脚本会自动**配置环境变量**(`~/.zshrc`下):
```shell
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # 加载 nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # 加载 nvm bash_completion
```
* 自行修改后,需执行 `source ~/.zshrc`
**验证安装**
```shell
nvm --version
```
> 注意:[nvm](https://github.com/nvm-sh/nvm) 的维护者明确表示 `brew` 不是官方推荐的安装方式。
## 使用指南
### 查看所有可用版本
```shell
nvm ls-remote --lts # 查看所有长期支持 (LTS) 版本
nvm ls-remote # 查看所有可用版本
```
### 安装
```shell
nvm install # 安装指定版本(如 nvm install 20)
nvm install --lts # 安装最新的 LTS 版本
nvm install node # 安装最新的稳定版本
```
### 切换版本
```shell
nvm use # 切换到指定版本(如 nvm use 16)
nvm use default # 切换到默认版本
```
### 设置默认版本
```shell
nvm alias default # 设置默认版本(如 nvm alias default 18)
```
### 列出已安装的版本
```shell
nvm list # 列出本地已安装的 Node.js 版本
nvm ls # 同上,显示已安装的版本
```
### 当前版本
```shell
node -v # 显示当前 Node.js 版本
nvm current # 显示当前 NVM 管理的 Node.js 版本
```
### 卸载
```shell
nvm uninstall # 卸载指定版本(如 nvm uninstall 16)
```
================================================
FILE: src/md/installation-guide/os/windows-office-activation.md
================================================
---
title: Windows、Office激活密钥,脚本,程序
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-04-23
category: 激活
tag:
- Windows
- Office
---
# Windows、Office激活密钥,脚本,程序
## ① 微软正版秘钥(官方)
### 01.Windows系统激活
1. `Win+R` 打开终端,输入命令
```shell
slui 3
```
2. 打开“系统激活”窗口,点击“更改”按钮

3. 输入“秘钥”,点击“下一页” 进行 “激活” !获取 [微软Windows官方正版秘钥](https://learn.microsoft.com/zh-cn/windows-server/get-started/kms-client-activation-keys)

4. “激活”成功

### 02.Office软件激活
1. **打开Office应用程序:** 启动`Word`、`Excel`或`PowerPoint`等
2. **访问文件菜单:** 在应用程序中,点击左上角的"文件"菜单
3. **点击账户:** 在文件菜单中,选择"账户"
4. **更改产品密钥:** 在账户页面,找到并点击"更改产品密钥"
5. **输入产品密钥:** 在弹出的对话框中输入你的25位密钥 !获取 [微软Office官方正版秘钥](https://learn.microsoft.com/zh-cn/deployoffice/vlactivation/gvlks)
6. **激活Office:** 输入密钥后,点击"继续"或"激活产品"
## ② 激活脚本(原理)
> 一键激活脚本:[https://wwi.lanzoup.com/b0foh27ne](https://wwi.lanzoup.com/b0foh27ne), 密码:`gkjb`

### 01.脚本激活命令
**将密钥写入系统,实现激活**
```shell
slmgr /ipk XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
```
如果你有新的零售产品密钥,将 `X` 替换为实际的密钥,运行以上命令即可激活
### 02.脚本激活网站推荐
[KMS一键激活windows/office网站](https://kms.cx/)

## ③ 激活程序(推荐)
> 获取 [一键激活程序](https://m.tb.cn/h.g0ZtnKz?tk=OoXyWIKR74J) ,提供多种激活方式

::: note 注意:使用一键激活程序,需先关闭电脑相关的杀毒程序
* [关闭 Windows 安全中心中的Defender 防病毒保护](https://support.microsoft.com/zh-cn/windows/%E5%85%B3%E9%97%AD-windows-%E5%AE%89%E5%85%A8%E4%B8%AD%E5%BF%83%E4%B8%AD%E7%9A%84defender-%E9%98%B2%E7%97%85%E6%AF%92%E4%BF%9D%E6%8A%A4-99e6004f-c54c-8509-773c-a4d776b77960)
* [360安全卫士设置白名单](http://www.fastaux.com/index.php?c=show&id=54)
* [360杀毒软件怎么添加文件白名单](https://www.eyunsou.com/360sd/gongnneg/bmd/)
:::
### 01.数字激活工具 HWID_KMS38_CN_62
**概述**
`HWID_KMS38_CN_62.exe` 是一种**数字激活工具**,主要用于激活 Microsoft Windows 操作系统。它采用 HWID 技术,生成硬件标识符并将其发送到
Microsoft 的激活服务器进行验证,实现系统长期激活。
**激活步骤**
1. 鼠标右键点击“打开”,进入程序中

2. 进入程序界面后,工作模式选择“==安装数字密钥==(HWID)”,点击“开始”

3. 完成后执行[一键激活脚本](#一键激活脚本)中的`06.查看是否永久激活.bat`验证激活状态

### 02.KMS激活工具 DragonKMS
互联网论坛上的知名激活工具,也是我个人常用的激活工具,常用于激活Windows和Office全家桶,通过联网KMS激活

### 03.开源激活工具 HEU_KMS_Activator
仅供研究激活原理使用,在“智能激活”中点击“开始”,即按照最佳激活方式"数字许可证/KMS38/OEM/KMS"依次激活,直到激活成功

等待一会,激活成功后提示

### 04.小马激活工具 KMS10
老牌激活工具,文件大小仅1M,我以前还用来激活Win7,可以想象年代的久远。支持离线激活,永久有效,无需考虑激活时限。office版本为retail的用户请自行转换版本到volume,即channel
switch。

### 05.大神制作 W10DigitalActivation
大神Ratiborus制作的,它还有开发激活Office的 **KMSAuto++** 工具,以及可定制化安装Office的 **Office2013-2021 C2R Install**
。可以关注它的[Twitter](https://twitter.com/ratiborus58)。


### 06.云萌激活工具 CMWTAT_Digital
云萌 Windows 10 激活工具,专门用于数字权利激活的软件。本软件是一款使用CSharp编写的 Windows 10 数字权利激活工具

================================================
FILE: src/md/java/basic/java-basic-oop.md
================================================
---
title: Java核心思想 - 面向对象编程(OOP)
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2025-09-13
category: Java
tags:
- JavaBasic
- OOP
---
# Java核心思想-面向对象编程(OOP)
## 什么是面向对象编程?
### 发展历史简述
- **1960 年**:类(Class)和对象(Object)的概念首次出现在 **Simula** 语言中。
- **1970 年代**:**Smalltalk** 引入了“面向对象编程(OOP)”的概念,被认为是第一个真正意义上的 OOP 语言。
- **1980 年左右**:**C++** 出现,推动了 OOP 的广泛流行。
- **今天**:大多数主流语言都支持 OOP(Java、C++、Python、C#、Ruby、JavaScript、Go、Scala、PHP 等)。
### 类和对象
面向对象编程中有两个非常重要、非常基础的概念,那就是**类(Cass)** 和**对象(Object)**。
* **类(Class)** 是指对某一类事物的抽象描述,包含属性和方法。
* **对象(Object)** 是类的实例,是在程序中具体存在的实体。
### 核心定义
* **面向对象编程(OOP, Object-Oriented Programming)** 是一种编程范式或编程风格。它以**类**或**对象**作为组织代码的基本单元,并将**封装**、**抽象**、**继承**、**多态**四个特性,作为代码设计和实现的基石 。
* **面向对象编程语言(OOPL, Object-Oriented Programming Language)** 是支持类或对象的语法机制,并有现成的语法机制,能方便地实现面向对象编程四大特性(封装、抽象、继承、多态)的编程语言。
## 四大特性
### 封装(Encapsulation)
- 核心思想:==隐藏实现、保护数据、对外提供接口==。
- **实现方式:** 通过 `private`、`protected`、`public` 等权限控制,把属性隐藏起来,通过方法来操作。
- **意义:**
- 防止数据被随意修改,提高代码的**安全性和可维护性**。
- 对外只暴露必要的接口,提高类的**易用性**。
- 👉 通俗理解:**“把属性藏起来,通过方法管起来”**。
### 抽象(Abstraction)
- 核心思想:==提取共性,屏蔽细节==。
- **实现方式:** 接口、抽象类,或者只提供方法定义而隐藏实现。
- **意义:**
- 使用者只需关心**做什么**,不必关心**怎么做**。
- 提高代码的**扩展性和维护性**,修改实现不影响对外接口。
- 👉 通俗理解:**“只告诉你功能,不告诉你原理”**。
⚠️ 有的教程会把**抽象** 视为 **封装的一部分**(抽象 ≈ “隐藏细节、暴露共性”)
### 继承(Inheritance)
- 核心思想:==子类复用父类的属性和方法==,体现 `is-a` 关系。
- **模式:**
- 单继承:一个子类只能继承一个父类(Java)。
- 多继承:一个子类可继承多个父类(C++ 支持,Java 通过接口间接实现)。
- **意义:**
- 解决**代码复用**问题。
- 为多态打下基础。
- 👉 通俗理解:**“子承父业”**。
### 多态(Polymorphism)
- 核心思想:==同一接口,不同实现,表现出不同行为==。
- **定义:** 多态是指子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。
- **实现方式:** 继承 + 方法重写、接口实现、duck typing(动态语言)。
- **多态的三种类型:**
1. **编译时多态(静态多态)**
- 通过**方法重载(Overload)**实现。
- 编译阶段决定调用哪个方法。
- 示例:`print(int a)` 和 `print(String s)`。
2. **运行时多态(动态多态)**
- 通过**方法重写(Override)**和**向上转型**实现。
- 运行时根据实际对象类型决定调用哪个方法。
- 示例:父类 `Animal` 的 `speak()` 被子类 `Dog`、`Cat` 重写。
3. **参数化多态(泛型多态)**
- 通过**泛型(Generics)** 实现。
- 方法或类可以操作不同类型,而无需写多份代码。
- 示例:`List`、`List` 都能用同一个 `List` 实现。
- **意义:**
- 提高代码的**扩展性**(新增子类无需修改原有逻辑)。
- 提升代码的**复用性**(相同代码可适配多种对象)。
- 是**设计模式与面向对象编程技巧**的核心基础。
- 👉 通俗理解:**“一个接口,多种形态”**。
================================================
FILE: src/md/java/basic/java-common-classes.md
================================================
---
title: Java 常用类与工具
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2025-09-13
category: Java
tags:
- JavaBasic
---
# Java 常用类与工具
## Object 通用方法
`Object` 类是Java编程语言中的根类(基类),具有以下特点:
- **继承关系**:所有类都直接或间接继承自 `Object` 类
- **默认父类**:如果一个类没有明确指定父类,它会自动继承 `Object` 类
- **核心方法**:提供了所有对象都具备的基本方法

## 对象包装器与自动装箱
Java 为每种基本数据类型提供了对应的**包装类(Wrapper Class)**,其特点如下:
- 所有包装类位于 `java.lang` 包中,均为 `final` 类
- 包装类是引用类型,可以像对象一样使用基本类型

从 Java 5 开始,JDK 支持了**自动装箱/拆箱(Autoboxing / Unboxing)**,简化了基本类型与包装类之间的转换:
```java
Integer i = 100; // 装箱:基本类型 → 包装类对象
int j = i; // 拆箱:包装类对象 → 基本类型
```
根据 Java 自动装箱规范,部分包装类在特定范围内使用**缓存池(CachePool)** 复用对象,提高性能,避免重复创建:
- Boolean:缓存 `true` / `false`
- Character:缓存 `\u0000 ~ \u007F`(0 ~ 127)
- Byte:缓存所有取值范围 `[-128, 127]`
- Short / Integer/Long:默认缓存 `[-128, 127]`,其中 `Integer` 上限可通过 JVM 参数调整(`-XX:AutoBoxCacheMax=`)
- Float / Double:不使用缓存
## String
String 是 Java 中最常用的**引用类型**之一,使用`final`修饰,具有**不可变性**(`Immutable`)。
- 位于 `java.lang` 包中
- **线程安全**,可被多个线程共享
- JDK8 底层使用`char`数组实现,JDK9+ 改用`byte`数组+ `coder`编码标志
### String API
```java
1️⃣ 字符串创建
String s = "..." → 字面量创建(常量池复用)
new String(...) → 构造函数创建(堆中新建对象)
2️⃣ 长度与空值判断
length() → 获取字符串的长度
isEmpty() → 判断是否为空(即:length == 0)
isBlank() → 判断是否为空白
3️⃣ 字符串查找
indexOf(...) → 查找子字符串首次出现位置
lastIndexOf(...) → 查找子字符串最后出现位置
contains(...) → 判断是否包含某子串
4️⃣ 字符串截取
substring(begin) → 截取子字符串(从 begin 到 结尾)
substring(begin, end) → 截取子字符串(从 begin 到 end-1)
charAt(index) → 获取指定位置字符
5️⃣ 字符串拼接
+ → 最常用
concat(...) → 辅助拼接
String.join(delimiter, ...) → 多字符串拼接
6️⃣ 字符串替换
replace(old, new) → 替换字符或子串
replaceAll(regex, replacement) → 正则全局替换
7️⃣ 大小写转换
toUpperCase() → 转大写
toLowerCase() → 转小写
8️⃣ 字符串比较
equals(...) → 精确比较
equalsIgnoreCase(...) → 忽略大小写
compareTo(...) → 字典序比较(负数/0/正数表示小于/相等/大于)
9️⃣ 类型转换与格式化
String.valueOf(...) → 基本类型转字符串
String.format(...) → 模板格式化
toCharArray() → 字符串转字符数组(char[])
```
### JDK9+ Compact Strings
- [JEP 254: Compact Strings](https://openjdk.org/jeps/254)
- 前面提到:JDK8 底层使用`char`数组实现,JDK9+ 改用`byte`数组+`coder`编码标志。
- **背景**:JDK8 String 使用 `char[]`,每字符占 2 字节,存储 ASCII/Latin1 时存在内存浪费。
- **优化方案**:JDK9 起改为 `byte[] + coder`,根据字符串内容自动选择最优编码方式:
- `Latin1` 编码 → 每个字符占 1 字节
- `UTF-16` 编码 → 每个字符占 2 字节
- **效果**:减少内存占用,提高缓存命中率,且 API 保持兼容。
### [Java21 String Templates](/md/java/features/Java21/jep430-string-templates.html)
### 内存分配演进
1. 在 Java 中,String 是引用类型,其引用存放在**栈(Stack)** 中,对象内容存放在**堆(Heap)** 中。
2. 通过**字符串常量池(String Pool)** 缓存已创建的字面量,避免重复创建对象,提高内存利用率。
3. 利用`String.intern()`方法,将字符串对象**放入常量池**,并返回常量池中的对象引用。
**HotSpot中字符串常量池保存哪里?**
| JDK 版本 | 常量池位置 | 说明 |
|--------|--------------------------------|----------------------------------------------------------------------|
| ≤ JDK6 | **方法区(Method Area / PermGen)** | 字符串常量池在类加载阶段创建,固定大小,存储在永久代中 |
| JDK7 | **堆(Heap)** | 为了扩展灵活性,字符串常量池移到了堆,JVM 管理更方便 |
| ≥ JDK8 | **堆(Heap)** | 方法区被移除(PermGen 被 Metaspace 替代),字符串常量池仍在堆上,`String.intern()` 的对象存储在堆中 |
## Arrays
`Arrays` 类位于 `java.util` 包,是 Java 提供的**数组工具类**,特点如下:
* 方法均为 `static`,无需创建对象即可调用
* 提供对数组的排序、搜索、填充、复制、比较等操作
* 支持基础类型数组与对象数组
**Arrays API**
```java
1️⃣ 排序
Arrays.sort(arr); → 升序排序
Arrays.sort(arr, from, to); → 指定区间排序 [from, to)
Arrays.sort(arr, Collections.reverseOrder()); → 降序(对象数组)
Arrays.parallelSort(arr); → 并行排序(大数组更快)
2️⃣ 搜索
Arrays.binarySearch(arr, key); → 二分查找,数组需已排序
Arrays.binarySearch(arr, from, to, key); → 在 [from, to) 区间查找
3️⃣ 比较与哈希
Arrays.equals(arr1, arr2); → 数组内容是否相等
Arrays.deepEquals(arr1, arr2); → 多维数组内容是否相等
Arrays.hashCode(arr); → 一维数组哈希
Arrays.deepHashCode(arr); → 多维数组哈希
4️⃣ 填充与复制
Arrays.fill(arr, value); → 将数组元素全部填充为指定值
Arrays.fill(arr, from, to, value); → 填充区间 [from, to)
Arrays.copyOf(arr, newLength); → 复制并改变长度(扩容/缩容)
Arrays.copyOfRange(arr, from, to); → 复制区间 [from, to)
5️⃣ 转字符串/集合
Arrays.toString(arr); → 一维数组转字符串
Arrays.deepToString(arr); → 多维数组转字符串
Arrays.asList(arr); → 数组 → 固定大小 List 视图
6️⃣ 并行/批量操作(Java 8+)
Arrays.setAll(arr, i -> i * 2); → 顺序初始化数组
Arrays.parallelSetAll(arr, i -> i * 2); → 并行初始化数组
Arrays.parallelPrefix(arr, Integer::sum); → 前缀和计算
7️⃣ 流式 API(Java 8+)
Arrays.stream(arr); → 数组 → Stream
Arrays.stream(arr, from, to); → [from, to) 区间 → Stream
```
## Math
Math 类位于 `java.lang` 包,是 Java 提供的**数学工具类**。特点如下:
- 使用`final`修饰,具有**不可变性**(`Immutable`),线程安全
- 方法均为 `static`,无需创建对象即可调用
- 用途:支持基本运算(如加减乘除、取绝对值)、幂运算、三角函数、对数与指数运算、舍入操作,以及生成随机数等
**Math API**
```java
1️⃣ 基本数学运算
Math.abs(x) → 绝对值
Math.max(a, b) → 最大值
Math.min(a, b) → 最小值
Math.signum(x) → 符号函数 (-1/0/1)
2️⃣ 幂运算与开方
Math.pow(a, b) → a 的 b 次幂
Math.sqrt(x) → 平方根
Math.cbrt(x) → 立方根
Math.hypot(x, y) → √(x² + y²)
3️⃣ 指数与对数
Math.exp(x) → e 的 x 次幂
Math.expm1(x) → e^x - 1
Math.log(x) → 自然对数 ln(x)
Math.log10(x) → 以 10 为底的对数
Math.log1p(x) → ln(1+x),高精度
4️⃣ 三角函数
Math.sin(x) / Math.cos(x) / Math.tan(x) → 正弦/余弦/正切
Math.asin(x) / Math.acos(x) / Math.atan(x) → 反三角函数
Math.atan2(y, x) → y/x 的角度(弧度)
5️⃣ 舍入与取整
Math.ceil(x) → 向上取整
Math.floor(x) → 向下取整
Math.rint(x) → 返回最接近 x 的整数(double)
Math.round(x) → 四舍五入
Math.floorDiv(a, b) → 整数除法向下取整
Math.floorMod(a, b) → 向下取整取模
6️⃣ 随机数与常量
Math.random() → 返回 [0~1) 之间随机 double
Math.PI → π 常量
Math.E → e 常量
```
**常见用法**
```java
1️⃣ 分页计算
int pages = (int) Math.ceil(totalItems / (double) pageSize); → 向上取整
2️⃣ 随机数生成
int randInt = (int) (Math.random() * n); → 0 ~ n-1
double randDouble = Math.random() * (max - min) + min; → min ~ max
boolean flip = Math.random() < 0.5; → 随机布尔
3️⃣ 数值比较与约束
int value = Math.max(minValue, Math.min(maxValue, inputValue)); → 限定范围 [minValue, maxValue]
4️⃣ 取整与舍入
double d = 2.7;
double ceil = Math.ceil(d); → 3.0 向上取整
double floor = Math.floor(d); → 2.0 向下取整
long round = Math.round(d); → 3 四舍五入
5️⃣ 几何与距离计算
double distance = Math.hypot(x2 - x1, y2 - y1); → 两点间欧几里得距离
6️⃣ 幂运算
double area = Math.pow(radius, 2) * Math.PI; → 圆面积
```
## Random
`Random` 类位于 `java.util` 包,是 Java 提供的**伪随机数生成器**,基于 **线性同余算法(LCG)** 实现。特点如下:
- **伪随机**:由确定算法生成,种子相同会得到相同序列。
- **线程不安全**:每个 `Random` 实例在多线程中使用可能会有竞争问题。
- JDK 8 引入 `ThreadLocalRandom` 解决。
- **可复现性**:使用相同种子可生成相同随机序列。
- **多种数据类型支持**:可生成 `int`、`long`、`float`、`double`、`boolean` 等随机值。
**Random API**

**常见用法及扩展**
```java
// Random(单线程随机数)
Random random = new Random();
int min = 10, max = 20;
int randInt = random.nextInt(max - min) + min; // 区间随机整数 [min, max)
double randDouble = random.nextDouble() * (max - min) + min; // 区间随机浮点数 [min, max)
List prizes = List.of("奖品A","奖品B","奖品C");
String winner = prizes.get(random.nextInt(prizes.size())); // List 随机选择
// Java 8+ 流式生成随机整数/双精度
random.ints(5, 50, 100).forEach(System.out::println);
random.doubles().limit(5).forEach(System.out::println);
// ThreadLocalRandom(多线程安全)
int tRand = ThreadLocalRandom.current().nextInt(0, 100); // 区间随机整数 [0, 100)
double tDouble = ThreadLocalRandom.current().nextDouble(); // 区间浮点数 [0.0, 1.0)
// SplittableRandom(高性能/并行流)
SplittableRandom sr = new SplittableRandom();
sr.ints(5, 50, 100).forEach(System.out::print);
sr.doubles().limit(5).forEach(System.out::println);
```
## 时间日期(Date、Calendar、LocalDateTime 等)
* [Java 日期时间API](https://www.yuque.com/yublog/nokuzy/xkk0xf)
## 序
* [lombok 使用及技巧](https://www.yuque.com/yublog/nokuzy/wmmc6g)
* [stream 使用及其技巧](https://www.yuque.com/yublog/nokuzy/dioi46)
* [lambda 使用及其技巧](https://www.yuque.com/yublog/nokuzy/mtonym)
* [自定义注解(元注解)](https://www.yuque.com/yublog/nokuzy/qb14q0)
* [反射专题-框架的灵魂](https://www.yuque.com/yublog/nokuzy/gff0zc)
================================================
FILE: src/md/java/basic/java-from-scratch.md
================================================
---
title: Java基础 - 从零开始学习Java
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2025-09-09
category: Java
tags:
- JavaBasic
---
# Java基础 - 从零开始学习Java
## 1.Java语言的发展历史
### Java特征
Java语言是美国Sun公司(Stanford University Network),在1995年推出的高级的编程语言。
**特征:**
- 面向对象(OOP)
- 跨平台性强(“一次编写,到处运行”)
- 安全性高、可移植性强
### Java之父

詹姆斯·高斯林 (James Gosling)是一名软件专家,1955年5月19日出生于加拿大,Java编程语言的共同创始人之一,一般公认他为“Java之父”。
1977年获得了加拿大卡尔加里大学计算机科学学士学位,1983年获得了美国卡内基梅隆大学计算机科学博士学位。
### 发展历史

* 2004年推出了Java 1.5版本
* 2009年Sun公司被Oracle甲骨文公司收购
* 2014年推出了Java 8.0版本
### 计算机语言发展历史
1. 第一代**机器语言** 打孔机 二进制010101
2. 第二代**汇编语言** 助记符
3. 第三代**高级编程语言** C、C++、Java、Python 、php、c#等

### Java跨平台原理
* **平台** = 操作系统(`Windows`,`Linux`,`Mac`)
* Java的程序可以在任意的操作系统上运行

## 2.下载安装使用JDK
### 下载JDK11
JDK11的下载官方网站:
[https://www.oracle.com/java/technologies/javase-jdk11-downloads.html](https://www.oracle.com/java/technologies/javase-jdk11-downloads.html)
注意:需要注意下载对应不同操作系统版本的JDK

### 安装后目录介绍
傻瓜式安装,下一步


### 配置JDK环境变量
**1. 为什么需要配置JDK环境变量?**
不配置JDK环境变量的话,就需要每次到JDK安装路径下的Bin目录执行对应的exe文件;
在环境变量中加入软件的安装路径后,即使没有在该软件的安装目录下,我们在命令行输入软件的名称也可启动该软件。
**2.如何配置环境变量**
右键“此电脑”>>“属性”,在弹出的页面上点击高“高级系统设置”

在弹出的“系统属性”窗口中“高级”标签下点击“环境变量”按钮

在弹出的“环境变量”窗口中,点击下方的“新建”按钮,在弹出的“新建系统变量”窗口中,新建一个名为`JAVA_HOME`的环境变量,设置`Path`环境变量

验证是否配置成功,进入win+r输入cmd,输入指令`java -version`

JDK8之后的版本只需要配置`JAVA_HOME`,在PATH中添加`JAVA_HOME/bin`即可。
### JRE,JDK,JVM之间的关系

### JavaSE,JavaEE,JavaME之间的区别
Java总共有三个版本
1. **标准版Java SE**
* Java SE 以前称为 J2SE。它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序。Java SE 包含了支持 Java Web 服务开发的类;
2. **企业版Java EE**
* JavaEE是在JavaSE的基础上构建的,用来开发B/S架构软件,也就是开发企业级应用,所以称为企业版帮助开发和部署可移植、健壮、可伸缩且安全的服务器端 Java 应用程序。Java EE 是在 Java SE 的基础上构建的,它提供 Web 服务、组件模型、管理和通信 API;
3. **微型Java ME**
* Java ME为在移动设备和嵌入式设备(比如手机、PDA、电视机顶盒和打印机)上运行的应用程序提供一个健壮且灵活的环境。Java ME包括灵活的用户界面、健壮的安全模式、许多内置的网络协议以及对于动态下载的连网和离线应用程序的丰富支持。基于Java ME规范的应用程序只需要编写一次,就可以用于许多设备,而且可以利用每个设备的本级功能。
**简而言之**
* JavaSE是Java的基础,主要针对桌面程序开发;
* JavaEE是针对企业级应用开发;
* JavaME是主要针对嵌入式设备软件开发。
### Dos常用命令

### 编译运行Java程序
开发java程序,需要三个步骤:**1.编写程序(源代码)>> 2.编译程序 >> 3.运行程序**
1. 新建文本文件,修改名称为`HelloWorld.java`
2. 用记事本打开,编写以下程序
```java
public class HelloWorld{
public static void main(String[] args){
System.out.println("HelloWorld!");
}
}
```
3. 打开命令提示窗口,进入`HelloWorld`所在目录,输入编译和执行命令
```shell
# 编译,生成 .class 字节码文件
javac -encoding UTF-8 HelloWorld.java
# 运行程序(执行字节码)
java HelloWorld
```

## 3.Java基础语法
### Java注释
注释是指在程序指定的位置添加的说明信息,不参与运行。**Java 支持三种注释方式:**
1. 单行:`// 注释内容`
2. 多行:`/* 注释内容 */`
3. 文档注释:`/** 注释内容 */`

### 什么是关键字
**Java关键字**是电脑语言里事先定义的,**有特别意义的标识符**,有时又叫保留字。
一律用**小写字母标志**,根据用途分为如下分组:
1. **数据类型**:`boolean`、`byte`、`char`、 `double`、 `false`、`float`、`int`、`long`、`new`、`short`、`true`、`void`、`instanceof`。
2. **语句控制**: `break`、`case`、 `catch`、 `continue`、 `default` 、`do`、 `else`、 `for`、 `if`、`return`、`switch`、`try`、 `while`、 `finally`、 `throw`、`this`、 `super`。
3. **修饰符**: `abstract`、`final`、`native`、`private`、 `protected`、`public`、`static`、`synchronized`、`transient`、 `volatile`。
4. **用于类,方法,包,接口和异常**:`class`、 `extends`、 `implements`、`interface`、 `package`、`import`、`throws`。
### 常量与变量
- **常量**:Java中使用`final` 修饰,表示运行中不可变化的量。
- **变量**:在程序运行过程中,可以取不同数值的量;可变,需先声明后使用。
- 语法:变量的数据类型 变量名称 = 变量的值(赋值操作);
- 为整数常量,浮点数常量,字符常量,字符串常量,布尔常量(true,false),NULL是常量
- 命名规则:字母、数字、下划线 `_`、$ 符号开头,不能以数字开头,遵循驼峰命名法
### Java变量命名规则
1. 不能使用Java中的关键字,比如public class void int char等等..
2. 变量名必须以 字母 下划线_ 或者 $ 符号开头
3. 变量名可以包含数字,但是不能够以数字开头
4. 变量名除了 下划线_ 或者 $ 符号以外不能包含其他任何特殊字符
**驼峰命名法**

### 存储单位与数据类型
**常见存储单位换算**
数据必须首先在计算机内被表示,然后才能被计算机处理。计算机表示数据的部件主要是存储设备;而存储数据的具体单位是**存储单元**;因此,了解存储单元的结构是十分必要的。
```shell
1 Byte(字节)= 8 bit(位)
1 KB = 1024 Byte
1 MB = 1024 KB
1 GB = 1024 MB
1 TB = 1024 GB
1 PB = 1024 TB
1 EB = 1024 PB
1 ZB = 1024 EB
```
* 计算机中存储设备的最小单元叫”位(bit)”,又称为“比特位”,用小写字母b表示。
* 计算机中最小存储单元叫”字节(byte)”,用大写字母B表示,字节由8个位组成。
**Java中的数据类型**
Java语言是强类型语言,对于每一个数据都给出明确的数据类型,不同的数据类型也分配了不同的内存空间,所以它们表示的数据大小也是不一样的。

| **数据类型** | **类型** | **内存占用** | **取值范围** |
| -------- | -------------- | -------- | ---------------------------------------------------------------- |
| **整数** | **byte** | 1字节 | -128~127 |
| | **short** | 2字节 | -32768-32767 |
| | **int(默认)** | 4字节 | -2的31次方到2的31次方-1 |
| | **long** | 8字节 | -2的63次方到2的63次方-1 |
| **浮点数** | **float** | 4字节 | 负数:-3.402823E+38到-1.401298E-45
正数:-1.401298E-45到3.402823E+38 |
| | **double(默认)** | 8字节 | 负数:-1.797693E+308到-4.9000000E-324
正数:4.9000000E到1.797693E+308 |
| **字符** | **char** | 2字节 | 0-65535 |
| **布尔** | **boolean** | 1字节 | true,false |
说明:E+38表示是乘以10的38次方,同样,E-45表示乘以10的-45次方。
### 类型转换
在 java 程序中,不同的基本类型的值经常需要进行相互类型转换,类型转换分为**自动类型转换**和**强制类型转换**。

**自动类型转换**: 把一个表示数据范围小的数值或者变量赋值给另一个表示数据范围大的变量。
* 例如:`double a = 10;`
* **规律**
- 小的类型自动转化为大的类型
- 整数类型可以自动转化为浮点类型,可能会产生舍入误差
- 字符可以自动提升为整数
**强制类型转换**:把一个表示数据范围大的数值或者变量赋值给另一个表示数据范围小的变量。
* 例如:`int j = (int) 99.99;`
* 在要强制类型转换的前面加上括号,然后在括号里面加上你要转换的类型(强制转换需要程序员手动处理)
### 运算符
* **运算符**:对常量或者变量进行操作的符号
* **表达式**:用运算符吧常量或者变量连接起来的符号
#### 算术运算符
| **操作符** | **说明** | **举例** |
| ------- | ------ | --------------- |
| **+** | **加** | **加法** |
| **-** | **减** | **减法** |
| **\*** | **乘** | **乘法** |
| **/** | **除** | **除法** |
| **%** | **取余** | **得到两个数做除法的余数** |
| **++** | **自增** | **变量的值+1** |
| **--** | **自减** | **变量的值-1** |
**搞清楚`i++`,`++i`,`i--`,`--i`的区别?**
* `++i`,表示参与运算之前先自加1.
* `i++`,表示参与运算以后再加1.
* `--i`,`i--` 类似。
#### 关系运算符
| **操作符** | **说明** |
| ------- | ------------------------------------ |
| **==** | **检查如果两个操作数的值是否相等,如果相等则条件为真。** |
| **!=** | **检查如果两个操作数的值是否相等,如果值不相等则条件为真。** |
| **>** | **检查左操作数的值是否大于右操作数的值,如果是那么条件为真。** |
| **<** | **检查左操作数的值是否小于右操作数的值,如果是那么条件为真。** |
| **>=** | **检查左操作数的值是否大于或等于右操作数的值,如果是那么条件为真。** |
| **<=** | **检查左操作数的值是否小于或等于右操作数的值,如果是那么条件为真。** |
* 注意事项:关系运算符的结果都是boolean类型,要么是true,要么是false。
* 千万不要把“==”误写成“=”,“==”是判断是否相等的关系,“=”是赋值。
#### 位运算符
| **操作符** | **说明** |
| ------- | -------------------------------------- |
| **&** | 如果相对应位都是1,则结果为1,否则为0 |
| \| | 如果相对应位都是 0,则结果为 0,否则为 1 |
| **〜** | 按位取反运算符翻转操作数的每一位,即0变成1,1变成0。 |
| **^** | 如果相对应位值相同,则结果为0,否则为1 |
| **<<** | 按位左移运算符。左操作数按位左移右操作数指定的位数。左移一位相当于将原数*2 |
| **>>** | 按位右移运算符。左操作数按位右移右操作数指定的位数。右移一位相当于将原数/2 |
#### 逻辑运算符
逻辑运算符把各个运算的关系表达式联系起来组成一个复杂的逻辑表达式,以判断程序中的表达式是否成立,判断结果是`true`或`false`。
| **操作符** | **说明** | **举例** |
| ------- | --------- | --------------------------------------- |
| **&&** | 逻辑与 运算符。 | 当且仅当两个操作数都为真,条件才为真。 |
| \|\| | 逻辑或 操作符。 | 如果任何两个操作数任何一个为真,条件为真。 |
| **^** | 逻辑异或 操作符。 | 如果任意两个操作数结构不相同,条件为真,否则条件假 |
| **!** | 逻辑非 运算符。 | 用来反转操作数的逻辑状态。如果条件为true,则逻辑非运算符将得到false。 |
#### 短路运算符
| **操作符** | **说明** | **举例** |
| ------- | ------ | ----------------- |
| **&&** | 短路与 | 作用和 & 相同,但是有短路效果 |
| \|\| | 短路或 | 作用和 \| 相同,但是有短路效果 |
* 短路与 &&:如果左边为真,右边执行;如果左边为假,右边不执行。
* 短路或 ||:如果左边为假,右边执行;如果左边为真,右边不执行。
#### 三元运算符 ( ? : )
* **语法是:“条件表达式 ? 表达式1 : 表达式2”;**
* `?`前面的位置是判断的条件,判断结果为`boolean`型
* 为`true`时调用**表达式1**,为`false`时调用**表达式2**

### Scanner打印机用法
```java
import java.util.Scanner; // ① 导包
public class ScannerDemo {
public static void main(String[] args) {
// ② 创建对象
Scanner scan = new Scanner(System.in);
// ③ 接收数据
System.out.print("请输入你的名字:");
String name = scan.nextLine(); // 接收字符串
System.out.print("请输入你的年龄:");
int age = scan.nextInt(); // 接收整数
System.out.print("请输入你的身高(米):");
double height = scan.nextDouble(); // 接收小数
// 输出结果
System.out.println("------ 信息展示 ------");
System.out.println("姓名:" + name);
System.out.println("年龄:" + age);
System.out.println("身高:" + height + " 米");
// 关闭Scanner(养成好习惯)
scan.close();
}
}
```
### 流程控制语句
#### 顺序结构
是程序中最简单的流程控制,按照代码执行的先后顺序,依次执行,程序中的大多数代码都是这样执行的。

#### 分支结构(if,switch)
选择结构也被称为分支结构。选择结构有特定的语法规则,代码要执行具体的逻辑运算进行判断,逻辑运算的结果有两个,所以产生选择,按照不同的选择执行不同的代码。
##### if语句
```java
// 语法
if(布尔表达式){
语句体
}
```

##### if-else语句
```java
// 语法
f(关系表达式) {
语句体1;
} else {
语句体2;
}
```

##### 多重if-else语句
```java
// 语法
if (判断条件1) {
执行语句1
} else if (判断条件2) {
执行语句2
}
...
else if (判断条件n) {
执行语句n
} else {
执行语句n+1
}
```

##### switch语句
```java
// 语法
switch(表达式) {
case目标值1:
语句体1;
break;
case目标值2:
语句体2;
break;
…
default:
语句体n+1;
break;
}
```

#### 循环结构(for,while,do..while)
循环语句可以在满足循环条件的情况下,反复执行某一段代码,这段被重复执行的代码被称为循环体语句,当反复执行这个循环体时,需要在合适的时候把循环判断条件修改为false,从而结束循环,否则循环将一直执行下去,形成死循环。
##### for循环
```java
// 语法
for(初始化表达式;布尔表达式;步进表达式){
循环体
}
```

##### **while循环**
```java
// 语法
while(布尔表达式) {
循环体语句;
}
// 扩展格式
初始化表达式①
while(布尔表达式②){
循环体③
步进表达式④
}
```

##### **do while循环**
```java
// 语法
do{
循环体语句;
}while(布尔表达式);
// 扩展格式
初始化表达式①
do{
循环体③
步进表达式④
}while(布尔表达式②);
```

##### 三种不同循环的区别
* `do…while`循环至少会执行一次循环体。
* `for`循环和`while`循环只有在条件成立的时候才会去执行循环体
* `for`循环语句和while循环语句的小区别:
**使用区别:** 控制条件语句所控制的那个变量,在for循环结束后,就不能再被访问到了,而while循环结束还可以继续使用,如果你想继续使用,就用while,否则推荐使用for。原因是for循环结束,该变量就从内存中消失,能够提高内存的使用效率。
#### break和contiune区别
* `break`直接中断当前的整个循环,`continue`跳出本次的循环进入下一次。
* `continue`只能在循环中进行使用。
#### 嵌套for循环语句
可以通过带**标记**的`break`和`continue`结束或跳出多重循环。

## 4.Java 方法
### 什么是方法?
方法的本意是**功能块**,就是实现某个功能的语句块的集合。
### 方法的定义与调用
```java
// 语法
修饰符 返回类型 方法名(参数列表) {
// 方法体:具体执行的代码
return 返回值; // 如果有返回类型,必须返回相应类型的值
}
```
各部分解释:
1. 修饰符(modifier):如 `public`, `private`, `static`,控制方法的访问范围和行为
2. 返回类型(return type):方法执行后返回的数据类型,如果不返回值则写 `void`
3. 方法名(method name):方法的名字,命名规则和变量名类似
4. 参数列表(parameters):方法执行所需的外部数据,可以没有参数
5. 方法体(body):实际执行的操作
6. 返回值(return):方法执行后返回的结果,如果返回类型是 `void`,则无需 `return`
**方法的调用**
```java
public class MethodInvoke {
// 静态方法
public static void staticMethod() {
System.out.println("这是静态方法");
}
// 无参数、无返回值
public void sayHello() {
System.out.println("Hello, Java!");
}
// 有参数
public void greet(String name) {
System.out.println("Hello, " + name + "!");
}
// 有返回值
public int add(int a, int b) {
return a + b;
}
public static void main(String[] args) {
// 调用静态方法
MethodInvoke.staticMethod();
// 创建对象,调用非静态方法
MethodInvoke invoke = new MethodInvoke();
invoke.sayHello(); // 输出问候
invoke.greet("Alice"); // 输出个性化问候
System.out.println(invoke.add(5, 7)); // 输出 12
}
}
```
### 方法的重载
**定义:** 重载就是在一个类中,有相同的函数名称,但形参不同的函数。
* 返回类型可以相同,也可以不同
* **仅靠返回类型不同不能构成重载**
```java
public class OverloadDemo {
public void show() {
System.out.println("无参数方法");
}
public void show(String name) {
System.out.println("一个参数:" + name);
}
public void show(String name, int age) {
System.out.println("两个参数:" + name + ", " + age);
}
public static void main(String[] args) {
OverloadDemo demo = new OverloadDemo();
demo.show(); // 调用无参数方法
demo.show("Alice"); // 调用一个参数方法
demo.show("Bob", 25); // 调用两个参数方法
}
}
输出:
无参数方法
一个参数:Alice
两个参数:Bob, 25
```
### 命令行传参
有时候你希望运行一个程序时候再传递给它消息。这要靠传递命令行参数给`main()`函数实现。
```java
public class CommandLine {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("args[" + i + "]: " + args[i]);
}
}
}
```
运行方式(文件名为 `CommandLine.java`):
```shell
javac CommandLine.java
java CommandLine hello world 123
输出结果:
args[0]: hello
args[1]: world
args[2]: 123
```
### 可变参数(...)
在 Java 里,**可变参数(varargs)** 用来让方法接收不定数量的参数。
```java
// 语法
返回类型 方法名(参数类型... 参数名) { }
```
**可变参数求最大值案例:**
```java
public class VarArgsMax {
// 使用可变参数求最大值
public static void printMax(double... numbers) {
if (numbers.length == 0) {
System.out.println("未传递参数");
return;
}
double result = numbers[0];
for (int i = 1; i < numbers.length; i++) {
if (numbers[i] > result) {
result = numbers[i];
}
}
System.out.println("最大值是 " + result);
}
public static void main(String[] args) {
printMax(1.2, 3.4, 0.5); // 多个参数
printMax(10); // 单个参数
printMax(); // 没有参数
printMax(new double[]{7.7, 9.9, 8.8}); // 传数组
}
}
```
### 递归(recursion)
👉 递归本质就是就是:**方法自己调用自己**。
递归结构包含两个部分:
- **递归头**:终止条件(避免无限调用)
- **递归体**:继续调用自身(问题规模要缩小)
#### 阶乘(Factorial)
```java
public class RecursionDemo {
// 计算 n 的阶乘:n! = n * (n-1) * (n-2) * ... * 1
public static int factorial(int n) {
if (n == 1) return 1; // 递归头
return n * factorial(n - 1); // 递归体
}
public static void main(String[] args) {
System.out.println("5! = " + factorial(5)); // 输出 120
}
}
```
#### 斐波那契(Fibonacci Sequence)
```java
public class FibonacciDemo {
/**
* 斐波那契数列(Fibonacci Sequence)
* 特点:
* 1. 第1项 = 1,第2项 = 1(固定)
* 2. 从第3项开始,每一项 = 前两项之和
* 数学公式:
* F(1) = 1
* F(2) = 1
* F(n) = F(n-1) + F(n-2) (n >= 3)
*
* 示例:
* 第1项:1
* 第2项:1
* 第3项:2 (1+1)
* 第4项:3 (2+1)
* 第5项:5 (3+2)
* 数列:1, 1, 2, 3, 5, 8, 13, 21, 34, ...
*/
// 使用递归实现斐波那契
public static int fib(int n) {
// 递归出口:第1项或第2项,直接返回1
if (n == 1 || n == 2) return 1;
// 递归体:第n项 = 前两项之和
return fib(n - 1) + fib(n - 2);
}
public static void main(String[] args) {
// 输出前10项
for (int i = 1; i <= 10; i++) {
System.out.print(fib(i) + " ");
}
}
}
```
## 5.Java 数组(Array)
### 什么是数组?
定义:数组是**相同数据类型元素**的有序集合,具有以下特点:
- **连续存储**:数组在内存中占用连续的存储空间。
- **下标访问**:每个元素通过唯一索引访问,下标从0开始。
- **长度固定**:一旦创建,长度不可改变。
- **类型统一**:数组中的所有元素必须是相同类型,可以是基本类型或引用类型。
- **数组是对象**:在Java中,数组属于引用类型,存储在堆内存中,数组元素类似对象的成员变量。
### 数组声明与创建
**声明**
```java
// 声明,以 int 为例
int[] numbers; // ✅ 推荐写法
int numbers2[];
```
**创建**
```java
int[] numbers = new int[5]; // 创建一个长度为5的int数组
```
**访问元素**
* 数组索引从 0 开始,到 length - 1 结束。
* 使用 `数组名[索引]` 访问元素。
```java
numbers[0] = 10; // 修改第一个元素
int first = numbers[0]; // 访问第一个元素
System.out.println(numbers[0]);
```
注意:数组下标范围 `[0, length-1]`,越界会抛出 `ArrayIndexOutOfBoundsException`。
### 数组初始化方式
```java
① 静态初始化
int[] a = {1, 2, 3};
Man[] mans = {new Man(1,1),new Man(2,2)};
② 动态初始化
int[] a = new int[2];
a[0] = 1;
a[1] = 2;
③ 数组的默认初始化
当使用动态初始化时,数组元素会自动被赋予该类型的默认值
```
### 数组内存分析
**先了解Java 内存模型**

数组对象存储在**堆内存**中。
数组变量本身在栈内存中保存**堆地址引用**。
每个线程都有独立的程序计数器(PC),控制执行流程。

### 反转数组
```java
public void reverse(int[] nums) {
int left = 0, right = nums.length - 1;
while (left < right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++;
right--;
}
}
```
### java.util.Arrays 类
```java
import java.util.Arrays;
public class ArraysDemo {
public static void main(String[] args) {
// 初始化一个数组
int[] a = {1, 6, 8, 19, 1000, 999, 178, -1, 0, 5};
System.out.println("原始数组: " + Arrays.toString(a));
// 1. 排序(升序)
Arrays.sort(a);
System.out.println("排序后: " + Arrays.toString(a));
// 2. 二分查找(前提是数组已排序)
int index = Arrays.binarySearch(a, 19);
System.out.println("数字 19 的索引: " + index);
// 3. 填充数组
int[] b = new int[5];
Arrays.fill(b, 7);
System.out.println("填充后的数组: " + Arrays.toString(b));
// 4. 数组复制(扩容)
int[] c = Arrays.copyOf(a, 15);
System.out.println("扩容后的数组: " + Arrays.toString(c));
// 5. 部分复制
int[] d = Arrays.copyOfRange(a, 2, 6); // 下标 [2,6) System.out.println("部分复制: " + Arrays.toString(d));
// 6. 比较数组是否相等
int[] e = { -1, 0, 1, 5, 6, 8, 19, 178, 999, 1000 };
Arrays.sort(e); // 确保顺序一致
System.out.println("数组a与数组e是否相等: " + Arrays.equals(a, e));
// 7. 多维数组打印
int[][] f = {{1, 2}, {3, 4, 5}};
System.out.println("二维数组: " + Arrays.deepToString(f));
// 8. 并行排序(大数组时更快)
int[] g = {10, 9, 8, 7, 6, 5};
Arrays.parallelSort(g);
System.out.println("并行排序结果: " + Arrays.toString(g));
}
}
```
### 冒泡排序
冒泡的代码还是相当简单的。两层循环,外层冒泡轮数,里层依次比较,江湖中人人尽皆知。
```java
import java.util.Arrays;
// 冒泡排序
public class BubbleSortDemo {
public static void main(String[] args) {
int[] a = {1, 6, 8, 19, 1000, 999, 178, -1, 0, 5};
bubbleSort(a);
System.out.println("冒泡排序后: " + Arrays.toString(a));
}
public static void bubbleSort(int[] array) {
// 外层循环控制轮数
for (int i = 0; i < array.length - 1; i++) {
boolean swapped = false; // 标记本轮是否发生过交换
// 内层循环控制比较和交换
for (int j = 0; j < array.length - 1 - i; j++) {
if (array[j] > array[j + 1]) {
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
swapped = true;
}
}
if (!swapped) break;
}
}
}
```
================================================
FILE: src/md/java/datetime/README.md
================================================
---
title: Java 日期时间
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2026-02-03
category: Java Dates
tags:
- date-time
---
# Java 日期时间
> 此文参考文献:[【howtodoinjava】-Java 日期时间教程](https://howtodoinjava.com/java/date-time/guides-java-date-time/)
长期以来,传统的 Java API 一直是 Java 开发人员的一大痛点。随着[Java 8](https://howtodoinjava.com/java-8-tutorial/)版本([JSR-310](https://jcp.org/en/jsr/detail?id=310)
)的发布,`java.time`包引入了新的[不可变](https://howtodoinjava.com/java/basics/how-to-make-a-java-class-immutable/)类,解决了原有类存在的问题。
以下文章旨在帮助您开始使用[新的日期时间 API ](/md/java/date-time/intro-to-date-time-api)的一些常见任务。
## 1. 获取当前日期和时间
- [获取当前日期和时间](/md/java/datetime/datetime)
- [获取当前时间戳](/md/java/datetime/timestamp)
- [获取当前用户区域设置、国际化](/md/java/datetime/locale)
## 2. 比较日期和时间
- [日期比较](/md/java/datetime/compare-dates)
- [ZonedDateTime 和 OffsetDateTime 的区别](/md/java/datetime/zoneddatetime-vs-offsetdatetime)
## 3. 转换日期和时间
- [转换日期时间实例](/md/java/datetime/convert-dates)
## 4. 将字符串解析为日期
- [Java 日期格式验证](https://howtodoinjava.com/java/date-time/date-validation/)
- [将字符串转换为 UTC 日期时间](https://howtodoinjava.com/java/date-time/parse-string-to-date-time-utc-gmt/)
- [将字符串转换为 ZonedDateTime](https://howtodoinjava.com/java/date-time/zoneddatetime-parse/)
- [将字符串转换为本地日期时间](https://howtodoinjava.com/java/date-time/localdatetime-parse/)
- [将字符串转换为本地日期](https://howtodoinjava.com/java/date-time/localdate-parse-string/)
- [将字符串解析为 java.util.Date](https://howtodoinjava.com/java/date-time/java-date-examples/)
- [Java 严格、智能和宽松的日期解析](https://howtodoinjava.com/java/date-time/resolverstyle-strict-date-parsing/)
- Spring Boot 日期格式验证注解(_待办事项_)
- 验证多种日期格式(_待办事项_)
## 5. 将日期格式化为字符串
- [在 Java 中将日期格式化为字符串](https://howtodoinjava.com/java/date-time/java-date-formatting/)
- [格式化分区日期时间](https://howtodoinjava.com/java/date-time/format-zoneddatetime/)
- [格式化本地日期时间](https://howtodoinjava.com/java/date-time/format-localdatetime-to-string/)
- [格式化本地日期](https://howtodoinjava.com/java/date-time/localdate-format-example/)
- [格式 XMLGregorianCalendar](https://howtodoinjava.com/java/date-time/format-xmlgregoriancalendar-to-date-pattern/)
- [基于位置的货币格式](https://howtodoinjava.com/java/date-time/location-based-currency-formatting-in-java/)
- [基于位置的日期时间格式](https://howtodoinjava.com/java/date-time/locale-based-date-formatting/)
- [以用户时区显示本地化时间戳](https://howtodoinjava.com/java/date-time/display-localized-timestamps/)
- [以 12 小时制格式格式化日期/时间戳](https://howtodoinjava.com/java/date-time/format-time-12-hours-pattern/)
- [将毫秒级持续时间格式化为小时、分钟和秒](https://howtodoinjava.com/java/date-time/format-millis-to-hh-mm-ss/)
## 6. 日期和时间提取与处理
- [测量经过时间](https://howtodoinjava.com/java/date-time/execution-elapsed-time/)
- [两个日期之间的差异](/md/java/datetime/date-diff)
- [计算两个日期之间的天数](https://howtodoinjava.com/java/date-time/calculate-days-between-dates/)
- [获取两个日期之间的所有日期](https://howtodoinjava.com/java/date-time/dates-between-two-dates/)
- [检查日期或本地日期是否为周末。](https://howtodoinjava.com/java/date-time/check-weekend/)
- [计算两个日期之间的工作日](https://howtodoinjava.com/java/date-time/calculate-business-days/)
- [增加或减少工作日](https://howtodoinjava.com/java/date-time/add-subtract-business-days/)
- [获取下一个和上一个日期](https://howtodoinjava.com/java/date-time/java8-next-previous-date/)
- [请检查给定年份是否为闰年?](https://howtodoinjava.com/java/date-time/check-leap-year/)
- [增加或减少天数、月份和年份](https://howtodoinjava.com/java/date-time/add-days-months-years/)
- [增加或减少小时、分钟和秒](https://howtodoinjava.com/java/date-time/add-subtract-hours-minutes-seconds/)
- [从日期中获取年、月和日](https://howtodoinjava.com/java/date-time/get-year-month-day-from-date/)
- [确定约会的星期几](https://howtodoinjava.com/java/date-time/finding-day-of-week/)
- [用于匹配日期模式的正则表达式](https://howtodoinjava.com/java/regex/java-regex-date-format-validation/)
- [如何设置 JVM 时区](https://howtodoinjava.com/java/date-time/setting-jvm-timezone/)
- [一天的开始和结束](https://howtodoinjava.com/java/date-time/start-and-end-of-day/)
## 7. 日期时间 API
- [Java 本地化类](https://howtodoinjava.com/java/date-time/java-locale-api-examples/)
- [Java 类 LocalTime](https://howtodoinjava.com/java/date-time/java-localtime/)
- [Java 类 LocalDate](https://howtodoinjava.com/java/date-time/java-time-localdate-class/)
- [Java 类 LocalDateTime](https://howtodoinjava.com/java/date-time/java-localdatetime-class/)
- [Java ZonedDateTime 类](https://howtodoinjava.com/java/date-time/zoneddatetime-class/)
- Java OffsetDateTime(_待办事项_)
- [Java XMLGregorianCalendar](https://howtodoinjava.com/java/date-time/xmlgregoriancalendar-date-string-example/)
- Java SimpleDateFormat (_TODO_)
- [Java DateTimeFormatter](https://howtodoinjava.com/java/date-time/java8-datetimeformatter-example/)
- [爪哇时期](https://howtodoinjava.com/java/date-time/java8-period/)
- Java 持续时间(_待办事项_)
- [Java 星期几](https://howtodoinjava.com/java/date-time/find-dayofweek/)
- [Java 时间调整器](https://howtodoinjava.com/java/date-time/java8-temporal-adjusters/)
- [Java TemporalQuery](https://howtodoinjava.com/java/date-time/temporalquery/)
- Java InstantSource(_待办事项_)
快乐学习!
================================================
FILE: src/md/java/datetime/api.md
================================================
---
title: Java 日期时间 API
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2026-02-03
category: Java Dates
tags:
- date-time
---
# Java 日期时间 API
> 此文参考文献:
>
> - [【菜鸟教程】-Java 日期时间](https://www.runoob.com/java/java-date-time.html)
> - [【howtodoinjava】- Java 8 日期时间 API](https://howtodoinjava.com/java/date-time/intro-to-date-time-api/)
Java 主要使用两个包`java.time`和`java.util`支持日期和时间特性。 `java.time`包是在 Java 8 [JSR-310](https://jcp.org/en/jsr/detail?id=310) 中新增的,解决了旧版
`java.util.Date`和`java.util.Calendar`类的不足之处。
## 遗留 API
Java 8 之前的日期时间处理主要依赖 `Date`、`Calendar`、`SimpleDateFormat` 和 `TimeZone` 等遗留 API,本质上围绕**毫秒时间戳**进行封装、计算、格式化及时区转换。
| 类/方法 | 用途 |
|-------------------------------------------------------------------------------------------------------------------------|-----------------------------------|
| [System.currentTimeMillis()](https://docs.oracle.com/javase/7/docs/api/java/lang/System.html#currentTimeMillis%5C(%5C)) | 获取当前时间的 **毫秒时间戳** |
| [java.util.Date](https://docs.oracle.com/javase/7/docs/api/java/util/Date.html) | 对时间戳进行封装,表示一个具体的 **时间点** |
| [java.util.Calendar](https://docs.oracle.com/javase/7/docs/api/java/util/Calendar.html) | 提供按 **年、月、日、时、分、秒** 等字段进行计算和操作的能力 |
| [java.text.SimpleDateFormat](https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html) | 负责 **日期与字符串之间的格式化与解析**,受时区和区域设置影响 |
| [java.util.TimeZone](https://docs.oracle.com/javase/7/docs/api/java/util/TimeZone.html) | 表示 **时区偏移量**,可计算夏令时 |
**核心缺陷**:
旧的日期时间 API 既易出错又不直观,难以安全、清晰地表示日期与时间。
1. _可变性_:`Date` 和 `Calendar` 类是可变的,这可能导致意外的副作用和**线程安全问题**。
2. _设计缺陷_:月份从 0 开始计数,年份从 1900 开始计数,这可能导致混淆。
3. _有限的功能_:这些类缺乏对日期和时间操作的丰富支持,例如处理时区、闰秒等。
因此,其他许多第三方库(例如 [Joda-Time](https://www.joda.org/joda-time/) 或 [Apache Commons](https://commons.apache.org/) 中的类)也更加受欢迎。
## Java 8 Time API
新的日期 API 尝试解决旧类的上述问题。 它主要包含以下类:
| 类/接口 | 用途 |
| ----------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| [java.time.LocalDate](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html) | 不带时间的**日期** |
| [java.time.LocalTime](https://docs.oracle.com/javase/8/docs/api/java/time/LocalTime.html) | 不带日期的**时间** |
| [java.time.Instant](https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html) | 时间线上的一个**瞬间点** |
| [java.time.LocalDateTime](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html) | **日期和时间** |
| [java.time.ZonedDateTime](https://docs.oracle.com/javase/8/docs/api/java/time/ZonedDateTime.html) | 包含**时区**的完整日期和时间 |
| [java.time.OffsetDateTime](https://docs.oracle.com/javase/8/docs/api/java/time/OffsetDateTime.html) | 包含**UTC 偏移量**的完整日期和时间 |
| [java.time.Duration](https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html) | **时间量**,两个时间点之间的**时间段** |
| [java.time.Period](https://docs.oracle.com/javase/8/docs/api/java/time/Period.html) | **时间量**,两个日期之间的**日期段** |
| [java.time.format.DateTimeFormatter](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html) | **格式化解析器** |
| [java.time.ZoneId](https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html) | **时区标识符**(如 `Asia/Shanghai`) |
| [java.time.ZoneOffset](https://docs.oracle.com/javase/8/docs/api/java/time/ZoneOffset.html) | **UTC 偏移量**(如 `+08:00`) |
## 执行常见任务
[🐟代码小抄-Java 日期、时间常用API](https://codecopy.cn/post/suloc9)
```java
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
/**
* Java8 日期时间API的通用操作
*/
public class Java8DateTimeCommonOperations {
public static void main(String[] args) throws InterruptedException {
// 获取当前日期和时间
// 所有日期时间类都有一个工厂方法 now() 这是在 Java 8 中获取当前日期和时间的首选方法。
LocalTime currentTime = LocalTime.now();
LocalDate currentDate = LocalDate.now();
LocalDateTime currentDateTime = LocalDateTime.now();
System.out.println("当前时间 (LocalTime): " + currentTime);
System.out.println("当前日期 (LocalDate): " + currentDate);
System.out.println("当前日期时间 (LocalDateTime): " + currentDateTime);
// 解析日期和时间
// 日期解析是借助 DateTimeFormatter 类和日期时间类中的 parse() 方法完成的。
String dateString = "2026-02-04 12:30";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime parsedDateTime = LocalDateTime.parse(dateString, formatter);
System.out.println("解析后的日期时间: " + parsedDateTime);
// 格式化日期和时间
// 日期格式化是借助 DateTimeFormatter 类和日期时间类中的 format() 方法完成的。
LocalDateTime myDateObj = LocalDateTime.now();
DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm");
String formattedDate = myDateObj.format(myFormatObj);
System.out.println("格式化后的日期时间: " + formattedDate);
// 测量经过时间
// 要获取不同时间单位的执行时间,可以使用 java.time.Instant 和 java.time.Duration 类中的
// toDays() 、 toHours() 、 toMillis() 、 toMinutes() 、 toNanos() 和 getSeconds() 等方法。
Instant start = Instant.now();
Thread.sleep(1000); // 休眠1s
Instant finish = Instant.now();
long timeElapsed = Duration.between(start, finish).toMillis();
System.out.println("执行时间(毫秒): " + timeElapsed);
// 计算两个日期之间的天数
// 在 Java 8 中使用 ChronoUnit.DAYS.between() 和 LocalDate.until() 方法计算两个日期之间的天数 。
LocalDate date1 = LocalDate.now();
LocalDate date2 = date1.plusDays(99);
long diffInDays = ChronoUnit.DAYS.between(date1, date2);
System.out.println("两个日期之间的天数: " + diffInDays);
}
}
```
================================================
FILE: src/md/java/datetime/compare-dates.md
================================================
---
title: 比较两个日期
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2026-02-06
category: Java Dates
tags:
- date-time
---
# 比较两个日期
> 此文参考文献:
>
> - [【howtodoinjava】- 用 Java 比较两个日期](https://howtodoinjava.com/java/date-time/compare-dates/)
> - [比较 LocalDate](https://howtodoinjava.com/java/date-time/compare-localdates/)
> - [比较 LocalDateTime](https://howtodoinjava.com/java/date-time/compare-localdatetime/)
> - [比较 ZonedDateTime](https://howtodoinjava.com/java/date-time/zoneddatetime-comparison/)
| 方法 / API | 支持类 / 类型 | Java 版本 | 说明 |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------------- |
| `isBefore / isAfter / isEqual` | [LocalDate](https://howtodoinjava.com/java/date-time/java-time-localdate-class/), [LocalTime](https://howtodoinjava.com/java/date-time/java-localdatetime-class/), [LocalDateTime](https://howtodoinjava.com/java/date-time/zoneddatetime-class/), `ZonedDateTime`, `OffsetDateTime`, `Instant` | Java 8+ | 判断先后或相等,语义清晰、线程安全 |
| `compareTo` | `LocalDate`, `LocalTime`, `LocalDateTime`, `ZonedDateTime`, `OffsetDateTime`, `Instant`, `java.util.Date` | 通用 | 排序或比较大小 |
| `before / after` | [java.util.Date](https://howtodoinjava.com/java/date-time/java-date-examples/), `java.util.Calendar` | Java 7 以前 | 判断早于或晚于 |
| `equals` | `java.util.Date`, `java.util.Calendar` | 通用 | 判断相等 |
| `getTime / getTimeInMillis` | `java.util.Date`, `java.util.Calendar` | Java 7 以前 | 获取毫秒时间戳,可直接比较大小 |
**Date 日期比较**
```java
import java.util.Calendar;
import java.util.Date;
public class CompareDatePartOnly {
public static void main(String[] args) {
Date date1 = new Date();
Date date2 = new Date(date1.getTime() + 24 * 60 * 60 * 1000); // 次日
int diff = compareDatePartOnly(date1, date2);
if (diff > 0) {
System.out.println(date1 + " 大于 " + date2);
} else if (diff < 0) {
System.out.println(date1 + " 小于 " + date2);
} else {
System.out.println(date1 + " 等于 " + date2);
}
}
private static int compareDatePartOnly(final Date date1, final Date date2) {
Calendar cal1 = Calendar.getInstance();
Calendar cal2 = Calendar.getInstance();
cal1.setTime(date1);
cal2.setTime(date2);
int result = cal1.get(Calendar.YEAR) - cal2.get(Calendar.YEAR);
if (result == 0) {
result = cal1.get(Calendar.DAY_OF_YEAR) - cal2.get(Calendar.DAY_OF_YEAR);
}
return result;
}
}
```
================================================
FILE: src/md/java/datetime/convert-dates.md
================================================
---
title: 转换日期时间实例
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2026-02-09
category: Java Dates
tags:
- date-time
---
# 转换日期时间实例
本文总结了 Java 中常见的日期时间类之间的转换方法,涵盖了 Java 8 引入的 `java.time` 包中的类(如 `LocalDate`, `LocalDateTime`, `ZonedDateTime`)以及旧版的 `java.util.Date` 和 `java.sql.Date` 等。
* [🐟代码小抄-Java日期转换工具类](https://codecopy.cn/post/q7kdjf)
## 1. LocalDateTime 与 ZonedDateTime 互转
`LocalDateTime` 表示不带时区的日期时间,而 `ZonedDateTime` 是带有时区的日期时间(`LocalDateTime` + `ZoneId`)。
### LocalDateTime -> ZonedDateTime
要将 `LocalDateTime` 转换为 `ZonedDateTime`,必须添加时区信息(Zone Offset)。
```java
LocalDateTime ldt = LocalDateTime.now(); // 本地日期时间
// 添加时区信息
ZoneId zoneId = ZoneId.of("Asia/Shanghai");
ZonedDateTime zdtAtAsia = ldt.atZone(zoneId);
// 转换到另一个时区(保持时间瞬间一致)
ZonedDateTime zdtAtET = zdtAtAsia.withZoneSameInstant(ZoneId.of("America/New_York"));
```
### ZonedDateTime -> LocalDateTime
直接使用 `toLocalDateTime()` 方法,这将丢弃时区信息。
```java
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("America/New_York"));
LocalDateTime ldt = zdt.toLocalDateTime();
```
## 2. LocalDate 与 ZonedDateTime 互转
`LocalDate` 仅包含日期信息,转换为 `ZonedDateTime` 需要补充时间和时区。
### LocalDate -> ZonedDateTime
**方法一:使用 `atStartOfDay`(默认时间为 00:00)**
```java
LocalDate localDate = LocalDate.now();
// 默认当天开始时间 (00:00) + 指定时区
ZonedDateTime zdt = localDate.atStartOfDay(ZoneId.of("America/New_York"));
```
**方法二:先转 LocalDateTime 再转 ZonedDateTime**
```java
LocalDate localDate = LocalDate.now();
// 指定具体时间
LocalDateTime localDateTime = localDate.atTime(14, 30, 0);
// 添加时区
ZonedDateTime zdt = localDateTime.atZone(ZoneId.of("Asia/Shanghai"));
```
### ZonedDateTime -> LocalDate
直接使用 `toLocalDate()`。
```java
ZonedDateTime zdt = ZonedDateTime.now();
LocalDate localDate = zdt.toLocalDate();
```
## 3. LocalDate 与 LocalDateTime 互转
`LocalDateTime` = `LocalDate` + `LocalTime`。
### LocalDate -> LocalDateTime
需要补充时间部分。
```java
LocalDate date = LocalDate.now();
// 1. 当天开始时间 (00:00)
LocalDateTime startOfDay = date.atStartOfDay();
// 2. 当前时间
LocalDateTime withCurrentTime = date.atTime(LocalTime.now());
// 3. 指定时间
LocalDateTime specificTime = date.atTime(14, 30, 0);
```
### LocalDateTime -> LocalDate
直接使用 `toLocalDate()`。
```java
LocalDateTime dateTime = LocalDateTime.now();
LocalDate date = dateTime.toLocalDate();
```
## 4. LocalDate 与 java.sql.Date 互转
`java.sql.Date` 是旧版 JDBC API 使用的类,仅包含日期部分(时间设为 00:00:00)。
### java.sql.Date -> LocalDate
```java
java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis());
LocalDate localDate = sqlDate.toLocalDate();
```
### LocalDate -> java.sql.Date
```java
LocalDate localDate = LocalDate.now();
java.sql.Date sqlDate = java.sql.Date.valueOf(localDate);
```
## 5. LocalTime 与 java.sql.Time 互转
### LocalTime -> java.sql.Time
注意:`java.sql.Time` 不包含纳秒精度。
```java
LocalTime localTime = LocalTime.now();
java.sql.Time sqlTime = java.sql.Time.valueOf(localTime);
```
### java.sql.Time -> LocalTime
```java
java.sql.Time sqlTime = new java.sql.Time(System.currentTimeMillis());
LocalTime localTime = sqlTime.toLocalTime();
```
## 6. LocalDate 与 java.util.Date 互转
`java.util.Date` 包含日期和时间,且基于 Epoch 毫秒数。转换时通常需要借助 `Instant` 和系统默认时区。
### java.util.Date -> LocalDate
```java
Date date = new Date();
LocalDate localDate = Instant.ofEpochMilli(date.getTime())
.atZone(ZoneId.systemDefault())
.toLocalDate();
```
### LocalDate -> java.util.Date
需要先转为当天开始时间的 `ZonedDateTime` 或 `Instant`。
```java
LocalDate localDate = LocalDate.now();
Date date = Date.from(localDate.atStartOfDay()
.atZone(ZoneId.systemDefault())
.toInstant());
```
## 7. LocalDateTime 与 java.util.Date 互转
### java.util.Date -> LocalDateTime
```java
Date date = new Date();
LocalDateTime ldt = Instant.ofEpochMilli(date.getTime())
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
```
### LocalDateTime -> java.util.Date
```java
LocalDateTime ldt = LocalDateTime.now();
Date date = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
```
## 8. 时区转换与 EST/EDT 处理
### ZonedDateTime 时区转换
使用 `withZoneSameInstant` 方法将同一时刻转换为另一时区的表达。
```java
ZonedDateTime now = ZonedDateTime.now(); // 当前时区
ZonedDateTime utc = now.withZoneSameInstant(ZoneId.of("UTC"));
ZonedDateTime ny = now.withZoneSameInstant(ZoneId.of("America/New_York"));
```
### OffsetDateTime 时区转换
类似地,使用 `withOffsetSameInstant`。
```java
OffsetDateTime now = OffsetDateTime.now();
OffsetDateTime utc = now.withOffsetSameInstant(ZoneOffset.UTC);
```
### java.util.Date 时区格式化
java.util.Date 表示一个时间点(UTC),但在格式化时会根据默认时区进行展示。可以通过设置 `SimpleDateFormat` 的时区来控制输出。
- [如何设置java.util.Date的时间区域? - Stack Overflow](https://stackoverflow.com/questions/2891361/how-to-set-time-zone-of-a-java-util-date/30403673#30403673).
```java
SimpleDateFormat FORMATTER = new SimpleDateFormat("MM/dd/yyyy 'at' hh:mma z");
Date currentDate = new Date();
// 默认系统时区(东八区)
System.out.println(FORMATTER.format(currentDate)); //02/12/2026 at 02:08上午 CST
// 指定 UTC 时区
FORMATTER.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(FORMATTER.format(currentDate)); //02/11/2026 at 06:08下午 UTC
```
### EST, EDT 与 "America/New_York"
* **EST**: Eastern Standard Time (UTC-5),不包含夏令时。
* **EDT**: Eastern Daylight Time (UTC-4),夏令时。
* **EST5EDT**: 自动切换 EST 和 EDT。
* **America/New_York**: 推荐使用,涵盖了 1966 年以后的所有规则,自动处理夏令时。
转换示例:
```java
// 将 ZonedDateTime 转换为 ET 时区
ZonedDateTime ist = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime et = ist.withZoneSameInstant(ZoneId.of("America/New_York")); //推荐
```
## 9. Instant 与 LocalDateTime 互转
`Instant` 代表时间轴上的一个点(UTC)。
### Instant -> LocalDateTime
需要指定时区偏移。
```java
Instant instant = Instant.now();
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
LocalDate ld = LocalDate.ofInstant(instant, ZoneOffset.systemDefault());
LocalTime lt = LocalTime.ofInstant(instant, ZoneOffset.systemDefault());
// 或者
LocalDateTime ldt2 = instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
LocalDate ld2 = instant.atZone(ZoneOffset.systemDefault()).toLocalDate();
LocalTime lt2 = instant.atZone(ZoneOffset.systemDefault()).toLocalTime();
```
### LocalDateTime -> Instant
需要指定时区偏移。
```java
LocalDateTime ldt = LocalDateTime.now();
Instant instant = ldt.atZone(ZoneId.systemDefault()).toInstant();
```
================================================
FILE: src/md/java/datetime/date-diff.md
================================================
---
title: 获取两个日期之间的时间差
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2026-02-05
category: Java Dates
tags:
- date-time
---
# 获取两个日期之间的时间差
## 1. Java 8 推荐
传统的 Java 类一直缺乏足够的支持来有效地表达日期和时间段。Java 8 首次尝试升级此日期/时间 API。
### 1.1 ChronoUnit
**用于计算所有时间单位之间的差值**
- 获取两个日期或时间之间的绝对差值(按**单位**),适用所有`Temporal`类型(如 `LocalDate`,`LocalDateTime`,`ZonedDateTime` 等)。
```java
// 日期级单位(仅依赖日期)
long years = ChronoUnit.YEARS.between(startDate, endDate);
long months = ChronoUnit.MONTHS.between(startDate, endDate);
long days = ChronoUnit.DAYS.between(startDate, endDate);
// 时间级单位(需包含时间部分,如 LocalDateTime)
long hours = ChronoUnit.HOURS.between(startDateTime, endDateTime);
long minutes = ChronoUnit.MINUTES.between(startDateTime, endDateTime);
long seconds = ChronoUnit.SECONDS.between(startDateTime, endDateTime);
long millis = ChronoUnit.MILLIS.between(startDateTime, endDateTime);
long micros = ChronoUnit.MICROS.between(startDateTime, endDateTime);
long nanos = ChronoUnit.NANOS.between(startDateTime, endDateTime);
```
### 1.2 Period-日期段
**用于计算天数、月数和年数的差值**
```java
// 包含开始日期,不包含结束日期
Period diff = Period.between(startDate, endDate);
int years = diff.getYears(); // 完整年数
int months = diff.getMonths(); // 剩余月数
int days = diff.getDays(); // 剩余天数
// ⚠️ 仅为近似值,不推荐用于精确计算
int totalDays = years * 365 + months * 30 + days;
```
⚠️ 注意事项
- `Period` 以 “x 年 y 月 z 日” 的形式表示时间差
- `getDays()` 返回的是剩余天数,并非两个日期之间的总天数
- 结果可能为负值(当结束日期早于开始日期)
### 1.3 Duration-时间段
**计算基于时间线的精确时间差**
**Duration**表示以小时、分钟、秒、纳秒等较小时间单位表示的时间差。
```java
Duration duration = Duration.between(dateTime, dateTime2);
long hours = duration.toHours(); // 总小时数
long minutes = duration.toMinutes(); // 总分钟数
long seconds = duration.getSeconds(); // 总秒数
long millis = duration.toMillis(); // 总毫秒数
long micros = duration.toNanos() / 1_000; // 总微秒数
int nanos = duration.getNano(); // 剩余纳秒(非总纳秒)
```
⚠️ 仅对包含时间部分的日期时间对象有效(如 `LocalDateTime`、`Instant`)。
## 2. Joda‑Time 库
对于Java8之前的旧项目,还可以用 [Joda‑Time](https://www.joda.org/joda-time/)。

由于我们都更喜欢可读性,我建议使用 Jodatime 库(它实际上启发了 Java 8 日期/时间 API)。
1. 导入依赖`joda-time`依赖
2. 演示代码
```java
DateTime dateOfBirth = new DateTime(1988, 7, 4, 0, 0, GregorianChronology.getInstance());
DateTime currentDate = new DateTime(); // 当前时间
Days diffInDays = Days.daysBetween(dateOfBirth, currentDate); // 计算天数差
Hours diffInHours = Hours.hoursBetween(dateOfBirth, currentDate); // 计算小时差
Minutes diffInMinutes = Minutes.minutesBetween(dateOfBirth, currentDate); // 计算分钟差
Seconds diffInSeconds = Seconds.secondsBetween(dateOfBirth, currentDate); // 计算秒数差
```
## 3. Legacy API 方法(不推荐)
为了便于参考,我们来看一个使用 `java.util.Date` 和 `TimeUnit` 类查找日期差的示例。
```java
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class DateDifference {
public static void main(final String[] args) {
// 第一个日期:今天
Date today = new Date();
// 第二个日期:下个月同一天
Date sameDayNextMonth = new Date();
sameDayNextMonth.setMonth(today.getMonth() + 1); // 注意:月份从0开始
// 计算两个日期的时间差
long days = getDateDiff(today, sameDayNextMonth, TimeUnit.DAYS);
long hours = getDateDiff(today, sameDayNextMonth, TimeUnit.HOURS);
long minutes = getDateDiff(today, sameDayNextMonth, TimeUnit.MINUTES);
long seconds = getDateDiff(today, sameDayNextMonth, TimeUnit.SECONDS);
long mills = getDateDiff(today, sameDayNextMonth, TimeUnit.MILLISECONDS);
}
/**
* 计算两个日期之间的差值,并以指定的时间单位返回
*
* @param date1 起始日期
* @param date2 结束日期
* @param timeUnit 返回的时间单位(天、小时、分钟等)
* @return 两个日期之间的差值
*/
public static long getDateDiff(final Date date1, final Date date2,
final TimeUnit timeUnit) {
long diffInMillies = date2.getTime() - date1.getTime(); // 毫秒差
// 将毫秒差转换成指定时间单位
return timeUnit.convert(diffInMillies, TimeUnit.MILLISECONDS);
}
}
```
================================================
FILE: src/md/java/datetime/datetime.md
================================================
---
title: 获取当前日期和时间
shortTitle:
description: Java 提供了许多有用的方法来获取当前日期或当前时间,可以使用 `Date`、`Calendar` 以及 Java 8 Date/Time API 类中新引入的 `LocalDate`、`LocalDateTime` 和 `ZonedDateTime` 类。
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2026-02-04
category: Java Dates
tags:
- date-time
---
# 获取当前日期和时间
介绍在 Java 中获取当前 **日期 / 时间 / 日期时间** 的不同方式,分 Java 8 及更高版本和旧版本来讲解。
- 对于 JDK 8 或更高版本,推荐使用 [LocalDate](https://www.runoob.com/java/java-localdate-class.html) 和 [LocalTime](https://www.runoob.com/java/java-localtime-class.html) 类。
- 对于 JDK 7 或更早版本,我们只能使用 [Date](https://www.runoob.com/java/java-date-class.html) 和 [Calendar](https://www.runoob.com/java/java-calendar-class.html) 类。
## 1. Java 8 及以后(推荐方式)
### 1.1 核心 API(java.time 包)
- [java.time.LocalDate](https://howtodoinjava.com/java/date-time/java-time-localdate-class/) – **仅**表示 _yyyy-MM-dd_ 格式的日期信息。
- [java.time.LocalTime](https://howtodoinjava.com/java/date-time/java-localtime/) – 表示 _HH:mm:ss.SSSSSSSS_ 格式的时间信息 。
- [java.time.LocalDateTime](https://howtodoinjava.com/java/date-time/java-localdatetime-class/) – 表示**日期和时间信息,不包含任何时区信息** 。其模式是本地日期和时间信息的组合。
要获取**其他时区/语言环境下的当前日期和时间信息** ,我们可以使用以下类。
- [java.time.ZonedDateTime](https://howtodoinjava.com/java/date-time/zoneddatetime-class/) – 表示**给定时区的日期和时间信息** 。
### 1.2 示例代码
```java
LocalDate today = LocalDate.now(); //2026-02-04
LocalTime currentTime = LocalTime.now(); //13:05:55.540946
LocalDateTime now = LocalDateTime.now(); //2026-02-04T13:05:55.540982
```
若需要带时区:
```java
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("GMT")); //2026-02-04T05:06:28.188180Z[GMT]
```
默认的 `now()` 返回当前系统时区的日期/时间。
### 1.3 自定义格式化输出
如果希望控制显示格式(比如 `dd‑MM‑yyyy hh:mm`),可以用 **DateTimeFormatter**:
```java
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm");
String formatted = fmt.format(LocalDateTime.now()); // 04-02-2026 01:06
```
这样可以输出自定义样式的字符串。
## 2. 旧版 Java(JDK 7 及更早)
在 Java 8 之前,没有 `java.time` 包,需要用旧类:
- **java.util.Date**
- **java.util.Calendar**
### 2.1 代码示例
```java
Date date = new Date(); //Wed Feb 04 13:07:38 CST 2026
Calendar cal = Calendar.getInstance(); //Wed Feb 04 13:08:13 CST 2026
```
### 2.2 自定义格式化输出
并通过 **SimpleDateFormat** 做格式化输出。
```java
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy hh:mm");
System.out.println(sdf.format(date)); //04-02-2026 01:10
System.out.println(sdf.format(cal.getTime())); //04-02-2026 01:10
```
⚠️ 这种方式的类设计较旧且非线程安全,因此不推荐在新代码中使用。
================================================
FILE: src/md/java/datetime/locale.md
================================================
---
title: 获取用户的区域设置、国际化
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2026-02-04
category: Java Dates
tags:
- date-time
- 国际化
---
# 获取用户的区域设置、国际化
> 此文参考文献:
>
> * [【howtodoinjava】- 在 Java 中获取当前区域设置](https://howtodoinjava.com/java/date-time/how-to-get-current-user-locale-in-java/)
> * [【howtodoinjava】- Java 本地化:一份全面的指南](https://howtodoinjava.com/java/date-time/java-locale-api-examples/)
## 1. Locale 简介
`java.util.Locale` 是 Java 国际化 (i18n) 的核心类,用于标识特定的**语言**和**地区**。它是所有**区域设置敏感**类(如 `DateFormat`, `NumberFormat`, `MessageFormat`)的基础配置对象。
### 1.1 核心组成
`Locale` 对象在逻辑上由语言、国家、脚本、变体和扩展等字段组成(遵循 [IETF BCP 47](https://www.rfc-editor.org/info/bcp47) 标准)
| 组成部分 | 描述 | 示例 |
| :------------------ | :---------------------- | :------------------------------------------------ |
| **语言** (Language) | ISO 639-1 两字母代码 | `en` (英语), `zh` (中文) |
| **国家/地区** (Country) | ISO 3166-1 两字母代码 | `US` (美国), `CN` (中国) |
| 脚本(Script) | ISO 15924 四字母代码 | `"Latn"` (拉丁字母), `"Hans"` (简体中文), `"Hant"` (繁体中文) |
| 变体 (Variant) | 特定方言或版本 (可选) | `Traditional_WIN`, `POSIX` |
| 扩展(Extensions) | 额外信息,遵循 BCP 47 扩展语法(可选) | `u-ca-gregory`(使用公历), `x-lvariant-POSIX`(私有扩展) |
### 1.2 获取用户区域设置

**Web 应用**:通常根据 HTTP 请求头 `Accept-Language` 获取用户偏好:
```java
Locale locale = request.getLocale();
System.out.println(locale.getDisplayName()); // 例如: 中文 (中国)
```
**桌面/服务端应用**:默认使用 JVM 或操作系统的设置:
```java
// 获取默认 Locale
Locale defaultLocale = Locale.getDefault();
// 或者通过系统属性
String lang = System.getProperty("user.language");
String region = System.getProperty("user.country");
```
## 2. 创建 Locale 实例
### 2.1 推荐方式 (Java 19+)
从 Java 19 开始,推荐使用静态工厂方法 `Locale.of()`:
```java
Locale us = Locale.of("en", "US");
Locale zh = Locale.of("zh", "CN");
```
> **注意**:`new Locale("en", "US")` 构造函数在 Java 19 中已被弃用,建议改用 `Locale.of()`。
### 2.2 内置常量
对于主要国家和语言,直接使用内置常量:
```java
Locale us = Locale.US;
Locale china = Locale.CHINA;
Locale simplifiedChinese = Locale.SIMPLIFIED_CHINESE;
```
### 2.3 构建器与标签解析
针对复杂场景或标准标签:
```java
// 1. 使用 Builder (更灵活,自带校验)
Locale locale = new Locale.Builder()
.setLanguage("en")
.setRegion("US")
.build();
// 2. 解析 BCP 47 语言标签
Locale usLocale = Locale.forLanguageTag("en-US");
Locale fromTag = Locale.forLanguageTag("zh-cmn-Hans-CN");
```
## 3. 设置默认 Locale
虽然通常**不建议**修改全局默认值,但在某些特定场景下需要强制指定:
```java
// 全局设置
Locale.setDefault(Locale.US);
// 分类设置 (Java 7+):独立控制显示与格式化
Locale.setDefault(Locale.Category.DISPLAY, Locale.US); // 影响 UI 界面/日志语言
Locale.setDefault(Locale.Category.FORMAT, Locale.FRANCE); // 影响 日期/货币格式
```
## 4. 常见应用场景
### 4.1 格式化 (日期/数字/货币)
`Locale` 决定了数据的展示格式:
```java
Locale us = Locale.US;
double num = 123456.789;
// 1. 数字格式化
System.out.println(NumberFormat.getInstance(us).format(num));
// 输出: 123,456.789
// 2. 货币格式化
System.out.println(NumberFormat.getCurrencyInstance(us).format(num));
// 输出: $123,456.79
// 3. 日期格式化
System.out.println(DateFormat.getDateInstance(DateFormat.LONG, us).format(new Date()));
// 输出示例: February 5, 2026
```
### 4.2 国际化消息 (ResourceBundle)
根据 `Locale` 加载对应的资源文件(如 `msg_en.properties`, `msg_zh.properties`):
```java
Locale zhCN = Locale.SIMPLIFIED_CHINESE; //简化中文
Locale enUS = Locale.US; //美式英语
// 加载 resources/i18n 包下的
// messages_zh_CN.properties 和 messages_en_US.properties
ResourceBundle bundleZh = ResourceBundle.getBundle("i18n.messages", zhCN);
ResourceBundle bundleEn = ResourceBundle.getBundle("i18n.messages", enUS);
System.out.println("中文消息: " + bundleZh.getString("greeting"));
System.out.println("英文消息: " + bundleEn.getString("greeting"));
```
---
**参考资料:**
* [Java国际化实践实现多语言及日期货币本地化-开发者社区-阿里云](https://developer.aliyun.com/article/1556409)
* [Java学习路线-21:国际化Locale、ResourceBundle、MessageFormat-阿里云开发者社区](https://developer.aliyun.com/article/1008126)
**资源:**
* [ISO 国家代码](https://www.iso.org/obp/ui/#search)
* [ISO 语言代码](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
================================================
FILE: src/md/java/datetime/timestamp.md
================================================
---
title: 获取当前时间戳
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2026-02-04
category: Java Dates
tags:
- date-time
---
# 获取当前时间戳
> 在Java中,**时间戳**(Timestamp)用来表示一个特定的**时间点**(`Instant`),本质上是一个与时区无关的时间线坐标。
> 它通常表示自 **1970-01-01 00:00:00 UTC**(Unix 纪元) 起经过的时间长度。精度到**纳秒**。
## 1. 获取当前时间戳
在Java中,时间戳用以下类表示:
- [java.time.Instant](https://www.runoob.com/java/java-instant-class.html)
- `java.sql.Timestamp`(Java 7 及以前)
### 1.1 Instant
`Instant`表示从 **系统 UTC 时钟** 获取的 **当前时间点**。
```java
Instant instant = Instant.now(); //2026-02-04T14:19:17.793293Z
```
⚠️ 思考 [Java 17 中 Instant.now 的精度改变了吗?](https://www.reddit.com/r/java/comments/1ffwkc7/has_the_precision_of_instantnow_changed_in_java_17/?tl=zh-hans)
### 1.2 Timestamp
`Timestamp`是`java.util.Date`的子类,表示一个具体的时间点,精确到**毫秒**。
```java
// 基于系统毫秒时间戳
Timestamp timestamp1 = new Timestamp(System.currentTimeMillis()); //2026-02-04 22:47:47.582
// 通过 Date 转换
Date date = new Date();
Timestamp timestamp2 = new Timestamp(date.getTime()); //2026-02-04 22:47:47.586
```
如果你需要兼容旧 API(比如 JDBC),可以用 `java.sql.Timestamp`:
## 2. Instant vs ZonedDateTime
从表面上看, _Instant 类_ 和 [ZonedDateTime](https://howtodoinjava.com/java/date-time/zoneddatetime-class/) 类似乎很相似,但实际上并非如此。
- `Instant` 是 UTC时间的一个时间点。
- `ZonedDateTime` 是一个**特定时区**的实际时间点。

[【howtodoinjava】- 了解 java.time.ZonedDateTime](https://howtodoinjava.com/java/date-time/zoneddatetime-class/)
================================================
FILE: src/md/java/datetime/zoneddatetime-vs-offsetdatetime.md
================================================
---
title: ZonedDateTime 和 OffsetDateTime 的区别
shortTitle:
description: 详解 Java 8 日期时间 API 中 ZonedDateTime 和 OffsetDateTime 的核心区别、使用场景及代码示例。
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2026-02-07
category: Java Dates
tags:
- date-time
---
# ZonedDateTime 和 OffsetDateTime 的区别
> 此文参考文献:1
>
> * [【howtodoinjava】- ZonedDateTime 和 OffsetDateTime 的区别](https://howtodoinjava.com/java/date-time/zoneddatetime-vs-offsetdatetime/)
> * [【Baeldung】- ZonedDateTime 和 OffsetDateTime 的区别](https://www.baeldung.com/java-zoneddatetime-offsetdatetime)
先了解两个所依赖的两个核心概念:
- [ZoneOffset](https://docs.oracle.com/javase/8/docs/api/java/time/ZoneOffset.html):**UTC 偏移量**(如 `+08:00`)
- [ZoneId](https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html):**时区标识符**(如 `Asia/Shanghai`),包含该区域的所有时间规则(**夏令时**切换等)。
## 1. 核心定义
| 特性 | OffsetDateTime | ZonedDateTime |
|:--------------|:-------------------------------|:--------------------------------------------|
| **组成** | `LocalDateTime` + `ZoneOffset` | `LocalDateTime` + `ZoneId` (+ `ZoneOffset`) |
| **时区感知** | 仅知道偏移量(Offset),不知道地理时区 | 知道具体的地理时区(Zone ID) |
| **夏令时 (DST)** | **不处理**。偏移量是固定的。 | **自动处理**。根据规则调整偏移量。 |
| **使用场景** | 数据库存储、网络通信、日志记录 | 用户界面显示、复杂的日期计算(如日历应用) |
| **复杂度** | 较低 | 较高 |
### 1.1 OffsetDateTime
`OffsetDateTime` 表示一个**不可变的日期时间**,精确到纳秒,并携带相对于 UTC 的**固定偏移量**(ISO-8601)。
- 每个实例都对应**时间线上的唯一时刻**
- 非常适合**与时区无关的时间戳**场景(数据库存储,网络传输 / XML / API 时间戳)
```java
// 本质上:OffsetDateTime = LocalDateTime + ZoneOffset
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.of("UTC"));
```
### 1.2 ZonedDateTime
`ZonedDateTime` 也是带时区的日期时间表示,但它基于**完整的时区规则**。
- 使用 **ZoneId**
- **会自动处理夏令时(DST)**
- 同一地区在不同时间点,**偏移量可能不同**
```java
// 本质上:ZonedDateTime = LocalDateTime + ZoneId
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("America/Los_Angeles"));
```
内置方法,用于将给定日期从一个时区转换为另一个时区:
```java
```java
ZonedDateTime destZonedDateTime = sourceZonedDateTime.withZoneSameInstant(destZoneId);
```
## 2. 代码示例
### 2.1 创建实例
```java
// 获取当前时间
OffsetDateTime odt = OffsetDateTime.now();
ZonedDateTime zdt = ZonedDateTime.now();
System.out.println("OffsetDateTime: "+odt);
// 输出示例: 2026-02-07T23:30:00.132+08:00
System.out.println("ZonedDateTime: "+zdt);
// 输出示例: 2026-02-07T23:32:53.132632+08:00[Asia/Shanghai]
```
⚠️注意 `ZonedDateTime` 的输出末尾包含 `[Asia/Shanghai]` 这样的时区 ID。
### 2.2 相互转换
可以轻松地在两者之间进行转换:
```java
// ZonedDateTime -> OffsetDateTime
OffsetDateTime odtFromZdt = zdt.toOffsetDateTime();
// OffsetDateTime -> ZonedDateTime (需要提供 ZoneId)
ZonedDateTime zdtFromOdt = odt.atZoneSameInstant(ZoneId.of("America/New_York"));
```
### 2.3 夏令时处理差异
假设我们要给当前时间加上 6 个月。如果跨越了夏令时切换点:
- `OffsetDateTime` 会简单地增加时间,**保持偏移量不变**。
- `ZonedDateTime` 会增加时间,并**检查新日期的时区规则**,如果该日期处于夏令时(或退出了夏令时),它会自动调整偏移量。
```java
// 使用纽约时区,2026 年美国夏令时切换是在 3月8日(第二个周日)
ZoneId zoneId = ZoneId.of("America/New_York");
// LocalDateTime(本地时间不包含时区信息)
LocalDateTime localDateTime = LocalDateTime.of(2026, 3, 7, 10, 0);
System.out.println("LocalDateTime (未关联时区): " + localDateTime);
// ZonedDateTime(会关联时区并自动考虑夏令时规则)
ZonedDateTime zdt = ZonedDateTime.of(localDateTime, zoneId);
System.out.println("ZonedDateTime (关联时区): " + zdt);
ZonedDateTime zdtNextDay = zdt.plusDays(1);
System.out.println("ZonedDateTime (DST感知 +1天): " + zdtNextDay);
// OffsetDateTime(固定偏移量,不受夏令时影响)
OffsetDateTime odt = OffsetDateTime.of(localDateTime, ZoneOffset.of("-05:00"));
System.out.println("OffsetDateTime (固定偏移): " + odt);
OffsetDateTime odtNextDay = odt.plusDays(1);
System.out.println("OffsetDateTime (固定偏移 +1天): " + odtNextDay);
```
================================================
FILE: src/md/java/features/Java10/jep286-local-variable-type-inference.md
================================================
---
title: Java 10 新特性:局部变量类型推断
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
date: 2023-12-23
sticky: false
star: false
category: Java Features
tag:
- java
order: 286
---
# Java 10 新特性:局部变量类型推断
Java 10 引入了一项新的语言特性,即**局部变量类型推断**(Local-Variable Type Inference),
它允许在局部变量声明时,根据变量的初始值,推断出变量的数据类型。
## 语法
局部变量类型推断的语法非常简单,只需要将 `var` 关键字作为局部变量的类型即可。
```java
var list = new ArrayList(); // 自动推断 ArrayList
var stream = list.stream(); // 自动推断 Stream
```
## 示例
相比传统的方式和 Java 7 的钻石操作符(Diamond Operator),Java 10 的局部变量类型推断使得代码更加精炼:
```java
// 传统方式(等号两边都需要)
List list = new ArrayList();
// Java7的钻石操作符(Diamond Operator)(只需要在左边申明类型即可)
List list = new ArrayList<>();
// Java10的局部变量类型推断(类型在等号右边决定)
var list = new ArrayList();
```
在使用 var 进行局部变量类型推断时,需要注意以下几点:
1. 必须在声明的同时进行初始化
2. 仅限于局部变量,不能用于定义成员变量、方法参数和返回类型
3. 每次只能定义一个变量,不能复合声明多个变量
通过使用局部变量类型推断,我们能够在不失代码可读性的前提下,减少了冗余的类型声明,使得代码更加简洁清晰。
这一特性尤其在Lambda表达式、集合初始化等场景下表现突出,提高了代码的书写效率。
在实际项目中,合理运用局部变量类型推断,将有助于代码的维护和阅读。
================================================
FILE: src/md/java/features/Java11/jep320-remove-JavaEE-CORBA.md
================================================
---
title: Java 11 新特性 :移除JavaEE和CORBA模块以及JavaFX
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2023-12-24
category: Java Features
tag:
- java
order: 320
---
# Java 11 新特性 :移除JavaEE和CORBA模块以及JavaFX
Java 11 中移除了 `Java EE` 和 `CORBA` 模块,同时 `JavaFX` 也被剥离,但仍可作为独立模块使用。
## Java9 弃用过程
在 Java 9 中,`Java EE` 和 `CORBA` 模块被标记为 @Deprecated,为开发者提供了适应期。
## Java11 彻底删除
Java 11 完全删除了以下九个模块:
* java.xml.ws(包含 JAX-WS、SAAJ 和 Web 服务元数据)
* java.xml.bind(JAXB)
* java.activation(JAF)
* java.xml.ws.annotation(常用注解)
* java.corba(CORBA)
* java.transaction(JTA)
* java.se.ee (以上6个模块的聚合模块)
* jdk.xml.ws (JAX-WS 工具)
* jdk.xml.bind (JAXB 工具)
删除后的影响:
* 源代码从 OpenJDK 存储库中删除
* 在 JDK 运行时映像中将不包含这些类
* 相关工具将不再可用:
* wsgen and wsimport (来自 jdk.xml.ws)
* schemagen and xjc (来自 jdk.xml.bind)
* idlj, orbd, servertool, and tnamesrv (来自 java.corba)
* JNDI CosNaming 提供者 (来自 java.corba) 将不再可用
* 不再有命令行标志能够启用它们,就像 JDK 9 上的 --add-modules 一样
## JavaFX 移除
`JavaFX` 在 Java 11 中被移除,但仍可以作为独立模块使用。
开发者需要额外的配置和依赖,以在项目中继续使用 JavaFX 技术。
================================================
FILE: src/md/java/features/Java14/jep361-switch-expressions.md
================================================
---
title: Java 14 新特性:switch表达式增强
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2023-12-25
category: Java Features
tag:
- java
order: 361
---
# Java 14 新特性:switch表达式增强
Java 14(JEP 361)引入了 switch 表达式的新特性,其中包括了 "**箭头标签**(`case ... ->`)" 和 `yield` 语句的增强,
同时支持 `Lambda` 语法,使得代码更加灵活、简洁,并为未来的**模式匹配**(JEP 305)特性做好了准备。
## 传统的switch语句
首先,让我们回顾一下传统的switch语句,它们在处理多个条件时可能显得有些冗长:
```java
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
System.out.println(6);
break;
case TUESDAY:
System.out.println(7);
break;
case THURSDAY:
case SATURDAY:
System.out.println(8);
break;
case WEDNESDAY:
System.out.println(9);
break;
}
```
传统的 switch 语句存在以下问题:
1. 设计受到C和C++等低级语言的影响,且默认支持fall through语义
2. 过多的`break`语句使得代码显得冗长
## switch表达式增强
### 箭头标签(case L ->)
1. 引入了一种新的开关标签"`case L ->`",用于表示只有一个分支的情况
2. 允许每种情况下有多个常量,用逗号分隔
3. 标签右侧的代码仅限于表达式、块或抛出异常throw语句
```java
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
case TUESDAY -> System.out.println(7);
case THURSDAY, SATURDAY -> System.out.println(8);
case WEDNESDAY -> System.out.println(9);
}
```
### 局部变量独立作用域
在 Java 14 中,允许在每个分支中声明局部变量,避免块中变量命名冲突和误用。
```java
switch (day) {
case MONDAY:
case TUESDAY:
int temp = ... // 'temp'的作用域延续到 }
break;
case WEDNESDAY:
case THURSDAY:
int temp2 = ... // 不能将此变量命名为'temp'
break;
default:
int temp3 = ... // 不能将此变量命名为'temp'
}
```
### switch表达式
Switch 表达式被引入,允许将 `switch` 语句用作表达式,通过 `Lambda` 语法,根据输入值返回不同的结果。
```java
// 根据输入值`k`的不同,返回不同的字符串,并通过`System.out.println`打印结果
static void howMany(int k) {
System.out.println(
switch (k) {
case 1 -> "one";
case 2 -> "two";
default -> "many";
}
);
}
```
Switch表达式的常见形式如下:
```java
T result = switch (arg) {
case L1 -> e1;
case L2 -> e2;
default -> e3;
};
```
> Switch表达式是多态表达式(poly expression)。
>
> 多态性是指在编译时不确定具体类型,而在运行时确定类型的特性。
### yield语句返回值
允许在switch表达式中使用`yield`语句,而不是使用`break`语句,用于返回一个值,结束switch表达式的执行。
```java
int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> {
int temp = performComplexCalculation(); // 在这里进行一些复杂的计算
yield temp; // 使用yield返回计算结果
}
};
```
## 发展脉络
追溯JEP 361的发展历程:从JDK 12预览版(JEP 325)到JDK 13预览版(JEP 354),
虽然部分功能在早期版本中已经出现,但建议在 JDK 14 及以后的版本中使用,以获得更好的稳定性和支持。
================================================
FILE: src/md/java/features/Java15/jep371-hidden-classes.md
================================================
---
title: Java 15 新特性:隐藏类(Hidden Classes)
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2023-12-26
category: Java Features
tag:
- java
order: 371
---
# Java 15 新特性:隐藏类(Hidden Classes)
**隐藏类**(Hidden Classes) 提供了一种在运行时生成类的机制,在编译时未知,并且不能直接在源代码中引用,
需要通过反射间接使用它们,隐藏类是为框架设计的,具有以下特性:
* **动态生成内部类**:隐藏类天生为框架设计,在运行时生成内部类
* **反射访问限制**:隐藏类只能通过反射访问,不能直接被其他类的字节码访问
* **独立加载和卸载**:隐藏类可以独立于其他类加载和卸载
* **框架扩展性**:适用于需要在运行时生成类的框架,提高语言的灵活性和效率
## 原理
## 框架中应用
[https://bugs.openjdk.org/browse/JDK-8220607](https://bugs.openjdk.org/browse/JDK-8220607)
================================================
FILE: src/md/java/features/Java15/jep378-text-blocks.md
================================================
---
title: Java 15 新特性:文本块(Text Blocks)
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2023-12-27
category: Java Features
tag:
- java
order: 378
---
# Java 15 新特性:文本块(Text Blocks)
Java 15(JEP 378)引入了**文本块**(Text Blocks)这一新特性,旨在简化多行字符串的表示,提高代码可读性,并减少在字符串中使用转义符的需求。
文本块通过引入三个双引号的**胖分隔符**(`"""`)来实现,同时支持转义序列,为开发人员提供更直观、易读的字符串处理方式。
## 快速上手
**HTML示例**
```java
// 使用“一维”字符串文字
String html = "\n" +
" \n" +
" Hello, world
\n" +
" \n" +
"\n";
// 使用“二维”文本块
String html = """
Hello, world
""";
```
**SQL示例**
```sql
// 使用“一维”字符串文字
String query = "SELECT \"EMP_ID\", \"LAST_NAME\" FROM \"EMPLOYEE_TB\"\n" +
"WHERE \"CITY\" = 'INDIANAPOLIS'\n" +
"ORDER BY \"EMP_ID\", \"LAST_NAME\";\n";
// 使用“二维”文本块
String query = """
SELECT "EMP_ID", "LAST_NAME" FROM "EMPLOYEE_TB"
WHERE "CITY" = 'INDIANAPOLIS'
ORDER BY "EMP_ID", "LAST_NAME";
""";
```
**Polyglot语言示例**
```polyglot
// 使用“一维”字符串文字
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Object obj = engine.eval("function hello() {\n" +
" print('\"Hello, world\"');\n" +
"}\n" +
"\n" +
"hello();\n");
// 使用“二维”文本块
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Object obj = engine.eval("function hello() {\n" +
" print('\"Hello, world\"');\n" +
"}\n" +
"\n" +
"hello();\n");
```
## 编译时处理
文本块是String类型的常量表达式,类似于字符串字面量。然而,与字符串字面值不同,文本块的内容在编译时经历三个步骤的处理:==行终止符的规范化==、==附带白色空间的移除==和==解释转义序列==:
1. 转换内容的行终止符
* 行终止符从CR(\u000D)和CRLF(\u000D\u000A)规范化为`LF(\u000A)`
2. 删除内容周围附带的白色空间(用于匹配Java源代码的缩进)
3. 解释内容中的转义序列,执行解释作为最后一步开发人员可以编写转义序列,如\n,而不会被前面的步骤修改或删除
处理后的内容以`CONSTANT_String_info`形式记录在**类文件的常量池**中,运行时,文本块被计算为String的实例。
## 新增转义序列
为了更精细地控制==换行符==和==空格==的处理,引入了两个新的转义序列:\ 和 \s。
### 换行符 \
```java
// 传统方式
String literal = "Lorem ipsum dolor sit amet, consectetur adipiscing " +
"elit, sed do eiusmod tempor incididunt ut labore " +
"et dolore magna aliqua.";
// 使用 \
String text = """
Lorem ipsum dolor sit amet, consectetur adipiscing \
elit, sed do eiusmod tempor incididunt ut labore \
et dolore magna aliqua.\
""";
```
> Tips: 因为字符和传统字符串不允许嵌入换行符,所以\ 转义序列只适用于文本块
### 单个空格 \s
新的 \s 转义序列简单地转换为单个空格(\u0020)
```java
// 使用 \s 保持固定长度
String colors = """
red \s
green\s
blue \s
""";
```
转义序列直到去除无意义的空格后才被解释,\s 可以作为栅栏,防止尾随空格被去除。
在这个示例中,每行末尾使用 \s 可以确保每行长度恰好为六个字符。
## 文本块连接
文本块的连接是引入的一个方便的特性,使得字符串的拼接变得更加简洁。
在连接时,相邻的文本块将自动合并,无需显式使用加号连接操作符。
```java
// 字符串和文本块连接
String code = "public void print(Object o) {" +
"""
System.out.println(Objects.toString(o));
}
""";
// 相邻的文本块将自动合并,无需显式使用加号连接操作符
String code = """
public void print(Object o) {
"""
"""
System.out.println(Objects.toString(o));
}
""";
```
在上述示例中,两个相邻的文本块会自动连接,形成一个整体的字符串。
这种自动连接的特性让代码更加清晰,减少了冗余的拼接操作。
## 文本块新方法
文本块引入了一些新方法,以便更方便地处理多行字符串:
- `String::stripIndent()`:去除多行字符串的前导空格
- `String::translateEscapes()`:转义多行字符串中的转义字符
- `String::formatted()`:在文本块中使用占位符进行值替换
- `String::lines():`:将多行字符串拆分为行的流,方便逐行处理。
================================================
FILE: src/md/java/features/Java16/jep394-pattern-matching-for-instanceof.md
================================================
---
title: Java 16 新特性:instanceof 模式匹配
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2023-12-28
category: Java Features
tag:
- java
order: 394
---
# Java 16 新特性:instanceof 模式匹配
Java 16 引入了`instanceof`**模式匹配**的增强语法,用于更简便地判断对象是否是某个类的实例并进行相应的**局部类型转换**。
## instanceof 基础用法
```java
if (obj instanceof String) {
String someString = (String) obj; // 强制类型转换
// ...
}
```
这个**强制转换**通常是在 `instanceof` 检查之后 的第一件事,所以为什么不围绕它优化一下语法呢?
## instanceof 增强用法
```java
if (obj instanceof String someString) {
// ...
}
// 这里 someString 超出了作用域
```
1. 若 `instanceof` 检查成功,将自动将变量转换为指定类型
2. 定义的变量实质上是一个**局部变量**,只在if语句的范围内可见
## 常见用法建议
不仅如此!使用模式匹配,我们可以更灵活地应用条件测试。
* 利用已定义的 obj,在不需要额外嵌套的情况下判断字符串是否以“Awesome”开头
```java
// 以前
return (someObject instanceof String) && ((String) someObject).startsWith("Awesome");
// 现在
return someObject instanceof String someString && someString.startsWith("Awesome");
```
* 甚至在 equals 方法中,代码会更加简洁
```java
// 以前
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer) obj).intValue();
}
return false;
}
// 现在
public boolean equals(Object obj) {
return (obj instanceof Integer i) && value == i.intValue();
}
```
多么不同!现在代码简洁而直观。
## 发展脉络
该功能经历了2个预览版本(JDK 14中的JEP 305、JDK 15中的JEP 375),最终定稿于JDK 16中的JEP 394。
================================================
FILE: src/md/java/features/Java16/jep395-records.md
================================================
---
title: Java 16 新特性:record类
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2023-12-29
category: Java Features
tag:
- java
order: 395
---
# Java 16 新特性:record类
Java 16 引入的**记录类**(Records Classes)是一种用于简化不可变数据管理的特殊类。
它通过紧凑的语法提供了对不可变数据的支持,并自动生成常见的方法,
如`equals()`、`hashCode()`和`toString()`等,从而减少了开发者的样板代码。
## 传统Java Bean问题
在处理不可变数据时,传统的Java Bean存在“==繁琐==”和“==冗长==”的问题。
为了管理少量值的不可变数据,开发者需要编写大量低价值、重复且容易出错的代码,
包括`构造函数`、`访问器(getter/setter)`、`equals`、`hashCode`、`toString`等。
例如,用于携带x和y坐标的类通常会演变成以下繁琐形式:
```java
class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
int x() { return x; }
int y() { return y; }
public boolean equals(Object o) {
if (!(o instanceof Point)) return false;
Point other = (Point) o;
return other.x == x && other.y == y;
}
public int hashCode() {
return Objects.hash(x, y);
}
public String toString() {
return String.format("Point[x=%d, y=%d]", x, y);
}
}
```
## 引入record类
**语法如下**:使用 `record`==关键字==,指定==类名称==为 Point,定义==参数列表== x 和 y 作为组件
```java
record Point(int x, int y) { }
```
record申明的类,具备这些特点:
1. 它是一个`final`类
2. 自动实现`equals`、`hashCode`、`toString`函数
3. 成员变量均为`public`属性
所以,对于之前写的Point类,它等价于一个这样的类:
```java
// Record类声明,使用record关键字,名称为Point,带有两个参数x和y
public final class Point {
// 1. 自动生成成员变量(fields)
final int x;
final int y;
// 2. 自动生成构造函数(constructor)
public Point( int x, int y){
this.x = x;
this.y = y;
}
// 3. 自动生成的访问方法
public int x () {
return x;
}
public int y () {
return y;
}
// 4. 自动生成equals和hashCode方法
@Override
public boolean equals (Object obj){
// 实现相等性比较的逻辑
}
@Override
public int hashCode () {
// 生成哈希码的逻辑
}
// 5. 自动生成toString方法
@Override
public String toString () {
return "Point{" +
"x=" + x +
", y=" + y +
'}';
}
}
```
通过使用record类,你可以更专注于业务逻辑而不是样板代码,提高了代码的可读性和可维护性。
## 显示声明紧凑构造函数
未显式声明构造函数时,系统会自动生成包含所有成员变量的隐式构造函数。
当显式声明紧凑构造函数可以==省略形式参数列表==、编译后在构造函数的末尾==自动分配==给相应的形式参数(this.x = x;)。
* **验证参数**的紧凑构造函数
```java
record Book(String title, String author, int pageCount) {
Book {
if (pageCount <= 0) {
throw new IllegalArgumentException("页数必须大于零.");
}
}
}
```
* **规范**的紧凑构造函数
```java
// 记录类 Rational 表示有理数,包含分子(num)和分母(denom)两个成员变量
record Rational(int num, int denom) {
Rational {
int gcd = gcd(num, denom); // 计算最大公约数
num /= gcd; // 将分子除以最大公约数
denom /= gcd; // 将分母除以最大公约数
}
}
```
这个声明等同于传统的构造函数形式:
```java
record Rational(int num, int denom) {
Rational(int num, int demon) {
// 逻辑代码
int gcd = gcd(num, denom);
num /= gcd;
denom /= gcd;
// 赋值代码
this.num = num;
this.denom = denom;
}
}
```
* 记录类**语义一致性**
例如,考虑以下方式声明的记录类R:
```java
record R(T1 c1, ..., Tn cn){ }
```
如果通过以下方式复制R的实例r1:
```java
R r2 = new R(r1.c1(), r1.c2(), ..., r1.cn());
```
则假设 r1 不是空引用,表达式 r1.equals(r2) 总是为 true。
这是因为记录类的隐式声明的 equals 方法保证了相同字段值的两个记录实例相等。
> Tips: 程序员需要谨慎确保显式声明的方法维持语义一致性
* 避免不良风格的代码
```java
record SmallPoint(int x, int y) {
public int x() { return this.x < 100 ? this.x : 100; }
public int y() { return this.y < 100 ? this.y : 100; }
}
```
这是一个不良风格的代码,违反了语义一致性的原则,访问器`x()`,`y()`方法调整类实例的状态;
**改进建议**, 如果需要限制坐标值的范围,应该在构造函数或其他明确的位置处理。
```java
record SmallPoint(int x, int y) {
// 紧凑构造函数中限制坐标值的范围
public SmallPoint {
this.x = Math.min(x, 100);
this.y = Math.min(y, 100);
}
}
```
## 发展脉络
`record` 类最初在JDK 14预览版(JEP 359)提出,随后在JDK 15预览版(JEP 384)再次推出。
最终,它在JDK 16中(JEP 395)正式发布,成为Java语言的一项重要特性。
================================================
FILE: src/md/java/features/Java17/jep406-pattern-matching-for-switch-preview.md
================================================
---
title: Java 17 新特性:switch模式匹配(Preview)
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2023-12-30
category: Java Features
tag:
- java
order: 406
---
# Java 17 新特性:switch模式匹配(Preview)
当case标签可以有模式时,有如下四个主要的设计问题,我们一一来看:
1. 增强类型检查
2. switch表达式和语句的完整性
3. 模式变量声明的作用域
4. 处理null
## 模式匹配设计
### 增强类型检查
通过扩展`switch`模式匹配的`case`标签,现在支持除了**原始数据类型**(`char`、`byte`、`short` 或 `int`)之外,
相应的**包装类**(`Character`、`Byte`、`Short` 或 `Integer`)、`String` 以及`Enum`类型等**任何引用类型**。
```java
record Point(int i, int j) {}
enum Color { RED, GREEN, BLUE; }
static void typeTester(Object o) {
switch (o) {
case null -> System.out.println("null");
case String s -> System.out.println("String");
case Color c -> System.out.println("Enum,颜色具有 " + Color.values().length + " 个值");
case Point p -> System.out.println("Record Class: " + p.toString());
case int[] ia -> System.out.println("Array,长度为" + ia.length);
default -> System.out.println("其他情况");
}
}
```
> **注意⚠️:要避免模式标签支配**(编译异常)
>
> 如果一个模式标签在switch块中被先前的模式标签支配, 或者存在多个全匹配的标签(default 和 total类型模式), 则会产生编译时错误。
* 例1: 模式 `case CharSequence cs` 支配 `case String s` ,因为 String 是 CharSequence 的子类
* 例2: 总模式的情况,如 `case p` 支配 `case null` 模式,因为总模式匹配所有值,包括null
* 例3: 模式 `case p` 支配 `case p && e` ,因为满足第一个模式的值也满足第二个模式
* 例4: 模式 `case String s` 支配了带条件的模式 `case String s && s.length() > 0`
```java
switch(o) {
case CharSequence cs ->
System.out.println("一个长度为" + cs.length() + "的序列");
case String s -> // 编译错误 - 模式被前一个模式支配
System.out.println("一个字符串:" + s);
default -> {
break;
}
}
```
### switch表达式和语句的完整性
通常情况下,通过添加`default`标签,可以确保`switch`块的完整性。
```java
static void printType(Object o) {
switch (o) {
case String s -> System.out.println("String");
case Integer i -> System.out.println("Integer");
default -> System.out.println("Other");
}
}
```
如果switch表达式的类型是**密封类**([JEP 409](https://openjdk.org/jeps/409)),
则类型覆盖检查会考虑密封类的permits子句,以确保switch块的完整性。
以下是一个密封接口Animal的示例,包括Dog和Cat两个允许的子类:
```java
sealed interface Animal permits Dog, Cat {}
class Dog implements Animal {}
class Cat implements Animal {}
static String getSound(Animal animal) {
return switch (animal) {
case Dog d -> "Woof!";
case Cat c -> "Meow!";
// no default needed!
};
}
```
在这种情况下,由于编译器知道只有Dog和Cat是可能的类型,所以可以不需要`default`标签。
同样,对于枚举类,每个常量都有一个子句,也不需要default标签。
### 模式变量声明的作用域
`instanceof`([JEP 394](https://openjdk.org/jeps/394))进行**模式匹配**,
**模式变量**的作用域限定在`匹配的条件表达式`和相应的`then`块中。 如果匹配失败,模式变量在`else`块中不可见。
```java
static void test(Object o) {
if ((o instanceof String s) && s.length() > 3) {
System.out.println(s);
} else {
System.out.println("Not a string");
}
}
```
`switch`语句的`case`标签进行**模式匹配**,有以下两条规则:
1. `->`形式:作用域包括箭头右侧的表达式、块或 throw 语句
```java
static void test(Object o) {
switch (o) {
case Character c -> {
if (c.charValue() == 7) {
System.out.println("Ding!");
}
System.out.println("Character");
}
case Integer i ->
throw new IllegalStateException("Invalid Integer argument of value " + i.intValue());
default -> {
break;
}
}
}
```
2. `:`形式,则其作用域包括语句组的块语句,直到遇到下一个`switch`标签或其他控制流语句
```java
static void test(Object o) {
switch (o) {
case Character c:
if (c.charValue() == 7) {
System.out.print("Ding ");
}
if (c.charValue() == 9) {
System.out.print("Tab ");
}
System.out.println("character");
default:
System.out.println();
}
}
```
### 处理null
引入新的`null`标签,用于明确处理选择表达式为`null`的情况
```java
// test(null) 不再抛出NullPointerException,而是打印 "null!"
static void test(Object o) {
switch (o) {
case null -> System.out.println("null!");
case String s -> System.out.println("String");
default -> System.out.println("Something else");
}
}
```
由空标签产生的新标签形式, JDK 16中,`switch`块支持两种风格,
1. `:` 形式,允许`fallthrough`,多个标签通常写为`case l1: case l2:`
2. `->`形式,不允许`fallthrough`,多个标签写为`case l1, l2->`
```java
// 处理 null 和 String 标签,使用 : 形式
switch (o) {
case null: case String s:
System.out.println("String, including null");
break;
// 更多的 cases...
}
// 结合 null case 和 default 标签,使用 -> 形式
switch (o) {
// 更多的 cases...
case null, default ->
System.out.println("The rest (including null)");
}
```
## 保护模式和括号模式
为了增强代码的可读性并避免歧义,引入了两种新的模式匹配技术:
* 保护模式 (guarded patterns),允许在模式匹配成功后添加一个布尔表达式
* 括号模式 (parenthesized patterns),将模式放在括号中,避免歧义,控制顺序
在成功匹配模式后,我们经常会进一步测试匹配结果。这会导致代码变得繁琐,例如:
```java
static void test(Object o) {
switch (o) {
case String s:
if (s.length() == 1) { ... }
else { ... }
break;
...
}
}
```
使用**保护模式**,写成`p && e`改进上面的代码,使其更加简洁
```java
static void test(Object o) {
switch (o) {
case String s && s.length() == 1 -> ...
case String s -> ...
}
}
```
> JDK 17中还加入了**括号模式**,以避免解析歧义。支持括号内写入`(p)` 其中p是一个模式。在JDK 21中,括号模式被移除。
## 启用预览功能
Preview阶段的功能并不是默认开启的,需要在编译和运行时启用。
```shell
java --enable-preview --source 17 PatternMatching.java
```
================================================
FILE: src/md/java/features/Java17/jep409-sealed-classes.md
================================================
---
title: Java 17 新特性:sealed类
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2023-12-31
category: Java Features
tag:
- java
order: 409
---
# Java 17 新特性:sealed类
Java 17 中引入了**密封类**(Sealed Classes),它是一种限制的类和接口,
可以控制哪些类继承或实现它,保证在编译时就能够确定类的继承关系,提高代码的可读性和可维护性。
## 密封类语法
密封类的声明使用关键字 `sealed`,并通过 `permits` 关键字声明允许继承或实现的类。
```java
// 密封类
public abstract sealed class 类名 extends 父类名 permits 子类名1, 子类名2, ... {
// 类的成员
}
// 密封接口
public sealed interface 接口名 extends 父接口名 permits 子类名1, 子类名2, ... {
// 接口的成员
}
```
密封类对其允许的子类施加了三个约束:
1. 密封类及其允许的子类必须属于同一个模块或同一包(对于未命名模块)
2. 每个允许的子类必须直接扩展密封类
3. 每个允许的子类必须使用修饰符描述其继承关系:
* `final`:表示该类不能被继承(记录类隐式声明为 `final`)
* `sealed`:表示该类可以被继承,但只能被允许的子类继承
* `non-sealed`:表示该类可以被继承,且可以被任意类继承
## 历史限制继承手段
对于继承能力的限制,Java 语言已经提供了以下几种手段:
1. `final`修饰类,这样类就无法被继承了
2. 构造函数声明为`private`或`package-private`,则只能在同一类或同一包中创建该类的子类
## 发展脉络
该功能经历了2个预览版本(JDK 15中的JEP 360、JDK 16中的JEP 397),最终定稿于JDK 17中的JEP 409。
================================================
FILE: src/md/java/features/Java18/jep400-utf8-by-default.md
================================================
---
title: Java 18 新特性:指定UTF-8为默认字符集
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-01-01
category: Java Features
tag:
- java
order: 400
---
# Java 18 新特性:指定UTF-8为默认字符集
在Java 18中,将`UTF-8`指定为标准Java API 的默认字符集,
以提高Java程序在不同实现、操作系统、区域设置和配置下的一致性。
## 目标
* 使Java程序在依赖默认字符集的代码上更加可预测和**可移植**
* 明确标准Java API在何处使用默认字符集
* 在标准Java API中统一使用UTF-8,除了控制台I/O
尽管这项工作可能会发现新的便利方法可能会使现有的API更易于使用,但这一更改并不是要弃用或删除依赖默认字符集的标准Java API。
## 动机
默认情况下,Java API 会根据**运行时环境**(操作系统、用户的区域设置等)选择默认字符集。
为了提高 Java API 的一致性并降低潜在的兼容性问题,我们建议将所有 Java API 统一使用 `UTF-8` 作为默认字符集。
尽管这一变更可能对迁移到 JDK 18 的程序产生兼容性影响,但我们提供了一个 `COMPAT` 选项,允许恢复到之前的行为,即默认字符集取决于环境。
## 描述
### 兼容性危害示例
在MacOS上以`UTF-8`编码的日语文本文件在Windows上以美英或日语区域设置读取时被损坏
```java
java.io.FileReader(“hello.txt”) -> “こんにちは” (macOS)
java.io.FileReader(“hello.txt”) -> “ã?“ã‚“ã?«ã?¡ã? ” (Windows (en-US))
java.io.FileReader(“hello.txt”) -> “縺ォ縺。縺ッ” (Windows (ja-JP)
```
### 查询默认字符集
通过方法 `java.nio.charset.Charset.defaultCharset()` 可以获取默认字符集。
另外,使用以下命令可以快速查看当前 JDK 的默认字符集:
```java
java -XshowSettings:properties -version 2>&1 | grep file.encoding
```
如果想在所有 Java 版本上获取从环境中确定的字符集,可以使用以下代码:
```java
// 获取native.encoding系统属性(在Java 18及更高版本上赋值)
String encoding = System.getProperty("native.encoding");
// 使用三元运算符选择字符集,如果encoding不为null,则使用指定字符集,否则使用默认字符集
Charset cs = (encoding != null) ? Charset.forName(encoding) : Charset.defaultCharset();
// 使用指定字符集创建 FileReader 对象,打开名为 "file.txt" 的文件
var reader = new FileReader("file.txt", cs);
```
### 兼容使用默认字符集API(迁移)
多个标准 Java API 使用默认字符集,包括:
* 在 java.io 包中,InputStreamReader、FileReader、OutputStreamWriter、FileWriter 和 PrintStream
提供了构造函数,用于创建使用默认字符集进行编码或解码的读取器、写入器和打印流
* 在 java.util 包中,Formatter 和 Scanner 提供了构造函数,使用默认字符集进行操作
* 在 java.net 包中,URLEncoder 和 URLDecoder 提供了使用默认字符集的已弃用方法
我们将更新所有使用 Charset.defaultCharset() 进行交叉引用的标准 Java API 的规范。
这些 API 包括上述提到的 API,但不包括 System.out 和 System.err,它们的字符集将由 Console.charset() 指定。
### file.encoding 和 native.encoding 系统属性
`file.encoding` 是 Java 虚拟机的系统属性,用于指定默认的字符编码
```shell
java -Dfile.encoding=COMPAT # COMPAT 模式, 默认字符集取决于环境
java -Dfile.encoding=UTF-8 # UTF-8 模式, 默认字符集为UTF-8
```
`native.encoding` 在Java 17 中引入,该属性提供了底层主机环境的字符编码名称
Java内部使用了三个字符集相关的系统属性,它们目前未指定且不受支持。这里简要记录一下:
1. `sun.stdout.encoding`
2. `sun.stderr.encoding`
3. `sun.jnu.encoding`:
> Tips:对于JDK(8-17):强烈建议开发人员使用`java -Dfile.encoding=UTF-8`指定默认字符集为UTF-8启动程序
### 源文件编码
Java语言允许源代码使用`UTF-16`编码方式表达`Unicode`字符,并且这不受默认字符集UTF-8的影响。
但是,`javac`编译器会受到影响,因为它需要将源代码转换为平台默认的字符集,除非通过`-encoding`选项进行配置
如果源文件以非UTF-8编码保存并在较早的JDK上进行编译,然后在JDK 18或更高版本上重新编译,可能会导致问题。
例如,如果非UTF-8源文件中的字符串文字包含非ASCII字符,则在JDK 18或更高版本中,除非使用`-encoding`选项,否则这些文字可能会被`javac`错误解释。
在使用UTF-8作为默认字符集的JDK上编译之前,强烈建议开发人员通过在当前JDK(8-17)上使用javac -encoding UTF-8 ... 进行编译来检查字符集问题。
另外,喜欢以非UTF-8编码保存源文件的开发人员可以通过将JDK 17及更高版本上的`-encoding`选项设置为`native.encoding`系统属性的值,防止javac假定UTF-8。
### 旧版默认字符集(US-ASCII)
在JDK 17及更早版本中,名称`default`会被识别为`US-ASCII`字符集的别名。
在JDK 18中,默认字符集`UTF-8`,保留`default`作为`US-ASCII`的别名将会非常混乱,于是重新定义`default`不再是`US-ASCII`的别名。
Java程序使用枚举常量StandardCharsets.US_ASCII来明确其开发人员意图,而不是向Charset.forName(...)传递字符串。
因此,在JDK 18中,`Charset.forName("default")`将抛出 UnsupportedCharsetException。
这将为开发人员提供检测到这种惯用法并迁移到US-ASCII或Charset.defaultCharset()结果的机会。
================================================
FILE: src/md/java/features/Java18/jep408-simple-web-server.md
================================================
---
title: Java 18 新特性:简单Web服务器
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-01-02
category: Java Features
tag:
- java
order: 408
---
# Java 18 新特性:简单Web服务器
Java 18 引入了**简单Web服务**器(Simple Web Server),一个专为教育或非正式任务设计的最小HTTP服务器,为单个目录层次结构提供服务。
基于JDK中的`com.sun.net.httpserver`包实现,旨在简化服务器的创建和请求处理过程。
主要特点:
* 不能替代成熟的商业服务器,如`Jetty`、`Nginx` 和 `Apache Tomcat`等
* 不提供身份验证、访问控制或加密等安全功能
* 仅支持HTTP/1.1,不支持HTTPS
* 仅支持GET、HEAD请求,否则返回 501 Not Implemented 或 405 Not Allowed
## 命令行工具
为了开始使用简单Web服务器,您需要准备一个`index.html`文件,并执行以下步骤:
1. 打开终端。
2. 输入命令:`jwebserver`。
```shell
$ jwebserver
```
默认情况下,服务器将绑定到本地回环地址,并在端口8000上提供服务。看到类似以下的输出:
```shell
Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving /cwd and its subdirectories on 127.0.0.1 port 8000
URL: http://127.0.0.1:8000/
```
尝试访问一下 `http://127.0.0.1:8000/` ,就可以获得之前准备的HTML内容了。
### 支持的命令行选项
```shell
选项:
-h 或 -? 或 --help
打印帮助信息并退出.
-b addr 或 --bind-address addr
指定绑定的地址。默认:127.0.0.1或::1(回环地址)。要使用所有接口,请使用 -b 0.0.0.0 或 -b ::.
-d dir 或 --directory dir
指定要提供服务的目录。默认:当前目录.
-o level 或 --output level
指定输出格式。none | info | verbose。默认:info.
-p port 或 --port port
指定要监听的端口。默认:8000.
-version 或 --version
打印版本信息并退出。
要停止服务器,请按 Ctrl + C.
```
## API编程方式
尽管命令行工具提供了便利,但为了更灵活地定制处理程序的行为,与现有代码集成,提高代码的可读性和可维护性,我们引入了新的API。
> 新的API中引入了三个新的类是`SimpleFileServer`、`HttpHandlers`和`Request`,
> 每个类都构建在`com.sun.net.httpserver`包中的现有类和接口上:`HttpServer`、`HttpHandler`、`Filter`和`HttpExchange`。
### 简单文件服务器(SimpleFileServer)
`SimpleFileServer`支持创建文件服务器、文件服务器处理程序和输出过滤器。
```java
package com.sun.net.httpserver;
public final class SimpleFileServer {
// 创建文件服务器实例
public static 1 createFileServer(
InetSocketAddress addr, Path rootDirectory, OutputLevel outputLevel) {...}
// 创建文件服务器处理程序
public static HttpHandler createFileHandler(
Path rootDirectory) {...}
// 创建输出过滤器
public static Filter createOutputFilter(
OutputStream out, OutputLevel outputLevel) {...}
...
}
```
有了这个类,在`jshell`中只需几行代码,就可以启动一个最小但定制的**文件服务器**:
```java
jshell> var server = SimpleFileServer.createFileServer(new InetSocketAddress(8080),
...> Path.of("/some/path"), SimpleFileServer.OutputLevel.INFO);
jshell> server.start()
```
相当于命令行模式的:
```shell
jwebserver -p 8080 -d /some/path -o info
```
### 自定义处理程序和过滤器
将自定义的**文件服务器处理程序**添加到现有服务器:
```java
jshell> var handler = SimpleFileServer.createFileHandler(Path.of("/some/path"));
jshell> var server = HttpServer.create(new InetSocketAddress(8080),
...> 10, "/store/", new SomePutHandler());
jshell> server.createContext("/browse/", handler);
jshell> server.start();
```
将自定义的**输出过滤器**在创建过程中添加到服务器:
```java
jshell> var filter = SimpleFileServer.createOutputFilter(System.out,
...> OutputLevel.INFO);
jshell> var server = HttpServer.create(new InetSocketAddress(8080),
...> 10, "/store/", new SomePutHandler(), filter);
jshell> server.start();
```
两个例子是由`HttpServer`和`HttpsServer`类中的新重载`create`方法启用的:
```java
public static HttpServer create(InetSocketAddress addr,
int backlog,
String root,
HttpHandler handler,
Filter... filters) throws IOException {...}
```
### 增强的请求处理(HttpHandlers)
简单Web服务器的核心功能是**处理程序**。为了与现有代码兼容,我们引入了`HttpHandlers`类,
提供两个静态方法用于==创建==和==自定义处理程序==,还有`Filter`类中的新方法用于适配请求:
```java
package com.sun.net.httpserver;
public final class HttpHandlers {
// handleOrElse方法补充条件处理程序
public static HttpHandler handleOrElse(Predicate handlerTest,
HttpHandler handler,
HttpHandler fallbackHandler) {...}
// of方法创建具有预设响应状态的处理程序
public static HttpHandler of(int statusCode, Headers headers, String body) {...}
{...}
}
```
```java
public abstract class Filter {
// adaptRequest方法获取预处理过滤器,用于在处理请求之前检查和调整请求的某些属性
public static Filter adaptRequest(
String description, UnaryOperator requestOperator) {...}
{...}
}
```
这些方法的用例包括基于请求方法委托交换,创建总是返回特定响应的“canned response”处理程序,或向所有传入请求添加标头。
### 请求(Request)
现有API中,使用HttpExchange类来表达HTTP==请求-响应对==,描述了请求-响应交换的完整可变状态。
然而,并非所有这状态对于处理程序的定制和适配都是必要的。
因此,我们引入了更简单的Request接口,提供==请求==的不可变状态的有限视图。
```java
public interface Request {
URI getRequestURI(); // 获取请求的URI
String getRequestMethod(); // 获取请求的方法
Headers getRequestHeaders(); // 获取请求的标头
// 用于修改请求头部信息
default Request with(String headerName, List headerValues)
{...}
}
```
这使得可以直接定制现有的处理程序,例如:
```java
// 创建一个处理程序,根据请求方法选择对应的处理器,如果请求方法为 PUT,则使用 SomePutHandler,否则使用 SomeHandler
var h = HttpHandlers.handleOrElse(r ->
r.getRequestMethod().equals("PUT"), new SomePutHandler(), new SomeHandler());
// 创建一个过滤器,用于修改请求头部信息,在请求中添加名为 "Foo" 的头部,值为 "Bar"
var f = Filter.adaptRequest("Add Foo header", r -> r.with("Foo", List.of("Bar")));
// 创建一个 HTTP 服务器,并指定端口为 8080,最大连接数为 10,根路径为 "/",处理程序为 h,过滤器为 f
var s = HttpServer.create(new InetSocketAddress(8080), 10, "/", h, f);
// 启动服务器
s.start();
```
## 替代品
**命令行工具**的替代方案:
* 最初,使用 `java -m jdk.httpserver` 命令运行 Simple Web Server,没有专门的命令行工具
* 为了提高便利性和可访问性,我们引入了一个专门的工具 `jwebserver`
* 实际上 `jwebserver` 在后台使用了 `java -m ...` 命令
**API编程方式**替代方案:
* 新的 `DelegatingHandler` 类:捆绑定制方法在一个单独的类中,但由于引入了新类型并未添加更多功能,我们放弃了这个选项。
* 将 `HttpHandler` 作为服务:将 `HttpHandler` 转换为服务,并提供内部文件服务器处理程序实现。然而,这种方法对于我们要提供的功能来说过于复杂。
* 使用**过滤器**而不是 `HttpHandler`:仅使用过滤器来处理请求,但这样做不符合直觉,并且过滤器的方法会更难找到。
================================================
FILE: src/md/java/features/Java18/jep413-code-snippets-in-api-documentation.md
================================================
---
title: Java 18 新特性:新增@snipppet标签
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-01-03
category: Java Features
tag:
- java
order: 413
---
# Java 18 新特性:新增@snipppet标签
Java 18 引入了`@snippet`标签,用于在API文档中嵌入代码片段,以便更好地展示API的使用方法。
主要特点:
* 有效性检查,代码包含语法错误时,会出现错误提示
* 启用现代样式,例如语法高亮显示,以及名称与声明的自动链接
* 为创建和编辑代码段提供更好的IDE支持
## 存在的@code标签
用于单独的小段代码, 当代码片段复杂时, 使用复合模式的文档注释,如下所示:
```java
* {@code
* 源代码行1
* ...
* 源代码行n
* }
```
## 引入@snippet标签
解决了`@code`标签的不足,允许在API文档中直接嵌入代码片段,以便更好地展示API的使用方法。
```java
/**
* 以下代码显示了如何使用 {@code Optional.isPresent}:
* {@snippet :
* if (v.isPresent()) {
* System.out.println("v: " + v.get());
* }
* }
*/
```
作为外部片段导入
```java
/**
* 以下代码显示了如何使用 {@code Optional.isPresent}:
* {@snippet file="ShowOptional.java" region="example"}
*/
```
其中`ShowOptional.java`是一个包含以下内容的文件:
```java
public class ShowOptional {
void show(Optional v) {
// @start region="example"
if (v.isPresent()) {
System.out.println("v: " + v.get());
}
// @end
}
}
```
================================================
FILE: src/md/java/features/Java19/java19-new-features-summary.md
================================================
---
title: Java19 新特性总结
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-01-04
category: Java Features
tag:
- java
---
# Java 19 新特性总结
该版本推出的均为孵化与预览功能,所以这里不做单独的详细解读,大部分内容均放在Java 21中介绍。
* 422: Linux/RISC-V Port:RISC-V是一个基于精简指令集(RISC)原则的开源指令集架构(ISA),这个JEP的主旨则是移植JDK到RISC-V上。
以下预览特性在Java 21中正式定稿:
* [405: Record Patterns (Preview):终稿见 440: Record Patterns](/java-features/Java21/jep440-record-partterns)
* [425: Virtual Threads (Preview):终稿见 444: Virtual Threads](/java-features/Java21/jep444-virtual-threads)
* [427: Pattern Matching for switch (Third Preview):终稿见 441: Pattern Matching for switch](/java-features/Java21/jep441-pattern-matching-for-switch)
以下内容在Java 21中继续迭代:
* 424: Foreign Function & Memory API (Preview)
* 426: Vector API (Fourth Incubator)
* 428: Structured Concurrency (Incubator)
================================================
FILE: src/md/java/features/Java20/java20-new-features-summary.md
================================================
---
title: Java20 新特性总结
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-01-05
category: Java Features
tag:
- java
---
# Java 20 新特性总结
该版本推出的均为孵化与预览功能,所以这里不做单独的详细解读,大部分内容均放在Java 21中介绍。
以下内容在Java 21中正式定稿,可根据链接查看终稿内容:
* [432: Record Patterns (Second Preview):终稿见440: Record Patterns](/java-features/Java21/jep440-record-partterns)
* [433: Pattern Matching for switch (Fourth Preview):终稿见441: Pattern Matching for switch](/java-features/Java21/jep441-pattern-matching-for-switch)
* [436: Virtual Threads (Second Preview):终稿见444: Virtual Threads](/java-features/Java21/jep444-virtual-threads)
以下内容在Java 21中继续迭代:
* 429: Scoped Values (Incubator)
* 434: Foreign Function & Memory API (Second Preview)
* 437: Structured Concurrency (Second Incubator)
* 438: Vector API (Fifth Incubator)
================================================
FILE: src/md/java/features/Java21/jep430-string-templates.md
================================================
---
title: Java 21 新特性:字符串模版(Preview)
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-01-06
category: Java Features
tag:
- java
order: 430
---
# Java 21 新特性:String Templates(字符串模版)
Java 21 中引入了**字符串模版**(String Templates),它是一种新的字符串字面量,用于更简洁地构建字符串。
## 字符串组合的机制
在之前,Java 提供了几种字符串组合的机制,但不幸的是,它们都存在一些缺点
1. 使用 `+` 操作符, 代码难以阅读
```java
String s = x + " plus " + y + " equals " + (x + y);
```
2. 使用 `StringBuilder` 和 `StringBuffer`,代码冗长
```java
String s = new StringBuilder()
.append(x)
.append(“plus“)
.append(y)
.append(“equals“)
.append(x + y)
.println();
```
3. 使用 `String::format` 和 `String::formatted`,容易出现参数数量和类型不匹配的问题
```java
String s = String.format("%2$d plus %1$d equals %3$d", x, y, x + y);
String t = "%2$d plus %1$d equals %3$d".formatted(x, y, x + y);
```
4. 使用 `java.text.MessageFormat` 格式化消息,语法复杂对一些人来说可能不太熟悉
```java
MessageFormat mf = new MessageFormat("{0} plus {1} equals {2}");
String s = mf.format(x, y, x + y);
```
下面,我们将学习Java 21中的字符串模版,以及它的使用方法。
## 模版表达式(插值)
在Java 21中处理字符串的新方法称为:`Template Expressions`,即:**模版表达式**。
* 优点:模版表达式可以执行**字符串插值**,插值不仅比串联更方便,而且在阅读代码时也更清晰
* 缺点:但插值是危险的,尤其是对于SQL语句,因为它可能导致注入攻击
```java
String name = "Joan";
String info = STR."My name is \{name}";
assert info.equals("My name is Joan"); // true
```
上述代码中的第2行就是一个模版表达式,其中主要包含三个部分:
1. 模板处理器`STR`;
2. 一个`.`字符,类似于方法调用
3. 包含嵌入表达式(`\{name}`)的模版
运行时,计算模板表达式,模板处理器将模板中的文本与嵌入表达式的值组合在一起,以产生结果。
## STR模版处理器
> STR模板处理器用于将模板中的每个==嵌入表达式==替换成==表达式的(字符串)值==来执行字符串插值
* STR是一个`public static final`字段,它会自动导入到每个Java源文件中
使用STR模板处理器的模板表达式示例。符号 `|` 后显示前一条语句的值,类似于`jshell`。
```java
// 嵌入式表达式可以是字符串
String firstName = "Bill";
String lastName = "Duck";
String fullName = STR."\{firstName} \{lastName}";
| "Bill Duck"
String sortName = STR."\{lastName}, \{firstName}";
| "Duck, Bill"
// 嵌入式表达式可以执行算术运算
int x = 10, y = 20;
String s = STR."\{x} + \{y} = \{x + y}";
| "10 + 20 = 30"
// 嵌入式表达式可以调用方法和访问字段
String s = STR."You have a \{getOfferType()} waiting for you!";
| "You have a gift waiting for you!"
String t = STR."Access at \{req.date} \{req.time} from \{req.ipAddress}";
| "Access at 2022-03-25 15:34 from 8.8.8.8"
```
* 为了帮助重构,嵌入式表达式中可以使用双引号字符,而无需将它们转义为`"`
```java
String filePath = "tmp.dat";
File file = new File(filePath);
String old = "The file " + filePath + " " + (file.exists() ? "does" : "does not") + " exist";
String msg = STR."The file \{filePath} \{file.exists() ? "does" : "does not"} exist";
| "The file tmp.dat does exist" 或 "The file tmp.dat does not exist"
```
* 为了提高可读性,在源文件中,嵌入式表达式可以跨越多行而不会引入新的换行符
```java
String time = STR."The time is \{
// java.time.format包非常有用
DateTimeFormatter
.ofPattern("HH:mm:ss")
.format(LocalTime.now())
} right now";
// "The time is 12:34:56 right now"
```
* 字符串模板表达式中嵌入表达式的数量没有限制
```java
// 嵌入式表达式可以是后缀递增表达式
int index = 0;
String data = STR."\{index++}, \{index++}, \{index++}, \{index++}";
// "0, 1, 2, 3"
```
* 任何Java表达式都可以用作嵌入式表达式,甚至是模板表达式。例如:
```java
// 嵌入式表达式是(嵌套的)模板表达式
String[] fruit = { "apples", "oranges", "peaches" };
String s = STR."\{fruit[0]}, \{STR."\{fruit[1]}, \{fruit[2]}"}";
// "apples, oranges, peaches"
```
* 在这里,模板表达式 `STR."\{fruit[1]}, \{fruit[2]}"` 嵌入到另一个模板表达式的模板中。
由于存在大量的 `"` `,` `\` 和 `{ }` 字符,这段代码很难阅读,因此最好将其格式化为:
```java
String s = STR."\{fruit[0]}, \{
STR."\{fruit[1]}, \{fruit[2]}"
}";
```
## 多行模板表达式
模板表达式的模板可以跨越多行源代码,类似于Java 15中的[文本块](/java-features/Java15/jep378-text-blocks)的语法。
开发者可以用它来方便的组织`html`、`json`、`xml`等字符串内容,比如下面这样:
```java
// 多行模板表达式示例:HTML文档
String title = "My Web Page";
String text = "Hello, world";
String html = STR."""
\{title}
\{text}
""";
| 输出结果:
| """
|
|
| My Web Page
|
|
| Hello, world
|
|
| """
// 多行模板表达式示例:JSON文档
String name = "Joan Smith";
String phone = "555-123-4567";
String address = "1 Maple Drive, Anytown";
String json = STR."""
{
"name": "\{name}",
"phone": "\{phone}",
"address": "\{address}"
}
""";
| 输出结果:
| """
| {
| "name": "Joan Smith",
| "phone": "555-123-4567",
| "address": "1 Maple Drive, Anytown"
| }
| """
record Rectangle(String name, double width, double height) {
double area() {
return width * height;
}
}
Rectangle[] zone = new Rectangle[] {
new Rectangle("Alfa", 17.8, 31.4),
new Rectangle("Bravo", 9.6, 12.4),
new Rectangle("Charlie", 7.1, 11.23),
};
// 多行模板表达式示例:表格
String table = STR."""
Description Width Height Area
\{zone[0].name} \{zone[0].width} \{zone[0].height} \{zone[0].area()}
\{zone[1].name} \{zone[1].width} \{zone[1].height} \{zone[1].area()}
\{zone[2].name} \{zone[2].width} \{zone[2].height} \{zone[2].area()}
Total \{zone[0].area() + zone[1].area() + zone[2].area()}
""";
| 输出结果:
| """
| Description Width Height Area
| Alfa 17.8 31.4 558.92
| Bravo 9.6 12.4 119.03999999999999
| Charlie 7.1 11.23 79.733
| Total 757.693
| """
```
## FMT模板处理器
除了STR模版处理器之外,Java中还提供了另外一个模版处理器:FMT。
FMT与STR相似之处在于它执行插值,但还提供了==格式化处理==能力。
* 格式说明符与`java.util.Formatter`中定义的格式说明符相同
```java
record Rectangle(String name, double width, double height) {
double area() {
return width * height;
}
}
Rectangle[] zone = new Rectangle[] {
new Rectangle("Alfa", 17.8, 31.4),
new Rectangle("Bravo", 9.6, 12.4),
new Rectangle("Charlie", 7.1, 11.23),
};
// 多行模板表达式示例:表格
String table = FMT."""
Description Width Height Area
%-12s\{zone[0].name} %7.2f\{zone[0].width} %7.2f\{zone[0].height} %7.2f\{zone[0].area()}
%-12s\{zone[1].name} %7.2f\{zone[1].width} %7.2f\{zone[1].height} %7.2f\{zone[1].area()}
%-12s\{zone[2].name} %7.2f\{zone[2].width} %7.2f\{zone[2].height} %7.2f\{zone[2].area()}
\{" ".repeat(28)} Total %7.2f\{zone[0].area() + zone[1].area() + zone[2].area()}
""";
| 输出结果:
| """
| Description Width Height Area
| Alfa 17.80 31.40 558.92
| Bravo 9.60 12.40 119.04
| Charlie 7.10 11.23 79.73
| Total 757.69
| """
```
================================================
FILE: src/md/java/features/Java21/jep431-sequenced-collections.md
================================================
---
title: Java 21 新特性:有序集合
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-01-07
category: Java Features
tag:
- java
order: 431
---
# Java 21 新特性:有序集合(Sequenced Collections)
在JDK 21中,**有序集合**(Sequenced Collections)引入了新的接口和方法来简化集合处理。
> 此增强功能旨在解决访问Java中各种集合类型的第一个和最后一个元素需要非统一且麻烦处理场景
`Sequenced Collections` 引入如下 3 个新接口,用于处理顺序`List`、`Set`和`Map`,
并将它们整合到现有的集合类型中。这些新接口中的方法都具有默认实现。
1. SequencedCollection
2. SequencedSet
3. SequencedMap
## SequencedCollection
提供了在集合两端添加、检索和移除元素的方法,沿着`reversed()`方法提供了该集合的逆序视图。
```java
interface SequencedCollection extends Collection {
// 新方法:返回反转后的序列化集合
SequencedCollection reversed();
// 以下方法是从Deque提升的,支持在两端添加、获取和删除元素
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}
```
* 新的`reversed()`方法提供了原始集合的反向视图,对原始集合的任何修改都可以在视图中看到
* 如果允许,对视图的修改将写入原始集合
* 逆序视图使得不同的序列类型可以在两个方向上处理元素
* 如:增强for循环、显式iterator()循环、forEach()、stream()、parallelStream() 和 toArray()
例如,从`LinkedHashSet`获得逆序流以前很难,现在很简单
```java
linkedHashSet.stream().sorted(Comparator.reverseOrder()) // 获取逆序流非常困难
linkedHashSet.reversed().stream() // 现在:直接使用 reversed() 方法获取逆序流
```
> `reversed()` 方法本质上是 `NavigableSet::descendingSet`的重命名,并升级为 SequencedCollection
## SequencedSet
sequenced set 是一个不包含重复元素的 SequencedCollection,区别是`SequencedSet.reversed()`的返回类型是SequencedSet。
```java
interface SequencedSet extends Set, SequencedCollection {
// 重写父接口的 reversed() 方法
SequencedSet reversed();
}
```
**SortedSet**
* Java集合框架中`SortedSet`接口,表示一个有序集合
* 不能支持显式定位操作,因为是基于元素之间的相对比较来确定它们位置的,而不是基于顺序插入
* 如果尝试使用`addFirst(E)`或`addLast(E)`方法,会抛出`UnsupportedOperationException`异常
**SequencedSet**
* `SequencedSet`接口扩展了`SortedSet`
* 其中`addFirst(E)`和`addLast(E)` 方法对于集合(如`LinkedHashSet`)具有特殊情况语义:
* 如果元素已经存在于集合中,则将其移动到适当的位置
* 这弥补了LinkedHashSet中的一个长期缺陷,即无法重新定位元素
## SequencedMap
sequenced map 是一个映射,其键具有已定义的顺序,
它不实现SequencedCollection,而是提供了自己的方法,这些方法将访问顺序应用于映射条目,而不是单个元素。
```java
interface SequencedMap extends Map {
// 新方法
SequencedMap reversed(); // 返回一个反转的映射
SequencedSet sequencedKeySet(); // 返回键的序列化集合
SequencedCollection sequencedValues(); // 返回值的序列化集合
SequencedSet> sequencedEntrySet();// 返回条目的序列化集合
V putFirst(K key, V value); // 将键值对放在映射的第一个位置
V putLast(K key, V value); // 将键值对放在映射的最后一个位置
// 从 NavigableMap 提升的方法,支持在映射的两端获取和移除条目
Entry firstEntry(); // 返回映射的第一个条目
Entry lastEntry(); // 返回映射的最后一个条目
Entry pollFirstEntry(); // 移除并返回映射的第一个条目
Entry pollLastEntry(); // 移除并返回映射的最后一个条目
}
```
新的`put*(K, V)`方法具有特殊的含义,类似于 SequencedSet 中对应的`add*(E)`方法:
* 对于 LinkedHashMap 映射,如果已存在相同键的条目,该方法将重新定位该条目
* 对于 SortedMap 映射,这些方法会抛出 `UnsupportedOperationException` 异常
## 改造
上述定义的三个新接口完美地适应了现有的集合类型层次结构(点击放大):

具体而言,我们对现有的类和接口进行以下调整:
* List 现在将 SequencedCollection 定义为其直接超级接口
* Deque 现在将 SequencedCollection 定义为其直接超级接口
* LinkedHashSet 另外实现了 SequencedSet
* SortedSet 现在将 SequencedSet 定义为其直接超级接口
* LinkedHashMap 另外实现了 SequencedMap
* SortedMap 现在将 SequencedMap 定义为其直接超级接口
我们在适当的位置为`reversed()`方法定义协变覆盖。
例如: `List::reversed` 被覆盖为返回 List 类型的值,而不是 `SequencedCollection` 类型的值。
我们还向 Collections 实用类添加了新方法,用于创建三种新类型的不可修改包装:
* Collections.unmodifiableSequencedCollection(sequencedCollection)
* Collections.unmodifiableSequencedSet(sequencedSet)
* Collections.unmodifiableSequencedMap(sequencedMap)
## 第一个和最后一个元素的访问
引入顺序接口的动机是对获取集合的第一个和最后一个元素的简单方法的长期未决需求。
目前,在Java 21之前,JDK API调用访问第一个和最后一个元素的一些示例:
| 访问位置 | List | Deque | SortedSet |
|--------|-------------------------|------------------|-------------|
| 第一个元素 | list.get(0) | deque.getFirst() | set.first() |
| 最后一个元素 | list.get(list.size()-1) | deque.getLast() | set.last() |
可以看到,一个简单的操作,在不同的集合中需要不同的编写方式,非常麻烦!
但在JDK 21之后,访问第一个和最后一个元素就方法多了:
对于`List`, `Deque`, `Set`这些有序的集合,访问方法变得统一起来:
* 第一个元素:`collection.getFirst()`
* 最后一个元素:`collection.getLast()`
================================================
FILE: src/md/java/features/Java21/jep439-generational-zgc.md
================================================
---
title: Java 21 新特性:分代ZGC
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-01-08
category: Java Features
tag:
- java
order: 439
---
# Java 21 新特性:分代ZGC(Generational ZGC)
Java以其垃圾回收机制而闻名。这是它的主要优势之一,但也可能是许多头疼的根源。
* Java 11([JEP 333](https://openjdk.org/jeps/333))中引入了一个可扩展的低延迟垃圾收集器,称为ZGC
* Java 15([JEP 377](https://openjdk.org/jeps/377))中 ZGC 可用于生产
* 现在,随着Java 21的出现,它已经发展成为一种分代GC([JEP 439](https://openjdk.org/jeps/439))
## 垃圾收集(Garbage Collection)
在Java中,垃圾收集器负责释放堆内存,堆内存是存储Java对象的地方。
这有助于防止内存泄漏并确保有效的资源使用,否则,程序会抛出`OutOfMemoryError`异常。
“[垃圾收集](https://wiki.c2.com/?GarbageCollection)”的概念本质上是**自动内存管理**, 这可能导致如下潜在的错误:
1. 需要时间来清理和重新排列内存,引入了运行时开销,超出了程序员的控制。
2. GC运行的实际点通常是不确定的,对于高吞吐量内存消耗大的应用,可能会长时间的“**GC暂停**”
3. 讽刺的是,GC的非确定性也是它的优点之一,我们不必担心内存是何时或如何释放的,它将自动发生
有三种主要的自动内存管理技术:
1. 引用计数([ReferenceCounting](https://wiki.c2.com/?ReferenceCounting))
2. 标记和清除([MarkAndSweep](https://wiki.c2.com/?MarkAndSweep))
3. 复制([StopAndCopy](https://wiki.c2.com/?StopAndCopy))
## 不同语言如何管理内存
* **C/C++**:手动管理内存,程序员负责分配和释放内存
* **Objective-C 和 Swift**:引入了自动引用计数(ARC),但仍然需要手动释放内存
* **Rust**:使用[仿射类型系统](https://en.wikipedia.org/wiki/Substructural_type_system#Affine_type_systems)
而不是GC,引入了所有权和借用,编译器在编译时检查内存安全性
* **Kotlin**:与Java类似,但引入了`Kotlin/Native`,允许手动内存管理
* **Java、[Python](https://devguide.python.org/internals/garbage-collector/)
、Go、[C#](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals)
、JavaScript**:自动内存管理,垃圾收集器负责释放内存
## HotSpot JVM垃圾收集器
内存管理有许多不同的方法,并且没有“最好”的方法。
即使在一种语言/运行时中,也可以有不止一种垃圾收集方法,JVM就是一个很好的例子。
与单一的GC不同,[HotSpot JVM](https://docs.oracle.com/en/java/javase/11/gctuning/available-collectors.html)有5个GC可供选择:
* Garbage-First Collector(G1)(Java 9后的默认选项)
* Serial Collector
* Parallel Collector
* ~~Concurrent Mark Sweep (CMS) Collector(Java 9中已弃用)~~
* Shenandoah GC(Java 12+)
* Z Garbage Collector(Java 15中可用于生产)
此外,不要忘记还有其他的JDK实现!
* [Eclipse OpenJ9](https://eclipse.dev/openj9/) 使用具有多个收集策略的分代并发GC
* [GraalVM](https://www.graalvm.org/latest/reference-manual/native-image/optimizations-and-performance/MemoryManagement/)
有 Epsilon GC,它是一个 No-Op GC,完全不进行内存清理
## 如何选择JVM GC
许多语言只提供了一种垃圾收集方法,而Java之所以提供多种GC选项,取决于您的应用程序对于“全局停顿”事件和总体暂停时间的容忍程度。
GC算法主要关注三个指标:
1. **吞吐量**:应用程序的运行时间与GC时间的比率
2. **延迟**:GC暂停时间
3. **内存占用**:GC对堆内存的使用
与许多问题一样,您无法为所有这些问题进行优化,因此每个GC都需要在它们之间找到平衡点。以下是一些场景及其匹配的GC作为起点:
| 垃圾收集器 | 场景 |
|-------------|------------------------------------------------|
| Serial | 小数据集 (最大~100 MB)
资源有限 (例如单核)
暂停时间短 |
| Parallel | 多核系统上的峰值性能
非常适合高计算负载
暂停时间 > 1秒是可以接受的 |
| G1
CMS | 响应时间 > 吞吐量
堆内存较大
暂停时间 < 1秒 |
| Shenandoah | 尽量减少暂停时间
可预测的延迟 |
| ZGC | 响应时间是高优先级的,和/或
非常大的堆内存 |
| Epsilon GC | 性能测试和故障排除 |
每种方法都有自己的优点和缺点,这在很大程度上取决于应用程序的需求和可用资源。
## 分代ZGC
Java 11引入的实验性功能**ZGC**是一种**非分代**的垃圾收集方法。
尽管如此,它在**GC暂停**时间方面仍然带来了显著改进,至少在资源足够的情况下,可以比并发线程消耗内存更快地回收内存。
缺点是,它将所有对象存储在一起,而不考虑年龄,因此每个周期都会收集所有对象。
[generational hypothesis](https://www.memorymanagement.org/glossary/g.html#generational.hypothesis)
观察到==年轻对象==比==年长对象==更有可能“**早逝**”,于是产生了分代假设。
Java 21 中,**分代 ZGC** 将堆分为两个逻辑代:一个用于最近分配的对象,另一个用于长期存活对象。
GC 可以专注于更频繁地收集**年轻**(最近分配)且更有**前途**(可能长期存在)的对象,而不会增加GC暂停时间,将其保持在 1 毫秒以下。
**分代ZGC**与非分代ZGC相比的关键优势:
* 减少分配停滞的风险
* 降低堆内存开销要求
* 减少垃圾回收CPU开销
此外,目标是在保留非分代方法已有优势的基础上实现这些优势:
* 暂停时间保持在在 1 毫秒以下
* 支持多达数万TB的堆大小
* 最少的手动配置
为了保持最后一点,新的GC不需要手动配置代的大小、GC使用的线程数,或对象在年轻代中停留的时间。
## 如何使用 JVM GC
在Java 21中,分代ZGC是默认的垃圾收集器。为了顺利过渡,分代ZGC将与非分代ZGC一起提供,您可以通过以下方式进行配置:
```shell
# 启用ZGC(默认为非分代)
$ java -XX:+UseZGC
# 使用分代ZGC
$ java -XX:+UseZGC -XX:+ZGenerational
```
如果您需要关闭分代ZGC,可以通过将加号(+)替换为减号(-)来实现:
```shell
# 不使用分代ZGC
$ java -XX:+UseZGC -XX:-ZGenerational
```
计划在更晚的版本中完全删除非分代ZGC。
================================================
FILE: src/md/java/features/Java21/jep440-record-partterns.md
================================================
---
title: Java 21 新特性:记录模式
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-01-09
category: Java Features
tag:
- java
order: 440
---
# Java 21 新特性:记录模式(Record Patterns)
Java 21 中的**记录模式**(Record Patterns)是对模式匹配的扩展,它允许在模式匹配中使用**记录**(Records)类型。
同时,记录模式还支持嵌套,可以实现更复杂的数据查询和处理。
## 仅仅是类型匹配
到目前为止,Java中的模式匹配主要局限于匹配类型:[instanceof类型匹配](/java-features/Java16/jep394-pattern-matching-for-instanceof)
```java
// Java 16 之前
if (obj instanceof String) {
String str = (String) obj;
System.out.println(str);
}
// JAVA 16+
if (obj instanceof String str) {
System.out.println(str);
}
```
Java
21扩展了这个概念,使其可用于switch语句和表达式: [switch的模式匹配](/java-features/Java21/jep441-pattern-matching-for-switch)
```java
// JAVA 21之前
static String asStringValue(Object anyValue) {
String result = null;
if (anyValue instanceof String str) {
result = str;
} else if (anyValue instanceof BigDecimal bd) {
result = bd.toEngineeringString();
} else if (anyValue instance Integer i) {
result = Integer.toString(i);
} else {
result = "n/a";
}
return result;
}
// JAVA 21+
static String asStringValue(Object anyValue) {
return switch (anyValue) {
case String str -> str;
case BigDecimal bd -> bd.toEngineeringString();
case Integer i -> Integer.toString(i);
default -> "n/a";
};
}
```
代码比之前更加简洁,同时也更加易读。但是,这种模式匹配仍然局限于类型匹配。
## record模式
当我们将模式匹配与记录类型结合使用时,我们称之为**记录模式**。这意味着我们可以在模式匹配中使用记录类型,以及记录类型的属性。
```java
record Point(int x, int y) {}
// Java 16 之前
static void printSum(Object obj) {
if (obj instanceof Point p) {
int x = p.x();
int y = p.y();
System.out.println(x+y);
}
}
// JAVA 21+
static void printSum(Object obj) {
if (obj instanceof Point(int x, int y)) {
System.out.println(x+y);
}
}
```
其中`Point(int x, int y)`就是记录模式,它匹配`Point`类型的对象,将记录的实例(obj)分解到它的组件(`x`和`y`)。
## 嵌套record的解构
假设我们设计了一个记录,表示一个矩形,其中包含左上角和右下角的颜色点。
如果我们想要获取左上角点的颜色,我们可以这样写:
```java
record Point(int x, int y) {}
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
Rectangle r = new Rectangle(new ColoredPoint(new Point(x1, y1), c1),
new ColoredPoint(new Point(x2, y2), c2));
// Java 16 之前
static void printUpperLeftColoredPoint(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) {
System.out.println(ul.c());
}
}
// JAVA 21+
static void printUpperLeftColoredPoint(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint(Point ul, Color c), ColoredPoint lr)) {
System.out.println(c);
}
}
```
嵌套模式允许我们使用与将其组合的代码一样清晰简洁的代码来拆解聚合。
## 发展脉络
该功能最初作为预览功能在Java 19(JEP 405)中首次亮相,随后经过Java 20(JEP 432)的迭代,最终在Java 21中定稿(JEP 440)。
此功能与模式匹配的switch语句(JEP 441)共同演进,并且它们之间存在相当大的互动。
================================================
FILE: src/md/java/features/Java21/jep441-pattern-matching-for-switch.md
================================================
---
title: Java 21 新特性:switch模式匹配
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-01-10
category: Java Features
tag:
- java
order: 441
---
# Java 21 新特性:switch模式匹配
Java 21 引入了 switch 模式匹配功能,它增强了 switch 语句的功能,允许使用更简洁的语法来执行类型检查和数据提取。
该功能与[记录模式(JEP 440)](/java-features/Java21/jep440-record-partterns)共同发展,并与之有相当大的互动。
## switch + instanceof
与if条件中的`instanceof`一样,`switch case`现在可以对其值进行类型检查,并创建一个case作用域变量:
```java
static String asStringValue(Object anyValue) {
return switch (anyValue) {
case String str -> str;
case JSONObject json -> json.toCompactString();
case BigDecimal bd -> bd.toEngineeringString();
case Integer i -> Integer.toString(i);
case LocalDate ld -> ld.format(DateTimeFormatter.ISO_LOCAL_DATE);
default -> "n/a";
};
}
```
## switch + null
```java
static String asStringValue(Object anyValue) {
return switch (anyValue) {
case null -> "n/a";
case String str -> str;
...
};
}
```
## switch + enum
```java
sealed interface CardClassification permits Suit, Tarot {}
public enum Suit implements CardClassification { CLUBS, DIAMONDS, HEARTS, SPADES }
final class Tarot implements CardClassification {}
static void exhaustiveSwitchWithBetterEnumSupport(CardClassification c) {
switch (c) {
case CLUBS -> System.out.println("梅花");
case DIAMONDS -> System.out.println("方块");
case HEARTS -> System.out.println("红桃");
case SPADES -> System.out.println("黑桃");
case Tarot t -> System.out.println("塔罗牌");
default -> System.out.println("未知的卡片类型");
}
}
```
================================================
FILE: src/md/java/features/Java21/jep444-virtual-threads.md
================================================
---
title: Java 21 新特性:虚拟线程
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-01-11
category: Java Features
tag:
- java
order: 444
---
# Java 21 新特性:虚拟线程(Virtual Threads)
Java 21 引入了**虚拟线程**(Virtual Threads)功能,类似于Go语言中的`Goroutines`。
虚拟线程是一种轻量级的线程,它可以极大地减少了编写、维护和管理高吞吐量并发应用程序所需的工作量。
Java平台目前为止有两种类型的线程:**传统线程**,也称为==平台线程==,和**虚拟线程**。
## 平台线程
在引入虚拟线程之前,我们所使用的线程`java.lang.Thread`是由所谓的平台线程支持的。
这些线程通常是 1:1 映射到操作系统线程的,因此它们是重量级的,创建和销毁线程的开销很大。
且每个请求都需要一个独立的线程,这会导致线程资源的快速耗尽,从而限制了应用程序的可伸缩性。
### 创建平台线程
```java
Thread thread = new Thread(() -> {
// 由平台线程执行的代码
}).start();
```
随着[Project Loom](https://openjdk.org/projects/loom/)简化了新的并发方法,它还提供了一种新的方法来创建平台支持的线程:
```java
Thread thread = Thread.ofPlatform()
.start(runnable);
// 或者
Thread thread = Thread.ofPlatform().
.daemon()
.name("platform-thread")
.unstarted(runnable);
```
## 虚拟线程
虚拟线程是JDK提供的**轻量级线程**实现,可以在同一个OS线程上运行许多虚拟线程。
虚拟线程为平台线程提供了一种更有效的替代方案,允许开发人员以显著降低的开销处理大量任务。
这些线程提供了与现有Java代码的兼容性和无缝迁移路径,从而从增强的性能和资源利用率中获益。
许多语言中都有某种形式的轻量级线程:
* Go语言的[Goroutines](https://go.dev/tour/concurrency/1)
* Erlang的[Erlang Processes](https://www.erlang.org/docs/23/efficiency_guide/processes.html)
* Haskell的[Haskell Threads](https://wiki.haskell.org/Lightweight_concurrency)
* 等等
### 创建虚拟线程
1. 使用`Thread.startVirtualThread()`方法创建虚拟线程:
```java
// 使用静态构建器方法
Thread.startVirtualThread(() -> {
// 由虚拟线程执行的代码
});
```
也可以使用`Thread.ofVirtual()`来创建,这里还可以设置一些属性,比如:线程名称等。具体如下代码:
```java
Thread virtualThread = Thread.ofVirtual()
.name("virtual-thread")
.start(runnable);
```
2. 使用`ExecutorService`创建虚拟线程:
从Java 5开始,就推荐开发人员使用`ExecutorServices`而不是直接使用`Thread`类了。
现在,Java 21中引入了使用虚拟线程,所以也有了新的ExecutorService来适配,看看下面的例子:
```java
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
} // executor.close() 被隐式调用, 然后 waits
```
3.使用`ThreadFactory`创建虚拟线程:
开发者还可以创建一个生成虚拟线程的工厂来管理,具体看下面的例子例子:
```java
ThreadFactory virtualThreadFactory = Thread.ofVirtual()
.name("virtual-thread", 0)
.factory();
Thread factoryThread = virtualThreadFactory.newThread(() -> {
// 由虚拟线程执行的代码
});
factoryThread.start();
```
这段代码创建了一个虚拟线程工厂,每个虚拟线程都会以`virtual-thread`为前缀、以数字结尾(从0开始累加)的名称。
## 虚拟线程如何工作
虚拟线程是一个新的轻量级`java.lang.Thread`变体,由JVM的[Project Loom](https://openjdk.org/projects/loom/)项目实现的。
它使用了一种称为`Continuation`的技术,不受操作系统的管理或调度。相反,JVM负责调度。

应用程序实例化虚拟线程,而 JVM 分配计算资源来处理它们。
与传统线程相对比,传统线程直接映射到操作系统(OS)进程。
传统线程中,应用程序代码负责提供和释放 OS 资源。
而虚拟线程中,应用程序实例化虚拟线程,从而表达并发需求。
但实际上是 JVM 从操作系统获取并释放资源。

所需的平台线程在 FIFO 工作窃取
[ForkJoinPool](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/ForkJoinPool.html)
中进行管理,默认情况下使用所有可用处理器,
但可以通过调整系统属性 `jdk.virtualThreadScheduler.parallelism` 来根据您的需求进行修改。
您熟悉的 `ForkJoinPool` 和其他功能(如并行流)使用的公共池的主要区别在于,公共池以 LIFO 模式运行。
================================================
FILE: src/md/java/features/Java9/jep222-jshell.md
================================================
---
title: Java 9 新特性:交互式编程环境JShell
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2023-12-21
category: Java Features
tag:
- java
order: 222
---
# Java 9 新特性:交互式编程环境JShell
JShell 是 Java 9 引入的一个**交互式编程环境**,它是 Java 编程语言的 REPL(Read-Eval-Print Loop)实现。
REPL 是一种编程环境,允许用户输入表达式并立即看到结果,而无需事先编写和编译完整的程序。
JShell 的目标是提供一个轻量级、灵活且易于使用的工具,使得 Java 开发者能够更直观地编写和测试代码。
## JShell快速入门
### 启动JShell
打开终端,然后执行命令:`jshell`,执行效果如下:
```java
➜ ~ jshell
| 欢迎使用 JShell -- 版本 9
| 要大致了解该版本, 请键入: /help intro
jshell>
```
### 帮助介绍 /help intro
执行 `/help intro` 命令以获取有关 JShell 工具的简要介绍,**intro** 是主题,提供了关于 jshell 工具的核心概念和使用方法的信息。
```
jshell> /help intro
|
| intro
| =====
|
| 使用 jshell 工具可以执行 Java 代码,从而立即获取结果。
| 您可以输入 Java 定义(变量、方法、类等等),例如:int x = 8
| 或 Java 表达式,例如:x + x
| 或 Java 语句或导入。
| 这些小块的 Java 代码称为“片段”。
|
| 这些 jshell 工具命令还可以让您了解和
| 控制您正在执行的操作,例如:/list
|
| 有关命令的列表,请执行:/help
jshell>
```
### 定义变量、方法、类
```java
// 定义变量
jshell> int x = 8
x ==> 8
// 定义方法
jshell> int square(int num) {
...> return num * num;
...> }
| 已创建 方法 square(int)
// 定义类
jshell> public class Message{
...> private String msg;
...> public Message(String msg){
...> this.msg = msg;
...> }
...> public String getMessage(){
...> return msg;
...> }
...> }
| 已创建 类 Message
jshell>
```
### 执行表达式、调用方法
```java
// 执行 Java 表达式
jshell> x + x
$4 ==> 16
// 调用方法
jshell> square(5)
$5 ==> 25
// 创建类实例并调用方法
jshell> Message messageObj = new Message("Hello, JShell!");
messageObj ==> Message@6d4b1c02
jshell> messageObj.getMessage()
$7 ==> "Hello, JShell!"
```
## 查看定义的变量:/vars
```java
jshell> /vars
| int x = 8
| int $4 = 16
| int $5 = 25
| Message messageObj = Message@6d4b1c02
| String $7 = "Hello, JShell!"
```
## 查看定义的方法:/methods
```java
jshell> /methods
| int square(int)
```
## 查看定义的类:/types
```java
jshell> /types
| class Message
```
## 列出输入源条目:/list
执行后,可以看到之前在`jshell`中输入的内容清单:
```java
jshell> /list
1 : int x = 8;
2 : int square(int num) {
return num * num;
}
3 : public class Message{
private String msg;
public Message(String msg){
this.msg = msg;
}
public String getMessage(){
return msg;
}
}
4 : x + x
5 : square(5)
6 : Message messageObj = new Message("Hello, JShell!");
7 : messageObj.getMessage()
jshell>
```
## 编辑源条目:/edit
上面通过`/list`列出了输入的条目信息,下面试试通过`/edit`编辑下,比如:
```java
jshell> /edit 2
```
这将打开编辑器,修改先前定义的 `square` 方法。
修改完成后,点击 **accept** 即可
## 删除源条目:/drop
使用 `/drop` 命令可以删除之前输入的源代码块。可以通过指定**名称**或 **ID** 删除特定的源代码块。例如:
```java
jshell> /drop Message
| 已删除 类 Message
```
这将删除之前定义的 `Message` 类。
## 保存文件:/save
通过 `/save` 命令,您可以将 JShell 中的源代码保存到文件中,以便将其保留或与他人共享。例如:
```java
jshell> /save myCode.java
```
这将把当前所有的源代码保存到一个名为 `myCode.java` 的文件中。
## 打开文件:/open
使用 `/open` 命令可以将文件的内容导入到 JShell 中,以便重新使用或修改。例如:
```java
jshell> /open myCode.java
```
这将导入之前保存的 `myCode.java` 文件中的源代码。
## 重置jshell:/reset
使用 `/reset` 命令可以清空 JShell 的状态,包括所有定义的变量、方法和类。例如:
```java
jshell> /reset
| 正在重置状态
```
这将重置 JShell 并清除所有之前定义的内容。
## 查看引入的包:/imports
使用 `/imports` 命令可以查看当前已经导入的包。这对于确保您在 JShell 中能够访问所需的类和方法非常有用。例如:
```java
jshell> /imports
| import java.util.*
```
这表明已经导入了 `java.util` 包。
## 退出jshell:/exit
使用 `/exit` 命令可以退出 JShell。如果需要,在命令后可以添加一个整数表达式片段作为退出代码。例如:
```java
jshell> /exit 0
```
这将以退出代码 0 退出 JShell。
## 查看命令:/help
最后,使用 `/help` 命令可以随时查看 JShell 的帮助信息,了解各种命令和主题的使用方法。例如:
```java
jshell> /help
| 键入 Java 语言表达式, 语句或声明。
| 或者键入以下命令之一:
| /list [<名称或 id>|-all|-start]
| 列出您键入的源
| /edit <名称或 id>
| 编辑源条目
| /drop <名称或 id>
| 删除源条目
| /save [-all|-history|-start] <文件>
| 将片段源保存到文件
| /open
| 打开文件作为源输入
| /vars [<名称或 id>|-all|-start]
| 列出已声明变量及其值
| /methods [<名称或 id>|-all|-start]
| 列出已声明方法及其签名
| /types [<名称或 id>|-all|-start]
| 列出类型声明
| /imports
| 列出导入的项
| /exit []
| 退出 jshell 工具
| /env [-class-path <路径>] [-module-path <路径>] [-add-modules <模块>] ...
| 查看或更改评估上下文
| /reset [-class-path <路径>] [-module-path <路径>] [-add-modules <模块>]...
| 重置 jshell 工具
| /reload [-restore] [-quiet] [-class-path <路径>] [-module-path <路径>]...
| 重置和重放相关历史记录 -- 当前历史记录或上一个历史记录 (-restore)
| /history [-all]
| 您键入的内容的历史记录
| /help [|]
| 获取有关使用 jshell 工具的信息
| /set editor|start|feedback|mode|prompt|truncation|format ...
| 设置配置信息
| /? [|]
| 获取有关使用 jshell 工具的信息
| /!
| 重新运行上一个片段 -- 请参阅 /help rerun
| /
| 按 ID 或 ID 范围重新运行片段 -- 参见 /help rerun
| /-
| 重新运行以前的第 n 个片段 -- 请参阅 /help rerun
|
| 有关详细信息, 请键入 '/help', 后跟
| 命令或主题的名称。
| 例如 '/help /list' 或 '/help intro'。主题:
|
| intro
| jshell 工具的简介
| keys
| 类似 readline 的输入编辑的说明
| id
| 片段 ID 以及如何使用它们的说明
| shortcuts
| 片段和命令输入提示, 信息访问以及
| 自动代码生成的按键说明
| context
| /env /reload 和 /reset 的评估上下文选项的说明
| rerun
| 重新评估以前输入片段的方法的说明
jshell>
```
这将显示 JShell 的主要帮助信息。
================================================
FILE: src/md/java/features/Java9/jep269-convenience-factory-methods-for-collections.md
================================================
---
title: Java 9 新特性:不可变集合的快捷创建方法
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2023-12-22
category: Java Features
tag:
- java
order: 266
---
# Java 9 新特性:不可变集合的快捷创建方法
Java 9 引入了一项令人期待的新特性,即**集合的便利工厂方法**(Convenience Factory Methods for Collections),旨在使不可变集合的创建更加简单和便捷。
在此之前,我们通常使用构造方法来初始化集合,而Java 9为我们提供了一些全新的静态工厂方法,使得创建不可变集合的过程更为优雅。
## Java 9的集合创建方式
Java 9引入了一些便利的工厂方法,使得创建和初始化集合对象变得更加简洁和方便。
这些改进包括List.of()、Set.of()和Map.of()等方法,用于创建不可变的集合对象。
```java
// 创建不可变列表
List immutableList = List.of("item1", "item2", "item3");
// 创建不可变集合
Set immutableSet = Set.of("item1", "item2", "item3");
// 创建不可变映射
Map immutableMap = Map.of("a", 1, "b", 2, "c", 3);
```
这样一行代码就完成了整个集合的创建和初始化过程,使得代码更加简洁、清晰,并且具有更高的可读性。
## Java 8的集合创建方式
Java 8引入了Lambda表达式和流式操作,这使得集合的初始化过程变得更加流畅和具有函数式编程的特性。
```java
// 创建不可变列表
List immutableList = Collections.unmodifiableList(
Stream.of("item1", "item2", "item3").collect(Collectors.toList())
);
// 创建不可变集合
Set immutableSet = Collections.unmodifiableSet(
Stream.of("item1", "item2", "item3").collect(Collectors.toSet())
);
// 创建不可变映射
Map immutableMap = Collections.unmodifiableMap(
Stream.of(new String[][]{{"a", "1"}, {"b", "2"}, {"c", "3"}})
.collect(Collectors.toMap(data -> data[0], data -> Integer.parseInt(data[1])))
);
```
虽然相较于传统方式,Java 8的写法更为紧凑,但仍显得略显繁琐。
## 传统的集合创建方式
```java
// 创建不可变列表
List traditionalList = new ArrayList<>();
traditionalList.add("item1");
traditionalList.add("item2");
traditionalList.add("item3");
traditionalList = Collections.unmodifiableList(traditionalList);
// 创建不可变集合
Set traditionalSet = new HashSet<>();
traditionalSet.add("item1");
traditionalSet.add("item2");
traditionalSet.add("item3");
traditionalSet = Collections.unmodifiableSet(traditionalSet);
// 创建不可变映射
Map traditionalMap = new HashMap<>();
traditionalMap.put("a", 1);
traditionalMap.put("b", 2);
traditionalMap.put("c", 3);
traditionalMap = Collections.unmodifiableMap(traditionalMap);
```
这种方式繁琐且不够直观,给代码的可读性和编写效率带来了一定的挑战。
## List.of() vs. Arrays.asList()
* **可变性:**`List.of` 创建的是不可变集合,`Arrays.asList` 是可变集合
```java
// List.of 创建建的列表是不可变的
List immutableList = List.of(1, 2, 3);
// 无法添加、删除或修改元素,以下操作会导致 UnsupportedOperationException
immutableList.add(4);
immutableList.set(0, 0);
// Arrays.asList() 创建的列表是可变的
List mutableList = Arrays.asList(1, 2, 3);
// 可以使用 add()、set() 方法修改元素,但不允许改变列表的大小
mutableList.add(4);
mutableList.set(0, 0);
```
* **null元素:**`List.of` 不允许包含 null 元素,`Arrays.asList` 允许包含 null 元素,但不推荐
```java
List listWithNull = Arrays.asList(1, null, 3);
```
* **底层数据结构:**`List.of `使用不可变数据结构,`Arrays.asList`底层使用数组,对列表修改将反映在原始数组上
```java
List immutableList = List.of(1, 2, 3);
List mutableList = Arrays.asList(1, 2, 3);
```
================================================
FILE: src/md/java/features/README.md
================================================
---
title: Java新版本特性(持续连载)
shortTitle:
description: Java 8-21:前沿特性解读系列(持续连载),截至2023年9月中旬,Java已达到第21版本。
尽管如此,众多开发者仍停滞在Java 8的认知水平。为填补这一差距,我们推出专栏,聚焦分享最新Java知识。
icon: java
cover:
author: 流浪码客
isOriginal: true # 是否原创文章
sticky: true # 置顶特定文章
star: true # 收藏star标记
date: 2023-12-20
category: Java Features
tag:
- java
# 布局 Frontmatter 配置
# navbar: false
# sidebar: heading
---
# Java新版本特性(持续连载)
为了帮助大家跟上最新的技术潮流,计划启动一个专栏,重点分享Java领域的前沿知识。
这个专栏将详细解读从**Java 8 - 最新版本**的各种有趣的新特性。
会持续不断地发布相关内容,希望大家能够关注并收藏起来,跟随我们一起深入了解Java的最新进展!
详细了解Java Enhancement Proposals(JEPs)及其最新动态
* 🚀 访问官方网站:[OpenJDK JEPs](https://openjdk.java.net/jeps/)
* ❄️ 参考howtodoinjava博客:[Java Versions and Features](https://howtodoinjava.com/series/java-versions-features/)
## Java 23(in development)
## Java 22(GA 2024/03/19)
* 423: Region Pinning for G1
* 447: Statements before super(...) (Preview)
* 454: Foreign Function & Memory API
* 456: Unnamed Variables & Patterns
* 457: Class-File API (Preview)
* 458: Launch Multi-File Source-Code Programs
* 459: String Templates (Second Preview)
* 460: Vector API (Seventh Incubator)
* 461: Stream Gatherers (Preview)
* 462: Structured Concurrency (Second Preview)
* 463: Implicitly Declared Classes and Instance Main Methods (Second Preview)
* 464: Scoped Values (Second Preview)
## Java 21(GA 2023/09/19)
* [430: String Templates (Preview)](Java21/jep430-string-templates)
* [431: Sequenced Collections](Java21/jep431-sequenced-collections)
* [439: Generational ZGC](Java21/jep439-generational-zgc)
* [440: Record Patterns](Java21/jep440-record-partterns)
* [441: Pattern Matching for switch](Java21/jep441-pattern-matching-for-switch)
* 442: Foreign Function & Memory API (Third Preview)
* 443: Unnamed Patterns and Variables (Preview)
* [444: Virtual Threads](Java21/jep444-virtual-threads)
* 445: Unnamed Classes and Instance Main Methods (Preview)
* 446: Scoped Values (Preview)
* 448: Vector API (Sixth Incubator)
* 449: Deprecate the Windows 32-bit x86 Port for Removal
* 451: Prepare to Disallow the Dynamic Loading of Agents
* 452: Key Encapsulation Mechanism API
* 453: Structured Concurrency (Preview)
## Java 20(GA 2023/03/21)
* 429: Scoped Values (Incubator)
* 432: Record Patterns (Second Preview)
* 433: Pattern Matching for switch (Fourth Preview)
* 434: Foreign Function & Memory API (Second Preview)
* 436: Virtual Threads (Second Preview)
* 437: Structured Concurrency (Second Incubator)
* 438: Vector API (Fifth Incubator)
## Java 19(GA 2022/09/20)
* 405: Record Patterns (Preview)
* 422: Linux/RISC-V Port
* 424: Foreign Function & Memory API (Preview)
* 425: Virtual Threads (Preview)
* 426: Vector API (Fourth Incubator)
* 427: Pattern Matching for switch (Third Preview)
* 428: Structured Concurrency (Incubator)
## Java 18(GA 2022/03/22)
* [400: UTF-8 by Default](Java18/jep400-utf8-by-default)
* [408: Simple Web Server](Java18/jep408-simple-web-server)
* [413: Code Snippets in Java API Documentation](Java18/jep413-code-snippets-in-api-documentation)
* 416: Reimplement Core Reflection with Method Handles
* 417: Vector API (Third Incubator)
* 418: Internet-Address Resolution SPI
* 419: Foreign Function & Memory API (Second Incubator)
* 420: Pattern Matching for switch (Second Preview)
* 421: Deprecate Finalization for Removal
## Java 17(GA 2021/09/14)
* 306: Restore Always-Strict Floating-Point Semantics
* 356: Enhanced Pseudo-Random Number Generators
* 382: New macOS Rendering Pipeline
* 391: macOS/AArch64 Port
* 398: Deprecate the Applet API for Removal
* 403: Strongly Encapsulate JDK Internals
* [406: Pattern Matching for switch (Preview)](Java17/jep406-pattern-matching-for-switch-preview)
* 407: Remove RMI Activation
* [409: Sealed Classes](Java17/jep409-sealed-classes)
* 410: Remove the Experimental AOT and JIT Compiler
* 411: Deprecate the Security Manager for Removal
* 412: Foreign Function & Memory API (Incubator)
* 414: Vector API (Second Incubator)
* 415: Context-Specific Deserialization Filters
## Java 16(GA 2021/03/16)
* 338: Vector API (Incubator)
* 347: Enable C++14 Language Features
* 357: Migrate from Mercurial to Git
* 369: Migrate to GitHub
* 376: ZGC: Concurrent Thread-Stack Processing
* 380: Unix-Domain Socket Channels
* 386: Alpine Linux Port
* 387: Elastic Metaspace
* 388: Windows/AArch64 Port
* 389: Foreign Linker API (Incubator)
* 390: Warnings for Value-Based Classes
* 392: Packaging Tool
* 393: Foreign-Memory Access API (Third Incubator)
* [394: Pattern Matching for instanceof](Java16/jep394-pattern-matching-for-instanceof)
* [395: Records](Java16/jep395-records)
* 396: Strongly Encapsulate JDK Internals by Default
* 397: Sealed Classes (Second Preview)
## Java 15(GA 2020/09/15)
* 339: Edwards-Curve Digital Signature Algorithm (EdDSA)
* 360: Sealed Classes (Preview)
* [371: Hidden Classes](Java15/jep371-hidden-classes)
* 372: Remove the Nashorn JavaScript Engine
* 373: Reimplement the Legacy DatagramSocket API
* 374: Disable and Deprecate Biased Locking
* 375: Pattern Matching for instanceof (Second Preview)
* 377: ZGC: A Scalable Low-Latency Garbage Collector
* [378: Text Blocks](Java15/jep378-text-blocks)
* 379: Shenandoah: A Low-Pause-Time Garbage Collector
* 381: Remove the Solaris and SPARC Ports
* 383: Foreign-Memory Access API (Second Incubator)
* 384: Records (Second Preview)
* 385: Deprecate RMI Activation for Removal
## Java 14(GA 2020/03/17)
* 305: Pattern Matching for instanceof (Preview)
* 343: Packaging Tool (Incubator)
* 345: NUMA-Aware Memory Allocation for G1
* 349: JFR Event Streaming
* 352: Non-Volatile Mapped Byte Buffers
* 358: Helpful NullPointerExceptions
* 359: Records (Preview)
* [361: Switch Expressions (Standard)](Java14/jep361-switch-expressions)
* 362: Deprecate the Solaris and SPARC Ports
* 363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector
* 364: ZGC on macOS
* 365: ZGC on Windows
* 366: Deprecate the ParallelScavenge + SerialOld GC Combination
* 367: Remove the Pack200 Tools and API
* 368: Text Blocks (Second Preview)
* 370: Foreign-Memory Access API (Incubator)
## Java 13(GA 2019/09/17)
* 350: Dynamic CDS Archives
* 351: ZGC: Uncommit Unused Memory
* 353: Reimplement the Legacy Socket API
* 354: Switch Expressions (Preview)
* 355: Text Blocks (Preview)
## Java 12(GA 2019/03/19)
* 189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental)
* 230: Microbenchmark Suite
* 325: Switch Expressions (Preview)
* 334: JVM Constants API
* 340: One AArch64 Port, Not Two
* 341: Default CDS Archives
* 344: Abortable Mixed Collections for G1
* 346: Promptly Return Unused Committed Memory from G1
## Java 11(GA 2018/09/25)
* 181: Nest-Based Access Control
* 309: Dynamic Class-File Constants
* 315: Improve Aarch64 Intrinsics
* 318: Epsilon: A No-Op Garbage Collector
* [320: Remove the Java EE and CORBA Modules](Java11/jep320-remove-JavaEE-CORBA)
* 321: HTTP Client (Standard)
* 323: Local-Variable Syntax for Lambda Parameters
* 324: Key Agreement with Curve25519 and Curve448
* 327: Unicode 10
* 328: Flight Recorder
* 329: ChaCha20 and Poly1305 Cryptographic Algorithms
* 330: Launch Single-File Source-Code Programs
* 331: Low-Overhead Heap Profiling
* 332: Transport Layer Security (TLS) 1.3
* 333: ZGC: A Scalable Low-Latency Garbage Collector(Experimental)
* 335: Deprecate the Nashorn JavaScript Engine
* 336: Deprecate the Pack200 Tools and API
## Java 10(GA 2018/03/20)
* [286: Local-Variable Type Inference](Java10/jep286-local-variable-type-inference)
* 296: Consolidate the JDK Forest into a Single Repository
* 304: Garbage-Collector Interface
* 307: Parallel Full GC for G1
* 310: Application Class-Data Sharing
* 312: Thread-Local Handshakes
* 313: Remove the Native-Header Generation Tool (Javah)
* 314: Additional Unicode Language-Tag Extensions
* 316: Heap Allocation on Alternative Memory Devices
* 317: Experimental Java-Based JIT Compiler
* 319: Root Certificates
* 322: Time-Based Release Versioning
## Java 9(GA 2017/09/21)
* 102: Process API Updates
* 110: HTTP 2 Client
* 143: Improve Contended Locking
* 158: Unified JVM Logging
* 165: Compiler Control
* 193: Variable Handles
* 197: Segmented Code Cache
* 199: Smart Java Compilation, Phase Two
* 200: The Modular JDK
* 201: Modular Source Code
* 211: Elide Deprecation Warnings on Import Statements
* 212: Resolve Lint and Doclint Warnings
* 213: Milling Project Coin
* 214: Remove GC Combinations Deprecated in JDK 8
* 215: Tiered Attribution for javac
* 216: Process Import Statements Correctly
* 217: Annotations Pipeline 2.0
* 219: Datagram Transport Layer Security (DTLS)
* 220: Modular Run-Time Images
* 221: Simplified Doclet API
* [222: jshell: The Java Shell (Read-Eval-Print Loop)](Java9/jep222-jshell)
* 223: New Version-String Scheme
* 224: HTML5 Javadoc
* 225: Javadoc Search
* 226: UTF-8 Property Files
* 227: Unicode 7.0
* 228: Add More Diagnostic Commands
* 229: Create PKCS12 Keystores by Default
* 231: Remove Launch-Time JRE Version Selection
* 232: Improve Secure Application Performance
* 233: Generate Run-Time Compiler Tests Automatically
* 235: Test Class-File Attributes Generated by javac
* 236: Parser API for Nashorn
* 237: Linux/AArch64 Port
* 238: Multi-Release JAR Files
* 240: Remove the JVM TI hprof Agent
* 241: Remove the jhat Tool
* 243: Java-Level JVM Compiler Interface
* 244: TLS Application-Layer Protocol Negotiation Extension
* 245: Validate JVM Command-Line Flag Arguments
* 246: Leverage CPU Instructions for GHASH and RSA
* 247: Compile for Older Platform Versions
* 248: Make G1 the Default Garbage Collector
* 249: OCSP Stapling for TLS
* 250: Store Interned Strings in CDS Archives
* 251: Multi-Resolution Images
* 252: Use CLDR Locale Data by Default
* 253: Prepare JavaFX UI Controls & CSS APIs for Modularization
* 254: Compact Strings
* 255: Merge Selected Xerces 2.11.0 Updates into JAXP
* 256: BeanInfo Annotations
* 257: Update JavaFX/Media to Newer Version of GStreamer
* 258: HarfBuzz Font-Layout Engine
* 259: Stack-Walking API
* 260: Encapsulate Most Internal APIs
* 261: Module System
* 262: TIFF Image I/O
* 263: HiDPI Graphics on Windows and Linux
* 264: Platform Logging API and Service
* 265: Marlin Graphics Renderer
* 266: More Concurrency Updates
* 267: Unicode 8.0
* 268: XML Catalogs
* [269: Convenience Factory Methods for Collections](Java9/jep269-convenience-factory-methods-for-collections)
* 270: Reserved Stack Areas for Critical Sections
* 271: Unified GC Logging
* 272: Platform-Specific Desktop Features
* 273: DRBG-Based SecureRandom Implementations
* 274: Enhanced Method Handles
* 275: Modular Java Application Packaging
* 276: Dynamic Linking of Language-Defined Object Models
* 277: Enhanced Deprecation
* 278: Additional Tests for Humongous Objects in G1
* 279: Improve Test-Failure Troubleshooting
* 280: Indify String Concatenation
* 281: HotSpot C++ Unit-Test Framework
* 282: jlink: The Java Linker
* 283: Enable GTK 3 on Linux
* 284: New HotSpot Build System
* 285: Spin-Wait Hints
* 287: SHA-3 Hash Algorithms
* 288: Disable SHA-1 Certificates
* 289: Deprecate the Applet API
* 290: Filter Incoming Serialization Data
* 291: Deprecate the Concurrent Mark Sweep (CMS) Garbage Collector
* 292: Implement Selected ECMAScript 6 Features in Nashorn
* 294: Linux/s390x Port
* 295: Ahead-of-Time Compilation
* 297: Unified arm32/arm64 Port
* 298: Remove Demos and Samples
* 299: Reorganize Documentation
## Java 8(GA 2014/03/18)
================================================
FILE: src/md/java/jvm/README.md
================================================
---
title: 深入理解Java虚拟机
shortTitle:
description:
icon: jvm-xx
cover:
author: 流浪码客
isOriginal: true
sticky: true # 置顶特定文章
star: true # 收藏star标记
date: 2024-08-08
category: JVM
tag:
- jvm
---
# 深入理解Java虚拟机目录
## 前言(Preface)
## 致谢(Acknowledgements)
## 第一部分 走近Java(Part 1: Approaching Java)
- **第1章 走近Java(Chapter 1: Approaching Java)**
- 1.1 概述(Overview)
- 1.2 Java技术体系(Java Technology System)
- 1.3 Java发展史(History of Java)
- [1.4 Java虚拟机家族(Java Virtual Machine Family)](part1/overview.html#java虚拟机家族)
- 1.5 展望Java技术的未来(Future of Java Technology)
- [1.6 实战:自己编译JDK(Practical: Compiling JDK)](part1/compile_jdk)
## 第二部分 自动内存管理(Part 2: Automatic Memory Management)
- **第2章 Java内存区域与内存溢出异常(Chapter 2: Java Memory Areas and OutOfMemoryError)**
- 2.1 概述(Overview)
- [2.2 运行时数据区域(Runtime Data Areas)](part2/runtime-data-areas)
- [2.3 HotSpot虚拟机对象探秘(HotSpot Virtual Machine Object Exploration)](part2/heap-object-flow)
- 2.4 实战:OutOfMemoryError异常(Practical: OutOfMemoryError Exception)
- **第3章 垃圾收集器与内存分配策略(Chapter 3: Garbage Collectors and Memory Allocation Strategies)**
- 3.1 概述(Overview)
- 3.2 对象已死吗?(Is the Object Dead?)
- 3.3 垃圾收集算法(Garbage Collection Algorithms)
- 3.4 HotSpot的算法实现(HotSpot Algorithm Implementation)
- 3.5 垃圾收集器(Garbage Collectors)
- **第4章 虚拟机性能监控、故障处理工具(Chapter 4: JVM Performance Monitoring and Troubleshooting Tools)**
- 4.1 概述(Overview)
- 4.2 JConsole介绍(Introduction to JConsole)
- [4.3 VisualVM介绍(Introduction to VisualVM)](part2/visual-tools/visualvm.md)
- 4.4 其他工具(Other Tools)
- **第5章 调优案例分析与实战(Chapter 5: Optimization Case Analysis and Practices)**
- 5.1 概述(Overview)
- 5.2 实战案例分析(Practical Case Analysis)
- 5.3 调优实践(Optimization Practices)
## 第三部分 虚拟机执行子系统(Part 3: JVM Execution Subsystem)
- **第6章 类文件结构(Chapter 6: Class File Structure)**
- 6.1 概述(Overview)
- [6.2 无关性的基石(The Cornerstone of Independence)](part3/class-file-structure.html#跨平台的基石)
- [6.3 Class类文件的结构(Structure of Class File)](part3/class-file-structure.html#class类文件结构-理论)
- [6.4 字节码指令简介(Introduction to Bytecode Instructions)](part3/bytecode-instructions-set)
- 6.5 公有设计,私有实现(Public Design, Private Implementation)
- 6.6 Class文件结构的发展(Development of Class File Structure)
- **第7章 虚拟机类加载机制(Chapter 7: JVM Class Loading Mechanism)**
- 7.1 概述(Overview)
- 7.2 类加载的时机(Timing of Class Loading)
- [7.3 类加载的过程(Class Loading Process)](part3/class-loading-mechanism.html#类加载的过程)
- [7.4 类加载器(Class Loaders)](part3/class-loading-mechanism.html#类加载器)
- 7.5 Java模块化系统(Java Modular System)
- **第8章 虚拟机字节码执行引擎(Chapter 8: JVM Bytecode Execution Engine)**
- 8.1 概述(Overview)
- 8.2 运行时栈帧结构(Runtime Stack Frame Structure)
- 8.3 方法调用与返回(Method Invocation and Return)
- **第9章 类加载及执行子系统的案例与实战(Chapter 9: Case Studies and Practices of Class Loading and Execution Subsystem)**
- 9.1 概述(Overview)
- 9.2 实战案例分析(Practical Case Analysis)
- 9.3 调优实践(Optimization Practices)
## 第四部分 程序编译与代码优化(Part 4: Program Compilation and Code Optimization)
- **第10章 前端编译与优化(Chapter 10: Front-end Compilation and Optimization)**
- 10.1 概述(Overview)
- 10.2 语法与语义分析(Syntax and Semantic Analysis)
- 10.3 字节码生成(Bytecode Generation)
- **第11章 后端编译与优化(Chapter 11: Back-end Compilation and Optimization)**
- 11.1 概述(Overview)
- 11.2 即时编译器(Just-in-time Compiler)
- 11.3 编译优化(Compilation Optimization)
## 第五部分 高效并发(Part 5: Efficient Concurrency)
- **第12章 Java内存模型与线程(Chapter 12: Java Memory Model and Threads)**
- 12.1 概述(Overview)
- 12.2 Java内存模型(Java Memory Model)
- 12.3 线程与线程池(Threads and Thread Pools)
- **第13章 线程安全与锁优化(Chapter 13: Thread Safety and Lock Optimization)**
- 13.1 概述(Overview)
- 13.2 线程安全(Thread Safety)
- 13.3 锁优化(Lock Optimization)
## 附录(Appendices)
- **附录A 在Windows系统下编译OpenJDK 6(Appendix A: Compiling OpenJDK 6 on Windows)**
- **附录B 展望Java技术的未来(2013年版)(Appendix B: Outlook of Java Technology's Future (2013 Edition))**
- **附录C 虚拟机字节码指令表(Appendix C: JVM Bytecode Instruction Table)**
- C.1 指令集概述(Overview of Instruction Set)
- C.2 常用指令详解(Detailed Explanation of Common Instructions)
- **附录D 对象查询语言(OQL)简介(Appendix D: Introduction to Object Query Language (OQL))**
- D.1 概述(Overview)
- D.2 OQL语法(OQL Syntax)
- D.3 使用案例(Usage Cases)
- **附录E JDK历史版本轨迹(Appendix E: Historical Versions of the JDK)**
- E.1 概述(Overview)
- E.2 版本列表(Version List)
================================================
FILE: src/md/java/jvm/part1/compile_jdk.md
================================================
---
title: 实战编译JDK
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-07-26
category: JVM
tag:
- jvm
order: 1.6
---
# 实战编译JDK
想要窥探Java虚拟机内部的实现原理,最直接的路径就是编译自己的JDK。
尽管网络上有不少开源JDK实现,但OpenJDK无疑是最广泛使用的,我们将选择OpenJDK进行编译实战。
## 获取源码
* 版本选择OpenJDK 12,下载地址 [https://hg.openjdk.org/jdk/jdk12](https://hg.openjdk.org/jdk/jdk12)
* 点击“browse”链接,然后选择对应的压缩包(zip、gz)进行下载

## 系统需求
建议在Linux或MacOS上构建OpenJDK,构建工具链和依赖项比起Windows或Solaris平台要容易许多。
* 认真阅读一遍源码中的`doc/building.html`文档
* 确保源码和依赖项不要放在包含中文的目录里面
## 构建编译环境
## 进行编译
## 在IDE工具中进行源码调试
================================================
FILE: src/md/java/jvm/part1/overview.md
================================================
---
title: Java虚拟机概述
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-07-19
category: JVM
tag:
- jvm
order: 1.4
---
# Java虚拟机概述
> **Java虚拟机**(Java Virtual Machine,简称JVM)是运行所有Java程序的虚拟计算机,是Java平台的核心实现。
> 它提供了一种独立于底层硬件和操作系统的运行环境,使Java程序能够在任何安装了JVM的系统上执行。
> JVM通过将Java字节码(.class文件)转换为机器码来实现跨平台运行,这一特性被称为“Write Once, Run Anywhere”。
## 跨平台开发的通用平台
随着发展,JVM不再是Java独享的Moment,越来越多的语言开始在JVM上运行,使JVM逐渐演变成一个**跨平台开发的通用平台**。

* JVM本质上只关心`.class`的字节码文件,而不关心源代码是用什么语言编写的。
* 通过Oracle TCK(Technology Compatibility Kit)测试,就是合格的Java虚拟机。
## Java虚拟机家族
**虚拟机始祖:Sun Classic/Exact VM(Sun/Oracle公司)**
* **Classic VM:**
* 1996年1月23日,Sun发布JDK 1.0,正式商用,最早的Java虚拟机实现
* 直到JDK 1.4,才完全退出商用虚拟机的历史舞台
* ==纯解释器==,可外挂即时编译器(JIT),**缺点**是只能二选一
* **Exact VM:**
* 在JDK 1.2时,在Solaris平台发布,是Classic VM的改进版
* 因准确式内存管理(Exact Memory Management)而得名,是垃圾收集时准确判断堆上数据的前提
* 它的编译执行系统已经具备现代高性能虚拟机雏形
* 如热点探测、两级即时编译器、==编译器与解释器混合工作==模式等
* **缺点**是只能在Solaris平台上运行
**武林盟主:HotSpot VM(Sun/Oracle公司)**
* **HotSpot VM**
* 最初由Longview Technologies公司开发,后被Sun公司收购
* 从JDK 1.3至今(2024),HotSpot VM成为默认虚拟机,目前使用最广泛
* HotSpot VM集成了Sun以上两款虚拟机优点(准确式内存管理,热点代码探测技术...)
* **优点**是同时支持解释执行和即时编译执行,在响应速度和执行速度上取得平衡
* Oracle收购Sun以后,建立HotRockit项目,把BEA JRocki优秀特性融合到HotSpot之中
* 2014年JDK 8时期,HotSpot移除掉[永久代](/md/jvm/part2/runtime-data-areas.html#方法区),吸收了JRockit的Java Mission Control监控工具等功能
**小家碧玉:Mobile/Embedded VM(Sun/Oracle公司)**
专门为移动设备和嵌入式设备设计的Java虚拟机(JavaME)
* **KVM(Kilobyte Virtual Machine)**:
* 用于早期的移动设备,但智能手机市场已被Android和iOS主导
* **CDC(Connected Device Configuration)**:
* 用于功能更强的嵌入式设备,但面临自家Java SE Embedded(eJDK)的竞争
* 由于Java SE的普及,CDC市场快速萎缩,Oracle基本砍掉了CDC-HI项目,将其划归Java SE Embedded
**天下第二:BEA JRockit/IBM J9 VM**
* **BEA JRockit**:
* 最初由BEA Systems开发,后被Oracle收购,永远停留在R28(JDK 6版JRockit代号)
* ==专注于服务器==硬件和服务端应用场景,不关注启动速度,不包含解释器实现
* 以其出色的垃圾收集器和性能和诊断工具(如Java Mission Control)著称
* **IBM J9 VM**:
* 全称“IBM Technology for Java Virtual Machine”,简称IT4J,但普遍称为J9
* 作为通用型JVM,其市场定位接近HotSpot,主要优势在IBM产品上
* 由IBM开发,2017年开源为[OpenJ9](https://www.eclipse.org/openj9/),现由Eclipse基金会维护
* 模块化设计优于HotSpot
* 核心组件库(包括垃圾收集器、即时编译器、诊断监控子系统等)构成了IBM OMR项目
* 可以在其他语言平台如Ruby、Python中快速组装成相应的功能
**软硬合璧:BEA Liquid VM/Azul VM(Zing)**
* **BEA Liquid VM**:
* 也被称为JRockit VE(Virtual Edition,VE),专为BEA WebLogic实时运行环境设计
* BEA公司开发的JRockit虚拟机虚拟化版本,可直接运行在自家Hypervisor系统上
* 不需要操作系统支持,自身实现了必要的操作系统功能(如线程调度、文件系统、网络支持等)
* 直接控制硬件,避免内核态/用户态切换,最大限度发挥硬件性能,提升Java程序执行效率
* 随着JRockit虚拟机终止开发,Liquid VM项目也停止了
* **Azul VM**:
* 适用于Azul Systems专有硬件Vega产品线,在HotSpot基础改进
* 采用PGC和C4收集器,停顿时间可控,每个Azul VM实例可管理数十个CPU和数百GB内存
* **Zing VM**:
* 2010年起,Azul公司重心转向软件,发布Zing虚拟机,基于HotSpot某旧版代码分支
* 低延迟,配备PGC和C4垃圾收集器
* 支持TB级别Java堆内存,暂停时间不超过10毫秒
* HotSpot直到JDK 11和JDK 12的ZGC和Shenandoah收集器才达到类似目标,但效果仍不及C4
* Zing的ReadyNow(快速预热、启动)!
* 利用之前收集的性能监控数据,使虚拟机在启动后快速达到高性能水平
* 减少从解释执行到即时编译的等待时间
* 易于监控(ZVision/ZVRobot工具)
* 方便用户监控JVM运行状态,包括代码热点、对象分配监控、锁竞争监控等
**挑战者:Apache Harmony/Google Android Dalvik VM**
* **Apache Harmony**:
* 一个开源的Java SE实现项目,旨在提供兼容Java SE的JVM及类库。虽然项目已停止,但其代码和理念影响深远
* Apache软件基金会开源项目,兼容JDK 5和JDK 6,提供自己的虚拟机和Java类库API。
* 没有通过TCK认证,无法正式称为“Java虚拟机”
* 曾对Java生态系统构成巨大挑战,导致Apache基金会退出JCP组织
* 随Sun公司开源OpenJDK,Harmony项目的优势逐渐减弱
* 主要贡献(如Java类库代码)被吸纳进IBM JDK 7和Google Android SDK
* **Google Android Dalvik VM**:
* Android平台核心虚拟机,名字来源于冰岛的小渔村Dalvik
* 非Java虚拟机,使用寄存器架构,不直接执行Java Class文件,而是执行DEX文件
* 通过Class文件转化为DEX文件,支持Java语法和API,推动Android迅速发展
* Android 2.2引入即时编译器,提升性能
* Android 4.4开始引入提前编译(Ahead of Time Compilation,AOT)的ART虚拟机
* Android 5.0开始ART全面替代Dalvik虚拟机
**没有成功,但并非失败:Microsoft JVM及其他**
* **Microsoft JVM**:
* 微软开发的Java虚拟机,曾用于早期的Windows平台。但由于与Sun的法律纠纷,微软最终停止了其开发
* 为支持Internet Explorer 3中的Java Applets,开发Microsoft JVM,仅限Windows平台
* 被认为是当时Windows系统下性能最好的Java虚拟机,1997年和1998年连续获得《PC Magazine》“编辑选择奖”
* 1997年被Sun公司控告侵犯商标、不正当竞争,最终微软赔偿2000万美元,并承诺停止开发和逐步移除其Java虚拟机
* 虽然微软的Java虚拟机未能长期发展,但其短暂的成功对当时Java的推广起到了积极作用。
**百家争鸣**
* KVM:为小型设备设计的轻量级Java虚拟机
* Java Card VM:支持智能卡和小型嵌入式设备的Java虚拟机
* Squawk VM:针对嵌入式系统和传感器网络的Java虚拟机
* JavaInJava:用Java自身编写的Java虚拟机
* Maxine VM:由Java编写、用于研究和实验的Java虚拟机
* Jikes RVM: IBM开源的高性能研究虚拟机
* IKVM.NET:在.NET平台上运行Java代码的虚拟机
* JamVM:[http://jamvm.sourceforge.net/](http://jamvm.sourceforge.net/)
* CacaoVM:[http://www.cacaovm.org/](http://www.cacaovm.org/)
* SableVM:[http://www.sablevm.org/](http://www.sablevm.org/)
* Kaffe:[http://www.kaffe.org/](http://www.kaffe.org/)
* Jelatine JVM:[http://jelatine.sourceforge.net/](http://jelatine.sourceforge.net/)
* NanoVM:[http://www.harbaum.org/till/nanovm/index.shtml](http://www.harbaum.org/till/nanovm/index.shtml)
* MRP:[https://github.com/codehaus/mrp](https://github.com/codehaus/mrp)
* Moxie JVM:[http://moxie.sourceforge.net/](http://moxie.sourceforge.net/)
## Java虚拟机架构
理解总体知识点在全局上与知识体系之间的对应关系

从整体上看,JVM 由三个不同的组件组成:
1. **类加载子系统(Class Loader SubSystem)**:主要负责将类`.class`加载到内存中
2. **运行时数据区(Runtime Data Area)**:管理JVM运行时所需的数据结构
3. **执行引擎(Execution Engine)**:负责执行字节码指令,将其转换为机器代码,供机器理解

---
### 类加载子系统
第一个组件 ==**类加载过程**== 分为三个阶段:加载、链接和初始化。
**1、加载(Loading)**:将类的字节码文件加载到内存中,并生成 JVM 运行时的类表示
* **启动类加载器(Bootstrap Class Loader):**
* 负责加载Java的核心类库,如`java.lang`、`java.net`、`java.util`、`java.io`等
* 这些类库位于`$JAVA_HOME/jre/lib`目录中,例如`rt.jar`
* **扩展类加载器(Extension Class Loader):**
* Bootstrap类加载器的子类,同时也是Application类加载器的父类。
* 它负责加载位于`$JAVA_HOME/jre/lib/ext`目录中的Java标准库的扩展
* **系统类加载器(Application Class Loader):**
* Extension类加载器的子类,加载位于类路径上的类文件,默认情况下,类路径为应用程序的目录
* 可通过添加命令行选项`-classpath`或`-cp`来修改类路径
**2、链接(Linking)**:将类的二进制数据合并到JVM的运行时状态中,分为以下三个步骤:
- **验证(Verify)**:通过一组约束或规则检查`.class`文件的正确性,验证失败抛出`VerifyException`
- **准备(Prepare)**:为类或接口的静态字段分配内,存并设置默认初始值
- **解析(Resolve)**:将符号引用替换为常量池中存在的直接引用
**3、初始化(Initialization)**:执行类的静态初始化块和静态变量的初始化

---
### 运行时数据区
第二个组件 ==**运行时数据区域**== 内含有五个数据区域:
* *线程共享区*:
- **方法区**(Method Area):存储已加载的类信息、常量池、静态变量和即时编译后的代码
- **堆(Heap Area)**:存储对象实例和数组(即`new`出来的对象)
- GC管理的主要区域,分为新生代和老年代
* *线程隔离区*:
- **虚拟机栈(Virtual Machine Stack)**:存储局部变量、操作数栈(用于计算)、栈帧和方法调用信息
- **程序计数器(PC寄存器,Program Counter Register)**:存储当前线程执行的字节码指令的地址,以便线程切换时能恢复
- **本地方法栈(Native Method Stack)**:用于执行本地方法(即 JNI 调用的C/C++代码)

---
### 执行引擎
第三个组件 ==**执行引擎**== 包含解释器、编译器和垃圾回收区:
* **解释器(Interpreter)**:**逐行**解释执行字节码指令,速度较慢,但实现简单
* **即时编译器(JIT Compiler,Just-In-Time Compiler)**:将**热点代码**(经常执行的字节码)编译成机器码,以提高执行效率
* **垃圾回收器(Garbage Collector)**:自动管理和回收堆中的无用对象,防止内存泄漏

这些部分共同组成了JVM的核心功能,使得Java程序可以跨平台运行,并且具有良好的性能和安全性
* 参考:[Siben Nayak—《面向初学者的Java虚拟机架构》](https://www.freecodecamp.org/news/jvm-tutorial-java-virtual-machine-architecture-explained-for-beginners/)
================================================
FILE: src/md/java/jvm/part2/heap-object-flow.md
================================================
---
title: 堆中对象分配、布局和访问的全过程
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-08-10
category: JVM
tag:
- jvm
order: 2.3
---
# 堆中对象分配、布局和访问的全过程
> 本文将深入探讨HotSpot虚拟机中Java堆中对象分配、布局和访问的全过程。
## 对象的创建
在Java中,创建对象通常使用`new`关键字,而在JVM中,创建对象的过程如下:
**1、类加载检查**
JVM首先检查`new`指令所引用的类是否已加载、连接和初始化。
如果没有,会先执行[类加载过程](/md/jvm/part3/class-loading-mechanism.html#类加载的过程)。
**2、分配内存**
JVM为新对象分配内存,其大小在类加载完成时确认,**内存分配的方式:**
1. **指针碰撞:** 适用于规整的堆内存,将内存分为已使用和未使用两部分,通过移动指针分配内存。
2. **空闲列表:** 适用于不规整的堆内存,JVM维护一个空闲列表,从中找到合适的内存块进行分配。
使用压缩整理功能的收集器(如Serial、ParNew)通常采用指针碰撞分配,而基于标记-清除(Sweep)算法的CMS收集器使用空闲列表分配。
**线程安全的内存分配方式:**
* **同步处理:** 通过同步操作或CAS加重试机制确保原子性。
* **线程本地分配缓冲(TLAB):** 每个线程在Java堆中预先分配一小块内存,分配时在`TLAB`中进行,
只有`TLAB`耗尽分配新的`TLAB`时才才需同步。
* 通过`-XX:+UseTLAB`参数来启用`TLAB`。
**3、初始化零值(不包括对象头)**
JVM将分配的内存空间(不包括对象头)初始化为零值,确保对象字段在未显式初始化时可直接使用。
**4、设置对象头**
JVM设置对象头,包括:
* **Mark Word:** 存储对象的哈希码(调用`Object::hashCode()`时计算)、GC分代年龄、锁信息等。
* **类元数据指针:** 指向对象所属类的元数据,确定对象是哪个类的实例。
**5、执行``方法**
至此,在JVM层面,一个新的对象已经产生了。
但从Java程序视角,对象创建才刚刚开始,所有的字段都还为零值。
只有构造方法``执行后,对象才按照程序员的意图完成初始化,成为一个真正可用的对象。
## 对象的内存布局
## 对象的访问定位
================================================
FILE: src/md/java/jvm/part2/runtime-data-areas.md
================================================
---
title: 运行时数据区
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-08-10
category: JVM
tag:
- jvm
order: 2.2
---
# 运行时数据区
> **运行时数据区**是指在运行程序时存储数据的内存区域。分为程序计数器、Java虚拟机栈、本地方法栈、Java堆和方法区五个部分。

* **线程私有:**
* **程序计数器** - 存储线程执行位置
* **虚拟机栈** - 存储Java方法调用与执行过程的数据
* **本地方法栈** - 存储本地方法的执行数据
* **线程共享:**
* **堆** - 主要存储对象
* **方法区** - 存储类/方法/字段等定义(元)数据
* **运行时常量区** - 保存常量static数据
## 程序计数器
> **程序计数器**是线程私有的,用于记录当前程序执行的字节码指定位置。
**知识点:**
1. 线程私有
2. 不会被垃圾回收
3. 访问速度最快(JVM内存区域中)
4. 占用内存少,不会出现`OutOfMemoryError`
5. 执行Java方法时,记录的是字节码指令地址
6. 执行Native方法时,记录为未定义(`undefined`)
**思考以下问题,加强理解:**
1. 程序计数器如何保证线程能够准确地恢复到之前的执行位置?
2. 字节码执行与程序计数器的关系?
## 虚拟机栈
> **虚拟机栈**是线程私有的内存区域,其生命周期与线程相同。
> 它描述了**方法执行的内存模型**。当方法被执行时,JVM会为该方法同步创建一个==栈帧(Stack Frame)==。
**知识点:**
1. 线程私有
2. 不会被垃圾回收
3. 访问速度仅次于程序计数器
4. 栈大小可设置,限制深度:
* 推荐固定大小设置(`-Xss 数值[k|m|g]`),达到上限,抛出`StackOverflowError`
* 动态扩展,可用内存不足时,抛出`OutOfMemoryError`
**栈帧的内部结构:**

* **局部变量表:** 用于存储方法中的局部变量和参数。
* **操作数栈:** 后进先出(LIFO)结构,用于方法执行时存储执行指令产生中间结果。
* **动态链接:** 指在方法调用时,将符号引用转换为直接引用的过程。
* **方法返回地址:** 指方法调用后返回位置的地址。
## 本地方法栈
> **本地方法栈**是线程私有,与虚拟机栈功能相似。其中虚拟机栈为Java方法(字节码)服务,本地方法栈则为Native方法服务。
* HotSpot虚拟机把虚拟机栈和本地方法栈合二为一。
* 与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出`StackOverflowError`和`OutOfMemoryError`异常。
## Java堆
> **Java堆**是虚拟机管理的内存中最大的一块,线程共享,并在虚拟机启动时创建。
> 它的唯一目的是存放对象实例,几乎所有的对象实例以及数组都在堆上分配。
**堆内存模型:**

现代垃圾收集器采用分代收集理论进行设计,因此堆内存被划分为多个区域,包括:
* **新生代:**
* 存放生命周期较短的对象
* 通常由Eden区和两个Survivor区(被称为from/to或s0/s1)组成,默认比例是`8:1:1`
* 填满时触发`Minor GC`(小型垃圾回收)
* 采用复制算法,将存活的对象复制到Survivor区,然后清理Eden区和使用过的Survivor区。
* **老年代:**
* 存放生命周期较长,或多次垃圾收集后任然存活的对象
* 填满时触发`Major GC`或`Full GC`,耗时严重
* 使用的垃圾收集算法通常是标记-清除算法或标记-整理算法。
* **永久代(PermGen):**
* 存放Class元数据,包括类结构、方法、字段信息等
* 属于“堆”的一部分,无法扩展时会抛出`OutOfMemoryError`异常
* 通过命令`-Xms`设置初始堆大小,`-Xmx`设定最大堆大小
* 从JDK8开始,被元空间(Metaspace)取代,称为“非堆”,使用的是本地内存
[DigitalOcean——Java (JVM) 内存模型 - Java 中的内存管理](https://www.digitalocean.com/community/tutorials/java-jvm-memory-model-memory-management-in-java)
## 方法区
> **方法区**是JVM规范中的一个逻辑区域,用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。
* 在Java7的时候,方法区被称为“**永久代**(PermGen)”。
* 从Java8开始,方法区的实现被改为**元空间**(Metaspace),元空间使用的是本地内存,而不是像永久代那样在JVM的堆内存中分配。
## 运行时常量池
> **运行时常量池**是方法区的一部分,用于存放编译期生成的各种字面量与符号引用,支持在运行时动态添加新的常量。
* **字面量:** 表示固定的数据值,如整数、浮点数、字符串等常量。
* **符号引用:** 一组符号,用于描述所引用的目标,包括类和接口的全限定名、字段和方法的名称。
**知识点:**
1. 具备动态性,如`String.intern()`方法将字符串对象添加到运行时常量池中。
2. 会产生`OutOfMemoryError`异常
**思考以下问题,加强理解:**
1. Class常量池与运行时常量池的关系?
## 直接内存
**直接内存**并不是虚拟机运行时数据区的一部分,也未在《Java虚拟机规范》中明确定义。
然而,由于其频繁使用且可能导致`OutOfMemoryError`异常,值得在此进行讨论。
**关键点:**
- **NIO的引入:** JDK 1.4引入了NIO(New Input/Output)类,通过通道(Channel)和缓冲区(Buffer)实现了一种新的I/O方式。它使用本地(Native)函数库直接分配堆外内存,并通过在Java堆中的`DirectByteBuffer`对象进行引用和操作。
- **性能优势:** 这种方法能够显著提高性能,因为它避免了在Java堆和本地堆之间的数据复制,从而加快了I/O操作。
- **内存限制:** 虽然直接内存的分配不受Java堆大小的限制,但仍受到本机总内存(包括物理内存、SWAP分区或分页文件)大小和处理器寻址空间的限制。
- **配置问题:** 在配置虚拟机参数(如`-Xmx`)时,管理员通常会根据实际物理内存来设置Java堆的大小,但可能忽略直接内存的占用。如果各个内存区域的总和超过了物理内存限制,可能在动态扩展时导致`OutOfMemoryError`异常。
================================================
FILE: src/md/java/jvm/part2/visual-tools/visualvm.md
================================================
---
title: VisualVM介绍
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2025-03-06
category: JVM
tag:
- jvm
order: 4.3
---
# VisualVM介绍
> 官网地址: https://visualvm.github.io/
* IDEA 集成:[VisualVM Launcher](https://plugins.jetbrains.com/plugin/7115-visualvm-launcher/versions)
* Github开源翻译助手:[VVM-Translator](https://github.com/zedoCN/VVM-Translator)
* 安装插件VisualVM GC
================================================
FILE: src/md/java/jvm/part3/bytecode-instructions-set.md
================================================
---
title: 字节码指令集
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-07-26
category: JVM
tag:
- jvm
order: 6.4
---
# 字节码指令集
> 字节码指令集是Java虚拟机(JVM)能理解和执行的低级指令集合。具体保存在Java类文件(`.class`)的方法区描述中。
总数不超过256个,由*操作码*和*操作数* 组成。
- **操作码(`Opcode`):** 一个字节长度的数字,代表某种特定操作
- **操作数(`Operands`):** 跟随操作码之后的零至多个参数,用于该操作所需的数据
由于JVM采用面向操作数栈而不是面向寄存器的架构,大多数指令都不包含操作数,只有一个操作码,指令参数存放在操作数栈中。
## 操作码助记符
数据类型相关的字节码指令,包含特定的**操作码助记符**:
| 数据类型 | 操作码助记符 |
|-----------|--------|
| int | `i` |
| long | `l` |
| short | `s` |
| byte | `b` |
| char | `c` |
| float | `f` |
| double | `d` |
| reference | `a` |
也有一些指令没有明确的类型字符:
| 指令 | 描述 |
|---------------|-----------------|
| `arraylength` | 操作数为数组类型对象 |
| `goto` | 无条件跳转指令,与数据类型无关 |
由于操作码长度只有一个字节,如果每种类型的指令都支持所有数据类型,指令数量将超出范围。
因此,Java虚拟机的指令集设计成非完全独立的(“Not Orthogonal”)。
即并非每种数据类型和每一种操作都有对应的指令。
## 操作码表
使用数据类型对应的操作码助记符替换操作码`opcode`列指令模板中的`T`,得到具体的字节码指令。
参考下表**Java虚拟机指令集所支持的数据类型**。
| opcode | byte | short | int | long | float | double | char | reference |
|---------------|--------|---------|-----------|---------|---------|---------|---------|-----------|
| **Tpush** | bipush | sipush | | | | | | |
| **Tconst** | | | iconst | lconst | fconst | dconst | | aconst |
| **Tload** | | | iload | lload | fload | dload | | aload |
| **Tstore** | | | istore | lstore | fstore | dstore | | astore |
| **Tinc** | | | iinc | | | | | |
| **Taload** | | baload | saload | iaload | laload | faload | daload | caload | aaload |
| **Tastore** | | bastore | sastore | iastore | lastore | fastore | dastore | castore | aastore |
| **Tadd** | | | iadd | ladd | fadd | dadd | | |
| **Tsub** | | | isub | lsub | fsub | dsub | | |
| **Tmul** | | | imul | lmul | fmul | dmul | | |
| **Tdiv** | | | idiv | ldiv | fdiv | ddiv | | |
| **Trem** | | | irem | lrem | frem | drem | | |
| **Tneg** | | | ineg | lneg | fneg | dneg | | |
| **Tshl** | | | ishl | lshl | | | | |
| **Tshr** | | | ishr | lshr | | | | |
| **Tushr** | | | iushr | lushr | | | | |
| **Tand** | | | iand | land | | | | |
| **Tor** | | | ior | lor | | | | |
| **Txor** | | | ixor | lxor | | | | |
| **i2T** | i2b | i2s | i2i | i2l | i2f | i2d | | |
| **l2T** | | | l2i | l2l | l2f | l2d | | |
| **f2T** | | | f2i | f2l | f2f | f2d | | |
| **d2T** | | | d2i | d2l | d2f | d2d | | |
| **Tcmp** | | | icmp | lcmp | | | | |
| **Tcmpl** | | | | | fcmpl | dcmpl | | |
| **Tcmpg** | | | | | fcmpg | dcmpg | | |
| **if_TcmpOP** | | | if_icmpOP | | | | | if_acmpOP |
| **Treturn** | | | ireturn | lreturn | freturn | dreturn | | areturn |
从表中看来,大部分指令不支持`byte`、`char`和`short`类型,`boolean`类型更是没有任何指令支持。
* 编译器会在编译期或运行期将`byte`和`short`类型数据带符号扩展(Sign-Extend)为`int`类型
* 将`boolean`和`char`类型数据零位扩展(Zero-Extend)为`int`类型
* 在处理这些类型的数组时,也会转换为使用`int`类型的字节码指令
因此,大多数对`boolean`、`byte`、`short`和`char`类型数据的操作,实际上都是使用`int`类型进行的。
## 字节码指令分类
### 加载和存储指令
> 加载和存储指令用于在栈帧中的局部变量表和操作数栈之间传输数据。
- **将局部变量加载到操作数栈**
* 整数加载指令:`iload`、`iload_`
* 长整型加载指令:`lload`、`lload_`
* 浮点型加载指令:`fload`、`fload_`
* 双精度浮点型加载指令:`dload`、`dload_`
* 引用类型加载指令:`aload`、`aload_`
- **将数值从操作数栈存储到局部变量表**
* 整数存储指令:`istore`、`istore_`
* 长整型存储指令:`lstore`、`lstore_`
* 浮点型存储指令:`fstore`、`fstore_`
* 双精度浮点型存储指令:`dstore`、`dstore_`
* 引用类型存储指令:`astore`、`astore_`
- **将常量加载到操作数栈**
* 字节常量加载指令:`bipush`
* 短整型常量加载指令:`sipush`
* 常量池加载指令:`ldc`、`ldc_w`、`ldc2_w`
* 空常量加载指令:`aconst_null`
* 整数常量加载指令:`iconst_m1`、`iconst_`
* 长整型常量加载指令:`lconst_`
* 浮点型常量加载指令:`fconst_`
* 双精度浮点型常量加载指令:`dconst_`
- **扩充局部变量表访问索引的指令**
* 扩展索引指令:`wide`
加载和存储指令主要用于操作数栈和局部变量表之间的数据传输。
此外,一些指令(如访问对象字段或数组元素的指令)也会涉及操作数栈的数据传输。
### 运算指令
> 算术指令用于对两个操作数栈上的值进行特定运算,并将结果重新存入到操作栈顶。
- **算术指令列表:**
* 加法指令:`iadd`、`ladd`、`fadd`、`dadd`
* 减法指令:`isub`、`lsub`、`fsub`、`dsub`
* 乘法指令:`imul`、`lmul`、`fmul`、`dmul`
* 除法指令:`idiv`、`ldiv`、`fdiv`、`ddiv`
* 求余指令:`irem`、`lrem`、`frem`、`drem`
* 取反指令:`ineg`、`lneg`、`fneg`、`dneg`
* 位移指令:`ishl`、`ishr`、`iushr`、`lshl`、`lshr`、`lushr`
* 按位或指令:`ior`、`lor`
* 按位与指令:`iand`、`land`
* 按位异或指令:`ixor`、`lxor`
* 局部变量自增指令:`iinc`
* 比较指令:`dcmpg`、`dcmpl`、`fcmpg`、`fcmpl`、`lcmp`
### 类型转换指令
> 类型转换指令可以将两种不同的数值类型相互转换。
> 用于实现用户代码中的显式类型转换操作,或处理字节码指令集中数据类型相关指令无法与数据类型一一对应的问题。
- **宽化类型转换:** 即小范围类型向大范围类型的安全转换
* `int`类型到`long`、`float`或者`double`类型
* `long`类型到`float`、`double`类型
* `float`类型到`double`类型
- **窄化类型转换:** 与“宽化”相对,需显式指令,可能导致正负号变化和精度丢失
* `i2b`、`i2c`、`i2s`、`l2i`、`f2i`、`f2l`、`d2i`、`d2l`、`d2f`
### 对象创建与访问指令
虽然类实例和数组都是对象,但Java虚拟机对类实例和数组的创建与操作使用了不同的字节码指令。
对象创建后,可以通过对象访问指令来获取对象实例或数组中的字段或者数组元素。
- **对象创建指令**
* 创建类实例:`new`
* 创建数组:`newarray`、`anewarray`、`multianewarray`
- **对象访问指令**
* 访问字段:`getfield`、`putfield`、`getstatic`、`putstatic`
* 访问数组元素:`baload`、`caload`、`saload`、`iaload`、`laload`、`faload`、`daload`、`aaload`
* 存储数组元素:`bastore`、`castore`、`sastore`、`iastore`、`fastore`、`dastore`、`aastore`
* 数组操作:`arraylength`
* 类型检查和转换:`instanceof`、`checkcast`
### 操作数栈管理指令
如同操作一个普通数据结构中的堆栈那样,Java虚拟机提供了一些用于直接操作操作数栈的指令,包括:
- **操作数栈管理指令**
* 出栈:`pop`、`pop2`
* 复制栈顶元素:`dup`、`dup2`、`dup_x1`、`dup2_x1`、`dup_x2`、`dup2_x2`
* 互换栈顶两个元素:`swap`
### 控制转移指令
> 控制转移指令用于在程序执行过程中有条件或无条件地跳转到其他指令位置,修改程序计数器(PC)的值。
- **条件分支**
* `ifeq`、`iflt`、`ifle`、`ifne`、`ifgt`、`ifge`
* `ifnull`、`ifnonnull`
* `if_icmpeq`、`if_icmpne`、`if_icmplt`、`if_icmpgt`、`if_icmple`、`if_icmpge`
* `if_acmpeq`、`if_acmpne`
- **复合条件分支**
* `tableswitch` — 使用表的方式处理范围内的分支
* `lookupswitch` — 使用查找表的方式处理分支
- **无条件分支**
- `goto`、`goto_w` — 无条件跳转到指定位置
- `jsr`、`jsr_w` — 跳转到子程序并保存返回地址
- `ret` — 从子程序返回
所有比较最终都转为`int`类型,Java虚拟机提供了丰富的 `int` 类型条件分支指令:
* `boolean`、`byte`、`char`和`short`直接使用`int`类型指令
* `long`、`float` 和 `double`先用对应比较指令,再转换为`int`进行条件分支
### 方法调用和返回指令
- **方法调用(分派、执行过程)** 分为以下五种指令,用于不同类型的方法调用:
* `invokevirtual`:调用对象的实例方法,依据对象的实际类型进行分派(虚方法分派),最常见
* `invokeinterface`:调用接口方法,运行时搜索实现了接口的方法
* `invokespecial`:调用需要特殊处理的实例方法,如实例初始化方法、私有方法和父类方法
* `invokestatic`:调用类静态方法(static方法)
* `invokedynamic`:运行时动态解析和调用方法,分派逻辑由用户定义
- **方法返回指令** 根据返回值的类型区分,包括:
* `ireturn`:返回 `boolean`、`byte`、`char`、`short` 和 `int` 类型的值
* `lreturn`:返回 `long` 类型的值
* `freturn`:返回 `float` 类型的值
* `dreturn`:返回 `double` 类型的值
* `areturn`:返回引用类型的值
* `return`:用于声明为 `void` 的方法、实例初始化方法、类初始化方法
### 异常处理指令
在Java程序中,**显式抛出异常**(`throw` 语句)由`athrow`指令实现。
除了显式抛出异常,在JVM指令检测到异常状况时,会自动抛出**运行时异常**。
例如,在整数运算中,当除数为零时,虚拟机会在`idiv`或`ldiv`指令中抛出`ArithmeticException`异常。
处理异常(`catch` 语句)在Java虚拟机中不是通过字节码指令实现的,而是采用**异常表**来完成。
### 同步指令
Java虚拟机支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用**管程**(Monitor,通常称为“锁”)来实现的。
**方法级同步**
方法级同步是隐式的,通过方法调用和返回操作实现。
虚拟机从方法常量池中的方法表结构中的 `ACC_SYNCHRONIZED` 访问标志来判断方法是否被声明为同步方法。同步方法的执行过程如下:
1. 方法调用时,检查 `ACC_SYNCHRONIZED` 标志。
2. 如果设置了该标志,执行线程需先成功持有管程,然后才能执行方法。
3. 方法执行完成(无论正常还是异常)后,释放管程。
4. 在方法执行期间,持有管程的线程独占管程,其他线程无法获取同一个管程。
**指令序列同步**
同步一段指令集序列由 `synchronized` 语句块表示。Java虚拟机的指令集中有 `monitorenter` 和 `monitorexit` 两条指令来支持 `synchronized` 关键字的语义。以下是一个示例代码及其编译后的字节码序列:
```java
void onlyMe(Foo f) {
synchronized(f) {
doSomething();
}
}
```
编译后的字节码序列:
```shell
Method void onlyMe(Foo)
0 aload_1 // 将对象f入栈
1 dup // 复制栈顶元素(即f的引用)
2 astore_2 // 将栈顶元素存储到局部变量表变量槽 2中
3 monitorenter // 以栈顶元素(即f)作为锁,开始同步
4 aload_0 // 将局部变量槽 0(即this指针)的元素入栈
5 invokevirtual #5 // 调用doSomething()方法
8 aload_2 // 将局部变量槽 2的元素(即f)入栈
9 monitorexit // 退出同步
10 goto 18 // 方法正常结束,跳转到18返回
13 astore_3 // 异常路径起始,见下面异常表的Target 13
14 aload_2 // 将局部变量槽 2的元素(即f)入栈
15 monitorexit // 退出同步
16 aload_3 // 将局部变量槽 3的元素(即异常对象)入栈
17 athrow // 把异常对象重新抛出给onlyMe()方法的调用者
18 return // 方法正常返回
Exception table:
From To Target Type
4 10 13 any
13 16 13 any
```
编译器必须确保无论方法通过何种方式完成,方法中调用过的每条`monitorenter`指令都有其对应的`monitorexit`指令,无论是正常结束还是异常结束。
为了保证在方法异常完成时`monitorenter`和`monitorexit`指令依然正确配对执行,编译器会自动生成一个异常处理程序,用于执行`monitorexit`指令。
================================================
FILE: src/md/java/jvm/part3/class-file-structure.md
================================================
---
title: 类文件结构
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-07-19
category: JVM
tag:
- jvm
order: 6.3
---
# 类文件结构
> 代码编译的结果从本地机器码转变为**字节码**,是存储格式发展的一小步,却是编程语言发展的一大步。
## 无关性的基石
**字节码**是构成==平台无关性==和的==语言无关性==基石:
* **平台无关性:**
* 字节码不依赖特定操作系统或硬件架构,任何支持**JVM**的环境(如`Windows`、`Linux`、`macOS`、甚至嵌入式设备)都能运行相同的字节码,实现 “*一次编写,到处运行*”。
* **语言无关性:**
* 多种编程语言(如`Java`、`Kotlin`、`Scala`、`Groovy`)都可以编译成字节码,并在**JVM**(如`GraalVM`)上运行,不仅限于Java。

## Class类文件结构
我们通俗的将类和接口的定义信息
Java技术的良好向后兼容性得益于Class文件结构的稳定性,Class文件对应的是类或接口的定义信息,是一组以8个字节为单位的二进制流。各数据项严格按顺序排列,没有任何分隔符。
Class文件格式类似于C语言的结构体,这种伪结构只有两种数据类型:“无符号数”和“表”。
* **无符号数:** ==基本数据类型==,使用`u1`、`u2`、`u4`、`u8`表示1、2、4、8个字节的无符号数。
* 它们可以描述数字、索引引用、数量值或按照`UTF-8`编码的字符串值。
* **表:** 由多个无符号数或其他表构成的==复合数据类型==,通常以“**_info**”结尾。
* 描述有层次关系的复合结构,整个Class文件本质上也是一个表,由按严格顺序排列的数据项构成。
[JVM虚拟机规范第四章](https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-4.html)中规定了`ClassFile`的结构,如下所示:
```java
ClassFile {
u4 magic; // 魔数 (0xCAFEBABE),标识class文件格式
u2 minor_version; // 次版本号
u2 major_version; // 主版本号
u2 constant_pool_count; // 常量池计数
cp_info constant_pool[constant_pool_count-1]; // 常量池
u2 access_flags; // 访问标志
u2 this_class; // 当前类索引
u2 super_class; // 父类索引
u2 interfaces_count; // 接口计数
u2 interfaces[interfaces_count]; // 接口索引表
u2 fields_count; // 字段计数
field_info fields[fields_count]; // 字段表
u2 methods_count; // 方法计数
method_info methods[methods_count]; // 方法表
u2 attributes_count; // 属性计数
attribute_info attributes[attributes_count]; // 属性表
}
```
通过分析 `ClassFile` 的内容,我们便可以知道 `class` 文件的组成。

### 1.魔数
> **魔数:** 区分文件类型的依据,验证文件是否有效。
* `0xCAFEBABE`是`Class`文件的魔数。
* 其他文件格式也使用魔数来进行身份识别。
* JPEG(jpg):`FFD8FF`
* PNG(png):`89504E47`
* GIF(gif):`47494638`
* TIFF(tif):`49492A00`
**`0xCAFEBABE`的由来**
在Java语言仍被称为“Oak”的早期(约 1991 年),Java开发团队便选定`0xCAFEBABE`作为魔数。
据Java开发小组关键成员*Patrick Naughton*透露,他们选择这个值是因为它好玩且容易记忆。
象征着著名咖啡品牌*Peet’s Coffee*备受喜爱的*Baristas*咖啡,也预示着日后“Java-咖啡”商标的出现。
### 2.Class文件版本号
> **版本号:** 紧跟魔数之后,占 4 个字节,包含主版本号和次版本号,用于标识Class文件的版本。
* 第5~6字节:**次**版本号(Minor Version)
* 第7~8字节:**主**版本号(Major Version),Java 8 = 52.0
主版本号从JDK 1.0 = **45**开始,每次大版本发布都会 **+1** ;
**向下兼容:**
* 使用JDK 1.8版本,编译出1.7版本的`class`
* 编译后的字节码版本高于JVM版本时,运行会产生`UnsupportedClassVersionError`异常
```shell
javac –source 1.8 –target 1.7 Example.java
```

注:从JDK 9开始,`javac`编译器不再支持使用`-source`参数编译版本号小于1.5的源码。
### 3.常量池
> **常量池:** 紧随版本号之后,可以理解成Class文件的资源仓库。用于存放*字面量*,*符号引用*,*常量池类型数据表*。
**1、常量池计数**
由于常量项不固定,入口处`u2`类型的数据值表示**常量池计数**(`constant_pool_count`)。
* 常量池的计数从1开始,即:`常量项 = 常量池计数 - 1`
* Class文件格式规范刻意将第0项常量空出,特定情况下索引值0表示“不引用任何常量池项”
**2、常量类型**
常量池主要存放两大类常量:**字面量**(Literal)和**符号引用**(Symbolic References)
* **字面量:** 比较接近于Java语言层面的常量概念,如数值、文本字符串、final常量等
* **符号引用:** 符号引用则属于编译原理方面的概念,包括以下几类常量:
* 被模块导出或者开放的包(Package)
* 类和接口的全限定名(Fully Qualified Name)
* 字段的名称和描述符(Descriptor)
* 方法的名称和描述符
* 方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
* 动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)
不同于C/C++编译时有“连接”步骤,JVM在加载Class文件时才进行“动态连接”。
因此,Class文件不保存方法、字段最终在内存中的布局信息。
JVM在类加载时从常量池获取符号引用,并在类创建或运行时解析为具体地址。
**3、常量池数据类型表**
[JVM虚拟机规范第四章-常量池](https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-4.html#jvms-4.4)
定义了`constant_pool`表条目具有以下通用格式:
```java
cp_info {
u1 tag;
u1 info[];
}
```
常量池中的每项常量都是一个表,起始的第一位是一个`u1`类型的标志位(tag),表示当前常量的类型。
1. 最初设计的11种常量类型
2. 为支持动态语言,增加了4种动态语言相关的常量
3. 为支持Java模块化系统(Jigsaw),新增了`CONSTANT_Module_info`和`CONSTANT_Package_info`
| 类型(tag) | 描述 | 结构细节(起始`u1 tag;`) |
|--------------------------------------|-----------------|----------------------------------------------------------------------------------------------------|
| CONSTANT_Utf8_info(1) | UTF-8编码的字符串 | `u2 length;`
字符串的字节长度
`u1 bytes[length];`
UTF-8编码的字节数据 |
| CONSTANT_Integer_info(3) | 整型字面量 | `u4 bytes;` 32位整数值 |
| CONSTANT_Float_info(4) | 浮点型字面量 | `u4 bytes;` 32位浮点数值 |
| CONSTANT_Long_info(5) | 长整型字面量 | `u4 high_bytes;` 高32位
`u4 low_bytes;` 低32位 |
| CONSTANT_Double_info(6) | 双精度浮点型字面量 | `u4 high_bytes;` 高32位
`u4 low_bytes;` 低32位 |
| CONSTANT_Class_info(7) | 类或接口的符号引用 | `u2 name_index;`
指向类或接口名称的索引 |
| CONSTANT_String_info(8) | 字符串类型字面量 | `u2 string_index;`
指向字符串字面量的索引 |
| CONSTANT_Fieldref_info(9) | 字段的符号引用 | `u2 class_index;`
指向字段所在类的索引
`u2 name_and_type_index;`
指向字段名称和描述符的索引 |
| CONSTANT_Methodref_info(10) | 类中方法的符号引用 | `u2 class_index;`
指向方法所在类的索引
`u2 name_and_type_index;`
指向方法名称和描述符的索引 |
| CONSTANT_InterfaceMethodref_info(11) | 接口中方法的符号引用 | `u2 class_index;`
指向接口所在类的索引
`u2 name_and_type_index;`
指向方法名称和描述符的索引 |
| CONSTANT_NameAndType_info(12) | 字段或方法的部分符号引用 | `u2 name_index;`
指向字段或方法名称的索引
`u2 descriptor_index;`
指向字段或方法描述符的索引 |
| CONSTANT_MethodHandle_info(15) | 表示方法句柄 | `u1 reference_kind;`
方法句柄的类型
`u2 reference_index;`
指向方法句柄引用的索引 |
| CONSTANT_MethodType_info(16) | 表示方法类型 | `u2 descriptor_index;`
指向方法类型描述符的索引 |
| CONSTANT_Dynamic_info(17) | 表示一个动态计算常量 | `u2 bootstrap_method_attr_index;`
指向引导方法属性的索引
`u2 name_and_type_index;`
指向名称和描述符的索引 |
| CONSTANT_InvokeDynamic_info(18) | 表示一个动态方法调用点 | `u2 bootstrap_method_attr_index;`
指向引导方法属性的索引
`u2 name_and_type_index;`
指向名称和描述符的索引 |
| CONSTANT_Module_info(19) | 表示一个模块 | `u2 name_index;`
指向模块名称的索引 |
| CONSTANT_Package_info(20) | 表示一个模块中开放或者导出的包 | `u2 name_index;`
指向包名称的索引 |
常量池的数据结构复杂,因为包含17种独立的常量类型,彼此没有共性,因此需要逐项讲解。
### 4.访问标志
> **访问标志:** 紧随常量池之后,占2个字节,表示类或接口的访问权限和属性,如是否为`public`、`abstract`、`final`等。
* 访问标志不仅用于描述类或接口,在字段表和方法表中也存在各自的访问标志。
**类和接口的访问标志(access_flags)**
| 标志名称 | 标志值 | 含义 |
|----------------|--------|--------------------------------------------------------------------------------------------------------------------|
| ACC_PUBLIC | 0x0001 | 是否为 `public` 类型 |
| ACC_FINAL | 0x0010 | 是否被声明为 `final`,只有类可设置 |
| ACC_SUPER | 0x0020 | 是否允许使用 `invokespecial` 字节码指令的新语义,`invokespecial` 指令的语义在 JDK 1.0.2 发生过改变,为了区别该指令使用哪种语义,JDK 1.0.2 之后编译出来的类的这个标志都必须为真 |
| ACC_INTERFACE | 0x0200 | 标识这是一个接口 |
| ACC_ABSTRACT | 0x0400 | 是否为 `abstract` 类型,对于接口或抽象类来说,此标志值为真,其他类型值为假 |
| ACC_SYNTHETIC | 0x1000 | 标识这个类并非由用户代码产生的 |
| ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |
| ACC_ENUM | 0x4000 | 标识这是一个枚举 |
| ACC_MODULE | 0x8000 | 标识这是一个模块 |
总共有 16 个标记位可供使用,但常用的只有其中 7 个,见下图:

### 5.类索引、父类索引与接口索引集合
> **类索引、父类索引与接口索引集合:** 用于确定类的继承和实现关系,分别指出当前类,父类,以及所实现的接口。
* **类索引(`this_class`):** 当前类的全限定名
* **父类索引(`super_class`):** 父类的全限定名
* **接口索引集合(`interfaces`):** 当前类实现的所有接口
对于接口索引集合,首项为`u2`类型的接口计数器`interfaces_count`,表示索引表的容量。
如果该类没有实现任何接口,则该计数器值为0,后面接口的索引表不再占用任何字节。
否则每个索引指向常量池中的一个`CONSTANT_Class_info`类型的接口名称
**类索引查找全限定名的过程**
类索引和父类索引都是`u2`类型的索引值,指向`CONSTANT_Class_info`常量,再通过`CONSTANT_Class_info`常量中的索引值,找到定义在`CONSTANT_Utf8_info`中的全限定名字符串

### 6.字段表集合
> **字段表(`field_info`):** 描述类或接口中声明的字段。
[JVM虚拟机规范第四章-字段表](https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-4.html#jvms-4.5)
定义了结构:
```java
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
```
**1、字段访问标志(`access_flags`)**
与类中的`access_flags`类似,都是一个`u2`的数据类型,取值如下表:
| 标志名称 | 标志值 | 含义 |
|---------------|--------|------------------|
| ACC_PUBLIC | 0x0001 | 字段是否 `public` |
| ACC_PRIVATE | 0x0002 | 字段是否 `private` |
| ACC_PROTECTED | 0x0004 | 字段是否 `protected` |
| ACC_STATIC | 0x0008 | 字段是否 `static` |
| ACC_FINAL | 0x0010 | 字段是否 `final` |
| ACC_VOLATILE | 0x0040 | 字段是否 `volatile` |
| ACC_TRANSIENT | 0x0080 | 字段是否 `transient` |
| ACC_SYNTHETIC | 0x1000 | 字段是编译器自动产生 |
| ACC_ENUM | 0x4000 | 字段是否 `enum` |
受Java语法规则的约束:
* `public`、`private`、`protected` 只能三选一
* `final`、`volatile`不能同时选择
* 接口中的字段必须有 `public`、`static`和`final`
**2、简单名称(`name_index`)和描述符(`descriptor_index`)**
跟随`access_flags`标志之后的两项索引值;以及**全限定名**这三种特殊字符串的概念解释:
* 全限定名:表示字段或方法在类中的完整路径,包括包名和类名。例如`java.lang.String`
* 简单名称:表示字段或方法的名称。例如`name`是字段的简单名称,`toString`是方法的简单名称
* 描述符:表示字段或方法的类型信息
* 对于字段,描述符表示字段的类型,例如`I`表示`int`类型
* 对于方法,描述符表示方法的参数和返回类型,例如`(I)V`表示接受`int`参数且无返回值的方法
**描述符标识字符含义**
| 标识字符 | 含义 |
|------|------------------------------|
| B | 基本类型 `byte` |
| C | 基本类型 `char` |
| D | 基本类型 `double` |
| F | 基本类型 `float` |
| I | 基本类型 `int` |
| J | 基本类型 `long` |
| S | 基本类型 `short` |
| Z | 基本类型 `boolean` |
| V | 特殊类型 `void` |
| L | 对象类型,例如 `Ljava/lang/Object;` |
**3、属性表集合**
Class文件、字段表、和方法表都包含各自的属性表集合,用于记录特定场景下的附加信息。
每个属性表集合由属性计数(`attributes_count`)和若干属性信息(`attribute_info`)组成。
* **属性计数 (`attributes_count`):** 表示该集合中包含的属性个数
* **属性信息 (`attribute_info`):** 每个属性的信息结构,提供详细的元数据
### 9.方法表集合
> **方法表(`method_info`):** 描述类或接口中声明的方法。
[JVM虚拟机规范第四章-方法表](https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-4.html#jvms-4.6)
定义了结构,与属性表相似:
```java
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
```
**1、方法访问标志(`access_flags`)**
* 去除`volatile`和`transient`关键字,不能修饰方法
* 新增`synchronized`、`native`、`strictfp`和`abstract`关键字
| 标志名称 | 标志值 | 含义 |
|------------------|--------|----------------------|
| ACC_PUBLIC | 0x0001 | 方法是否为 `public` |
| ACC_PRIVATE | 0x0002 | 方法是否为 `private` |
| ACC_PROTECTED | 0x0004 | 方法是否为 `protected` |
| ACC_STATIC | 0x0008 | 方法是否为 `static` |
| ACC_FINAL | 0x0010 | 方法是否为 `final` |
| ACC_SYNCHRONIZED | 0x0020 | 方法是否为 `synchronized` |
| ACC_BRIDGE | 0x0040 | 方法是否是由编译器产生的桥接方法 |
| ACC_VARARGS | 0x0080 | 方法接受不定参数 |
| ACC_NATIVE | 0x0100 | 方法是否为 `native` |
| ACC_ABSTRACT | 0x0400 | 方法是否为 `abstract` |
| ACC_STRICT | 0x0800 | 方法是否为 `strictfp` |
| ACC_SYNTHETIC | 0x1000 | 方法是否由编译器自动产生 |
**2、方法的代码`Code`**
方法的定义可以通过访问标志、名称索引、描述符索引来表达清楚。
而方法的代码,经过`javac`编译成[字节码指令](/md/jvm/basics/bytecode)后存放在**方法属性表集合**中的`Code`的属性中。
### 8.属性表集合
> **属性表:** 用于存储一些额外的信息,如源文件名称、编译器版本等。
* Class文件、字段表、和方法表都可以携带自己的属性表集合,以描述某些场景专有的信息。
* 其限制相对宽松,不要求具有严格顺序。编译器可向属性表写入自定义信息,JVM运行时会忽略不认识的属性。
**虚拟机规范预定义的属性**
| 属性名称 | 使用位置 | 含义 |
|--------------------------------------|-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Code | 方法表 | Java代码编译成的字节码指令 |
| ConstantValue | 字段表 | 由`final`关键字定义的常量值 |
| Deprecated | 类、方法表、字段表 | 被声明为`deprecated`的方法和字段 |
| Exceptions | 方法表 | 方法抛出的异常列表 |
| EnclosingMethod | 类文件 | 仅当一个类为局部类或者匿名类时才可能拥有此属性,用于标示此类存在的外部方法 |
| InnerClasses | 类文件 | 内部类列表 |
| LineNumberTable | Code属性 | Java代码的行号与字节码指令的对应关系 |
| LocalVariableTable | Code属性 | 方法的局部变量信息 |
| StackMapTable | Code属性 | JDK6新增属性,供新的类型检查验证器(Type Checker)检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配 |
| Signature | 类、方法表、字段表 | JDK5新增属性,用于支持泛型标记下的方法签名。在 Java 语言中,任何类、接口、初始化方法或成员的字段如果包含了类型变量(Type Variables)或参数化类型(Parameterized Types),则 Signature 属性会记录泛型签名信息。由于 Java 的泛型采用擦除实现,为了能够在泛型擦除后还能确保签名信息,可以通过 Signature 属性记录泛型签名相关信息 |
| SourceFile | 类文件 | 记录源文件名称 |
| SourceDebugExtension | 类文件 | JDK5新增属性,用于存储额外的调试信息。譬如如在 JSP 文件调试时,无法通过 Java 推栈来推导到 JSP 文件的代码。JSR 45 提议的运行时通过插桩机制向虚拟机中的程序提供了一种进行调试的标准机制,使用该属性可以用于存储插桩时额外新增的调试信息 |
| Synthetic | 类、方法表、字段表 | 标示为编译器自动生成的代码 |
| LocalVariableTypeTable | 类 | JDK5新增属性,使用扩展的签名标示符,是为了引入泛型方法之后能描述泛型参数的类型而添加 |
| RuntimeVisibleAnnotations | 类、方法表、字段表 | JDK5新增属性,为动态注解提供支持。该属性用于指明哪些注解是在运行时(实际在运行时就意味着反射调用)可见的 |
| RuntimeInvisibleAnnotations | 类、方法表、字段表 | JDK5新增属性,与 RuntimeVisibleAnnotations 属性作用相反,用于指明哪些注解是在运行时不可见的 |
| RuntimeVisibleParameterAnnotations | 方法表 | JDK5新增属性,作用与 RuntimeVisibleAnnotations 属性类似,只不过作用对象为方法参数 |
| RuntimeInvisibleParameterAnnotations | 方法表 | JDK5新增属性,作用与 RuntimeInvisibleAnnotations 属性类似,只不过作用对象为方法参数 |
| AnnotationDefault | 方法表 | JDK5新增属性,用于记录注解类型元素默认值 |
| BootstrapMethods | 类文件 | JDK7新增属性,用于保存 invokedynamic 指令引用的引导方法限定符 |
| RuntimeVisibleTypeAnnotations | 类、方法表、字段表、Code 属性 | JDK8新增属性,为实现 JSR 308 中新增的类型注解提供的支持。用于指明哪些注解是在运行时(实际在运行时意味着反射调用)可见的 |
| RuntimeInvisibleTypeAnnotations | 类、方法表、字段表、Code 属性 | JDK8新增属性,为实现 JSR 308 中新增的类型注解提供的支持。与 RuntimeVisibleTypeAnnotations 属性作用相反,用于指明哪些注解是在运行时不可见的 |
| MethodParameters | 方法表 | JDK8新增属性,用于支持(编译时加上 -parameters 参数)将方法参数名称保存进 Class 文件中,并可运行时获取此数据。此数据可用于方法参数名称(典型的如 IDE 的代码提示)只能通过 Javadoc 中得到 |
| Module | 类 | JDK9新增属性,用于记录一个 Module 的名称以及相关信息(requires、exports、opens、uses、provides) |
| ModulePackages | 类 | JDK9新增属性,用于记录一个模块中所有存在 exports 或者 opens 的包 |
| ModuleMainClass | 类 | JDK9新增属性,用于指定一个模块的主类 |
| NestHost | 类 | JDK11新增属性,用于支持嵌套类(Java中的内部类)的成员和访问控制的 API。——宿主类通过此属性知道自己有哪些内部类 |
| NestMembers | 类 | JDK11新增属性,用于支持嵌套类(Java中的内部类)的成员和访问控制的 API。——宿主类通过此属性知道自己有哪些内部类 |
[JVM虚拟机规范第四章-属性表](https://docs.oracle.com/javase/specs/jvms/se23/html/jvms-4.html#jvms-4.7)
定义了结构:
```shell
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
```
对于每个属性,其名称从常量池中引用1个`CONSTANT_Utf8_info`表示,
通过1个`u4`的`attribute_length`说明属性值的字节数,属性值的结构完全自定义。
1. Code属性
2. Exceptions属性
3. LineNumberTable属性
4. LocalVariableTable及LocalVariableTypeTable属性
5. SourceFile及SourceDebugExtension属性
6. ConstantValue属性
7. InnerClasses属性
8. Deprecated及Synthetic属性
9. StackMapTable属性
10. Signature属性
11. BootstrapMethods属性
12. MethodParameters属性
13. 模块化相关属性
14. 运行时注解相关属性
## 编译字节码分析(实践)
使用`javac Main.java`命令,编译生成`Main.class`文件:
```java
public class Main {
private int m;
public int inc() {
return m + 1;
}
}
```
使用[WinHex](https://www.ghxi.com/winhex.html)(十六进制编辑器) 打开`.class`文件查看:
```shell
CA FE BA BE 00 00 00 3D 00 13 0A 00 02 00 03 07
00 04 0C 00 05 00 06 01 00 10 6A 61 76 61 2F 6C
61 6E 67 2F 4F 62 6A 65 63 74 01 00 06 3C 69 6E
69 74 3E 01 00 03 28 29 56 09 00 08 00 09 07 00
0A 0C 00 0B 00 0C 01 00 04 4D 61 69 6E 01 00 01
6D 01 00 01 49 01 00 04 43 6F 64 65 01 00 0F 4C
69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 01 00
03 69 6E 63 01 00 03 28 29 49 01 00 0A 53 6F 75
72 63 65 46 69 6C 65 01 00 09 4D 61 69 6E 2E 6A
61 76 61 00 21 00 08 00 02 00 00 00 01 00 02 00
0B 00 0C 00 00 00 02 00 01 00 05 00 06 00 01 00
0D 00 00 00 1D 00 01 00 01 00 00 00 05 2A B7 00
01 B1 00 00 00 01 00 0E 00 00 00 06 00 01 00 00
00 01 00 01 00 0F 00 10 00 01 00 0D 00 00 00 1F
00 02 00 01 00 00 00 07 2A B4 00 07 04 60 AC 00
00 00 01 00 0E 00 00 00 06 00 01 00 00 00 06 00
01 00 11 00 00 00 02 00 12
```
* `CA FE BA BE`:**魔数**,用于标识Class文件格式
* `00 00 00 3D`:**版本号**,其中`00 00`是次版本号,`00 3D`是主版本号(61,对应Java17)
* `00 13`:**常量池计数**,`0x13`十进制为19,第0项常量空出,因此常量池中有18个常量
使用`javap -verbose Main`命令查看**常量池**:
```shell
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "":()V
#4 = Utf8 java/lang/Object
#5 = Utf8
#6 = Utf8 ()V
#7 = Fieldref #8.#9 // Main.m:I
#8 = Class #10 // Main
#9 = NameAndType #11:#12 // m:I
#10 = Utf8 Main
#11 = Utf8 m
#12 = Utf8 I
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 inc
#16 = Utf8 ()I
#17 = Utf8 SourceFile
#18 = Utf8 Main.java
```
* `00 21`:**访问标志**,`ACC_PUBLIC`(public)和`ACC_SUPER`(super)

* `00 08`:**类索引**,指向常量池第8项`#8 = Class #10 // Main`,表示当前类是`Main`
* `00 02`:**父类索引**,指向常量池第2项`#2 = Class #4 // java/lang/Object`,表示父类是`Object`
* `00 00`:**接口计数器**,为0表示该类没有实现任何接口,所以**接口索引集合**为空
* `00 01`:**字段计数器**,表示有1个字段
* `00 02 00 0B 00 0C 00 00`: **字段表集合**
* `access_flags`:`00 02`表示`private`访问权限
* `name_index`:`00 0B`指向常量池中的第11项`#11 = Utf8 m`,表示字段名为`m`
* `descriptor_index`:`00 0C`指向常量池中的第12项`#12 = Utf8 I`,表示字段类型为`int`
* `attributes_count`:`00 00`表示没有属性,所以`attribute_info`为空
* `00 02`:**方法计数器**,表示有2个方法
使用`javap -verbose Main`命令查看**方法表集合**,
或使用[IDEA jclasslib插件](https://plugins.jetbrains.com/plugin/9248-jclasslib-bytecode-viewer)查看:
```shell
{
public Main();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 1: 0
public int inc();
descriptor: ()I
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: getfield #7 // Field m:I
4: iconst_1
5: iadd
6: ireturn
LineNumberTable:
line 6: 0
}
```
[酷 壳 – CoolShell《实例分析JAVA CLASS的文件结构》](https://coolshell.cn/articles/9229.html)
================================================
FILE: src/md/java/jvm/part3/class-loading-mechanism.md
================================================
---
title: 类加载机制
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-07-20
category: JVM
tag:
- jvm
order: 7.3
---
# 类加载机制
## 类的生命周期
> 类的生命周期将会经历**加载** (Loading)、**验证**(Verification)、**准备**(Preparation)、**解析**(Resolution)、**初始化**(Initialization)、**使用**(Using)和**卸载**(Unloading)七个阶段
* 其中加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,按部就班地开始。
* 解析阶段不一定,某些情况下可以在初始化之后,以支持Java的运行时绑定(动态绑定)特性。

* 注:并非所有的类都会经历完整的生命周期,有些类可能在某些阶段就结束其在JVM中的生涯。
## Class实例何时被创建
在Java虚拟机(JVM)规范中,类的**加载**过程是由JVM自行决定的,但**初始化**过程则必须严格按照规范执行。
(在初始化之前,类的加载、验证、准备阶段必然已经完成)。
### 主动引用(六种情况)
> 在Java虚拟机规范中,“有且只有”以下六种情况会触发类的初始化,称为对一个类的**主动引用**:
1. 创建对象实例、访问静态字段(非常量)、调用静态方法
* 即遇到`new`、`getstatic`、`putstatic`或`invokestatic`这 4 种字节码指令。
2. **反射调用** 使用`java.lang.reflect`对类型(类和接口)进行反射调用
3. 初始化子类时,先初始化父类
4. **启动主类**,JVM启动时,先初始化包含`main()`方法的主类。
5. **JDK 1.7的动态语言支持**
* [周志明-解析 JDK 7 的动态类型语言支持](https://www.infoq.cn/article/jdk-dynamically-typed-language/)
* JDK 1.7 引入`java.lang.invoke.MethodHandle` 动态语言支持,解析静态字段/方法或构造方法时
* 即解析的句柄属于`REF_getStatic`、`REF_putStatic`、`REF_invokeStatic`或`REF_newInvokeSpecial`四种情况时,会触发目标类初始化。
6. **接口默认方法**初始化规则
* JDK 1.8 新增`default`默认方法,若实现类初始化,则需要先初始化该接口。
### 被动引用(不触发)
> 除了以上六种场景外,所有其他引用类的方式都不会触发初始化,称为**被动引用**。
* **例1:通过子类引用父类的静态字段,不会导致子类初始化,只有父类会被初始化**
* 子类是否加载和验证,取决于虚拟机的具体实现。
* 在HotSpot虚拟机(JDK 1.8 亲测)中,使用`-XX:+TraceClassLoading`观察到此操作会导致子类加载。
* **例2:通过数组定义来引用类,不会触发此类的初始化**
* 例如,`MyClass[] sca = new MyClass[10];`,不会初始化`MyClass`类
* 但这段代码触发了另一个名为`[L包名.MyClass`的类的初始化阶段。它是由虚拟机自动生成的、继承自`java.lang.Object`
的子类,由字节码指令`newarray`触发。这个类表示`MyClass`的一维数组,包含数组应有的属性和方法(如`public`的`length`
属性和`clone()`方法)。
* Java语言对数组的访问比C/C++更安全,因为这个类包装了数组元素的访问,C/C++中直接翻译为对数组指针的移动。
在Java语言里,发生数组越界时会抛出`java.lang.ArrayIndexOutOfBoundsException`异常,避免非法内存访问。
* **例3:引用常量不会触发定义常量的类的初始化**
* 因为常量在编译阶段就会被存入调用类的常量池中。
### 接口和类“加载与初始化”的差异
**相同点**
* 类 & 接口都经历:加载 → 验证 → 准备 → 解析 → **初始化(`()`)**
**差异**
* 接口不能使用`static{}`语句块,但编译器仍会生成`()`方法用于初始化静态变量。
* 类在初始化时,其父类必须先初始化。
* 接口在初始化时不要求父接口初始化,只有在真正使用到父接口的常量等成员时才会触发其初始化。
## 类加载的过程
Java类加载过程主要分为**加载**、**连接**(验证、准备、解析)、**初始化**三个阶段。
### 加载(Loading)
> 1. 通过类的全限定名**读取类的二进制字节流**。
> 2. 并将字节流所代表的 静态存储结构转化为**方法区**的运行时数据结构。
> 3. 在**堆**内存中生成`java.lang.Class`对象,作为方法区这个类的各种数据的的访问入口。
* **类文件来源:**
* 通常包括本地文件系统、压缩文件(如JAR、WAR)、网络、数据库、加密文件(防止反编译)、运行时动态生成,以及由其他文件生成(如JSP应用生成的Class文件)。
* **数组类加载:**
* 数组类本身不通过类加载器创建,而是由Java虚拟机直接在内存中构建出来。但数组类的元素类型最终还是靠类加载器来完成加载。
### 连接(Linking)
分为三个阶段:
* **验证**(Verify):确保字节码符合虚拟机要求
* **准备**(Prepare):为字段赋予初始值
* **解析**(Resolve):符号引用转换为直接引用
> **验证**(Verify)是连接阶段的第一阶段,目的是确保字节码的正确性和安全性,包括文件格式验证、元数据验证、字节码验证和符号引用验证四个阶段。
验证阶段大致上会完成下面四个阶段的检验动作:
1. **文件格式验证:** 检查字节流是否符合Class文件的格式规范。
2. **元数据验证:** 对类的元数据信息进行语义校验,确保其符合Java语言的语法和语意规则。
3. **字节码验证:** 通过数据流分析和控制流分析,确保字节码指令的合法性和逻辑正确性。
4. **符号引用验证:** 在解析阶段之前,检查符号引用是否能被正确解析。

> **准备**(Prepare)阶段为类中的静态变量分配内存并设置初始值。
静态变量的内存分配发生在**方法区**中。
* 在 JDK 7及之前,HotSpot使用**永久代**(PerGen)来实现方法区,存放在堆内存中。
* 在 JDK 8及之后,永久代被移除,取而代之的是**元空间**(Metaspace),存放在操作系统本地内存中。
> **解析**(Prepare)阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程。
* **符号引用(Symbolic References):** 字符串形式表示的对目标的逻辑引用
* **直接引用(Direct References):** 是直接定位到目标内存位置的指针、偏移量或句柄。
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这 7 类符号引用进行。
### 初始化(Initialization)
> 类加载过程的最后一个阶段,负责执行类构造器方法 ``。
* **自动生成:** 由javac编译自动生成``,是所有静态变量赋值和静态代码块的集合。
* **非法前向引用:** 静态语句块中只能访问定义在其之前的变量。
* 父类的 `` 方法会先于子类的 `` 方法执行。
* 如果一个类没有静态语句块和静态变量的赋值操作,那么编译器可能不会生成 `` 方法。
* `+TraceClassLoading` 查看类加载过程
* 多线程环境下,类的初始化可能会出现并发问题,JVM会保证``方法的线程安全执行。
## 类加载器
> 加载阶段“通过一个类的全限定名来获取描述该类的二进制字节流”,这个动作的代码被称为“类加载器”(Class Loader)。
### 类与类加载器
一个类在JVM中由其完全限定名和对应的类加载器共同确定唯一性。
即使两个类具有相同的完全限定名,由不同的类加载器加载的,JVM也会将它们视为两个“不相等”的类。
* “不相等” 包括Class对象的`equals`方法,`isAssignableFrom()`方法,`isInstance()`方法的返回结果。
**不同的类加载器对instanceof关键字运算的结果的影响**
```java
/**
* 类加载器与instanceof关键字演示
*
* @author zzm
*/
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
// 创建一个自定义的类加载器
ClassLoader myLoader = new ClassLoader() {
@Override
public Class> loadClass(String name) throws ClassNotFoundException {
try {
// 获取类名对应的文件名(去除包名,只保留类名)
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
// 从当前类路径中加载Class文件
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
// 如果找不到文件,则调用父类加载器加载
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
Object obj = myLoader.loadClass("org.fenixsoft.classloading.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof org.fenixsoft.classloading.ClassLoaderTest);
}
}
```
运行结果:
```shell
class org.fenixsoft.classloading.ClassLoaderTest
false
```
### 双亲委派模型
双亲委派模型是Java类加载机制的一种设计模式 ,用于确保类的唯一性和安全性。
它规定,类加载器在接收到类加载请求时,首先将该请求委派给父类加载器处理。
如果父类加载器无法完成加载,子类加载器才会尝试加载该类。

* 从Java虚拟机的角度,类加载器分为两类:
* 启动类加载器(Bootstrap ClassLoader):这是Java虚拟机的一部分,使用`C++`实现,负责加载核心类库,如`rt.jar`中的类。这个类加载器不可被Java程序直接引用。
* 其他类加载器:这些是由Java实现的类加载器,继承自`java.lang.ClassLoader`,并且独立存在于虚拟机之外。
* 从Java开发人员的角度,可以细分为三层类加载器:
* **启动类加载器**(Bootstrap ClassLoader):加载`\lib`目录中的核心类库。
* **扩展类加载器**(Extension ClassLoader):加载`\lib\ext`目录或通过java.ext.dirs指定路径中的扩展类库。
* **应用程序类加载器**(Application ClassLoader):加载用户类路径(ClassPath)上的所有类库,通常是程序的默认类加载器。
**双亲委派模型的实现**
```java
protected synchronized Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 首先,检查请求的类是否已经被加载过了
Class> c = findLoadedClass(name);
if (c == null) { // 如果该类还未被加载
try {
if (parent != null) { // 如果存在父类加载器
c = parent.loadClass(name, false); // 让父类加载器尝试加载该类
} else {
c = findBootstrapClassOrNull(name); // 如果没有父类加载器,尝试用引导类加载器加载
}
} catch (ClassNotFoundException e) {
// 如果父类加载器抛出 ClassNotFoundException
// 说明父类加载器无法完成加载请求,继续在本加载器中寻找
}
if (c == null) {
// 在父类加载器无法加载时
// 调用本加载器的 findClass 方法来加载类
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c; // 返回加载的类
}
```
### 破坏双亲委派模型
双亲委派模型并不是强制性约束的模型,直到Java模块化出现为止,出现过3次较大规模“被破坏”的情况。
**第 1 次被破坏:**
在JDK 1.2之前,由于没有双亲委派模型的约束,开发者可以直接覆盖 `loadClass()` 方法。
为了兼容现有代码并引导开发者遵循双亲委派模型,JDK 1.2引入了 `findClass()`
方法,建议开发者重写该方法而非直接覆盖 `loadClass()`。
* **loadClass():** 双亲委派模型的实现,父类加载异常时,调用 `findClass()` 方法进行加载。
* **findClass():** 自定义类加载器具体实现。
---
**第 2 次被破坏:**
由于基础类型有时需调用用户代码,如JNDI(Java命名和目录接口)加载SPI(服务提供者接口)代码,这打破了双亲委派模型的层次结构来逆向使用类加载器。
* Java引入一个不太优雅的设计:线程上下文类加载器,来实现SPI加载。当SPI的服务提供者多于一个的时候,代码就只能根据具体提供者的类型来硬编码判断。
* 为了消除这种极不优雅的实现方式,在JDK 6时,引入了 `java.util.ServiceLoader`,以 `META-INF/services`
中的配置信息,辅以责任链模式,提供了更合理的SPI加载方式。
---
**第 3 次被破坏:**
由于用户对程序动态性的追求,如代码热替换(Hot Swap)和模块热部署(Hot Deployment),导致双亲委派模型在OSGi中再次“被破坏”。
这种“动态性”在大型系统或企业级软件中尤其重要,因为频繁重启生产系统可能会被视为生产事故。
* **OSGi中的类加载器机制:**
* 每个OSGi模块(Bundle)都有一个独立的类加载器。更换 Bundle 时,同时替换其类加载器,实现代码热替换。
* **类加载顺序:**
* 以 `java.*` 开头的类由父类加载器加载。
* 委派列表内的类由父类加载器加载。
* Import 列表中的类由 Export 该类的 Bundle 加载器加载。
* 当前 Bundle 的 ClassPath 中的类由自己的类加载器加载。
* Fragment Bundle 中的类由对应的类加载器加载。
* Dynamic Import 列表中的类由对应 Bundle 的类加载器加载。
* 若以上均失败,则类加载失败。
================================================
FILE: src/md/java/kotlin/kotlin-quick-for-java.md
================================================
---
title: Java 程序员快速掌握 Kotlin
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2025-09-21
category: Kotlin
tags:
- Kotlin
- Java
---
# Java 程序员快速掌握 Kotlin
## 数据类型
下面是 Java 和 Kotlin 数据类型的对比与 Kotlin 示例讲解:
**1️⃣ 基本类型(Primitive Types)**
- **Java**:有原生类型(`int`、`double`)和包装类型(`Integer`、`Double`)。
- **Kotlin**:所有基本类型都是对象,没有 primitive/wrapper 区分。
```kotlin
val a: Int = 10 // 显式声明
val b = 20 // 类型推断为 Int
val pi = 3.14 // 类型推断为 Double
val flag: Boolean = true
val ch: Char = 'A'
println("Int: $a, Double: $pi, Boolean: $flag, Char: $ch")
```
---
**2️⃣ 空安全(Null Safety)**
- **Java**:引用类型可为 `null`,容易 NPE。
- **Kotlin**:默认不可空,可空类型用 `?` 声明。
```kotlin
var str: String = "Hello" // 非空
// str = null // ❌ 编译错误
var str2: String? = null // 可空
println(str2?.length ?: 0) // 安全调用 + Elvis 操作符
```
扩展:
- `?.` 安全调用操作符,避免 NPE。
- `?:` Elvis 操作符,当左侧为 null 时使用右侧默认值。
- `!!` 非空断言,可能抛 NPE,不推荐随意使用。
---
**3️⃣ 字符串(String)**
- **Java**:用 `+` 拼接字符串。
- **Kotlin** 支持字符串模板 `$variable` 和多行字符串 `""" ... """`
```kotlin
val name = "Tom"
val msg = "Hello, $name"
val multiLine = """
多行字符串示例:
第一行
第二行
""".trimIndent()
println(msg)
println(multiLine)
```
---
**4️⃣ 数组(Array)**
- **Java**:`int[] arr = {1, 2, 3};`
- **Kotlin**:统一用 `Array`,也有专门的基本类型数组:`IntArray`, `DoubleArray`。
```kotlin
val arr: Array = arrayOf(1, 2, 3)
val arr2: IntArray = intArrayOf(4, 5, 6)
```
---
**5️⃣ 集合(Collections)**
- **Java**:默认可变集合。
- **Kotlin**:区分 **只读** 和 **可变** 集合。
- `List`、`Set`、`Map` → 只读
- `MutableList`、`MutableSet`、`MutableMap` → 可变
```kotlin
val list: List = listOf("a", "b")
val mList: MutableList = mutableListOf("x", "y")
mList.add("z")
```
---
**6️⃣ 类型检查与智能转换(Smart Cast)**
- **Java**:需要 `(String)obj` 强制转换。
- **Kotlin**:用 `is` 判断类型后,编译器自动推断类型。
```kotlin
val obj: Any = "I am a String"
if (obj is String) {
println("长度: ${obj.length}") // 自动转换为 String
}
```
---
**7️⃣ 类型转换**
- **Java**:自动装箱/拆箱。
- **Kotlin** 不会自动扩大或缩小数字类型,需要显式 `toXXX()`。
```kotlin
val x: Int = 100
val y: Long = x.toLong() // 必须调用
```
---
**8️⃣ 特殊类型**
- **Any**:类似 Java 的 `Object`,所有非空类型的超类
- **Unit**:类似 Java 的 `void`,但是真正的类型
- **Nothing**:表示“不会有值”,用于抛异常或死循环
```kotlin
fun log(msg: String): Unit { println(msg) }
fun fail(message: String): Nothing { throw IllegalArgumentException(message) }
```
---
**9️⃣ 枚举(Enum)**
- **Java**:`enum`。
- **Kotlin**:`enum class`,功能类似,但可结合 `when` 表达式使用更优雅。
```kotlin
enum class Direction { NORTH, SOUTH, WEST, EAST }
fun move(dir: Direction) = when(dir) {
Direction.NORTH -> "向北走"
Direction.SOUTH -> "向南走"
Direction.WEST -> "向西走"
Direction.EAST -> "向东走"
}
println(move(Direction.NORTH))
```
---
**Java ↔ Kotlin 数据类型速查表**
| Java | Kotlin |
| ------- | ------- |
| int | Int |
| long | Long |
| double | Double |
| float | Float |
| short | Short |
| byte | Byte |
| char | Char |
| boolean | Boolean |
| String | String |
| Object | Any |
| void | Unit |
**参考:**
- [代码笔记:Kotlin 数据类型对比示例(Java 程序员专用)](https://pl.kotl.in/28omCucvR?theme=darcula)
- [Kotlin 官方文档-基本类型](https://kotlinlang.org/docs/basic-types.html)
- [Kotlin 与 Java 互操作性](https://kotlinlang.org/docs/java-interop.html)
## 条件与循环控制语句
下面是 Java 和 Kotlin 条件控制语句的对比与 Kotlin 示例讲解:
**1️⃣ if 表达式**
- Java 和 Kotlin 都有 if,但 Kotlin 的 if 是表达式,可以有返回值。
```kotlin
val a = 10
val b = 20
val max = if (a > b) a else b
println("较大值: $max")
```
---
**2️⃣ when 表达式(替代 switch)**
- Kotlin 用 `when` 替代 Java 的 `switch`,更强大灵活。
```kotlin
fun describe(obj: Any): String =
when (obj) {
in 1..10 -> obj.toString() // 1~10 返回本身
"Hello" -> "字符串 Hello"
is Long -> "Long 类型"
!is String -> "不是字符串"
else -> "未知"
}
println(describe(1))
println(describe("Hello"))
println(describe(100L))
println(describe(3.14))
```
---
**3️⃣ for 循环**
- Kotlin 没有传统的 `for(;;)` 循环,常用区间和集合遍历。
```kotlin
for (i in 1..5) {
print("$i ")
}
val list = listOf("a", "b", "c")
for (item in list) {
println(item)
}
```
---
**4️⃣ while 和 do...while**
- 用法与 Java 基本一致。
```kotlin
var x = 5
while (x > 0) {
print("$x ")
x--
}
```
---
**总结表:**
| Java | Kotlin |
| ---------- | ---------- |
| if/else | if/else |
| switch | when |
| for(;;) | for-in |
| while | while |
| do...while | do...while |
Kotlin 的条件控制语句更简洁、表达力更强,推荐多用表达式风格。
## In和区间
Kotlin 的 `in` 关键字和区间(Range)用于判断元素是否在某个范围内,常用于条件判断和循环。
**1️⃣ 区间的创建与遍历**
- 使用 `..` 创建闭区间(包含两端)。
- `in` 判断元素是否在区间内。
```kotlin
val range = 1..5 // 1,2,3,4,5
for (i in range) {
print("$i ")
}
println(3 in range) // true
println(10 in range) // false
```
---
**2️⃣ 降序区间与步长**
- `downTo` 创建降序区间,`step` 设置步长。
```kotlin
for (i in 5 downTo 1 step 2) {
print("$i ") // 输出: 5 3 1
}
```
---
**3️⃣ until 创建半开区间**
- `until` 创建左闭右开区间(不包含右端)。
```kotlin
for (i in 1 until 5) {
print("$i ") // 输出: 1 2 3 4
}
```
---
**4️⃣ 字符和集合也支持 in**
- `in` 可用于判断字符、集合等是否包含某元素。
```kotlin
val chars = 'a'..'z'
println('c' in chars) // true
val list = listOf("a", "b", "c")
println("a" in list) // true
```
---
**总结:**
- `in` 用于区间、集合的包含判断。
- 区间常用于循环、条件判断,语法简洁。
## 循环控制
Kotlin 的循环控制语句主要包括 `break`、`continue` 和带标签的跳转,功能比 Java 更灵活。
**1️⃣ break 和 continue**
- `break`:跳出最近一层循环。
- `continue`:跳过本次循环,进入下一次。
```kotlin
for (i in 1..5) {
if (i == 3) break
print("$i ") // 输出: 1 2
}
for (i in 1..5) {
if (i % 2 == 0) continue
print("$i ") // 输出: 1 3 5
}
```
**2️⃣ 带标签的 break/continue**
- Kotlin 支持为循环加标签,配合 `break`/`continue` 跳出多层循环。
```kotlin
tag@ for (i in 1..3) {
for (j in 1..3) {
if (i == 2 && j == 2) break@tag
print("($i,$j) ")
}
}
// 输出: (1,1) (1,2) (1,3) (2,1)
```
**3️⃣ return 用于 Lambda**
- 在 Lambda 表达式中,`return` 默认跳出整个函数。
- 用标签 `return@label` 只跳出当前 Lambda。
```kotlin
listOf(1, 2, 3, 4).forEach {
if (it == 3) return@forEach
print("$it ") // 输出: 1 2 4
}
```
---
**总结:**
- `break`/`continue` 用于控制循环流程。
- 标签让多层循环跳转更灵活。
- Lambda 中用 `return@label` 精确控制跳出范围。
## 函数
Kotlin 的函数(Function)语法简洁,支持多种声明和调用方式,适合 Java 程序员快速上手。
**1️⃣ 基本函数声明与调用**
- 使用 `fun` 关键字定义函数,参数类型在后,返回值类型用 `: 类型` 指定。
```kotlin
fun sum(a: Int, b: Int): Int {
return a + b
}
println(sum(3, 5)) // 输出: 8
```
---
**2️⃣ 表达式函数体**
- 如果函数体只有一行,可以用 `=` 简化。
```kotlin
fun max(a: Int, b: Int) = if (a > b) a else b
println(max(4, 7)) // 输出: 7
```
---
**3️⃣ 默认参数与命名参数**
- 参数可设置默认值,调用时可按名称传参。
```kotlin
fun greet(name: String = "World") {
println("Hello, $name")
}
greet() // 输出: Hello, World
greet("Kotlin") // 输出: Hello, Kotlin
```
---
**4️⃣ 可变参数(vararg)**
- 用 `vararg` 声明可变参数,类似 Java 的 `...`。
```kotlin
fun printAll(vararg items: String) {
for (item in items) println(item)
}
printAll("A", "B", "C")
```
---
**5️⃣ 单表达式返回 Unit 可省略**
- 没有返回值时,`Unit` 可省略。
```kotlin
fun log(msg: String) = println(msg)
```
---
**6️⃣ Lambda 表达式**
- 支持匿名函数和 Lambda,常用于集合操作。
```kotlin
val list = listOf(1, 2, 3)
list.forEach { println(it) }
```
---
**总结表:**
| Java | Kotlin |
|---------------------|----------------------|
| public int sum(int) | fun sum(a: Int): Int |
| void func() | fun func() |
| 可变参数 ... | vararg |
| 默认参数无 | 默认参数/命名参数 |
Kotlin 函数语法更灵活,推荐多用表达式和默认参数风格。
## 类和对象
Kotlin 文件支持声明多个类、接口、函数等。每个 Kotlin 源文件可以包含任意数量的类和顶层函数,不要求一个文件只能有一个类,也不要求类名与文件名一致。这比 Java 更灵活。
**1️⃣ 类的声明与构造函数**
- 用 `class` 关键字声明类,主构造函数直接写在类名后。
```kotlin
// 带主构造函数的类
class Person(val name: String, var age: Int)
val p = Person("Tom", 20)
println("${p.name}, ${p.age}")
```
---
**2️⃣ 属性与方法**
- 属性用 `val`(只读)或 `var`(可变)声明,方法用 `fun`。
```kotlin
class Dog(var name: String) {
fun bark() = println("$name: 汪汪!")
}
val d = Dog("旺财")
d.bark()
```
---
**3️⃣ 继承与重写**
- 类默认 `final`,要继承需加 `open`。重写方法用 `override`。
```kotlin
open class Animal(open val name: String) {
open fun sound() = println("动物叫声")
}
class Cat(override val name: String) : Animal(name) {
override fun sound() = println("$name: 喵喵!")
}
val c = Cat("小花")
c.sound()
```
---
**4️⃣ 数据类(data class)**
- 自动生成 `equals`、`hashCode`、`toString` 等,适合存储数据。
- 使用 `val`(只读属性)会自动生成的 `getter`
- 使用 `var`(可变属性)会自动生成的 `getter`和`setter`
```kotlin
data class User(val id: Int, val name: String)
val u = User(1, "Alice")
println(u)
```
---
**5️⃣ 单例对象(object)**
- 用 `object` 声明单例。
- **本质上是“饿汉式单例”**,但带有 **延迟初始化特性**
- Kotlin 编译器会在背后生成静态字段和 `` 方法来保证初始化是线程安全的。
```kotlin
object Counter {
var count = 0
fun inc() = ++count
}
Counter.inc()
println(Counter.count)
```
---
**6️⃣ 伴生对象(companion object)**
- 类的静态成员用 `companion object`。
```kotlin
class Utils {
companion object {
fun hello() = println("Hello from Utils")
}
}
Utils.hello()
```
---
**总结表:**
| Java | Kotlin |
|---------------------|----------------------------|
| class | class |
| extends | : 父类 |
| final | 默认 final,需 open 才能继承 |
| static | companion object |
| 单例 | object |
| 数据类无 | data class |
Kotlin 类和对象语法更简洁,支持数据类、单例、伴生对象等现代特性。
## 访问和属性修饰符
Kotlin 的访问和属性修饰符与 Java 类似,但更简洁灵活。常用修饰符如下:
**1️⃣ 访问修饰符(Visibility Modifiers)**
- `public`:默认,任何地方可见。
- `internal`:同一模块内可见(Kotlin 独有)。
- `protected`:子类可见(仅用于类成员)。
- `private`:当前类/文件可见。
```kotlin
class Foo {
private val a = 1 // 仅 Foo 内可见
protected val b = 2 // Foo 及子类可见
internal val c = 3 // 同模块可见
val d = 4 // public,任何地方可见
}
```
---
**2️⃣ 属性修饰符**
- `val`:只读属性(类似 Java 的 final)。
- `var`:可变属性。
```kotlin
class Person(val name: String, var age: Int)
```
---
**3️⃣ 其他常用修饰符**
- `open`:允许被继承或重写(Kotlin 类默认 final)。
- `final`:禁止继承或重写(默认)。
- `abstract`:抽象类或方法,不能实例化。
- `override`:重写父类成员。
- `lateinit`:延迟初始化(仅用于 var,非基本类型)。
- `const`:编译时常量(只能用于顶层或 object 的 val)。
```kotlin
open class Animal
abstract class Shape { abstract fun draw() }
class Cat : Animal()
```
---
**总结表:**
| Java | Kotlin | 说明 |
|--------------|---------------|------------------------|
| public | public | 公开 |
| protected | protected | 受保护 |
| private | private | 私有 |
| default | internal | 同包/同模块可见 |
| final | final(默认) | 默认不可继承 |
| abstract | abstract | 抽象 |
| static | companion obj | 伴生对象实现静态成员 |
| final field | val | 只读属性 |
Kotlin 的修饰符更细致,`internal` 和 `open` 是常用的 Kotlin 特有特性。
## 类的继承与重写
Kotlin 的类继承与重写比 Java 更安全简洁,默认所有类和方法都是 final,需显式声明 `open` 才能被继承或重写。
**1️⃣ 基本继承语法**
- 用 `open` 修饰父类和可重写方法。
- 子类用 `:` 继承父类,重写用 `override`。
```kotlin
open class Animal(open val name: String) {
open fun sound() = println("动物叫声")
}
class Dog(override val name: String) : Animal(name) {
override fun sound() = println("$name: 汪汪!")
}
val d = Dog("旺财")
d.sound() // 输出: 旺财: 汪汪!
```
---
**2️⃣ 构造函数与属性重写**
- 子类主构造函数需调用父类构造函数。
- 可以重写父类的 `val`/`var` 属性。
```kotlin
open class Person(open val name: String)
class Student(override val name: String, val grade: Int) : Person(name)
```
---
**3️⃣ 方法重写与 super**
- 用 `override` 重写方法,`super` 调用父类实现。
```kotlin
open class Parent {
open fun hello() = println("Parent hello")
}
class Child : Parent() {
override fun hello() {
super.hello()
println("Child hello")
}
}
```
---
**4️⃣ 抽象类与方法**
- 用 `abstract` 声明抽象类/方法,子类必须实现。
```kotlin
abstract class Shape {
abstract fun area(): Double
}
class Circle(val r: Double) : Shape() {
override fun area() = Math.PI * r * r
}
```
---
**总结表:**
| Java | Kotlin |
| --------- | --------------- |
| extends | : 父类 |
| @Override | override |
| abstract | abstract |
| super | super |
| final | 默认 final,需 open |
Kotlin 继承机制更安全,需显式声明可继承/可重写,推荐优先使用组合而非继承。
## 抽象,嵌套和内部类
Kotlin 的抽象类、嵌套类和内部类用法简洁,和 Java 有相似也有不同之处。
**1️⃣ 抽象类与抽象方法**
- 用 `abstract` 修饰,不能实例化。
- 抽象方法无方法体,子类必须实现。
```kotlin
abstract class Animal {
abstract fun sound()
fun sleep() = println("睡觉")
}
class Dog : Animal() {
override fun sound() = println("汪汪!")
}
val d = Dog()
d.sound() // 输出: 汪汪!
d.sleep() // 输出: 睡觉
```
---
**2️⃣ 嵌套类(Nested Class)**
- 默认静态(类似 Java 的 static class),不能访问外部类成员。
```kotlin
class Outer {
val x = 1
class Nested {
fun foo() = 2
}
}
val n = Outer.Nested().foo() // 2
```
不能访问外部类成员
**3️⃣ 内部类(Inner Class)**
- 用 `inner` 修饰,可访问外部类成员。
```kotlin
class Outer {
val x = 10
inner class Inner {
fun foo() = x
}
}
val o = Outer()
val i = o.Inner()
println(i.foo()) // 输出: 10
```
---
**4️⃣ 匿名内部类**
- 用 `object : 接口/父类 {}` 创建匿名类实例。
```kotlin
interface Clickable {
fun click()
}
val btn = object : Clickable {
override fun click() = println("Clicked!")
}
btn.click()
```
---
**总结表:**
| Java | Kotlin | 说明 |
|---------------------|----------------------|------------------------|
| abstract class | abstract class | 抽象类 |
| static class | class Nested | 嵌套类,默认静态 |
| inner class | inner class | 内部类,访问外部成员 |
| 匿名内部类 | object : ... {} | 匿名对象/类 |
Kotlin 嵌套类默认静态,内部类需 `inner`,匿名类用 `object`。抽象类和方法用法与 Java 类似。
## 接口与接口实现
Kotlin 的接口(interface)用法与 Java 类似,但更灵活,支持默认实现、多接口继承等。
**1️⃣ 接口声明与实现**
- 用 `interface` 关键字声明接口,方法可有默认实现。
```kotlin
interface Clickable {
fun click()
fun showOff() = println("I'm clickable!")
}
```
---
**2️⃣ 类实现接口**
- 用 `:` 实现接口,必须实现未提供默认实现的方法。
```kotlin
class Button : Clickable {
override fun click() = println("Button clicked")
}
val btn = Button()
btn.click() // 输出: Button clicked
btn.showOff() // 输出: I'm clickable!
```
---
**3️⃣ 多接口实现与冲突解决**
- 支持多接口实现,若有同名方法需显式指定实现。
```kotlin
interface Focusable {
fun showOff() = println("I'm focusable!")
}
class MyButton : Clickable, Focusable {
override fun click() = println("MyButton clicked")
override fun showOff() {
super.showOff()
super.showOff()
}
}
```
---
**4️⃣ 接口属性**
- 接口可声明属性,但不能有状态,只能有 `getter`。
```kotlin
interface Named {
val name: String
}
class Person(override val name: String) : Named
```
---
**总结表:**
| Java | Kotlin | 说明 |
| ---------- | --------- | -------- |
| interface | interface | 接口声明 |
| implements | : 接口 | 实现接口 |
| default | 默认实现 | 支持方法默认实现 |
| 多接口 | 多接口 | 支持多接口继承 |
Kotlin 接口支持默认实现、多继承,语法更简洁灵活。
## 数据类,伴生类,枚举类
**1️⃣ 数据类(data class)**
- 用 `data class` 声明,自动生成 `equals`、`hashCode`、`toString`、`copy` 等方法,适合只用于存储数据的类。
- 主构造函数参数需用 `val` 或 `var` 修饰。
```kotlin
data class User(val id: Int, val name: String)
val u1 = User(1, "Alice")
val u2 = u1.copy(name = "Bob")
println(u1) // User(id=1, name=Alice)
println(u2) // User(id=1, name=Bob)
println(u1 == u2) // false
```
**特点:**
- 自动实现结构相等(\=\=)、解构、复制等。
- 常用于 DTO、VO、简单实体。
---
**2️⃣ 伴生对象(companion object)**
- Kotlin 没有 static 关键字,静态成员用 `companion object` 实现。
- 伴生对象内的方法和属性可通过类名直接访问。
```kotlin
class Utils {
companion object {
const val VERSION = "1.0"
fun hello() = println("Hello from Utils")
}
}
Utils.hello() // 直接通过类名调用
println(Utils.VERSION) // 访问常量
```
**特点:**
- 每个类最多一个伴生对象。
- 伴生对象本身是对象,可以实现接口。
---
**3️⃣ 枚举类(enum class)**
- 用 `enum class` 声明,类似 Java 的枚举。
- 可为每个枚举常量定义属性和方法。
```kotlin
enum class Direction(val desc: String) {
NORTH("北"), SOUTH("南"), WEST("西"), EAST("东");
fun show() = println("方向: $desc")
}
val d = Direction.NORTH
println(d.name) // NORTH
println(d.desc) // 北
d.show() // 方向: 北
```
**特点:**
- 可结合 `when` 表达式优雅分支。
- 枚举常量可带构造参数和方法。
---
**总结表:**
| Java | Kotlin | 说明 |
|--------------|----------------------|------------------------|
| POJO/VO | data class | 数据类,自动方法 |
| static | companion object | 伴生对象实现静态成员 |
| enum | enum class | 枚举类 |
Kotlin 语法更简洁,数据类、伴生对象、枚举类常用于简化业务模型和工具类开发。
## 单例和对象表达式
Kotlin 的单例和对象表达式用法非常简洁,适合替代 Java 的 static 工具类、匿名内部类等场景。
**1️⃣ 单例对象(object)**
- 用 `object` 关键字声明单例,线程安全,懒加载。
- 适合全局唯一实例、工具类、管理器等。
```kotlin
object Counter {
var count = 0
fun inc() = ++count
}
Counter.inc()
println(Counter.count) // 输出: 1
```
**特点:**
- 编译器自动保证线程安全和唯一性。
- 不能有构造函数。
---
**2️⃣ 伴生对象(companion object)**
- 用于类的“静态成员”,每个类最多一个伴生对象。
- 伴生对象成员可通过类名直接访问。
```kotlin
class Utils {
companion object {
const val VERSION = "1.0"
fun hello() = println("Hello from Utils")
}
}
Utils.hello()
println(Utils.VERSION)
```
---
**3️⃣ 对象表达式(object expression)**
- 用 `object : 父类/接口 {}` 创建匿名对象,类似 Java 匿名内部类。
- 常用于临时实现接口、回调等。
```kotlin
interface Clickable {
fun click()
}
val btn = object : Clickable {
override fun click() = println("Clicked!")
}
btn.click()
```
---
**总结表:**
| Java | Kotlin | 说明 |
| ---------- | ----------------- | -------- |
| static 工具类 | object | 单例对象 |
| static 成员 | companion object | 伴生对象 |
| 匿名内部类 | object expression | 匿名对象/表达式 |
Kotlin 单例和对象表达式让代码更简洁,推荐多用 object 替代 static/匿名类。
## 密封类和密封接口
Kotlin 的**密封类**(sealed class)和**密封接口**(sealed interface)用于表达受限的继承层次,常用于表达有限集合的状态、结果等,配合 `when` 表达式更安全。
**1️⃣ 密封类(sealed class)**
- 用 `sealed class` 声明,所有子类必须在同一文件中定义。
- 适合表达有限状态、结果类型等。
- 配合 `when`,编译器能检查分支是否覆盖所有情况。
```kotlin
sealed class Result
data class Success(val data: String) : Result()
data class Error(val code: Int) : Result()
object Loading : Result()
fun handle(result: Result) = when (result) {
is Success -> println("成功: ${result.data}")
is Error -> println("错误: ${result.code}")
Loading -> println("加载中")
}
```
---
**2️⃣ 密封接口(sealed interface)**
- 用 `sealed interface` 声明,所有实现类也需在同一文件中。
- 适合需要多继承的受限类型体系。
```kotlin
sealed interface UiState
data class ShowText(val text: String) : UiState
object ShowLoading : UiState
object ShowError : UiState
```
---
**3️⃣ 特点与优势**
- 受限继承,所有子类/实现类可知,`when` 分支更安全。
- 可用于表达状态机、网络请求结果、UI 状态等。
---
**总结表:**
| Java | Kotlin | 说明 |
|---------------------|----------------------|------------------------|
| 无直接等价 | sealed class | 密封类,受限继承 |
| 无直接等价 | sealed interface | 密封接口,受限多继承 |
| enum | enum/sealed class | 枚举/密封类表达有限集合 |
Kotlin 密封类/接口让状态建模更安全,推荐用于有限状态、结果类型等场景。
## 扩展函数
扩展函数是 Kotlin 的一大特色,允许你为现有类添加新函数,而无需继承或修改原类,非常适合工具方法、DSL 等场景。下面是扩展函数的核心知识点和示例:
**1️⃣ 基本语法**
- 扩展函数的声明格式为: `fun 接收者类型.函数名(参数): 返回类型 { ... }`
- 扩展函数在调用时和普通成员函数调用一样,但它**不会真正改变类**。
```kotlin
// 为 String 添加扩展函数
fun String.hello(): String = "Hello, $this"
val msg = "Kotlin".hello() // Hello, Kotlin
println(msg)
```
---
**2️⃣ 扩展属性**
- 扩展不仅限于函数,也可以添加属性(只能有 getter,没有 backing field)。
- 扩展属性不能存储状态,只能通过现有数据计算得到值。
```kotlin
val String.lastChar: Char
get() = this[this.length - 1]
println("Kotlin".lastChar) // n
```
---
**3️⃣ 泛型扩展函数**
- 扩展函数支持泛型,让工具方法更通用。
```kotlin
fun List.second(): T = this[1]
println(listOf(1, 2, 3).second()) // 2
```
---
**4️⃣ 作用域与静态解析**
- 扩展函数**不会真正修改类**,调用取决于变量的**声明类型**。
- 可以定义在:
- 顶层函数(常用)
- 类内部(成员扩展函数)
- 伴生对象
```kotlin
open class Parent
class Child: Parent()
fun Parent.foo() = "Parent"
fun Child.foo() = "Child"
val p: Parent = Child()
println(p.foo()) // 输出: Parent,静态解析
```
---
**5️⃣ Java 互操作**
- 在 Java 中,Kotlin 扩展函数表现为 **静态方法**:
```kotlin
// Kotlin
fun String.hello() = "Hello, $this"
// Java 调用方式
String result = MyKotlinFileKt.hello("Kotlin");
```
---
**6️⃣ 常见应用:简化统一响应封装**
```kotlin
// 假设有一个 R 响应类
class R(val data: T, val success: Boolean) {
companion object {
fun success(data: T) = R(data, true)
}
}
```
- 为任意对象添加扩展函数,将其快速包装成 `R`:
```kotlin
// 为任意对象添加扩展函数,将其包装成 R
fun T.rs(): R = R.success(this)
val result = "Hello".rs()
println(result.success) // 输出: true
println(result.data) // 输出: Hello
```
- 这样就可以直接使用 `对象.rs()` 来快速生成统一响应,代码更简洁。
================================================
FILE: src/md/java/thread/thread-concurrency.md
================================================
---
title: Java多线程与并发编程
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2025-02-18
category: 并发编程
tag:
- JUC
---
# Java多线程与并发编程
> 你聊一聊多线程以及并发你是如何实现的,以往在实际工作中,遇到高并发的环境采用那些方案进行处理。
## 目标
- **基本概念 (Basic)**
- **多线程 (Thread)**
- [线程池 (Executor)](#线程池-executor)
- [并发容器 (Collections)](#并发容器-collections)
- [同步工具 (Tools)](#同步工具-tools)
- [原子对象 (Atomic)](#原子对象-atomic)

## 并发背后的故事
**什么是并发?**
并发就是指程序同时处理多个任务的能力。
并发编程的根源在于多任务情况下对资源访问的有效控制。
**你的程序在并发环境下一定是正确的吗?**
* [🐟代码小抄-并发统计App Store下载次数](https://codecopy.cn/post/c51tdh)
## 你必须知道的概念
**程序,进程与线程**
1. **程序**:程序是静态的概念,Windows下通常指`exe`文件。
2. **进程**:进程是动态的概念,当你双击一个程序,操作系统会把它加载到内存中运行,进程说明程序在内存中的边界。
3. **线程**:一个进程包含多个线程,线程是CPU调度的基本单位,每个线程都有自己的功能。
**并行与并发**
1. **🚗并行 (Parallelism)**:*多核CPU同时处理*
* **就像多车道高速公路:** 多辆车可以同时在不同的车道上行驶,互不干扰。
* **在计算机中:** 多个处理器或多核CPU同时执行多个任务,真正同时进行。
2. **🚚🚙🚗并发 (Concurrency)**:*单核CPU交替执行*
* **就像单车道上的多辆车:** 虽然只有一条车道,但通过交替行驶,看起来像是同时前进。
* **在计算机中:** 单个处理器通过快速切换任务,给人同时处理的错觉,实际是交替执行。
**同步和异步**
1. **⏳同步 (Sync)**:*按顺序执行,干等着*
* **就像银行柜台办理业务:** 你去银行取钱,需要排队,前面的人不办完,你不能办。
* **在编程中:** 代码按顺序执行,每个任务必须等前一个任务完成后才能执行下一个。
2. **🏃♂️异步 (Asyn)**:*不等了,先去忙别的,等有结果再回来处理*
* **就像去餐厅点餐:** 你点完餐后不用等着,先去刷手机,等饭做好了,服务员再叫你来取。
* **在编程中:** 代码遇到耗时操作(比如网络请求)时不会等,而是先去执行别的任务,等操作完成后再回来处理结果。
**临界区**
1. **🚧临时区:** *公共资源与共享数据,同一时间只能有一个线程访问*
* **就像公司的打印机**:公司只有一台打印机(临界区),多个员工(线程)需要打印文件,每次只能一个员工使用。
* **在编程中:** 临时区是指对共享资源访问的代码片段,通过互斥锁(Mutex),信号量(Semaphore),原子操作(Atomic Operations)来实现。
**死锁、饥饿、活锁**
1. **🛑死锁(Deadlock):🔒**
* **就像公路上堵车:** 谁也不让谁,大家都卡住
* **在编程中:** 两个或多个线程互相持有对方需要的资源,导致无法继续执行。
2. **🍽️饥饿 (Starvation):**
* **就像奶茶店排队:** 总有人插队,有些人一直没机会买到奶茶。
* **在编程中:** 线程本身是有执行优先级的,如果优先级低的线程一直无法获取到CPU执行权,就会一直处于等待状态。
3. **🔁活锁 (Livelock):**
* **就像礼让路口:** 你让我,我让你,谁也不走。
* **在编程中:** 是指多个线程(进程)互相礼让,不断进行状态调整,但始终无法完成任务。
**线程安全**
1. **线程安全 (Thread-Safe)** 理解: 多线程环境下,对共享资源的访问不会引起数据不一致。
2. **线程安全三大特性**
1. **🏗️原子性 (Atomicity)**:
* **就像银行转账:** 要么扣钱和加钱都成功,要么都不成功。
* **在编程中:** 一个或多个操作要么全部执行成功,要么全部不执行。
2. **👀可见性 (Visibility)**:
* **就像聊天室:** 你发的消息,其他人都能看到。
* **在编程中:** 一个线程对共享变量的修改,其他线程能立即看到。
3. **🔀有序性 (Ordering)**:程序执行的顺序按照代码的先后顺序执行。
* **就像排队买票:** 先来的先买票,后来的后买票。
* **在编程中:** 程序按照代码的先后顺序执行,不会乱序
通过`volatile`,`synchronized`,`Lock`来实现线程安全。
## Java内存模型(JMM)
[bilibili-磊哥-什么是Java内存模型(JMM)?](https://www.bilibili.com/video/BV1Cu4m1A7ha)
| **概念** | **作用** | **比喻** |
|--------------------------|---------------|-------------|
| **主内存(Main Memory)** | 线程共享的内存 | 🏢 共享数据库 |
| **工作内存(Working Memory)** | 线程私有的内存 | 📖 每个人的小笔记本 |
| **volatile** | 保证可见性、有序性 | 📢 共享公告 |
| **synchronized** | 保证原子性、可见性、有序性 | 🔒 互斥锁 |
| **happens-before 规则** | 规定线程间的执行顺序 | 📜 规则手册 |
## 创建线程的三种方式
* [🐟代码小抄-Java创建线程的三种方式](https://codecopy.cn/post/cq3kmh)
1. **继承Thread类**
1. **实现Runnable接口**
1. **实现Callable接口**
## Synchronized线程同步机制
* [🐟代码小抄-Synchronized 三种使用场景](https://codecopy.cn/post/bxd0nq),对应不同锁对象:
1. **synchronized 代码块(锁定任意对象)**
2. **synchronized 方法(锁定当前实例对象 this)**
3. **synchronized 静态方法(锁定类的字节码对象 Class)**
## Java线程的6种状态
* [🐟代码小抄-Java线程的6种状态](https://codecopy.cn/post/s6z4y5)
```mermaid
stateDiagram-v2
state "新建 (NEW)" as NEW
state "可运行 (RUNNABLE)" as RUNNABLE
state "阻塞 (BLOCKED)" as BLOCKED
state "等待 (WAITING)" as WAITING
state "计时等待 (TIMED_WAITING)" as TIMED_WAITING
state "终止 (TERMINATED)" as TERMINATED
[*] --> NEW : 线程创建
TERMINATED --> [*] : 线程结束
NEW --> RUNNABLE : 调用 start()
RUNNABLE --> BLOCKED : 进入同步锁 (synchronized)
BLOCKED --> RUNNABLE : 获取锁后恢复
RUNNABLE --> WAITING : 调用 wait()/ join() (无超时)
WAITING --> RUNNABLE : 调用 notify()/ notifyAll()
RUNNABLE --> TIMED_WAITING : 调用 sleep(time)/ wait(timeout)/ join(timeout)
TIMED_WAITING --> RUNNABLE : 超时/ 调用 notify()/ notifyAll()
RUNNABLE --> TERMINATED : run() 执行完毕
```
## 死锁的产生
1. [🐟代码小抄-Java死锁的案例](https://codecopy.cn/post/udd9hm)
2. **死锁产生的原因:**
* **说人话就是互掐资源:**
* 你要用空调遥控器才把电视遥控器还我,我要用电视遥控器才把空调遥控器还你。结果俩人干瞪眼,谁都动不了。
* **计算机中:**
* 在多线程对公共资源(文件、数据)等进行操作时,彼此不释放自己的资源,而去试图操作其他线程的资源,而形成交叉引用,就会产生死锁。
3. **死锁的四个必要条件**(同时满足就死锁):
* **互斥条件**(Mutual Exclusion):资源一次只能被一个线程占用,不能共享。
* **占有并等待条件**(Hold and Wait):线程拿着已有的资源不释放,但还想申请新资源。
* **不可抢占条件**(No Preemption):资源不能强行夺走,只能等占有者主动释放。
* **循环等待条件**(Circular Wait):线程之间形成环形依赖,每个线程都在等待下一个线程释放资源。
4. **解决死锁的建议:**
* 减少对公共资源的占用,或一次性申请所需的所有资源
* 用完资源及时释放,释放资源后再申请新资源
* 减少`synchornized`的使用,采用“副本”方式替代。
## 重新认识线程安全(ThreadSafe)
1. **线程安全定义**
* 在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过**同步机制**保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
2. **通过`synchornized`,`Lock`,`Atomic`等方式实现线程安全。**
3. **线程安全与不安全的区别**
* **线程安全:**
* 优点:可靠
* 缺点:执行速度慢
* 使用建议:在需要线程共享时使用
* **线程不安全:**
* 优点:速度快
* 缺点:可能与预期不符
* 使用建议:在线程内部使用,无需线程间共享时使用
## JUC并发工具
### 线程池(Executor)
1. **并发工具包:** (`java.util.concurrent`)
在 Java 5 之前,我们只能使用 `synchronized`、`wait`、`notify` 等低级 API 来管理线程,代码复杂且容易出错。**从 Java 5 开始**,JDK 提供了 **`java.util.concurrent`** 并发工具包,帮我们更方便地处理线程、任务调度、数据共享等问题。
2. **💡什么是线程池**(Thread Pool)
线程池(Thread Pool)是一种管理多个线程的技术,它通过**复用**已创建的线程来执行多个任务,避免了线程的**频繁创建和销毁**,从而提高系统性能。
* **优点:**
* 重用存在的线程,减少对象对象、消亡的开销
* 线程总数可控,提高资源的利用率
* 避免过多资源竞争,避免阻塞
* 提供额外功能,定时执行、定期执行、监控等。
3. [🐟代码小抄-Executors线程池的4种创建方式](https://codecopy.cn/post/rpat5a)
1. **可缓存线程池(`CachedThreadPool`)**
* 特点:线程数动态伸缩,适合大量短任务(如日志处理、网络请求)。
2. **定长线程池:`FixedThreadPool`**
* 特点:线程数固定,适合长期稳定任务。
3. **单线程池:`SingleThreadExecutor`**
* 特点:单线程按顺序执行,适合日志、数据库操作等顺序任务。
4. **调度线程池**:`ScheduledThreadPool`
* 特点:定时或周期性任务,适用于心跳检测、定时任务。
4. **✅推荐自定义 `ThreadPoolExecutor`(避免 OOM)**
* [bilibili-老齐-阿里为什么要禁用Java内置线程池?](https://www.bilibili.com/video/BV15m4y1o7gT)
### 同步工具 (Tools)
[🐟代码小抄-JUC同步工具实战:黑神话悟空开服场景](https://codecopy.cn/post/guuz4b),理解使用场景,掌握API
1. **CountDownLatch(倒计时锁)**
* 允许一个或多个线程等待**多个线程完成任务**后再继续执行。
* 适用于 **并发初始化多个资源** 或 **多个任务完成后统一汇总**。
2. **Semaphore(信号量)**
* **限制并发线程的数量**,控制资源的访问。
* 适用于 **限流**、**连接池** 等场景。
3. **CyclicBarrier(循环屏障)**
* 让多个线程**在某个时间点上同步执行**。
* 适用于**所有线程都到达后才能继续执行**的场景。
4. **ReentrantLock(可重入锁)**
* 重入锁是指任意线程在获取到锁之后,再次获取该锁而不会被该锁所阻塞。
* ReentrantLock设计的目标是用来替代synchronized关键字。
* [🐟代码小抄-ReentrantLock三大特性&扩展功能](https://codecopy.cn/post/ua7j1p)
**ReentrantLock 和 synchronized 的区别**
| 特征 | synchronized(==推荐==) | reentrantLock |
| ---- | -------------------- | ---------------------------- |
| 底层原理 | JVM实现 | JDK实现 |
| 性能区别 | 低->高(JDK5+) | 高 |
| 锁的释放 | 自动释放(编译器保证) | 手动释放(finally保证) |
| 编码程度 | 简单 | 复杂 |
| 锁的粒度 | 读写不区分 | 读锁、写锁 |
| 高级功能 | 无 | 公平锁、非公平锁唤醒Condition分组唤醒中断等待锁 |
5. **Condition(线程等待与唤醒)**
* `Condition` 是和 `ReentrantLock` 搭配使用的 **等待/通知** 机制。
* 设计的目标是用来替代传统的 `wait()`/`notify()`,但 **更灵活**,控制线程执行顺序!
* [🐟代码小抄-Condition结合ReentrantLock的应用场景](https://codecopy.cn/post/gkuf8f)
6. [bilibili-老齐-CompletableFuture多线程编排实践](https://www.bilibili.com/video/BV1YUp2eKEpa)
* [🐟代码小抄-CompletableFuture使用](https://codecopy.cn/post/4n2wwz)
* [🤖CatGPT-CompletableFuture 多线程编排实践](https://chatgpt.com/canvas/shared/67b198f0b0d081918e2f53b6acd74052)
1. **基本概念**: CompletableFuture 是 Java 8 引入的异步编程工具,扩展了 Future,支持链式回调、任务组合和异常处理。
* 既实现了 `Future` 接口,又实现了 `CompletionStage` 接口。
* 支持非阻塞式异步任务,提高系统吞吐量。
* 提供丰富的任务组合方式,如串行执行、并行执行、结果合并等。
* 提供异常处理机制,能够在任务失败时执行回退逻辑。
2. **异步任务创建**:`supplyAsync()` 有返回值,`runAsync()` 无返回值。
* `completedFuture()`:已完成,用于测试或默认值。
3. **串行执行**:`thenApply()` 转换结果,`thenAccept()` 只消费,`thenRun()` 只执行任务。
4. **并行组合**:`thenCombine()` 组合结果,`allOf()` 等待所有完成,`anyOf()` 选取最快的。
5. **异常处理**:`exceptionally()` 处理异常默认值,`handle()` 处理异常并可返回新值,`whenComplete()` 记录异常但不改变结果。
### **并发容器 (Collections)**
**面试题:线程安全的类**
| **类别** | **线程安全** | **线程不安全** |
| -------------- | --------------------------------- | ------------------------ |
| **List**(列表) | `Vector` | `ArrayList`、`LinkedList` |
| **Set**(集合) | ❌(默认都不安全) | `HashSet`、`TreeSet` |
| **Map**(映射) | `Hashtable` | `HashMap` |
| **Properties** | `Properties`(继承 `Hashtable`,线程安全) | ❌ |
| **String** | `StringBuffer` | `StringBuilder` |
**JUC下线程安全的并发容器**
* `ArrayList` -> CopyOnWriteArrayList - 写复制列表
* `HashSet` -> CopyOnWriteArraySet - 写复制集合
* `HashMap` -> **ConcurrentHashMap** - 分段锁映射
```mermaid
mindmap
root
List(列表)
✅ Vector
❌ ArrayList
✅ CopyOnWriteArrayList(写复制列表)
❌ LinkedList
Set(集合)
❌ HashSet
✅ CopyOnWriteArraySet(写复制集合)
✅ ConcurrentSkipListSet(并发跳表集合)
❌ TreeSet
Map(映射)
✅ Hashtable
✅ Properties(继承自 Hashtable)
❌ HashMap
✅ ConcurrentHashMap(分段锁映射)
✅ ConcurrentSkipListMap(并发跳表映射)
Queue(队列)
✅ ConcurrentLinkedQueue(无界链表队列)
✅ LinkedBlockingQueue(有界链表队列)
✅ ArrayBlockingQueue(有界数组队列)
✅ PriorityBlockingQueue(优先级阻塞队列)
✅ DelayQueue(延时队列)
✅ SynchronousQueue(同步队列)
String()
✅ StringBuffer
❌ StringBuilder
```
### **原子对象 (Atomic)**
1. **原子性 (Atomicity)**:
* **定义**:一个操作或多个操作要么全部执行,且执行的过程不会被任何因素打断,要么就都不执行。
2. [🐟代码小抄-乐观锁 vs 悲观锁](https://codecopy.cn/post/zgx6tt)
* **乐观锁**:
* 每次不加锁,而是假设没有冲突而去完成某项操作。如果因为冲突失败,就重试,直到成功。
* **悲观锁**:
* 假设最坏情况,独占资源,确保不会被其它线程修改。例如 `synchronized`,数据库 `select... for update`。
3. **`Atomic` 包**
* 提供无锁(lock-free)的原子类,确保并发数据安全,适用于高并发场景。
* **原理**:通过 `CAS`(Compare And Swap)操作,保证数据的原子性。
* [🐟代码小抄-原子对象 (Atomic) 的使用](https://codecopy.cn/post/o904qw)
| **分类** | **类名** | **作用** |
| --------- | ----------------------------------------------------------------------------- | -------------------------- |
| **基本类型** | `AtomicInteger`、`AtomicLong`、`AtomicBoolean` | 提供基本类型的原子操作 |
| **数组类型** | `AtomicIntegerArray`、`AtomicLongArray` | 对数组元素进行原子更新 |
| **引用类型** | `AtomicReference`、`AtomicStampedReference`、`AtomicMarkableReference` | 适用于对象引用的原子更新,支持版本控制和标记 |
| **字段更新器** | `AtomicIntegerFieldUpdater`、`AtomicLongFieldUpdater` | 允许对 `volatile` 修饰的字段进行原子更新 |
4. **解决 ABA 问题**
* 当变量从 `A → B → A` 变化,CAS 可能认为它未被修改,导致错误。
* 解决方案:使用 `AtomicStampedReference` 记录版本号。
================================================
FILE: src/md/spring-boot/README.md
================================================
---
title: Spring Boot 入门教程
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-03-15
category: Spring Boot
tag:
- spring boot
---
# Spring Boot 教程
================================================
FILE: src/md/spring-boot/quickstart.md
================================================
---
title: SpringBoot 快速入门
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-03-15
category: Spring Boot
tag:
- spring boot
order: 1
---
# SpringBoot 快速入门
================================================
FILE: src/md/spring-data-jpa/README.md
================================================
---
title: Spring Data JPA
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-04-04
category: Spring Data
tag:
- spring data jpa
---
# Spring Data JPA
## 目录
* [Spring Data JPA 快速入门](/spring-data-jpa/jetbrains/getting-started)
================================================
FILE: src/md/spring-data-jpa/jetbrains/getting-started.md
================================================
---
title: Spring Data JPA 快速入门
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: 2024-04-04
category: Spring Data
tag:
- spring data jpa
order: 1
---
# Spring Data JPA 快速入门
> 目标: 了解如何使用Spring Data JPA从数据库中存储和检索数据。
参考:
* [Spring Data JPA 官方文档](https://docs.spring.io/spring-data/jpa/reference/jpa/getting-started.html)
* [Jetbrains 官方文档](https://www.jetbrains.com/guide/java/tutorials/getting-started-spring-data-jpa/)
## 介绍
[Spring Data JPA](https://spring.io/projects/spring-data-jpa)是一个功能强大的框架,允许用户轻松地与数据库交互,同时最大限度地减少样板代码。
在本教程中,我们将研究如何使用Spring Data JPA插入数据库并从数据库查询数据。
我们将使用[IntelliJ IDEA Ultimate](https://www.jetbrains.com/lp/intellij-frameworks/)创建一个简单的Spring靴子应用程序,以利用其Spring特性支持。
## 创建一个新的Spring Boot项目
首先,在 IntelliJ IDEA Ultimate 中,我们将通过在欢迎屏幕中点击 "**New Project**" 来创建一个新项目。
我们将从左侧菜单中选择 "**Spring Initializr**"。然后,我们将指定项目的名称 - 你可以将其命名为 ==SpringDataJPA==。
我们还可以将 "**Group**" 字段更改为我们公司的名称。
对于其余的字段,你可以接受默认值。请随意为你的项目使用最新的 Java 版本。

接下来,我们将点击 "**Next**"。
在接下来的窗口中,我们将选择我们可用的最新的 Spring Boot 版本。
然后在 "Dependencies" 下,我们将在搜索框中搜索 "==data=="。
在 SQL 下,我们将从列表中选择 **Spring Data JPA** 和 **H2 Database** 的复选框。
对于本教程,我们将使用 H2 作为我们的数据库,因为它很容易设置。
如果你想使用不同的数据库,比如 MySQL 或 HyperSQL,可以随意选择这些依赖项,或者稍后将它们添加到你的 pom.xml 文件中。

一旦我们点击 "**Finish**",IntelliJ IDEA 就会创建一个带有 Spring Data JPA 和数据库依赖的新的 Spring Boot 项目。
## 创建员工实体
实体是一个Java类,代表着你想要插入到数据库中的数据。 对于我们的应用程序,我们将创建一个名为 Employee 的实体,
我们将使用它来将员工数据插入到我们数据库中的 Employee 表中。
在项目工具窗口中,我们将导航到我们的 `src/main/java` 目录,选择 `com.jetbrains.springdatajpaapp` 包,
并按下 **⌘N** (macOS) / **Alt+Insert** (Windows/Linux)。选择 **Java Class**,然后输入我们的实体名称 - ==Employee==。
然后,按下 **⏎** (macOS) / **Enter** (Windows/Linux)。
在 Employee 类中,我们将通过在类定义中添加 ==@Entity== 注解并导入 `javax.persistence.Entity` 包来将其设置为实体。
一旦你这样做了,你会注意到你的类中出现了一个错误。

正如错误消息所指示的,实体必须有一个由 `@Id `注解指定的主键字段。我们将使用 **⌥⏎** (macOS) / **Alt+Enter** (Windows/Linux)
快捷键,
这样 IntelliJ IDEA 就可以为我们添加 ID,这将导致提示输入 ID 字段的信息。 我们将采用默认的名称和类型。
你可以选择 "**Field Access**" 复选框(我更喜欢在字段上使用注解,而不是在 setter 方法上,因为我觉得这样更易读)。
你也可以选择 "**Generated**" 复选框,这样你就不必自己分配员工 ID。

然后,我们将点击 "**OK**"。你会注意到 IntelliJ IDEA 创建了一个带有其 setter 和 getter 的 `id` 字段。
接下来,让我们添加几个 String 字段:==firstName== 和 ==lastName==。
接下来,我们将通过调出 Generate 菜单 **⌘N** (macOS) / **Alt+Insert** (Windows/Linux),然后选择 **Constructor** 来生成我们的构造函数。
我们不需要构造函数接收一个 id,因为我们的 id 将会自动生成,
所以我们将点击 **firstName**,然后按住 **⌘** (macOS) / **Ctrl** (Windows/Linux) 键,然后选择 **lastName**。

然后,我们将点击 "**OK**"。
你现在会注意到你的 `Employee` 类上出现了一个错误,因为你没有一个无参构造函数。
如果你在错误上按下 **Alt+Enter** (Windows/Linux)
或 **⌥⏎** (macOS),IntelliJ IDEA 将为你提供创建它的选项 - 让我们继续执行。
接下来,我们将生成字段的 setter 和 getter。
我们将调出 Generate 菜单 ⌘N (macOS) / Alt+Insert (Windows/Linux),然后选择 Getter 和 Setter。
我们将按住 ⌘ (macOS) / Ctrl (Windows/Linux) 键,并选择两个变量。
然后,我们将点击 "**OK**"。IntelliJ IDEA 为这两个变量生成了 getter 和 setter。
最后,让我们再次调出 Generate 菜单 ⌘N (macOS) / Alt+Insert (Windows/Linux),然后选择 `toString` 来生成一个 toString 方法。
我们将保持所有字段选中,然后点击 "**OK**"。
最终,Employee 实体应该类似于以下内容(格式可能会有所不同):
```java
package com.jetbrains.springdatajpaapp;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Employee {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
public Employee() {
}
public Employee(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public void setId(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
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;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
'}';
}
}
```
## 创建 Repository 接口
现在我们已经创建了 Employee 实体,我们需要一种方式来执行 CRUD(创建、读取、更新、删除)操作。
幸运的是,Spring Data JPA 通过 ==Repositories== 提供了所有基本的操作。让我们看看它们是如何工作的。
在项目工具窗口中,我们将选择 `com.jetbrains.springdatajpaapp` 包,并按下 ⌘N (macOS) / Alt+Insert (Windows/Linux)。
选择 **Java Class**。
我们将其命名为 ==EmployeeRepository==,这次我们选择 **Interface** 然后按下 ⏎ (macOS) / Enter (Windows/Linux)。
为了使我们的接口成为一个 repository,我们需要让它扩展 ==CrudRespository== 接口,其中泛型参数是我们的实体类和实体的
id 类型。
所以对于我们的应用程序,我们的 repository
接口定义将会是:`public interface EmployeeRepository extends CrudRepository`。
我们将使用这个 `EmployeeRepository` 接口来在我们的应用程序代码中执行 CRUD 操作。
由于我们正在扩展 `CrudRepository`,我们默认就可以访问基本的 CRUD 方法。
例如,我们可以调用 `save` 方法将一个 Employee 对象插入到我们的数据库中。
我们也可以调用 `findAll` 方法来列出你的 Employee 表中的所有员工。
当我们编写我们的应用程序逻辑时,我们将看到如何做到这一点。
在大多数应用程序中,你会发现自己希望做的事情超出了 CrudRepository 接口默认提供的范围。
例如,假设我们想要找到所有姓氏中包含空格的员工。你当然可以编写一个 SQL 查询来实现这个功能。
然而,一个更简单的方法是利用 Spring Data JPA,它允许你在你的 repository 中创建方法,这些方法将根据方法名被转换为查询。
例如,假设我们想要添加一个方法来查找所有姓氏中包含特定字符串的员工。
我们将前往我们的 `EmployeeRepository` 接口,并开始声明我们的方法。
我的方法将返回一个员工列表,因此我们将使用 `List` 作为返回类型。
然后,我们需要指定一个由两部分组成的方法名:引入部分和条件部分。
我们可以在 IntelliJ IDEA 中使用 **⌃␣** (macOS) / **Ctrl+Space** (Windows/Linux) 来查看引入部分的方法建议列表:

我们将选择 ==findEmployeesBy== 作为引入部分。
然后,我们将再次点击 **⌃␣** (macOS) / **Ctrl+Space** (Windows/Linux) 来查看可以选择的条件列表。

我们将选择 `LastNameContaining`。最后,我们将声明一个 String 方法参数,表示我们要检查的字符串。
现在我们有了一个方法,可以找到所有姓氏中包含所提供字符串的员工。
你可能会想:如果我定义了这个接口,我不是必须要实现它吗?答案是否定的。
Spring Data JPA 会为你处理这些!你只需要定义你的 Repository 接口,并遵循 Spring Data 的约定声明你的方法。
当你添加更多方法时,如果在方法名中犯错,IntelliJ IDEA 将会指出错误。
例如,如果我们尝试创建一个用于查询具有不正确 `LName` 属性的员工的方法,IntelliJ IDEA
会给出一个错误,说`Cannot resolve property LName`。

最终,你的 EmployeeRepository 接口应该如下所示:
```java
package com.jetbrains.springdatajpaapp;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
public interface EmployeeRepository extends CrudRepository {
List findEmployeesByLastNameContaining(String str);
}
```
## 配置数据库
此时,我们可以使用 Spring Boot 为我们创建的默认内存数据库。
然而,默认数据库功能有限,并且在应用程序终止后不允许数据保留,因此让我们继续配置一个数据库。
我们将调出搜索菜单 **⇧⇧** (macOS) / **Shift+Shift** (Windows/Linux) 并搜索我们的 ==application.properties== 文件。
在我们的 application.properties 文件中,我们可以利用 IntelliJ IDEA 的建议来指定连接到我们的 H2 数据库所需的属性。
我们将开始输入 ==url==,并从建议列表中选择 ==spring.datasource.url== 属性。

如果你没有使用 H2 作为你的数据库,你可以在这里指定你自己的数据库 URL。
在我们的情况下,我们将指定 ==jdbc:h2:file:./data/myDB== 作为我们的 URL,这将创建一个名为 ==myDB== 的 H2 数据库。
接下来,我们需要指定我们的驱动类名。
我们可以类似地搜索 ==driver==,并从我们的建议列表中选择 ==spring.datasource.driver-class-name==。
一旦我们选择了这个,IntelliJ IDEA 将根据我们之前指定的 URL 提供 H2 驱动程序的建议值,因此我们可以选择它。

接下来,我们同样搜索 ==username== 和 ==password== 属性,并将它们的值分别设置为 ==sa== 和 ==password==。
最后,我们不想手动创建任何表,因此我们将添加 ==spring.jpa.hibernate.ddl-auto== 属性,并使用代码完成来查看可能的值列表。

对于我的应用程序,我将把属性值设置为 ==update==,这样如果表不存在,它将在数据库中创建表,并在我对实体进行更改时更新它们。
最终,你的 application.properties 文件应该如下所示:
```properties
spring.datasource.url=jdbc:h2:file:./data/myDB
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
```
## 从应用程序写入数据库
现在我们有了我们的 Employee 实体、Repository 和数据库配置,我们准备编写我们的应用程序逻辑。
假设我们想要向我们的数据库插入四个员工。我们将导航到为我们的 Spring Boot 应用程序
创建的 ==SpringDataJpaApplication== 类 **⇧⇧** (macOS) / **Shift+Shift** (Windows/Linux)。
**练习**:
花几分钟时间看看你能否创建一个 `insertFourEmployees(EmployeeRepository repository)` 方法,使用 `save()` 方法插入员工。
完成了吗?以下是你的方法应该是什么样子的:
```java
private void insertFourEmployees(EmployeeRepository repository) {
repository.save(new Employee("Dalia", "Abo Sheasha"));
repository.save(new Employee("Trisha", "Gee"));
repository.save(new Employee("Helen", "Scott"));
repository.save(new Employee("Mala", "Gupta"));
}
```
现在,让我们继续在我们的应用程序中调用那个方法。在一个典型的 Spring Boot 应用程序中,我们会有一个服务类,其中包含服务提供的功能。
然而,由于我们只是做一些一次性的操作,让我们保持简单,使用一个 Bean 来调用我们的 `insertFourEmployees` 方法。
然后,让我们调用 `repository.findAll()` 来检索已插入的实体。下面是代码示例:
```java
@Bean
public CommandLineRunner run(EmployeeRepository repository) {
return (args) -> {
insertFourEmployees(repository);
System.out.println(repository.findAll());
};
}
```
让我们运行我们的应用程序并看到它的运行情况。我们将按 **⌃R** (macOS) / **Shift+F10** (Windows/Linux)。
最终结果,一旦我们的应用程序启动,我们可以看到控制台日志显示我们的 Spring Boot 应用程序正在启动。
然后,我们可以看到 `findAll` 调用的结果,打印出数据库中所有的员工。

## 创建数据源
在你的项目中使用数据库时,在IntelliJ IDEA中创建一个数据源连接非常有用。
它允许你在IDE中轻松地与数据库进行交互。让我们为我们的[H2](https://www.h2database.com/html/main.html)数据库创建一个数据源连接。
如果你正在使用其他数据库,你可以使用类似的步骤,或者你可以按照
IntelliJ IDEA[数据库连接帮助页面](https://www.jetbrains.com/help/java/connecting-to-a-database.html)中概述的说明进行操作。
请注意,此功能仅适用于[IntelliJ IDEA Ultimate](https://www.jetbrains.com/idea/features/editions_comparison_matrix.html)版。
要创建我们的H2数据源,我们将打开数据库工具窗口(**View | Tool Windows | Database**),然后点击`+`按钮。
有多种方式可以创建我们的连接。我们将使用**Data source from URL**选项。

我们将输入我们的数据库URL:==jdbc:h2:file:./data/myDB==,然后点击确定。
接下来,我们将完成数据库的配置。对于我们的用户和密码字段,
我们将输入在我们的`application.properties`文件中设置的用户和密码(==sa==,==password==)。
如果你收到有关缺少H2驱动程序的警告,请点击下载缺失的驱动程序文件。

**可选步骤**:在选项选项卡下,启用“设置后自动断开连接”,并将其设置为在3秒后断开连接。
此设置将断开IntelliJ IDEA中的数据库并释放所有锁定,使我们应用程序的进程可以持续连接并写入数据库。
这将防止来自你的应用程序的`database may already be in use`的错误。
如果执行了此步骤,则可能需要在数据库工具窗口中点击“刷新”按钮以更新数据源。
然后,我们将点击“**Test Connection**”以确保我们的配置是有效的。

一旦我们点击"**OK**",我们就会看到一个新的数据源,用于我们的H2数据库。
最终结果 ,在数据库视图中,我们现在可以导航到我们的Employee表,并通过双击表格查看所有员工数据。

## 调用自定义查询
假设我们想要查询数据库中所有姓氏中包含空格的员工。
**练习**:花几分钟时间编写代码,查找并打印姓氏中带有空格的所有员工。
完成了吗?以下是实现代码的样子:
```java
@Bean
public CommandLineRunner run(EmployeeRepository repository) {
return (args) -> {
System.out.println(repository.findEmployeesByLastNameContaining(" "));
};
}
```
最终结果, 如果你插入了教程中提到的相同数据,你应该在控制台输出中看到"`Dalia`"员工。

## 总结
在这个教程中,我们创建了一个简单的Spring Boot应用程序,它使用Spring Data JPA来存储和检索数据库中的数据。
一些在教程中提到的有用的快捷方式包括:
| Name | Windows Shortcut | macOS Shortcut |
|---------------|------------------|----------------|
| 创建类并生成构造函数/方法 | Alt+Insert | ⌘N |
| 上下文感知代码完成 | Ctrl+Space | ⌃Space |
| 到处搜索 | Shift+Shift | Shift+Shift |
| 运行你的应用程序 | Shift+F10 | ⌃R |
你可以在这个[GitHub仓库](https://github.com/daliasheasha/SpringDataJPA)中找到通过这个教程创建的最终项目。
**帮助链接**
* [Spring.io: Spring Data JPA](https://spring.io/projects/spring-data-jpa)
* [Spring Guide: Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/)
* [Working with Hibernate/JPA in IntelliJ IDEA (2021) (video)](https://youtu.be/QJddHc41xrM)
* [(documentation) Spring Support in IntelliJ IDEA](https://www.jetbrains.com/help/idea/spring-support.html)
* [(documentation) Explore Spring support features](https://www.jetbrains.com/help/idea/spring-support-tutorial.html)
* [(documentation) Database Connection in IntelliJ IDEA](https://www.jetbrains.com/help/idea/connecting-to-a-database.html)
* [(video) Getting started with Spring Data JPA](https://www.youtube.com/watch?v=wuX2ESOy-Ts)
================================================
FILE: src/md/spring-framework/core/README.md
================================================
---
title: Spring 核心技术
icon: spring
author: 流浪码客
isOriginal: true
date: 2024-03-18
category: Spring
tag: Spring Framework
---
# Spring 核心技术
## 核心技术
Spring 框架的核心技术主要包括:
* **依赖注入(dependency injection),也称为控制反转(IoC)**
* 事件(events)
* 资源(resources)
* 国际化(i18n)
* 数据验证(validation)
* 数据绑定(data binding)
* 类型转换(type conversion)
* SpEL(Spring Expression Language)
* **面向切面编程(AOP)**
## Spring IoC容器和Bean简介
**Spring IoC(控制反转)也被称为依赖注入(DI)**
它是一个过程,对象仅通过构造参数、工厂方法参数或在**对象实例**被构造函数或工厂方法返回后,在其上设置的属性来定义它们的依赖关系。
在IoC容器创建Bean时,它会自动注入这些依赖项。 不再需要通`直接构造依赖项`或使用`服务定位器模式`等方式来管理对象的实例化或位置,
而是交由IoC容器来管理,因此称为**控制反转**。
[org.springframework.beans](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/beans/factory/BeanFactory.html)
和 [org.springframework.context](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/context/ApplicationContext.html)
包是Spring Framework的IoC容器的基础。
[BeanFactory](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/beans/factory/BeanFactory.html)
接口提供了一种高级配置机制,能够管理任何类型的`object`对象。
[ApplicationContext](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/context/ApplicationContext.html)
是 BeanFactory 的一个子接口。它增加了:
* 与Spring的AOP特性更好的集成
* `Message resource`消息资源处理(用于国际化)
* `Event publication`事件发布
* 应用层特定的上下文,例如Web应用程序的`WebApplicationContext`
简而言之,`BeanFactory` 提供了配置框架和基本功能,`ApplicationContext` 添加了更多企业特定的功能。
`ApplicationContext` 是 `BeanFactory` 的一个超集。 在特别要求轻量级应用程序的情况下,可以考虑使用`BeanFactory`。
想要了解 BeanFactory
参阅 [BeanFactory API](https://docs.spring.io/spring-framework/reference/core/beans/beanfactory.html) 。
================================================
FILE: src/md/spring-framework/core/beans-definition.md
================================================
---
title: Bean 定义(Definition)
author: 流浪码客
isOriginal: true
date: 2024-03-18
category: Spring
tag: Spring Framework
---
# Bean 定义(Definition)
## 概述
> 构建应用程序主干并由Spring IoC 容器管理的对象称为 Bean。
>
> 在容器中,Bean的定义表示为`org.springframework.beans.factory.config.BeanDefinition`对象。
**BeanDefinition包含以下元数据:**
* **全路径类名**:通常,被定义为Bean的实现类
* **行为配置元素**:说明了Bean在容器中的行为方式,例如作用域scope、生命周期回调等
* **依赖关系**:描述Bean与其他Bean之间的依赖关系,包括依赖注入,依赖查找等
* 其他配置信息:如:管理连接池的Bean可以配置pool的大小限制,使用的连接数量等
该元数据转换为组成每个Bean定义的一组属性。 下表介绍了这些属性:
| 属性 | 描述 |
|--------------------------|--------------|
| Class | Bean的全限定类名 |
| Name | Bean的名称 |
| Scope | Bean的作用域 |
| Constructor arguments | Bean的构造函数参数 |
| Properties | Bean的属性 |
| Autowiring mode | Bean的自动装配模式 |
| Lazy initialization mode | Bean的延迟初始化模式 |
| Initialization method | Bean的初始化方法 |
| Destruction method | Bean的销毁方法 |
## 命名Beans
在Spring IoC容器中,每个Bean都必须有一个**唯一的标识符**(identifier),如果需要一个以上的标识符,多余的标识符可以被视为**别名**。
**基于XML的配置元数据**
| 属性/元素 | 描述 |
|---------|----------------------------------------|
| `id` | Bean的唯一标识符;默认采取小写字母开头的驼峰命名法 |
| `name` | Bean的别名,可以有多个别名;用逗号(`,`)、分号(`;`)或空格分隔 |
| `alias` | 与name作用相同,都是用于指定Bean的别名(Spring 5.0中废弃) |
| `ref` | 引用其他Bean |
建议为每个Bean提供一个唯一的`id`属性,以便使用`ref`属性引用该Bean。
不提供Bean名称的动机与使用[内部Bean](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-properties-detailed.html#beans-inner-beans)
和[自动装配协作者](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-autowire.html)有关。
**使用`Introspector`生成默认Bean名称**
在classpath中的组件扫描,Spring会自动为未命名的组件按照`java.beans.Introspector`的规则生成一个默认的bean名称
默认将类名的转为==小写字母开头的驼峰命名法==;如`com.example.MyBean`类的Bean名称是`myBean`。
特殊的,如果类名的第一个和第二个字符都是大写字母,则 Spring 会保留原始的大小写;如`URL`类的默认Bean名称还是`URL`
> 如果你使用**Java配置**,`@Bean` 注解可以被用来提供别名。
> 参阅 [使用@Bean注释](https://docs.spring.io/spring-framework/reference/core/beans/java/bean-annotation.html)。
## 实例化Bean
Bean定义(definition)本质上是创建一个或多个对象的“配方”。
容器在被询问时查看指定名称的Bean的“配方”,并使用该Bean定义所封装的元数据来创建(或获取)一个对象。
**使用XML配置元数据实例化Bean**
如果使用基于XML的配置元数据,可以在``元素中的`class`属性中指定要实例化对象的类型。
这个`class`属性通常是必需的,用于定义内部`BeanDefinition`对象实例的`Class`属性。
对于一些例外情况,参阅 [使用实例工厂方法实例化](#使用实例工厂方法实例化)
以及 [Bean定义的继承](https://docs.spring.io/spring-framework/reference/core/beans/child-bean-definitions.html)。
**使用`Class`属性**
1. 通过反射调用构造函数创建Bean
* 这种方式类似于Java中的`new`操作符,容器通过反射调用构造函数来创建Bean
```xml
```
2. 通过静态工厂方法创建Bean
* 这种方式不太常见,容器会调用一个类上的`static`工厂方法来创建Bean
```xml
```
**嵌套类的Bean定义**
> 如果要为嵌套类配置Bean定义,可以使用嵌套类的==二进制名称==或==源名称==,通过美元符号 (`$`) 或点 (`.`) 分隔。
假设有一个名为`OuterClass`的外围类,其中包含一个名为`InnerClass`的嵌套类
* 二进制名称:`com.example.OuterClass$InnerClass`
* 源名称:`com.example.OuterClass.InnerClass`
### 使用构造函数实例化
通过构造函数方法创建`Bean`时,所有普通的类都可以被`Spring`使用并与之兼容
* 被开发的类不需要实现任何特定的接口,或以特定的方式进行编码。只需指定`Bean`类就足够了
* 根据你对该特定`Bean`使用的`IoC`类型,可能需要一个==默认(空)构造函数==
通过基于XML的配置元数据,你可以按以下方式指定你的bean类。
```xml
```
关于向==构造函数传递参数==和==对象被构造后设置对象实例属性==的机制的详细信息,请参阅 [依赖注入](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html)。
### 使用静态工厂方法实例化
定义一个静态工厂方法创建Bean时,使用`class`属性指定工厂类,使用`factory-method`属性指定工厂方法。
这种Bean定义的一个用途是在遗留代码中调用`static`工厂,做到==无侵入性==。
示例Bean定义,它使用静态工厂方法创建Bean:
```xml
```
下面的例子展示了一个与Bean定义(definition)一起工作的类:
```java
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
```
关于向==工厂方法提供(可选)参数==以及==从工厂返回对象后设置对象实例属性==的机制,参阅 [依赖和配置详解](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-properties-detailed.html)。
### 使用实例工厂方法实例化
使用**实例工厂方法**进行的实例化,与[使用静态工厂方法实例化](#使用静态工厂方法实例化)类似;
通过从容器中调用现有`Bean`的非静态方法来创建一个新的`Bean`。
要使用这种机制,请将`class`属性留空,`factory-bean`属性指定工厂Bean,`factory-method`属性指定工厂方法。
```xml
```
以下示例显示了相应的类:
```java
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
```
这种方法表明,工厂Bean本身可以通过依赖注入(DI)进行管理和配置。参阅 [依赖和配置详解](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-properties-detailed.html)。
::: note
在 Spring 文档中,“`factory bean`”是指Spring容器中配置的Bean,它通过[实例](#使用实例工厂方法实例化)
或[静态](#使用静态工厂方法实例化)工厂方法创建对象。 相比之下,`FactoryBean`(注意大小写)是Spring特定
[FactoryBean](https://docs.spring.io/spring-framework/reference/core/beans/factory-extension.html#beans-factory-extension-factorybean)
接口的实现,它允许自定义实例化逻辑。
:::
### 确定Bean的运行时类型
在 Spring 应用程序中,要确定特定`Bean`的==运行时类型==可能会有些复杂。
这是因为`Bean`的类型可能受到多种因素的影响,包括但不限于以下几点:
1. **Bean 元数据定义**
* Bean元数据定义中指定的`class`类只是一个初始的类引用
2. **静态工厂方法和 FactoryBean**
* 与静态工厂方法或者FactoryBean的实现结合使用,可能会导致Bean的实际类型与`class`属性指定的类型不同
3. **实例工厂方法**
* 与实例工厂方法结合使用,根本不会使用`class`属性指定的类型,而是通过指定的`factory-bean`名称来解决
4. **AOP 代理**
* AOP代理可能会使用基于接口代理包装Bean实例,目标Bean的实际类型公开有限(只有其实现的的代理接口)
考虑到上述所有情况,要了解某个特定Bean的实际运行时类型。
推荐的方法是使用`BeanFactory`接口的`getType()`方法, 并返回与`BeanFactory.getBean()`调用返回的对象类型相同的类型。
================================================
FILE: src/md/spring-framework/core/beans-lifecycle.md
================================================
---
title: Bean生命周期(Lifecycle)
author: 流浪码客
isOriginal: true
date: 2024-04-08
category: Spring
tag: Spring Framework
---
# Bean生命周期(Lifecycle)
Spring框架提供了多种接口,你可以使用这些接口来定制Bean的性质。本节将它们分为以下几类:
* [生命周期回调](#生命周期回调)
* [ApplicationContextAware 和 BeanNameAware](#applicationcontextaware和beannameaware)
* [其他Aware接口](#其他aware接口)
## 生命周期回调
为了与容器管理Bean的生命周期进行交互,你可以实现Spring的`InitializingBean`和`DisposableBean`接口。
容器调用前者的`afterPropertiesSet()`方法以及后者的`destroy()`方法,让Bean在初始化和销毁时执行特定的操作。
::: tip
在现代的Spring应用程序中,通常认为JSR-250的`@PostConstruct`和`@PreDestroy`注解是用于接收生命周期回调最佳实践。
使用这些注解意味着你的Bean不会与Spring特定的接口耦合。参阅 [使用@PostConstruct和@PreDestroy](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config/postconstruct-and-predestroy-annotations.html)。
如果你不想使用JSR-250注解,但仍然希望消除耦合,可以考虑使用`init-method`和`destroy-method`的Bean定义元数据。
:::
Spring框架在内部使用`BeanPostProcessor`实现来处理它找到的任何回调接口,并调用适当的方法。
如果你需要自定义功能或其他Spring默认不提供的生命周期行为,你可以自己实现一个`BeanPostProcessor`。
参阅 [容器扩展点](https://docs.spring.io/spring-framework/reference/core/beans/factory-extension.html)。
除了初始化和销毁回调之外,Spring管理的对象还可以实现`Lifecycle`接口,以便这些对象可以参与由容器自身生命周期驱动的启动和关闭过程。
生命周期回调接口在本节中有详细描述。
### 初始化回调
实现`org.springframework.beans.factory.InitializingBean`接口允许Bean在容器设置了所有必要属性之后执行初始化工作。
`InitializingBean`接口指定了一个方法:
```java
void afterPropertiesSet() throws Exception;
```
我们建议不要使用`InitializingBean`接口,因为它会将代码不必要地耦合到Spring。
相反,我们建议使用`@PostConstruct`注解或指定一个POJO初始化方法。
在基于XML的配置中,你可以使用`init-method`属性来指定具有`void`无参数签名的方法的名称。
对于Java配置,你可以使用`@Bean`的`initMethod`属性。
参阅 [接收生命周期回调](https://docs.spring.io/spring-framework/reference/core/beans/java/bean-annotation.html#beans-java-lifecycle-callbacks)。
考虑以下示例:
```xml
```
```java
public class ExampleBean {
public void init() {
// 做一些初始化工作
}
}
```
上一个示例与以下示例几乎具有相同的效果:
```xml
```
```java
public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// 做一些初始化工作
}
}
```
然而,前面两个示例中的第一个并未将代码与Spring耦合。
::: note
请注意,`@PostConstruct`和初始化方法一般在容器的单例创建锁内执行。只有在从`@PostConstruct`方法返回后,
Bean实例才被视为完全初始化并准备好发布给其他对象。
这些单独的初始化方法仅用于验证配置状态并可能根据给定的配置准备一些数据结构,但不涉及外部Bean访问相关的进一步活动。
否则,存在初始化死锁的风险。
对于需要触发昂贵的初始化后活动的场景,例如异步数据库准备步骤,你的Bean应该实现
`SmartInitializingSingleton.afterSingletonsInstantiated()`方法,或依赖于上下文刷新事件:实现
`ApplicationListener`或声明其注解等效的 `@EventListener(ContextRefreshedEvent.class)`。
这些变体在所有常规单例初始化之后,因此不会在任何单例创建锁内。
或者,你可以实现`(Smart)Lifecycle`接口并与容器的整体生命周期管理集成,包括自动启动机制、预销毁停止步骤和潜在的停止/重新启动回调(参见下文)。
:::
### 销毁回调
实现`org.springframework.beans.factory.DisposableBean`接口允许Bean在包含它的容器销毁时获得回调。
`DisposableBean`接口指定了一个方法:
```java
void destroy() throws Exception;
```
我们建议不要使用`DisposableBean`回调接口,因为它会将代码不必要地耦合到Spring。
相反,我们建议使用`@PreDestroy`注解或指定一个由Bean定义支持的通用方法。
在基于XML的配置中,你可以在``元素中使用`destroy-method`属性。
在Java配置中,你可以使用`@Bean`的`destroyMethod`属性。
参阅 [接收生命周期回调](https://docs.spring.io/spring-framework/reference/core/beans/java/bean-annotation.html#beans-java-lifecycle-callbacks)。
考虑以下示例:
```xml
```
```java
public class ExampleBean {
public void cleanup() {
// 做一些销毁工作(比如释放池连接)
}
}
```
上一个示例与以下示例几乎具有相同的效果:
```xml
```
```java
public class AnotherExampleBean implements DisposableBean {
@Override
public void destroy() {
// 做一些销毁工作(比如释放池连接)
}
}
```
然而,前面两个示例中的第一个并未将代码与Spring耦合。
请注意,Spring还支持推断销毁方法,可以检测到公开的`close`或`shutdown`方法。
这是Java配置类中`@Bean`方法的默认行为,并且自动匹配`java.lang.AutoCloseable`或`java.io.Closeable`实现,也不会将销毁逻辑与Spring耦合。
::: tip
在XML配置中,你可以将``元素的`destroy-method`属性设置为一个特殊的(inferred)值。
该值指示Spring自动检测特定Bean类上的public `close`或`shutdown`方法。
另外,你也可以将这个特殊(inferred)值赋给``元素的`default-destroy-method`属性,以将此行为应用于一组Bean定义。
(参阅 [默认初始化和销毁方法](https://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html#beans-factory-lifecycle-default-init-destroy-methods))。
:::
::: note
要实现扩展的关闭阶段,你可以实现`Lifecycle`接口,这样可以在调用任何单例Bean的销毁方法之前接收到早期停止信号。
此外,你还可以实现`SmartLifecycle`接口,用于时间限制的停止步骤,容器将等待所有这类停止处理完成后再继续执行销毁方法。
:::
### 默认的初始化和销毁方法
当你编写初始化和销毁方法时,如果不使用Spring特定`InitializingBean`和`DisposableBean`回调接口,
通常会使用`init()`、`initialize()`、`dispose()`等名称的方法。
理想情况下,这些生命周期回调方法的命名应在项目中标准化,以便所有开发人员使用相同的方法名称并确保一致性。
在Spring中,你可以配置容器来自动"寻找"每个Bean上具有特定名称的初始化和销毁回调方法。
这意味着作为应用开发者,你可以编写应用类并使用名为`init()`的初始化回调,而无需在每个Bean定义中配置`init-method="init"`属性。
Spring IoC容器会在创建Bean时调用该方法
(并且符合[之前描述](https://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html#beans-factory-lifecycle)
的标准生命周期回调约定)。这个特性还可以强制执行初始化和销毁方法回调的一致命名约定。
假设你的初始化回调方法命名为`init()`,销毁回调方法命名为`destroy()`。那么你的类将类似于以下示例中的类:
```java
public class DefaultBlogService implements BlogService {
private BlogDao blogDao;
public void setBlogDao(BlogDao blogDao) {
this.blogDao = blogDao;
}
// 这个方法被标记为初始化回调方法
public void init() {
if (this.blogDao == null) {
throw new IllegalStateException("必须设置 [blogDao] 属性");
}
}
}
```
然后你可以在一个类似于以下示例的Bean中使用该类:
```xml
```
在顶层``元素中添加`default-init-method`属性会导致Spring IoC容器识别Bean类中名为`init`的方法作为初始化方法的回调。
当创建和组装Bean时,如果Bean类具有这样的方法,它会在适当的时候被调用。
你可以类似地(在XML中)通过在顶层``元素上使用`default-destroy-method`属性来配置销毁方法的回调。
如果现有的Bean类已经有了与约定不符的回调方法的名称,你可以通过在``本身上使用`init-method`和`destroy-method`
属性(在XML中)来覆盖默认值,指定方法的名称。
Spring容器保证在为Bean提供所有依赖项之后立即调用配置的初始化回调。 因此,初始化回调在原始Bean引用上被调用,这意味着AOP拦截器等还没有被应用到Bean上。
首先完全创建目标Bean,然后再应用AOP代理(例如)及其拦截器链。 如果目标Bean和代理是分开定义的,你的代码甚至可以与原始目标Bean交互,绕过代理。
因此,将拦截器应用于`init`方法是不一致的,因为这样做会将目标Bean的生命周期与它的代理或拦截器耦合在一起,当你的代码直接与原始目标Bean交互时,会留下奇怪的语义。
### 组合式生命周期机制
截至Spring 2.5,你有三种选项来控制bean的生命周期行为:
* [InitializingBean](https://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html#beans-factory-lifecycle-initializingbean)
和[DisposableBean](https://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html#beans-factory-lifecycle-disposablebean)
回调接口
* 自定义`init()`和`destroy()`方法
* `@PostConstruct`和`@PreDestroy`注解
1. 你可以组合这些机制来控制Bean的生命周期行为
::: note
如果为一个Bean配置了多种生命周期机制,并且每种机制都配置了不同的方法名称,则每个配置的方法按照本说明后面列出的顺序运行。
然而,如果为这些生命周期机制中的一个或多个配置了相同的方法名称,例如使用`init()`
作为初始化方法的名称,则该方法只会被执行一次,详细说明参考 [上一节](https://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html#beans-factory-lifecycle-default-init-destroy-methods)。
:::
为同一个Bean配置了多个生命周期机制,并且使用了不同的初始化方法时,调用顺序如下:
1. 使用`@PostConstruct`注解的方法
2. 实现`InitializingBean`接口定义的`afterPropertiesSet()`方法
3. 自定义配置的`init()`方法
销毁方法的调用顺序也类似:
1. 使用`@PreDestroy`注解的方法
2. 实现`DisposableBean`接口定义的`destroy()`方法
3. 自定义配置的`destroy()`方法
### 启动和关闭的回调
`Lifecycle`接口定义了任何具有自身生命周期要求的对象的基本方法(例如启动和停止某些后台进程):
```java
public interface Lifecycle {
// 启动方法
void start();
// 停止方法
void stop();
// 是否正在运行
boolean isRunning();
}
```
任何由Spring管理的对象都可以实现`Lifecycle`接口。
然后,当`ApplicationContext`本身接收到启动和停止信号时(例如,在运行时进行停止/重启场景),它会将这些调用级联到该上下文中定义的所有`Lifecycle`实现中。
它通过委托给一个`LifecycleProcessor`来实现这一点,如下所示。
```java
public interface LifecycleProcessor extends Lifecycle {
// 刷新时触发的方法
void onRefresh();
// 关闭时触发的方法
void onClose();
}
```
请注意,`LifecycleProcessor`本身就实现了`Lifecycle`接口。它还添加了另外两个方法,用于在上下文(context)被刷新和关闭时做出反应。
::: tip
请注意,常规的`org.springframework.context.Lifecycle`接口是一个明确的`start`和`stop`通知的简单约定,并不意味着在上下文刷新时自动启动。
为了对特定Bean的自动启动进行细粒度控制(包括启动和停止阶段),建议实现扩展的`org.springframework.context.SmartLifecycle`接口。
此外,请注意,`stop`通知不能保证在销毁之前执行。
在正常关闭时,所有`Lifecycle` Bean首先接收到`stop`通知,然后才会被传播到一般的销毁回调。
然而,在上下文生命周期中的热刷新或`stop`刷新时,只会调用销毁方法。
:::
启动和关闭调用的顺序可能非常重要。如果任何两个对象之间存在“依赖(depends-on)”关系,则依赖方会在其依赖项之后启动,并在其依赖项之前停止。
然而,有时候直接的依赖关系是未知的。你可能只知道某种类型的对象应该在另一种类型的对象之前启动。
在这种情况下,`SmartLifecycle`接口定义了另一种选择,即其父接口`Phased`上定义的`getPhase()`方法。以下代码展示了`Phased`接口的定义:
```java
public interface Phased {
// 返回一个整数值,表示该对象的启动和停止顺序
int getPhase();
}
```
下面列出了`SmartLifecycle`接口的定义:
```java
public interface SmartLifecycle extends Lifecycle, Phased {
// 返回一个boolean值,表示该对象是否应该自动启动
boolean isAutoStartup();
// 通知该对象已请求停止
void stop(Runnable callback);
}
```
启动时,`phase`最低的对象首先启动。停止时,按相反的顺序执行。
因此,实现`SmartLifecycle`接口并且`getPhase()`方法返回Integer.MIN_VALUE的对象会是最先启动和最后停止的对象之一。
在另一端,如果阶段值为Integer.MAX_VALUE,则表示该对象应该最后启动并且最先停止(通常是因为它依赖于其他正在运行的进程)。
在考虑`phase`值时,还要知道任何没有实现`SmartLifecycle`接口的“正常”`Lifecycle`对象的默认`phase`是0。
因此,任何负的`phase`值表示对象应该在这些标准组件之前启动(并在它们之后停止)。反之,任何正的`phase`值也是如此。
`SmartLifecycle`定义的`stop`方法接受一个回调。 任何实现都必须在该实现的关闭过程完成后调用该回调的`run()`方法。
这使得在必要时可以实现异步关机,因为`LifecycleProcessor`接口的默认实现`DefaultLifecycleProcessor`会等待每个阶段内的对象组调用该回调,直到其超时值。
每个阶段的默认超时时间是30秒。你可以通过在上下文中定义一个名为`lifecycleProcessor`的Bean来覆盖默认的生命周期处理器实例。
如果你只想修改超时时间,定义以下内容就足够了:
```xml
```
如前所述,`LifecycleProcessor`接口还定义了用于刷新和关闭上下文(context )的回调方法。
后者驱动关闭过程,就像显式调用了`stop()`方法一样,但它发生在上下文关闭时。
另一方面,“`refresh`”回调实现了`SmartLifecycle` Bean的另一个特性。
当上下文被刷新(在所有对象都被实例化和初始化之后)时,该回调被调用。
此时,默认的生命周期处理器会检查每个`SmartLifecycle`对象的`isAutoStartup()`方法返回的布尔值。
如果为true,该对象将在此时启动,而不是等待上下文或自身`start()`方法的显式调用(与上下文刷新不同,上下文的启动不会自动发生在标准的上下文实现中)。
如前所述,`phase`值和任何"依赖"关系决定了启动的顺序。
### 在非Web应用中优雅地关闭Spring IoC容器
::: note
本节仅适用于非Web应用。Spring的基于Web的`ApplicationContext`实现已经有代码可以在相关Web应用关闭时优雅地关闭Spring IoC容器。
:::
如果你在非Web应用程序环境中(例如,在客户端桌面环境中)使用Spring的IoC容器,请向JVM注册一个关闭钩子(shutdown hook)。
这样做可以确保优雅地关闭,并调用你的单例Bean上的相关销毁`destroy`方法,以释放所有资源。你仍然必须正确配置和实现这些销毁`destroy`回调。
要注册一个关闭钩子(shutdown hook),调用`ConfigurableApplicationContext`接口上声明的`registerShutdownHook`()方法,如下例所示。
```java
public final class Boot {
public static void main(final String[] args) throws Exception {
// 加载Spring配置文件并创建应用上下文
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// 为上述上下文添加一个关闭钩子...
ctx.registerShutdownHook();
// 应用程序在此处运行...
// main方法退出前,钩子在应用关闭之前被调用...
}
}
```
### 线程安全性和可见性
Spring核心容器以线程安全的方式发布创建的单例实例,通过一个单例锁来保护访问,并确保在其他线程中的可见性。
因此,由应用程序提供的Bean类不必担心其初始化状态的可见性。
只要常规配置字段仅在初始化阶段被修改,它们就不需要被标记为`volatile`,从而提供了类似于`final`的可见性保证,
即使是对于在初始阶段可变的基于setter的配置状态也是如此。
如果这些字段在Bean创建阶段之后及其随后的初始发布之后被更改,则需要将它们声明为`volatile`或在访问时受到公共锁的保护。
请注意,在从容器方面进行安全初始发布后,对单例Bean实例中的这种配置状态进行并发访问
(例如控制器实例或存储库实例) 是完全线程安全的。这还包括通用的单例`FactoryBean`实例,这些实例也在通用单例锁中进行处理。
对于销毁回调,配置状态仍然是线程安全的,但在初始化和销毁之间累积的任何运行时状态应该保存在线程安全的结构中
(或者对于简单情况,保存在`volatile`字段中),根据常见的Java指导方针。
如上所示,更深入的生命周期集成涉及到运行时可变状态,例如一个可运行字段,这个字段将需要声明为`volatile`。
虽然常见的生命周期回调遵循一定的顺序,例如,启动回调只会在完全初始化之后发生,而停止回调只会在初始启动之后发生,
但与常见的停止前销毁安排有一个特殊情况:强烈建议在任何这样的Bean中内部状态也允许在没有先前停止的情况下立即进行销毁回调,
因为这可能会在取消引导时或在由另一个bean引起的停止超时的情况下发生非常规关闭时发生。
## ApplicationContextAware
当一个类实现了`org.springframework.context.ApplicationContextAware`接口时,
该类的实例会得到对应的`ApplicationContext`实例的引用。
以下是`ApplicationContextAware`接口的定义示例:
```java
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
```
这意味着,当一个Bean实现了`ApplicationContextAware`接口或 引用了该接口的已知子类(如`ConfigurableApplicationContext`),
它就可以通过`ApplicationContext`接口来访问Spring容器的各种功能,
比如访问其他Bean、获取文件资源、发布事件,以及访问`MessageSource`的功能。
这些额外功能在[ApplicationContext的附加功能](https://docs.spring.io/spring-framework/reference/core/beans/context-introduction.html)
中描述。 不过,通常情况下不推荐过度使用这种方式,因为它会将代码与Spring框架耦合在一起,不符合控制反转的原则。
另一种获得对`ApplicationContext`引用的方式是通过自动装配(Autowiring)。
你可以使用`@Autowired`注解来自动装配 ApplicationContext,这样就可以在需要时轻松访问Spring容器的功能。
详细信息可以查阅使用[使用@Autowired](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config/autowired.html)。
## BeanNameAware
当一个类实现了`org.springframework.beans.factory.BeanNameAware`接口时,
这个类的实例会得到对应的Bean名称的引用。
以下是`BeanNameAware`接口的定义示例:
```java
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
```
回调方法会在Bean的属性填充后 & 初始化回调之前(如`InitializingBean.afterPropertiesSet`() 或 自定义`init`方法)被调用。
通过实现`BeanNameAware`接口,Bean可以在需要时获取自己在Spring容器中的名称,这在某些场景下可能会很有用。
## 其他Aware接口
除了之前讨论的`ApplicationContextAware`和`BeanNameAware`,Spring还提供了一系列广泛的`Aware`回调接口,
让Bean向容器表明它们需要某种基础设施的依赖性。通常,接口名称反映了依赖的类型。以下表格总结了最重要的`Aware`接口:
| Aware 接口 | 注入的依赖 | 解释 |
|----------------------------------|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `ApplicationContextAware` | `ApplicationContext` | 获取对Spring容器的引用,参阅 [ApplicationContext](#applicationcontextaware) |
| `ApplicationEventPublisherAware` | `ApplicationEventPublisher` | 获取对`ApplicationEventPublisher`的引用,用于发布事件,参阅[ApplicationContext的附加功能](https://docs.spring.io/spring-framework/reference/core/beans/context-introduction.html) |
| `BeanClassLoaderAware` | `ClassLoader` | 获取加载Bean类的类加载器,参阅[实例化Bean](/spring-framework/core/beans-definition.html#实例化bean) |
| `BeanFactoryAware` | `BeanFactory` | 获取对BeanFactory的引用,参阅[BeanFactory API](https://docs.spring.io/spring-framework/reference/core/beans/beanfactory.html) |
| `BeanNameAware` | `String` | 获取Bean的名称,参阅 [BeanNameAware](#beannameaware) |
| `LoadTimeWeaverAware` | `LoadTimeWeaver` | 获取对`LoadTimeWeaver`的引用,用于加载时织入,参阅 [AspectJ加载时编织](https://docs.spring.io/spring-framework/reference/core/aop/using-aspectj.html#aop-aj-ltw) |
| `MessageSourceAware` | `MessageSource` | 获取对`MessageSource`的引用,用于访问消息资源,参阅[ApplicationContext的附加功能](https://docs.spring.io/spring-framework/reference/core/beans/context-introduction.html) |
| `NotificationPublisherAware` | `NotificationPublisher` | 获取对`NotificationPublisher`的引用,用于发布通知,参阅[Notifications](https://docs.spring.io/spring-framework/reference/integration/jmx/notifications.html) |
| `ResourceLoaderAware` | `ResourceLoader` | 获取对`ResourceLoader`的引用,用于访问资源,参阅[Resources](https://docs.spring.io/spring-framework/reference/web/webflux-webclient/client-builder.html#webflux-client-builder-reactor-resources) |
| `ServletConfigAware` | `ServletConfig` | 获取对`ServletConfig`的引用,参阅[Spring MVC](https://docs.spring.io/spring-framework/reference/web/webmvc.html#mvc) |
| `ServletContextAware` | `ServletContext` | 获取对`ServletContext`的引用,参阅[Spring MVC](https://docs.spring.io/spring-framework/reference/web/webmvc.html#mvc) |
请注意,使用这些接口会将你的代码与Spring API紧密耦合,并且不符合控制反转的风格。因此,我们建议将它们用于需要以编程方式访问容器的基础设施Bean。
================================================
FILE: src/md/spring-framework/core/beans-scope.md
================================================
---
title: Bean作用域(Scope)
author: 流浪码客
isOriginal: true
date: 2024-04-03
category: Spring
tag: Spring Framework
---
# Bean作用域(Scope)
当你创建一个Bean定义时,实际上是在创建Bean定义所定义类的实际实例的配方。
将Bean定义视为“配方”的概念非常重要,因为它意味着,就像一个类一样,你可以从一个单一的“配方”中创建多个对象实例。
你不仅可以控制Bean定义中的各种依赖项和配置值,还可以控制由Bean定义创建的对象的作用域(scope)。
这种方法是强大且灵活的,因为你可以通过配置选择创建的对象的作用域,而不必在Java类级别上固定对象的作用域。
Bean定义可以是多种作用域之一。Spring框架支持六种作用域,其中四种仅在使用Web感知(aware)的`ApplicationContext`时才可用。
你还可以创建[自定义作用域](https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html#beans-factory-scopes-custom)。
| Bean作用域(Scope) | 描述 |
|----------------|--------------------------------------|
| singleton | (默认) 在整个应用程序中只创建一个Bean实例 |
| prototype | 每次请求时,创建一个新的Bean实例 |
| request | Web程序中,为每个HTTP请求创建一个Bean实例 |
| session | Web程序中,为每个HTTP会话创建一个Bean实例 |
| application | Web程序中,为每个`ServletContext`创建一个Bean实例 |
| websocket | Web程序中,为每个`WebSocket`连接创建一个Bean实例 |
::: note
线程作用域(Thread Scope)在Spring框架中是可用的,但默认情况下并没有注册。参阅 [SimpleThreadScope](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/context/support/SimpleThreadScope.html)。
关于如何注册此Scope或任何其他自定义Scope的说明,参阅 [自定义Scope](https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html#beans-factory-scopes-custom-using)。
:::
## 单例作用域(singleton)
单例作用域(singleton scope)是Spring框架中Bean定义的默认作用域。
当你将一个Bean定义为单例作用域时,对所有具有匹配ID或名称的Bean的调用都会返回这个特定的Bean实例。
下图说明了单例作用域:

Spring的单例Bean概念与《设计模式》GoF(四人帮)书中定义的单例模式有所不同。
* GoF单例模式通过硬编码对象的作用域,确保每个类加载器(ClassLoader)下,仅有一个特定类的实例被创建
* Spring单例的作用域最好被描述为每个容器(per-container)和每个bean(per-bean)
单例作用域是Spring中的默认作用域。要在XML中将一个Bean定义为单例,参考按照以下示例:
```xml
```
## 原型作用域(prototype)
原型作用域(prototype scope)的Bean部署,意味着每次请求该特定Bean时都会创建一个新的Bean实例。
也就是说,当一个Bean被注入到另一个Bean中,或者通过容器上的`getBean()`方法调用请求它,每次都会产生一个新的实例。
作为一项规则,将原型(prototype)作用域用于所有有状态的Bean,将单例(singleton)作用域用于无状态的Bean。
下图说明了原型作用域:

(注意⚠️:以上图片中的数据访问对象(DAO)通常不配置为原型作用域,因为典型的DAO不持有任何会话状态。)
以下示例展示了如何在XML中将一个Bean定义为原型作用域:
```xml
```
与其他作用域相比,Spring并不管理原型(prototype)Bean的完整生命周期。
容器实例化、配置并组装原型对象,然后将其交给客户端,之后就不会对那个原型实例保持任何记录。
因此,尽管初始化生命周期回调方法(如`@PostConstruct`)会在所有对象上调用,而不考虑作用域,
但在原型作用域的情况下,配置的销毁生命周期回调方法(如`@PreDestroy`)则不会被调用。
客户端代码必须清理原型作用域的对象,并释放原型Bean所持有的昂贵资源。
要让Spring容器释放原型作用域Bean所持有的资源,可以尝试使用一个自定义的[Bean后置处理器](https://docs.spring.io/spring-framework/reference/core/beans/factory-extension.html#beans-factory-extension-bpp)
,该后置处理器持有需要清理的Bean的引用。
在某些方面,Spring容器对于原型(prototype)作用域Bean的角色类似于Java中的`new`运算符。
但是,一旦Spring容器创建并交付原型Bean给客户端,所有生命周期管理的工作都需要由客户端自行处理。
有关Spring容器中Bean的生命周期的详细信息,参阅 [生命周期回调](https://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html#beans-factory-lifecycle)
## 单例Bean与原型Bean依赖
当你在单例作用域的Bean中使用对原型作用域Bean的依赖时,请注意依赖关系是在实例化时解析的。
因此,如果你将一个原型作用域的Bean注入到一个单例作用域的Bean中,将会实例化一个新的原型Bean,然后将其依赖注入到单例Bean中。
这个原型实例是唯一供给单例作用域Bean的实例。
然而,假设你希望单例作用域的Bean在运行时重复获取原型作用域的Bean的新实例。
你不能将一个原型作用域的Bean注入到你的单例Bean中,因为这种注入只会在Spring容器实例化单例Bean并解析并注入其依赖时发生一次。
如果你需要在运行时多次获取原型Bean的新实例,参阅 [方法注入(Method Injection)](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-method-injection.html)。
## 请求、会话、应用程序和WebSocket作用域
`request`、`session`、`application`和`webSocket`作用域只有在使用Web感知(aware)的Spring应用程序上下文实现,
如`XmlWebApplicationContext`时才可用。
如果你在常规的Spring IoC容器中使用这些作用域,比如`ClassPathXmlApplicationContext`,
将会抛出一个IllegalStateException异常,提示未知的Bean作用域。
### 初始Web配置
对于Web作用域的Bean,即`request`、`session`、`application`和`websocket`的Bean,需要进行特定的作用域范围设置,
初始设置取决于你的特定Servlet环境。
对于标准作用域,如`singleton`和`prototype`则不需要进行这些初始设置。
如果你在Spring Web MVC中访问作用域内的Bean,实际上是在Spring `DispatcherServlet`处理的请求中进行访问,
无需进行特殊设置。`DispatcherServlet`已经暴露了所有相关状态。
如果你使用Servlet Web容器,在Spring的`DispatcherServlet`之外处理请求(例如,使用`JSF`),需要进行以下配置:
* 注册`org.springframework.web.context.request.RequestContextListener` `ServletRequestListener`,
可以通过使用`WebApplicationInitializer`接口以编程方式完成
* 或者,在你的Web应用程序的`web.xml`文件中添加以下声明:
```xml
...
org.springframework.web.context.request.RequestContextListener
...
```
如果你在设置监听器(listener)时遇到问题,可以考虑使用Spring的`RequestContextFilter`。
过滤器(filter)的映射取决于周围Web应用程序的配置,因此你需要根据实际情况进行适当的调整。
以下示例展示了Web应用中过滤器的部分配置:
```xml
...
requestContextFilter
org.springframework.web.filter.RequestContextFilter
requestContextFilter
/*
...
```
其中`DispatcherServlet`、`RequestContextListener`和`RequestContextFilter`都实现相同的作用,
即把HTTP请求对象绑定到正在处理该请求的线程(Thread)上。
这使得请求范围(request-scoped)和会话范围(session-scoped)的Bean在整个调用链下游可用。
### 请求作用域(request)
以下XML示例中Bean的作用域是HTTP请求(request)级别的:
```xml
```
* 对于每个HTTP请求,Spring容器会创建一个新的`LoginAction`实例
* 每个实例独立,状态改变不会影响其他实例
* 请求结束后,相关实例被销毁
可以使用`@RequestScope`注解可将组件限定在请求作用域内:
```java
@RequestScope
@Component
public class LoginAction {
// ...
}
```
### 会话作用域(session)
以下XML示例中Bean的作用域是HTTP会话(Session)级别的:
```xml
```
* 对于单个HTTP会话(Session),Spring容器会创建一个新的`UserPreferences`实例
* 允许会话内状态更改,但不会影响其他会话
* 当HTTP会话(Session)结束时,相关联的Bean实例也会被销毁
可以使用`@SessionScope`注解将组件限定在会话作用域内:
```java
@SessionScope
@Component
public class UserPreferences {
// ...
}
```
### 应用程序作用域(application)
以下XML示例中Bean的作用域是`ServletContext`级别的:
```xml
```
* 对于整个Web应用程序,Spring容器仅会创建一个`AppPreferences`实例,存储在`ServletContext`属性中
* 这类似于Spring的单例Bean,但在两个重要方面有所不同:
1. 它是每个`ServletContext`的单例,而不是每个Spring `ApplicationContext`
(在任何给定的Web应用程序中可能有多个`ApplicationContext`)
2. 它实际上是作为`ServletContext`属性暴露和可见的
可以使用`@ApplicationScope`注解将组件限定在应用程序作用域内:
```java
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
```
### WebSocket作用域
WebSocket作用域与WebSocket会话的生命周期相关联,适用于基于WebSocket的STOMP应用程序,
详情参阅:[WebSocket作用域](https://docs.spring.io/spring-framework/reference/web/websocket/stomp/scope.html)
### Bean Scope作为依赖项
Spring IoC容器不仅管理对象(Bean)的实例化,还管理协作对象(或依赖项)的注入。
当需要将生命周期较短的Bean(HTTP请求作用域的Bean)注入到生命周期较长的Bean中,可以选择**注入一个AOP代理对象**。
换句话说,你需要注入一个代理对象,具有与被代理Bean相同的接口,能够从相关作用域获取实际的Bean实例,并代理其方法调用。
::: note
你还可以在定义`singleton`作用域的Bean之间使用 ``,
这样引用就会通过一个可序列化的中间代理进行,因此能够在反序列化时重新获取目标`singleton` Bean。
当对`prototype`作用域的Bean声明``时,对共享代理的每个方法调用都会导致创建一个新的目标实例,并将调用转发到新创建的实例上。
此外,作用域代理并不是以生命周期安全的方式从较短作用域中访问Bean的唯一方法。
你还可以将注入点(即构造函数或setter参数或autowired字段)声明为`ObjectFactory`,
允许在每次需要时通过调用`getObject()`来获取当前实例,而无需持有实例或将其分开存储。
作为一个扩展变体,你还可以声明`ObjectProvider`,它提供了几个额外的访问变体,包括`getIfAvailable`
和`getIfUnique`。
JSR-330的变体被称为Provider,使用`Provider`声明,并且每次检索尝试时都需要对应的`get()`调用。有关JSR-330的更多细节,
请参阅[此处](https://docs.spring.io/spring-framework/reference/core/beans/standard-annotations.html)。
:::
以下示例中的配置只有一行,但理解其“为什么(why)”以及“如何(how)”背后的原因同样重要:
```xml
(1) 定义代理的行
```
要创建`userPreferences`代理,需要在作用域Bean定义中插入一个子元素``
(参阅[选择要创建的代理类型](https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html#beans-factory-scopes-other-injection-proxies)
和[基于XML模式的配置](https://docs.spring.io/spring-framework/reference/core/appendix/xsd-schemas.html))。
**为什么在`request`、`session`和自定义作用域层次上的Bean定义需要使用``元素?**
考虑以下单例Bean定义,并将其与上述作用域所需的定义进行对比:
```xml
```
如上,单例Bean(`userManager`)被注入了对HTTP会话作用域的Bean(`userPreferences`)的引用。
这里**关键点是**:
* 单例Bean(`userManager`)它在容器中只被实例化一次,并且它的依赖项`userPreferences` Bean也只被注入一次
* 这意味着`userManager` Bean始终操作同一个的`userPreferences`对象(即最初注入时的对象),这不是期望的行为
**问题描述:单例与会话作用域的交互**
当把一个生命周期较短的作用域Bean注入到一个生命周期较长的作用域Bean时,这不是你想要的行为
(例如,在单例Bean中注入一个HTTP Session作用域的协作Bean作为依赖项)。
相反,你需要一个单例的`userManager`对象,而且,在HTTP Session的生命周期内,你需要一个特定于HTTP Session的`userPreferences`对象。
**解决方案:使用代理对象**
因此,容器会创建一个代理对象,具有与被代理Bean相同的接口(最好是一个`UserPreferences`实例),能够从相关作用域获取实际的Bean实例,并代理其方法调用。
容器将这个代理对象注入到`userManager` Bean中,而这个`userManager` Bean并不知道这个`UserPreferences`引用是一个代理。
在这个例子中,当`UserManager`实例调用依赖注入的`UserPreferences`对象上的方法时,实际上是在调用代理上的方法。
然后,代理从HTTP Session中获取真实的`UserPreferences`对象,并将方法调用委托给真实的`UserPreferences`对象。
以下是将请求作用域和会话作用域的 Bean 注入到协作对象中的**正确完整配置**:
```xml
```
#### 选择要创建的代理类型
默认情况下,当Spring容器为使用``元素标记的Bean创建代理时,会创建一个基于CGLIB的类代理。
::: note
CGLIB代理只拦截public方法的调用! 不要在这样的代理上调用非public的方法。它们不会被委托给实际的作用域目标对象。
:::
另外,你也可以通过在``元素的`proxy-target-class`属性中指定`false`的方式,
配置Spring容器为这些作用域Bean创建基于JDK接口的标准代理。
使用基于JDK接口的代理,意味着你的应用程序 classpath 中不需要额外的库来影响这种代理。
然而,这也意味着作用域Bean的类**必须实现至少一个接口**,并且所有注入该作用域Bean的协作对象必须通过其中一个接口引用该Bean。
以下示例展示了基于接口的代理:
```xml
```
关于选择基于类或基于接口的代理的更多详细信息,请参阅 [代理机制](https://docs.spring.io/spring-framework/reference/core/aop/proxying.html)。
### 直接注入`request`/`session`引用
作为**工厂作用域的替代方案**,Spring `WebApplicationContext`还支持将
`HttpServletRequest`、`HttpServletResponse`、`HttpSession`、`WebRequest`
和(如果存在 JSF)`FacesContext`和`ExternalContext`直接注入到Spring管理的Bean中,
只需通过基于类型的自动装配即可,与普通Bean的其他注入点一起。
Spring 通常为这些请求和会话对象注入代理,这样做的好处是可以在单例Bean和可序列化Bean中正常工作,类似于工厂作用域Bean的作用域代理。
## 自定义作用域
Bean作用域机制是可扩展的。你可以定义自己的作用域,甚至重新定义现有的作用域,尽管后者被认为是不良实践,而且你不能覆盖内置的`singleton`和`prototype`作用域。
### 创建自定义 Scope
要将自定义作用域集成到Spring容器中,你需要实现`org.springframework.beans.factory.config.Scope`接口,该接口在本节中有详细描述。
要了解如何实现自定义作用域,请参阅Spring框架自带的Scope实现以及[Scope](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/beans/factory/config/Scope.html)
javadoc,其中更详细地解释了你需要实现的方法。
Scope 接口有四个方法用于从作用域中获取对象、将它们从Scope中移除,以及让对象被销毁。
**获取作用域内的对象**
例如,会话作用域的实现会返回会话作用域的Bean(如果不存在,则该方法会返回该Bean的新实例,并将其绑定到会话中以供将来引用)。
以下方法返回底层作用域中的对象:
```xml
Object get(String name, ObjectFactory> objectFactory)
```
**移除作用域内的对象**
例如,会话作用域的实现会从底层会话中移除会话作用域的Bean。
应该返回对象,但如果找不到指定名称的对象,则可以返回`null`。以下方法从底层作用域中移除对象:
```xml
Object remove(String name)
```
**注册销毁回调**
以下方法注册一个回调(callback),该回调在作用域被销毁 或 作用域中的指定对象被销毁时调用:
```xml
void registerDestructionCallback(String name, Runnable destructionCallback)
```
参阅 [javadoc](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/beans/factory/config/Scope.html#registerDestructionCallback)
或 Spring Scope 的实现,以了解更多关于销毁callback的信息。
**获取会话标识符**
以下方法获取底层作用域的会话标识符(conversation id):
```java
String getConversationId()
```
对于每个作用域,这个标识符是不同的。对于会话作用域的实现,这个标识符(id)可以是会话标识符(session id)。
### 使用自定义 Scope
在编写和测试一个或多个自定义Scope实现之后,你需要让Spring容器知道你的新作用域。
下面的方法是向Spring容器注册新Scope的核心方法:
```java
void registerScope(String scopeName, Scope scope);
```
该方法声明在`ConfigurableBeanFactory`接口上,可以通过大多数Spring ApplicationContext实现中的BeanFactory属性访问到。
`registerScope(..)`方法的第一个参数是与作用域相关联的唯一名称。
Spring容器本身中的示例名称包括`singleton`和`prototype`。
`registerScope(..)`方法的第二个参数是你希望注册和使用的自定义Scope实现的实际实例。
假设你编写了自定义的Scope实现,并按下面的示例进行注册:
::: note
下面的示例使用了`SimpleThreadScope`,它包含在Spring中,但不是默认注册的。对于你自己的自定义Scope实现,注册的步骤是相同的。
:::
```java
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
```
接下来可以创建符合你自定义Scope规则的Bean定义,示例如下:
```xml
```
使用自定义Scope实现,你不仅可以通过编程方式注册作用域,还可以通过使用`CustomScopeConfigurer`类进行声明性的作用域注册,示例如下:
```xml
```
::: note
当你将``放置在`FactoryBean`实现的``声明内部时,作用域的是工厂Bean本身,而不是从`getObject()`返回的对象。
:::
================================================
FILE: src/md/spring-framework/core/child-bean-definitions.md
================================================
---
title: Bean定义继承
author: 流浪码客
isOriginal: true
date: 2024-04-10
category: Spring
tag: Spring Framework
---
# Bean定义继承
一个Bean定义可以包含大量的配置信息,包括构造函数参数、属性值以及容器特定的信息,比如初始化方法、静态工厂方法名称等等。
一个子Bean定义会从父定义中继承配置数据。子定义可以根据需要覆盖一些值或添加其他值。
使用父子Bean定义可以节省大量的输入工作。实际上,这是一种模板化的形式。
如果你以编程方式使用`ApplicationContext`接口,子Bean定义由`ChildBeanDefinition`类表示。
大多数用户不会在这个层面上直接操作它们。相反,他们会在诸如`ClassPathXmlApplicationContext`之类的类中以声明性方式配置Bean定义。
当你使用基于XML的配置元数据时,可以通过使用`parent`属性指定父Bean来表示子Bean定义,将父Bean作为此属性的值。以下示例展示了如何这样做:
```xml
```
子Bean定义如果未指定Bean类,则会使用父定义中的Bean类,但也可以覆盖它。
在后一种情况下,子Bean类必须与父Bean类兼容(即,它必须接受父Bean的属性值)。
子Bean定义从父Bean继承作用域(Scope)、构造函数参数值、属性值和方法重写,并有添加新值的选项。
你指定的任何作用域、初始化方法、销毁(destroy)方法或静态(static)工厂方法设置都会覆盖相应的父设置。
剩余的设置始终来自于子Bean定义:依赖(depends on)、自动注入(autowire)模式、依赖检查、singleton以及懒加载(lazy init)。
在上面的示例中,通过使用`abstract`属性显式地将父Bean定义标记为抽象。
如果父定义没有指定类,则需要显式地将父Bean定义标记为抽象,如下面的示例所示:
```xml
```
父Bean定义不能单独实例化,因为它是不完整的,并且也明确标记为`abstract`。
当一个定义是`abstract`的时候,它只能作为纯模板Bean定义使用,用作子定义的父定义。
尝试单独使用这样的`abstract`父Bean,通过将其作为另一个Bean的`ref`属性引用或者显式地使用`getBean()`方法调用父Bean的ID,都会返回错误。
同样,容器的内部`preInstantiateSingletons()`方法会忽略那些被定义为抽象的Bean定义。
::: note
`ApplicationContext` 默认预设了所有的单例Bean。因此,重要的是(至少对于单例Bean来说),
如果你有一个(父)Bean定义,你打算只作为模板使用,并且这个定义指定了一个类,你必须确保将`abstract`属性设置为
true,否则应用上下文将尝试预实化`abstract` Bean。
:::
================================================
FILE: src/md/spring-framework/core/dependencies/README.md
================================================
---
title: 依赖(Dependencies)
author: 流浪码客
isOriginal: true
date: 2024-03-26
category: Spring
tag: Spring Framework
---
# 依赖(Dependencies)
企业应用程序通常不是由单个对象(或`Spring`术语中的`Bean`)构成的。
即使是最简单的应用程序,也是由多个对象共同协作来呈现给最终用户一个连贯的应用体验。
下一节将解释如何从定义独立的Bean开始,逐步实现一个完整的应用程序。
在这个应用程序中,各个对象将相互协作,实现一个共同的目标。
## 章节摘要
* [依赖注入](./factory-collaborators.md)
* [依赖和配置详解](./factory-properties-detailed.md)
* [使用depends-on](./factory-dependson.md)
* [懒加载Bean](./factory-lazy-init.md)
* [自动装配协作者](./factory-autowire.md)
* [方法注入](./factory-method-injection.md)
================================================
FILE: src/md/spring-framework/core/dependencies/factory-autowire.md
================================================
---
title: 自动装配协作者
author: 流浪码客
isOriginal: true
date: 2024-03-26
category: Spring
tag: Spring Framework
---
# 自动装配协作者(Autowiring Collaborators)
Spring容器可以自动装配协作Bean之间的关系。
你可以通过检查`ApplicationContext`的内容,让Spring自动为你的Bean解析协作对象(其他Bean)。
**自动装配的优势**
* **减少手动配置**:自动装配可以显著减少对手动指定属性或构造方法参数的需求。
[其他机制](https://docs.spring.io/spring-framework/reference/core/beans/child-bean-definitions.html)
,如bean模板,在这方面也是非常有价值的。
* **动态更新配置**:随着项目的发展,对象可能会增加新的依赖。自动装配能够适应这种变化,自动满足新的依赖关系,而无需手动更新配置。
这一点在项目的迭代开发过程中尤为有用。同时,当项目稳定下来后,开发者仍然可以选择切换到显式装配,以获得更精确的控制。
**四种自动装配模式**
在使用XML配置时,
(参阅 [依赖注入](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html)),
可以通过``元素的`autowire`属性来定义Bean的自动装配模式。
Spring提供了四种自动装配模式,允许你为每个Bean单独指定使用哪种模式。以下是这四种模式的详细描述:
| 模式 | 描述 |
|-------------|----------------------------------------------------------------------------------------------------------------------------------------------|
| no | **默认模式**:
不执行自动装配。开发者需要使用`ref`标签显式声明依赖关系。 |
| byName | **按名称自动装配**:
容器会寻找一个属性名称或setter方法名称相匹配的Bean定义将其注入。
例如:Bean的属性名为`master`(也就是说,它有一个`setMaster(..)`方法),Spring会寻找名为`master`的Bean定义来注入。 |
| byType | **按类型自动装配**:
容器会寻找与属性类型相匹配的Bean定义将其注入。如果存在多个相同类型的Bean,将抛出异常。 |
| constructor | **按构造函数参数类型自动装配**:
容器会寻找与构造函数参数类型相匹配的Bean定义将其注入。如果没有匹配的Bean定义,将抛出异常。这种模式确保了Bean在创建时其必需的依赖就已经被满足。 |
利用`byType`或`constructor`自动装配模式,你可以装配数组(`array`)和泛型集合(`collection`)。
在这种模式下,Spring容器会自动寻找所有与所需类型相匹配的Bean,并将其注入到定义的数组或集合中,从而满足配置的依赖关系。
此外,当预期的键(key)类型为`String`时,还可以自动装配一个强类型化的`Map`实例。
在这个`Map`中,键(key)将是Bean的名称,而值(value)则是所有匹配期望类型的Bean实例。
> 这样的自动装配`Map`实例,为我们提供了一种便捷的方式来管理和访问一组具有相同类型的Bean,特别是当我们需要根据名称快速查找特定的Bean时。
## 自动装配的局限和缺点
自动装配在项目中保持一致性时效果最佳。如果自动装配没有被普遍采用,而只有少数Bean定义使用它,可能会引起开发人员的困惑。
以下是考虑自动装配时需要注意的限制和缺点:
1. **显式依赖优先**:当存在显式依赖(`property`或`constructor-arg`)时,自动装配将被忽略
2. **不适用于基本类型**:自动装配不适用于基本类型,如`int`、`long`、`String`等,因为它们没有Bean名称
3. **精确性问题**:自动装配不如显式注入精确,因为它依赖于类型匹配,而不是Bean的名称或其他限定符,这可能导致意外的结果
4. **文档化缺失**:自动装配的依赖关系可能不会自动包含在生成的文档中,因此开发者需要额外的文档或注释来解释Bean之间的依赖关系
5. **多重匹配问题**:当存在多个Bean定义与自动装配的属性类型匹配时,Spring将抛出异常
6. **歧义性**:当存在多个Bean定义与自动装配的属性类型匹配时,Spring将无法确定哪个Bean是首选的,就会抛出异常
你有以下几种方法来解决**多重匹配问题和歧义性**:
* 放弃自动装配,使用显式装配
* 通过将Bean定义的`autowire-candidate`属性设置为`false`来排除Bean
* 通过将Bean定义的`primary`属性设置为`true`来指定首选Bean
* 实现更细粒度的控制,可以使用基于注解的配置方式,
参阅 [基于注解的容器配置](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config.html)
## 从自动装配中排除Bean
在每个Bean定义中,你可以设置`autowire-candidate`属性来控制Bean是否标记为自动装配候选项。
* 默认情况下,`autowire-candidate`属性的值为`true`,表示Bean是自动装配的候选项
* 属性为`false`时,Bean将被排除在自动装配之外,这一设置对注解式配置
(如[@Autowired](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config/autowired.html))同样有效
::: note
**注意⚠️**:`autowire-candidate`属性仅影响基于类型的自动装配模式。
对于按名称的显示引用,`autowire-candidate`属性不起作用。 只要名称匹配,它仍然可以被注入。
:::
你还可以根据`byName`的模式匹配来限制自动装配候选项。
顶层的``元素接受一个或多个模式,并将其放在 `default-autowire-candidates` 属性中,以逗号分隔。
例如,要将自动装配候选项限制为任何**名称以Repository结尾的Bean**,提供值为`*Repository`。
```xml
```
Bean定义的`autowire-candidate`属性显式设置为 true 的值始终具有优先权。
对于这种Bean,不适用于模式匹配规则。
使用这些技术,你可以精确地控制自动装配的行为,避免不期望的依赖注入,确保Bean的自动装配符合你的设计意图。
================================================
FILE: src/md/spring-framework/core/dependencies/factory-collaborators.md
================================================
---
title: 依赖注入
author: 流浪码客
isOriginal: true
date: 2024-03-26
category: Spring
tag: Spring Framework
---
# 依赖注入
## 依赖注入(DI)是什么?
**Spring IoC(控制反转)也被称为依赖注入(DI)**
它是一个过程,对象仅通过构造参数、工厂方法参数或在**对象实例**被构造函数或工厂方法返回后,在其上设置的属性来定义它们的依赖关系。
在IoC容器创建Bean时,它会自动注入这些依赖项。 不再需要通`直接构造依赖项`或使用`服务定位器模式`等方式来管理对象的实例化或位置,
而是交由IoC容器来管理,因此称为**控制反转**。
**DI解耦**
采用依赖注入(DI)原则,可以使代码更干净简洁,同时也更有效地解耦。
通过DI,对象不需要查找其依赖,也不知道依赖的位置或类别。
因此,你的类变得更易于测试,特别是当依赖是在接口或抽象基类上时,可以使用`stub`或`mock`进行单元测试。
这种方式使代码更加整洁,同时也更符合面向对象的设计原则。
DI有两个主要的变体。 基于[构造器的依赖注入](#基于构造函数的依赖注入)和[基于Setter的依赖注入](#基于setter的依赖注入)。
## 基于构造函数的依赖注入
**基于构造函数的依赖注入**是容器(如Spring框架)调用具有多个参数的构造函数来实现,每个参数代表一个依赖项,容器负责在创建Bean时注入这些依赖项。
**与静态工厂方法的比较**构造Bean几乎是等价的。
以下示例通过构造函数进行依赖注入:
```java
public class SimpleMovieLister {
// SimpleMovieLister 依赖于 MovieFinder
private final MovieFinder movieFinder;
// 构造函数,以便Spring容器可以注入MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// 省略了实际使用注入的 MovieFinder 的业务逻辑...
}
```
这个类并没有什么特别之处。它是一个普通的POJO(简单的Java对象),不依赖于容器特定的接口、基类或注解。
### 构造函数参数解析
**1.按定义的顺序传递构造函数的参数(参数无歧义时)**
构造函数参数的解析匹配是通过使用参数的类型来完成的。
如果Bean定义中的构造函数参数不存在歧义, 那么构造函数参数的顺序就是Bean定义中的定义顺序。
这样在实例化Bean时,Spring IoC容器就会按照定义的顺序传递参数。 请考虑以下这个类:
```java
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
```
假设`ThingTwo`和`ThingThree`类没有继承关系,不存在潜在的歧义。
因此,下面的配置可以正常工作,你不需要在``元素中显示指定构造函数参数的索引或类型。
```xml
```
**2.使用`type`属性显式指定构造函数参数的类型**
当引用另一个Bean时,类型是已知的,并且可以进行匹配(就像前面的例子那样)。
但是,当使用简单类型时,比如`true`,Spring不能确定值的类型,所以在没有帮助的情况下不能通过类型进行匹配。
请考虑以下这个类:
```java
package examples;
public class ExampleBean {
// 用于计算终极答案的年数
private final int years;
// 生命、宇宙和万物的终极答案
private final String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
```
在上述情况下,可以通过使用`type`属性显式指定构造函数参数的类型,容器就对简单类型进行类型匹配,如下例所示:
```xml
```
**3.使用`index`属性显示指定构造函数参数的索引**
你还可以使用`index`属性显示指定构造函数参数的索引,如下例所示:
```xml
```
除了解决多个简单值的歧义外,指定索引还可以解决构造函数具有两个相同类型参数的歧义问题。
::: note
**注意⚠️**:索引(下标)从0开始。
:::
你还可以使用构造函数的参数名称来消除值的歧义,如下例所示:
```xml
```
**4.使用`@ConstructorProperties`指定构造函数参数名称**
请记住,要使这一方法开箱即用,代码在编译时必须启用`debug`标志,以便Spring可以从构造函数中查找参数名称。
如果你不想用`debug`标志编译你的代码,
可以使用[@ConstructorProperties](https://download.oracle.com/javase/8/docs/api/java/beans/ConstructorProperties.html)
JDK注解来显式命名你的构造函数参数。 示例类将如下所示:
```java
package examples;
public class ExampleBean {
// 省略字段
// 指定构造函数属性,以便Spring容器可以注入参数
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
```
## 基于Setter的依赖注入
**基于Setter的依赖注入(DI)** 是指容器在调用`无参构造函数`或`无参静态工厂方法`实例化Bean后,调用Setter方法来实现的。
以下示例展示了一个只能通过Setter进行依赖注入的类。这个类是传统的Java类,是一个普通的POJO,不依赖于容器特定的接口、基类或注解。
```java
public class SimpleMovieLister {
// SimpleMovieLister 依赖于 MovieFinder
private MovieFinder movieFinder;
// Setter方法,以便Spring容器可以注入MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// 省略了实际使用注入的 MovieFinder 的业务逻辑...
}
```
**ApplicationContext的依赖注入支持**:`ApplicationContext`在管理的`Bean`时支持基于构造函数和基于Setter的依赖注入(DI)。
此外,它还支持通过构造函数注入依赖项后,再使用Setter方法注入其他依赖项。
**配置依赖关系和属性转换**:你可以通过`BeanDefinition`来配置依赖关系,并利用`java.beans.PropertyEditor`
接口实例将属性值从一种格式转换为另一种格式。
**Spring用户的使用方式**:然而,大多数Spring用户并不直接使用这些类(即以编程方式),而是使用XML Bean定义、
注解组件(即使用`@Component`、`@Controller`等注解的类),
或基于Java的`@Configuration`类中的`@Bean`方法。
**内部转换为BeanDefinition**:然后,这些来源在内部被转换为`BeanDefinition`的实例,并用于加载整个Spring IoC容器实例。
::: tip 基于构造器的DI还是基于Setter的DI?
通常建议对于必要的依赖项使用构造函数注入(DI),对于可选的依赖项使用Setter方法或配置方法进行注入。
需要注意的是,在Setter方法上使用[@Autowired](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config/autowired.html)
注解也可以使属性标记为必需的依赖项;但是,带有参数的程序化验证的构造函数注入更为推荐。
Spring团队倡导使用构造函数注入,它允许你将应用程序组件实现为不可变`final`对象,并确保了所需的依赖项不是`null`。
此外,构造函数注入组件总是以完全初始化的状态返回给客户端,这有利于提高稳定性和可预测性。
另一方面,Setter注入适用于可选的依赖项,可以在类内部设置默认值。
但是,它需要在整个代码库使用依赖性的地方进行额外的`null`值检查,以处理依赖项未设置的情况。
通过[JMX MBean](https://docs.spring.io/spring-framework/reference/integration/jmx.html)进行管理是Setter注入的一个很好的用例。
在某些情况下,选择构造函数注入或Setter注入可能由类本身决定,特别是当处理没有源代码的第三方类时。
例如,如果第三方类没有暴露任何Setter方法,则构造函数注入可能是唯一可用的DI形式。
:::
## 依赖的解析过程
容器执行Bean依赖解析的步骤如下:
1. **容器初始化**:
* `ApplicationContext` 是 Spring 容器的接口,用于创建和管理 Bean
* 配置元数据可以是 XML、Java 代码或注解,它们描述了 Bean 的配置信息
2. **依赖表达方式**:
* 依赖可以表达为属性、构造函数参数或静态工厂方法的参数
* Spring 会根据依赖表达方式来创建和注入依赖的 Bean
3. **属性和构造函数参数定义**:
* 属性或构造函数参数可以是值的定义,也可以是对容器中另一个 Bean 的引用
* **属性值转换**:对于值的定义,Spring 会将以字符串格式提供的值转换为所有内置类型,如`int`、`long`、`String`、`boolean`等等
4. **容器验证和Bean创建**:
* 容器在创建时,会验证每个Bean的配置
* 容器被创建时,单例作用域并被设置为预实例化的Bean(默认)被创建。
作用域范围在[Bean Scope](https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html)中定义
* 非单例Bean只有在被请求时才会被创建
5. **Bean的依赖项解析**:
* 创建Bean可能会导致创建Bean Graph,因为Bean的依赖项及其依赖项的依赖项(以此类推)会被创建和分配
6. **注意⚠️:解析不匹配问题**
* 依赖项之间的解析不匹配可能会在创建受影响的 Bean 时才会出现问题
::: tip 循环依赖
循环依赖是指在使用主要基于构造函数的依赖注入时,可能会创建一个无法解决的循环依赖场景。
例如:Class A通过构造函数注入需要Class B的实例,而Class B通过构造函数注入需要Class A的实例。
如果配置Bean A和Bean B相互注入对方,Spring IoC容器会在运行时检测到这种循环引用,并抛出`BeanCurrentlyInCreationException`
异常。
解决这种问题的一个可能方法是编辑某些类的源代码,改为通过Setter方法配置,而不是构造函数。
另一个方法是避免构造函数注入,只使用Setter注入。换句话说,虽然不推荐,但你可以通过Setter注入配置循环依赖。
与典型情况(没有循环依赖)不同,Bean A和Bean B之间的循环依赖会导致其中一个Bean在自身完全初始化之前被注入到另一个Bean中(经典的鸡生蛋蛋生鸡的情况)。
:::
**Spring容器的行为**: 通常情况下,你可以相信Spring会做正确的事情。
它在容器加载时检测配置问题,例如引用不存在的Bean或存在循环依赖等。
**异常生成可能性**: Spring会尽可能地延迟设置属性和解析依赖,直到真正创建Bean时才会进行。
这意味着,一个正确加载的Spring容器在请求对象时可能会生成异常;例如在创建该对象或其依赖关系时出现问题,Bean由于缺少或无效属性而抛出异常。
**ApplicationContext的预实例化**: 这种潜在的延迟暴露一些配置的情况,是`ApplicationContext`实现默认预先实例化单例Bean的原因。
在实际创建这些Bean之前,尽管需要花费一些前期时间和内存代价,但这样做可以在创建`ApplicationContext`时发现配置问题,而不是之后。
**覆盖默认行为**: 你仍然可以覆盖这种默认行为,使得单例Bean延迟(懒加载)初始化,而不是预先实例化。
## 依赖注入的例子
### Setter依赖注入
以下示例使用基于XML的配置元数据来实现基于Setter的依赖注入。Spring XML配置文件的一小部分如下所示,指定了一些`Bean`定义:
```xml
```
以下示例展示了相应的`ExampleBean`类:
```java
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
```
### 构造函数依赖注入
在上面的例子中,Setter 方法被声明来匹配 XML 文件中指定的属性。以下示例使用基于构造函数依赖注入(DI):
```xml
```
对应的`ExampleBean`类如下所示:
```java
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public ExampleBean(
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
}
```
在`Bean`定义中指定的构造函数参数将作为`ExampleBean`的构造函数参数使用。
### 静态工厂方法依赖注入
现在考虑这个示例的一个变体,在这个变体中,不是使用构造函数,而是告诉 Spring 调用一个静态工厂方法来返回对象的实例:
```xml
```
以下示例展示了相应的`ExampleBean`类:
```java
public class ExampleBean {
// 私有构造函数
private ExampleBean(...) {
...
}
// 静态工厂方法;该方法的参数可以被视为返回的Bean的依赖项,
// 不管这些参数实际上是如何使用的。
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// 其他操作...
return eb;
}
}
```
静态工厂方法的参数由``元素提供,与实际使用构造函数时完全相同。
被工厂方法返回的类的类型不一定与包含静态工厂方法的类的类型相同(尽管在这个例子中,它是相同的)。
实例(非静态)工厂方法可以以基本相同的方式使用(除了使用`factory-bean`属性而不是`class`属性),因此我们不在此讨论这些细节。
================================================
FILE: src/md/spring-framework/core/dependencies/factory-dependson.md
================================================
---
title: 使用depends-on
author: 流浪码客
isOriginal: true
date: 2024-03-26
category: Spring
tag: Spring Framework
---
# 使用depends-on
如果一个Bean是另一个Bean的依赖项,这意味着一个Bean被设置为另一个Bean的属性。
可以通过[元素](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-dependson.html#beans-ref-element)
来实现这一点。 然而,有时Bean之间的依赖关系并不那么直接。
举个例子:当一个类中的静态初始化器需要被触发时,比如数据库驱动程序的注册。
`depends-on`属性可以强制容器在初始化`beanOne` Bean之前先初始化指定的`manager` Bean。
以下示例使用`depends-on`属性来表达对单个Bean的依赖:
```xml
```
要表达对多个Bean的依赖,可以将多个Bean名称作为`depends-on`属性的值提供(通过逗号、空格和分号进行分隔):
```xml
```
::: note
`depends-on`属性不仅可以指定初始化时的依赖关系,而且在
[单例](https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html#beans-factory-scopes-singleton)
Bean的情况下,还可以指定相应的销毁时依赖关系。
`depends-on`指定的依赖Bean会在给定Bean本身被销毁之前被首先销毁。因此,`depends-on`也可以控制关闭顺序。
:::
================================================
FILE: src/md/spring-framework/core/dependencies/factory-lazy-init.md
================================================
---
title: 懒加载(Lazy Initialization)Bean
author: 流浪码客
isOriginal: true
date: 2024-03-26
category: Spring
tag: Spring Framework
---
# 懒加载(Lazy Initialization)Bean
在Spring框架中,`ApplicationContext`的默认行为是在启动过程中立即创建并配置所有的单例Bean。
这种做法有利于及时发现配置错误或环境问题,避免了错误在应用运行一段时间后才暴露。
然而,如果需要改变这一行为,可以通过设置Bean定义为懒加载(lazy-initialized)来实现。
这样一来,Bean的实例化将被推迟到第一次实际请求该Bean时进行,而不是在应用启动时完成,从而提供了更大的灵活性和控制。
在XML中,通过``元素上的`lazy-init`属性来控制这种行为,如下例所示:
```xml
```
当上述配置被`ApplicationContext`加载启动时,`lazy` Bean不会立即预实例化,而`not.lazy` Bean则会被急切地预实例化。
然而,当一个懒加载的Bean作为另一个未标记为懒加载单例Bean的依赖项时,`ApplicationContext`会在启动时创建这个懒加载的Bean,
因为它必须满足单例Bean的依赖关系。懒加载的Bean会被注入到一个未标记为懒加载的单例Bean中。
你还可以通过在``元素上使用`default-lazy-init`属性来控制容器级别的懒加载初始化,如下例所示:
```xml
```
================================================
FILE: src/md/spring-framework/core/dependencies/factory-method-injection.md
================================================
---
title: 方法注入
author: 流浪码客
isOriginal: true
date: 2024-03-26
category: Spring
tag: Spring Framework
---
# 方法注入
大多数应用场景中,容器中的大多数Bean都是[单例(singleton)](https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html#beans-factory-scopes-singleton)
的。
当一个单例(singleton)Bean需要与原型(prototype)Bean协作时,传统的注入方式可能不再适用。
这是因为单例Bean在整个应用生命周期内只创建一次,而原型Bean每次请求时都会创建一个新的实例。
一种解决方案是放弃一些控制反转(inversion of control)。
通过实现[ApplicationContextAware](https://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html#beans-factory-aware)
接口使Bean A意识到Spring IoC容器 ,并
[使用容器](https://docs.spring.io/spring-framework/reference/core/beans/basics.html#beans-factory-client)
进行`getBean("B")`调用,每次Bean A需要时请求(新建`new`)Bean B实例。 以下示例展示了这种方法:
```java
package fiona.apple;
// 导入 Spring 框架相关类
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* 使用一个有状态的 Command-style 类来执行一些处理的类。
*/
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// 获取一个适当的 Command 的新实例
Command command = createCommand();
// 设置 Command 的状态
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// 注意 Spring API 的依赖!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
```
上述方法并不理想,因为业务代码意识到并与Spring框架耦合。
方法注入(Method Injection)是Spring IoC容器的一种高级特性,可以干净地处理这种用例。
> 你可以在[这篇博客文章](https://spring.io/blog/2004/08/06/method-injection/)中阅读更多关于方法注入的动机。
## 查找方法依赖注入
## 任意方法替换
================================================
FILE: src/md/spring-framework/core/dependencies/factory-properties-detailed.md
================================================
---
title: 依赖和配置详解
author: 流浪码客
isOriginal: true
date: 2024-03-26
category: Spring
tag: Spring Framework
---
# 依赖和配置详解
正如前一节所述,您可以将`Bean`属性和构造函数参数定义为对其它==托管Bean(协作者)的引用==或==内联定义的值==。
Spring基于XML的配置元数据支持``和``元素内的子元素类型,以达到这个目的。
## 字面值 (基本类型、 String 等)
``元素的`value`属性指定了一个属性或构造函数参数的可读字符串表示。
Spring使用 [转换服务](https://docs.spring.io/spring-framework/reference/core/validation/convert.html#core-convert-ConversionService-API)
来将这些值从字符串转换为属性或参数的实际类型。以下示例展示了设置各种值的方式:
```xml
```
以下示例使用了[p-命名空间](#使用p-命名空间的xml快捷方式),使得 XML 配置更加简洁:
```xml
```
前面的 XML 更加简洁。但是,拼写错误是在运行时而不是设计时被发现。
除非你使用支持在创建 Bean 定义时进行自动属性完成的集成开发环境(例如[IntelliJ IDEA](https://www.jetbrains.com/idea/)
或[Spring Tools for Eclipse](https://spring.io/tools)),强烈建议使用这类 IDE 的帮助。
你也可以配置一个`java.util.Properties`实例,示例如下:
```xml
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
```
Spring容器会利用JavaBeans的`PropertyEditor`机制,将``元素内的文本转换为`java.util.Properties`实例。
这是一个很好的快捷方式,Spring团队中在一些场景中更倾向于使用嵌套的``元素而不是`value`属性的方式。
### idref元素
`idref`元素仅仅是将容器中另一个`Bean`的id(一个字符串值,而不是引用)
传递给``或``元素的一种防错方式。下面的例子展示了如何使用它。
```xml
```
上面的 Bean 定义片段(在运行时)与以下代码片段完全等价:
```xml
```
第一种形式比第二种形式好,使用`idref`标签可以在部署时验证引用的`Bean`是否存在,
避免在第二种情况下,当实例化时才发现拼写错误或不存在的引用(并且可能导致致命的结果)。这提高了配置文件的可靠性和易读性。
::: note
4.0 版Bean XSD中,不再支持`idref`元素上的`local`属性,因为它与常规的Bean引用没有区别。
在升级到4.0模式时,将你现有的`idref`局部引用改为`idref bean`。
:::
``元素带来价值的一个常见地方(至少在早于Spring 2.0的版本中)是在`ProxyFactory` Bean
定义中配置 [AOP interceptor(拦截器)](https://docs.spring.io/spring-framework/reference/core/aop-api/pfb.html#aop-pfb-1)
。当你指定拦截器名称时,使用``元素可以防止你把拦截器的ID拼错。
## 对其他Bean的引用(协作者)
**`ref`元素使用`bean`属性引用其他Bean**
`ref`元素作为``或``定义元素的子元素,可以将一个Bean的属性值设置为容器管理的另一个Bean(协作者)的引用。
这个被引用的Bean是依赖项,它将在属性设置之前被初始化。(如果这个协作者Bean是单例的,可能被容器进行预先初始化)
作用域(scope)和验证取决于你是否通过`bean`或`parent`属性指定了其他对象的ID或名称。
最常见的使用方式是通过``标签的`bean`属性来指定目标Bean。
这样做可以创建对同一容器或父容器中任何`Bean`的引用,无论它们是否定义在同一个XML文件中。
使用`bean`属性时,你可以指定目标Bean的`id`或`name`属性中的一个值。
以下是一个使用``元素的例子:
```xml
```
**`ref`元素使用`parent`属性引用父容器中的Bean**
通过`parent`属性,可以创建对当前容器的父容器中的Bean的引用。
`parent`属性的值可以与目标`Bean`的`id`属性或`name`属性中的一个值相同。 目标`Bean`必须在当前容器的父容器中。
这种方式特别适用于当你有一个容器层次结构,并且希望在子容器中引用父容器中的 Bean 时,且代理具有与父 Bean 具有相同名称的 Bean。
以下是如何使用`parent`属性的示例:
```xml
```
```xml
class="org.springframework.aop.framework.ProxyFactoryBean">
```
::: note
4.0 版Bean XSD中,`ref`元素上的`local`属性不在支持,因为它与常规的Bean引用没有区别。
在升级到4.0模式时,将你现有的`ref`局部引用改为`idref bean`。
:::
## 内部 Bean
在``或``元素内部使用``元素定义了一个内部Bean,如下例所示。
```xml
```
内部Bean定义不需要指定ID或名称。如果指定了,容器不会将它们作标识符。
容器在创建时也会忽略作用域(scope)标志,因为内部Bean总是匿名的,并且始终与外部Bean一起创建。
无法独立地访问内部Bean,也无法将其注入到除封闭Bean之外的其他协作Bean中。
作为一个特例,可以从自定义作用域(scope)中接收销毁回调,例如,对于包含在单例Bean中的请求作用域(scope)的内层Bean。
内层Bean实例的创建与其包含的Bean相关联,但是销毁回调允许它参与到请求作用域(scope)的生命周期中。
这并不是一种常见的情况。内层Bean通常只是简单地共享其包含Bean的作用域(scope)。
## 集合(Collection)
`
`、``、``和`` 元素分别用于设置 Java 集合类型`List`、`Set`、`Map`和`Properties`的属性和参数。
以下示例展示了如何使用它们:
```xml
administrator@example.org
support@example.org
development@example.org
a list element followed by a reference
just some string
```
`Map`的键值对中的值、或者`Set`中的元素,可以是以下任一元素:
```
bean | ref | idref | list | set | map | props | value | null
```
### 集合合并(merging)
Spring 容器还支持集合合并。开发者可以定义一个父级`
`、``、``或``元素,
并让子级`
`、``、`` 或 ``元素继承并覆盖父集合中的值。
也就是说,子集合的值是合并父子集合的元素后的结果,其中子集合的元素会覆盖在父集合中指定的值。
这部分关于合并(merging)的内容讨论了父子Bean机制。对于不熟悉父子Bean定义的读者,建议在继续阅读之前阅读[相关章节](https://docs.spring.io/spring-framework/reference/core/beans/child-bean-definitions.html)。
以下示例演示了集合合并(merging):
```xml
administrator@example.com
support@example.com
sales@example.com
support@example.co.uk
```
请注意,在子Bean定义的`adminEmails`属性的``元素上使用了`merge=true`属性。
当容器解析并实例化子Bean时,生成的实例具有一个`adminEmails Properties`集合,
该集合是合并子Bean的 `adminEmails`集合与父Bean的`adminEmails`集合的结果。
以下列表显示了合并的结果:
```properties
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
```
子代`Properties`集合的值集继承了父``中的所有属性元素,并且子集合中对于`support`键的值会覆盖父代集合中的值。
这种合并行为同样适用于`
`、`` 和 ``等集合类型。在`
`元素的特定情况下,与List集合类型相关的语义(即有序值集合的概念)被保留。
父集合的值位于子列表所有值之前。对于Map、Set和Properties集合类型,不存在任何排序。因此,在容器内部使用的关于
Map、Set和Properties实现的集合类型,没有排序语义。
### 集合合并的限制
你不能合并不同的集合类型(例如 Map 和 List)。如果你试图这样做,会抛出一个适当的`Exception`异常。
合并属性(merge attribute)必须在较低层级、被继承的子定义上指定。
在父级集合定义上指定`merge`属性是多余的,不会产生期望的合并行为。
### 强类型集合
得益于Java的泛型特性,你能够创建特定类型的强类型的集合(Collection)。
例如,可以声明一个Collection类型,使其只能包含(例如)String元素。
当你通过Spring进行依赖注入时,Spring的类型转换功能将确保Collection中的元素在添加之前被自动转换为正确的类型。
以下示例 Java 类和 Bean 定义展示了如何实现这一点:
```java
public class SomeClass {
private Map accounts;
public void setAccounts(Map accounts) {
this.accounts = accounts;
}
}
```
```xml
```
当准备注入`something` Bean 的`accounts`属性时,关于强类型`Map`的元素类型的泛型信息可通过反射获得。
因此,Spring 的类型转换基础设施能够识别出这些值元素属于 `Float` 类型,并将字符串值(9.99、2.75 和 3.99)转换为实际的 `Float` 类型。
## Null和空字符串值
Spring 将属性等的空参数视为空字符串。下面这个基于XML的配置元数据片段将`email`属性设置为空字符串值(`""`)。
```xml
```
上述示例等同于以下Java代码:
```java
exampleBean.setEmail("");
```
## 使用 p-命名空间
p-命名空间提供了一种便捷的XML配置方式,允许你直接在`bean`
元素的属性中定义属性值或引用其他Bean,而无需使用嵌套的``标签。
Spring框架支持通过XML Schema定义的扩展配置格式和命名空间。
虽然`beans`的配置格式在XML Schema文档中有所定义,但p-命名空间并未在XSD文件中声明,它是Spring框架内部的一个特性。
**简化bean属性设置**
以下是一个示例,展示了两种不同的XML配置片段(标准XML格式和使用p-命名空间的格式),它们将产生相同的配置结果:
```xml
```
这个示例展示了在Bean定义中,p-命名空间中有一个名为`email`的属性。这告诉Spring要包含一个属性声明。
如前所述,p-命名空间没有Schema定义,所以你可以直接使用属性名作为属性值,而无需按照常规方式指定属性值。
**简化bean属性引用**
以下示例包含了两个bean定义,它们都引用了另一个bean:
```xml
```
此示例不仅使用p-命名空间声明了属性值,还使用了一种特殊的格式来声明属性的引用。
第一个bean定义使用了传统的标签来创建从john到jane的引用,
而第二个bean定义则使用了`p:spouse-ref="jane"`属性,以更简洁的方式实现了相同的依赖注入。
* `p:name`:这是使用p-命名空间设置name属性值的简洁方式
* `p:spouse-ref`:这是使用p-命名空间声明spouse属性的引用,`-ref`后跟被引用Bean
::: note
注意⚠️:p-命名空间不如标准XML格式灵活。例如,声明属性引用的格式与以`Ref`结尾的属性发生冲突,而标准XML格式则不会。
我们建议你谨慎选择这种方式,并将此决策传达给你的团队成员,以避免同时使用这三种格式的XML文档。
:::
## 使用 c-命名空间
与带有[p-命名空间的XML快捷方式](#使用-p-命名空间)类似,
Spring 3.1中引入的c-命名空间,允许使用内联属性来配置构造函数参数,而不是嵌套的`constructor-arg`元素。
以下示例使用c-命名空间来实现与基于构造函数依赖注入相同的功能:
```xml
```
c-命名空间与p-命名空间使用相同的约定(以`-ref`结尾表示Bean引用)来通过名称设置构造函数参数。
同样,即使c-命名空间在XSD模式中未定义(它存在于Spring核心中),但在XML文件中仍然需要声明。
对于构造函数参数名称不可用的罕见情况(通常是因为字节码是在没有调试debug信息的情况下编译的),可以使用参数索引(下标)作为备用,如下所示:
```xml
```
::: note
由于XML语法限制,索引表示法需要以下划线(_)开头,因为XML属性名不能以数字开头(尽管一些IDE允许这样做)。
对于``元素,也有相应的索引表示法可用,但并不常用,因为通常情况下,普通的声明顺序已经足够了。
:::
在实践中,构造函数解析机制在匹配参数方面非常高效,因此除非确实需要,我们建议在整个配置中始终使用名称标记。
## 复合属性名
在设置Bean属性时,只要路径中除最终属性名外的所有组件不为null,就可以使用复合或嵌套的属性名称。以下是一个Bean定义的示例:
```xml
```
在这个示例中,`something` Bean有一个`fred`属性,该属性又有一个`bob`属性,属性下又有一个`sammy`属性,最终`sammy`属性被设置为值 123。
为了使这个设置生效,除了最终的属性名`sammy`外,要保证路径中的所有属性不为 `null`。否则,将抛出 NullPointerException 异常。
================================================
FILE: src/md/spring-framework/core/ioc-container.md
================================================
---
title: IoC 容器(Container)
author: 流浪码客
isOriginal: true
date: 2024-03-18
category: Spring
tag: Spring Framework
---
# IoC 容器(Container)
## 概述
> Spring IoC(控制反转)容器是Spring框架的核心。
> `org.springframework.context.ApplicationContext`接口代表Spring IoC容器,负责实例化、配置和组装`Bean`。
Spring提供了几个 ApplicationContext 接口的实现,在独立应用程序中,最常用的是:
* [ClassPathXmlApplicationContext](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/context/support/ClassPathXmlApplicationContext.html)
* [FileSystemXmlApplicationContext](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/context/support/FileSystemXmlApplicationContext.html)
* 等等
**支持以XML、Java注释或Java代码作为配置元数据的格式**
虽然`XML`一直是定义配置元数据的传统格式, 但你可以通过提供少量的`XML`配置来指定容器使用`Java注解`或`Java代码`作为元数据格式。
以声明式方式启用对这些元数据格式的支持,从而更灵活地定义应用程序的配置信息。
**为Web应用程序提供方便的ApplicationContext实例化**
在大多数应用场景中,无需手动编写代码来实例化**Spring IoC**容器;
例如:在Web应用场景中,通常只需要在应用程序的`web.xml`文件中编写 8
行(或更多)[模板式的Web描述符](https://docs.spring.io/spring-framework/reference/core/beans/context-introduction.html#context-create)
即可初始化`ApplicationContext`
**解析Spring框架的工作原理:==应用程序类==与==配置元数据==的整合**
下图表展示了Spring框架的工作原理高层视图。通过将你的应用程序类与配置元数据结合起来,
一旦`ApplicationContext`被创建和初始化,你就获得了一个完全配置且可执行的系统或应用程序。

## 配置元数据
如上图所示,Spring IoC容器消费配置元数据。
这种配置元数据代表了你作为一个应用开发者,如何告诉Spring容器在你的应用中实例化、配置和组装对象。
::: note
**注意⚠️**:Spring IoC容器本身与实际配置元数据的编写格式完全解耦。
如今,许多开发者选择使用[基于Java的容器配置](https://docs.spring.io/spring-framework/reference/core/beans/java.html)
来构建他们的Spring应用程序。
:::
有关在`Spring`容器中使用其他形式的元数据信息,参阅:
* [基于XML的容器配置](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html)
* [基于注解的容器配置](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config.html)(Spring
2.5开始支持)
* [基于Java的容器配置](https://docs.spring.io/spring-framework/reference/core/beans/java.html)(Spring
3.0开始支持;参阅 [@Configuration](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/context/annotation/Configuration.html), [@Bean](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/context/annotation/Bean.html),
[@Import](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/context/annotation/Import.html),
和 [@DependsOn](https://docs.spring.io/spring-framework/docs/6.1.5/javadoc-api/org/springframework/context/annotation/DependsOn.html)
注解)
Spring的配置包含至少一个,通常是多个``元素。容器必须管理这些定义的bean。
* XML配置:将这些Bean配置为顶层 `` 元素内的 `` 元素
* Java配置:将这些Bean配置为`@Configuration`类中的`@Bean`注解的方法
这些Bean的定义对应于构成应用程序的实际对象,
如服务层对象,持久层对象(Dao),表示层对象(Web控制器),基础设施对象(JPA EntityManagerFactory),JMS队列等。
通常,人们不会在容器中配置细粒度的`domain`对象,因为创建和加载`domain`对象的通常是`repository`和`service`层逻辑的责任。
下面的例子显示了基于XML的配置元数据的基本结构:
```xml
(1) (2)
```
1. `id`属性是一个字符串,用于==唯一标识==Bean
2. `class`属性是一个字符串,用于指定Bean的==完整类名==(包括包名)
`id`属性的值可以用来指代其他Bean的`ref`属性,从而实现Bean之间的依赖关系。
参阅 [依赖](https://docs.spring.io/spring-framework/reference/core/beans/dependencies.html)。
## 实例化容器
提供给`ApplicationContext`
构造函数的一条或多条路径是==资源字符串==,它让容器从各种外部资源(如本地文件系统、Java `CLASSPATH`
等)加载配置元数据。
```java
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
```
::: note
了解更多关于[资源加载](https://docs.spring.io/spring-framework/reference/core/resources.html)的信息;
它提供了一种简单的方法,可以从`URI`语法中定义的位置读取`InputStream`。 特别是,`Resource`路径被用来构建应用程序上下文, 如
[Application Context和资源路径](https://docs.spring.io/spring-framework/reference/core/resources.html#resources-app-ctx)
中所述。
:::
以下示例显示了**服务层对象**`services.xml` 配置文件:
```xml
```
以下示例显示**数据访问对象**(data access object)`daos.xml` 文件:
```xml
```
在前面的示例中,服务层由 `PetStoreServiceImpl` 类和两个类型为 `JpaAccountDao` 和 `JpaItemDao` 的数据访问对象组成(基于JPA对象-关系映射标准)。
* `property name` 元素指的是`JavaBean`属性的名称
* `ref` 元素指的是引用另一个`Bean`定义的名称
`id` 和 `ref`元素之间的这种联系,表达了协作对象之间的依赖关系。
有关配置对象依赖项的详细信息,参阅 [依赖](https://docs.spring.io/spring-framework/reference/core/beans/dependencies.html)。
## 使用容器
`ApplicationContext`是一个高级工厂的接口,能够维护不同Bean及其依赖关系的注册表。
通过使用方法 `T getBean(String name, Class requiredType)`,你可以检索到Bean的实例。
`ApplicationContext`可以让你读取Bean定义(definition)并访问它们,如下例所示。
```java
// 创建和配置Bean
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// 检索配置的实例
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// 使用配置的实例
List userList = service.getUsernameList();
```
**不直接依赖于Spring的API**
> 理想情况下,应用程序代码不应该直接依赖于Spring的API,而是通过元数据(如自动装配`@Autowired`注解)声明对特定Bean的依赖。
虽然 ApplicationContext 接口提供了一些检索 Bean 的方法,如 getBean() 等,但在设计上,应该避免直接依赖这些方法。
例如,Spring与Web框架的集成为各种**Web框架组件**(如Controller控制器和JSF管理的Bean)提供了依赖注入的能力,
使得你可以通过元数据(如`@Autowired`注解)声明对特定`Bean`的依赖,而不必直接调用`getBean()`等方法。
这样可以使代码更加模块化、可维护性更高。
================================================
FILE: src/md/spring-framework/overview/README.md
================================================
---
title: Spring Framework 概述
icon: spring
author: 流浪码客
isOriginal: true
date: 2024-03-15
category: Spring
tag: Spring Framework
star: true
sticky: true
---
# Spring Framework 概述
## Spring Framework 简介
Spring是一个开源、轻量级、依赖注入(DI)容器和框架,用于构建Java企业应用程序。
* Spring 官网:[spring.io](https://docs.spring.io/spring-framework/reference/)
* Spring 中文网:[springdoc.cn](https://springdoc.cn/spring/)
## 为什么使用Spring?
> 官网解释:[why-spring](https://spring.io/why-spring)
>
> Spring让Java编程变得更快、更容易、更安全。
> Spring对速度、简单性和生产力的关注使其成为世界上最受欢迎的Java框架。
我们使用了许多Spring框架提供的工具,并受益于许多==开箱即用==的解决方案,
无需担心编写大量额外的代码,因此这确实为我们节省了时间和精力。
## 核心思想
Spring的核心思想是 **控制反转(IOC)** 和 **面向切面编程(AOP)**。
**控制反转(IoC)**
控制反转是一种设计模式,它将对象的创建和对象之间的依赖关系的管理交给了Spring IoC容器。
在传统的开发模式中,对象的创建和对象之间的依赖关系的管理都是由程序员来完成的。
**面向切面编程(AOP)**
面向切面编程是一种编程范式,它将程序的业务逻辑和系统级服务(如日志,事务,安全等)分开,通过横切关注点的方式来解耦。
在传统的开发模式中,业务逻辑和系统级服务是混在一起的,这样会导致代码的复杂性增加。
## 版本支持
我们建议尽可能从Maven Central升级到最新的**Spring Framework 6.0.x / 5.3.x** 版本
在Spring Framework 6.0中, Spring需要Java 17+。
* 6.2.x (2024年11月) - 下一个功能分支
* 6.1.x (2023年11月) - 即将推出的功能分支
* 6.0.x (2022年11月) - 主要生产线,基于JDK 17和Jakarta EE 9
* 5.3.x - 第五代最终功能分支,长期支持,支持JDK 8、11、17和Java EE 8
* 4.3.x - EOL (2020年12月31日),不再提供维护和安全补丁
* 3.2.x - EOL (2016年12月31日),不再提供维护和安全补丁
您可以在 [spring.io#support](https://spring.io/projects/spring-framework#support)上找到有关官方支持日期的更多信息。
## 入门指南
使用[Spring Boot](https://spring.io/projects/spring-boot)来快速创建生产就绪的Spring应用程序
* 您可以通过[start.spring.io](start.spring.io)生成基本项目
* 或者遵循"[入门指南](https://spring.io/guides)"
之一,例如"[开始构建RESTful Web服务](https://spring.io/guides/gs/rest-service/)"
================================================
FILE: src/md/spring-framework/overview/quickstart.md
================================================
---
title: Spring Framework 快速开始
icon: spring
author: 流浪码客
isOriginal: true
date: 2024-03-15
category: Spring
tag: Spring Framework
---
# Spring Framework 快速开始
> **IntelliJ IDEA** 提供的专用项目向导,您能够快速创建Spring应用程序,这也是创建Spring应用程序的最佳方式之一。
参考🚀 :[Jetbrains 创建第一个Spring应用程序](https://www.jetbrains.com/help/idea/your-first-spring-application.html)
在本教程中,您将学习如何公开HTTP端点并将其映射到一个方法,当用户通过Web浏览器访问时,该方法会向用户返回问候信息。
## 创建 Spring Boot项目
1. 在主菜单中,转到 **文件(File) | 新建(New) | 项目(Project)**
2. 在 **新建项目(New Project)** 对话框中,选择 `Spring Initializr`
3. 指定项目的名称 ==spring-boot-tutorial==,单击 **下一步(Next)** 继续

4. 在**Web**组下选择**Spring Web**依赖项,然后单击 **创建(Create)** 生成并设置项目

## 添加 sayHello() 方法
`Spring Initializr` 会创建一个带有 `main()` 方法的类来启动你的 Spring 应用程序。
1. 转到 "**导航(Navigate) | 文件(File )**",搜索`SpringBootTutorialApplication.java`文件并打开

2. 添加 `sayHello()` 方法,并包含所有必要的注解和导入:
```java
package com.example.springboottutorial;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class SpringBootTutorialApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootTutorialApplication.class, args);
}
@GetMapping("/hello")
public String sayHello(@RequestParam(value = "myName", defaultValue = "World") String name) {
return String.format("Hello %s!", name);
}
}
```
`sayHello()`方法接受一个名字参数,并返回与参数值组合的`Hello`单词。其余的工作由添加Spring注解来处理:
* `@RestController`注解将`SpringBootTutorialApplication`类标记为请求处理程序(==REST控制器==)。
* `@GetMapping("/hello")`注解将`sayHello()`方法==映射==到GET请求的`/hello`路径。
*` @RequestParam`注解将名字**方法参数**==映射==到`myName` Web请求参数。如果您在Web请求中未提供`myName`
参数,则默认为`World`。
## 运行Spring应用程序
**IntelliJ IDEA** 创建了一个` Spring Boot` 运行配置,您可以使用它来运行新 `Spring` 应用程序。
* 如果选择了运行配置,请按 `Shift` `F10`键。
* 还可以点击 `SpringBootTutorialApplication.java` 文件` main()`方法旁边的▶️图标运行。
默认情况下,IntelliJ IDEA 会在运行工具窗口中显示你正在运行的 Spring Boot 应用程序。

控制台选项卡显示 Spring 日志消息的输出。
默认情况下,内置的 Apache Tomcat 服务器正在监听端口 8080。
打开你的网络浏览器并访问 [http://localhost:8080/hello](http://localhost:8080/hello)。
如果你操作正确,你应该会看到你的应用程序以 Hello World! 回应。

这是默认的响应。你可以在你的网络请求中提供一个参数,让应用程序知道如何适当地问候你。
例如,尝试访问 [http://localhost:8080/hello?myName=Human](http://localhost:8080/hello?myName=Human)。
## 添加主页
创建的 Spring Boot 应用程序在 `/hello` 路径下有一个端点可用。
然而,如果你在 [http://localhost:8080/](http://localhost:8080/) 中打开你的应用程序的根上下文,你会收到错误,因为没有定义根资源。
让我们添加一个静态 HTML 首页,其中包含指向你端点的链接。
1. 请在 `/src/main/resources/static/` 下创建 `index.html` 文件。
2. 你可以修改默认模板或者使用以下 HTML 代码替换它:
```html
您的第一个 Spring 应用程序
问候全世界!
```
3.在运行工具窗口中,点击 "**重新运行**" 按钮,或按下 `Shift` + `F10`。
现在你的应用程序将会在 [http://localhost:8080/](http://localhost:8080/) 上作为根资源提供 `index.html` 页面。

## 下一个教程
这个简单的应用程序演示了如何开始使用 Spring。 要了解 IntelliJ IDEA 如何帮助你编写代码并在运行时管理应用程序,
请参考下一个教程,该教程重点介绍更高级的 [Spring 支持功能](https://www.jetbrains.com/help/idea/spring-support-tutorial.html)。
================================================
FILE: src/md/template/blog template.md
================================================
---
title: {{title}}
shortTitle:
description:
icon:
cover:
author: 流浪码客
isOriginal: true
sticky: false
star: false
date: {{date}}
category: Java Dates
tags:
- date-time
---
# {{title}}
## {{title}}