ARTICLE DETAIL

资讯详情

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

Java入门术语全解析:从JDK到Lambda的人话翻译

Java入门术语全解析:从JDK到Lambda的人话翻译 能把人从入门劝退到放弃的往往不是Java语法有多难而是满屏的黑话JDK、JVM、JRE、多态、泛型、Lambda、序列化……每个词单独看都认识连在一起就集体装死。我当年学Java踩过的坑至少有一半不是代码逻辑写错而是根本没搞懂教程里那些术语到底在说什么。这篇就把Java入门路上最常见的术语一次性翻译成人话配合实际场景讲清楚它们解决什么问题、怎么用、有哪些坑。不管你是刚装好JDK还是正在背面试题这篇都值得收着慢慢看。1. JDK、JVM、JRE三个连读缩写到底谁装了谁1.1 三个缩写的关系一张关系图就能看懂先解决第一个劝退点。刚接触Java的人十有八九被这三个缩写搞懵过JDK、JRE、JVM看着像三兄弟其实是俄罗斯套娃。JVMJava Virtual MachineJava虚拟机是真正运行Java字节码的那个引擎。Java程序编译后不是直接给操作系统跑的而是给JVM跑的。JVM负责把字节码翻译成当前操作系统能识别的机器指令。JREJava Runtime EnvironmentJava运行环境包含JVM和Java核心类库。只要你想运行别人写好的Java程序装JRE就够了。它的作用是提供一个能跑Java程序的容器。JDKJava Development KitJava开发工具包包含JRE外加编译器javac、调试器、文档工具等一堆开发工具。你想自己写Java代码并编译运行必须装JDK。打个比方JDK是整套厨房有菜刀、砧板、炉灶JRE是餐厅后厨只需要炉灶热菜JVM就是那个炉灶本身。你在家想做饭得买整套厨房你只是去餐厅吃饭那只需要后厨帮你把菜加热。实际安装时装一个JDK就能覆盖所有需求因为它自带了JRE。但要注意下载的时候别装成JRE-only版本否则你会发现自己写不了代码只能干瞪眼。1.2 JAVA_HOME和PATH环境变量是入门第一道坎不少人在环境配置这步就被劝退了。装完JDK打开命令行敲java -version系统直接回一句java不是内部或外部命令。问题基本出在环境变量上。你需要配两个东西JAVA_HOME指向JDK的安装根目录比如C:\Program Files\Java\jdk-17。注意是根目录不是bin目录。很多工具比如Maven、Tomcat都要靠这个变量找到JDK。PATH在PATH里追加%JAVA_HOME%\binWindows或$JAVA_HOME/binLinux/macOS。这样命令行才能直接找到java和javac这两个可执行文件。配置完必须重新打开一次终端窗口因为环境变量是进程启动时读取的旧窗口不会自动感知新配置。我见过太多人改完变量在同一个窗口里反复试怀疑自己是不是配错了其实就是没重开。另外两个高频报错这里提前做个提醒编译时提示源发行版 17 需要目标发行版 17一般是项目SDK版本和编译器版本不一致。在IDE里统一Project SDK和Java语言级别Language Level就能解决比如都设成17。这个报错本质是编译器版本旧、代码版本新或者两边版本对不上。Lombok报错You arent using a compiler supported by lombok通常是Lombok版本太旧和你当前JDK版本不匹配。解决办法很简单升级Lombok依赖版本。Lombok是通过注解处理器在编译期生成代码的JDK版本变了它没跟上就会罢工。2. 面向对象三件套类、继承、接口的黑话翻译2.1 类与对象图纸和房子的关系面向对象是Java的核心思想也是面试必问。入门第一课就是类和对象很多人听完一脸懵类是什么对象又是什么简单说类是模板对象是这个模板造出来的具体东西。类定义了属性数据和行为方法对象是这些属性被赋予真实数值后的一个具体实例。比如你定义一个Student类public class Student { String name; int age; void sayHello() { System.out.println(我是 name); } }这只是一个图纸真正要用的时候得用new关键字造出一个实实在在的学生对象Student s new Student(); s.name 小明; s.age 18; s.sayHello();new Student()就是调用构造方法来创建对象。构造方法名字和类名一致、没有返回值哪怕你不写编译器也会给一个默认无参构造方法。这个机制很多人一开始不懂写着写着发现为什么我定义了一个带参构造方法new Student()就报错了——因为一旦你手写了构造方法默认无参构造方法就没了必须自己补上。2.2 三大特性封装、继承、多态这是Java入门绕不过去的三座大山也是面试八股文重灾区。封装把数据藏起来对外只暴露必要的方法。最直接的做法是用private修饰字段然后提供getter/setter。为什么要这样绕一圈因为直接暴露字段的话外部可以随便塞一个非法值进来比如年龄传个-1。加了setter之后你可以在里面做校验public void setAge(int age) { if (age 0 || age 150) { throw new IllegalArgumentException(年龄不合法); } this.age age; }继承用extends关键字让子类复用父类的代码。子类继承了父类的非私有字段和方法还能在此基础上扩展。继承描述的是is-a关系狗是动物、学生是人这才适合继承。如果只是为了复用几个方法就硬套继承会埋下很深的坑。多态同一个方法在不同对象上表现出不同行为。最典型的写法是父类引用指向子类对象Animal a new Dog(); a.makeSound(); // 输出汪汪编译期看的是Animal左边类型运行期实际调用的是Dog重写后的方法右边对象。这就是编译看左边运行看右边的由来。没有多态的话你每加一种动物就要写一堆if-else来判断类型代码又臭又长。顺便把两个面试必考的概念一起说了重载和重写。重载Overload同一个类里方法名相同参数列表不同个数、类型、顺序。跟返回值无关编译期就能确定调用哪个。重写Override子类里方法签名和父类完全相同用Override注解标注运行期体现多态。两者的本质区别是重载是同名的不同方法重写是把父类的方法换掉一套实现。2.3 抽象类和接口到底选哪个这也是经典送命题。先明确定义抽象类abstract class用abstract修饰的类。它不能直接new只能被继承。抽象类里可以有抽象方法只有声明没有实现也可以有普通方法、成员变量、构造方法。它的设计意图是把多个子类的公共部分抽出来。接口interface在Java 8之前接口里只能有抽象方法和常量Java 8之后加了default和static方法Java 9之后还允许私有方法。接口描述的是能做什么can-do是一种能力契约。两者的区别用表格看更清楚维度抽象类接口继承方式单继承可以多实现成员变量可以有实例变量只能是常量构造方法有没有8以后也不建议方法可以有抽象方法和普通方法抽象方法、default、static语义is-a是什么can-do能干什么实际开发里我个人的倾向是能用接口就用接口需要共享代码才用抽象类。因为Java是单继承类继承了抽象类就没办法再继承别的了而接口可以随便实现扩展性好得多。比如一个Animal抽象类描述动物的共性而Swimmable、Flyable接口可以任意组合到不同的动物类上。2.4 this和super两个容易被忽略的小关键字this指向当前对象super指向父类的引用。说指向其实不太严谨super不是对象它只是访问父类成员的特殊关键字。常见用法public class Son extends Father { private String name; public Son(String name) { super(); // 调用父类无参构造方法必须在构造方法第一行 this.name name; // 区分成员变量和局部变量 } public void print() { super.sayHello(); // 调用父类被重写的方法 } }一个容易踩的坑调用super()或者this()时必须放在构造方法第一行不能同时写两个。因为构造方法必须先确保父类部分初始化完成子类部分才能建起来。这个顺序问题面试官也喜欢拿来考。3. 写第一行代码就躲不掉的术语标识符、运算符、数组、枚举3.1 标识符命名规则编译器不管但同事会骂标识符就是你在代码里给变量、方法、类、包起的名字。Java对标识符有一套硬性规则违反了编译直接报错只能由字母、数字、下划线_、美元符$组成不能以数字开头不能是Java关键字比如class、int、if严格区分大小写name和Name是两个不同的标识符硬性规则之外还有一套行业软性习惯也就是驼峰命名法类名用大驼峰StudentInfo每个单词首字母大写方法和变量用小驼峰getStudentName首单词全小写后面单词首字母大写常量全大写加下划线MAX_AGE如static final int MAX_AGE 100。这些习惯编译器不管但你的同事和未来的自己会非常在意。一个变量叫a、一个叫totalCount前者看起来也没错但维护起来就是灾难。命名规范是入门阶段最值得养成的习惯没有之一。3.2 运算符和表达式和equals是一道分水岭表达式就是运算符 操作数组成的式子比如a b * 3。运算符本身不难难在怎么用对。最经典的坑就是和equals。比较基本类型时比较的是值比较引用类型时比较的是对象的内存地址。equals如果没有重写行为等同于String等类重写了它用来比较内容。看这段代码String a new String(abc); String b new String(abc); System.out.println(a b); // false两个不同对象地址不同 System.out.println(a.equals(b)); // true内容相同如果把String换成直接赋值String c abc; String d abc; System.out.println(c d); // true常量池复用字符串常量池的思想是同样内容的字符串字面量在常量池里只保留一份所以c和d实际上指向同一个对象。新手很容易在这里被绕晕记住一个口诀就行比较字符串内容一律用equals别整。运算符里还有一类容易忽略的位运算符、|、^、、。前面三个听起来像逻辑运算符和||但它们一个按位操作、一个短路求值不一样。入门阶段可以只做了解但面试偶尔会问比如判断奇数偶数用(n 1) 0比n % 2 0更高效。3.3 数组和越界异常下标从0开始这个设定很致命数组是Java里一种定长的、元素类型相同的数据结构。定义方式int[] arr new int[5]; // 声明一个长度为5的整型数组这个数组有5个位置可取的下标是0到4一共5个。如果你访问arr[5]就会抛出ArrayIndexOutOfBoundsException数组索引越界异常。这是Java初学者遇到频率最高的异常之一。为什么从0开始这是一个历史渊源问题你可以理解为数组的索引就是偏移量第一个元素的偏移量是0第二个是1……不管怎样记住了就行数组长度为n时合法下标是0到n-1。排查越界异常的方法很直接打印数组长度arr.length对比你写的循环边界。很多越界都出在for (int i 0; i arr.length; i)这种写法上多写了一个。关于数组还有一个概念叫顺序表。顺序表本质上就是用数组实现的一种线性表逻辑上相邻的元素在物理存储上也相邻。Java集合里的ArrayList就是一个动态扩容的顺序表它内部维护一个Object[]数组添加元素时如果容量不够就创建一个更大的新数组然后把旧数据复制过去。你写LeetCode或者其他算法题时需要手动实现顺序表其实就是封装一个数组提供增删改查的方法。数组和ArrayList的选择原则数量固定且基本不增删用数组数据量动态变化、频繁增删用ArrayList。数组访问快因为内存连续但增删元素要移动后面所有元素代价高。3.4 枚举类型比常量写法优雅一百倍在没有枚举之前定义一组常量通常是这样public static final int STATUS_SUCCESS 200; public static final int STATUS_ERROR 500;这种写法能用但有几个隐患类型不安全传一个300也不会报错、没有命名空间不好看、无法携带额外信息。枚举enum就是来解决这些问题的public enum Status { SUCCESS(200), ERROR(500); private final int code; Status(int code) { this.code code; } public int getCode() { return code; } }这样写Status.SUCCESS就是那个唯一实例想传一个不存在的值编译器直接拦下。枚举还能配合switch使用也可以调用values()拿到所有枚举值数组或者用valueOf(SUCCESS)按名字获取对应枚举。我在实际项目里用枚举最多的场景就是状态机订单状态待支付、已支付、已发货、已完成、已取消、接口返回码、错误码。枚举本质上是一个特殊的类你可以给它加字段、加方法这比单纯的常量高级太多。4. 进阶路上的硬骨头static、泛型、Lambda、异常体系4.1 static和final两个高频修饰符必须分清static表示静态的它修饰的成员属于类本身而不是某个对象。所以你可以不创建对象直接访问public class MathUtil { public static int add(int a, int b) { return a b; } } // 直接用类名调用 int result MathUtil.add(1, 2);static的坑在于静态方法里不能直接访问非静态成员。因为静态成员在类加载时就存在了此时可能还没有任何对象而非静态方法是依赖对象存在的。反过来非静态方法可以访问静态成员。final表示不可变的修饰的对象不一样含义也不同final类不能被继承。比如String类就是final的这是为了保证字符串的不可变性和安全性防止被子类篡改。final方法不能被重写。当你想限制子类不要改变某个方法的逻辑时用。final变量一旦赋值就不能再改。基本类型的值不能变引用类型的指向不能变但对象内部状态可以变。两者组合用的经典写法是定义常量public static final double PI 3.1415926;这里static保证只有一份final保证不可变。4.2 泛型把类型也变成一种参数泛型Generic的概念一句话概括把类型当作参数来传递。在没有泛型之前集合里放任何类型都可以取出来时就得强转稍不小心就ClassCastException类型转换异常。用了泛型之后ListString list new ArrayList(); list.add(hello); String s list.get(0); // 不用强转编译器保证类型正确泛型里那些字母有约定俗成的含义E是Element元素T是Type类型K和V是Key和Value键和值。比如MapString, Integer就是键为String、值为Integer的映射表。再进阶一点是通配符List?表示任意类型的List但不能往里添加元素除了nullList? extends Number表示泛型是Number或其子类的List用于读场景List? super Integer用于写场景。这个用熟了看框架源码就能轻松很多。泛型解决的核心问题就是提前把类型错误暴露在编译期而不是等你运行到一半才炸。4.3 Lambda表达式匿名函数的语法糖Lambda表达式是Java 8引入的重磅特性。它的本质是一个匿名函数用来简化那些只有一个抽象方法的接口实现。拿线程来说传统写法Runnable r new Runnable() { Override public void run() { System.out.println(线程执行了); } };Lambda写法Runnable r () - System.out.println(线程执行了);是不是清爽很多() -里的()是参数列表-后面是方法体。那什么样的接口能用Lambda简化答案就是函数式接口只有一个抽象方法的接口。为了防止误用Java提供FunctionalInterface注解来标记。常见的函数式接口有Runnable无参无返回值ConsumerT接收一个参数无返回值常用于forEachFunctionT,R接收一个T返回一个RPredicateT接收一个T返回boolean常用于过滤SupplierT无参返回一个T举个实际常用的场景排序。你要把一组人按名字排序people.sort(Comparator.comparing(Person::getName));Person::getName是方法引用相当于person - person.getName()的一种简写。如果想让某个特殊元素排最前面可以自定义比较器people.sort(Comparator.comparing(Person::getId, (id1, id2) - id1 1 ? -1 : id1.compareTo(id2)));这段的意思就是id为1的人排第一位其他人按id升序。这种写法在列表展示置顶功能时特别实用。4.4 异常体系Error不是ExceptionJava的异常体系顶层是Throwable类下面分两大支Error表示JVM层面的严重错误比如OutOfMemoryError内存溢出、StackOverflowError栈溢出。这类问题程序本身无法恢复通常不需要捕获处理而是需要调整JVM内存参数或优化代码。Exception表示程序运行中可以捕获、处理的异常。它又分两类受检异常Checked Exception编译器强制要求你必须处理比如IOException不处理代码编译不过。处理方式try-catch包裹或者用throws声明抛出。非受检异常Unchecked Exception也叫运行时异常是RuntimeException的子类比如NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException。编译器不强制处理但不代表你可以无视它它是程序写得不严谨的信号。处理异常的完整写法try (FileInputStream in new FileInputStream(a.txt)) { // 读取文件 } catch (IOException e) { e.printStackTrace(); }这里用到了try-with-resources语句Java 7引入的。只要资源类实现了AutoCloseable接口写在try后面的括号里代码块结束后资源会被自动关闭。好处是少写一堆finally里关资源的样板代码还能避免忘记关闭资源导致的内存泄漏。说句实在话异常这东西入门阶段最需要培养的思维是看异常栈信息不猜、不慌。第一行是异常类型往下是出错的文件名和行号从下往上找自己写的代码那一层基本就是问题所在。5. 面试和实战高频名词集合、框架、构建工具5.1 集合框架List、Set、Map三大阵营集合是Java里对容器的统称专门用来存多个对象。集合框架主要分两大派Collection和Map。Collection下面又分List有顺序、可重复、Set无顺序、不可重复、Queue队列。面试最爱问的是List的几个实现类区别实现类底层结构查询性能增删性能适用场景ArrayList数组快随机访问O(1)慢后移元素查询多、增删少LinkedList双向链表慢O(n)快改指针频繁在头部/尾部增删Vector数组慢方法同步慢基本已淘汰用ArrayList替代Map里的重头戏是HashMap它用哈希表实现put和get操作平均时间复杂度都是O(1)。面试官喜欢问它的底层原理首先计算key的hashCode找到对应的桶数组位置如果桶里已经有元素了就通过equals判断是覆盖还是形成链表当链表长度超过阈值8且数组长度达到64链表会转成红黑树提升查询效率当元素数量超过容量的75%时会触发扩容重新散列。Java 8之后HashMap的链表加红黑树是高频考点。你不需要背得特别细但至少要能说出哈希冲突时怎么处理、什么时候树化、为什么用红黑树。另外注意HashMap是线程不安全的多线程环境下要么用ConcurrentHashMap要么用Collections.synchronizedMap()。5.2 基本类型、包装类与StringJava里有8种基本类型byte、short、int、long、float、double、char、boolean。但集合里不能放基本类型只能放对象所以就有了对应的包装类Integer、Long、Character等。Java会自动完成基本类型和包装类之间的转换这就是自动装箱和自动拆箱Integer i 100; // 自动装箱int - Integer int j i; // 自动拆箱Integer - int这里有一个经典大坑Integer的缓存机制。Integer默认缓存了-128到127之间的值所以Integer a 100; Integer b 100; System.out.println(a b); // true命中缓存 Integer c 200; Integer d 200; System.out.println(c d); // false超出缓存范围创建了新对象对象用比的是地址所以超出缓存范围就变成false了。日常开发里包装类型的比较一律用equals这是一条铁律。再说String。String是不可变类内部用final的char数组Java 9后是byte数组存储。字符串拼接时千万不要用拼很多次每拼一次都会生成新的String对象效率低还浪费内存。频繁拼接字符串要用StringBuilder。StringBuffer和StringBuilder的区别是前者方法加了synchronized线程安全但性能略低单线程环境下用StringBuilder就够了。5.3 框架里的名词Spring Boot、Maven、Lombok都是啥很多新手在Java入门和找到工作之间卡在框架这座大山。其实框架相关的术语本质就是别人帮你封装好的工具和约定。框架Framework是什么你可以理解成一套半成品。骨架别人已经搭好了你只需要在指定的位置填业务代码。比如Spring Boot它预先做好了一堆配置你只要写一个RestController注解的类加一个GetMapping(/hello)方法接口就能跑起来不需要自己搭服务器、处理请求分发这些繁琐环节。Maven和Gradle是构建工具核心功能之一是依赖管理。以前你引入一个第三方库得自己下载jar包手动放进lib目录还要担心版本冲突。有了Maven你只需要在pom.xml里声明依赖它会自动帮你下载并管理版本。面试时经常提到的会不会Maven实际上就是问你能不能看懂pom.xml、会不会用mvn clean package这样的命令。Lombok是一个注解处理器框架作用是用注解减少模板代码。最典型的Data public class User { private String name; private int age; }一个Data注解Lombok在编译时自动帮你生成getter、setter、toString、equals、hashCode这些方法。我前面提到过它和JDK版本不匹配会报错这算是Lombok最大的痛点遇到问题先看版本升级Lombok通常能解决。框架相关术语还有几个高频的APIApplication Programming Interface应用程序编程接口就是别人定义好的一组方法/端点你按照约定调用就能拿到结果。比如你调微信支付接口、调第三方天气接口都是在调用API。依赖注入DISpring的核心思想。你不再自己new对象而是声明我需要什么Spring容器启动时自动把对应的对象给你装配进来。异步Asynchronize调用方发起请求后不用傻等结果先干别的事等那边处理完了再通知你。比如把数据异步写入Elasticsearch网上常见问题ES异步写入java或者放进消息队列就是典型的异步场景。对应的概念是同步必须等结果返回才继续下一步。这些名词不需要一次全搞懂先知道它们是干什么的在Spring Boot实操中你自然会写、会遇见、会理解。5.4 一张表看懂其他高频面试术语前面讲的是需要展开理解的还有一些术语遇到时能说出轮廓就够了这里给你列个速查表术语一句话解释反射运行时获取并操作类的信息字段、方法、构造器Spring框架的底层基石注解给代码加元数据的标记本身不含逻辑由框架或编译器解释执行序列化把对象转成可传输/可存储的字节流反序列化就是逆过程线程与进程进程是资源分配的最小单位线程是CPU调度的最小单位进程内可开多线程死锁两个以上线程互相持有对方需要的锁谁都不退让程序卡死单例模式一个类在整个程序运行期间只有一个实例比如数据库连接池常用MVCModel数据、View视图、Controller控制器三层分离Web开发经典模式ORM用对象去映射数据库表让你用面向对象的方式写数据库操作MyBatis、Hibernate都是ORM框架AOP面向切面编程把日志、权限等公共逻辑从业务代码里抽走Spring AOP就是干这个的这些术语你不用一次背完但学到对应知识点时最好都能在IDE里敲一个最小Demo去感受它到底干了什么。纯靠眼睛看永远记不住。6. 别被八股文劝退我学Java术语的真实方法有段时间JAVA八股文成了热词指的就是面试前需要背的那些概念题HashMap原理、Spring Bean生命周期、JVM内存模型。很多人觉得背术语就是背八股文没什么用。我的看法不太一样术语和八股文是两种东西。术语是沟通的基础单位八股文是没理解的术语堆砌。你把一个术语背后的为什么想明白了它就不是八股而是你的内功。我入门时的经验总结成三条分享给你第一遇到新术语不要急着背定义先把它当黑盒用一遍。比如泛型这个词刚出现时你也可以不深究原理先照着写ListString等用着用着发现诶取出元素不用强转了自然就理解了它的作用。理解来源于使用而不是背诵。第二每个术语都问自己三个问题它解决什么问题不写会有什么后果在哪个真实场景里最常见能回答上这三问你才算真正吃透了一个概念。比如接口解决的是多实现和多态的问题不写接口的话代码会高度耦合最常见的场景是定义统一的数据访问层。第三试着把一个术语讲给完全不懂编程的人听。这是费曼学习法的精髓。如果你能用女朋友来类比对象用相亲标准来类比类哪怕类比不太严谨也说明你已经把抽象概念转化为自己的理解了。我当时就是用这种方式把多态讲成同一句开门猫听到会走过来狗听到会汪汪叫门禁听到会解锁——同一个命令不同对象给出不同反应老有意思了。学Java最忌讳的事情就是拿着一本厚厚的教材从第一章背到最后一章。术语本来应该是地图上的路标而不是一座座需要翻越的大山。遇到一个看一个用过一个记一个过段时间你会发现那些曾经劝退你的黑话已经变成你和别人讨论时的日常用语了。真到那时候你离放弃就远了离入门和深入也就近了。
返回列表