一、多态(重中之重)
1、多态的语法格式
•父类类型引用变量名= new 子类类型();
•如:
Shape sr= new Rect();
sr.show();
2、案例题目
•编程实现Shape类的封装,特征有:横纵坐标,要求提供打印所有特征的方法。
•编程实现Rect类的封装并继承自Shape类,特征有:长度和宽度。
•编程实现ShapeRectTest类,在main方法中分别创建Shape和Rect类型对象并打印特征。
// 1.声明Shape类型的引用指向Shape类型的对象并打印特征 Shape s1 = new Shape(1, 2); // 当Rect类中没有重写show方法时,下面调用Shape类中的show方法 // 当Rect类中重写show方法后,下面调用Shape类中的show方法 s1.show(); // 1 2 // 使用ctrl+d快捷键可以复制当前行 System.out.println("------------------------------------"); // 2.声明Rect类型的引用指向Rect类型的对象并打印特征 Rect r1 = new Rect(3, 4, 5, 6); // 当Rect类中没有重写show方法时,下面调用Shape类中的show方法 // 当Rect类中重写show方法后,下面调用Rect类中的show方法 r1.show(); // 3 4 5 6 // 使用alt+shift+上下方向键 可以移动代码 System.out.println("------------------------------------"); // 3.声明Shape类型的引用指向Rect类型的对象并打印特征 // 相当于从Rect类型到Shape类型的转换 也就是子类到父类的转换 小到大的转换 自动类型转换 Shape sr = new Rect(7, 8, 9, 10); // 当Rect类中没有重写show方法时,下面调用Shape类中的show方法 // 当Rect类中重写show方法后,下面的代码在编译阶段调用Shape类的方法,在运行阶段调用Rect类中的show方法 sr.show(); // 7 8 9 10
运行截图:
3、多态的特点
• 当父类类型的引用指向子类类型的对象时,父类类型的引用可以直接调用父类独有的方法。
• 当父类类型的引用指向子类类型的对象时,父类类型的引用不可以直接调用子类独有的方法。
• 对于父子类都有的非静态方法来说,编译阶段调用父类版本,运行阶段调用子类重写的版本(动态绑定)。
• 对于父子类都有的静态方法来说,编译和运行阶段都调用父类版本
4、引用数据类型之间的转换
• 引用数据类型之间的转换方式有两种:自动类型转换 和 强制类型转换。
• 自动类型转换主要指小类型向大类型的转换,也就是子类转为父类,也 叫做向上转型。
• 强制类型转换主要指大类型向小类型的转换,也就是父类转为子类,也 叫做向下转型或显式类型转换。
• 引用数据类型之间的转换必须发生在父子类之间,否则编译报错
• 若强转的目标类型并不是该引用真正指向的数据类型时则编译通过,运 行阶段发生类型转换异常。
(见ShapeRectTest .java 的 49行)
• 为了避免上述错误的发生,应该在强转之前进行判断,格式如下: if(引用变量 instanceof 数据类型) 判断引用变量指向的对象是否为后面的数据类型
5、多态的实际意义
• 多态的实际意义在于屏蔽不同子类的差异性实现通用的编程带来不同的 效果
编译阶段调用父类的版本,运行阶段调用子类重写以后的版本
如图18行
package com.lagou.task09; public class ShapeTest { // 自定义成员方法实现将参数指定矩形对象特征打印出来的行为,也就是绘制图形的行为 // Rect r = new Rect(1, 2, 3, 4); // public static void draw(Rect r) { // r.show(); // 1 2 3 4 // } // 自定义成员方法实现将参数指定圆形对象特征打印出来的行为 // public static void draw(Circle c) { // c.show(); // 5 6 7 // } // 自定义成员方法实现既能打印矩形对象又能打印圆形对象的特征,对象由参数传入 子类 is a 父类 // Shape s = new Rect(1, 2, 3, 4); 父类类型的引用指向子类类型的对象,形成了多态 // Shape s = new Circle(5, 6, 7); 多态 // 多态的使用场合一:通过参数传递形成了多态 public static void draw(Shape s) { // 编译阶段调用父类的版本,运行阶段调用子类重写以后的版本 s.show(); } public static void main(String[] args) { // Rect r = new Rect(1, 2, 3, 4); // r.show(); ShapeTest.draw(new Rect(1, 2, 3, 4)); ShapeTest.draw(new Circle(5, 6, 7)); } }
运行截图:
package com.lagou.task09;
public class ShapeRectTest {
public static void main(String[] args) {
// 1.声明Shape类型的引用指向Shape类型的对象并打印特征
Shape s1 = new Shape(1, 2);
// 当Rect类中没有重写show方法时,下面调用Shape类中的show方法
// 当Rect类中重写show方法后,下面调用Shape类中的show方法
s1.show(); // 1 2
// 使用ctrl+d快捷键可以复制当前行
System.out.println("------------------------------------");
// 2.声明Rect类型的引用指向Rect类型的对象并打印特征
Rect r1 = new Rect(3, 4, 5, 6);
// 当Rect类中没有重写show方法时,下面调用Shape类中的show方法
// 当Rect类中重写show方法后,下面调用Rect类中的show方法
r1.show(); // 3 4 5 6
// 使用alt+shift+上下方向键 可以移动代码
System.out.println("------------------------------------");
// 3.声明Shape类型的引用指向Rect类型的对象并打印特征
// 相当于从Rect类型到Shape类型的转换 也就是子类到父类的转换 小到大的转换 自动类型转换
Shape sr = new Rect(7, 8, 9, 10);
// 当Rect类中没有重写show方法时,下面调用Shape类中的show方法
// 当Rect类中重写show方法后,下面的代码在编译阶段调用Shape类的方法,在运行阶段调用Rect类中的show方法
sr.show(); // 7 8 9 10
System.out.println("------------------------------------");
// 4.测试Shape类型的引用能否直接调用父类和子类独有的方法呢???
int ia = sr.getX();
System.out.println("获取到的横坐标是:" + ia); // 7
//sr.getLen(); error Shape类中找不到getLen方法,也就是还在Shape类中查找
// 调用静态方法
sr.test(); // 提示:不建议使用引用.的方式访问
Shape.test(); // 推荐使用类名.的方式访问
System.out.println("------------------------------------");
// 5.使用父类类型的引用调用子类独有方法的方式
// 相当于从Shape类型到Rect类型的转换,也就是父类到子类的转换 大到小的转换 强制类型转换
int ib = ((Rect) sr).getLen();
System.out.println("获取到的长度是:" + ib); // 9
// 希望将Shape类型转换为String类型 强制类型转换要求必须拥有父子类关系
//String str1 = (String)sr; Error
// 希望将Shape类型强制转换为Circle类型,下面没有报错
//Circle c1 = (Circle)sr; // 编译ok,但运行阶段发生 ClassCastException类型转换异常
// 在强制类型转换之前应该使用instanceof进行类型的判断
// 判断sr指向堆区内存中的对象是否为Circle类型,若是则返回true,否则返回false
if(sr instanceof Circle) {
System.out.println("可以放心地转换了!");
Circle c1 = (Circle)sr;
} else {
System.out.println("强转有风险,操作需谨慎!");
}
}
}
二、抽象方法和抽象类(重点)
1、抽象方法的概念
• 抽象方法主要指不能具体实现的方法并且使用abstract关键字修饰,也就 是没有方法体。
• 具体格式如下:
访问权限 abstract 返回值类型 方法名(形参列表); public abstract void cry();
•抽象类主要指不能具体实例化的类并且使用abstract关键字修饰,也就是不能创建对象
2、抽象类和抽象方法的关系
• 抽象类中可以有成员变量、成员方法、构造方法;
• 抽象类中可以没有抽象方法,也可以有抽象方法;
• 拥有抽象方法的类必须是抽象类,因此真正意义上的抽象类应该是具有 抽象方法并且使用abstract关键字修饰的类
3、抽象类的实际意义
• 抽象类的实际意义不在于创建对象而在于被继承。
• 当一个类继承抽象类后必须重写抽象方法,否则该类也变成抽象类,也就是抽象类对子类具有强制性和规范性,因此叫做模板设计模式
子类要么是抽象类要么有抽象方法,否则无法继承。
4、开发经验分享
• 在以后的开发中推荐使用多态的格式,此时父类类型引用直接调用的所 有方法一定是父类中拥有的方法,若以后更换子类时,只需要将new关键字后面的子类类型修改而其它地方无需改变就可以立即生效,从而提高了代码的可维护性和可扩展型。
• 该方式的缺点就是:父类引用不能直接调用子类独有的方法,若调用则 需要强制类型转换
5、抽象类的应用
• 银行有 定期账户和活期账户。继承自 账户类。账户类中:
• public class Account{ private double money; public double getLixi(){}
6、抽象类笔试考点
1、private 和abstract 关键字不能共同修饰一个方法
子类不能继承父类的只有两个:一个是私有的,一个是成员方法。
2、final修饰的方法不能被重写可以被继承。
abstract就是指望着重写
3、抽象类不能new对象,抽象类没有方法体。static相当于把抽象方法提升成类层级,就可以用类.的方式访问了,抽象类失去了意义。
4、一个抽象的类在于被继承,final修饰的类就不能被继承。
5、抽象类可以有静态方法,static定义
public /*final*/ abstract class Account {
private int money;
public Account() {
}
public Account(int money) {
setMoney(money);
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
if (money >= 0) {
this.money = money;
} else {
System.out.println("金额不合理哦!!!");
}
}
public static void jtfangfa(){} //抽象类可以定义static方法
// 自定义抽象方法实现计算利息并返回的功能描述
public abstract double getLixi();
// private 和 abstract 关键字不能共同修饰一个方法
//private abstract double getLixi();
// final 和 abstract 关键字不能共同修饰一个方法
//public final abstract double getLixi();
// static 和 abstract 关键字不能共同修饰一个方法
//public static abstract double getLixi();
}
三、接口(重点)
1、接口的基本概念
• 接口就是一种比抽象类还抽象的类,体现在所有方法都为抽象方法。
• 定义类的关键字是class,而定义接口的关键字是interface。
• 如: 金属接口 货币接口 黄金
a、只能有常量
b、java9开始允许接口中出现私有方法
代码如下:
public interface InterfaceTest {
/*public static final*/ int CNT = 1; //里面只能有常量
// private void show(){} //从java9开始允许接口中出现私有方法
/*public abstract */ void show(); //里面只能有抽象方法,新特性除外,注释中的关键字可以省略,但建议写上
}
2、练习题目
• 编程实现Runner接口,提供一个描述奔跑行为的抽象方法。
• 编程实现Hunter接口继承Runner接口,并提供一个描述捕猎行为的抽象 方法。
• 编程实现Man类实现Hunter接口并重写抽象方法,在main方法中使用多 态方式测试
1)Runner接口
public interface Runner { //自定义抽象方法奔跑的行为 public abstract void run(); }
2) Hunter类
package com.lagou.task09;
//接口只能继承接口,不能继承类
public interface Hunter extends Runner {
//自定义成员方法捕猎的行为
public abstract void hunt();
//在两个默认的方法中重复的代码可以提取出来打包成一个方法,在下面两个方法中分别调用即可,减少代码冗余
//该方法只是本类中使用,不需要其他类调用,就可以private JAVA9之后新特性,接口可以写private方法,减少代码冗余
//如果是静态方法也可以提供Private static的方法提供静态方法调用
/*public default*/private void show(){
System.out.println("在以后的开发过程中应减少代码的冗余!");
}
//JAVA8之后新增public default定义方法,可以提供实现类自由选择是否重写该方法
public default void show1(){
show();
//System.out.println("在以后的开发过程中应减少代码的冗余!");
System.out.println("在show1方法中:这里仅仅是接口中默认的功能,子类可以自由选择是否重写!");
}
public default void show2(){
show();
//System.out.println("在以后的开发过程中应减少代码的冗余!");
System.out.println("在show2方法中:这里仅仅是接口中默认的功能,子类可以自由选择是否重写!");
}
//JAVA8之后还可以增加静态方法,这里是接口层级,不违背以前不能new对象的方式
public static void test(){
System.out.println("这里是静态方法,可以直接通过接口名.的方式调用!");
}
}
3)Man类
package com.lagou.task09;
public class Man implements Hunter {
@Override
public void hunt(){
System.out.println("正在追赶一只小白兔");
}
@Override
public void run(){
System.out.println("正在被一只大熊追赶,玩命奔跑中");
}
@Override
public void show1(){
System.out.println("为了给你几分薄面,我就是想重写这个默认的功能");
}
public static void main(String[] args) {
//1.声明接口类型的引用指向实现类的对象,实现了多态
Runner run = new Man();
Hunter hunter = new Man() ;
run.run();
hunter.hunt();
System.out.println("---------------------------------------");
//2.可以使用接口类.的方式调用方法
Hunter.test();
}
}
3、类和接口之间的关系
名称 | 关键字 | 关系 |
类和类之间的关系 | 使用extends关键字实现继承 | 支持单继承 |
类和接口之间的关系 | 使用implements关键字表达实现关系 | 支持多实现 |
接口和接口之间的关系 | 使用extends关键字实现继承 | 支持多继承 |
4、抽象类和接口的主要区别(笔试题)
•定义抽象类的关键字是abstract class,而定义接口的关键字是interface。
•继承抽象类的关键字是extends,而实现接口的关键字是implements。
•继承抽象类支持单继承,而实现接口支持多实现。
•抽象类中可以有构造方法,而接口中不可以有构造方法。
•抽象类中可以有成员变量,而接口中只可以有常量。
5、抽象类和接口的主要区别
•抽象类中可以有成员方法,而接口中只可以有抽象方法。
•抽象类中增加方法时子类可以不用重写,而接口中增加方法时实现类需要重写(Java8以前的版本)。
•从Java8开始增加新特性,接口中允许出现非抽象方法和静态方法,但非抽象方法需要使用default关键字修饰。
•从Java9开始增加新特性,接口中允许出现私有方法。