前言
Java 语言基础中知识点繁多, 大多数只是在第一次学习时有印象, 但是因为长时间没有运用到会遗忘, 这篇博文就用来记录和总结遇到的生僻的知识点.
抽象类
abstract 修饰符可以用来修饰方法, 也可以用来修饰类, 如果修饰方法, 那么该方法就是抽象方法; 如果修饰类, 那么该类就是抽象类.
- 不能
new抽象类, 只能靠子类去实现它 - 抽象类中可以写普通方法
- 抽象方法必须在抽象类中
接口
- 在接口中定义的常量会默认加上修饰符
public static final, 在实现了该接口的类中可以直接拿来用 - 在接口中定义的方法会默认加上修饰符
pubilc abstract - 接口中没有构造方法, 这是不能被实例化的原因
内部类
成员内部类
定义以下的类:
1 | public class Outer { |
两个类中分别定义了各自的方法. 然后实例化外部类的对象, 对该类进行测试.
1 | public class Application { |
这里就要注意 new 内部类对象时写法上的区别了.
可以通过定义内部类的方法来获取外部类的私有属性, 修改外部类如下:
1 | public class Outer { |
然后通过内部类的对象调用:
1 | public class Application { |
输出为:

静态内部类
在定义内部类时添加 statc 关键字即可.
因为创建内部类的方式与调用方法的步骤与成员内部类相同, 所以就不再赘述.
但是需要注意的是, 静态内部类是随外部类加载时创建的, 所以不能调用成员变量.
局部内部类
在外部类的方法中定义的类叫局部内部类.
1 | public class Outer { |
异常

Error:
通常是灾难性的致命的错误, 是程序员无法控制和处理的, 当出现这些异常时, JVM一般会终止线程
Exception:
通常情况下是可以被程序处理, 并且在程序中应该尽可能的去处理这些异常
抛出异常
主动抛出异常
1 | public static void main(String[] args) { |
假设方法中处理不了这个异常, 直接在方法上抛出, 使用throws 关键字, 然后调用该方法时用try-catch 捕获处理
捕获异常
使用try-catch捕获异常并处理
1 | public static void main(String[] args) { |
输出为:
1 | 运算失败, 除数为0 |
异常处理五个关键字
try, catch, throw, throws, finally
先看try , catch, finally 关键字的常见用法
1 | public static void main(String[] args) { |
输出结果为:
1 | 程序出现异常, 变量b不能为0 |
这里需要注意的是finally, 不管是否能捕获到异常, 都会执行该代码块, 属于善后工作, 也可以不要
可以写多个catch 进行捕获, 如果要写多个的话,小的异常写上面, 层层递进, 大的写上面就给覆盖掉了
thow和throws 的区别在于, 前者为方法中主动抛出异常, 后者是该方法中处理不了让调用该方法的方法通过try-catch处理.
自定义异常
创建自定义异常要继承Exception 类
1 | public class MyException extends Exception { |
然后使用该异常:
1 | public class MyExceptionTest { |
输出为:
1 | 传递的参数为: 11 |
在这段代码中, 判断a>10 的时候抛出我们所定义的异常, 这里有两种处理方式, 一是在该方法中try-catch 进行异常的处理, 或者在方法层面抛出, 让该方法的调用者去try-catch 去处理. 这里采用的是第二种处理方法.
总之, 只要抛出了异常, 要么就地处理, 要么抛出去谁调用谁处理, 总是要还的.
以下是就地处理的代码和结果:
1 | public class MyExceptionTest { |
输出为:
1 | 传递的参数为: 11 |
要注意输出的结果是不一样的!!
类加载器与构造器的调用顺序
今天遇到一个考察继承的题目, 牵涉到类加载器和构造器调用顺序的问题, 很有趣.
问: new一个C02对象, 会输出什么信息?
1 | class A02 { |
分析:
这里有三个类,A02是父类, B02继承A02, C02继承B02. 每个类都有各自的静态代码块和无参构造器.
根据继承的特点可知, 当new 一个C02对象时, 会去调用C02的构造器, C02构造器第一行默认为super() , 即调用父类B02的构造器, 同理调用A02 的构造器, 然后顺序执行类A02 的代码 -> A02构造器内的代码 -> 顺序执行类B02 的代码 -> B02构造器内的代码 -> 顺序执行C02 的代码 -> C02 构造器内的代码.
然而加了类构造器之后该是什么顺序呢? 直接贴结果:
1 | getVal01 |
我们刚才分析的步骤是从第七行开始的, 后面全部严格按照分析的步骤打印信息, 所以可以得出结论: 类加载器工作的时间整体都在构造器之前.
不难看出, 类加载器的调用顺序与构造器是类似的, 也是从子类一路到父类, 然后再到子类. 更加严谨的调用顺序如下所述:
类加载器调用顺序
JVM会用类加载器加载xxx.C02这个class文件
加载(class){
if(class有父类){
加载(superclass);
}
1.静态域申明,默认初始化为0,false,null
2.按照申明顺序(从上而下书写顺序)执行静态域(赋值)和静态代码块(执行代码块体),
二者等价,因此不可在静态代码块中使用位于代码块之后申明的静态域,但是可以初始化
3.按照申明顺序加载静态方法
}
构造器调用顺序
所有实例域初始化为默认值0,false,null
按照申明顺序执行域初始化及块初始化
如果构造器”第一行”调用了其他构造器,则执行
执行构造器体
方法调用顺序
编译器查看对象的申明类型,找到它所有与方法名相同的方法
根据参数类型,找到相应”最合适”的父类方法可能会出现类型转换(向上转型)
如果是private,static,final,构造器 方法,那么已经确定就是该方法(这四种类型的方法没有多态特征),
因为没有多态所以也叫静态绑定
如果是其他方式,采用动态绑定:JVM去寻找改类的实际类型中对应的最合适方法
执行调用
结合理论表述, 本题的顺序总结来说就是:
- 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 父类的构造方法
- 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 子类的构造方法