能把人从"入门"劝退到"放弃"的,往往不是Java语法有多难,而是满屏的黑话:JDK、JVM、JRE、多态、泛型、Lambda、序列化……每个词单独看都认识,连在一起就集体装死。我当年学Java踩过的坑,至少有一半不是代码逻辑写错,而是根本没搞懂教程里那些术语到底在说什么。这篇就把Java入门路上最常见的术语一次性翻译成人话,配合实际场景讲清楚它们解决什么问题、怎么用、有哪些坑。不管你是刚装好JDK,还是正在背面试题,这篇都值得收着慢慢看。
1. JDK、JVM、JRE:三个连读缩写,到底谁装了谁
1.1 三个缩写的关系,一张关系图就能看懂
先解决第一个劝退点。刚接触Java的人,十有八九被这三个缩写搞懵过:JDK、JRE、JVM,看着像三兄弟,其实是俄罗斯套娃。
- JVM(Java Virtual Machine,Java虚拟机),是真正运行Java字节码的那个"引擎"。Java程序编译后不是直接给操作系统跑的,而是给JVM跑的。JVM负责把字节码翻译成当前操作系统能识别的机器指令。
- JRE(Java Runtime Environment,Java运行环境),包含JVM和Java核心类库。只要你想运行别人写好的Java程序,装JRE就够了。它的作用是提供一个"能跑Java程序"的容器。
- JDK(Java Development Kit,Java开发工具包),包含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%\bin(Windows)或$JAVA_HOME/bin(Linux/macOS)。这样命令行才能直接找到java和javac这两个可执行文件。
配置完必须重新打开一次终端窗口,因为环境变量是进程启动时读取的,旧窗口不会自动感知新配置。我见过太多人改完变量,在同一个窗口里反复试,怀疑自己是不是配错了,其实就是没重开。
另外两个高频报错,这里提前做个提醒:
编译时提示"源发行版 17 需要目标发行版 17",一般是项目SDK版本和编译器版本不一致。在IDE里统一Project SDK和Java语言级别(Language Level)就能解决,比如都设成17。这个报错本质是编译器版本旧、代码版本新,或者两边版本对不上。
Lombok报错"You aren't 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(类型转换异常)。
用了泛型之后:
List<String> list = new ArrayList<>(); list.add("hello"); String s = list.get(0); // 不用强转,编译器保证类型正确泛型里那些字母有约定俗成的含义:E是Element(元素),T是Type(类型),K和V是Key和Value(键和值)。比如Map<String, Integer>,就是键为String、值为Integer的映射表。
再进阶一点是通配符:List<?>表示任意类型的List,但不能往里添加元素(除了null);List<? 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:无参无返回值Consumer<T>:接收一个参数,无返回值,常用于forEachFunction<T,R>:接收一个T,返回一个RPredicate<T>:接收一个T,返回boolean,常用于过滤Supplier<T>:无参,返回一个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不是Exception
Java的异常体系,顶层是Throwable类,下面分两大支:
- Error:表示JVM层面的严重错误,比如
OutOfMemoryError(内存溢出)、StackOverflowError(栈溢出)。这类问题程序本身无法恢复,通常不需要捕获处理,而是需要调整JVM内存参数或优化代码。 - Exception:表示程序运行中可以捕获、处理的异常。它又分两类:
- 受检异常(Checked Exception):编译器强制要求你必须处理,比如
IOException,不处理代码编译不过。处理方式:try-catch包裹,或者用throws声明抛出。 - 非受检异常(Unchecked Exception):也叫运行时异常,是
RuntimeException的子类,比如NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException。编译器不强制处理,但不代表你可以无视它,它是程序写得不严谨的信号。
- 受检异常(Checked Exception):编译器强制要求你必须处理,比如
处理异常的完整写法:
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 基本类型、包装类与String
Java里有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通常能解决。
框架相关术语,还有几个高频的:
- API(Application Programming Interface),应用程序编程接口,就是别人定义好的一组方法/端点,你按照约定调用就能拿到结果。比如你调微信支付接口、调第三方天气接口,都是在调用API。
- 依赖注入(DI):Spring的核心思想。你不再自己
new对象,而是声明"我需要什么",Spring容器启动时自动把对应的对象给你装配进来。 - 异步(Asynchronize):调用方发起请求后不用傻等结果,先干别的事,等那边处理完了再通知你。比如把数据异步写入Elasticsearch(网上常见问题"ES异步写入java")或者放进消息队列,就是典型的异步场景。对应的概念是同步:必须等结果返回才继续下一步。
这些名词不需要一次全搞懂,先知道它们是干什么的,在Spring Boot实操中你自然会写、会遇见、会理解。
5.4 一张表看懂其他高频面试术语
前面讲的是需要展开理解的,还有一些术语,遇到时能说出轮廓就够了,这里给你列个速查表:
| 术语 | 一句话解释 |
|---|---|
| 反射 | 运行时获取并操作类的信息(字段、方法、构造器),Spring框架的底层基石 |
| 注解 | 给代码加元数据的标记,本身不含逻辑,由框架或编译器解释执行 |
| 序列化 | 把对象转成可传输/可存储的字节流,反序列化就是逆过程 |
| 线程与进程 | 进程是资源分配的最小单位,线程是CPU调度的最小单位,进程内可开多线程 |
| 死锁 | 两个以上线程互相持有对方需要的锁,谁都不退让,程序卡死 |
| 单例模式 | 一个类在整个程序运行期间只有一个实例,比如数据库连接池常用 |
| MVC | Model(数据)、View(视图)、Controller(控制器)三层分离,Web开发经典模式 |
| ORM | 用对象去映射数据库表,让你用面向对象的方式写数据库操作,MyBatis、Hibernate都是ORM框架 |
| AOP | 面向切面编程,把日志、权限等公共逻辑从业务代码里抽走,Spring AOP就是干这个的 |
这些术语你不用一次背完,但学到对应知识点时,最好都能在IDE里敲一个最小Demo去感受它到底干了什么。纯靠眼睛看,永远记不住。
6. 别被"八股文"劝退:我学Java术语的真实方法
有段时间"JAVA八股文"成了热词,指的就是面试前需要背的那些概念题:HashMap原理、Spring Bean生命周期、JVM内存模型。很多人觉得背术语就是背八股文,没什么用。我的看法不太一样:术语和八股文是两种东西。术语是沟通的基础单位,八股文是没理解的术语堆砌。你把一个术语背后的"为什么"想明白了,它就不是八股,而是你的内功。
我入门时的经验,总结成三条分享给你:
第一,遇到新术语不要急着背定义,先把它当黑盒用一遍。比如"泛型"这个词刚出现时,你也可以不深究原理,先照着写List<String>,等用着用着发现"诶,取出元素不用强转了",自然就理解了它的作用。理解来源于使用,而不是背诵。
第二,每个术语都问自己三个问题:它解决什么问题?不写会有什么后果?在哪个真实场景里最常见?能回答上这三问,你才算真正吃透了一个概念。比如"接口"解决的是多实现和多态的问题,不写接口的话代码会高度耦合,最常见的场景是定义统一的数据访问层。
第三,试着把一个术语讲给完全不懂编程的人听。这是费曼学习法的精髓。如果你能用"女朋友"来类比"对象",用"相亲标准"来类比"类",哪怕类比不太严谨,也说明你已经把抽象概念转化为自己的理解了。我当时就是用这种方式,把"多态"讲成"同一句'开门',猫听到会走过来,狗听到会汪汪叫,门禁听到会解锁"——同一个命令,不同对象给出不同反应,老有意思了。
学Java最忌讳的事情,就是拿着一本厚厚的教材从第一章背到最后一章。术语本来应该是地图上的路标,而不是一座座需要翻越的大山。遇到一个看一个,用过一个记一个,过段时间你会发现,那些曾经劝退你的黑话,已经变成你和别人讨论时的日常用语了。真到那时候,你离放弃就远了,离入门和深入,也就近了。