JAVA面向对象OOP与异常
对象 类 抽象类 封装 继承 多态 接口 异常 错误
java面向对象 OOP与异常
什么是面向对象
面向过程思想
- 步骤清晰简单,第一步做什么,第二步做什么…..
- 面向过程适合处理一些较为简单的问题
面向对象思想
- 物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索
- 面向对象适合处理复杂的问题,适合处理需要多人协助的问题!
对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是,具体到微观操作,仍然需要面向过程的思路去处理
面向对象编程 Object-Oriented Programming OOP
本质:以类的方式组织代码,以对象的组织(封装)数据
抽象
三大特性
- 封装
- 继承
- 多态
从认识论角度考虑是先有对象后有类。对象,是具体的事物。类,是抽象的,是对对象的抽象
从代码运行角度考虑是先有类后有对象。类是对象的模板
回顾方法的定义
方法的定义
1 | |
方法的调用
1 | |
类与对象的创建
类与对象的关系
类是一种抽象的数据类型,它是对某一类事物整体描述/定义,但是并不能代表某一具体的事物
- 动物、植物、手机、电脑…
- Person类、Pet类、Car类等,这些类都是用来描述/定义某一类具体的事物应该具备的特点和行为
对象是抽象概念的具体实例
- 张三就是人的一个具体实列,张三家里的旺财就是狗的一个具体实例
- 能够体现出特点,展现出功能的是具体的实例,而不是一个抽象的概念
创建与初始化对象
- 使用new关键字创建对象
- 使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用
- 类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下俩个特点:
- 必须和类的名字相同
- 必须没有返回类型,也不能写void
构造器详解 必须掌握
一个类即使什么都没有,也存在构造器
new对象的时候,自动调用的特殊方法,作用就是【对象创建出来的时候,给对象做初始化】
构造器不是用来造对象的!new才是在堆开辟内存、创建对象本体;构造器是对象内存分配好之后,马上执行,给对象的成员变量初始化赋值
构造器长啥样
1 | |
1 | |
两种构造器
1 | |
构造器重载+this() 在构造器之间的调用
1 | |
解释说明:
语法上,构造器长得像方法,但它不是普通的方法,它的使命是对象诞生瞬间的初始化,这件事情普通方法做不到,所以单独搞出构造器
如果程序员忘记调用init() ,对象就带着默认脏数据直接使用,产生bug
new和构造器绑定在一起,new的同时必然执行初始化,无法跳过,这就是设计它最核心的理由第二个用处:有参构造,new的时候直接给属性赋值,一行代码,创建对象+给属性赋值一步到位
第三个用处:构造器支持重载,this()构造器互调
可以规定多种对象创建方式,无参、单参、多参,控制对象创建的规则
构造器只是语法上长得像方法,但是它不属于对象的行为(比如 miao () 叫、run () 跑,是对象能干的动作)。
构造器不是对象的 “行为”,它是对象创建的初始化流程。
普通方法是对象已经造好之后,用来做事;
构造器是对象诞生的那一刻,强制完成初始化,防止漏初始化。 重点是强制
注意:
- 使用new关键字,本质是在调用构造器
- 用来初始化值
- 有参构造:一旦定义了有参构造,如果想用无参构造,就必须显示定义无参
关于this
- this.属性 / this.方法():this = 当前调用方法的对象(实例)
- this(参数):特殊语法,调用本类里面其他构造器(只能写在构造器首行)
创建对象内存分析

简单小结类与对象
类与对象 类是抽象,对象是具体
方法 定义 调用
对象的引用
引用类型: 对象通过引用来操作 栈—>堆
基本类型8大
属性:字段Field 成员变量 默认初始化
修饰符 属性类型 属性名=属性值
对象的创建和使用
- 必须使用new关键字创建对象 构造器
- 对象的属性 点操作符
类:
静态的属性
动态的行为
封装详解
该漏的漏,该藏的藏
程序设计追求“高内聚,低耦合”
高内聚就是类的内部数据操作细节自己完成,不允许外部干涉
低耦合就是仅暴露少量的方法给外部使用
封装(数据的隐藏)
通常,应禁止直接访问一个对象中的数据的实际表示,而应通过操作接口来访问。这称为信息隐藏
属性私有,private,提供一些可以操作这个属性的方法———get/set(提供一些public的get set方法)
get获得这个数据 set给这个数据设置值
1
2
3
4
5
6public String getName(){
return this.name;
}
public void setName(String name){
this.name=name;
}封装带来的好处
- 提高程序的安全性,保护数据
- 隐藏代码的实现细节
- 统一接口
- 系统的可维护增加了
什么是继承
- 继承的本质:对某一批类的抽象,从而实现对现实世界更好的建模
- extands的意思是“扩展”,子类是父类的扩展
- JAVA中类只有单继承,没有多继承
- 继承是类和类之间的一种关系。除此之外,类和类的关系还有依赖、组合、聚合等
- 继承关系的俩个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示
- 子类和父类之间,从意义上讲应该具有“is a”的关系
- object类 java中所有的类都默认直接或者间接地继承object
super详解
this:当前实例对象;super:访问父类成员的语法标记,二者指向同一个对象,不存在单独父类对象。
都只能在实例方法 / 构造器使用,静态方法不能用;this()和super()都必须在构造器首行,不能一起写。
super注意点
super调用父类的构造方法,必须在构造方法的第一个
super必须只能出现在子类的方法或者构造方法中
super和this不能同时构造方法
vs this
代表的对象不同:
this:本身调用者这个对象
super:代表父类对象的引用
前提:
this:没有继承也可以使用
super:只能在继承条件下才可以使用
构造方法;
this():本类的构造
super():父类的构造
方法重写
与方法重载不一样
重写都是方法的重写,和属性无关
就是子类写了一个和父类一模一样的方法
- 方法名相同
- 参数列表相同
- 返回值类型兼容,协变返回
- 权限不能比父类更严格
当子类对象调用这个方法时,优先执行子类里的版本,而不是父类的
产生目的:
父类定义一套通用行为,但每个子类的具体实现不一样,如果没有重写机制,只能在子类里写全新名字的方法,代码没法统一调用
设计目标:
同一个方法名,不同对象有不同的实现
统一接口,各自实现
父类规定“能干这件事”,子类自定义“这件事怎么做”
方法重写的硬性规则
必须发生在继承关系(父类和子类)
方法名、参数列表必须完全一致
子类访问权限不能低于父类
父类public,子类不能写private
为什么:
父类对外承诺:我有这个方法,可以被外部调用
子类是父类的一种,可以当成父类来使用(多态)。
如果子类把方法改成 private,那用父类引用指向子类对象时,调用这个方法,就会出现:父类说能调用,子类却不让调用,逻辑矛盾。
1
2
3
4
5
6
7
8//错误代码示例
class Father{
public void run(){}
}
class Son extends Father{
// 错误!权限变严格 public → private
private void run(){}
}返回值:可以是父类返回值的子类类型(协变返回)
1
2
3
4
5
6
7
8
9
10
11class Animal{}
class Dog extends Animal{}
class Father{
public Animal getAnimal(){return null;}
}
class Son extends Father{
// 合法,协变返回,Dog是Animal子类 子类返回更具体的类型,调用的时候不用强制类型转换,写代码更方便。
@Override
public Dog getAnimal(){return null;}
}父类如果抛出异常,子类不能抛出更大的异常
为什么:
外部代码在调用父类方法时,只会捕获父声明的异常。
如果子类抛出更大的异常,外部代码没有处理,程序直接崩,破坏代码安全。
运行时异常(RuntimeException)不受这个限制
用注解@Override,用来校验是不是真的重写(推荐必写)
没有 @Override 的时候,很容易手滑写错方法名,编译器不会报错,它会当成子类新增了一个独立方法,你以为是重写,实际根本没生效,bug 很难排查。
哪些不是重写
静态方法static:子类写同名静态方法,叫隐藏 hide,不是重写
静态属于类,不属于对象,不遵循多态。引用是谁,就调用谁的静态方法。
方法参数不一样->方法重载,不是重写
父类private:子类写同名方法,不算重写。父类私有对子类不可见
构造器不能重写
对比 重写vs重载
- 重写override:父子类之间。方法名、参数相同。运行期决定调用哪个方法(多态)
- 重载 overloading :同一个类里面,方法名相同,参数不同。编译器就确定调用哪个
final常量 不能重写
总结
重写就是:子类对父类已经定义好的行为,重新实现一套逻辑。目的是实现多态,统一调用方式,不同对象有不同行为。
四个访问修饰符
一句话:控制【谁能访问这个类/方法/变量】
范围从大到小:public >protected>default>private 公保默私 访问范围越来越小
Java的
package包,本质就是用来分类管理.java文件的文件夹包名用
.分割:com.animal、com.animal.dog目录结构:
1
2
3
4
5
6
7src
└── com
└── animal 包:com.animal
├─ Animal.java
|--Pig.java
└─ cat
└─ Cat.java 包:com.animal.cat
同类:
同一个
.java里面,同一个类里面的代码比如:Animal类里面,写在Animal大括号
{}里面的变量/方法同包
文件夹完全一样,包名一模一样
com.animal.Animal和com.animal.Pig两个文件在同一个文件夹
animal,包名都是com.animal就是同包不管有咩有继承关系,只要包相同,就是同包
子包
com.animal.cat是com.animal的子包(子文件夹)注意:
子包!=同包
虽然文件夹是嵌套关系,但是 Java 眼里:
com.animal和com.animal.cat是两个完全独立不同的包跨包
只要包名不一样,全部叫跨包。
两种情况都属于跨包:
com.animal和com.people(完全无关文件夹)com.animal和com.animal.cat(父子文件夹,子包,也是跨包)子类
继承关系:
class Dog extends AnimalDog 就是 Animal 的子类。
子类只看有没有
extends,和包没关系!子类可以:
- 和父类同包
- 也可以在别的包(跨包子类)
public
范围:全项目任何地方都能访问
对外暴露的API,所有人都能用
例子. main方法、我们写的对外提供的方法,一般public
protected 受保护
范围:同包随便用;跨包只有子类能用
父类有些东西,希望留给子类继承、重写,但是不想开放给项目里其他无关的类。
坑:跨包的普通类,就算导入了父类,也访问不到 protected 成员,只有子类才行。
default 不写修饰符
范围:仅限同一个包下的所有类,跨包直接不行,哪怕是子类
包内的工具类,包内可以互相使用,包外面隔离。
区分 protected 和 default :
protected:跨包子类还能用
default:跨包,哪怕是子类也不能访问!
private 私有
范围:只能当前这个类里面访问,出了这个类就完全看不见
封装思想 把类内部的细节隐藏起来,外部不能直接修改。外部只能通过 public 方法来间接操作。
实体类的成员变量一般都写 private,外部不能直接
obj.age,要用 get/set。防止外部随意乱改数据,做校验。
补充:
成员变量、成员方法 4 个修饰符都能用;
外部类(写在.java 文件里的类)只能用 public /default
什么是多态
同一种行为,多种形态
Java多态核心一句话:父类引用,指向子类对象。编译看左边,运行看右边。
1 | |
- 编译阶段:编译器只看左边
Animal,检查 Animal 有没有 shout 方法。左边没有,直接编译报错。 - 运行阶段:JVM 看
new出来真实对象(Dog),执行子类重写后的方法。
解决痛点
假设我们有:狗、猫、鸟,都继承动物,都有 shout ()。
没有多态的写法:
写一个方法接收狗,一个方法接收猫,一个接收鸟。新增动物,就要新增方法,代码无限膨胀
1 | |
有了多态之后
只需要一个方法,接收父类 Animal,所有 Animal 子类都能传进去。新增动物类,这个方法完全不用改。
1 | |
多态生效的前提
- 要有继承(子类 extends 父类)
- 要有重写(子类重写父类方法)
- 父类引用指向子类对象
Animal a = new Dog()
多态的坑
成员变量不参与多态
1
Animal a = new Dog();访问
a.name,永远看左边父类的变量。变量不存在重写。static静态方法不参与多态
静态方法属于类,不是对象。
a.staticMethod(),看左边引用类型,不是看 new 出来的对象。父类引用不能直接调用子类独有的方法
1
2
3
4
5Animal a = new Dog();
a.watchHouse(); // 编译报错!watchHouse是Dog独有,Animal没有。
// 如果一定要调用,必须向下转型:
Dog d = (Dog) a;
d.watchHouse();
父类定义通用规范,子类各自实现;同一个父类引用,传入不同子类对象,自动执行子类重写后的逻辑。
关系梳理
继承 → 方法重写 → 向上转型 → 产生多态效果
建立父子关系->重写,具体实现->子类对象交给父类引用接收->触发多态
形象比喻:
父类规定了一个接口:
shout()(可以叫)子类 Dog 重写:shout→汪汪;子类 Cat 重写:shout→喵喵
重写 = 提前录好不同版本的 “叫声”
多态 = 拿着一个通用遥控器(父类引用 Animal),拿到狗就播放汪汪,拿到猫就播放喵喵。
向上转型 统一调度 简化代码
1 | |
instanceof 和 类型转换
instanceof是判断引用变量指向的真实对象,是不是某个类(或者它子类)的实例
语法:
对象引用 instanceof 类名返回布尔值:
true / false
1
2
3Animal a = new Dog();
System.out.println(a instanceof Dog); // true
System.out.println(a instanceof Animal); // true,Dog是Animal子类为什么需要:
真实痛点:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Animal a = new Dog();
/* a是Animal引用,不能直接调用Dog独有的方法watchHouse()
想要调用,必须向下转型 Dog d = (Dog)a;
但是!如果这个引用实际不是 Dog 对象,强转直接抛异常 ClassCastException,程序崩掉。
所以 instanceof 的诞生目的:转型之前先做安全校验,判断能不能转,防止报错*/
//旧写法:先判断再强转
if(a instanceof Dog){
Dog d = (Dog) a;
d.watchHouse();
}
//Java14+ 模式匹配,简化写法(了解即可)
if(a instanceof Dog d){
d.watchHouse();
}
注意点:
instanceof判断的是堆里面真实对象的类型,不是左边引用类型!null 引用 instanceof 任何类,结果都是 false
1
2Animal a = null;
System.out.println(a instanceof Dog); // false,不会空指针编译期就不可能成立的类型,编译器直接报错
比如
1
String s = "abc"; if(s instanceof Animal)直接编译失败。
Java类型重新理解
Java就两大类:基本类型+引用类型
基本数据类型 (8个 固定死)
byte short int long float double boolean char变量里面直接存的就是真实的值,变量本身就是数据。
1
int a = 10;栈里的变量 a,里面写的就是数字 10。没有堆、没有指向。
引用类型(剩下所有全都是!)
- 自定义类 (我们自己写的
Man,Animal,Dog) - Java自带类
String,ArrayList - 数组 数组也是引用类型!!
引用类型变量,栈里面存的不是对象本身,是一个地址(引用)
这个地址指向堆里面那个真实对象
1
Dog d = new Dog();栈:变量
d,存地址堆:
new Dog()创建出来的 Dog 对象,真实数据在这。- 自定义类 (我们自己写的
引用变量的【声明类型】和【堆里真实对象类型】可以不一样 多态的根基
1 | |
左边:引用的类型(编译类型,写代码时定义的):Animal
规定:编译器只允许你调用 Animal 里面有的方法。
右边 new 出来:堆里面真实对象(运行时类型):Dog
运行的时候,JVM 看这个真实对象,执行 Dog 重写的方法。
引用变量是什么类型,决定你能调用哪些方法;堆里真实对象是什么类型,决定重写的方法执行哪一套。
区分:
变量的类型(引用是什么类型)
对象的类型(堆里面 new 出来是什么)
引用类型独有的特性:引用变量的类型,可以和堆里面真实对象的类型不一样(要有继承关系),这就是多态的底层来源。
封装 继承 多态 抽象类 接口
static关键字详解
- 静态变量 类变量
- 静态方法 类方法
- 静态代码块
1 | |
静态导入包
1
2import static java.lang.Math.random;
import static java.lang.Math.PI;
final修饰类,该类不能被继承
抽象类
abstract修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那么该类就是抽象类
抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类
抽象类,不能使用new关键字来创建对象,它是用来让子类继承的
抽象方法,只有方法的声明,没有方法的实现,它是用来让子类实现的
子类继承抽象类,那么就必须要实现抽象类没有实现的抽象方法,否则该子类也要声明为抽象类
普通类的缺陷:你没法阻止别人创建无意义的父类实例。
abstract 解决上面痛点
1 | |
加了abstract之后,它变成代码层面的半成品:
编译器强制:不能 new Animal,从根源杜绝创建 “纯粹动物” 这种无意义对象。
可以定义抽象方法(空壳):强制所有继承它的子类,必须把这个方法写出来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14//普通父类:子类可重写、可不重写。
class Animal{
void shout(){}
}
class Dog extends Animal{
// 可以不重写shout,代码照样编译通过
}
//抽象类:子类必须重写抽象方法,不写直接报错。
abstract class Animal{
abstract void shout();
}
class Dog extends Animal{
// 不写shout,直接编译报错!编译器逼你写!
}
与普通类对比
日常说的类(普通类)的抽象:
把多个对象的共同特征抽出来,封装成类。是面向对象的思想,所有类都有这个特性。
特点:类是完整的,可以 new。
Java关键字abstract 抽象类
是语法层面的标记,专门用来:
- 禁止 new 这个类,它只当模板,只给子类继承
- 定义抽象方法,强制子类实现某个行为,统一规范
- 只用来定义规范,不允许单独实例化,并且强制子类必须实现某些方法。
abstract 修饰符
修饰类
作用:禁止new,只做模板,用来继承修饰方法
额外增加一条强制规则,强制子类实现这个对象
思考题
- 写一个普通父类,不想让别人 new 它。除了改成 abstract 抽象类,还有别的办法吗?
原理回顾
new 类名()的本质:调用这个类的构造器
所以只要构造器外部访问不到,外面就无法new这个类
1 | |
私有构造器特点:
外部不能new
不能被继承
子类继承父类的时候,子类构造器默认第一行要调用父类无参构造super()
如果父类构造器是 private,子类根本调用不到,直接编译报错。
适合:不想被继承,也不想被 new 的类(比如工具类,Math类就是典型)
- private构造器:封死实例化+封死继承,工具类用
- abstract抽象类:封死实例化,但开放继承,用来做多态模板
抽象类有构造器方法吗
虽然抽象类不能直接 new,但是它一定有构造器。
一条继承铁律:子类构造器执行的时候,第一行默认会调用父类的构造器
super()抽象类是父类,子类继承抽象类,子类创建对象时,必须调用抽象类的构造器,用来初始化抽象类里面的成员变量。
抽象类不能自己 new,但是子类 new 的时候,会去调用抽象类的构造器!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21abstract class Animal{
String name;
//抽象类的构造器
public Animal(String name){
this.name = name;
System.out.println("抽象类Animal的构造器执行了");
}
}
class Dog extends Animal{
//子类构造器,必须调用父类抽象类的构造器
public Dog(String name){
super(name); // 调用抽象类Animal的构造器
}
}
public class Test{
public static void main(String[] args) {
Dog dog = new Dog("旺财"); //new子类Dog,触发抽象类构造器执行
}
}抽象类构造器的唯一用途:子类实例化时,初始化抽象类里面的成员
注意点:
- 抽象类里面可以有静态方法,但是:静态方法绝对不能用 abstract 修饰(不能是抽象方法)。
- 静态方法不依赖实例,和抽象类能不能 new 对象没有冲突。
- 静态方法属于类,编译阶段就确定调用哪个,不看堆里面 new 出来的对象是谁,只看变量声明类
- abstract 指望运行时多态重写;static 是编译期绑定,没有多态,二者矛盾,不能共存。
- 抽象类里面可以放普通实例方法(不带 abstract,有方法体)。
- 普通实例方法(final 修饰的)是已经实现好的,禁止子类修改。
- 抽象类只是用来做模板,模板里一部分规则强制子类重写 (abstract 方法),一部分固定逻辑不让子类改 (final 方法)。
复习 抽象类只是串联
abstract 修饰类 → 抽象类:不能 new,但可以被继承
抽象类有构造器,子类 new 的时候,通过 super 调用,用来初始化抽象类成员
抽象类可以包含:
普通成员变量、静态变量、普通实例方法、final 实例方法、静态方法、抽象方法
抽象方法
abstract void method();:没有方法体,强制子类重写;
abstract 不能和 static、private、final 一起用,因为它们都会破坏重写
子类继承抽象类:
要么实现全部抽象方法,要么子类也标记 abstract
抽象类的诞生目的:
制作模板父类,允许继承,但禁止直接创建无意义的父类对象;抽象方法强制子类实现规定行为,方便做多态统一调度
接口的定义与实现
普通类:只有具体实现
抽象类:具体实现和规范(抽象方法)都有
模板父类,有一部分已经写好代码,留一部分空白让子类补
接口:只有规范
纯粹的行为契约,只规定要做什么,完全不关心怎么做
基础语法
1 | |
- 接口不能new,不能创建对象
- 接口没有构造器!(重点!抽象类是有构造器的,接口完全没有)
- 类不是 extends 继承接口,而是 implements 实现接口
接口和抽象类最核心的区别
抽象类:is-a 【是一个】的关系
Animal抽象类,Dog extends Animal → 狗是一种动物
描述事物本身是什么,可以包含属性、普通方法、构造器。
只能单继承:一个类只能 extends 一个父类(抽象类也一样,只能继承 1 个)
接口:can-do 【能做某件事】的能力
Run接口,Dog implements Run → 狗拥有奔跑的能力
描述一种能力、行为规范,一般不存对象属性。
一个类可以同时实现多个接口!(多实现,弥补 Java 类只能单继承的短板)
1
2
3
4
5
6
7// 狗既能跑,也能叫
class Dog implements Run,Shout{
@Override
public void run(){}
@Override
public void shout(){}
}
Java8之后接口升级
Java8以前,接口里面只能由抽象方法+常量,完全纯规范
1 | |
接口就是可复用的能力模块,可以自由贴到任何类上,不受继承体系限制!
把能力抽离出来,脱离类的继承树,跨类复用,统一调度。
1 | |
Java8新增:
default默认方法:接口里可以带方法体的方法,有默认实现,子类可以选择重写或者直接用static静态方法:接口里面可以放静态方法。属于接口,只能接口名调用,实现类继承不到

N种内部类 (奇葩~)
定义
内部类就是在一个类的内部再定义一个类,不如,A类中定义了一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外部类了
设计初衷
把强关联、只给外部类用的类,打包封装在一起,不用单独建立.java文件,并且可以访问外部类的私有成员
分类
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
详解
成员内部类
写在外部类里面,和成员变量、成员方法平级,属于外部类的实例对象
必须先有外部类对象,才能new内部类对象
可以访问外部类的所有成员,包括private私有属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class Outer{
//外部类私有成员
private String name = "外部类名字";
//成员内部类,和方法、变量同级
class Inner{
public void show(){
//直接访问外部类private成员!这是它最大优势
System.out.println(name);
}
}
}
public class Test{
public static void main(String[] args) {
//写法:外部类对象.new 内部类()
Outer outer = new Outer();//先new外部例对象
Outer.Inner inner = outer.new Inner();
inner.show();
}
}这个内部类完全是为外部类服务的,外部类之外几乎不会单独使用。内部类可以直接读取外部类私有变量,不用写一堆getter
典型例子:
ArrayList里面的迭代器Itr,就是成员内部类
静态内部类 (也叫嵌套类 static class)
加了
static,写在外部类里面,属于外部类本身,不属于外部类实例不需要外部类对象,直接
外部类.内部类new只能访问外部类的static静态成员,不能直接访问外部实例变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Outer{
private static String msg = "静态消息";
private int age = 18; //实例变量
//静态内部类
static class StaticInner{
public void show(){
System.out.println(msg); //✅可以访问外部static
//System.out.println(age); //❌报错,不能访问外部实例成员
}
}
}
public class Test{
public static void main(String[] args) {
//不用new外部类!直接new静态内部类
Outer.StaticInner inner = new Outer.StaticInner();
inner.show();
}
}内部类和外部类逻辑绑定,但不需要依赖外部类对象
经典例子:
HashMap里面的Node,就是静态内部类。Node只是存数据,不需要外部HashMap实例区分:成员内部类:绑外部对象;静态内部类:绑外部类
局部内部类
定义在方法里面,方法内的类,作用域仅限当前这个方法
出了这个方法,这个类直接消失,外面完全看不见
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class Outer{
public void test(){
//局部内部类,写在方法内部!
class LocalInner{
public void show(){
System.out.println("局部内部类方法");
}
}
//只能在这个方法里面使用
LocalInner li = new LocalInner();
li.show();
}
}
public class Test{
public static void main(String[] args) {
Outer o = new Outer();
o.test();
// LocalInner 在这里无法使用,出了test方法就失效
}
}这个类只在当前这一个方法内临时使用,别的地方完全不用。不想单独建文件、不想暴露给整个类,缩小作用域,减少命名污染
JDK8前,访问方法局部变量,变量必须final
匿名内部类 最常用
没有类名的内部类,一次性使用,用完就没
一般用来实现接口/继承抽象类,只重写里面的方法,只创建这一个对象,不需要复用这个类
实现接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36interface Animal{//定义接口
void shout();//抽象方法,要实现
}
//情况1:不使用匿名内部类
// 单独写一个Dog类,实现Animal接口
class Dog implements Animal{
@Override
public void shout() {
System.out.println("汪汪汪");
}
}
public class Test{
public static void main(String[] args) {
Animal dog = new Dog(); // new我们写好的Dog类
dog.shout();
}
}
/*这个Dog类,就 main 里面只用这一次,后面再也不用。
还要单独写一个类,占名字,麻烦*/
//情况2:匿名内部类版本 不用单独定义 Dog 这个命名类,直接 new 的时候当场实现
public class Test{//测试类
public static void main(String[] args) {
// ==========下面这一整块,就是匿名内部类==========
//匿名内部类:没有类名,直接new接口,写实现
Animal dog = new Animal(){
//new 一个实现了 Animal 接口的子类对象,但是这个子类没有名字!
//创建了一个没有名字的类,implements Animal,然后 new 这个无名类的对象,再向上转型为 Animal 接口引用。
@Override
public void shout() {
System.out.println("汪汪汪");
}
};
dog.shout();
}
}继承抽象类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16abstract class Animal{
public abstract void shout();
}
public class Test{
public static void main(String[] args) {
Animal cat = new Animal(){
//new 一个继承 Animal 抽象类的无名子类,重写抽象方法。
@Override
public void shout() {
System.out.println("喵喵喵");
}
};
cat.shout();
}
}匿名内部类的特点:
- 只有一次使用机会,只能在这里 new 一次,没有类名,别的地方拿不到这个类,不能重复 new。
- 省去单独创建一个
.java文件,或者单独写一个类。 - 本质:是一个类 + 同时创建这个类的对象,两件事一次性完成。
- 匿名内部类可以访问方法里的局部变量,但是变量不能二次修改(有效 final)
Error和Exception
什么是异常
实际工作中,遇到的情况不可能是非常完美的。用户输入不一定符合你的要求,你的程序打开某个文件,这个文件可能不存在或者文件格式不对,你要读取的数据,数据可能是空的等。软件程序在运行过程中,非常可能遇到各种异常问题,我们叫异常,Exception,意思是例外。这些,例外情况,或者异常,怎么让我们写的程序做出合理的处理,而不至于程序崩溃。
简单分类
检查性异常
代表:用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在的文件,一个异常就发生了,这些异常在编译时不能被简单地忽略
运行时异常
是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略错误Error:错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。
例如:当栈溢出时,一个错误就发生了,它们在编译也检查不到的
异常处理框架
Java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类
在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和Exception


捕获和抛出异常
抛出异常
捕获异常
1 | |

异常处理五个关键字 try catch finally throw throws
自定义异常及经验小结

经验小结:
- 处理运行时异常时,采用逻辑去合理规避同时辅助try -catch 处理
- 在多重catch块后面,可以加一个catch(Exception)来处理可能被遗漏的异常
- 对于不确定的代码,也可以加上try-catch,处理潜在的异常
- 尽量去处理异常,切忌只是简单地调用printStackTrace()去打印输出
- 具体如何处理异常,要根据不同的业务需求和异常类型去决定
- 尽量添加finally语句块去释放占用的资源 ,IO Scanner
JavaSE总结
学习的一些思考
1 | |