JavaSE复习day4
胡家伟
13.多态
基本介绍
概念
- 多态是方法或对象具有多种形态,是面向对象的第三大特征。
- 多态的前提是两个对象(类)存在继承关系,多态是建立在封装和继承基础上的。
多态的具体实现
对象的多态是多态的核心和重点。
规则
- 一个对象的编译类型和运行类型可以不一致。
- 编译类型在定义对象时就确定了,不能改变,而运行类型是可以变化的。
- 编译类型看定义对象时 = 的左边,运行类型看 = 右边。
//人类
public void say(){
System.out.println("im a person");
}
//学生类
@Override
public void say() {
System.out.println("im a student");
}
//主方法
Person person = new Student();
person.say();
输出
im a student
多态的转型
向上转型
-
本质:父类的引用指向子类的对象
-
特点:
- 编译类型看左边,运行类型看右边
- 可以调用父类的所有成员(需遵守访问权限)
- 不能调用子类的特有成员
- 运行效果看子类的具体实现
-
语法:
父类类型 引用名 = new 子类类型();
//人类
public void say(){
System.out.println("im a person");
}
//学生类
@Override
public void say() {
System.out.println("im a student");
}
public void study(){
System.out.println("im studying");
}
//主方法
Person person = new Student();
person.say();
person.study();//无法调用,编译期报错
输出
im a student
向下转型
-
本质:一个已经向上转型的子类对象,将父类引用转为子类引用
-
特点:
- 只能强制转换父类的引用,不能强制转换父类的对象
- 要求父类的引用必须指向的是当前目标类型的对象
- 当向下转型后,可以调用子类类型中所有的成员
-
语法:
子类类型 引用名 = (子类类型) 父类引用;
//人类
public void say(){
System.out.println("im a person");
}
//学生类
@Override
public void say() {
System.out.println("im a student");
}
public void study(){
System.out.println("im studying");
}
//主方法
Person person = new Student();
Student student = (Student) person;
student.say();
student.study();
输出
im a student
im studying
转型异常
使用强转时,可能出现异常
//异常演示
//向上转型
Person person = new Student();
//调用Student的say
person.say();
//向下转型
Worker worker = (Worker) person;
//运行报错
worker.say();
输出
im a student
Exception in thread "main" java.lang.ClassCastException
这段代码在运行时出现了 ClassCastException 类型转换异常,原因是 Student 类与 Worker类 没有继承关系,因此所创建的是Student 类型对象在运行时不能转换成 Worker 类型对象。
instanceof比较操作符
为了避免上述类型转换异常的问题,我们引出 instanceof 比较操作符,用于判断对象的类型是否为XX类型或XX类型的子类型
Person person = new Student();
person.say();
if(person instanceof Student){
Student student = (Student) person;
student.study();
//可以把这两句简写为 ((Student) person).study();
}
if(person instanceof Worker){
Worker worker = (Worker) person;
worker.salary();
//可以把这两句简写为 ((Worker) person).salary();
}
输出
im a student
im studying
动态绑定
-
当调用对象方法的时候,该方法会和该对象的运行类型绑定
-
当调用对象属性时,没有动态绑定机制,即哪里声明,哪里使用
//向上转型(自动类型转换)
//程序在编译阶段只知道 person 是 Person 类型
//程序在运行的时候才知道堆中实际的对象是 Student 类型
Person person = new Student();
//程序在编译时 person 被编译器看作 Person 类型
//因此编译阶段只能调用 Person 类型中定义的方法
//在编译阶段,person 引用绑定的是 Person 类型中定义的 say 方法(静态绑定)
//程序在运行的时候,堆中的对象实际是一个 Student 类型,而 Student 类已经重写了 say 方法
//因此程序在运行阶段对象中绑定的方法是 Student 类中的 say 方法(动态绑定)
person.say();
输出
im a student
应用
多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
Person[] people = new Person[3];
people[0] = new Person();
people[1] = new Student();
people[2] = new Worker();
for (Person person : people) {
person.say();
}
输出
im a person
im a student
im a worker
多态参数
方法定义的形参类型为父类类型,实参类型允许为子类类型
public class Test {
public static void main(String[] args) {
Student student = new Student();
Worker worker = new Worker();
Test test =new Test();
test.test(student);
test.test(worker);
}
//定义方法test,形参为 Person 类型(形参是父类)
//功能:调用学生的study或工人的salary方法
public void test(Person person){
if(person instanceof Student){
((Student) person).study();//向下转型
}
if(person instanceof Worker){
((Worker) person).salary();//向下转型
}
}
}
输出
im studying
my salary is 10000
多态的优点
- 代码更加灵活:无论右边new的时候换成哪个子类对象,等号左边调用方法都不会变化。
- 提高程序的拓展性:定义方法的时候,使用父类类型作为参数,将来使用时,使用具体的子类类型操作
14.抽象类
概念
父类中的方法,被他的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了。我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类。
抽象方法
使用abstrct关键字修饰的方法就是抽象方法,值得注意的是,抽象方法只包含一个方法名,没有方法体。
public abstract void run()
抽象类
如果一个类包含抽象方法,那么该类必须是抽象类。
public abstract class Animal {
public abstract void run();
}
抽象类的使用
继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父类的抽象方法。
//动物抽象类
public abstract class Animal {
public abstract void run();
}
//猫类
public class Cat extends Animal{
@Override
public void run() {
System.out.println("A cat is running");
}
}
//测试类
public class Test {
public static void main(String[] args) {
new Cat().run();
}
}
输出
A cat is running
没有抽象方法的抽象类
抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
//动物抽象类
public abstract class Animal {
public Animal(){
System.out.println("动物产生了");
}
}
//猫类
public class Cat extends Animal{
}
//测试类
public class Test {
public static void main(String[] args) {
new Cat();
}
}
输出
动物产生了
从上面的例子可以看出,子类的构造方法需要访问父类的构造方法,与正常类相同。
抽象类和普通类的区别
- 抽象类不能被实例化(规定)。
- 抽象类中可以定义抽象方法,普通类不可以。
抽象方法和普通方法的区别
- 普通的方法可以被子类重写也可以不重写,总之子类可以有选择性地重写或者不重写父类中的普通方法。
- 抽象方法必须被子类实现(重写)。
15.接口
概述
-
接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么接口的内部主要就是封装了方法,包含抽象方法,默认方法和静态方法。
-
接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。
引用数据类型:数组,类,接口。
接口格式
public interface 接口名称 {
// 抽象方法
// 默认方法
// 静态方法
}
抽象方法
使用abstract修饰,可以省略
public abstract void method();
//public abstract 可以省略
默认方法
使用 default 修饰,不可省略,供子类调用或者子类重写
public default void method1() {
// 执行语句
}
静态方法
使用 static 修饰,供接口直接调用
public static void method2() {
// 执行语句
}
实现
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements 关键字。
非抽象子类实现接口:
- 必须重写接口中所有抽象方法。
- 继承了接口的默认方法,即可以直接调用,也可以重写。
实现格式
class 类名 implements 接口名 {
// 重写接口中抽象方法【必须】
// 重写接口中默认方法【可选】
}
抽象方法的实现
接口中的抽象方法必须全部实现。
//行为接口
public interface Action {
public abstract void run();
}
//猫类
public class Cat implements Action{
@Override
public void run() {
System.out.println("A cat is running");
}
}
//测试类
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.run();
}
}
输出
A cat is running
默认方法的实现
可以继承,可以重写,二选一,但是只能通过实现类的对象来调用。
继承默认方法
继承时,直接调用就可以。
//行为接口
public interface Action {
public default void eat() {
System.out.println("进食");
}
}
//猫类
public class Cat implements Action{
}
//测试类
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.eat();
}
}
输出
进食
重写默认方法
//行为接口
public interface Action {
public default void eat() {
System.out.println("进食");
}
}
//猫类
public class Cat implements Action{
@Override
public void eat() {
System.out.println("吃猫粮");
}
}
//测试类
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.eat();
}
}
输出
吃猫粮
静态方法的实现
静态与.class 文件相关,只能使用接口名调用,不可以通过实现类的类名或者实现类的对象调用。
//行为接口
public interface Action {
public static void die() {
System.out.println("ta死了");
}
}
//猫类
public class Cat implements Action{
}
//测试类
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
//cat.die();// 错误 无法继承方法,也无法调用
Action.die();
}
}
输出
ta死了
接口的多实现
在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。
实现格式
class 类名 [extends 父类名] implements 接口名1,接口名2,接口名3... {
// 重写接口中抽象方法【必须】
// 重写接口中默认方法【不重名时可选】
}
- 接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。
- 接口中,有多个默认方法时,实现类都可继承使用。如果默认方法有重名的,必须重写一次。
- 接口中,存在同名的静态方法并不会冲突,原因是只能通过各自接口名访问静态方法。
优先级问题
当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的默认方法重名,子类就近选择执行父类的成员方法。(只说明默认方法是因作为抽象方法必须要重写,静态方法则是只能使用接口名调用)
//行为接口
public interface Action {
public default void test(){
System.out.println("Action");
}
}
//动物类
public class Animal {
public void test(){
System.out.println("Animal");
}
}
//猫类
public class Cat extends Animal implements Action{
}
//测试类
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.test();
}
}
输出
Animal
接口的多继承
一个接口能继承另一个或者多个接口,这和类之间的继承比较相似。接口的继承使用 extends
关键字,子接口继承父接口的方法。如果父接口中的默认方法有重名的,那么子接口需要重写一次。
public interface A {
public default void method(){
System.out.println("A");
}
}
public interface B {
public default void method(){
System.out.println("B");
}
}
public interface C extends A,B{
@Override
default void method() {
System.out.println("C");
}
}
注意
- 接口中,无法定义成员变量,但是可以定义常量,其值不可以改变,默认使用public static final修饰。
- 接口中,没有构造方法,不能创建对象。
- 接口中,没有静态代码块。