Java 基础概念,一篇给你捋清楚
Java 基础概念,一篇给你捋清楚
最近有朋友刚入行,问我 Java 入门到底要搞懂哪些概念。想了想,把我自己当年学 Java 时绕过弯路的地方整理一下,按顺序讲,尽量不整虚的。
一、JDK、JRE、JVM 这三个东西
这三个概念绕晕过不少人,其实一句话就能说明白。
JVM 是 Java 虚拟机,负责执行编译后的字节码文件,也就是那个 .class 文件。我们常说的“一次编写,到处运行”,靠的就是它——不同平台有不同的 JVM 实现,但字节码是同一份。
JRE 是 Java 运行环境,比 JVM 大一圈,等于 JVM 加上运行程序必需的一些核心类库。你的程序只是要跑起来,装个 JRE 就够了。
JDK 是开发工具包,最大,等于 JRE 再加上一堆开发工具,javac、javadoc、jstack 这些都在里面。要写代码,装 JDK。
所以包含关系是 JDK > JRE > JVM,记住这个顺序基本就不会搞混。
二、基本数据类型和引用类型
Java 的数据类型分两大类,八种基本类型加引用类型。
基本类型是 byte、short、int、long、float、double、char、boolean,直接存值,存在栈上(严格说是存在栈帧的局部变量表里)。int 占 4 个字节,long 占 8 个,这个在算内存的时候用得上。
引用类型就多了,类、接口、数组都是。变量里存的不是对象本身,而是对象在堆里的地址,所以叫“引用”。这也是新手最容易懵的地方——两个变量用 == 比较,比的是地址不是内容,要比内容得用 equals:
String a = new String("hello");
String b = new String("hello");
System.out.println(a == b); // false,地址不同
System.out.println(a.equals(b)); // true,内容相同
顺带说一句 String,它虽然是个类,但因为用得太频繁,JVM 对它做了不少特殊处理,比如字符串常量池。这也是为什么面试总爱问 String 为什么设计成不可变的。
三、面向对象的三大特性
封装、继承、多态,这三个词背了这么多年,其实核心就一层意思:怎么把代码组织得更好维护。
封装就是把数据和操作数据的方法包在类里,内部实现细节藏起来,外面只暴露必要的方法。字段一般设成 private,通过 getter/setter 访问。有人觉得写 getter/setter 很啰嗦,但当几十处代码都直接读你的字段时,你就知道封装的价值了。
继承用 extends,子类拿到父类的字段和方法,可以复用也可以重写。Java 是单继承,一个类只能有一个直接父类,但可以实现多个接口。
多态是三个里面最精髓的。同一个方法调用,跑的时候执行的是哪个版本,取决于对象实际是什么类型:
Animal animal = new Dog();
animal.makeSound(); // 输出狗叫,而不是 Animal 里的默认实现
这就是动态绑定。写框架代码的时候特别依赖这个特性,面向接口编程,底下的实现随便换。
四、接口和抽象类
这两个东西长得像,用途不一样。
抽象类是“是什么”的关系,抽取的是一类事物的公共部分,可以带普通方法和字段。接口是“能做什么”的关系,定义的是一组行为契约,Java 8 之后接口里也能有 default 方法了。
选择上不用太纠结:要复用代码、有明显的父子层级,用抽象类;只关心能力、不关心是谁来实现,用接口。实际项目里接口用得多,因为解耦。
五、集合框架
平时写代码打交道最多的就是 java.util 包下这一堆。
List 是有序可重复,常用 ArrayList 和 LinkedList。绝大多数场景用 ArrayList 就行,底层是数组,随机访问快;LinkedList 是链表,理论上插删快,但实际因为它缓存不友好,多数时候反而不如 ArrayList。
Set 是不可重复,常用 HashSet,底层就是个 HashMap,add 的时候靠 hashCode 和 equals 判断重复。
Map 是键值对,HashMap 是绝对主力。它底层是数组加链表,JDK 8 之后链表长度超过 8 且数组长度达到 64 会转成红黑树,防止哈希冲突严重时查询退化成 O(n)。默认容量 16,负载因子 0.75,扩容翻倍。这些参数面试常问,但更重要的是理解为什么——扩容是有代价的,预估好初始容量能省不少事。
线程安全的场景别用老的 Hashtable,用 ConcurrentHashMap。
六、异常处理
异常分两大类:受检异常和非受检异常。受检异常比如 IOException,编译器强制你处理,要么 try-catch 要么往外抛。非受检异常就是 RuntimeException 及其子类,空指针、数组越界这些,编译器不管你。
实践上有几条朴素的经验。一是别捕获了异常什么都不干,空 catch 块是事故排查时的噩梦。二是别拿异常做流程控制,比如用 NumberFormatException 判断字符串是不是数字,能用正则就别这么写。三是自定义异常别滥用,业务代码里定义个统一的业务异常基类,够用了。
try {
doSomething();
} catch (IOException e) {
log.error("处理失败, 参数: {}", param, e); // 异常对象要传进去,堆栈别丢
throw new BizException("处理失败", e);
}
七、泛型
泛型解决的是类型安全的问题。没有泛型的年代,从集合里取元素都得强转,类型错了要到运行时才炸。有了泛型,这类错误在编译期就能拦住。
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // 不用强转
要理解的一点是 Java 的泛型是编译期的,运行时会擦除,泛型信息不在字节码里保留(部分场景例外)。这也是为什么 List<String> 和 List<Integer> 在运行时是同一个 Class,以及为什么不能写 new T() 这种代码。
八、最后
这些概念看着基础,但底层的细节要想全吃透,得在项目里踩过坑才有体会。刚入门不用追求一次全懂,先把封装继承多态和集合这两块用熟,其他的用着用着自然就通了。
- 点赞
- 收藏
- 关注作者
评论(0)