Java学习笔记02
Java学习笔记02
Lynx Frost面向对象基础
对象组成:属性和方法
属性
对象名.属性名
行为
对象名.方法名
对象与类的关系
- 类是抽象的,概念的,代表一类事物
- 对象是具体的,实际的,代表一个具体事务
- 类是对象的模板,对象是类的一个个体
类和对象的内存分配机制
Java 内存的结构分析
- 栈:一般存放基本数据类型(局部变量)
- 堆:存放对象(Cat cat , 数组等)
- 方法区:常量池(常量,比如字符串),类加载信息
Person p = new Person(); |
- 先加载Person类信息(属性和方法信息,只会加载一次)
- 再堆中分配空间,进行默认初始化
- 把地址赋给p ,p就指向对象
- 进行指定初始化,比如 p.name=”jack” p.age = 10
包
作用
- 区分相同名字的类
- 方便管理类
- 控制访问范围
基本语法
package cn.itrf |
package 关键词,表示打包
cn.itrf 表示包名
注意事项
- package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package
- import指令 位置放在package的下面,在类定义前面,可以有多句且没有顺序要求
方法
方法的调用机制原理
- 当程序执行到方法时,就会开辟一个独立的空间(栈空间)
- 当方法执行完毕,或者执行到return语句时,就会返回
- 返回到调用方法的地方
- 返回后,继续执行方法后面的代码
- 当main方法(栈)执行完毕,这个程序退出
方法的访问修饰符
public:公开的。无论是哪个类、哪个包、还是外部项目,都可以直接访问。protected:受保护的。同一个包内的类可以访问;不同包中的子类也可以访问。- 默认(无修饰符):包级私有(package-private)。只有同一个包内的类可以访问,跨包了就不能访问。
private:私有的。只有在定义它的类内部才能访问,体现了面向对象的封装性。通常属性会设为private,然后通过get/set方法来间接访问。
方法的返回数据类型
1) 一个方法最多有一个返回值 [多个结果 返回数组 ]
2) 返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
3) 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 值; 而且要求返回值类型必须和 return 的值类型一致或兼容
4) 如果方法是 void,则方法体中可以没有 return 语句,或者 只写 return ;
注意事项
- 方法不能嵌套定义,方法体中不能再定义方法
- 基本数据类型,传递的是值(值拷贝),形参的任何变化不影响实参
- 引用数据类型,传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参
递归重要规则
- 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
- 方法的局部变量时独立的,不会相互影响
- 如果方法中使用的是引用类型变量(比如数组,对象),就会共享引用类型的数据
- 递归必须向退出递归条件逼近,否则就是无限递归
- 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕
方法重载
- 方法名:必须相同
- 参数列表:必须不同
- 返回类型:无要求
可变参数
public int sum(int... nums) { |
int… 表示接受的是可变参数,类型是 int ,即可以接收多个 int(0-多)
使用可变参数时,可以当做数组来使用 即 nums 可以当做数组
遍历 nums 求和即可
注意事项
- 可变参数的实参可以为0个或者任意多个
- 可变参数的实参可以为数组
- 可变参数的本质就是数组
- 可变参数可以和普通类型的参数一起放在形参列表,但是必须保证可变参数在最后
- 一个形参列表中只能出现一个可变参数
作用域
- Java编程中,主要的变量就是属性(成员变量)和局部变量
- 局部变量一般是指在成员方法中定义的变量
- Java中作用域的分类
- 全局变量:也就是属性,作用域为整个类
- 局部变量:除了属性之外的其他变量,作用域是定义他的代码块
- 全局变量可以不赋值,直接使用,因为有默认值,局部变量必须赋值,才能使用,因为没有默认值
注意事项
- 属性和局部变量可以重名,访问时遵循就近原则
- 在同一个作用域中,两个变量不能重名
- 属性生命周期较长,便随着对象的创建而创建,伴随着对象的销毁而销毁。局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁
- 作用域范围不同
- 全局变量:可以被本类使用,或其他类使用
- 局部变量:只能在本类中对应的方法中使用
- 修饰符不同
- 全局变量可以加修饰符
- 局部变量不可用加修饰符
构造方法
[修饰符] 方法名(形参列表){ |
构造方法又叫构造器(constructor),是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。它有几个特点:
1) 方法名和类名相同
2) 没有返回值
3) 在创建对象时,系统会自动的调用该类的构造器完成对象的初始化。
4) 构造器没有返回值, 也不能写 void
注意事项
- 一个类可以定义多个不同的构造器,即构造器重载
- 构造器名要和类名相同
- 构造器没有返回值
- 构造器是完成对象的初始化,并不是创建对象
- 在创建对象时,系统自动的调用该类的构造方法
- 如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器(默认构造器)
- 一旦定义了自己的构造器,默认的构造器就被覆盖了,就不能再使用默认无参构成器了,除非显式的定义一下
this关键字
Java虚拟机会给每个对象分配this代表当前对象
注意事项
- this 关键字可以用来访问本类的属性、方法、构造器
2) this 用于区分当前类的属性和局部变量
3) 访问成员方法的语法:this.方法名(参数列表);
4) 访问构造器语法:this(参数列表); 注意只能在构造器中使用(即只能在构造器中访问另外一个构造器, 必须放在第一条语句)
5) this 不能在类定义的外部使用,只能在类定义的方法中使用
==和equals()的区别
==(双等号)- 基本数据类型(如
int,double,boolean):比较的是它们的数值是否相等。 - 引用数据类型(如对象、数组、字符串):比较的是它们的内存地址是否相同(即是否指向堆内存中的同一个对象)。
- 基本数据类型(如
equals()方法- 对象专属:它是
Object类的一个方法,所有类都继承了它。 - 默认行为:如果没有重写(Override),它的底层逻辑和
==一样,比较的还是内存地址。 - 常用重写:像
String、Integer等类都重写了equals()方法,用来比较它们的内容(值)是否相同。
- 对象专属:它是
面向对象的三大特征
封装、继承、多态
封装
封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其他部分只能通过授权的操作,才能对数据进行操作
将属性设置为private 创建setXXX和getXXX的方法 public
继承
class 子类 extends 父类{ |
子类就是会自动拥有父类定义的属性和方法
父类又叫 超类,基类
子类又叫派生类
注意事项
1) 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
2) 子类必须调用父类的构造器, 完成父类的初始化
3) 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。
4) 如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)
5) super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)
6) super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
7) Java 所有类都是 Object 类的子类, Object 是所有类的基类.
8) 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)
9) 子类最多只能继承一个父类(指直接继承),即 Java 中是单继承机制。思考:如何让 A 类继承 B 类和 C 类? 【A 继承 B, B 继承 C】
10) 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
super关键字
super代表父类的引用,用于访问父类的属性、方法、构造器
不能访问父类的private属性和方法
当子类中有何父类中的成员(属性和方法)重名时,为了访问父类成员,必须通过super。如果没有重名,使用super、this、直接访问的结果一样
super.成员 的查找起点是直接父类,若直接父类没有对应成员,会继续沿着继承链向上查找(可找到爷爷类等上层父类);查找遵循继承链就近原则,同时受访问权限约束遇到private就断了报错;无法主动跳过直接父类,强制直接访问爷爷类中被父类重写 / 覆盖的同名成员。
this和super的区别
| 对比项 | this | super |
|---|---|---|
| 含义 | 当前类对象 | 父类存储空间入口 |
| 成员查找起点 | 当前类 | 直接父类(跳过当前类) |
| 调用构造 | this () 调用本类构造 | super () 调用父类构造 |
| 使用位置 | 实例方法、构造器 | 实例方法、构造器 |
| static 中 | 不允许 | 不允许 |
| 单独输出 | this 可行 | super 不能直接打印 |
方法的重写/覆盖
方法的覆盖就是子类的一个方法和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的方法
子类重写方法的返回值类型和父类返回值类型一致,或者是其子类
子类的方法不能缩小父类方法的访问范围
多态
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
重点
- 一个对象的编译类型和运行类型可以不一致
- 编译类型在定义对象时,就确定了,不能改变
- 运行类型是可以变化的
- 编译类型看定义时 = 号 的左边,运行类型看 = 号的 右边
多态前提(四个条件缺一不可)
- 继承 / 实现:子类继承父类(类继承)或者类实现接口
- 方法重写:子类重写父类方法
- 父类引用指向子类对象(向上转型)
- 调用重写方法
向上转型(自动转型,隐式转型)
格式:父类类型 引用 = new 子类();
Animal animal = new Dog(); // 向上转型 |
特点:
- 自动转换,不需要强制
- 可以调用:父类所有方法 + 子类重写的方法,只有方法哦!!!
- 不能调用子类独有的方法
向下转型(强制转型,显式转型)
作用:向上转型后,想要调用子类独有方法,必须向下转回子类类型
格式:子类类型 引用 = (子类)父类引用;
Animal a = new Dog(); //向上转型 |
巨大坑:类型转换异常 ClassCastException
只有原本就是该子类对象,才能强转;
Animal a = new Cat(); |
解决方案:instanceof 判断
判断引用变量指向的实际对象是不是【某个类 / 它的子类 / 实现类】的实例
转型前先判断引用实际对象类型
if(a instanceof Dog){ |
| 类型 | 写法 | 是否强制 | 风险 | 能力 |
|---|---|---|---|---|
| 向上转型 | 父类 引用 = new 子类 () | 自动 | 无风险 | 只能访问父类方法,执行子类重写方法;不能访问子类独有方法 |
| 向下转型 | 子类 引用 = (子类) 父类引用 | 需要强制 | 可能抛 ClassCastException,配合 instanceof | 可以调用子类独有方法 |
易错总结
方法有多态,成员变量没有多态!
class A{ int num = 10; } |
- 静态方法重写不叫重写,叫隐藏,不存在多态;调用看左边
- 向上转型本质:多态的基础写法
- 向下转型不是多态,只是为了访问子类特有功能
Java的动态类型绑定
调用对象【方法】:存在动态绑定
调用方法时,看对象真实运行类型(堆里 new 出来的类型),执行子类重写后的方法。
通俗:方法 → 运行看右边
- 调用对象【成员属性 (变量)】:没有动态绑定
访问变量时,看引用声明类型(等号左边是什么类型),哪里声明,就取哪里的属性。
通俗:属性 → 编译 / 运行都看左边
类变量
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。
class Child { //类 |
访问类变量
类名.类变量名 对象名.类变量名 推荐用第一种
生命周期
- 类变量是在类加载时就完成初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量
- 类变量的生命周期是随类的加载开始,随着类消亡而销毁
类方法
类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:
类方法中无 this 的参数
普通方法中隐含着 this 的参数
类方法可以通过类名调用,也可以通过对象名调用。
普通方法和对象有关,需要通过对象名调用,比如对象名.方法名 (参数),不能通过类名调用。
类方法中不允许使用和对象有关的关键字,比如 this 和 super。普通方法 (成员方法) 可以。
类方法 (静态方法) 中 只能访问 静态变量 或静态方法 。
普通成员方法,既可以访问 非静态成员,也可以访问静态成员。
小结: 静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员 (必须遵守访问权限)
mian方法
public static void main(String[] args) |
public:公共访问权限。JVM 需要跨类调用 main 方法,必须对外公开可访问。
static:静态方法。JVM 启动时还没有创建类的实例对象,直接通过类名调用方法,不需要 new 对象。
void:无返回值。JVM 不接收 main 函数的返回结果。
main:固定方法名,JVM 识别的程序入口名称,不能修改。
String[] args:命令行参数数组
在控制台执行命令示例:
java TestDemo 张三 20 男
args[0]="张三"args[1]="20"args[2]="男"args 名称可以修改(例如
String[] params),但数组类型不能变。
三种代码块
态代码块
static { |
位置:类内部,方法外面,加
static触发时机:类加载的时候执行,只执行 1 次
作用:初始化静态变量
限制:
只能访问静态成员
不能使用 this、super
不可以写 return
构造代码块(实例代码块 / 普通代码块【类内】)
{ |
- 位置:类内部、方法外面,没有 static
- 触发时机:每次创建对象 new 的时候执行
- 执行时机:在构造方法之前执行
- 作用:提取多个构造方法的公共重复代码
- 可以访问静态、非静态成员,可以使用 this
局部代码块(方法内普通代码块)
public void test(){ |
- 位置:方法内部
- 作用:限定局部变量生命周期,缩小作用域
区分「构造代码块」和「局部代码块」
执行顺序
情况 1:只创建单个对象
1. 静态代码块(类加载,最先执行,只运行一次) |
示例演示:
public class Test { |
静态代码块 |
情况 2:多次 new 对象
new Test(); |
静态代码块 //类加载,仅第1次执行 |
关键结论:静态代码块随类加载,全局只执行一次;构造代码块每次 new 对象都会执行。
情况 3:存在父子继承关系
继承场景完整顺序口诀:
父静态 → 子静态 → 父构造代码块 → 父构造方法 → 子构造代码块 → 子构造方法
演示代码:
class Father{ |
运行结果:
父静态 |
易错提醒:
静态代码块只看类加载顺序,优先父类静态;静态代码块互不依赖实例,优先全部执行完所有静态,再执行实例相关代码块、构造器。
| 类型 | 格式 | 位置 | 执行时机 | 执行次数 |
|---|---|---|---|---|
| 静态代码块 | static {} |
类内,方法外 | 类加载阶段 | 整个程序只 1 次 |
| 构造代码块 | {} |
类内,方法外 | 每次 new 创建实例 | 每 new 一次执行一次 |
| 局部代码块 | {} |
方法内部 | 方法运行到该位置 | 运行到就执行 |
设计模式
- 静态方法和属性的经典使用
- 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。
单例模式
单例模式:保证一个类在整个程序中,只有唯一的一个实例对象,对外提供统一获取该对象的入口。
使用场景:工具类、线程池、数据库连接池、配置文件对象等,不需要多个实例,节约内存。
核心三大要点:
- 私有构造方法:
private 类名(){},禁止外部new创建对象 - 私有静态成员变量:保存唯一实例
- 公共静态方法:对外获取这个唯一实例
方式 1:饿汉式(静态常量写法,【线程安全】)
public class SingletonHungry { |
执行时机
类加载的时候直接创建对象。
优点:
- 写法简单,天然线程安全(类加载只执行一次,不存在多线程冲突)
缺点:
- 类一旦加载,立刻创建对象。即使程序全程没有用到这个实例,对象依然创建,造成内存浪费。
饿汉:肚子一饿立刻吃饭 → 类加载直接实例化。
方式 2:懒汉式(基础版,多线程不安全!)
public class SingletonLazy { |
执行时机
调用getInstance()方法的时候,才创建实例(延迟加载)
优点:懒加载,不用就不创建,节省内存
致命缺陷:多线程环境下会创建多个对象,线程不安全
线程安全优化:双重检查锁 DCL(面试标准懒汉写法)
public class SingletonLazyDCL { |
懒汉:不到饭点不吃饭 → 需要用到的时候才创建对象
| 对比 | 饿汉式 | 懒汉式 (DCL 双重检查锁) |
|---|---|---|
| 创建时机 | 类加载阶段 | 第一次调用 getInstance () |
| 懒加载 | ❌不支持 | ✅支持,节省内存 |
| 线程安全 | ✅天然安全 | 需要加锁 + volatile 保证安全 |
| 使用场景 | 确定一定会使用这个对象 | 不确定是否会使用,希望延迟创建 |
拓展知识点
- 私有构造方法依然可以被反射暴力破解,两种基础实现都不能防止反射创建多个实例;
- 还有第三种写法:静态内部类单例(推荐,兼顾懒加载与线程安全);
public class Singleton { |
final关键字
final 表示「最终的、不可修改」,可以修饰:变量、方法、类。
一、final 修饰【变量】
变量一旦赋值成功,不能重新赋值(常量)
- 局部变量
public void test(){ |
final 局部变量可以先声明、后面一次赋值,但是只能赋值一次。
- 成员变量(实例变量)
两种赋值时机,二选一:
- 定义时直接初始化
- 在构造方法中赋值
不能不赋值!final 成员变量必须保证对象创建完成时有值。
class A{ |
- 静态成员变量(static final)
俗称常量,命名规范:全部大写,下划线分隔 MAX_VALUE
赋值时机:
- 声明处初始化
- 静态代码块初始化
public static final double PI = 3.1415926; |
极易混淆重点:final 修饰引用类型变量
final Person p = new Person(); |
final限制的是引用地址不能变对象内部的属性依然可以修改!
形象理解:遥控器不能换电视,但是可以换频道。
二、final 修饰【方法】
public final void show(){} |
规则:
该方法不能被子类重写(override)
仍然可以被继承,子类可以调用,只是不能改写方法实现。
补充:
private 方法隐式带有 final 特性,子类无法重写私有方法。
三、final 修饰【类】
final class Animal{} |
规则:
这个类不能被继承,没有子类
例子:String、Integer 都是 final 类,你无法写 class MyStr extends String
四、易错知识点汇总
- final 和 static 区别?
- static:属于类,所有对象共享一份
- final:值不可修改;两者经常组合
static final定义常量
- 基本类型 final vs 引用类型 final
- 基本类型:数值不能变
- 引用类型:对象地址不能变,对象内部内容可变
- final 方法可以重载吗?
可以重载!只是禁止重写,重载不受影响。
class A{ |
- final 可以修饰构造方法吗?
不允许!构造方法本身不能被继承重写,语法不支持。
五、速记总结
- final 变量:赋值一次,终身不改(引用变量地址不变,对象内容可变)
- final 方法:禁止子类重写
- final 类:断绝继承,不能有子类
| 修饰目标 | 作用 |
|---|---|
| final 变量 | 只能赋值一次,不可重新赋值 |
| final 方法 | 不能被子类重写 |
| final 类 | 不能被继承 |
Java 抽象类(abstract class)
一、基础概念
用 abstract 关键字修饰的类,叫做抽象类。
抽象类用来提取多个子类的公共模板,允许定义没有方法体的抽象方法,强制子类去实现。
语法格式:
abstract class 类名 { |
二、抽象方法
语法:abstract 返回值 方法名();
- 只有方法声明,没有方法体(没有 {})
- 必须写在抽象类 / 接口当中
- 作用:规范子类必须实现这个方法
示例:
abstract class Animal { |
三、核心硬性规则
抽象类不能 new 创建对象
new Animal(); // 编译报错
原因:类中有未实现的抽象方法,对象不完整。
如果一个类继承抽象类:
- 方案①:普通子类 → 必须重写父类所有抽象方法
- 方案②:子类也定义为
abstract抽象类 → 可以不用实现抽象方法
// 普通子类,必须实现全部抽象方法 |
- 抽象类里可以包含什么?
普通成员变量、静态变量
普通实例方法、static 静态方法
构造方法(供子类创建对象时调用)
抽象方法
重点:抽象类有构造方法!只是不能自己实例化,子类
new的时候通过super()调用。
几个语法禁忌
abstract和final不能一起修饰类
final 类不能被继承,abstract 类要求必须被继承实现,互相矛盾。
abstract 和 private 不能一起修饰方法
private 方法子类看不见,无法重写实现,冲突。
abstract 和 static 不能一起修饰方法
静态方法可以直接类名调用,不需要子类实现,冲突。
| 类型 | 能否实例化 | 能否拥有抽象方法 |
|---|---|---|
| 普通类 | 可以 new | 不能有抽象方法 |
| 抽象类 | 不能直接 new | 可以定义抽象方法 |
四、易错知识点
抽象类一定包含抽象方法?
错误!抽象类可以没有任何抽象方法,只是很少这么写。
abstract class Test{ |
- 有抽象方法的类,一定是抽象类?
正确!普通类存在抽象方法直接编译报错。
Java 接口(interface)
接口是行为规范、标准契约,定义一组功能(方法声明),本身不关心怎么实现。
语法:
interface 接口名 { |
类实现接口,关键字:implements
class 类名 implements 接口1,接口2 { |
不同 JDK 版本接口内允许的成员
1)JDK7 及以前(经典老式接口)
接口里面只能有:全局常量、抽象方法
- 常量:
public static final修饰,默认隐藏
interface Usb{ |
必须初始化,不能修改。
- 抽象方法:
public abstract修饰,默认隐藏
interface Usb{ |
2)JDK 8 新增
默认方法(default):带方法体,实现类可以选择重写,不重写就用默认实现
default void show(){ |
静态方法(static):接口。静态方法 () 调用,实现类不能继承
3)JDK 9 新增
私有方法 private:供接口内部默认方法复用
面试常记:日常开发核心掌握【常量、抽象方法、default 默认方法、static 静态方法】
核心语法规则
- 接口不能 new 实例化,没有构造方法!
new Usb(); // 报错 |
- 类和接口:
implements多实现(一个类可以同时实现多个接口)
class Phone implements Usb, Bluetooth{} |
- 接口与接口:
extends多继承
interface A{} |
- 实现类要求:
- 普通类实现接口:必须重写接口中所有抽象方法
- 抽象类实现接口:可以不实现抽象方法,交给它的子类完成
- 方法访问权限:
接口抽象方法强制 public,重写的时候不能缩小权限(不能写 protected/private)
| 对比项 | 抽象类 abstract class | 接口 interface |
|---|---|---|
| 关键字 | class | interface |
| 构造方法 | 有构造方法(供子类 super 调用) | 无构造方法,不能实例化 |
| 成员变量 | 可以普通变量、静态变量 | 只能public static final常量 |
| 方法 (JDK7) | 普通方法 + 抽象方法 | 只能抽象方法、常量 |
| 继承关系 | 单继承 extends |
多实现implements;接口之间多继承extends |
| 设计角度 | 模板,描述事物是什么 | 行为标准,描述事物能做什么 |
设计思想区分:
Animal(抽象类):动物是什么
Run(接口):动物能跑
Dog extends Animal implements Run
易错知识点
default 默认方法冲突问题
一个类实现多个接口,如果多个接口存在同名同参数 default 方法,实现类必须手动重写该方法,否则编译报错。
接口静态方法不会被继承,普通类的可以被继承但不能重写
interface A{ |
- 接口常量默认
public static final,写不写都一样,不能修改。
Java 内部类
一、什么是内部类
把一个类定义在另一个类的内部,这个类就叫内部类;外面的类称为外部类(外围类)。
//外部类 |
分类(四大类)
- 成员内部类(实例内部类)
- 静态内部类(静态嵌套类)
- 局部内部类(方法内定义)
- 匿名内部类(重点,经常和接口 / 抽象类搭配)
成员内部类(没有 static,写在外部类成员位置)
class Outer{ |
核心规则
- 成员内部类依赖外部类对象,不能直接 new
Outer outer = new Outer(); |
简写:Outer.Inner inner = new Outer().new Inner();
- 内外类出现同名变量怎么区分?
class Outer{ |
限制
内部类里面不能定义静态成员(static 变量、静态方法)
因为成员内部类必须依托外部实例,静态属于类,冲突
访问权限
成员内部类可以使用
private / protected / public修饰;如果内部类被 private 修饰,外部其他类无法访问这个内部类。
静态内部类(static 修饰,也叫嵌套类)
class Outer{ |
核心规则
- 不依赖外部类对象,直接通过外部类类名创建
Outer.StaticInner inner = new Outer.StaticInner(); |
不能写 outer.new StaticInner ()
访问范围
只能直接访问外部类 static 静态属性、静态方法;
想要访问外部实例成员,必须传入外部类对象。
可以拥有自己的静态成员。
成员内部类 vs 静态内部类 关键区别
| 类型 | 创建方式 | 依赖外部对象 | 可访问外部成员 |
|---|---|---|---|
| 成员内部类 | 外部对象.new 内部类() |
必须有外部实例 | 所有成员(静态 + 实例 + 私有) |
| 静态内部类 | new 外部类.内部类() |
不需要外部实例 | 仅外部静态成员 |
局部内部类(定义在【方法里面】)
class Outer{ |
规则
- 作用域仅限当前方法,方法外面无法使用;
- 不能使用访问修饰符
public private protected; - JDK8 开始,如果访问方法里的局部变量,变量默认隐式
final(不能修改);
public void test(){ |
匿名内部类(重中之重)
没有类名的内部类,一般用来一次性实现接口 / 抽象类,简化代码。
语法格式:
new 接口/抽象类/父类(){ |
示例:基于接口
interface Usb{ |
核心特点
- 匿名内部类只能使用一次,只能创建一个对象;下次创建对象的时候必须重新写一边,没有名字不能重复使用;
- 没有构造方法;
- 本质:隐式继承父类 / 实现接口;
- 属于局部内部类的一种;
常用场景:方法传参、线程 new Thread(new Runnable(){})
拓展:JDK8 Lambda 表达式可以简化只有一个抽象方法的接口,替代匿名内部类。
通用核心考点:内部类访问外部类
- 内部类天生持有外部类的隐式引用:
外部类名.this - 外部类想要访问内部类成员:必须先创建内部类对象,不能直接访问
class Outer{ |
| 类别 | 定义位置 | 是否 static | 创建对象方式 | 核心特点 |
|---|---|---|---|---|
| 成员内部类 | 外部类中,方法外 | 无 | new Outer().new Inner() |
依赖外部对象,可访问所有外部成员,不能定义 static |
| 静态内部类 | 外部类中,方法外 | static | new Outer.StaticInner() |
无需外部对象,仅访问外部静态成员 |
| 局部内部类 | 方法体内 | 无 | 方法内 new | 仅限方法内使用,不能加权限修饰符 |
| 匿名内部类 | 方法 / 参数中 | 无 | new 父类/接口(){} |
无类名,一次性使用,实现抽象方法 |


