
抽象类的概念
- 抽象的产生
需求:创建类描述猫和狗;
猫:颜色;名字;品种; 吃;叫;抓老鼠;
狗:颜色;名字;品种; 吃;叫;看家;
- 老的实现方式:
//创建类描述狗
class Dog {
private String color;// 颜色
private String name;// name
private String pinZhong;// 品种
public Dog() {
}
public Dog(String color, String name, String pinZhong) {
this.color = color;
this.name = name;
this.pinZhong = pinZhong;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPinZhong() {
return pinZhong;
}
public void setPinZhong(String pinZhong) {
this.pinZhong = pinZhong;
}
// 吃
public void eat() {
System.out.println("吃骨头!");
}
// 叫
public void say() {
System.out.println("汪汪");
}
// 看家
public void lookHome() {
System.out.println("看家");
}
}
- 创建类描述猫
class Cat {
private String color;// 颜色
private String name;// 名字
private String pinZhong;// 品种
public Cat() {
}
public Cat(String color, String name, String pinZhong) {
this.color = color;
this.name = name;
this.pinZhong = pinZhong;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPinZhong() {
return pinZhong;
}
public void setPinZhong(String pinZhong) {
this.pinZhong = pinZhong;
}
// 猫的吃
public void eat() {
System.out.println("吃小鱼!");
}
// 猫的叫
public void say() {
System.out.println("喵喵喵");
}
// 抓老鼠
public void catchMouse() {
System.out.println("抓老鼠");
}
}
思考:这两个类中存在大量相同代码,每个类都要重新书写一遍,太麻烦。可以考虑使用继承技术来简化代码;
因为猫和狗之间没有 是 的关系,所以不能相互继承;
所以要找一个共同的父类,将猫和狗的共同代码都抽取到父类中;
因为猫和狗都 是 动物,所以可以定义一个动物类,将相同代码抽取到动物类中,然后让猫和狗都继承这个动物类;
/*
* 需求:创建类描述猫和狗;
猫:颜色;名字;品种; 吃;叫;抓老鼠;
狗:颜色;名字;品种; 吃;叫;看家;
*/
//创建一个共同的父类动物
abstract class Animal {
private String color;// 颜色
private String name;// name
private String pinZhong;// 品种
public Animal() {
}
public Animal(String color, String name, String pinZhong) {
this.color = color;
this.name = name;
this.pinZhong = pinZhong;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPinZhong() {
return pinZhong;
}
public void setPinZhong(String pinZhong) {
this.pinZhong = pinZhong;
}
// 吃
public abstract void eat();
// 叫
public abstract void say();
}
创建类描述狗
class Dog extends Animal {
public Dog() {
}
public Dog(String color, String name, String pinZhong) {
super(color, name, pinZhong);
}
// 吃
public void eat() {
System.out.println("吃骨头!");
}
// 叫
public void say() {
System.out.println("汪汪");
}
// 看家
public void lookHome() {
System.out.println("看家");
}
}
//创建类描述猫
class Cat extends Animal {
public Cat() {
}
public Cat(String color, String name, String pinZhong) {
super(color, name, pinZhong);
}
// 猫的吃
public void eat() {
System.out.println("吃小鱼!");
}
// 猫的叫
public void say() {
System.out.println("喵喵喵");
}
// 抓老鼠
public void catchMouse() {
System.out.println("抓老鼠");
}
}
新的问题:
在不同的子类中有相同的方法,但是方法的具体实现不同;因为是相同的方法,是共有的功能,所以应该抽取到共同的父类中;
但是又因为不同的子类具体实现不同,所以再父类中没办法给这些共同的方法提供一个具体的实现;
像这种方法,就应该使用新的技术:抽象函数来描述;
抽象函数与抽象类
- 抽象函数:
当一个类中,知道有某个功能,但是不确定这个功能该如何实现,就应该将这个方法定义为抽象函数;表示描述不清的功能;
- 书写格式:
抽象函数使用abstract关键字描述,直接写在函数的返回值类型前面;而且抽象函数没有函数体代码,连大括号都不能写;

- 抽象类:
当一个类中存在抽象函数时,就表示这个类描述不清楚,这个类也应该定义为抽象类;
- 书写格式:
抽象类也是用abstract关键字描述,直接写在class关键字前面;

结论:
当多个不能相互继承的类具有相同的功能时,就需要将共同的信息向上抽取,放到公共的父类中;如果公共的父类只能描述所有子类都具有的功能,但描述不清功能的具体实现,就需要将该函数定义为抽象的,使用关键字abstract修饰;
如果一个类中出现了抽象的函数,说明这个类也是不具体的,应该定义为抽象类,使用abstract修饰;

抽象类的使用
抽象类不能实例化,只能由子类继承
抽象类不能创建对象,因为抽象类中有抽象的函数;如果可以创建对象,就可以通过对象调用抽象的函数;而调用抽象的函数是没有意义的;

子类继承抽象父类,必须实现父类的所有抽象函数,否则子类也是抽象的
子类继承抽象父类,子类中就把父类的抽象函数也继承下来了;如果不重写这些抽象函数,就说明子类中也有抽象函数,所以子类就要定义为抽象类;
如果子类需要被实例化,就不能定义为抽象类,就不能有抽象函数,就必须在子类中将从父类继承下来的抽象函数重写一下;

抽象类的使用方法
一般开发中,会先定义共同的父类,然后根据具体需求,去书写子类;
如果有些功能在开始不确定如何书写,那么就应该定义为抽象函数,交给子类去实现;
需求:编写程序,模拟手机使用网络信号(NetworkSignal)打电话的功能;;
注意:联通手机(UnicomMobile)和移动手机(MobilePhone)使用的网络不同,所以具体实现也会有不同;
public class AbstractTest {
public static void main(String[] args) {
new UnicomMobile().call("18888888888");
new MobilePhone().call("18666886688");
}
}
/*
* 需求:编写程序,模拟手机使用网络信号(NetworkSignal)打电话的功能;;
* 注意:联通手机(UnicomMobile)和移动手机(MobilePhone)使用的网络不同,所以具体实现也会有不同;
*/
abstract class Phone {
// 定义一个获取网络的方法
public abstract String getNet();
public void call(String num) {
String net = getNet();
System.out.println("使用" + net + "网络,给" + num + "打电话!");
}
}
// 定义一个移动手机,可以使用移动网络
class MobilePhone extends Phone {
public String getNet() {
return "移动";
}
}
// 定义一个联通手机,可以使用联通网络
class UnicomMobile extends Phone {
public String getNet() {
return "联通";
}
}

抽象类的细节问题
1、一个类是抽象类,那么这个类不能创建对象。
因为如果类是抽象类,类中就有可能存在抽象方法,由于抽象方法是没有方法体。那么如果这个类可以创建对象,
那么就可能会调用到这个类中的抽象方法,那么抽象方法没有方法体,调用这样的方法根本就没有任何的意义。
2、抽象类中有没有构造函数?
有,虽然抽象类不能创建对象,但是它一定会子类,而子类中的构造函数中的隐式super会找父类的构造函数。
所以抽象类中的构造函数不是给自己用的,是给子类创建对象时候使用的。
3、抽象类一定是父类吗?
抽象肯定是父类,因为它必须有子类去复写其中的抽象函数。但不一定是最顶层的父类。
4、抽象类中可以没有抽象方法吗?
可以。如果一个类中一个抽象方法都没有,但是这个类却是抽象类,目的只有一个,就是不让创建这个类的对象。
这种用法主要用在适配器设计模式中。
5、abstract关键字不能和哪些关键字共存?
final:被final修饰的类不能被继承,被final修饰的方法不能被复写。但是abstract修饰的必须要求继承或复写。
private:被private修饰的函数,子类是根本继承不到的。但是abstract修饰的函数要求子类必须复写。
static:静态修饰的方法可以直接通过类名调用。但是abstract修饰的方法根本就没有方法体。调用就没有任何的意义。
- 一个类什么时候需要定义为抽象类?
- 类中存在抽象函数;
- 如果一个类中没有抽象函数,但是不希望被实例化,也需要定义为抽象类;

抽象类总结
- 抽象类的概念:
是使用关键字abstract修饰的类就是抽象类;
- 抽象类的产生:
当多个不能相互继承的类具有相同的功能时,就需要将共同的信息向上抽取,放到公共的父类中;如果公共的父类只能描述所有子类都具有的功能,但描述不清功能的具体实现,就需要将该函数定义为抽象的,使用关键字abstract修饰;如果一个类中出现了抽象的函数,说明这个类也是不具体的,应该定义为抽象类,使用abstract修饰;
- 抽象类的特点:
抽象类不能实例化;
抽象类中的抽象函数必须由子类去实现,否则子类也是抽象类;
抽象类有构造函数,是供子类创建对象时使用的;
因为抽象函数必须由子类实现,所以不参与继承的(private)和不能被子类重写的(final)关键字不能和abstract共存;
因为静态函数不需要类的对象就可以直接使用,所以static关键字也不能和abstract共存;
抽象类中可以书写所有成员,也可以没有抽象函数;
如果一个类不希望被实例化,但又希望被子类继承,就可以定义为抽象的,即使类中没有抽象函数;

多态
- 问题的引入
多态:就是同一个事物具有多种不同的表示方式;

狗;旺财;哈士奇;……
在现实生活中,我们一般更倾向于使用表示范围更广的概念描述这个具体的事物;
衣服:棉衣;羽绒服;毛衣……
水果:苹果、桔子、梨子、香蕉……

多态的写法

Java中,多态指的是:父类型引用指向子类对象;

(接口类型引用指向实现类对象)

- 多态的好处
生活中使用多态,可以指代更广泛的范围;
在Java中,使用多态,也可以使变量的表示范围更广泛,可以提高代码的复用性和程序的扩展性(降低耦合性);
需求:描述并测试猫和狗的行为;
猫具有吃、叫和抓老鼠的功能;
狗具有吃、叫和看家的功能;
过去的做法:
public class Demo2 {
public static void main(String[] args) {
// 创建一只猫
Cat c = new Cat();
// 调用函数测试猫的行为
test(c);
// 创建一条狗
Dog d = new Dog();
// 调用函数测试狗的行为
test(d);
}
private static void test(Dog d) {
// 测试狗的吃的行为
d.eat();
// 测试狗叫的行为
d.say();
// 测试狗看家的行为
d.lookHome();
}
private static void test(Cat c) {
// 测试猫吃的行为
c.eat();
// 测试猫叫的行为
c.say();
// 测试猫抓老鼠的行为
c.catchMouse();
}
}
/*
* 需求:描述并测试猫和狗的行为; 猫具有吃、叫和抓老鼠的功能; 狗具有吃、叫和看家的功能;
*/
// 定义一个类表示狗
class Dog {
// 吃
public void eat() {
System.out.println("吃骨头");
}
// 叫
public void say() {
System.out.println("汪汪……");
}
// 看家
public void lookHome() {
System.out.println("看家");
}
}
// 定义一个类表示猫
class Cat {
// 吃
public void eat() {
System.out.println("吃小鱼");
}
// 叫
public void say() {
System.out.println("喵喵……");
}
// 抓老鼠
public void catchMouse() {
System.out.println("抓老鼠");
}
}
问题:
首先,猫和狗之间存在相同功能;
其次:测试的功能函数,一个函数只能测试一种动物,而且每个测试函数的具体实现基本类似;那么每添加一个新的动物,就必须新写一个测试函数,挺麻烦;
解决办法:使用继承和多态技术:

public class Demo2 {
public static void main(String[] args) {
// 创建一只猫
Animal c = new Cat();
// 调用函数测试猫的行为
test(c);
// 创建一条狗
Dog d = new Dog();
// 调用函数测试狗的行为
test(d);
}
private static void test(Animal c) {
// 测试动物吃的行为
c.eat();
// 测试动物叫的行为
c.say();
// 测试动物抓老鼠的行为
// c.catchMouse();
}
}
需求:描述并测试猫和狗的行为;
猫具有吃、叫和抓老鼠的功能;
狗具有吃、叫和看家的功能;
//为了提高代码的复用性,可以定义一个猫和狗的共同的父类动物类,然后让猫和狗继承动物类
abstract class Animal {
public abstract void eat();
public abstract void say();
}
// 定义一个类表示狗
class Dog extends Animal {
// 吃
public void eat() {
System.out.println("吃骨头");
}
// 叫
public void say() {
System.out.println("汪汪……");
}
// 看家
public void lookHome() {
System.out.println("看家");
}
}
// 定义一个类表示猫
class Cat extends Animal {
// 吃
public void eat() {
System.out.println("吃小鱼");
}
// 叫
public void say() {
System.out.println("喵喵……");
}
// 抓老鼠
public void catchMouse() {
System.out.println("抓老鼠");
}
}


多态的弊端

结论:
如果程序中使用了多态,那么在编译期,编译器回去父类型中找需要使用的成员,如果找不到,就会报错;
多态的弊端:
只能使用父类中定义好的成员,不能使用子类特有的成员;

java的类型转换
自动向上转型: 可以直接将子类型引用赋值给父类型变量,可以自动进行,叫做自动向上转型;
例如:
class Fu{}
class Zi extends Fu{}
Zi zi = new Zi();
Fu f = zi;//这行代码就发生了自动向上转型
强制向下转型: java中允许将父类型引用赋值给子类型变量,叫做向下转型;但是这种转型不能自动实现,需要强制进行,所以叫做强制向下转型;
例如:
class Fu{}
class Zi extends Fu{}
Fu f = new Zi();//这行使用了多态,发生了向上转型;
Zi z = (Zi)f; //这行发生了强制向下转型
使用强制向下转型解决多态的弊端:


强制向下转型的问题
Exception in thread "main" java.lang.ClassCastException: com.kuaixueit.duotai.test.Dog cannot be cast to com.kuaixueit.duotai.test.Cat
at com.kuaixueit.duotai.test.Test.testAnimal(Test.java:9)
at com.kuaixueit.duotai.test.Test.main(Test.java:23)

类型转换异常,也是一个运行时异常。一般发生在强制向下转型时,当将一个不属于某个类型的对象强转为这个类型时,就会出现这个问题。
结论:
在使用强制向下转型时,如果将不属于某个类型的对象强制转换为这个类型,就会出现类型转换异常;

instanceof关键字
使用格式:
引用类型的变量名 instanceof 类名
作用:
表示判断左边这个变量指向的对象,是否是右边这个类的对象;
这个表达式返回的结果一定是一个布尔值;

多态中成员使用的特点
编译期:

运行期:

结论:
多态中,编译期,所有成员都要看父类有没有;
运行期,只有非静态函数使用子类中定义的,其他所有成员,都看父类;