面向对象—多态、接口
1.多态
-
概念:指的是一种实物,在不用时刻,所表现出的不同状态。
-
多态的前提:要有继承,如果没有继承,那多态也无从谈起
-
多态要有方法重写,如果不重写,语法上来说不报错,但是多态的存在就没有意义了
-
父类引用指向子类对象,举例:
Animal an = new Dog(); //这种写法才是多态!
2.多态的形式访问成员变量的特点:
- 多态的形式访问成员变量:编译看左边,运行看左边
Fu fu = new Zi();
system.out.println(fu.num) //编译看左边,运行看左边
//打印的是父类定义的变量值
//编译看左边的意思是:编译器检查报错
//运行看右边的意思是:运行代码的时候,到底运行的是谁的代码
- 多态的形式访问成员方法的特点:编译看左边,运行看右边(子类重写了父类的方法)
public class Test {
public static void main(String[] args) {
// Zi zi = new Zi();
// zi.show();
// System.out.println("这是主方法!");
// 父类引用指向子类对象:
// 左(父类) 右(子类)
Fu fu = new Zi();
((Zi) fu).show2(); //这样就不会报错!(这样感觉就成了普通的继承,并不是多态!)
//fu.show2(); //编译报错!(编译看左边,左边是父类,但父类没有相应的show2方法)
Fu fu2 = new Zi2();
fu2.show(); //运行的是右边的结果,即:子类的结果
}
}
class Fu{
public int age =100;
public void show (){
System.out.println(age);
System.out.println("这是父类的方法!");
}
}
class Zi extends Fu{
//public int age;
@Override //注解符
public void show() {
super.show();
System.out.println(age);
System.out.println("这是子类的方法!");
}
public void show2(){
System.out.println("这是Zi的show2方法");
}
}
class Zi2 extends Fu{
@Override
public void show() {
//super.show();
System.out.println("这是Zi2的show方法!");
}
}
----------------------------------------
输出:
这是Zi的show2方法
这是Zi2的show方法!
-
使用多态的好处:
- 1.提高了代码的复用性,是靠继承保证的。
- 2.提高了代码的扩展性(详情可以参考老师上课做得工具类的示例!!!,即:在工具类的类方法中的形参的类型用父类来声明,传递实际参数的时候,用子类对象来代表实际参数,即:造成"父类引用指向子类对象的效果"!)
- 3.高内聚,低耦合;
- 4.接口性;
- 5.灵活性;
- 6.简化性
-
测试类:只需要提供一个入口即可,不需要在测试类中写太多的类方法,即:测试类中最好只有一个main函数即可。
-
多态的弊端:
-
多态的形式,去访问子类特有的方法,是调用不到的!(必须利用向下类型转换来访问子类特有的方法!)
- 如果想要访问,我们可以向下转型
Father father = new Zi(); //向上转型! Zi zi = (Zi) father; //向下转型!
- 向下转型可能会遇到类型转换异常的情况:java.lang.ClassCastException(见老师上课演示)
原因就是:其实写成了子类和子类之间的相互转换,这是不能转换的!只能把向上转型过的子类对象再向下回转(型)为原子类对象,不能把向上转型过的子类对象向下回转(型)为另一个子类对象(同属于一个父类的另一个子类)
示例如下:
package westos.org.newtest; public class Test { public static void main(String[] args) { Cat cat = new Cat(); Animal an = cat; //多态:向上转换! Cat cat1 = (Cat) an; // 向下转换! //Tiger tiger1 = (Tiger)an;(编译器不报错,但是运行的时候会报出类型转换异常的错误!) //上下两者是等价的 //Tiger tiget1 = (Tiger)cat;(编译器直接报错,说明两个不同的子类是不能相互转换的!) Tiger tiger = new Tiger(); //新建立一个父对象,然后测试它能否被直接转换为子类对象(说明父类对象不能被强制转换为子类对象,子类指针不能指向父类对象(除非是向上转换过以后的!),但是父类对象可以指向子类对象(多态)) Animal an1 = new Animal(); Cat cat2 = (Cat)an1;//报错(类型转换异常)java.lang.ClassCastException westos.org.newtest.Animal cannot be cast to westos.org.newtest.Cat cat2.eat(); } } class Animal { String name; public void eat(){ System.out.println(name+"这是Animal的eat方法!"); } } class Cat extends Animal{ String name = "小猫咪"; @Override public void eat() { System.out.println(this.name +"爱吃小鱼干!"); } } class Tiger extends Animal { String name = "小老虎"; @Override public void eat() { System.out.println(name+"爱吃小猫咪!"); } }
- 多态本身就是“向上转型!”
-
-
多态(向上,向下类型转换)的一个非常经典的示例:课堂上讲述的**“孔子装爹”**案例!
-
动态绑定(在上课讲授多态情况下的内存图总提到的!)
- 向上转换的时候,指针实际指向的区域是堆中子类对象中的一块super区域(初始化子类过程中初始化的父类对象),所以应用变量的时候它会直接取父对象的变量,但是在应用其方法的时候,会进行动态绑定,看子类中是否有定义的同名方法,如果有的话,则执行子类方法,否则,则执行父类方法。
2.抽象类
-
abstract 关键字 :抽象的,可以修饰类,修饰方法!
- 被abstract修饰过的类(则,该类变成抽象类),此类就不能直接实例化了
- 在抽象类中用abstract 修饰一个方法,则继承该抽象类的子类必须重写这个方法!
-
特点:
- 一个类,里面有了抽象方法,那么这个类,必须为抽象类
- 一个抽象类里面,可以没有抽象方法!(如果有方法,则不一定全都为抽象方法!)
- 抽象类中可以有非抽象方法
- 抽象类里面有没有构造方法?有
- 采用多态的方式来完成父类数据的初始化(已证实:可以)
public class Test { public static void main(String[] args) { Animal an1 = new Cat(); System.out.println(an1.age); System.out.println(((Cat) an1).name); an1.eat(); an1.sleep(); ((Cat)an1).catchMouse(); } } ------------------------------------------- 输出: 抽象类初始化已经调用! Cat类初始化函数已经被调用! 1 小猫咪 10岁的小猫咪要吃小鱼干! 10岁的小猫咪在白天睡觉! 10岁的小猫咪会抓老鼠!
public abstract class Animal { int age; String name; Animal(){ System.out.println("抽象类初始化已经调用!"); age = 1; name = "动物"; } //定义吃饭的抽象方法:(具体由子类自己来实现) public abstract void eat(); //定义睡觉的抽象方法:(具体由子类自己来实现) public abstract void sleep(); }
package westos.org.test2; public class Cat extends Animal{ int age; String name; Cat(){ System.out.println("Cat类初始化函数已经被调用!"); age = 10; name ="小猫咪"; } @Override public void eat() { System.out.println(age+"岁的"+name+"要吃小鱼干!"); } @Override public void sleep() { System.out.println(age+"岁的"+name+"在白天睡觉!"); } public void catchMouse(){ System.out.println(age+"岁的"+name+"会抓老鼠!"); } }
package westos.org.test2; public class Dog extends Animal { String name = "小狗"; int age = 10; @Override public void eat() { System.out.println(age+"岁的"+name+"要啃骨头!"); } @Override public void sleep() { System.out.println(age+"岁的"+name+"在晚上睡觉!"); } public void watchDoor(){ System.out.println(age+"岁的"+name+"会看门!"); } }
- 抽象类的构造函数调用必须依靠多态?(个人认为单纯的继承也可以!已证实:可以)
public class Test { public static void main(String[] args) { Cat cat1 = new Cat(); cat1.eat(); cat1.sleep(); cat1.sleep(); } } ------------------------------------------------------- 输出: 抽象类初始化已经调用! Cat类初始化函数已经被调用! 10岁的小猫咪要吃小鱼干! 10岁的小猫咪在白天睡觉! 10岁的小猫咪在白天睡觉!
- 抽象类成员变量的特点:1.可以定义成员变量,也可以定义常量。
- abstract 不能修饰变量;
-
抽象类,既可以定义抽象方法,也可以定义非抽象方法
- 抽象方法:它是强制子类必须重写
- 非抽象方法: 一般就是让你继承袭来继续使用,但子类也可以重写非抽象类的方法!
-
一个类如果没有抽象方法,可不可以定义为抽象类?如果可以,有什么意义?
- 可以,外界不能创建该类的对象!(抽象类不能直接初始化,只能依靠子类间接初始化)
-
abstract 在方法中使用,能不能和下面的关键字一起使用?
- private abstract 两者互相矛盾,abstract 强制子类重写 ,private 限定后又无法继承(更不能重写了!)
- final abstract 两者互相矛盾, abstract 强制子类重写,final 修饰的也没法重写
- abstract static 两者也互相矛盾,abstract 强制子类重写,static 修饰过后的方法属于类方法,谈不上重写!(重写:是先造出父类的对象,再造出子类的对象,然后用子类对象与父类对象的同名方法去对父类的方法进行覆盖,但是static声明的方法属于类,不需要实例化即可使用,也就谈不上实例化的继承和覆盖了!,参考内存图!)
- 引出了一个问题:继承和static的关系:(待补充!)
-
IEDA 神器插件-----------> 彩虹括号(方便辨别!)
3.接口(注意区分接口和继承的区别!)
- 概念:用来定义实物的一些额外的扩展功能,将来那类实物,想要具备,这些额外的功能,就可以实现这个接口
- 定义接口的语法 interface 接口名{}
- 类 implements 接口 (这是一种实现关系!)
- 这个类就是这个接口的子类,这个接口 可以叫做这个类的父接口
接口的特点:
-
接口不能直接实例化
-
接口没有构造方法这一说
-
接口的子类有什么要求:
- 1.要求子类必须重写子类中所有的抽象方法
- 2.如果你不想重写,你这个类可以作为一个抽象类,将接口中的抽象方法继承下来即可(一般不推荐这么做,没啥意义!)
-
接口中的方法存在默认修饰符 public abstract
示例如下:
interface AA{ public abstract void aa(); void aa(); //这个与上面其实是一样的 }
接口中的成员特点:
- 接口中没有构造方法!
- 接口中的成员变量:
- 接口中的成员变量全是公共的静态常量
- 接口中的成员变量前面存在默认修饰符 public static final
- 接口中的成员变量如何使用?
- 接口中的成员方法:
- 接口中的方法存在默认修饰符 public abstract
public class Test {
public static void main(String[] args) {
MyInterface interface1 = new Student();//多态
System.out.println(interface1.num);//多态下的成员变量访问规则:编译看左边,运行看左边
Student student = new Student();//普通继承
System.out.println(student.num);
}
}
interface MyInterface{
int num = 20;
public abstract void show();
}
class Student implements MyInterface{
int num = 10;
@Override
public void show() {
System.out.println("子类的show方法!");
}
}
----------------------------
输出:
20
10
类,接口之间的关系:
-
类与类的关系:继承关系 extends Java中只支持单继承
-
类与接口的关系:实现关系 implements 可以多实现 一个类可以实现多个接口
class MyClass implements AA,BB,CC{ }
-
接口与接口之间的关系:继承关系 extends 支持多继承 一个接口可以继承多个接口
interface CCC extends AAA,BBB{ }
-
一个类在继承一个类的同时,也可以实现多个接口
class CCC extends AAA implemens BBB,DDD,EEE{ }
-
JDK 1.8 允许接口的方法有功能上的实现,但这个方法用default来修饰
- JAVA用接口上的多实现弥补了JAVA类不支持多继承的弊端。