首页 > 其他分享 >JDK1.8新特性(七):默认方法,真香,开动!接口?我要升级!!

JDK1.8新特性(七):默认方法,真香,开动!接口?我要升级!!

时间:2023-05-04 11:00:49浏览次数:42  
标签:JDK1.8 真香 int void 接口 我要 实现 默认 方法


JDK1.8新特性(七):默认方法,真香,开动!接口?我要升级!!_Java


前期回顾:

JDK1.8新特性(一):JDK1.8究竟有哪些新特性呢JDK1.8新特性(二):为什么要关注JDK1.8

JDK1.8新特性(三):Lambda表达式,让你爱不释手

JDK1.8新特性(四):函数式接口

JDK1.8新特性(五):Stream,集合操作利器,让你好用到飞起来

JDK1.8新特性(六):Stream的终极操作,轻松解决集合分组、汇总等复杂操作

一、接口可以升级吗?

在《Java编程思想·第4版》一书中提到:

interface这个关键字产生一个完全抽象的类,它根本就没有提供任何具体的实现。它允许创建者确定方法名、参数列表和返回类型,但是没有任何方法体。接口只提供了形式,而未提供任何具体实现。

我们在Java入门学习时,也知道接口只提供方法的声明,具体实现必须在对应的实现类中实现。实现接口的类必须为接口中定义的每个方法提供一个实现,否则就连编译都无法通过。

随着之前定义的接口,被广泛实现使用,一旦需要升级接口,在接口中新增方法,将不得不通知所有实现类来实现该新增方法。想象一下,你作为一个接口实现者,愿意莫名其妙的来实现一些自己压根就没用的方法么?这简直就会让人抓狂、疯掉!难道就没有解决办法了么?

且慢,其实你不必抓狂。JDK1.8的出现,解决了这些问题,从中引入了一种新的机制。JDK1.8中的接口支持在声明方法的同时提供实现,通过两种方式可以完成这种操作:

  • JDK1.8允许在接口内声明静态方法。
  • JDK1.8引入了一个新功能,叫默认方法,通过默认方法可以指定接口方法的默认实现。

换句话说,接口中能够提供方法的具体实现。因此,实现接口的类如果不显示提供该方法的具体实现,就会自动继承默认的实现。

默认方法的出现可以使你轻松、平滑地进行接口的优化、升级。

实际上,从你使用JDK1.8开始就已经使用了多个默认方法,比如List接口中的sort方法、以及Collection接口中的Stream。

public interface List<E> extends Collection<E> {
	……
    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }
    ……
}

可以看到,这个新增的sort方法有方法体,由default修饰符修饰,这就是接口的默认方法。

太开心了,在JDK1.8之后,接口居然可以这么轻松升级啦。

二、接口如何升级呢?

假如在一个绘图库中,存在一个接口Resizable,它定义了一个简单的可缩放形状必须支持的很多方法, 比如:setHeight、setWidth、getHeight、getWidth等。

此外,你还提供了几个额外的实现,如正方形、长方形。由于你的库非常流行,被广为流传,你的一些用户使用Resizable接口,创建了他们自己感兴趣的实现,比如椭圆。

接口Resizable如下:

public interface Resizable { 
     int getWidth(); 
     int getHeight(); 
     void setWidth(int width); 
     void setHeight(int height); 
     void setAbsoluteSize(int width, int height);
}

你的一位铁杆用户根据自身的需求实现了Resizable接口,创建了Ellipse类:

public class Ellipse implements Resizable { 
	 … 
}

他实现了一个处理各种Resizable形状(包括Ellipse)的游戏:

JDK1.8新特性(七):默认方法,真香,开动!接口?我要升级!!_新特性_02

库上线使用几个月之后,你收到很多请求,要求你更新Resizable的实现,让Square、Rectangle以及其他的形状都能支持setRelativeSize方法。为了满足这些新的需求,你不得不来升级你接口。

如果直接在接口Resizable中,新增setRelativeSize方法:

public interface Resizable { 
     int getWidth(); 
     int getHeight(); 
     void setWidth(int width); 
     void setHeight(int height);
     void setAbsoluteSize(int width, int height);
     // 新增方法
     void setRelativeSize(int wFactor, int hFactor);
}

将会导致一系列的问题,不得不要求接口的实现者(库用户)强制来添加setAbsoluteSize方法的实现,这肯定是不太现实的升级方法。

这时,我们想到了默认方法,便可以解决上面这种问题。不但满足了新用户的需求,而且也兼容了老用户。

public interface Resizable { 
     int getWidth(); 
     int getHeight(); 
     void setWidth(int width); 
     void setHeight(int height);
     void setAbsoluteSize(int width, int height);
     // 新增方法setRelativeSize,即:采用默认方法
     default void setRelativeSize(int wFactor, int hFactor){ 
 		setAbsoluteSize(getWidth() / wFactor, getHeight() / hFactor); 
	}
}

如果用户对方法setRelativeSize的功能有新的想法,自己完全也可以重写该方法。

完美,至此升级完成!

三、默认方法的妙用

默认方法的引入就是为了以兼容的方式解决像 Java API这样的类库的演进问题的,如下图所示:

JDK1.8新特性(七):默认方法,真香,开动!接口?我要升级!!_Java_03

简而言之,升级接口中的方法是诸多问题的罪恶之源;一旦接口发生变化,实现这些接口的类往往也需要更新,提供新添方法的实现才能适配接口的变化。

现在你已经了解了默认方法是怎样以兼容的方式来升级接口。除了上面的使用场景,还有其他场景也能利用这个新特性吗?当然有,你可以创建自己的接口,并为其提供默认方法。接下来,就介绍使用默认方法的两种场景:

  • 可选方法
  • 多继承

1. 可选方法

你很可能也碰到过这种情况,类实现了接口,不过却刻意地将一些方法的实现留白。我们以Iterator接口为例来说。Iterator接口定义了hasNext、next,还定义了remove方法。

Java 8之前,由于用户通常不会使用该方法,remove方法常被忽略。因此,实现Interator接口的类通常会为remove方法放置一个空的实现,这些都是些毫无用处的模板代码。

采用默认方法之后,你可以为这种类型的方法提供一个默认的实现,这样实体类就无需在自己的实现中显式地提供一个空方法。比如,在Java 8中,Iterator接口就为remove方法提供了一个默认实现,如下所示:

interface Iterator<T> { 
     boolean hasNext(); 
     T next(); 
     default void remove() { 
     	throw new UnsupportedOperationException(); 
     } 
}

通过这种方式,你可以减少无效的模板代码。实现Iterator接口的每一个类都不需要再声明一个空的remove方法了,因为它现在已经有一个默认的实现。

2. 多继承

默认方法让之前无法想象的事儿以一种优雅的方式得以实现,即多继承。这是一种让类从多个来源重用代码的能力,如图下图:

JDK1.8新特性(七):默认方法,真香,开动!接口?我要升级!!_默认方法_04

看到这里,不要误认为Java是支持多继承的哦。切记:“Java的类只能继承单一的类,但是一个类可以实现多接口。”

比如,我们设计一款游戏,需要定义多个具有不同特质的性质。有的形状需要调整大小,但是不需要有旋转的功能;有的需要能旋转和移动,但是不需要调整大小。我们可以如何设计呢?这里就可以用到默认方法了。

定义一个可旋转接口,并提供旋转的默认方法:

public interface Rotatable {     
	void setRotationAngle(int angleInDegrees);     
	int getRotationAngle();
    // 旋转方法
	default void rotateBy(int angleInDegrees){           
		setRotationAngle((getRotationAngle () + angle) % 360);     
	} 
}

定义一个可移动接口,并提供移动的默认方法:

public interface Moveable {     
	int getX();     
	int getY();     
	void setX(int x);     
	void setY(int y); 
	// 移动方法
    default void moveHorizontally(int distance){        
    	 setX(getX() + distance);    
    } 
    default void moveVertically(int distance){         
    	setY(getY() + distance);     
    } 
 }

定义一个可改变大小接口,并提供改变大小的默认方法:

public interface Resizable {     
	int getWidth();     
	int getHeight();     
	void setWidth(int width);     
	void setHeight(int height);     
	void setAbsoluteSize(int width, int height); 
    default void setRelativeSize(int wFactor, int hFactor){         
    	setAbsoluteSize(getWidth() / wFactor, getHeight() / hFactor);     
    }
}

现在,可以为游戏创建不同的实体类,比如Monster可以移动、 旋转和缩放。

public class Monster implements Rotatable, Moveable, Resizable { 
	//... 只需要给出所有抽象方法的实现,不需要重复实现默认方法
 }

Monster继承了rotateBy、moveHorizontally、moveVertically和setRelativeSize的实现。我们也可以直接调用不同的方法:

Monster m = new Monster(); 
//调用由Rotatable中继承而来的rotateBy 
m.rotateBy(180);  
//调用由Moveable中继承而来的moveVertically 
m.moveVertically(10);

假设你现在需要声明另一个类,它要能移动和旋转,但是不能缩放,比如说Sun。这时也无需复制粘贴代码,你可以像下面这样复用Moveable和Rotatable接口的默认实现。

public class Sun implements Moveable, Rotatable {
	…  
}

四、总结

通过本篇文章,认识了Java8新引入的默认方法,也通过实例看到了这种默认方法带来的灵活性,更能够轻松升级接口中的方法,所以此后若在项目中,需要对接口进行改动,也可以想想是否可以使用默认方法。


标签:JDK1.8,真香,int,void,接口,我要,实现,默认,方法
From: https://blog.51cto.com/xcbeyond/6241949

相关文章

  • 我要创造一个只有Kevin Durant的世界!!!
    \(KevinDurant\)他不相信长夜将至,因为火把就在他的手里。......
  • 我要创造一个只有Kevin Durant的世界!!!
    \(KevinDurant\)他不相信长夜将至,因为火把就在他的手里。......
  • linux安装jdk1.8
    为防止操作权限不足,建议切换root用户,当然如果你对Linux命令熟悉,能够自主完成权限更新操作,可以不考虑此推荐。环境:centos7.6ssh连接工具:tabby(自从用了这个工具,我再也不用xshell了,这个工具自带文件上传,还有网页版)创建目录mkdir/opt/jdk把jdk的压缩包上传到/opt/jdk目录下......
  • JDK1.8新特性(二):为什么要关注JDK1.8
    前期回顾:JDK1.8新特性(一):JDK1.8究竟有哪些新特性呢自1996年JDK1.0(Java1.0)发布以来,Java已经受到了学生、程序员、整个软件行业人员等一大批活跃用户的欢迎。这一语言极富活力,不断被用在大大小小的项目里。从Java1.1(1997年)一直到Java7(2011年),Java通过增加新功能,不断得到良好的升级......
  • linux安装jdk1.8
    为防止操作权限不足,建议切换root用户,当然如果你对Linux命令熟悉,能够自主完成权限更新操作,可以不考虑此推荐。环境:centos7.6ssh连接工具:tabby(自从用了这个工具,我再也不用xshell了,这个工具自带文件上传,还有网页版)创建目录mkdir/opt/jdk把jdk的压缩包上传到/opt/jdk......
  • Linux安装JDK1.8
    1.创建Linux软件目录并进入data目录mkdir/datacddata/  2.创建java目录,并进入java目录mkdirjavacdjava 3.上传"jdk-8u181-linux-x64.tar.gz"文件,到当前目录(/data/java)jdk文件地址:https://www.123pan.com/s/QcP7Vv-FrH8d.html 4.解压安装JDK(jdk-8u1......
  • 我要提取text4文本中的邮箱号 正则应该怎么写?
    大家好,我是皮皮。一、前言前几天在Python白银交流群【膨胀西瓜汁】问了一个Python正则表达式的问题,这里拿出来给大家分享下。代码如下:二、实现过程这里【甯同学】给了一个思路,如下图所示:直接使用字符串+列表推导式搞定了,太强了!不过粉丝正好在学习正则表达式,所以还是希望......
  • # yyds干货盘点 # 我要提取text4文本中的邮箱号 正则应该怎么写?
    大家好,我是皮皮。一、前言前几天在Python白银交流群【膨胀西瓜汁】问了一个Python正则表达式的问题,这里拿出来给大家分享下。代码如下:二、实现过程这里【甯同学】给了一个思路,如下图所示:直接使用字符串+列表推导式搞定了,太强了!不过粉丝正好在学习正则表达式,所以还是希望能够用正则......
  • Centos7.9安装JDK1.8
    卸载Centos自带JDK卸载系统自带的JDK;通过以下命令查看是否已经安装JDKyumlistinstalled|grepjava或者如下命令都可以查看到是否安装了JDKwhereisjavafind/-namejava如图:  如果安装了则通过以下命令进行删除:yum-yremovejava安装JDK下面通过两种方......
  • Linux安装jdk1.8
    第一步:去官网下载jdk>JavaDownloads|Oracle看你是什么系统2.把下载好的安装包(下载路径一定要记得)拖到xshell中第二步:......