1. 这不是语法手册,而是一份“能写代码”的Java入门实战指南
你点开这个标题,大概率不是想背诵“Java有八种基本数据类型”这种教科书定义——而是刚装好IDEA,新建完第一个HelloWorld.java文件,却卡在了public static void main(String[] args)这行代码上,反复琢磨:为什么必须是public?static到底锁住了什么?String[] args这个方括号到底该写在左边还是右边?甚至更实际一点:我改了代码,怎么才能让控制台立刻打印出我想要的结果,而不是一堆红色报错?
这就是我们今天要解决的。所谓“史上最全”,不是堆砌所有语法点的名词解释,而是把Java语法从“纸面规则”还原成“键盘操作流”:每一个符号、每一对括号、每一处关键字,都对应着你在编辑器里真实敲下的动作、编译器给出的反馈、以及程序运行时内存中发生的变化。它覆盖了从你双击安装包开始,到写出一个能处理用户输入、做简单计算、并把结果格式化输出的完整小工具为止,所必须掌握的全部语法骨架和血肉细节。适合零基础但手痒想立刻写点东西的人,也适合学过但总在if-else嵌套或数组越界时抓狂的初学者。它不讲JVM原理,不提前透支你的认知带宽,只聚焦一个问题:下一行代码,你该怎么写,为什么这么写,写错了会怎样,以及怎么一眼看出错在哪。
我带过不少刚转行的学员,发现一个高频痛点:他们能复述“类是对象的模板”,但一写Student s = new Student();就犹豫要不要加<String>;知道“for循环遍历数组”,但面对int[] scores = {85, 92, 78};时,愣是不敢动笔写循环体,生怕i从0开始还是1开始会崩掉整个程序。这种卡顿,根源不在概念不清,而在缺乏对语法“手感”的建立——就像学骑车,看一百页平衡原理不如摔两跤来得实在。所以这篇内容,会把每个语法点都锚定在一个可立即运行的小例子上,告诉你编译器报错时那串英文到底在喊什么,告诉你调试器里变量值跳变的瞬间意味着什么,告诉你为什么==比较字符串会出人意料,而.equals()又为什么不能随便乱用。它不承诺让你成为架构师,但能确保你合上这篇内容后,打开IDEA,能独立写出一个读取三个数字、找出最大值并用中文输出结果的完整程序,且清楚每一行代码在做什么。
2. 语法设计的底层逻辑:为什么Java要这样写?
2.1 Java语法不是凭空设计的,它是一套“防错协议”
很多初学者抱怨Java啰嗦,比如一个简单的输出都要写System.out.println("Hello");,而不是像Python那样直接print("Hello")。这背后其实是Java设计哲学的具象化:用显式声明换取确定性,用结构约束规避隐式错误。我们可以把它理解为一套给程序员的“防错协议”。
举个最典型的例子:public static void main(String[] args)。这行代码里的每一个词,都不是装饰,而是编译器和JVM之间的一份契约。
public:这是访问修饰符,它向JVM保证:“这个方法,谁都能调用”。因为JVM启动时,需要从外部(也就是操作系统)找到并执行这个入口点。如果写成private,JVM在类外根本看不到它,程序直接启动失败,报Main method not found。这不是Java故意刁难,而是明确划清边界——哪些是给外部调用的,哪些是内部私有的。static:这是静态修饰符,它向JVM承诺:“这个方法,不依赖于任何具体的对象实例”。想象一下,程序刚启动,连Student类的对象都还没创建出来,JVM总不能凭空捏造一个Student实例再去调用它的main方法吧?static就是告诉JVM:“别管对象,直接调用这个方法本身就行”。这也是为什么在main方法里,你不能直接使用非静态的成员变量或方法,因为它们属于“未来某个可能被创建的对象”,而此刻,那个对象还不存在。void:这是返回类型,它向JVM申明:“这个方法,不给你任何返回值”。main方法是程序的起点和终点,它的职责是启动程序逻辑,而不是把结果“返回”给操作系统(操作系统也不关心你算出的1+1等于几)。所以,它必须是void。如果你强行改成int main(...),编译器会立刻报错,因为这违反了JVM的启动协议。String[] args:这是参数列表,它向JVM约定:“你启动我的时候,可以把命令行参数打包成一个字符串数组,塞给我”。比如你在终端输入java HelloWorld arg1 arg2,那么args[0]就是"arg1",args[1]就是"arg2"。方括号[]写在String后面,是Java的语法规定,表示“这是一个String类型的数组”。它不能写成String args[](虽然Java也接受这种C风格写法,但强烈不推荐,因为它模糊了类型与变量名的关系,容易在复杂声明中引发歧义)。
这套协议看似繁琐,但它消灭了大量“不确定”带来的隐患。比如,C语言里main函数可以写成int main()或int main(int argc, char *argv[]),不同写法兼容性不同,新手很容易踩坑。而Java用强制的public static void main(String[] args),一刀切地统一了入口,让所有人的起点都一样清晰。这不是为了增加学习成本,而是为了在项目规模扩大、多人协作时,让代码的意图变得不可辩驳。
2.2 从“人话”到“机器指令”:编译期检查是你的第一道防线
Java语法的另一个核心设计,是把大量错误拦截在程序运行之前,也就是编译期。这得益于Java是一门“强类型、静态类型”的语言。这意味着,你在写代码的时候,就必须明确告诉编译器:“这个变量,它是什么类型,它能存什么值”。
比如,你声明int age = 25;,编译器立刻就在内存里为你预留了4个字节的空间,并且打上标签:“这里只能放整数”。如果你接下来写age = "twenty-five";,编译器会毫不留情地报错:incompatible types: java.lang.String cannot be converted to int。它不会等到程序跑起来,用户点了按钮才崩溃,而是在你按下Ctrl+F9(编译快捷键)的那一刻,就把错误扼杀在摇篮里。
这种“编译期检查”机制,是Java语法严谨性的基石。它衍生出一系列关键语法规则:
变量必须先声明,后使用。你不能像JavaScript那样,直接写
score = 95;,然后指望编译器自动帮你推断类型。Java要求你必须写int score = 95;。这看起来多了一步,但它强迫你思考:“这个数据,它的本质是什么?是整数?是小数?还是文本?”这种思考,恰恰是构建可靠程序的第一步。类型转换必须显式进行。比如,你想把一个
double类型的3.14赋值给int类型的变量,你不能直接写int x = 3.14;,编译器会报错。你必须写int x = (int) 3.14;,这个(int)就是“强制类型转换”,它像一个免责声明:“我知道会丢失精度,但我自己承担后果。” 这种显式性,杜绝了大量因隐式转换导致的诡异bug。数组长度在创建时就固定。
int[] numbers = new int[5];,这个5一旦确定,numbers的长度就永远是5。你不能像Python列表那样,随时append一个新元素。这种“固定性”牺牲了灵活性,但换来了内存布局的绝对可预测性,让JVM能进行极致的性能优化。
理解了这套“编译期防错”逻辑,你就能明白,为什么Java语法里充斥着各种final、@Override、try-catch这样的关键字。它们不是装饰品,而是你向编译器发出的、关于代码行为的精确指令。final说:“这个值,我发誓永不改变”;@Override说:“我写的这个方法,百分百是重写了父类的同名方法,如果父类没有,编译器请立刻告诉我”;try-catch说:“这段代码可能会出错,我已经准备好了应对方案,别让它把整个程序拖垮。” 每一个语法点,都是在和编译器签订一份关于“确定性”的契约。
2.3 面向对象不是口号,而是语法结构的自然延伸
很多人学Java,第一步就被“面向对象”四个字吓住。其实,抛开所有抽象概念,面向对象在Java语法层面,就是用class这个关键字,把数据(属性)和操作数据的代码(方法)打包在一起。它不是一个需要额外学习的“新范式”,而是Java语法最基础的组织单元。
我们来看一个最简化的Car类:
public class Car { // 属性:描述汽车的状态 String brand; int speed; // 方法:描述汽车能做什么 public void accelerate() { speed = speed + 10; } public void printStatus() { System.out.println("品牌:" + brand + ",当前速度:" + speed); } }这段代码的语法结构,完全遵循了前面讲的“防错协议”和“编译期检查”原则:
public class Car:public保证这个类可以被其他类访问;class是定义类的关键字;Car是类名,首字母大写是Java约定俗成的规范,它让代码更具可读性。String brand;和int speed;:这是属性声明。String和int是明确的类型,brand和speed是变量名。它们没有public或private修饰符,这意味着它们是“包级私有”的,即同一个包内的其他类可以访问它们。但在实际开发中,我们几乎总是用private修饰属性,然后提供public的getter/setter方法,这是为了封装——把数据的内部表示隐藏起来,只暴露安全的操作接口。public void accelerate():这是一个方法声明。public表示它可以被外部调用;void表示它不返回值;accelerate是方法名;()表示它没有参数。方法体{ speed = speed + 10; }就是具体的执行逻辑。
当你写下Car myCar = new Car();时,你不是在“创建一个对象”,而是在执行一个三步操作:1)在堆内存中分配一块空间;2)调用Car类的构造方法(这里用的是默认无参构造)来初始化这块空间;3)将这块空间的地址,赋值给栈内存中的变量myCar。myCar本身只是一个“遥控器”,它不存储品牌和速度,它只存储着通往真正数据的“地址”。
所以,“面向对象”的语法,本质上就是class、new、.(点号,用于访问属性和方法)这三个核心符号的组合。它把世界上的事物,映射成了代码里一个个可创建、可调用、可传递的实体。理解了这一点,你就不会再觉得OOP高深莫测,而会觉得它就是Java最顺手、最自然的表达方式。
3. 核心语法点逐层拆解:从变量到异常处理
3.1 变量与数据类型:你的代码世界的“原子”
在Java里,一切计算的起点,都是变量。而变量的本质,就是一个有名字的、有类型的内存盒子。Java的数据类型分为两大阵营:基本数据类型(Primitive Types)和引用数据类型(Reference Types)。这个区分,是理解Java内存模型和几乎所有后续语法的钥匙。
基本数据类型,是Java内建的、最底层的“原子”。它们不包含任何方法,只负责存储一个纯粹的值。Java有8种,可以按功能分成四组:
| 类型 | 示例 | 占用字节 | 取值范围 | 典型用途 |
|---|---|---|---|---|
byte | byte b = 100; | 1 | -128 ~ 127 | 网络传输、文件IO中处理单字节数据 |
short | short s = 30000; | 2 | -32768 ~ 32767 | 当数值范围确定很小,且需要节省内存时 |
int | int count = 1000; | 4 | -2^31 ~ 2^31-1 | 最常用的整数类型,计数、索引、一般运算 |
long | long bigNum = 10000000000L; | 8 | -2^63 ~ 2^63-1 | 处理超大整数,如时间戳(毫秒)、ID生成 |
float | float f = 3.14F; | 4 | 约±3.4e38 | 对精度要求不高的浮点运算,如图形渲染 |
double | double pi = 3.1415926; | 8 | 约±1.7e308 | 最常用的浮点类型,科学计算、金融计算(注意:金融计算应优先用BigDecimal) |
char | char c = 'A'; | 2 | 0 ~ 65535 (Unicode) | 存储单个字符,注意用单引号 |
boolean | boolean isOk = true; | 1 (JVM实现相关) | true/false | 表示逻辑状态,条件判断的基石 |
提示:
long类型的字面量必须以L或l结尾(推荐大写L,避免与数字1混淆),float类型的字面量必须以F或f结尾。这是Java语法的硬性规定,否则编译器会默认将其视为int或double,导致类型不匹配错误。
引用数据类型,则是指向对象的“遥控器”。它本身不存储数据,只存储一个地址。最常见的引用类型就是类(Class)、接口(Interface)和数组(Array)。
String name = "Alice";:name是一个String类型的变量,它存储的不是"Alice"这个字符串本身,而是堆内存中存放"Alice"的那个位置的地址。int[] numbers = {1, 2, 3};:numbers是一个int[]类型的变量,它存储的是一个int数组对象的地址。Car myCar = new Car();:myCar是一个Car类型的变量,它存储的是一个Car对象的地址。
理解这个区别至关重要。它直接决定了你如何比较两个变量:
- 比较基本类型:用
==,比较的是它们存储的值是否相等。int a = 5; int b = 5;,那么a == b为true。 - 比较引用类型:用
==,比较的是它们存储的地址是否相同,即是否指向同一个对象。String s1 = "hello"; String s2 = "hello";,由于字符串常量池的优化,s1 == s2可能为true;但String s3 = new String("hello");,那么s1 == s3一定为false,因为s3指向的是堆内存中新创建的对象。要比较两个字符串的内容是否相同,必须用s1.equals(s3)。
注意:
null是一个特殊的字面量,它表示“这个引用变量,目前没有指向任何对象”。你可以给任何引用类型变量赋值null,比如String name = null;。但如果你试图调用name.length(),程序会在运行时抛出NullPointerException。这是Java中最常见的运行时异常之一,也是新手最容易栽跟头的地方。养成习惯:在调用引用类型的方法前,先检查它是否为null。
3.2 运算符与表达式:让数据流动起来的“管道”
有了变量,下一步就是让它们动起来。Java提供了丰富的运算符,它们是连接变量、产生新值的“管道”。理解运算符的优先级(Precedence)和结合性(Associativity),是写出正确、可读代码的前提。
Java运算符的优先级,从高到低大致如下(只列最常用):
- 括号
():最高优先级,强制改变计算顺序。int result = (2 + 3) * 4;结果是20,而不是14。 - 一元运算符
++,--,!,-:作用于单个操作数。int i = 5; int j = i++;这里j得到的是i的旧值5,然后i自增为6;而int k = ++i;则是i先自增为7,k再得到7。!是逻辑非,-是取负。 - 算术运算符
*,/,%:乘、除、取余。%是取模运算,7 % 3结果是1。注意:/用于整数时是整除,5 / 2结果是2,不是2.5。 - 算术运算符
+,-:加、减。+还有字符串拼接功能,"Hello" + " " + "World"结果是"Hello World"。 - 关系运算符
<,>,<=,>=,==,!=:比较大小或相等,结果是boolean值。==和!=对于引用类型,比较的是地址,这点务必牢记。 - 逻辑运算符
&&,||,!:逻辑与、或、非。&&和||具有“短路”特性:a && b,如果a为false,b根本不会被计算;a || b,如果a为true,b也不会被计算。这在避免NullPointerException时非常有用,比如str != null && str.length() > 0,如果str为null,后面的length()就不会执行。 - 赋值运算符
=,+=,-=等:最低优先级。a += b等价于a = a + b,但更简洁,且在某些情况下(如a是复杂表达式时)更高效、更安全。
一个经典的易错点是==和=的混淆。if (x = 5)在Java中是语法错误,因为if后面必须是一个boolean表达式,而x = 5是一个赋值操作,返回的是int类型的5。这反而成了Java的一个优点——它用编译错误,帮你避免了C/C++中那种难以察觉的、把==写成=的致命bug。
另一个实用技巧是三元运算符? :,它是if-else语句的简洁版。int max = (a > b) ? a : b;这行代码的意思是:“如果a > b为真,max的值就是a,否则就是b”。它非常适合用于简单的条件赋值,让代码更紧凑。但切记,不要为了炫技而滥用,如果逻辑复杂,还是老老实实写if-else,可读性永远是第一位的。
3.3 流程控制:用if、for、while指挥代码的“交通”
再强大的数据,也需要被“指挥”才能完成任务。流程控制语句,就是Java代码的“交通警察”,它们根据条件,决定哪段代码该执行,哪段该跳过,哪段该重复。
if-else语句,是最基础的条件分支。它的核心是布尔表达式。一个完整的if-else链,应该覆盖所有可能的情况,避免逻辑漏洞。
int score = 85; if (score >= 90) { System.out.println("优秀"); } else if (score >= 80) { System.out.println("良好"); } else if (score >= 70) { System.out.println("中等"); } else if (score >= 60) { System.out.println("及格"); } else { System.out.println("不及格"); }实操心得:我见过太多学员,在写
if-else时,把条件写反,比如if (score < 90),然后后面跟着else if (score < 80),这会导致逻辑混乱。一个简单的心法是:始终从最严格的条件开始写。先写>= 90,再写>= 80,这样逻辑是递进的,不易出错。另外,else块永远是“兜底”的,它代表了所有前面if和else if都不满足的剩余情况,一定要有。
switch语句,是当有多个离散的、已知的选项时,if-else的优雅替代。Java 14之后,switch支持了表达式形式,更加简洁安全。
// 传统switch语句(需要break,否则会“穿透”) String day = "Monday"; switch (day) { case "Monday": System.out.println("星期一"); break; case "Tuesday": System.out.println("星期二"); break; default: System.out.println("其他日子"); } // Java 14+ 的switch表达式(更推荐) int dayNumber = switch (day) { case "Monday" -> 1; case "Tuesday" -> 2; case "Wednesday" -> 3; default -> -1; };switch表达式的优点在于:它必须有返回值,且->箭头代替了:,更重要的是,它自动防止了穿透(fall-through)。你再也不用担心忘记写break而导致多个case的代码被连续执行了。而且,它天然支持String、enum,甚至可以在一个case中列出多个值,用逗号分隔:case "RED", "GREEN", "BLUE" -> ...。
循环语句,是让代码重复执行的引擎。for、while、do-while三者各有适用场景。
for循环:适用于已知循环次数的场景。它的结构for (初始化; 条件; 更新)非常清晰。// 打印1到10 for (int i = 1; i <= 10; i++) { System.out.println(i); }初始化部分
int i = 1只执行一次;每次循环开始前,都会检查i <= 10;每次循环体执行完毕后,都会执行i++。i的作用域仅限于这个for循环内部,出了循环,i就“消失”了,这是Java的一个重要安全特性。while循环:适用于循环次数未知,但结束条件明确的场景。它先检查条件,再执行循环体。// 读取用户输入,直到输入"quit" Scanner scanner = new Scanner(System.in); String input; while (!(input = scanner.nextLine()).equals("quit")) { System.out.println("你输入了:" + input); }do-while循环:适用于必须至少执行一次的场景。它先执行循环体,再检查条件。// 让用户输入密码,直到输入正确为止(至少要输一次) String password; do { System.out.print("请输入密码:"); password = scanner.nextLine(); } while (!password.equals("123456"));
常见问题:无限循环。这通常是因为循环的更新语句写错了,或者条件判断永远为真。比如
for (int i = 0; i < 10; i--),i一直在减,永远不会大于等于10,就会无限循环。调试时,可以在循环体内加一句System.out.println("i = " + i);,观察变量变化,这是最朴实也最有效的排查方法。
3.4 方法:代码复用的“乐高积木”
如果把变量比作砖块,那么方法(Method)就是用这些砖块搭好的、可以随时拿去用的“乐高积木”。它把一段完成特定任务的代码,封装成一个有名字、有输入、有输出的独立单元。
一个标准的方法声明,由五部分组成:[修饰符] 返回类型 方法名 (参数列表) { 方法体 }
- 修饰符:如
public、private、static等,控制方法的访问权限和行为。 - 返回类型:方法执行完毕后,要“交还”给调用者什么东西。可以是
int、String、void(什么都不还)等。 - 方法名:遵循驼峰命名法,见名知意,如
calculateTotalPrice。 - 参数列表:方法的“输入口”,用逗号分隔,每个参数都必须声明类型和名字,如
(String name, int age)。 - 方法体:用
{}包裹的具体执行逻辑。
下面是一个综合示例,展示了方法的精髓:
public class Calculator { // 一个有返回值的方法 public static int add(int a, int b) { return a + b; // return语句将计算结果“交还”给调用者 } // 一个无返回值的方法 public static void printGreeting(String name) { System.out.println("Hello, " + name + "!"); // 没有return语句,或者可以写一个单独的return;来提前结束 } // 一个有多个参数、返回复杂类型的方法 public static String formatName(String firstName, String lastName, boolean useUpperCase) { String fullName = firstName + " " + lastName; return useUpperCase ? fullName.toUpperCase() : fullName; } }调用这些方法,就像使用一个工具:
public class Main { public static void main(String[] args) { int sum = Calculator.add(3, 5); // 调用add方法,接收返回值 System.out.println("3 + 5 = " + sum); Calculator.printGreeting("Alice"); // 调用printGreeting方法,不接收返回值 String formatted = Calculator.formatName("john", "doe", true); System.out.println(formatted); // 输出 "JOHN DOE" } }实操心得:方法的设计,是编程艺术的核心。一个好的方法,应该遵循“单一职责原则”:它只做一件事,并且把这件事做好。比如,上面的
add方法,只负责加法;printGreeting只负责打印问候;formatName只负责格式化姓名。如果你发现一个方法的名字里包含了“和”、“或”、“同时”,比如saveAndSendEmail,那它很可能违反了这个原则,应该被拆分成save()和sendEmail()两个独立的方法。这样做,不仅让代码更易测试、更易复用,也让你在调试时,能精准地定位到问题所在的那一小块积木,而不是在一团乱麻中大海捞针。
3.5 异常处理:为代码世界预设的“应急预案”
现实世界充满意外,代码世界亦然。用户可能输入一个字母,而你的程序正等着一个数字;网络请求可能超时;文件可能被其他程序占用。Java的异常处理机制,就是为这些“意外”准备的、一套标准化的“应急预案”。
Java的异常分为两大类:检查型异常(Checked Exception)和非检查型异常(Unchecked Exception)。
检查型异常:继承自
Exception类(但不包括RuntimeException及其子类)。这类异常,编译器强制要求你必须处理。最常见的就是IOException(输入输出异常)和SQLException(数据库异常)。如果你调用了一个可能抛出IOException的方法(比如FileReader的构造方法),编译器会报错,提示你必须用try-catch捕获它,或者在方法签名上用throws声明它。这是一种“防御性编程”的体现,它强迫你在设计阶段就思考:“如果这个操作失败了,我该怎么办?”非检查型异常:继承自
RuntimeException类。这类异常,编译器不强制要求处理,但它们是程序逻辑错误的标志。最常见的就是NullPointerException(空指针)、ArrayIndexOutOfBoundsException(数组越界)、ArithmeticException(算术异常,如除零)。它们通常源于程序员的疏忽,比如忘了初始化对象、数组下标写错了、或者没做除零检查。虽然不强制,但优秀的程序员,一定会在关键位置主动预防它们。
一个健壮的异常处理结构,通常是这样的:
public class FileReaderExample { public static void readFile(String fileName) { // 1. 声明资源(Java 7+ 的try-with-resources语法,自动关闭) try (FileReader reader = new FileReader(fileName)) { int character; // 2. 在try块中执行可能出错的操作 while ((character = reader.read()) != -1) { System.out.print((char) character); } } catch (FileNotFoundException e) { // 3. 分别捕获不同类型的异常,进行针对性处理 System.err.println("文件未找到:" + fileName); e.printStackTrace(); // 打印详细的错误堆栈,用于调试 } catch (IOException e) { System.err.println("读取文件时发生IO错误"); e.printStackTrace(); } finally { // 4. finally块总会执行,无论是否发生异常,常用于清理工作 System.out.println("文件读取操作结束。"); } } }注意事项:
catch块的顺序很重要。子类异常必须写在父类异常之前。比如,你不能先写catch (Exception e),再写catch (FileNotFoundException e),因为FileNotFoundException是Exception的子类,前者已经把所有异常都“抓”走了,后面的catch永远不会被执行。正确的顺序是,从最具体的异常(子类)到最笼统的异常(父类)。
实操心得:新手常犯的错误是“生吞异常”,即在
catch块里什么也不做,或者只写一个空的catch (Exception e) {}。这就像把报警器的电池拔掉,以为世界就太平了。真正的错误依然存在,只是你再也听不到警报了。一个负责任的catch块,至少应该做三件事:1)记录日志(e.printStackTrace()或更好的日志框架);2)给用户一个友好的提示(“对不起,系统繁忙,请稍后再试”);3)尝试恢复或降级处理(比如,从缓存中读取数据,而不是直接报错)。这才是异常处理的真正价值:不是掩盖错误,而是优雅地应对它。
4. 从零开始:一个完整的小项目实操演练
4.1 项目目标:一个“成绩分析助手”
理论讲得再多,不如亲手做一个小东西。我们将用前面学到的所有语法,从零开始,打造一个极简但功能完整的“成绩分析助手”。它的功能是:
- 提示用户输入3个学生的姓名和对应的数学成绩。
- 计算并显示这3个成绩的平均分。
- 找出最高分,并显示获得该分数的学生姓名。
- 将所有信息,以格式化的表格形式输出到控制台。
这个项目虽小,却涵盖了变量、数组、循环、条件判断、方法、输入输出等所有核心语法点,是检验你是否真正掌握的绝佳试金石。
4.2 步骤分解与代码实现
第一步:准备环境与输入工具
我们需要一个能读取用户键盘输入的工具。Java标准库提供了Scanner类,它是我们与用户交互的桥梁。
import java.util.Scanner; // 导入Scanner类 public class GradeAnalyzer { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 创建Scanner实例 // 后续所有输入操作,都将通过scanner来完成 } }提示:
import语句是告诉编译器:“我接下来要用到java.util.Scanner这个类,它不在当前包里,请帮我找到它。” 这是Java模块化思想的体现,让代码更清晰、更易维护。
第二步:定义数据容器
我们需要存储3个学生的信息。每个学生有姓名(String)和成绩(int)两个属性。最直接的方式,是创建两个平行的数组:
// 定义两个数组,分别存储姓名和成绩 String[] names = new String[3]; int[] scores = new int[3];但更好的方式,是创建一个Student类,把这两个属性封装在一起,这更符合面向对象的思想:
// 在GradeAnalyzer类的外面,定义Student类 class Student { String name; int score; // 构造方法,方便创建对象时初始化 public Student(String name, int score) { this.name = name; this.score = score; } }然后,在main方法里,创建一个Student数组:
Student[] students = new Student[3];第三步:收集用户输入
使用for循环,提示用户输入三次:
for (int i = 0; i < 3; i++) { System.out.print("请输入第" + (i + 1) + "个学生的姓名:"); String name = scanner.nextLine(); // nextLine()读取一整行 System.out.print("请输入" + name + "的数学成绩:"); int score = scanner.nextInt(); // nextInt()读取一个整数 // 创建Student对象,并存入数组 students[i] = new Student(name, score); // 注意:nextInt()不会读取