Java基础
内部类
内部类就是类中再套一个类
eg:
1 | class Outer { |
作用:
让一个类和另一个类关系更紧密
内部类可以方便访问外部类的成员
有些场景下写起来更方便,比如事件处理、临时小对象
Java 里常见有 4 种:
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
成员内部类
就是:像成员变量一样,直接写在类里面,不加 static。
1 | class Outer { |
这里 Inner 可以直接访问 Outer 的私有成员 name。
创建对象
成员内部类对象不能直接 new,一般要先有外部类对象:
1 | Outer outer = new Outer(); |
记忆点
成员内部类 依附于外部类对象存在。
静态内部类
就是:内部类前面加 static。
1 | class Outer { |
创建对象
静态内部类不需要依赖外部类对象:
1 | Outer.Inner inner = new Outer.Inner(); |
特点
它只能直接访问外部类的 静态成员,不能直接访问非静态成员。
如果想访问非静态成员,得先创建外部类对象。
局部内部类
就是:定义在方法里面的类。
1 | class Outer { |
特点
- 只能在当前方法里使用
- 作用域只在这个方法内部
匿名内部类
没有名字的内部类,通常只用一次。
例如:
1 | interface Animal { |
这里:
1 | new Animal() { |
就是匿名内部类。
它本质上做了什么
相当于:
- 创建了一个实现
Animal接口的类 - 这个类没有名字
- 顺手直接创建了这个类的对象
四种内部类快速对比
成员内部类
定义在类里,不加 static
1 | class Outer { |
特点:依赖外部类对象
静态内部类
定义在类里,加 static
1 | class Outer { |
特点:不依赖外部类对象
局部内部类
定义在方法里
1 | void test() { |
特点:只能在方法里用
匿名内部类
没有类名,通常只用一次
1 | new Runnable() { |
特点:简洁,常用于临时实现接口或继承类
Lambda
为什么会出现 Lambda
在 Java 8 以前,很多场景都要写匿名内部类,特别啰嗦。
比如线程:
1 | Runnable r = new Runnable() { |
这段代码真正重要的逻辑其实只有一句:
1 | System.out.println("hello"); |
但为了让 Java 知道“你是在实现 Runnable 接口的 run() 方法”,你得写很多模板代码。
所以 Java 8 引入 Lambda,把它简化成:
1 | Runnable r = () -> System.out.println("hello"); |
一下子就清爽很多。
Lambda 只能用于什么地方
Lambda 只能用于函数式接口。
函数式接口就是:
有且只有一个抽象方法的接口。
例如:
1 | interface MyRunnable { |
这就是函数式接口。
可以有别的方法,但只能有 一个抽象方法。
比如:
1 | @FunctionalInterface |
这里仍然是函数式接口,因为:
run()是抽象方法default方法不是抽象方法static方法也不是抽象方法
@FunctionalInterface
1 | @FunctionalInterface |
它的作用是:
- 告诉别人:这是个函数式接口
- 编译器会帮你检查:如果你不小心写了两个抽象方法,会直接报错
Lambda 的基本语法
Lambda 的核心语法是:
1 | (参数列表) -> { 方法体 } |
比如:
1 | (a, b) -> a + b |
意思就是:
- 接收两个参数
a、b - 返回
a + b
eg:
1 | interface Calculator { |
Lambda 写法:
1 | Calculator c = (a, b) -> { |
如果方法体只有一句 return,可以继续简化成:
1 | Calculator c = (a, b) -> a + b; |
Lambda 可以访问外部变量吗
它可以访问:
- 外部类成员变量
- 静态变量
- 局部变量,但局部变量必须是
final或 effectively final(你虽然没写final,但它的值没有再改过)
方法引用
普通 Lambda:
1 | list.forEach(s -> System.out.println(s)); |
方法引用:
1 | list.forEach(System.out::println); |
不是所有 Lambda 都能改成方法引用。
只有当 Lambda 逻辑本质上是:
- 拿到参数
- 直接调用一个已有方法
- 没有额外加工
才可以改。
比如这个可以:
1 | s -> Integer.parseInt(s) |
可以写成:
1 | Integer::parseInt |
但这个一般就不能直接改:
1 | s -> s.trim().toUpperCase() |
因为里面做了两步,不是单纯调用一个方法。
语法
1 | System.out::println |
分别对应:
- 对象实例方法
- 静态方法
- 类名引用实例方法
- 构造器引用
Stream API
Java 8引入了Stream API,它提供了一种高效且易于使用的数据处理方式,特别适合集合对象的操作,如过滤、映射、排序等。
它不存数据,只负责“加工数据”
1 | List<String> list = Arrays.asList("Java", "Go", "Python"); |
这段代码的意思是:
- 把
list变成流 - 过滤长度大于 2 的字符串
- 转成大写
- 输出
特点
- 写法更简洁
- 适合链式操作
- 常和 Lambda 一起用
基本流程
1 | 数据源.stream() |
常见中间操作
filter(按条件筛选元素)map(把一个元素转成另一个元素)sorted(排序)distinct(去重)
常见终止操作
forEach(遍历输出)collect(收集结果)count(统计项目)findFirst(找第一个)
并行流
普通流:
1 | list.stream() |
并行流:
1 | list.parallelStream() |
或者:
1 | list.stream().parallel() |
普通 stream() 是单线程顺序处理。
并行 parallelStream() 是多线程处理

对CPU密集型的任务来说,并行流使用ForkJoinPool线程池,为每个CPU分配一个任务,这是非常有效率的,但是如果任务不是CPU密集的,而是I/O密集的,并且任务数相对线程数比较大,那么直接用ParallelStream并不是很好的选择。
Java基础
Java特点

平台无关性:编译成字节码后能在任何装了jvm的机器上运行
面向对象
内存管理:Java有自己的垃圾回收机制,能自动管理对象,减少内存泄漏
JVM、JDK、JRE三者关系?

值传递和引用传递
在 Java 中,参数传递只有值传递一种方式
基本类型(int、double、boolean 等) 传的是值本身的副本,方法内改动不影响外部变量。
引用类型(对象、数组) 传的是对象引用的副本(即对象的地址值拷贝),不是对象本身。
面向对象三大特性
封装:把数据和操作数据的方法绑定在一起,对外隐藏内部实现细节,只暴露必要的访问接口。
继承:子类继承父类的属性和方法,实现代码复用。
多态:同一个方法调用,在不同对象上表现出不同行为。提高扩展性。
多态实现方式
- 继承或实现接口(有继承关系)
- 方法重写(子类重写父类方法)
- 父类引用指向子类对象(向上转型)
== 与 equals 有什么区别?
基本数据类型==比较数值;
引用类型:==和equals都是比较内存地址,但是用的类(比如 String、Integer)都重写了equals,重写后比较的是对象里面的内容。
重写equals必须重写hashcode
如果两个对象equals相等,那么hashcode必须相等
如果没重写hashcode,equals判定为true,但是hashcode不一样,放到hashse或者HashMap中会被当成两个不同元素,造成去重失效。
接口和抽象类
类只能单继承抽象类,可以实现多个接口
抽象类能有构造器,普通成员方法和任意权限成员变量;
接口默认成员变量是public static final,Java8后能有默认,静态方法,Java9还加了私有方法
抽象类能不能有构造方法:能。只是不能new抽象类。抽象类构造方法能初始化内部的公共成员变量。
final修饰变量
基本类型:不能改值
引用类型:不能再让变量指向另一个对象,但对象内部的数据可能仍然可以修改
error和异常的区别
**error:**无法处理的严重问题,jvm不可恢复(堆,栈溢出)
Exception: 表示程序本身可以处理的异常情况
受检异常,Checked Exception,编译时就必须被捕获或者声明抛出
非受检异常,Unchecked Exception 或 RuntimeException,运行时异常是不需要在编译时强制捕获或声明的
string为什么不可变
string类被final修饰,不能被继承重写
底层数组是private final 修饰的数组引用
创建string对象的两种方式:
new String()
字面量赋值
说一下 integer的缓存

Integer 用自动赋值时,数值在 - 128 到 127 之间,多个变量会共用同一个对象,用 == 比地址会相等;一旦数值超过 127,就会直接 new 新对象,哪怕数值一样,== 也会不等,但用 equals 比值就没问题。
方法重载与重写
同一个类,方法名相同,参数列表不同,和返回值、访问修饰符无关
父子类,方法名、参数列表、返回值必须一致
throw和throws
throws(方法声明上)
- 位置:方法签名后
- 作用:声明该方法可能抛出异常,交给调用者处理
- 可以多个异常,逗号隔开
1 | void test() throws IOException, RuntimeException {} |
throw(方法体内)
- 位置:方法内部
- 作用:手动抛出一个具体异常对象
1 | throw new NullPointerException(); |
final / finally / finalize
final
修饰符
- 类:不能被继承
- 方法:不能重写
- 变量:常量,只能赋值一次
finally
异常块 try-catch-finally
- 写在catch后面
- 无论是否异常,都会执行
- 常用于释放资源
finalize
Object里的方法
- JVM垃圾回收前尝试调用,用来清理资源
- 不保证一定执行,时机不确定,已废弃,不要用