ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从HelloWorld拆解Java核心关键字

从HelloWorld拆解Java核心关键字 岔个事儿先从HelloWorld说起如果你学过Java那你一定写过下面这段代码——它几乎出现在所有教材的第一页。但有个有意思的现象很多能熟练写几百行代码的人被问到public static void main里每个单词分别是什么意思反而会卡壳。尤其是class、package、public、static、void这些Java关键字单独拎出来都认识组合在一起就说不清为什么必须这样写。这篇博文就从这行最基础的HelloWorld开始把它们一个一个拆开讲清楚各自的作用、为什么必须这样组合以及面试官在问这个问题时真正想听的是什么。package com.example.demo; /** * 第一个Java程序 */ public class HelloWorld { public static void main(String[] args) { System.out.println(Hello, World!); } }这段代码不超过十行教程里通常是“照着敲一遍就行”。但对于认真学Java的人来说每一行都藏着值得深挖的东西package和class决定了这个文件在Java世界里的“身份证号”public static void main则是整个程序被JVM启动的入口协议。这篇文章适合所有刚学Java的同学也适合准备Java基础面试想查漏补缺的人——毕竟“讲讲main方法”这个话题在面试中出现频率高得离谱。1. 先看整体一个Java程序是怎么搭起来的1.1 代码结构的分层逻辑把上面那段HelloWorld拆开你会发现Java程序其实是一个清晰的嵌套结构最外层是package包往里是class类再往里是方法method最里面是具体的执行语句。可以把它类比成一个快递包裹package是快递单上的省市区地址class是写字楼里的某个公司main方法是前台接待台而那句System.out.println干的就是“把话喊出去”的活儿。初学者最容易犯的错误之一就是把这四层混在一起看。比如有人会问“我能不能不要package能不能不用class直接写代码”答案是能跑但会留下一堆隐患。没有package的类会掉进“默认包”里项目一复杂同名类就会互相冲突没有classJava这种强类型语言根本不知道你的代码描述的是什么东西。所以这四层不是写好看的是Java语言从设计上就把“组织代码”这件事给固化下来了。1.2 一次编译、处处运行的前提源码与“JVM能懂的东西”Java的编译过程和C语言有个本质区别javac编译后生成的不是机器码而是字节码.class文件。JVM启动后再把这些字节码解释或编译成当前操作系统能识别的指令。这就是“一次编写到处运行”的前提。这里有个很多人没意识到的点package和class这两层不只是写给程序员看的也是写给JVM看的。比如你用javac -d . HelloWorld.java编译后会自动生成com/example/demo/HelloWorld.class这样的目录结构。JVM通过全限定类名com.example.demo.HelloWorld去找到并加载这个类。也就是说包名、类名和文件目录之间有着强绑定关系你写的时候不规范编译那一步就直接给你报错。2. packageJava世界的门牌号与隔离墙2.1 package到底解决了什么问题package是Java里的第一个关键字在第一个非注释行出现。它的核心作用有三个命名空间隔离、代码组织、访问控制。命名空间这块我用一个实际场景解释一下——假设你写了一个工具类叫DateUtils同事也写了一个DateUtils加入没有包的概念这两个类在同一个项目里直接冲突代码根本编译不过去。有了包你们可以用com.zhangsan.utils.DateUtils和com.lisi.utils.DateUtils区分开这就是命名空间隔离。代码组织比较好理解就像电脑里的文件夹分层一样把业务相关的类放到同一个包里项目结构会清爽得多。访问控制这块则比较隐蔽默认包没有加public的类成员只能被同一个包内的类访问这个规则和C的namespace不是一个层级的概念更接近“目录级可见性”。换句话说package在Java里还承担了一部分权限管理功能。2.2 包名的命名规范和反规则包名的命名规范有三条硬性要求全小写、不能以数字开头、不能使用Java关键字。这三条里最容易踩坑的是第二条——比如有人想包名叫util.123编译直接报错还有包名用了class、public这类关键字也是同样的下场。行业里最通用的做法是“域名反写”com.公司名.项目名.模块名比如com.taobao.order.service。为什么反写因为域名的层级是右到左递减的反写后正好和Java包的层级方向一致保证包名的全球唯一性。实操里还有一个常见疑问包名和本地工程目录必须完全一致吗答案是必须一致。我之前遇到过有同事把com.demo的类放到了demo/com目录下用IDEA开发时一切正常但一到命令行用javac编译就报“找不到符号”。这是因为IDEA会自动帮你做映射但javac不会。新手如果自己用命令行编译切记-d参数要指定输出目录否则.class文件会生成在当前目录运行时的包路径就和文件结构对不上了。2.3 import省事儿的“快捷方式”与隐藏的坑import虽然不是本篇文章的主角但它和package成对出现必须提一嘴。很多人以为import是把别的类“导入”到当前文件里其实不是。import只是一个编译期的“快捷声明”让程序员不用每次写全限定名。比如你引入java.util.List之后代码里写List编译器会自动展开成java.util.List。这么做的好处是减少打字量坏处是容易产生同名混淆——如果你同时import了两个包里的Date编译器就会报冲突。这里有个很多人不知道的知识点同一个类里import和package的顺序是不能乱的。package必须在第一位跟着才是import再然后是class定义。我见过有人为了“好看”把注释写在了package前面编译居然也过了这是因为注释不属于语句不影响位置要求。但除了注释之外没有任何语句能放在package之前。3. class面向对象的最小单元和JVM的加载对象3.1 class定义了什么模板、状态、行为class关键字紧跟在package之后用来定义一个类。类是什么一句话类是对一类事物的模板描述。比如“学生”是一个类它有属性学号、姓名、年龄和行为上课、考试、交作业。在Java里属性对应成员变量行为对应方法。所以你可以把类想象成一张建筑图纸而new Student()就是用图纸造出一个个真实的人——这些真实的人叫对象或实例。很多初学者在class这里第一个搞不明白的问题就是类和对象到底是什么关系我打个比方class是“菜谱”对象是“做出来的一道菜”。菜谱可以一直重复使用做出一道菜后可以给这道菜再加点辣椒改变对象的状态但菜谱本身不会变。面试中常考“面向对象三大特性”封装、继承、多态全部是建立在class这个基础上的。没有class这个概念后面这些都不存在。3.2 一个文件只允许一个public class为什么Java的语法规定一个.java源文件中可以有多个类但public修饰的类最多只能有一个而且类名必须和文件名一致。这个限制很多人不理解觉得怎么这么死板。深究一下会发现这是为了伺候JVM的类加载机制——编译器拿到文件名就能定位到那个唯一的public类不用扫描整套源码就能确定入口类的位置。如果你把HelloWorld.java里的public去掉改成class HelloWorld文件随便叫Test.java都能编译通过但这样代码的可维护性会急速下降而且别的包也访问不到这个类了。顺带说一个实用技巧文件里可以放多个非public类它们的作用域限定在同一个包内。比如你可以在HelloWorld.java里额外写一个class Helper {}只要它不是public就行。这在初学者做练习的时候挺方便但在真实项目中不建议这么干。“一个文件一个公共类”是Java社区的约定俗成不是语言强制规定但遵循它能让代码审查和版本管理都轻松很多。3.3 JVM是怎么加载class的一个简化版的“激活”流程如果你面试的是Java后端岗位class这个关键字一定会被延伸到“类的加载过程”这一块。简化的流程是加载 → 验证 → 准备 → 解析 → 初始化。这个流程可以做这么一个类比加载class就是从硬盘上把.class文件读进内存验证是检查这个文件格式是否合法、代码里有没有危险操作准备阶段给类的静态变量分配内存并设置默认值——注意是默认值不是代码里写的初始值解析是把符号引用替换成实际的内存地址初始化才是真正执行静态变量的赋值操作和静态代码块。也就是说你写在static int count 10里的10并不是在准备阶段就生效的而是在初始化阶段才被赋值进去。面试如果问到这里记得区分两个概念主动引用和被动引用。比如使用new创建对象、调用静态方法、访问静态字段这些属于主动引用会触发类的初始化而通过子类访问父类的静态变量子类本身的初始化可能并不会被触发。这个问题我就被问过一次很多人死记硬背“首次主动使用”但说不清楚哪些算主动使用建议动手写个Demo验证一遍。4. 四个单词拆解public static void main4.1 为什么顺序是public static void main(String[] args)这一节是本文的重头戏。public static void main(String[] args)每一部分都能改吗哪些是硬性规定我们逐个拆。public是访问修饰符表示“公开的、谁都能访问”。为什么必须是public因为main方法是JVM的启动入口JVM在启动程序时并没有“当前对象”的概念它是在类的外部调用这个方法的。如果一个方法不是public外部类就无法访问它JVM自然也就找不到入口。可以试一下把public去掉编译能过但运行时JVM会给你报这个错误Error: Main method not found in class HelloWorld。你明明写了main方法但因为它不公开JVM就不认。这里顺便说一个冷知识修饰符的顺序其实不影响编译结果。public static void main和static public void main都能正常运行。JVM只检查这个方法的签名是不是“静态的、公有的、返回void、参数是String数组”并不在意修饰符的书写顺序。但业界约定俗成都是public static void main为了代码可读性建议你也这么写。4.2 static为什么入口不能是非静态的static这个关键字经常被认为是Java里最容易被误解的关键字之一因为它同时承担了“修饰方法”“修饰变量”“修饰代码块”多个职责。停留在main方法上static的作用是让这个不需要通过创建对象就能被调用。你设身处地想一想JVM的处境程序刚启动还没有任何一个被实例化的对象JVM怎么可能先new一个对象再去调用它的方法就算JVM能new我也不知道该new谁——启动类的构造器可能有参数可能有各种依赖。所以Java设计者干脆规定入口方法必须是静态的由类直接持有不用实例化。这个设计思路和C/C的入口函数在本质上是一样的程序启动时还没有运行时环境只能先调用静态入口。4.3 void入口方法为什么不能有返回值第三部分是void表示这个方法“无返回值”。有编程经验的人可能会困惑为什么不让入口方法返回一个整数作为程序运行状态码比如return 0;表示成功return -1;表示失败这不是很有用吗Java设计者是这么考虑的main方法是程序入口但程序的“退出码”由System.exit(int)方法显式控制而不靠main方法返回值。这就解放了main的签名让它统一返回void。如果你已经把main写成public static int main(String[] args)编译可以通过但JVM照样不识别因为它不是约定好的入口签名。这里再补充一句main方法内部是可以写return;的只是它只能代表方法执行到这里就结束并不会给JVM任何状态码。4.4 String[] args入口方法的参数到底有什么用args是命令行参数它是程序接收外界输入的第一道门。举个最直观的例子java com.example.demo.HelloWorld hello 123这两个参数会以字符串数组的形式进入args你在main里就能用args[0]拿到helloargs[1]拿到123。注意args这个名字是可以改的写String[] arguments完全没问题JVM只看类型不变量名。参数写法的三种合法形态也都见过public static void main(String[] args) public static void main(String args[]) public static void main(String... args)第三种用了可变参数语法本质还是数组。但有一点必须提醒如果类名写对了、参数形态也对了可程序还是报“找不到main方法”大概率是你把参数顺序写错了比如写成了String args[]就不是标准签名的识别范围虽然编译没问题但JVM不认。4.5 main方法的花样翻车现场既然说了这么多顺便把我在教学和面试中遇到的“main方法翻车现场”列一下大家避坑把main写成了mian。别笑这个错误我见过不下十次编译能过但运行直接报找不到。把参数写成了int args。运行时JVM找不到签名符合的方法报的一样是Main method not found。在一个类里写了两个main方法。这个是允许的因为可以重载但JVM只会去找那个标准签名的方法作为入口。你写一个public static void main(String arg)是没资格当启动入口的。把static去掉然后想着在main里new HelloWorld().main(...)自己启动。这在语法上确实能跑但JVM依然不认识你这个入口方法。5. 面试官真正想考你的关键字背后的Java基础5.1 从public延展到Java的四种访问修饰符聊完public面试官很容易顺着往下问“Java里除了public还有哪些访问权限修饰符”默认的访问权限又是什么这里整理成表格方便大家记忆修饰符同类同包子类不同包全局private✅❌❌❌默认无修饰符✅✅❌❌protected✅✅✅❌public✅✅✅✅注意这里有个容易答错的知识点同包子类访问protected成员是没问题的跨包子类也只能通过继承关系访问protected成员不能随便拿一个父类对象去访问它的protected字段。这个特性经常出选择题属于典型的“看起来简单实际动手就错”的知识点。5.2 static的多种身份变量、代码块、内部类的那些坑static在面试里的出镜率极高因为它有一堆“用起来简单、解释清楚很难”的场景。首先是最基本的静态变量属于类不属于实例。不管创建多少个对象静态变量只有一份所有实例共享它。正因为这个特性static修饰的变量天然适合做全局配置项、工具类的常量等。然后是静态代码块。很多人知道静态代码块在类加载时执行、且只执行一次但容易在继承场景下栽跟头。如果父类和子类都有静态代码块加载子类时的执行顺序是父类静态代码块 → 子类静态代码块 → 父类实例代码块和构造函数 → 子类实例代码块和构造函数。也就是说main方法所在类的静态代码块一定会在main执行之前跑完。这个顺序在笔试里被翻来覆去地考建议自己动手跑一遍验证。还有一个很容易出错的点静态方法里不能直接访问实例变量。因为静态方法不依赖对象它没有this引用。但如果静态方法里事先接收到一个外部传入的对象可以通过这个对象去访问实例变量。同一个道理实例方法同时能访问静态变量和实例变量因为实例方法一定在对象创建之后才能被调用类信息肯定早就加载好了。5.3 这道面试题的标准回答思路回到标题那个问题如果面试官真的问“public static void main是什么意思”你可以分两层回答。先讲每个关键字的作用public保证JVM从类外访问static保证不用创建对象就能被调用void表示入口方法不返回值main是约定好的方法名String[] args负责接收命令行参数。这是基础分。想拿高分再补充一句“Java规范规定JVM通过固定签名查找入口方法所以虽然语法上允许多个重载但只有这个签名是程序启动时被认可的入口。”把这一层说清楚面试官大概率会眼前一亮。6. 从HelloWorld走向真实项目这些关键字怎么用6.1 包结构在分层架构里的实战设计学完语法后很多人会问package在真实项目里到底怎么规划以现在最常见的Spring Boot项目为例通常会按技术分层来组织包结构。比如一个电商后台com.shop ├── controller // 接收HTTP请求 ├── service // 业务逻辑 ├── dao // 数据库访问 ├── model // 实体对象 ├── config // 配置类 └── util // 工具类这种“按层分包”的方式对中小项目比较友好角色清晰、依赖方向单一。但项目变大时团队经常会改成“按业务模块分包”同一个业务下自己带着controller、service、dao比如com.shop.order、com.shop.user。两种方式没有绝对的对错关键是保持一致。package是Java给你的免费的组织能力不用反而是一种浪费。6.2 一个类尽可能干好一件事类的设计原则说回class关键字它在真实项目里绑定着面向对象的设计规范。面试里的“面向对象设计原则”虽然有点虚但落到代码上都是具体可查的一个类应该只有一个引起它变化的原因这叫单一职责原则。你的UserService只用管用户的增删改查别去管发短信、登录日志一个类应该通过“扩展”而非“修改”来适应变化这叫开闭原则。初学的人总觉得这些原则很遥远。但你想想如果同一个类里什么代码都堆最后你会收获一个上千行的大杂烩类改一个需求要全流程回归测试。class定义了你能封装的东西而设计原则提醒你要封装得科学。所以class不只是关键字的语法更是一种建模思维。6.3 main方法在框架时代的“退居幕后”最后一个话题聊一聊main方法在真实工程世界的位置。在纯命令行程序里main方法就是你程序的全部——数据读进来处理输出结束了。但在今天的Java生态里绝大多数企业级项目都是基于Spring Boot这类框架的main方法往往只干一件事SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }你没看错它就是把启动权交给SpringApplication.run。所有的对象创建、依赖注入、配置加载、端口监听全都被框架接管了。你写的main只是个“点火开关”。但越是这样越说明理解public static void main的价值——框架再花哨JVM的门依然是从这一行进去的只是门后的事情变多了。就算以后切换到Java 21的“简化启动方式”去直接跑单文件这个固定签名的入口约定也依旧摆在明面是Java世界里少有的“跨版本不变”的东西。回头看package、class、public static void main这几个关键字其实是一整套“Java世界观”的浓缩package管的是代码在地理上“住哪”class管的是代码在逻辑上“是什么”而main管的是程序在运行时“从哪进”。把这三层想透了后面再学集合、IO、多线程、JVM心里都会有一个清晰的地图。我见过太多学了一个月Java还分不清“类加载”和“对象创建”的人回头补一下这篇东西很多东西一下就能串起来了。
返回列表