ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Java应用基础Day2:流程控制、方法、数组与面向对象入门

Java应用基础Day2:流程控制、方法、数组与面向对象入门 许多刚开始学 Java 的朋友在第一天装好 JDK、写出 HelloWorld 之后第二天往往最迷茫语法看懂了但不知道下一步该学什么也不知道自己写的代码到底“能不能干活”。“Java应用基础 Day2”这个概念就是想帮大家把第二天的时间花在刀刃上——不啃枯燥的理论而是围绕流程控制、方法、数组、字符串和面向对象的第一小步把“能运行”的程序逐渐变成“能解决问题”的程序。这篇内容主要面向两类人一类是零基础转行、正在系统刷 Java 基础的初学者另一类是已经看过一些教程但感觉知识零散、想通过 Day2 这类节奏把核心脉络串起来的朋友。今天的内容不会堆砌概念每个部分都配有可直接运行的代码、容易踩的坑以及一些面试时经常被问到的底层逻辑。你会发现Java 的基础部分并不难真正难的是把“学过”变成“写得出来”。1. 从Day1到Day2之间我建议你先做一次环境“自检”很多人在第一天安装完 JDK、运行出 HelloWorld 之后就急着进入下一个知识点结果到了第二天写多个类文件时连最基本的javac编译流程都说不清楚。我强烈建议你先花十分钟做一次环境自检这不是浪费时间而是为了让后续所有代码示例都能稳定跑起来。先打开命令行依次执行下面两条命令java -version javac -version如果两条命令都能输出对应的版本号说明 JDK 安装正常。如果java有输出但javac提示“无法识别”问题几乎都出在环境变量 PATH 的配置上——没有把 JDK 的 bin 目录加进去或者配置了 JRE 而非 JDK 的路径。这个错误在初学者中非常常见而且往往不会立刻暴露因为很多 IDE 会用内置的编译器一旦你脱离 IDE 用命令行编译就马上露馅。接下来我建议你用命令行手工编译一次而不是继续依赖 IDE 的“运行按钮”。新建一个SelfCheck.java文件内容先保持最简单的 HelloWorld然后执行javac SelfCheck.java java SelfCheck这里有一个初学者几乎都会踩的坑运行java SelfCheck的时候不要加.class后缀也不要写完整路径只需要类名。因为java命令的工作机制是从 classpath 中找类而不是找文件。如果你写成java SelfCheck.classJVM 会直接报“找不到或无法加载主类”这个报错我见得太多了几乎每期基础班都有同学中招。顺手把 classpath 的概念也说一下。classpath 代表 JVM 在编译和运行阶段查找类文件的路径集合。默认情况下它包含当前目录所以你在当前目录编译出来的.class文件能被直接找到。等你以后用 Maven、Gradle 管理依赖classpath 会变成一长串 jar 包路径但本质还是同一个东西告诉 JVM“你要用的类都放在哪”。如果你能顺利完成上面的步骤那么恭喜你Day2 的所有示例都可以放心在命令行或 IDE 里复现。环境自检的目的不是让你记住这些命令而是让你在遇到报错时不会慌——知道去哪看、知道哪条命令对应哪个环节。这一步走稳了后面写再多代码都不会因为环境问题卡壳。2. 流程控制程序开始有判断、有循环如果 Day1 是让你理解“代码按顺序从上到下执行”那么 Day2 的第一座山就是流程控制。它让程序不再是平铺直叙的流水账而是具备了“根据条件走不同分支、根据规律重复执行”的能力。这部分学完你就能写一些真正有逻辑的小程序了。2.1 if-else 的分支思维先想清楚“有几种情况”Java 里最基础的判断结构就是if-else。它的写法很直白如果条件成立执行一段代码否则执行另一段。但我在批改作业时发现很多初学者不是不会写if而是不会分析“这件事到底有几种情况”。举个例子判断一个年份是不是闰年。规则是能被4整除但不能被100整除或者能被400整除。如果你上来就写if (year % 4 0)后面的边界情况肯定要反复修补。正确做法是先列出所有可能的结果分支——“是闰年”和“不是闰年”再找出两个分支各自的判断条件最后落笔写代码。int year 2024; boolean isLeap; if ((year % 4 0 year % 100 ! 0) || (year % 400 0)) { isLeap true; } else { isLeap false; } System.out.println(year 是闰年吗 isLeap);嵌套if不是不能用但如果发现嵌套了三四层代码几乎没法读。这时建议把内层条件抽成独立判断或者用switch、卫语句等方式把逻辑拉平。记住一个原则代码的可读性不是靠注释堆出来的而是靠结构本身让人一眼看懂。2.2 switch 的演进老语法有了新写法switch是另一个分支选择工具适合“根据一个变量的多个取值走不同分支”的场景。传统写法里每个分支都要写break漏写就会产生“穿透”效果这在早期是个经典bug源。Java 14 之后引入了箭头语法不需要 break代码干净很多。int scoreLevel 2; String desc switch (scoreLevel) { case 1 - 优秀; case 2 - 良好; case 3 - 及格; default - 继续加油; }; System.out.println(desc);这里我特意没有写break因为新语法本身就防止了穿透。实际工作中如果用老式switch请一定检查每个分支的break或者直接改用新语法。另外要注意switch适合分支条件相对固定、变量取值有限的情况如果分支条件涉及范围判断比如分数是否大于 60那还是老老实实用if-else。2.3 三种循环怎么选for、while、do-while 的适用场景循环是让程序“重复做某件事”的核心机制。Java 里有三种循环写法很多初学者会纠结该用哪个其实判断标准非常直接知道循环次数用for不知道次数、只关心“条件是否还成立”用while无论如何都想先执行一次再判断用do-while。实际开发中for和while用得最多do-while相对少但它在“菜单选择、重试机制”这类场景里非常合适。下面是一个for计算 1 到 100 累加和的例子int sum 0; for (int i 1; i 100; i) { sum i; } System.out.println(12...100 sum);关于循环里的break和continuebreak是直接结束整个循环continue是跳过本轮进入下一轮。这两个关键字在嵌套循环里容易混淆我给你的建议是——能用条件判断改写就尽量少用尤其是多层嵌套时用break只会跳到最内层很容易留下逻辑隐患。判断和循环是后面几乎所有算法题的骨架。我见过太多人跳过这个阶段直接去刷题结果连数组遍历都写得磕磕绊绊。你不需要做大量题目但至少要把1到100求和、九九乘法表、猜数字游戏这类小练习独立写出来才算真正过关。3. 方法写出可以被随时调用的逻辑块当程序逻辑变复杂把所有代码塞进main方法里就会变成一团乱麻。几乎所有的 Java 初学者都会经历“一个 main 走天下”的阶段然后随着需求增加代码越来越臭。方法Method就是用来解决这个问题的把一段可复用的逻辑包装起来给它起个名字传参数进去拿返回值出来。3.1 方法的定义与签名参数和返回值是沟通的桥梁定义一个方法最核心的三要素是方法名、参数列表、返回值类型。调用者需要知道“我要传什么进去”方法需要告诉调用者“我会返回什么”。public static int add(int a, int b) { return a b; } public static void main(String[] args) { int result add(3, 5); System.out.println(result); }这里有个知识点值得强调如果方法没有返回值必须用void声明而且可以省略return语句如果有返回值那么每个分支路径都必须返回对应类型的值否则编译直接报错。初学者最容易犯的错误就是把return放在条件分支里面编译器检查时发现某些路径没有返回值直接红灯。方法名应该做到“见名知意”。我看到很多同学写method1、method2这样三天后自己都看不懂。命名虽然不影响功能但严重影响维护效率。比如calcAverage一眼就知道是算平均值的比methodA强一百倍。3.2 值传递还是引用传递面试必考的经典辨析Java 的参数传递是面试里极其高频的一个点。标准答案是Java 只有值传递没有引用传递。对于基本类型传递的是值的副本对于对象类型传递的是对象引用的副本。看起来有点绕我直接上例子。如果传入方法的参数是基本类型int在方法内部修改它外面的变量不会变public static void change(int num) { num 10; } int a 5; change(a); System.out.println(a); // 输出 5如果传入的是数组或对象通过引用修改了对象内部的数据外面的“同一个对象”就会被影响public static void changeFirst(int[] arr) { arr[0] 99; } int[] data {1, 2, 3}; changeFirst(data); System.out.println(data[0]); // 输出 99这个知识点难就难在“引用本身也是按值传递”这句话的理解上。你可以这样记方法拿到的是一把钥匙的复印件而不是钥匙本身通过复印件能开门修改对象内容但把复印件换了重新赋值引用不影响原件。这一块是 Java 八股文里的常客务必彻底弄懂。3.3 方法重载与递归两个看似高级但基础的技术方法重载Overload指的是同一个类中方法名相同但参数列表不同。public static int add(int a, int b) { return a b; } public static double add(double a, double b) { return a b; }调用时 JVM 会根据你传入的参数类型自动选择匹配的方法。这个设计的本质是“对调用者友好”不同的实现细节藏在同名方法后面调用逻辑统一。不过重载时不能只改返回值类型因为 JVM 无法仅凭返回值判断你要调用哪个方法编译器也不会通过。递归则是方法调用自身。经典例子是斐波那契数列的递归实现public static int fib(int n) { if (n 1) { return n; } return fib(n - 1) fib(n - 2); }递归写起来简洁但初学者一定要知道它的代价每次递归调用都会占用新的栈帧如果递归层数太深会抛出StackOverflowError。所以递归必须有清晰的终止条件而且如果问题能迭代解决优先考虑循环。递归适合树形结构遍历、分治算法这类天然带有嵌套性质的问题但不适合无脑“用递归显得很厉害”的场景。4. 数组与冒泡排序第一次接触“算法”数组是 Java 中用于存储同类型多个数据的基础容器。很多从没写过代码的人一听到“算法”两个字就紧张其实第一道算法题往往就是“给一个数组排序”而最直观的入门排序算法就是冒泡排序。这也是面试里出现频率极高的题目尤其适合检验你有没有真正理解数组和循环的结合使用。4.1 数组的创建与遍历从静态数据到动态操作创建数组有两种方式声明长度再赋值或者直接用大括号初始化。int[] nums new int[5]; // 默认元素值为 0 int[] scores {85, 92, 76, 88, 90}; // 直接初始化数组的索引从 0 开始最后一个元素的索引是length - 1。遍历数组最常用的方式是 for 循环for (int i 0; i scores.length; i) { System.out.println(scores[i]); }从 Java 5 开始还支持增强 for 循环语法更简洁for (int value : scores) { System.out.println(value); }增强 for 循环适合“只需要读取元素”的场景但如果需要修改某个位置的元素或者需要跳过/删除元素还是用带索引的传统 for 循环更顺手。另一个常见需求是找数组中的最大值和最小值思路其实非常简单用一个临时变量记录当前最大/最小值遍历时逐一比较即可。4.2 冒泡排序的推演过程为什么叫“冒泡”冒泡排序的核心思想是相邻两个元素两两比较大的往后移就像气泡逐渐浮到水面。以数组{5, 1, 4, 2, 8}为例第一轮比较的过程是比较 5 和 1交换位置变成{1, 5, 4, 2, 8}比较 5 和 4交换位置变成{1, 4, 5, 2, 8}比较 5 和 2交换位置变成{1, 4, 2, 5, 8}比较 5 和 8不交换第一轮结束后最大的数 8 已经“沉”到了最后一位。第二轮只需要比较前 4 个元素最后一位不必再参与。这样每轮都会有一个数被放到正确位置所以总共需要 n-1 轮。对应代码public static void bubbleSort(int[] arr) { for (int i 0; i arr.length - 1; i) { boolean swapped false; for (int j 0; j arr.length - 1 - i; j) { if (arr[j] arr[j 1]) { int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; swapped true; } } if (!swapped) { break; // 本轮没有发生交换说明已经有序 } } }这里我做了一个小优化用一个swapped标志判断当前轮次是否有交换发生。如果某一轮没有任何交换说明数组已经有序可以直接跳出循环。这个优化在面试中会是一个加分点因为大多数基础版本没有这一行。4.3 时间复杂度的直觉理解为什么说冒泡排序“不是最优”冒泡排序的时间复杂度在最坏情况下是 O(n²)也就是说数据规模翻倍耗时大约变成四倍。对少量数据没问题但对十万级数据就会非常慢。所以实际生产环境中排序一般直接用Arrays.sort()它会根据数据类型自动选择更高效的排序算法。初学者学冒泡排序的意义不是让它替代系统库函数而是通过亲手实现一个基础算法理解“循环嵌套”和“元素交换”这两个基本操作。二维数组在 Day2 这里可以先做个了解核心概念是“数组的元素还是数组”。后面学到矩阵运算、图像处理时会频繁使用现在只要知道int[][] matrix new int[3][4]表示三行四列即可不必深挖。5. String的不可变性与比较equals和千万别混用String 是 Java 里最常用也最特殊的类。它特殊在有字面量直接赋值的方式有字符串常量池还有不可变特性。这三个属性叠加在一起制造了初学者最容易犯的几个错误。这个知识点同时也是面试中极高频的八股文题目放在 Day2 讲完全不算超前。5.1 字符串常量池与不可变性为什么“同一个内容”不是“同一个对象”先看下面这段代码String s1 hello; String s2 hello; String s3 new String(hello); System.out.println(s1 s2); // true System.out.println(s1 s3); // false System.out.println(s1.equals(s3)); // trues1 s2为什么是true因为字面量hello会放入字符串常量池s1和s2都指向池中的同一个对象它们的内存地址相同所以判断为真。s3用new String(hello)强制创建了一个新对象这个对象不在常量池里而是在堆内存中另起炉灶所以s1 s3是false。但从内容上看三者都是hello所以equals比较结果为true。String 的不可变性意思是创建后的字符串对象内容不能改变。每次调用substring、toUpperCase等看似“修改”的方法底层其实是创建了一个新字符串对象原对象保持不变。这个设计的好处是安全、可以缓存哈希值、可以安全地共享坏处是频繁拼接字符串会创建大量中间对象性能不佳。5.2 内容比较请统一使用 equals从今天开始养成习惯核心结论很简单判断两个字符串内容是否相同永远使用equals()不要使用。只要涉及用户输入、从文件读取、从网络获取的字符串多数情况下它们都不是常量池里的字面量用肯定会踩坑。比如判断用户输入的单词是否等于quitScanner scanner new Scanner(System.in); String input scanner.nextLine(); if (input.equals(quit)) { System.out.println(程序退出); }如果你写成input quit大概率永远为false因为input是运行时创建的新对象和常量池里的quit不是同一个引用。这也是我在帮初学者看代码时发现频率最高的逻辑bug之一。这里还建议用“常量放在前面”的写法来避免空指针if (quit.equals(input)) { // ... }如果input为nullinput.equals(quit)会抛出NullPointerException而quit.equals(input)会安全地返回false。5.3 String、StringBuilder 与 StringBuffer 的选择正因为 String 不可变频繁拼接字符串时不断创建新对象会带来性能损耗和内存压力。循环一万次拼接字符串就会产生一万个中间对象。这时应该用StringBuilderStringBuilder sb new StringBuilder(); for (int i 0; i 10000; i) { sb.append(i); } String result sb.toString();StringBuilder是可变的字符序列append 操作原地修改不产生中间对象。StringBuffer是它的线程安全版本方法加了同步锁但性能稍差。单线程环境下优先用StringBuilder多线程环境下才考虑StringBuffer。这个区别也是面试里经常提到的考点简单记法就是单线程用 builder多线程用 buffer日常开发大多数时候用 builder。6. 踏入面向对象的第一步类、对象、封装面向对象是 Java 的核心思想也是从“写脚本”到“设计程序”的分水岭。Day2 不需要你把面向对象三特性全部吃透但至少要在代码里写出第一个类并理解“类是用来模拟现实事物的模板”这句话的含义。6.1 从现实事物到类字段是属性方法是行为假设我们要管理一个学生信息系统的简单模型。现实中的学生有名字、年龄、成绩这些属性也有“自我介绍”的行为。用 Java 类来表达就是这样public class Student { private String name; private int age; private double score; public Student(String name, int age, double score) { this.name name; this.age age; this.score score; } public void introduce() { System.out.println(我叫 name 今年 age 岁); } }这里的关键词private把字段对外隐藏外部代码不能直接访问这些字段。再通过public的构造方法和 getter/setter 提供受控的访问入口这就是最简单的封装。封装的意义在于字段的读取和修改可以加入校验逻辑比如设置年龄时不能为负数而不是让外部代码随意赋值。6.2 构造方法、this 关键字与对象创建构造方法负责“初始化对象”名字必须与类名完全一致没有返回值。上面代码里的Student构造方法接收三个参数用this.name name把参数值赋给实例字段。这里的this指代的就是“当前正在创建的这个对象”。创建对象使用new关键字Student stu new Student(张三, 18, 89.5); stu.introduce();new的完整流程是在堆内存中分配空间执行字段的默认初始化然后调用构造方法完成自定义初始化最后返回对象引用。初学者只需要先把这个流程理解成“造了一个新学生出来他自带名字、年龄、成绩还能做自我介绍”即可。6.3 static 修饰符属于类而不属于对象static也是一个让初学者困惑的关键字。它的作用是把成员从“属于对象”变成“属于类本身”。比如学生类里定义static int studentCount用来统计一共创建了多少个学生那么这个变量应该属于类而不是某个学生个体。访问它时通过类名Student.studentCount更规范不推荐用对象名去访问。同理static方法不依赖对象状态可以直接通过类名调用。最典型的就是Math.max()和Arrays.sort()。后面学main方法时会发现它本身就是 static 的因为 JVM 启动时还没有创建任何对象必须要能直接调用。在 Day2 阶段static 不需要深入太多但你需要记住static 成员在类加载时就会初始化它是全局共享的如果在一个 static 方法里直接访问非 static 的实例字段编译器会直接报错因为 static 方法没有this可以指代。7. 新手常见的几个运行时异常从 OutOfMemoryError 看 JVM 内存写代码遇到的报错其实分两类编译错误和运行时异常。编译错误很好处理IDE 会画红线告诉你哪一行有问题运行时异常才是真正让人头疼的因为程序能编译一运行就崩。Day2 阶段我建议你认识四个高频运行时异常它们几乎覆盖了初学者 90% 的报错场景而且都能对应到具体的排查思路。7.1 OutOfMemoryErrorJVM 内存真的不够了热搜词里出现了java: outofmemoryerror: insufficient memory这个场景很多初学者在安装某个大型 IDE 插件或运行内存密集型程序时都可能见到。首先澄清一个概念OutOfMemoryError不是普通的异常Exception它是Error代表 JVM 内存已经严重不足程序通常无法自行恢复。出现这一类问题通常有两种情况。第一种是 JVM 堆内存确实不够用——比如程序里创建了大量对象没有释放或者配置的堆最大值太小。可以通过 JVM 启动参数调整java -Xms256m -Xmx1g MyApp-Xms设置初始堆大小-Xmx设置最大堆大小。但注意调大内存只是“缓解方案”如果程序本身有内存泄漏不改代码的话迟早还会爆。第二种情况更日常你在一些工具或 IDE 里看到 “insufficient memory” 的提示这可能是指 IDE 进程本身启动内存不足常见于电脑内存较小又同时开了多个开发工具的情况。解决方法是关闭不必要的应用或者调整 IDE 的vmoptions文件把堆内存上限调高。对于 Day2 的初学者我的建议是先学会看报错栈顶的异常类型和出错行号而不是一看到内存相关就慌。后面学到集合、IO 时需要注意及时释放不再使用的大对象这样才能从根本上避免 OOM。7.2 NullPointerException最经典的空指针空指针异常是 Java 世界里出现频率最高的运行时异常。它的本质是你调用了“一个不存在的对象”的方法或字段。比如String s null; System.out.println(s.length()); // 这里抛出 NullPointerException排查空指针有一个固定套路看异常栈中报错的那一行找到是哪个对象可能是 null然后用日志或调试器确认。生产环境里还要注意“方法返回 null”的场景特别是从数据库、文件、网络读取数据时返回结果可能是 null调用前最好做判空处理。7.3 ArrayIndexOutOfBoundsException数组越界数组越界异常本质是你访问了不存在的索引。很多人以为“数组有 5 个元素索引就是 0 到 5”实际上是 0 到 4访问arr[5]就会越界。int[] arr {1, 2, 3}; System.out.println(arr[3]); // 越界抛出异常排查方法很简单看报错行号中访问的是哪个索引再确认数组长度。如果是在循环里越界重点检查循环条件是不是写了 length正确写法是 length。掌握这些异常不是为了死记硬背而是为了建立“看异常栈定位问题”的能力。很多初学者看到一堆红色报错就慌其实只要冷静下来看第一行Exception in thread main下面的信息找到at com.example.MyClass.main(MyClass.java:12)这行就知道出错位置和原因了。8. Day2 实战自测三个练习题 三道面试问答热身理论看了不少如果不动手写三天后就会忘光。Day2 的最后我建议你用下面三个练习来检验自己再花十分钟看看三组面试问答的核心逻辑。练习题不需要提交给任何人但请务必自己动手写一遍不要直接看答案。8.1 练习题设计覆盖今天的核心知识点练习一打印带行号的九九乘法表要求用双层循环实现但每行开头要标注行号增强 for 和传统 for 至少各用一次。这个练习考察循环嵌套和格式化输出如果你能在二十分钟内独立完成流程控制这关就算过了。练习二数组排序与逆序并输出定义一个长度 10 的整型数组手动填入一些乱序数据用冒泡排序实现升序然后将数组逆序输出。这道题要求你不仅要写出来还要能说清楚每轮排序后数组的变化过程这会训练你的逻辑推演能力。练习三设计一个简单“银行账户”类定义一个BankAccount类包含私有字段balance余额提供deposit(double amount)、withdraw(double amount)和getBalance()三个方法并且保证余额不能为负数。这道题要求你真正理解封装、方法设计和校验逻辑是 Day2 面向对象部分的综合应用。8.2 三道经典面试问答先理解再背诵第一问和equals()有什么区别对基本类型比较的是值对引用类型比较的是内存地址equals()默认比较地址但 String 等类重写后比较内容。实际开发中判断对象内容是否相同绝大多数场景用equals()。第二问Java 是值传递还是引用传递严格答案是 Java 只有值传递。基本类型传递值的副本引用类型传递引用地址的副本。因此方法内部修改参数重新赋值不会影响外部变量但通过引用修改对象内部状态会影响外部。第三问String 为什么设计成不可变的主要原因有三个安全字符串常被用作参数、编码、类名不可变防止被篡改、缓存字符串哈希值可以被缓存提升 Map 等容器的性能、线程安全不可变对象天然线程安全。这也是为什么字符串常量池能复用同一个对象。8.3 一份来自过来人的学习节奏建议写代码这件事输入和输出是两回事。看懂了、听懂了和能独立写出来中间隔着大量的刻意练习。Day2 的内容密度已经不小如果你在某个点上卡住了不用硬钻可以先把示例代码抄一遍并运行通过然后再尝试改动一些参数观察结果变化。代码运行出错不是失败而是程序在向你传递信息。我的个人经验是当天学完的内容一定要在晚上再花半小时从头写一遍不参考笔记。能写出来就说明真的理解了写不出来明天早晨先复习再学新课。基础阶段不需要追求速度而要在意每个概念是否真正长在了自己的脑子里。
返回列表