尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java基础核心模块详解:从环境搭建到集合框架

Java基础核心模块详解:从环境搭建到集合框架 不废话直接聊正事。我这些年带过不少刚入行的新人也看过太多简历上写着“熟悉Java基础”但一问HashMap为什么线程不安全就卡壳的候选人。很多人觉得Java基础就是语法背一背就完事真到写代码和面试的时候才发现基础不牢后面学Spring Boot、微服务全是空中楼阁。这篇博文就老老实实把Java基础一阶段最核心的几个模块拆开讲清楚——环境搭建、语法核心、面向对象、集合框架每一步该怎么做、为什么要这么做、坑在哪里我尽量一次讲透。这篇内容适合两类人一类是完全零基础、准备系统入坑Java的初学者另一类是已经写过一些代码但基础不扎实、想回头补课顺便应对面试的朋友。看完你至少能自己完成环境配置、看懂基础语法、理解面向对象和集合的核心用法也知道接下来该往哪个方向继续学。1. 学习路线与核心模块拆解Java基础先啃哪几块1.1 为什么我把“环境搭建”放在第一位很多教程上来就直接写System.out.println(Hello World)环境变量这些细节一笔带过。我个人的观点恰恰相反环境搭建和配置值得你专门花半天时间认真搞明白。原因很简单如果你连JDK和JRE的区别、JAVA_HOME为什么要配、PATH里加了什么都不知道后面出了问题你连排查的方向都没有。我见过太多人在群里问“为什么javac不好使”结果一问SDK根本没装对版本或者环境变量配到了用户变量里命令行窗口没重启。环境搭建这个环节不是“装个软件”那么简单。它背后涉及Java程序从源代码到运行的本质.java源文件经过javac编译成.class字节码再由JVM解释执行。这个“一次编译到处运行”的跨平台特性靠的就是JVM。你配置环境变量本质上是告诉操作系统“去哪找编译器和运行时”所以搞清楚这一套流程比单纯记命令有意义得多。1.2 Java基础一阶段必须掌握的五块内容参考我自己的学习经历和实际带人的经验Java基础一阶段的知识范围可以收敛成五块开发环境JDK安装、环境变量配置、IDE选型IDEA、Eclipse或MyEclipse、Maven/Gradle的基础认知。语法核心变量与数据类型、标识符命名规则、运算符与表达式、流程控制、数组、枚举。面向对象类与对象、封装继承多态、接口与抽象类、内部类、static/final关键字。核心APIString、StringBuilder、Date/Time、包装类、异常处理。集合框架List/Set/Map的核心实现类、泛型、迭代器、Stream基础操作。这一篇重点讲前两块和集合框架面向对象单独拎出来讲一部分因为内容量太大拆开消化效果更好。我见过不少人一上来就啃《Java编程思想》看了两个月还在前几章反而把自己劝退了。基础阶段别贪多先把这五块内容吃透能写点小工具、小游戏练手就算过关。1.3 学习方式上的一个建议先抄后理解这里我多插一句学习方法。基础阶段的语法和API刚开始没必要硬背。我当年学运算符优先级的时候也是死记硬背后来发现与其背表格不如多做几道表达式求值的题目做错了再看优先级表印象反而更深。包括集合类的API方法也不需要一个个背多用几遍自然记住了。所以这篇文章里我给的代码示例都不长但我建议你务必亲手敲一遍运行出结果再试着改动参数看输出变化这才是你自己的经验。2. 环境搭建实操从JDK下载到javac命令跑通2.1 JDK版本怎么选才不踩坑先聊版本。现在Oracle JDK和OpenJDK都能用我个人建议初学者直接用官方JDK版本选择按“稳定、生态兼容性好”的原则来。目前主流企业项目很多还在Java 8和Java 11新项目用Java 17的也不少。如果你是刚开始学装Java 17或者Java 21都可以但如果IDE用了某些旧版插件、或者跟着老教程学可能遇到兼容问题。我踩过的一个坑是本机同时装了JDK 8和JDK 17结果java -version显示8javac -version却是17两个版本混着用编译出来的class文件行为很诡异。后来把旧版彻底卸载只保留一个JDK问题立刻消失。所以建议初学者一台机器就装一个JDK版本不要折腾多版本管理等有经验了再用JAVA_HOME切换或者工具管理也不迟。还有一个常见问题是“JDK和JRE的区别”。JRE是Java运行环境只包含JVM和核心类库JDK是Java开发工具包除了JRE还包括编译器、调试器等开发工具。简单说你要开发就装JDK只运行Java程序装JRE就够但JDK是向下包含的所以直接装JDK最省事。提示下载JDK时注意区分平台架构Windows版有x64和ARM64之分macOS有Intel和Apple Silicon之分。装错架构虽然能装但运行性能有影响个别情况下还会报错。2.2 环境变量配置详解JAVA_HOME、PATH和CLASSPATH环境变量配置是新手最容易出问题的地方。以Windows为例配置步骤是右键“此电脑” - 属性 - 高级系统设置 - 环境变量。然后按下面这套来配。JAVA_HOME新建系统变量变量值填JDK的安装根目录比如C:\Program Files\Java\jdk-17。配这个变量不是为了好看Maven、Tomcat、IDEA这些工具都会通过它来找JDK以后你切换JDK版本只需要改这一个变量其他工具不用动。PATH在已有的Path变量里追加一条%JAVA_HOME%\bin。注意是追加不是覆盖。如果覆盖了系统原来的命令可能全部失效。这一步的作用是让操作系统能在任意目录下找到java.exe和javac.exe。CLASSPATH这个东西在JDK 1.5之后其实可以不配了因为JVM默认会在当前目录找类。但如果某些老教程让你配你可以配成.;%JAVA_HOME%\lib注意最前面那个点代表当前目录。我见过一个真实案例CLASSPATH里少配了那个点结果Java文件执行时报“找不到或无法加载主类”排查半天才发现是CLASSPATH的问题。macOS和Linux环境下一般在~/.bash_profile或~/.zshrc里配置环境变量。比如export JAVA_HOME$(/usr/libexec/java_home -v 17) export PATH$JAVA_HOME/bin:$PATH/usr/libexec/java_home是macOS自带的JDK路径查找工具能自动定位比硬编码路径稳得多。Linux下就老老实实写export JAVA_HOME/usr/local/jdk-17这种实际路径。2.3 验证安装javac和java缺一不可配置完环境变量最关键的一步是验证。关掉之前打开的命令行窗口重新打开一个然后执行java -version javac -versionjava -version能输出版本号说明JVM可用javac -version能输出编译器的版本号说明CDK可用。这两条命令必须都通过缺一个都不行。我第一次配环境变量的时候就只验证了java -version以为完了结果一写Hello World编译直接报“找不到javac”又重新折腾了半天。验证通过后再准备一个测试文件名字必须是Hello.java因为Java要求public类的类名和文件名一致。内容public class Hello { public static void main(String[] args) { System.out.println(Hello Java!); } }在命令行进入文件所在目录执行javac Hello.java会生成一个Hello.class文件再执行java Hello不带.class后缀输出Hello Java!就说明整个链路完全跑通了。这里强调一点运行时一定不要带.class后缀java Hello.class会报错这个坑新手基本都踩过。3. 语法核心拆解标识符、运算符、枚举这些点必须吃透3.1 标识符命名规则网上报错一大半和它有关标识符就是给类、方法、变量起的名字。Java的命名规则归纳起来就是几条只能由字母、数字、下划线_、美元符号$组成数字不能开头。不能是Java关键字比如class、public、static这些不能当标识符。严格区分大小写Name和name是两个不同的标识符。理论上中文可以做标识符但我强烈不建议写编码问题会坑死自己和同事。命名规范方面不用死记记住三个习惯类名用大驼峰StudentInfo变量和方法名用小驼峰studentName、getName常量全大写加下划线MAX_SIZE。这个规范不是Java语法强制的但所有Java开发都这么写你写student_name这种蛇形命名在Python里没问题在Java团队里会被review打死。我遇到过一个很有意思的报错场景某人写了个变量叫class_1没毛病编译通过了另一个人写1class直接编译报错。还有人把$用在标识符里虽然合法但$在一些框架里是有特殊含义的常规代码里最好别用。总之一句话命名就按规范来语法上取巧没什么实际好处。3.2 运算符与表达式优先级不用死背但要会查会验证运算符这一块网上搜“Java运算符”能找到一堆优先级表看着就头大。我的建议是常见的优先级要记住冷门的不要背需要的时候用括号。需要熟练掌握的运算符有几类算术运算符-*/%。注意整数相除结果还是整数5 / 2结果是2而不是2.5%是取余判断奇偶、判断倍数都用它。关系运算符!结果是布尔值。比较基本类型时比较值比较引用类型时比较的是地址这个面试高频考点后面细说。逻辑运算符||!。重点记住短路特性左边为false右边不再执行||左边为true右边不再执行。后面写代码判空经常靠这个特性。赋值运算符-*/%。比如a 1等价于a a 1只是一般会做一次隐式类型转换。三元运算符条件 ? 值1 : 值2相当于简化的if-else表达式。位运算符|^~。新手阶段了解即可但和|不短路这点要留个印象和、||区分开。优先级方面儿口诀“单目 算术 关系 逻辑 赋值”大体没错但真要写复杂表达式别考验阅读者直接加括号。比如(a b) * c写成这样谁看都知道先算加法。有一点要特别提醒和equals的区别这是新人重灾区。字符串比较用经常得到false因为比较的是对象引用地址。字符串内容比较应该用equals()。比如String a new String(java); String b new String(java); System.out.println(a b); // false两个不同对象 System.out.println(a.equals(b)); // true内容相同顺便一提如果直接String s1 java; String s2 java;由于字符串常量池的存在s1 s2的结果是true很多新手在这里彻底懵掉。这个点放到进阶篇讲JVM时再展开现在你只要记住字符串内容比较不要用。3.3 枚举类型比常量类好用太多枚举enum是Java 5引入的基础阶段很多人不重视觉得用public static final int定义常量就够了。实际开发中枚举的用处非常大尤其是状态机、类型区分这类场景。先看一个简单示例public enum Status { PENDING(0, 待处理), APPROVING(1, 审批中), APPROVED(2, 已通过), REJECTED(3, 已驳回); private final int code; private final String desc; Status(int code, String desc) { this.code code; this.desc desc; } public int getCode() { return code; } public String getDesc() { return desc; } }这种写法比散落的常量强在几个方面。第一类型安全。方法参数如果是Status类型调用方必须传枚举值你传0、1这种数字编译都过不去第二可读性高。看到Status.APPROVED就知道含义比看到数字2强得多第三可以绑定属性和行为。枚举里可以加字段、加方法一个枚举就是一个完整的状态定义。用枚举踩过的一个坑是在switch里用枚举时case分支直接写枚举名不带枚举类型前缀比如case PENDING:而不是case Status.PENDING:。这个语法刚开始容易写错IDE一般会提示自己写的时候留个心。另外要注意枚举本身可以有自己的构造函数但构造函数的访问权限隐含是private这是为了阻止外部直接new枚举实例。枚举值是单例的每个枚举常量在JVM中只有一个实例所以枚举还经常被用来实现单例模式——这是后话但你现在知道这个特性不亏。4. 面向对象基础类、对象、三大特性一次理清4.1 类与对象用生活的例子讲清楚面向对象可能是Java基础里最抽象的一块。别慌我用一个特别俗但特别有效的例子类是图纸对象是依据图纸建出来的房子。图纸定义了房子有什么属性面积、楼层、户型和什么行为开门、关窗但图纸本身不是房子。你new一次就是按图纸建了一栋房子。一栋房子的改动不影响图纸图纸改了新房子才跟着变。代码层面的对应关系public class House { // 属性 private double area; private int floorCount; // 构造方法建房子时执行 public House(double area, int floorCount) { this.area area; this.floorCount floorCount; } // 行为 public void openDoor() { System.out.println(门开了); } }使用的时候House myHouse new House(120.0, 3); House yourHouse new House(89.0, 2);myHouse和yourHouse是两个完全独立的对象各自拥有自己的area和floorCount。这里有个高频面试点myHouse本身存的是引用相当于门牌号不是房子本体。House house2 myHouse;并没有新建房子只是把同一个门牌号又抄了一份给了house2所以改house2的属性myHouse也会变。this关键字在构造方法里也是个考点。this.area area;的意思是“把参数area的值赋给当前对象的area属性”。如果写成area area两边都是参数那属性根本没赋值。4.2 封装、继承、多态面试必问的三大特性封装把属性私有化private对外提供公开的getter/setter方法或者通过公开方法间接操作属性。核心思想是隐藏内部实现细节外部只能通过你暴露的接口操作对象。好处是你能在setter里加校验逻辑防止非法值灌入对象。比如对年龄这个属性你可以在setAge里判断age 0 age 150才赋值否则抛异常这比直接公开字段安全得多。继承用extends关键字实现。子类继承父类的非私有属性和方法代码复用的核心手段。Java是单继承一个类只能有一个直接父类。继承关系用is-a来理解狗是动物所以Dog extends Animal合理但“猫是动物”和“猫是宠物”是两套不同维度的关系只能用接口来解决这就是“组合优先于继承”这句经验之谈的由来。多态同一个方法在不同对象上有不同的行为。多态有三个必要条件继承、方法重写、父类引用指向子类对象。看这个例子Animal a new Dog(); a.makeSound(); // 输出狗的叫声 Animal b new Cat(); b.makeSound(); // 输出猫的叫声a的编译类型是Animal运行时类型是Dog调用的makeSound是Dog重写后的版本。这就是“编译看左边运行看右边”。多态能让你写出面向抽象编程的代码新增一种动物类不需要改动调用方的代码符合开闭原则。4.3 接口与抽象类什么时候用哪个接口和抽象类是面向对象设计里最容易混淆的一对。粗暴一点理解抽象类描述“是什么”接口描述“能干什么”。一个类只能继承一个抽象类但可以实现多个接口。从语法层面看抽象类可以包含普通成员变量、可以定义普通方法、构造函数方法可以用public、protected等任意访问修饰符接口里的成员变量默认是public static final常量方法默认是public abstractJava 8之后可以有default和static方法。什么时候用抽象类当多个类有公共的属性和不完整的行为逻辑时。比如Animal有name属性有eat()具体方法大多数动物都需要吃还有makeSound()抽象方法不同动物声音不同这种情况下定义抽象类很合适。什么时候用接口当你关注“能力”的时候。比如Flyable接口定义fly()方法飞机、鸟、超人各自实现他们之间没有任何继承关系但都有“飞”的能力。这时候用接口灵活得多。我记得有个面试候选人一口气背完了接口和抽象类的区别但问他“为什么Java接口成员变量必须是public static final”他就懵了。这个问题的本质是接口是行为约定不关心内部状态所以不允许有可变的实例字段。理解这个设计意图比背八条区别强多了。5. 集合框架入门List、Set、Map怎么选怎么用5.1 集合框架全景别再用数组打天下能熟练用数组做点小练习之后就该进入集合框架了。集合Collection比数组灵活在两方面长度可变元素可以动态添加删除封装好了各种数据结构按需选用即可。Java集合框架的根接口是Collection下面主要分三大分支List有序、可重复。常用实现ArrayList、LinkedList、Vector。Set无序多数实现、不可重复。常用实现HashSet、LinkedHashSet、TreeSet。Map键值对键不可重复。常用实现HashMap、LinkedHashMap、TreeMap、Hashtable。学习集合框架不建议逐个类去背方法而是抓牢两个核心底层数据结构和使用场景。底层数据结构决定了性能特征使用场景决定了你该选谁。5.2 ArrayList与LinkedList差别比想象中大ArrayList底层是Object数组LinkedList底层是双向链表。这个关键差异决定了它们各自的优缺点。ArrayList的查询快因为底层数组支持按下标随机访问时间复杂度O(1)但中间插入删除慢因为要移动后面的元素。LinkedList的插入删除快因为只需调整前后节点的指针但按下标查找慢需要从头遍历。所以日常开发中ArrayList用得最多因为大多数场景都是遍历和尾部追加这些操作ArrayList都很快。LinkedList适合频繁在头部或中部插入删除的场景比如实现一个队列、双端队列时它更方便。至于面试老爱问的“ArrayList什么时候扩容、扩容多少”默认容量是10扩容是变成原来的1.5倍你可以在源码里看到这句话int newCapacity oldCapacity (oldCapacity 1);右移一位就是除以2所以是1.5倍。HashMap则是另一个高频重点它在Java 8之后底层是数组链表红黑树。默认初始化容量是16负载因子是0.75当元素数量超过容量*负载因子时触发扩容容量翻倍。为什么负载因子是0.75而不是0.5或1.0这是时间和空间的折中太小浪费空间太大容易冲突。我实际写代码时如果能预估数据量就会用带初始容量的构造方法比如new HashMap(64)避免频繁扩容带来的性能损耗。说一个真实的线上案例有一次团队里一个同事用HashMap存了订单数据没指定初始容量数据量从几十万涨到几百万时频繁扩容和rehash接口延迟从50ms飙到2秒。改成预估容量后延迟降回60ms左右。集合的选型和初始化参数选择真不是八股文是有实际性能影响的。5.3 集合实战stream().toArray()与遍历方式的选择Java 8的Stream给集合操作带来了很大的便利但基础阶段我建议先把传统遍历练扎实再上Stream否则容易看不懂老代码。传统遍历常用两种方式ListString list Arrays.asList(java, python, go); // for-each 增强for for (String s : list) { System.out.println(s); } // 迭代器 IteratorString it list.iterator(); while (it.hasNext()) { System.out.println(it.next()); }两者本质上是同一个东西for-each只是迭代器的语法糖。用迭代器遍历时如果要删除元素必须用it.remove()用list.remove()会抛ConcurrentModificationException这个坑我踩过一次后面排查了很久才明白是“在遍历过程中修改了集合结构”导致的问题。至于Stream基础阶段掌握几个常用操作就够filter过滤、map转换、collect收集、toArray转数组。看这个例子ListString names Arrays.asList(Alice, Bob, Charlie); String[] result names.stream() .filter(name - name.length() 3) .map(String::toUpperCase) .toArray(String[]::new);toArray(String[]::new)的意思是“创建一个String数组来接收结果”。很多新人会写成.toArray()这样返回的是Object[]赋值给String[]会编译报错。如果想把Stream转回List用.collect(Collectors.toList())这是最常用的收尾操作。热词里提到的“java list.stream().toarray”应该就是指这个我把参数写法的含义讲清楚String[]::new是一个数组构造器引用告诉Stream按String类型新建数组。如果不传参数直接toArray也行但结果类型是Object[]不方便直接用。我自己在实际开发中toArray用得相对少更多是用collect转List因为List操作更灵活。但有些老接口需要数组参数就必须用到toArray了。5.4 集合线程安全的基础认知集合这块还有一个基础但重要的认知ArrayList、HashMap这些都不是线程安全的。多线程环境下同时写可能出现数据错乱、甚至死循环JDK 7的HashMap在并发扩容时会形成环。引入线程安全的方案有Collections.synchronizedList(list)简单粗暴地加锁CopyOnWriteArrayList写时复制读多写少场景好用ConcurrentHashMap分段锁/细粒度锁并发性能比HashTable好得多。基础阶段不要求你深入理解并发原理但你至少要形成一个意识多线程环境下选了不安全的集合类是要出大事的。这个意识在面试里很加分因为说明你写过并发代码、踩过并发坑而不只是背过“HashMap线程不安全”这个结论。6. 常见问题与排查技巧实录从安装报错到基础面试题6.1 “javac不是内部或外部命令”到底怎么查这是环境变量配置失败后的经典报错。排查顺序我建议这样走确认JDK是否安装成功到C:\Program Files\Java下看有没有jdk目录没有就是安装没完成。确认环境变量配置的位置对不对。系统变量和用户变量都配置了JAVA_HOME时系统变量优先级更高改完系统变量要重启命令行窗口。确认PATH里的引用是否写对%JAVA_HOME%\bin两边的百分号不能少这是Windows环境变量的引用语法。在命令行执行echo %JAVA_HOME%看输出是否为正确的JDK路径不是的话就是JAVA_HOME本身配错了。如果以上都没问题检查是否安装过其他版本的JDK残留旧版本的java.exe可能还在C:\Windows\System32里干扰命令解析。第五点是我自己的惨痛经验很多机器上装过绿色版、免安装版的JDK或者某些软件自带了Java环境导致命令行解析到了错误的java.exe。这种问题单纯改环境变量没用得去C:\Windows\System32下看看有没有java.exe有的话要么删掉要么用完整的绝对路径命令去确认。6.2 MyEclipse启动报“exit code -1”的处理思路热词里有“myeclipse2020 java was started but returned exit code -1”这是Windows下MyEclipse/Eclipse系IDE常见启动失败问题。报错信息的意思是说Eclipse自身运行在一个Java虚拟机上但这个JVM启动失败退出码是-1。常见原因和对策JVM版本不兼容。Eclipse 2020对Java的版本有要求如果JAVA_HOME指向的是JDK 17或更高部分老版本Eclipse会启动失败。解决方法是装一个 Java 8 或 Java 11并在eclipse.ini里显式指定-vm参数指向对应的javaw.exe路径。eclipse.ini参数问题。-Xmx、-Xms这些内存参数设置过大或者-vmargs顺序不对也会导致启动失败。直接删除eclipse.ini里可疑的配置恢复默认再试。工作空间损坏。亏过一次之后我就习惯了把workspace目录下的.metadata临时改名让Eclipse重新生成一个默认工作空间。排查这个报错时还可以打开命令行直接手动执行eclipse.ini里的java命令看它到底报什么错。这个方法比盯着IDE的报错弹窗高效得多因为IDE窗口只会给你一句“exit code -1”而命令行会给你完整的异常堆栈。6.3 Java基础面试高频题速查附答题思路每次聊到Java基础就绕不开面试这个话题。我把热词里那些“java面试题”“java面试八股文”“java面试大全”里出现频率最高、也最能区分基础扎实与否的题整理成一份速查表问题核心答题思路和equals()的区别比较基本类型是值引用类型是地址equals默认也是地址比较但String等类重写后比较内容。String、StringBuilder、StringBuffer区别String不可变StringBuilder线程不安全但性能好StringBuffer方法加了synchronized线程安全但性能略差。ArrayList和LinkedList区别底层结构不同、查询/插入删除性能不同、内存占用不同。HashMap和Hashtable区别线程安全层面HashMap不安全Hashtable安全但性能差空key层面HashMap允许一个null keyHashtable不允许底层结构层面HashMap在Java 8后加了红黑树。接口和抽象类的区别设计思想、语法限制、使用场景三方面展开。重载和重写的区别重载是同一个类里方法名相同但参数列表不同编译期决定重写是子类对父类方法的覆盖运行期决定。为什么重写equals时必须重写hashCode因为HashMap等集合依赖hashCode找桶equals相等则hashCode必须相等否则集合会存出两个“相同”对象。答题的核心技巧是先给结论再解释原因最后补一个例子。背下来没用关键是把过程和原理讲清楚。6.4 我个人的一点体会聊到这儿Java基础一的内容就差不多了。说句掏心窝的话基础阶段的学习最忌“求快”。我见过太多人一周学完“Java基础”下周就冲Spring Boot结果连和equals都分不清写出的代码漏洞百出。基础这个东西快就是慢慢就是快。我自己当年在集合和面向对象上反复折腾了一个多月后来学框架、看源码时反而觉得顺风顺水因为底层的东西早就明白了。最后分享一个小建议学完这一篇别急着继续往后赶先自己尝试写一个包含类、继承、接口、集合、枚举的小项目哪怕是一个“学生管理系统”的命令行版本都行。写完你再看下一篇会有完全不一样的感受。这一篇先到这后面我再接着写Java基础二——异常、泛型、IO流和网络编程咱们按部就班一个一个来。
返回列表