目录
案例3:编程实现Runner接口,提供一个描述奔跑行为的抽象方法。
编程实现Hunter接口继承Runner接口,并提供一个描述捕猎行为的抽象方法。
编程实现Man类实现Hunter接口并重写抽象方法,在main方法中使用多态方式测试。
多态的概念
多态主要值同一种事物表现出来的多种形态。例:饮料:可乐、雪碧、红牛....
多态的语法格式
父类类型 引用变量名 = new 子类类型();
Shape sr = new Rect();
sr.show();
多态的特点
当父类类型的引用指向子类类型的对象时,父类类型的引用可以直接调用父类独有的方法。
当父类类型的引用指向子类类型的对象时,父类类型的引用不可以直接调用子类独有的方法。
当父类类型的引用指向子类类型的对象时,对于父子类都有的非静态方法来说,编译阶段调用父类版本,允许阶段调用子类重写的版本。(动态绑定)
当父类类型的引用指向子类类型的对象时,对于父子类都有的静态方法来说,编译和允许阶段都调用父类版本。
引用数据类型之间的转换
引用数据类型之间的转换方式有两种:自动类型转换和强制类型转换。
自动类型转换主要指从小类型转换为大类型的转换,也就是子类转为父类,也叫做向上转型。
强制类型转换主要指从大类型转换为小类型的转换,也就是父类转为子类,也叫做向下转型或显式类型转换。
引用数据类型之间的转换必须发生在父子类之间,否则编译报错。
若强转的目标类型不是该引用真正指向的数据类型时,则编译通过,运行阶段发生类型转换异常。为了避免上述错误的发生,应该在强制转换之前进行判断,格式如下:
if(引用变量 instanceof 数据类型)-----判断引用变量指向的对象是否为后面的数据类型
多态的实际意义
多态的实际意义在于屏蔽了不同子类的差异性实现通用的编程带来不同的效果。
案例1:(1)编程实现Shape类的封装,特征有:横纵坐标,要求提供打印所有特征的方法。(2)编程实现Rect类的封装并继承自Shape类,特征有:长度和宽度。(3)编程实现ShapeRectTest类,在main方法中分别创建Shape和Rect类型对象并打印特征。
public class Shape {
private int x;
private int y;
public Shape() {
}
public Shape(int x, int y) {
setX(x);
setY(y);
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public void show() {
System.out.println("横坐标:" + x + ",纵坐标:" + y + ".");
}
//自定义静态方法
public static void test() {
System.out.println("Shape类中的静态方法!");
}
}
public class Rect extends Shape {
private int length;
private int width;
public Rect() {
}
public Rect(int x, int y, int length, int width) {
super(x, y);
setLength(length);
setWidth(width);
}
public int getLength() {
return length;
}
public void setLength(int length) {
if(length > 0) {
this.length = length;
} else {
System.out.println("长度不合理!!!");
}
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
if(width > 0) {
this.width = width;
} else {
System.out.println("宽度不合理!!!");
}
}
@Override
public void show() {
super.show();
System.out.println("长度是:" + getLength() + ",宽度是:" + getWidth() + ".");
}
//@Override 错误:不是真正意义上的重写
public static void test() {
System.out.println("Rect类中的静态方法!");
}
}
public class ShapeRectTest {
public static void main(String[] args) {
System.out.println("-----------------------");
//1.声明Shape类型的引用指向shape类型的对象并打印特征
Shape s1 = new Shape(1,2);
//当Rect类没有重写show方法时,下面调用Shape类中的show方法
//当Rect类重写show方法时,下面调用Shape类中的show方法
s1.show();//1 2
System.out.println("-----------------------");
//2.声明Rect类型的引用指向Rect类型的对象并打印特征
Rect r1 = new Rect(3,4,5,6);
//当Rect类没有重写show方法时,下面调用Shape类中的show方法
//当Rect类重写show方法时,下面调用Rect类中的show方法
r1.show();//
System.out.println("-----------------------");
//3.声明Shape类型的引用指向Rect类型的对象并打印特征
//相当于从Rect类型到Shape类型到转换,也就是子类向父类到转换,自动类型到转换
Shape sr = new Rect(7,8,9,10);
//当Rect类没有重写show方法时,下面调用Shape类中的show方法
//当Rect类重写show方法时,在编译阶段调用Shape类的show方法,在允许阶段调用Rect类中的show方法
sr.show();
System.out.println("-----------------------");
//测试Shape类型的引用能否调用父类和子类独有的方法?
int ia = sr.getX();
System.out.println("获取到的横坐标是:" + ia);//获取到的横坐标是:7
//int ib = sr.getLength(); Shape类中找不到getLength方法,也就是还在Shape类中查找
System.out.println("-----------------------");
//调用静态方法 无论是编译还是允许阶段 都用的是父类Shape类中的test方法
sr.test();//提示:不建议使用引用.的方式访问 Shape类中的静态方法!
Shape.test();//推荐使用类名.的方式访问 Shape类中的静态方法!
System.out.println("-----------------------");
//使用父类类型都引用调用子类独有方法的方式
//相当于从Shape类型到Rect类型到转换,也就是父类到子类的(大到小)的转换,强制类型转换。
int ib = ((Rect) sr).getLength();
System.out.println("获取到到长度是:" + ib);//获取到到长度是:9
//希望将Shape类型转换为String类型
//String str1 = (String)sr; 错误
//希望将Shape类型转换为Circle类型
//Circle c1 = (Circle)sr;//编译ok,允许阶段发生ClassCastException类型转换异常
//在强制转换之前使用instanceof进行强制类型转换
//判断sr指向堆区内存的对象是否为circle类型,若是返回true,若不是返回false
if(sr instanceof Circle) {
System.out.println("可以放心的转换了");
} else {
System.out.println("强转有风险");
}
}
}
public class Circle extends Shape {
private int ir;
public Circle() {
}
public Circle(int x, int y, int ir) {
super(x, y);
setIr(ir);
}
public int getIr() {
return ir;
}
public void setIr(int ir) {
if (ir > 0) {
this.ir = ir;
} else {
System.out.println("半径不合理!!!");
}
}
@Override
public void show() {
super.show();
System.out.println("圆形的半径是:" + getIr());
}
}
public class ShapeTest {
//自定义成员方法实现参数指定矩形对象特征打印出来的行为,也就是绘制图形的行为
//用static关键字修饰,使用类名.调用,不用创建对象
/*public static void draw(Rect r) {
r.show();
}
//自定义成员变量实现将参数指定圆形对象打印出来的行为
public static void draw(Circle c) {
c.show();
}*/
//自定义成员方法实现既能实现打印矩形对象特征又能打印圆形对象特征,对象由参数传入 子类 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)
ShapeTest.draw(new Rect(1,2,3,4));
System.out.println("--------------------");
ShapeTest.draw(new Circle(5,6,7));
}
}
抽象方法的概念
抽象方法主要指不能具体实现的方法并且使用abstract关键字修饰,也就是没有方法体。
具体语法格式如下:
访问权限 abstract 返回值类型 方法名(形参列表); public abstract void cry();
抽象类的概念
抽象类主要指不能具体实例化的类并且使用abstract关键字修饰,也就是不能创建对象。
抽象类和抽象方法的关系
抽象类中可以有成员变量、构造方法、成员方法;
抽象类中可以有抽象方法,也可以没有。
拥有抽象方法的类必须是抽象类,因此真正意义上的抽象类应该是具有抽象方法并且使用abstract关键字修饰的类。
抽象类的实际意义
抽象类的实际意义不在于创建对象而在于被继承。
当一个类继承抽象类后必须重写抽象方法,否则该类也变成抽象类,也就是抽象类对子类具有强制性和规范性,因此叫做模板设计模式。
在以后的开发中推荐使用多态的格式,此时父类类型引用直接调用的所有方法一定是父类中拥有的方法,若以后更换子类时,只需要将new关键字后面的子类类型修改而其他地方无需改变就可以立即生效,从而提高了代码的可维护性和可扩展性。该方法的缺点是:父类引用不能直接调用子类独有的方法,若调用则需要强制类型转换。
public abstract class AbstractTest {
private int cnt;
public AbstractTest() {
}
public AbstractTest(int cnt) {
setCnt(cnt);
}
public int getCnt() {
return cnt;
}
public void setCnt(int cnt) {
this.cnt = cnt;
}
//自定义抽象方法
public abstract void show();
public static void main(String[] args) {
//什么该类类型的引用指向该类类型的对象
//AbstractTest at = new AbstractTest(); 抽象类不能实例化
//System.out.println("at.cnt =" + at.cnt);//0
}
}
public class SubAbstractTest extends AbstractTest{
@Override
public void show() {
System.out.println("其实我是被迫重写的,否则也得变成抽象类");
}
public static void main(String[] args) {
SubAbstractTest sat = new SubAbstractTest();
sat.show();//其实我是被迫重写的,否则也得变成抽象类
//声明AbstractTest类型对引用指向子类对象,构成多态
//多态的使用场合之二,直接在方法体中使用抽象类的引用指向子类类型的对象
//推荐使用
AbstractTest at = new SubAbstractTest2();
SubAbstractTest2 sat2 = new SubAbstractTest2();
at.show();//其实我是被迫重写的,否则也得变成抽象类 使用多态方式可以提供代码的可维护性
((SubAbstractTest2) at).test();//第二个子类中独有的方法
sat2.test();// 第二个子类中独有的方法
}
}
public class SubAbstractTest2 extends AbstractTest{
@Override
public void show() {
System.out.println("使用多态方式可以提供代码的可维护性");
}
public void test() {
System.out.println("第二个子类中独有的方法");
}
}
案例2:抽象类的应用
银行有定期账户和活期账户。继承自账户类。
public abstract class Account {
private double money;
public Account() {
}
public Account(double money) {
setMoney(money);
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
if(money >= 0) {
this.money = money;
} else {
System.out.println("金额不合理!!!");
}
}
public abstract double getLixi();
//private abstract double getLixi(); private和abstract关键字不能共同修饰一个方法
//public final abstract double getLixi();final和abstract不能共同修饰一个方法 final不能被重写可以被继承
//public static abstract double getLixi();static abstract不能共同修饰一个方法
}
public class FixedAccount extends Account{
public FixedAccount() {
}
public FixedAccount(int i) {
//使用父类的有参构造方法
super(i);
}
@Override
public double getLixi() {
//利息 = 本金 * 利率 * 时间
return (getMoney() * 0.03 * 1);
}
public static void main(String[] args) {
//1.声明一个Account类型的引用指向子类类型的对象,形成了多态
Account a1 =new FixedAccount(30000);
double ia = a1.getLixi();
System.out.println("利息为" + ia);
}
}
接口的基本概念
接口是一种比抽象类还抽象的类,体现在所有方法都为抽象方法。
定义类的关键字是class,定义接口的关键字是interface.
public interface Metal {
//自定义抽象方法描述发光的行为
public abstract void shine();
}
public interface Money {
//1.自定义抽象方法描述购物行为
public abstract void buy();
}
public class Gold implements Metal,Money {
@Override
public void shine() {
System.out.println("发出了金黄色的光芒...");
}
@Override
public void buy() {
System.out.println("买了好多好吃的...");
}
public static void main(String[] args) {
//1.声明接口类型的引用指向实现类的对象,形成多态
Metal mt = new Gold();
mt.shine();//发出了金黄色的光芒...
Money mn = new Gold();
mn.buy();//买了好多好吃的...
}
}
案例3:编程实现Runner接口,提供一个描述奔跑行为的抽象方法。
编程实现Hunter接口继承Runner接口,并提供一个描述捕猎行为的抽象方法。
编程实现Man类实现Hunter接口并重写抽象方法,在main方法中使用多态方式测试。
public interface Runner {
//自定义一个抽象方法描述奔跑的行为
public abstract void run();
}
public interface Hunter extends Runner{
public abstract void hunt();
}
public class Man implements Hunter {
@Override
public void hunt() {
System.out.println("正在追捕一只小白兔...");
}
@Override
public void run() {
System.out.println("正在被一只大熊追赶,玩命跑跑跑...");
}
public static void main(String[] args) {
//声明接口类型的引用指向实现类的对象,形成多态
Runner runner = new Man();
runner.run();//正在被一只大熊追赶,玩命跑跑跑...
Hunter hunter = new Man();
hunter.hunt();//正在追捕一只小白兔...
}
}
类与接口之间的关系
名称 | 关键字 | 关系 |
---|---|---|
类与类 | extends表达继承 | 支持单继承 |
类与接口 | implements表达实现 | 支持多实现 |
接口与接口 | extends表达继承 | 支持多继承 |
接口与抽象类的主要区别
定义抽象类的关键字是abstract class,定义接口的关键字是interface。
继承抽象类的关键字是extends,实现接口的关键字是implements。
继承抽象类支持单继承,实现接口支持多实现。
抽象类中可以有构造方法,接口中不可以有构造方法。
抽象类中可以有成员变量,接口中只能有常量。
抽象类中可以有成员方法,接口中只能有抽象方法。
抽象类中增加方法时子类可以不用重写,而接口中增加方法时实现类需要重写。(java8以前)
从java8开始增加新特性,接口中允许出现非抽象方法和静态方法,但非抽象方法需要使用default(默认)关键字修饰。增加静态方法,属于接口层级。
从java9开始增加新特性,接口中允许出现私有方法。