Java 类的抽象

这里将介绍类在不同层次上的抽象方法。首先说明如果只有部分方法为抽象方法,那么这是一个抽象类,如果全部方法都为抽象方法,那么这是一个接口;然后描述抽象类与接口的使用差异;最后详速接口的相关用法,包括Java8对接口做了哪些扩展,以及如何通过匿名内部类简化接口的实现编码。

1. 抽象类

在Java编程中,_功能不确定的方法_被称作抽象方法,而包含抽象方法的类受到牵连就变成了抽象类

抽象类的注意点:

  • 被abstract修饰的抽象方法,由于_方法_的具体实现并_不明确_,因此抽象方法没有花括号所包裹着的方法体;
  • 被abstract修饰的抽象类,由于包含至少一个抽象方法,因此不允许外部创建抽象类的实例
  • abstract只能修饰抽象方法和抽象类,不能修饰成员属性,因为属性值本身就允许通过赋值改变,无所谓抽象不抽象的;
  • 虽然抽象类有构造方法,但它的构造方法并不能直接被外部调用,因为外部不允许通过构造方法来创建抽象类的实例化,抽象类的构造方法只能提供给子类调用

以上的概念听起来很模糊,那接下来就用代码来实现理解。
首先,我们定义一个鸡的抽象类;


//演示抽象类的定义。这是一个抽象鸡类
abstract public class Chicken {
	public String name; // 鸡的名称
	public int sex; // 鸡的性别

	// 定义一个抽象的叫唤方法。注意后面没有花括号,并且以分号结尾
	abstract public void call();

	// 即使抽象类定义了构造方法,外部也无法创建它的实例,只能被子类所重写
	public Chicken() {
	}

	// Java只有抽象类和抽象方法,没有抽象属性的说法
	// abstract public String cry;

}

然后,如果鸡类的抽象方法call()不被子类重写,那么子类仍为抽象类,只有被重写了子类才能具备所有完善的行为动作。

//定义一个继承自抽象鸡类的公鸡类
public class Cock extends Chicken {
	public Cock() {
		sex = 0; // 公鸡的性别固定为雄性
	}

	// 重写了公鸡的叫唤方法。如果不重写父类的抽象方法,那么该子类仍旧为抽象类
	public void call() {
		System.out.println("喔喔喔");
	}

}
//定义一个继承自抽象鸡类的母鸡类
public class Hen extends Chicken {
	public Hen() {
		sex = 1; // 母鸡的性别固定为雌性
	}

	// 重写了母鸡的叫唤方法。如果不重写父类的抽象方法,那么该子类仍旧为抽象类
	public void call() {
		System.out.println("咯咯咯");
	}

}

最后就是外部调用各种鸡类了,对于外部而言,唯一的区别是不能创建抽象类的实例。

//演示抽象类派生而来的子类用法
public class TestAbstract {

	public static void main(String[] args) {
		// 不能创建抽象类的实例,因为抽象类是个尚未完工的类
		// Chicken chicken = new Chicken();
		Cock cock = new Cock(); // 创建一个公鸡实例,公鸡类继承自抽象类Chicken
		cock.call(); // 调用公鸡实例的叫唤方法
		Hen hen = new Hen(); // 创建一个母鸡实例,母鸡类继承自抽象类Chicken
		hen.call(); // 调用母鸡实例的叫唤方法
	}

}

抽象类的小结: 抽象类是一个尚未完工的类,不能实例化;父类的抽象方法是由实现类(子类)来完善的。

2. 简单接口——Java 8 之前

在Java体系中,我们知道每个类最多只能继承一个父类,不能同时继承多个类,即单继承。而为了解决这个问题,就引出接口,某个类可以实现多个接口,待实现的接口名称之间以逗号分隔。

接口不从属于类,而是与类平级,类通过关键字class标识,而接口通过关键字interface标识。

接口的注意点:

  • 凡是类都有构造方法,即便是抽象类也支持定义构造方法,但接口不允许定义构造方法,因为接口只用于_声明某些行为动作_,本身并非一个实体。
  • 在Java8前,接口内部的所有方法都必须是抽象方法,具体的方法内部代码有赖于该接口的实现类来补充,abstract可加可不加。
  • 至于接口内部的属性,默认为终态属性,即添加final前缀的成员属性(可加可不加)

依据上面的注意点,我们就用代码实现动物类。

//定义一个接口。接口主要声明一些特定的行为方法
public interface Behavior {
	// 注意,接口内部的方法默认为抽象方法,所以不必添加abstract前缀
	// abstract public void fly(); // 这里的abstract可加可不加
	public void fly(); // 声明了一个抽象的飞翔方法
	public void swim(); // 声明了一个抽象的游泳方法
	public void run(); // 声明了一个抽象的奔跑方法

	// 接口内部的属性默认都是终态属性,所以不必添加final前缀
	public String TAG = "动物世界";
	// public final String TAG = "动物世界"; // 这里的final可加可不加

	// 接口不允许定义构造方法。在Java8以前,接口内部的所有方法都必须是抽象方法

}

接着定义一个鹅类,它不但继承自Bird鸟类,而且还实现了接口类。

注意:子类继承父类的格式:extends 父类名 ;实现某个接口的格式:implements 接口名

//定义一个实现了接口Behavior的鹅类。注意鹅类需要实现Behavior接口的所有抽象方法
public class Goose extends Bird implements Behavior {

	public Goose(String name, int sexType) {
		super(name, sexType);
	}

	// 实现了接口的fly方法
	@Override
	public void fly() {
		System.out.println("鹅飞不高,也飞不远。");
	}

	// 实现了接口的swim方法
	@Override
	public void swim() {
		System.out.println("鹅,鹅,鹅,曲项向天歌。白毛浮绿水,红掌拨清波。");
	}

	// 实现了接口的run方法
	@Override
	public void run() {
		System.out.println("槛外萧声轻荡漾,沙间鹅步满蹒跚。");
	}

}

对于外部调用,跟一般类没什么区别。

	// 演示简单接口的实现类用法
	private static void testSimple() {
		Goose goose = new Goose("家鹅", 0); // 创建一个家鹅实例
		goose.fly(); // 实现了接口的fly方法
		goose.swim(); // 实现了接口的swim方法
		goose.run(); // 实现了接口的run方法
	}

3. 简单接口——Java 8 之后

在Java8之前,接口内部的所有方法都必须是抽象方法,而Java8之后,接口的内部方法也可能不是抽象方法吗?

鉴于此,从Java8开始,接口补充了以下规定:

  • 增加了默认方法,并通过前缀default来标识。接口内部需要编写默认方法的完整实现代码,这样实现类无须重写该方法即可直接继承并使用,仿佛默认方法就是父类方法一样,唯一的区别在于实现类不允许重写默认方法。(即:默认方法不支持重写,但可以继承
  • 增加了静态属性和静态方法,而且通过static来标识。接口的静态属性同时也是终态属性,初始化赋值之后便无法再次修改。接口的静态方法不能被实现类继承,因而实现类允许定义同名的静态方法,缘于接口的静态方法与实现类的静态方法没有任何关联,仅仅是它们两个恰好同名而已。(即:接口内部的静态属性也默认为终态属性静态方法支持重写,但不能被继承
//定义一个增加了Java8新特性的接口
public interface ExpandBehavior {
	public void fly(); // 声明了一个抽象的飞翔方法
	public void swim(); // 声明了一个抽象的游泳方法
	public void run(); // 声明了一个抽象的奔跑方法

	// 默认方法,以前缀default标识。默认方法不支持重写,但可以被继承。
	public default String getOrigin(String place, String name, String ancestor) {
		return String.format("%s%s的祖先是%s。", place, name, ancestor);
	}

	// 接口内部的静态属性也默认为终态属性,所以final前缀可加可不加
	public static int MALE = 0; // 雄性
	public static int FEMALE = 1; // 雌性
	// public final static int MALE = 0;
	// public final static int FEMALE = 1;
	
	// 静态方法,以关键字static标识。静态方法支持重写,但不能被继承。
	public static String getNameByLeg(int leg_count) {
		if (leg_count == 2) {
			return "二足动物";
		} else if (leg_count == 4) {
			return "四足动物";
		} else if (leg_count >= 6) {
			return "多足动物";
		} else {
			return "奇异动物";
		}
	}

}

//定义实现了扩展接口的鹅类
public class ExpandGoose extends Bird implements ExpandBehavior {

	public ExpandGoose(String name, int sexType) {
		super(name, sexType);
	}

	// 实现了接口的fly方法
	@Override
	public void fly() {
		System.out.println("鹅飞不高,也飞不远。");
	}

	// 实现了接口的swim方法
	@Override
	public void swim() {
		System.out.println("鹅,鹅,鹅,曲项向天歌。白毛浮绿水,红掌拨清波。");
	}

	// 实现了接口的run方法
	@Override
	public void run() {
		System.out.println("槛外萧声轻荡漾,沙间鹅步满蹒跚。");
	}

	// 根据产地和祖先拼接并打印该动物的描述文字
	public void show(String place, String ancestor) {
		// getOrigin是来自扩展接口ExpandBehavior的默认方法,可以在实现类中直接使用
		String desc = getOrigin(place, getName(), ancestor);
		System.out.println(desc);
	}

}

接着外部调用,注意以下两点

  • 接口的静态属性:实现类的名称 . 静态属性名
  • 接口的静态方法:扩展接口的名称 . 静态方法名(***)
	// 演示扩展接口的实现类用法
	private static void testExpand() {
		// 实现类可以继承接口的静态属性
		ExpandGoose goose = new ExpandGoose("鹅", ExpandGoose.FEMALE);
		// goose.fly();
		// goose.swim();
		// goose.run();
		goose.show("中国", "鸿雁");
		goose.show("欧洲", "灰雁");
		// 接口中的静态方法没有被实现类所继承,因而只能通过扩展接口自身访问
		String typeName = ExpandBehavior.getNameByLeg(2);
		System.out.println("鹅是" + typeName);
	}

4. 匿名内部类

众所周知,前面所写的接口都是自己定义的,其实Java自身也带有接口,接下来说明接口的几种调用方式。

不知大家是否有印象之前所讲着 Java之数组工具Arrays 中sort方法,该方法是默认为升序的,那时我考虑过如何将其改变为降序呢?

我便查了相关的API文档,发现有一个sort方法允许在第二个参数中传入比较器对象,该比较器就是系统自带的接口Comparator,对应的唯一抽象方法是compare,该方法的两个输入参数都是两个待比较的元素,返回-1则前者大于后者,返回1则前者小于后者。因此,我只需要自己新写一个比较器就能按照降序排列了。

最原始升序排序:

// 演示数组工具的默认升序排列
private static void sortInArrayAsx() {
		Integer[] array = {83,86,95,2,4,99,55};
		Arrays.sort(array);// Arrays的sort方法默认为升序
		String scr = "array的升序结果:";
		for(Integer array_1 : array) {// 拼接排序后的数组元素
			scr = scr + array_1 + ",";
		}
		System.out.println(scr);
	}

接着就自己定义一个数组比较器,

//定义一个整型数组的降序比较器
public class SortDescend implements Comparator<Integer> {
	@Override
	public int compare(Integer o1,Integer o2) {
		//return Integer.compare(o1, o2);// 默认的参数顺序是升序
		return Integer.compare(o2,o1);// 倒过来的参数顺序变成了降序
	}

}

然后使用sort方法中输入两个参数,

	// 利用新定义的降序比较器实现对数组的降序排列
	private static void sortIntArrayDesc() {
		Integer[] intArray = { 89, 3, 67, 12, 45 };
		// sort方法支持按照指定的排序器进行排列判断,新定义的SortDescend类实现了降序排列
		Arrays.sort(intArray, new SortDescend());
		String descDesc = "整型数组的降序结果为:";
		for (Integer item : intArray) { // 拼接排序后的数组元素
			descDesc = descDesc + item + ", ";
		}
		System.out.println(descDesc);
	}

从上面代码可以看出,固然实现了降序功能,但是存在以下缺点:

  • 简简单单的几行compare代码,还得专门用一个代码文件来存储;
  • 即使不开辟,也得在源代码增加内部类,也会把排序方法与比较器隔开一定距离,尽管说距离产生美,但距离也会产生隔阂呀!

为了解决上面问题,我们将采取最便捷的方案处理。为此,Java创造一种名叫“匿名内部类”的概念,这个本质上属于内部类
优点:匿名内部类的方法定义与实例创建操作合二为一,代码看起来更加流利(即不需要另外定义一个继承接口的实现类)。

“匿名内部类”的实例创建格式:

  new 接口名称(){
  	  //这里要实现该接口声明的抽象方法
  }

以上两个重要信息

  • new表示创建实例对象;
  • 接口名称表示该对象实现了指定接口。

匿名内部类无需专门定义形态完整的类,只需指明新创建的实例从哪个接口扩展而来。

接下来就把它应用到数组降序优化环节,

// 通过匿名内部类完成自定义的排序操作
private static void sortInArrayDescAnonymous() {
		Integer[] array = {83,86,95,2,4,99,55};
		// 匿名内部类无需专门定义形态完整的类,只需指明新创建的实例从哪个接口扩展而来
		Arrays.sort(array, new Comparator<Integer>() {
			public int compare(Integer o1, Integer o2) {
				return Integer.compare(o2, o1);
			}
		});
		String scr = "array采用匿名内部类的降序结果:";
		for(Integer array_1 : array) {
			scr = scr + array_1 + " ";
		}
		System.out.println(scr);
		
	}

数组比较器不仅适用于整型数组,还适用于其他类型。再举个例子,利用Arrays工具的sort方法给字符串数组长度进行排序。

// 通过匿名内部类对字符串数组按照字符串长度排序
	private static void sortStrArrayByLength() {
		String[] strArray = { "说曹操曹操就到", "东道主", "风马牛不相及", "亡羊补牢", "无巧不成书",
					"冰冻三尺非一日之寒", "同窗", "青出于蓝而胜于蓝" };
		// 字符串数组的默认排序方式为根据首字母的拼写顺序,
		// 下面的匿名内部类把排序方式改成了按照字符串长度排序
		Arrays.sort(strArray, new Comparator<String>() {
			@Override
			public int compare(String o1, String o2) {
				return o1.length() > o2.length() ? -1 : 1; // 比较前后两个数组元素的字符串长度大小
			}
		});
		String desc = "字符串数组比较字符串长度的升序结果为:";
		for (String item : strArray) { // 拼接排序后的数组元素
			desc = desc + item + ",";
		}
		System.out.println(desc);
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涛涛同学debug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值