Java面向对象(下)

1.类的继承

1.1. 继承的概念

1.1.1.定义

  • 在Java中,类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类被称作子类,现有类被称作父类或基类,子类会自动拥有父类所有可继承的属性和方法。
1
2
3
[修饰符] class 子类名 extends 父类名 {
// 程序核心代码
}

1.1.2.注意

  • 在Java中,类只支持单继承,不允许多重继承,也就是说一个类只能有一个直接父类
1
2
3
class A{} 
class B{}
class C extends A,B{} //C类不可以同时继承A类和B类
  • 在Java中,多个类可以继承同一个父类
1
2
3
class A{}
class B extends A{}
class C extends A{} // 类B和类C都可以继承类A
  • 在Java中,多层继承是可以的,即一个类的父类可以再去继承另外的父类
1
2
3
class A{}
class B extends A{} //类B继承类A,类B是类A的子类
class C extends B{} //类C继承类B,类C是类B的子类,同时也是类A的子类
  • 在Java中,子类和父类是一种相对概念,也就是说,一个类是某个类父类的同时,也可以是另一个类的子类

1.2. 重写父类方法

1.2.1.定义

  • 在继承关系中,子类会自动继承父类中公共的方法,但有时在子类中需要对继承的方法进行一些修改,即对父类的方法进行重写

1.2.2.注意

  • 子类中重写的方法需要和父类被重写的方法具有相同的方法名、参数列表以及返回值类型
  • 子类重写父类方法时,不能使用比父类中被重写的方法更严格的访问权限

1.3. super关键字

  • 使用super关键字调用父类的成员变量和成员方法
1
2
super.成员变量
super.成员方法([参数1,参数2...])
  • 使用super关键字调用父类的构造方法
1
super([参数1,参数2...])

1.4. Object类

1.4.1.基本概念

  • 在Java中提供了一个Object类,它是所有类的父类,即每个类都直接或间接继承自该类
  • Object类通常被称之为超类、基类或根类
  • 当定义一个类时,如果没有使用extends关键字为这个类显示地指定父类,那么该类会默认继承Object类

1.4.2.常用方法

方法声明功能描述
boolean equals(Object obj)判断某个对象与此对象是否相等
final Class<?> getClass()返回此Object的运行时类
int hashCode()返回该对象的哈希码值
String toString()返回该对象的字符串表示
void finalize()垃圾回收器调用此方法来清理没有被任何引用变量所引用对象的资源

Object类的toString()方法中输出信息格式及说明:

1
getClass().getName() + "@" + Integer.toHexString(hashCode());
  • getClass().getName():代表返回对象所属类的类名,即包名+类名的全限定名称。
  • hashCode():代表返回该对象的哈希值。
  • Integer.toHexString(hashCode()):代表将对象的哈希值用16进制表示。其中, hashCode()是Object类中定义的一个方法,这个方法将对象的内存地址进行哈希运算,返回一个int类型的哈希值。

2.final关键字

  • final修饰的类不能被继承
  • final修饰的方法不能被子类重写
  • final修饰的变量(成员变量和局部变量)是常量,只能赋值一次

3.抽象类和接口

3.1. 抽象类

3.1.1.语法格式

1
2
3
4
5
6
// 定义抽象类
[修饰符] abstract class 类名 {
// 定义抽象方法
[修饰符] abstract 方法返回值类型 方法名([参数列表]);
// 其他方法或属性
}

3.1.2.注意

  • 包含抽象方法的类必须定义为抽象类,但抽象类中可以不包含任何抽象方法
  • 抽象类是不可以被实例化的,如果想调用抽象类中定义的抽象方法,需要创建一个子类,在子类中实现抽象类中的抽象方法。

3.2. 接口

3.2.1.对比

  • 老版本(JDK < 8):接口是一种特殊的抽象类,它不能包含普通方法,其内部的所有方法都是抽象方法,它将抽象进行的更为彻底。
  • 新版本(JDK >= 8):接口中除了抽象方法外,还可以有默认方法和静态方法(也叫类方法),默认方法使用default修饰,静态方法使用static修改,并且这两种方法都允许有方法体。

3.2.2.语法格式(JDK >= 8)

1
2
3
4
5
6
7
8
9
10
[修饰符] interface 接口名 [extends 父接口1,父接口2,...] {
[public] [static] [final] 常量类型 常量名 = 常量值;
[public] [abstract] 方法返回值类型 方法名([参数列表]);
[public] default 方法返回值类型 方法名([参数列表]) {
// 默认方法的方法体
}
[public] static 方法返回值类型 方法名([参数列表]) {
// 类方法的方法体
}
}

3.2.3.说明

  • 修饰符可以使用public或直接省略(省略时默认采用包权限访问控制符)
  • 在接口内部可以定义多个常量和抽象方法,定义常量时必须进行初始化赋值,定义默认方法和静态方法时,可以有方法体
  • 在接口中定义常量时,可以省略“public static final”修饰符,接口会默认为常量添加“public static final”修饰符
  • 在接口中定义抽象方法时,也可以省略“public abstract”修饰符
  • 定义default默认方法和static静态方法时,可以省略“public”修饰符
1
2
3
[修饰符] class 类名 [extends 父类名] [implements 接口1,接口2,...] {
...
}
  • 接口中可以包含三类方法,抽象方法、默认方法、静态方法
  • 静态方法可以通过“接口名.方法名”的形式来调用
  • 抽象方法和默认方法只能通过接口实现类的实例对象来调用。
  • 接口的实现类,必须实现接口中的所有抽象方法

3.2.4.总结

  • 从JDK 8开始,接口中的方法除了包含抽象方法外,还包含默认方法和静态方法,默认方法和静态方法都可以有方法体,并且静态方法可以直接通过“接口.方法名”来调用

  • 当一个类实现接口时,如果这个类是抽象类,只需实现接口中的部分抽象方法即可,否则需要实现接口中的所有抽象方法

  • 一个类可以通过implements关键字同时实现多个接口,被实现的多个接口之间要用英文逗号(,)隔开

  • 接口之间可以通过extends关键字实现继承,并且一个接口可以同时继承多个接口,接口之间用英文逗号(,)隔开

  • 一个类在继承一个类的同时还可以实现接口,此时,extends关键字必须位于implements关键字之前。

4.多态

4.1. 多态概述

4.1.1.定义

  • 多态指的是在一个类中定义的属性和功能被其他类继承后,当把子类对象直接赋值给父类引用变量时,相同引用类型的变量调用同一个方法所呈现出的多种不同行为特性。

4.1.2.实现方式

  • 子类对象 -> 父类对象
  • 子类中改写方法
  • 用父类变量调用方法

4.1.3.实例

1
2
3
abstract class Animal {
abstract void shout();
}
1
2
3
4
5
class Cat extends Animal {
public void shout() {
System.out.println("喵喵。。。");
}
}
1
2
3
4
5
class Dog extends Animal {
public void shout() {
System.out.println("汪汪。。。");
}
}
1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
Animal an1 = new Cat();
Animal an2 = new Dog();
an1.shout();
an2.shout();
}
}
1
2
3
4
5
6
/**
* 打印结果
*
* 喵喵。。。
* 汪汪。。。
*/

4.1.4.注意

  • Java的多态性是由类的继承、方法重写以及父类引用指向子类对象体现的

4.2. 对象的类型转换

4.2.1.向上转型

  • 将子类对象当做父类类型使用
1
2
Animal an1 = new Cat();     // 将Cat类对象当做Animal类型来使用
Animal an2 = new Dog(); // 将Dog类对象当做Animal类型来使用
  • 注意
    • 将子类对象当做父类使用时不需要任何显式地声明,需要注意的是,此时不能通过父类变量去调用子类特有的方法

4.2.2.对象的类型转换

  • instanceof :判断一个对象是否为某个类(或接口)的实例或者子类实例
1
对象(或者对象引用变量) instanceof 类(或接口)

5.内部类

5.1. 成员内部类

5.1.1.定义

  • 在一个类中除了可以定义成员变量、成员方法,还可以定义类,这样的类被称作成员内部类

5.1.2.语法

1
外部类名.内部类名 变量名 = new 外部类名().new 内部类名();

5.1.3.注意

  • 在成员内部类中,可以访问外部类的所有成员,包括成员变量和成员方法;在外部类中,同样可以访问成员内部类的变量和方法

5.2. 局部内部类

5.2.1.定义

  • 也叫做方法内部类,定义在某个局部范围中的类,它和局部变量一样,都是在方法中定义的,其有效范围只限于方法内部

5.2.2.示例

1
2
3
4
5
6
7
8
9
10
11
class Outer {
public void test() {
class Inner {
public void show() {
System.out.println("InnerClass");
}
}
Inner inner = new Inner();
inner.show();
}
}
1
2
3
4
5
6
class Main {
public void static main(String[] args) {
Outer outer = new Outer();
outer.test();
}
}

5.2.3.注意

  • 在局部内部类中,局部内部类可以访问外部类的所有成员变量和方法,而局部内部类中的变量和方法却只能在创建该局部内部类的方法中进行访问

5.3. 静态内部类

5.3.1.定义

  • 使用static关键字修饰的成员内部类。

5.3.2.语法

1
外部类名.静态内部类名 变量名 = new 外部类名.静态内部类名();

5.3.3.注意

  • 静态内部类在成员内部类前增加了static关键字,在功能上,静态内部类中只能访问外部类的静态成员,同时通过外部类访问静态内部类成员时,可以跳过外部类从而直接通过内部类访问静态内部类成员

5.4. 匿名内部类

5.4.1.定义

  • 没有名称的内部类。

5.4.2.语法

1
2
3
new 父接口() {
// 匿名内部类实现部分
}

5.4.3.注意

  • 在调用包含有接口类型参数的方法时,通常为了简化代码,可以直接通过匿名内部类的形式传入一个接口类型参数,在匿名内部类中直接完成方法的实现
  • 从JDK 8开始,允许在局部内部类、匿名内部类中访问非final修饰的局部变量,而在JDK 8之前,局部变量前必须加final修饰符,否则程序编译报错。

6.JDK – Lambda表达式

6.1. Lambda表达式入门

6.1.1.基本概念

  • Lambda表达式是JDK 8中一个重要的新特性,它使用一个清晰简洁的表达式来表达一个接口,同时Lambda表达式也简化了对集合以及数组数据的遍历、过滤和提取等操作。
  • 使用JDK 8中新增的Lambda表达式,这种表达式只针对有一个抽象方法的接口实现,以简洁的表达式形式实现接口功能来作为方法参数。

6.1.2.语法

1
([数据类型 参数名,数据类型 参数名,...]) -> { 表达式主体 }
  • 参数:用来向表达式主体内部实现的接口方法传入参数。多个参数之间用逗号分隔,可以省略数据类型,只有一个参数时还可以省略小括号
  • ->:表示Lambda表达式箭牌,用来指定参数数据指向,不能省略
  • 表达式主体:本质就是接口中抽象方法的具体实现。如果只有一条语句,可以省略大括号;在Lambda表达式主体中只有一条return语句时,也可以省略return关键字

6.2. 函数式接口

  • 函数式接口是指有且仅有一个抽象方法的接口,Lambda表达式就是Java中函数式编程的体现,只有确保接口中有且仅有一个抽象方法,Lambda表达式才能顺利地推导出所实现的这个接口中的方法。
  • 在JDK 8中,接口上标注有@FunctionalInterface注解的即为函数式接口,在函数式接口内部有且只有一个抽象方法。
  • @FunctionalInterface注解只是显示的标注了接口是一个函数式接口,并强制编辑器进行更严格的检查,确保该接口是函数式接口。

6.3. 方法引用与构造器引用

6.3.1.定义

  • Lambda表达式的主体只有一条语句时,程序不仅可以省略包含主体的花括号,还可以通过英文双冒号“::”的语法格式来引用方法和构造器(即构造方法)
  • 本质都是对Lambda表达式的主体部分已存在的方法进行直接引用,主要区别就是对普通方法与构造方法的引用。

6.3.2.引用类型

种类Lambda表达式示例对应的引用示例
类名引用普通方法(x,y,…)-> 对象名x.类普通方法名(y,…)类名::类普通方法名
类名引用静态方法(x,y,…) -> 类名.类静态方法名(x,y,…)类名::类静态方法名
对象名引用方法(x,y,…) -> 对象名.实例方法名(x,y,…)对象名::实例方法名
构造器引用(x,y,…) -> new 类名 (x,y,…)类名::new
6.3.2.1.类名引用静态方法
  • 通过类名对静态方法的引用,该类可以是Java自带的特殊类,也可以是自定义的普通类
1
2
3
4
// 使用Lambda表达式方式
printAbs(-10, n -> Math.abs(n));
// 使用方法引用的方式
printAbs(-10, Math::abs);
6.3.2.2.对象名引用方法
  • 通过实例化对象的名称来对其方法进行的引用
1
2
3
4
// 使用Lambda表达式方式
printUpper("Hello", t -> stu.printUpperCase(t));
// 使用方法引用的方式
printUpper("Hello", stu::printUpperCase);
6.3.2.3.构造器引用方法
  • 对类自带的构造器的引用
1
2
3
4
// 使用Lambda表达式方式
printName("xxx", name -> new Person(name));
// 使用构造器引用的方式
printName("xxx", Person::new);
6.3.2.4.类名引用普通方法
  • 通过一个普通类的类名来对其普通方法进行的引用
1
2
3
4
// 使用Lambda表达式方式
printUpper(new StringUtils(),"Hello",(object, t) -> object.printUpperCase(t));
// 使用方法引用的方式
printUpper(new StringUtils(),"Hello",StringUtils::printUpperCase);

7.异常 Exception

7.1. 什么是异常

7.1.1.定义

图片1

  • Exception称为异常类,它表示程序本身可以处理的错误。在Java程序开发中进行的异常处理,都是针对Exception类及其子类的。
  • Error称为错误,表示Java运行时产生的系统内部错误或资源耗尽的错误,是比较严重的,仅靠修改程序本身是不能恢复执行的,例如系统崩溃,虚拟机错误等。

7.1.2.Throwable类中的常用方法

方法声明功能描述
String getMessage()返回此throwable的详细消息字符串
void printStackTrace()将此throwable及其追踪输出至标准错误流
void printStackTrace(PrintStream s)将此throwable及其追踪输出到指定的输出流
String getMessage()返回此throwable的详细消息字符串

7.2. 异常的类型

7.2.1.编译时异常

7.2.1.1.基本概念
  • 在程序编译时期产生的异常,而这些异常必须要进行处理,也称为checked异常
  • 异常类汇总:在Exception的子类中,除了RuntimeException类及其子类外,其他子类都是编译时异常。
  • 特点:编译时异常的特点是在程序编写过程中,Java编译器就会对编写的代码进行检查,如果出现比较明显的异常就必须对异常进行处理,否则程序无法通过编译。
7.2.1.2.处理方式
  • 使用try…catch语句对异常进行捕获处理
  • 使用throws关键字声明抛出异常,让调用者处理

7.2.2.运行时异常

7.2.2.1.基本概念
  • 这种异常即使不编写异常处理代码,依然可以通过编译,也称为unchecked异常
  • 运行时异常一般是由于程序中的逻辑错误引起的,在程序运行时无法恢复。
  • 异常汇总RuntimeException类及其子类都是运行时异常。
  • 特点:运行时异常是在程序运行时由Java虚拟机自动进行捕获处理的,即使没有使用try..catch语句捕获或使用throws关键字声明抛出,程序也能编译通过,只是在运行过程中可能报错。
7.2.2.2.常见异常
异常类名称异常类说明
ArithmeticException,算术异常
IndexOutOfBoundsException角标越界异常
ClassCastException类型转换异常
NullPointerException空指针异常
NumberFormatException数字格式化异常

7.3. try…catch和finally

7.3.1.定义

  • 当程序发生异常时,会立即终止,无法继续向下执行。为了保证程序能够有效的执行,Java中提供了一种对异常进行处理的方式——异常捕获。

7.3.2.语法

1
2
3
4
5
6
7
try {
// 可能发生异常的语句
} catch(Exception类或其子类 e){
// 对捕获的异常进行相应处理
} finally {
// 无论是否发生异常,此代码块代码总会执行
}
  • try{}代码块中,发生异常语句后面的代码是不会被执行的。在程序中,如果希望有些语句无论程序是否发生异常都要执行,这时就可以在try…catch语句后,加一个finally{}代码块。

7.4. throws 关键字

7.4.1.定义

  • 将这种异常从当前方法中抛出,然后让后续的调用者在使用时再进行异常处理

7.4.2.语法

1
2
3
[修饰符] 返回值类型 方法名([参数类型 参数名1...]) throws 异常类1,异常类2,... {
// 方法体...
}
  • throws关键字需要写在方法声明的后面,并在后面需要声明方法中发生异常的类型

7.4.3.注意

  • 当调用者在调用有抛出异常的方法时,除了可以在调用程序中直接进行try…catch异常处理外,也可以根据提示使用throws关键字继续将异常抛出,这样程序也能编译通过。但是,程序发生了异常,终究是需要进行处理的,如果没有被处理,程序就会非正常终止

7.5. throw 关键字

7.5.1.定义

  • throw关键字用于方法体内,并且抛出的是一个异常类对象
  • throws关键字用在方法声明中,用来指明方法可能抛出的多个异常
  • 通过throw关键字抛出异常后,还需要使用throws关键字或try…catch对异常进行处理

7.5.2.语法

1
2
3
4
[修饰符] 返回值类型 方法名([参数类型 参数名,...]) throws 抛出的异常类 {
// 方法体...
throw new Exception类或其子类构造方法;
}

7.5.3.注意

  • 如果throw抛出的是ErrorRuntimeException或它们的子类异常对象,则无需使用throws关键字或try…catch对异常进行处理。

7.6. 自定义异常

1
2
3
4
5
6
7
8
/**
* 创建MyException类,继承自RuntimeException
*/
public class MyException extends RuntimeException {
public MyException(String message) {
super(message);
}
}
1
2
3
4
5
6
7
8
9
/**
* test方法抛出一个MyException实例,添加信息"这是MyException异常"
*/

public class ExceptionTest {
public void test() throws MyException {
throw new MyException("这是MyException异常");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 使用try catch 抓住MyException异常,并输出其中的信息
*/
public class Main {
public static void main(String[] args) {
ExceptionTest exceptionTest = new ExceptionTest();
try {
exceptionTest.test();
} catch (MyException e) {
System.out.println(e.getMessage());
}
}
}