抽象再抽象

开发人员一定会按计划来写程序吗?

先来看个例子。我们现在需要开发一款宠物养成的小游戏,为了方便表示各种宠物以及规范开发,我们先设计一个Pet类,作为所有宠物类的父类。

public class Pet {
	String name;
	//表示吃饭的方法
	public void eat(String food) {
		System.out.println(food+"太好吃了");
	}
	//表示叫的方法
	public void shout() {
		System.out.println("大吼一声");
	}
}

这里仅仅展示了一小部分代码,凡是和宠物相关以及游戏开发需要的属性和方法都可以写进去。
当我们需要创建一个宠物类,只需要继承Pet类就可以减少一些工作量。但是不是继承过来的属性和方法都能用呢?我们可以看到,eat()方法勉强还能用,但shout()方法明显不行,毕竟不同的宠物的叫声是有区别的。
聪明的你可能想到的,我们可以子类中重写shout()方法啊。没错,只要重写,就能让shout方法符合我们的需要。
问题是:所有的开发人员在继承Pet类的时候都一定会按照要求重写shout()方法吗?
墨菲定理说:凡是会出错的地方就一定会出错。
虽然有点调侃成分,但在现实中,确实有很多人是不会完全按照规定去做开发的(无论什么原因)。所以,指望开发人员自己完全遵守要求是不现实的。

抽象,再抽象一下

如果我们把程序员必须重写的方法改成抽象方法,那么在继承这个类时,程序员就必须实现抽象方法,否则就会出现语法错误——语法错误的约束力可比开发要求之类的文件强大多了。(这里用了实现一词,和重写一个方法的操作方法基本相同,只不过重写的对象是普通方法,实现的对象是抽象方法)
什么是抽象方法?抽象的,就不是具体的,所以抽象方法就是没有具体实现过程的方法。一般写法是:权限修饰 abstract 返回值 方法名(参数列表);
其实和定义一般的方法很相似,就是前面加个abstract,然后把方法体连通花括号换成;就可以了。
不过如果普通类中包含了一个抽象方法,当我们生成这个类的对象时就会出现问题——这个对象将可能执行一个没有方法体的方法。简单来说,就是知道要做一件事情,但不知道怎么去做。这对计算机而言是不可容忍的。
所以,抽象方法必须包含在抽象类中,而抽象类不能生成对象。
不过,抽象类中不是必须包含抽象方法的。抽象类中可以包含普通方法和抽象方法。
抽象类的定义也很简单,在class关键字前面加个abstract就可以了。
下面看看修改后的Pet类:

public abstract class Pet {
	String name;
	//表示吃饭的方法
	public void eat(String food) {
		System.out.println(food+"太好吃了");
	}
	//表示叫的方法
	public abstract void shout();
}

这样,当子类继承Pet类后,就必须实现抽象方法shout()——除非子类也是一个抽象类,否则就会报错。

04

将抽象进行到底

很显然,抽象类和抽象方法就是为了规范开发而设计的,它可以强制要求开发人员必须编写某些方法,而且还规定了方法的返回值、名称、参数等。如果我们有很多这种方法呢?或者能否更进一步,把所有需要实现的抽象方法放到同一个抽象类里面?
当然可以,只不过,不再叫做抽象类,而是接口,使用的关键字是interface
在接口中,所有的变量默认使用public static final修饰;所有的方法默认为抽象方法,并使用public abstract修饰。
下面我们把宠物游戏中的一些方法和常量放到接口中。

public interface PetMethods {
	//游戏名称
	String GAMENAME="宠物养成";
	//宠物洗澡的方法
	void bathe();
	//宠物嬉戏的方法
	void play();
}

在使用接口时,使用implements关键字引入接口,然后实现其中的抽象方法即可。
我们来定义一个关于宠物猫的类:

public class Cat extends Pet implements PetMethdos {
	@Override
	public void bathe() {
		System.out.println("要洗澡了,我不喜欢泡泡啊");
	}

	@Override
	public void play() {
		System.out.println("小毛球最好玩了");
	}

	@Override
	public void shout() {
		System.out.println("喵喵……喵喵喵……");
	}
}

抽象类不够用吗?

可能有人会问,抽象类中可以定义抽象方法,也可以定义方法,为什么非要再设计一个接口呢?
一个主要原因就是:Java不支持多重继承
一般来说,继承有两种,单一继承和多重继承。单一继承是指一个类最多只能有一个直接父类——也就是说,extends关键字的后面最多只允许出现一个父类;而多重继承是指一个类可以有多个直接父类。
需要注意的,Java中一个类可以有一个父类,这个父类还可以有父类……这就是多层父类,和多重父类并不相同。父类的父类,可以称为子类的父类或者间接父类,是不存在“爷爷类”或“孙子类”这种说法的。
单一继承和多重继承各有优缺点。多重继承可以很方便地让一个类同时继承多个类,也就同时具有了多个父类的属性和方法,但问题也很突出,例如多个父类具有同名、同形式参数的方法时,当子类对象调用这个名字的方法时,到底调用的是从哪个父类中继承过来的呢?为了解决这个问题,允许多重继承的编程语言必须再设计一套机制来解决这些问题,这就增加了语言的复杂性。
为了简化编程语言本身,Java不允许多重继承,只允许单一继承。不过,多重继承的便捷性也确实很诱人,所以Java通过接口,部分实现了多重继承。
在Java中,一个类只能继承一个直接父类,但可以同时实现多个父接口——反正这个类必须实现所有父接口中的所有方法,前面提到的问题也就迎刃而解了。

04

接口中只能有抽象方法吗?

在最初的设计中,接口中只能有抽象方法。但在实际应用中发现,这种设计并不是特别方便。例如,设计一个接口,其中有一个对数组排序的方法。不同的程序员对其可能会有相同或类似的实现,这就相当于进行了一些重复工作。以往会设计一个抽象类,实现该接口中那些可能会有相同实现的方法,从而减少程序员的总工作量。但一个类毕竟只能有一个父类,但可以有多个父接口,将一个接口中的部分方法实现,也就产生了一个类——它就可能会占用子类那个唯一的“父类名额”。
从JDK 8开始,接口中允许出现带有方法体的方法——但必须是以默认方法或静态方法的形式。
静态方法我们前面讲过,就是用static修饰的方法;默认方法则是使用default关键字修饰的方法。静态方法只能使用接口名.静态方法的形式调用,而默认方法只能通过实现了该接口所有抽象方法的子类对象来调用。
我们来看个例子:

package cn.edu.vk.java11.visitDemo.p3;
//宠物常用方法的接口
interface PetMethods {
	//游戏名称
	String GAMENAME="宠物养成";
	//宠物洗澡的方法
	void bathe();
	//宠物嬉戏的方法
	void play();
	//静态方法
	public static void intro() {
		System.out.println("您正在玩的游戏是:"+GAMENAME);
	}
	//默认方法
	public default void sleep() {
		System.out.println("好困,我要睡觉了……ZZzzz");
	}
}
//实现接口的类
class Dog implements PetMethods{

	@Override
	public void bathe() {
		System.out.println("洗澡好舒服,汪汪汪");
	}

	@Override
	public void play() {
		System.out.println("汪汪,谁来和我玩飞盘游戏?");
	}
	
}

public class MethodsDemo {

	public static void main(String[] args) {
		Dog d=new Dog();
		//调用已经实现的方法
		d.bathe();
		d.play();
		//调用默认方法
		d.sleep();
		//调用静态方法
		PetMethods.intro();
	}
}

04

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值