写点什么

万字长文详细总结!关于继承、重写与重载、封装、接口的硬核干货

用户头像
codevald
关注
发布于: 2021 年 02 月 01 日




Java 语言在面向对象方面的知识点复杂繁琐,但是几乎是每个小伙伴学习编程必须踩的坑,其实,面向对象的底层都是一些计算机底层知识的结合,所以,不注重基础的程序猿,一定不是一个可以走的远的程序猿。那么,今天,我们先逐一地深入了解继承、重载、接口和构造器的知识。


在学习这篇文章之前,如果有小伙伴对 new 过程发生了什么很感兴趣的话,可以先用几分钟看完这篇文章。


new的过程发生了什么?看完这一篇就懂了


好了,正题开始。



类的继承

类的继承是指从已经定义好存在的类中派生出一个新的类,我们在定义新类时,可以基于一个已经设计好的类,从已存在的类中继承有用的功能(属性和方法),这时已经存在的类便称为父类,而这个继承的新类则称为子类。在类的继承中,父类一般会具有所有子类的共有特性,而子类则可以增加一些个性化的方法。而子类还可以继续派生出新的子类因此位于上层的类在概念上会更抽象,而位于下层的类就会更具体。


假设一个场景,你有一个朋友,家里超级有钱,你为了能够继承他的家产,认他做了爸爸,这个时候,假如他去了美国,感染了新冠肺炎,你以为你可以顺理成章地继承他的百万油田,千万矿山了,但是有一句话说得好,[你是想笑死我,然后继承我的支付宝花呗借呗吗😂],原来,他平时消费都用花呗,借呗,京东白条,光信用卡就几十张了[少年,你渴望力量吗],这个时候你只好认栽,好的坏的都继承了。


所以,这个例子就说明了,继承就是不好的好的都继承了,所以父类的全局属性,方法都可以在子类中轻松的访问到


  • 父类和子类

在 Java 中实现继承十分地简单,具体语法如下:



<修饰符> class <子类名字> extends <父类名字> {
[<成员变量定义>] [<方法的定义>]
}
复制代码

当然,通常所说的子类一般指的是某个父类地直接子类,所以父类也可称为该子类的直接超类。如果存在多层继承关系的话,那么,就得按情况具体分析。


在 Java 程序中,一个类只能有一个父类,也就是说在 extends 关键字后面只能有一个类,它不支持多重继承而接口是允许的


来看一个例子



public class Test { public static void main(String[] args) { SubClass sc = new SubClass("valdcode","codevald",21);
}
}
class SuperClass {
//父类 String name; int age;
//构造方法 SuperClass(String name,int age) { System.out.println("大家好,我是" + name); System.out.println("今年" + age + "岁"); }}
class SubClass extends SuperClass {
//定义子类 SubClass(String wechat,String name,int age) { super(name,age); System.out.println(1 > 0 ? "目前专注于研究计算机底层知识的研究" : "否"); System.out.println("我的微信号是" + wechat + ",有问题欢迎添加一起交流"); }
}
}
复制代码

在上面的代码中,类 SubClass 是一个子类,继承了父类 SuperClass 的属性和方法。在子类的构造方法中,通过 Super()调用了父类的构造方法。这充分地说明子类是可以适用父类的属性和方法。在 main()函数里面,调用子类 SubClass 的构造方法,子类调用父类的构造方法 Super(),设置对应的参数值


来看下控制台会输出什么。 


那么,在刚才的例子中,涉及到一个概念,构造方法,构造方法是 Java 类中比较重要的特殊方法,一个子类可以访问父类的构造方法。在 Java 中调用父类构造方法会使用 super(参数)这个方法。


再来看下一段代码



public class JavaEnginer extends Enginer { //super和this //定义子类 JavaEnginer() { super(); //调用父类无参数构造器 prt("A Java Enginer."); //调用父类中的方法prt() } JavaEnginer(String name) { super(name); prt("A Java Enginer: " + name); } JavaEnginer(String name,String official_Accounts) { this(name); //调用有参数的构造器 prt(name + "'s Official Accounts: " + official_Accounts); //调用父类中的方法 } public static void main(String[] args) { JavaEnginer je = new JavaEnginer(); je = new JavaEnginer("codevald"); je = new JavaEnginer("codevald","@CodeVald"); }}
class Enginer { public static void prt(String s) {
//定义静态方法prt() System.out.println(s); //输出 }
//没有参数的构造器 Enginer() { prt("A Enginer"); //输出文本 } Enginer(String name) { prt("A enginer: " + name); //输出文本 }}
复制代码

调用父类的构造方法

在上面的代码中,在子类中使用了 super 和 this 两个不同的关键字表示不同的意思。在 super 后面加参数调用的是父类中具有相同形参的构造函数。


比如这个


JavaEnginer(String name) {		super(name);		prt("A Java Enginer: " + name);	}
复制代码

而在 this 后面加参数调用的是当前类中具有另一个形参的构造函数。比如这段


JavaEnginer(String name,String official_Accounts) {		this(name); //调用有参数的构造器		prt(name + "'s Official Accounts: " + official_Accounts); //调用父类中的方法	}	}
复制代码

而在子类中,可以随意调用父类中的方法,当然了,也可以加上 this 表示调用当前类继承父类中的方法或者加上 super 的形式,也是可以正常编译的。


比如下面这段


JavaEnginer() {		super(); //调用父类无参数构造器		prt("A Java Enginer."); //调用父类中的方法prt()    /**    *this.prt("A Java Enginer.") or     *super.prt("A Java Enginer.")都可以    **	}
复制代码

来看下最终的运行结果



访问父类的属性和方法

在 Java 中,子类可以轻松的访问父类的属性和方法,当然还是用刚才提过的 super()了。具体语法格式 就是 super.[方法或全局变量] 。


来新建一个 Demo


//子类public class AccessSuperClassProperty extends SuperClass {	public String name = "CodeVald";	//输出子类的name	public void printOwner() {		System.out.println(name);	}
public void printSuper() { System.out.println(super.name); }
public static void main(String[] args) { AccessSuperClassProperty scp = new AccessSuperClassProperty(); System.out.println(scp.name); //直接输出子类的name scp.printOwner(); //直接输出子类的name scp.printSuper(); //直接输出父类的name }}
//父类class SuperClass { public String name = "codevald"; }
复制代码

在上面的代码中,分别在子类和父类中创建了同一个名字的变量属性 name,name 的初始值不同。


然后,通过 super 来访问与方法调用者对应的父类的对象


当系统创建 AccessSuperClassProperty 对象时,会对应创建一个父类对象 SuperClass,不过父类对象的属性只有通过 super 作为调用者才可以访问到而已。


来看下代码执行后的结果 


在上述的代码中,覆盖的是父类的属性,那么在子类的方法中可以通过父类名作为调用者来访问被覆盖的类属性,如果子类中没有包含和父类同名的属性,则子类可以继承父类属性,那么在子类实例方法中就无需显式地适用 super 作为调用者。所以,如果在某个方法中访问一个同名的属性,且没有显式地指定调用者,那么系统查找地顺序为


  • 查找方法中是否有名为 x 的局部变量

  • 查找当前类中是否包含名为 x 的属性

  • 查找 x 的直接父类中是否包含名为 x 的属性,依次上溯到 x 的父类,直到 java.lang.object 类。如果最终没有找到,就提示编译错误

多重继承

在 Java 中,多重继承指的是 A 继承了 B,而 C 继承了 A,那么,这种就叫做多重继承


来看个 Demo


public class TestDemo {
public static void main(String[] args) { Singer s1 = new Singer(); Singer s2 = new Singer(s1); s1.print(); s2.print(); FolkSinger fs1 = new FolkSinger(); FolkSinger fs2 = new FolkSinger("沈以城","《绿洲》"); FolkSinger fs3 = new FolkSinger(fs2); fs1.print(); fs2.print(); fs3.print(); FavoriteFolkSinger ffs1 = new FavoriteFolkSinger(); FavoriteFolkSinger ffs2 = new FavoriteFolkSinger("沈以城","《绿洲》"); FavoriteFolkSinger ffs3 = new FavoriteFolkSinger(ffs2); ffs1.print(); ffs2.print(); ffs3.print(); }}
class FavoriteFolkSinger extends FolkSinger {
FavoriteFolkSinger() { super(); } FavoriteFolkSinger(FavoriteFolkSinger fs) { super(fs); }
FavoriteFolkSinger(String name,String songTitle) { super(name,songTitle);
} @Override void print() { System.out.println("codevald最喜欢的" + "歌手为: " + super.name + " 代表作为: " + super.songTitle); }}
class FolkSinger extends Singer {
FolkSinger() { super("徐秉龙","《迂回》"); }
FolkSinger(FolkSinger fs) { super(fs);
} FolkSinger(String name,String songTitle) { super(name,songTitle); } @Override void print() { System.out.println("歌手为: " + super.name + " 代表作为: " + super.songTitle); }}
class Singer {
String name;//歌手名字 String songTitle; //歌曲名字 Singer() { //构造方法用于初始化 name = "民谣歌手,流行音乐歌手,嘻哈歌手"; songTitle = "民谣歌曲,流行音乐,嘻哈音乐";
} //定义参数为引用类型的构造方法
Singer(Singer s) { name = s.name; songTitle = s.songTitle; } //定义构造方法,并且有参数 Singer(String name,String songTitle) { this.name = name; this.songTitle = songTitle;
} void print() { //输出 System.out.println("歌手类型为: " +this.name + " 歌曲类型为: " + this.songTitle); }
}
复制代码

代码执行后结果如下图所示 


重写和重载

- 重写

在面向对象编程中,经常会听到重写重载两个词。这两个都是十分重要的概念,虽然两者的名字十分接近,但是实际上却相差的很远,并不是一个概念。


重写是建立在继承关系之上的,在子类中重新编写来自父类的方法以达到自己的需求的目的


那么在重写的时候,有一些规则需要遵守


  • 重写方法的访问权限应该与父类中的方法的访问权限相同或者可以进一步扩大,但是绝不能缩小

  • 表示为 final 的方法不能进行重写,静态的方法不能重写

  • 重写方法的返回值类型必须与被重写方法的返回值类型相同

  • 重写方法的参数列表必须与被重写方法的参数列表相同

  • 抽象方法必须在具体类中重写

  • 无论被重写方法是否抛出异常,重写的方法都可以抛出任何非强制异常,但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以成立

在编写 Java 程序的时候,有时候经常需要子类重写父类,新定义的类具有新的功能,下面给出一个实例文件,感受下重写的重要性


public class TestOverride {
public static void main(String[] args) { SubClass s = new SubClass("计科1班",21,52,"广州市"); s.print(); //调用子类的print() }}
class SubClass extends SuperClass {
String saddr; SubClass(String name,int id,int number,String address) { super(name,id,number); saddr = address; } @Override void print() { System.out.println("codevald" + "所在的班级为" + sname + ",学号为" + sid + ",总人数为" + snumber + " 学校所在地址为: " + saddr); }}
class SuperClass {
String sname; //名字 int sid; //工号 int snumber; //人数 SuperClass(String sname,int sid,int snumber) { this.sname = sname; this.sid = sid; this.snumber = snumber; } void print() { System.out.println("组织名为: " + sname + " 工号为: " + sid + " 组织人数为:" + snumber); }
}
复制代码

运行结果 


但是,在编写重写方法的时候,要注意权限问题,如果把上面的子类中 print()方法权限改写为 private,编译时将会报错。


- 重载

在 Java 中,重载大大减少了程序猿的变成负担,开发者不需要记住那些复杂而难记的方法名称或者参数即可实现项目的开发需求。


那么,在 Java 的程序中,同一类中可以有两个或者多个方法具有相同的方法名,只要他们的参数不同即可,这就是方法重载。


重载的规则十分简单,参数决定重载方法的调用。当调用重载方法时,要确定调用哪个参数是基于其参数的。


来看下具体的实例,感受下重载的魅力。


public class TestOverload {
public void test() { System.out.println("无参数的方法"); } public void test(String message) { System.out.println("重载的test方法" + message); }
public static void main(String[] args) { TestOverload o = new TestOverload(); int i = 0; do { System.out.print(args[i]); i++;
} while (i < args.length); System.out.println(""); o.test(); o.test("@CodeVald"); }}
复制代码

上面分别定义了两个同名方法 test(),但是其方法的形参列表不同,系统可以自动区分这两个方法,这两种类型的方法称为方法重载。


在上面的例子中,我编写了一段代码,利用 do {} while ()语句输出了运行时自定义的字符串,在运行时在文件的后面假如自定义的字符串即可输出,其实这里是利用了 main()函数后面的字符串数组 args[],它可以检测输入的字符串并存储起来


来看下运行结果



重写和重载其实十分容易,我写了一段话来区分它们


重写发生在具有继承关系的类之间,而重载则是在同一个类中有多个同名的方法,主要通过参数来区别。[继承可重写,方法可重载]。


那么,在重写中还需要注意:


在重写了父类中的方法,按正常的办法子类对象是无法访问到父类中被覆盖的方法的,但是也是可以在子类中调用父类的被覆盖的方法。如果需要访问到被覆盖的方法,则可以适用 super[被覆盖的是实例方法]或者父类名[被覆盖的是类方法]作为调用者来调用父类中被覆盖的方法。


如果父类方法具有私有访问权限,则该方法对其子类是隐藏的,其子类无法重写该方法。那么要是在子类中定义了一个与父类私有方法相同的名字,相同的形参列表,相同的返回值类型的方法,这时候并不是重写,只是重新定义了一个新方法。


封装

封装(encapsulation)是面向对象的特征之一,是指将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象的内部信息,通过该类对外所暴露的方法实现对内部信息的操作和访问。在 Java 中封装类或对象的目的如下所示:


  • 隐藏类的实现细节

  • 让调用者只能通过实现预定的方法访问数据,在访问代码中加入控制逻辑,限制对属性的不合理访问

  • 进行数据检查

  • 需求更改时,便于修改,提高代码的可维护性

使用封装特性时,访问控制符的知识也需要先了解一下


在 Java 中,提供了 3 个访问控制符,分别是 private、protected、public,分别代表了 3 个访问控制级别,如果不适用,会使用默认的 default,具体说明如下:


private: 如果类里的一个成员(包括属性和方法)使用 private 访问修饰符时,那么这个成员只能在类的内部被访问。所以,使用 private 修饰属性可以把属性隐藏在类的内部。


default:如果类里的一个成员(包括属性和方法)或者一个顶级类不适用任何访问控制符来修饰,则称它为默认访问控制,由 default 访问控制符修饰的成员或者顶级类可以被相同包下其他类访问。


protected 如果一个成员(包括属性和方法)使用 protected 访问控制符修饰,那么这个成员既可以被同一个包中的其他类访问,也可以被不同个包中的其他子类访问, 通常情况下,如果使用 protected 修饰一个办法,那么通常是希望子类重写这个方法。


public 这是一个最宽松的访问控制级别,如果一个成员(包括属性和方法)或者一个顶级类使用了 public 来修饰,那么这个成员或者顶级类就可以被所有类访问,这时不管访问类和被访问类是否处于同一个包中,他们是否具有父子继承关系。


访问控制控制一个类的成员是否可以被其他类访问。对于局部变量来说,其作用域就是他所在的方法,不可能被其它类来访问,因此他们不能使用访问控制符来修饰


Java 中的顶级类可以使用访问控制符来修饰,但是顶级类只能有两种访问控制级别,分别是 public 和 default(默认的)。顶级类不能使用 private 和 protected 修饰。由于顶级类既不处于任何类的内部,也没有外部类的子类,因此 private 和 protected 访问控制符对顶级类没有意义。


来看个例子


public class Person {
//定义类Person private String name; //定义私有属性变量 private int age; public Person() { //定义没有参数的公有构造方法 }
public Person(String name,int age) { //定义有参数的公有构造方法 this.name = name; this.age = age; }
public void setName(String name) { //执行校验,要求用户名长度必须在1-10位之间 if (name.length() > 10 || name.length() < 1) { System.out.println("您设置的人名不符合要求."); return; } else { this.name = name; } } public String getName() { //返回属性name return this.name; } public void setAge(int age) { //执行校验,要求用户年龄必须在0-100之间 if (age > 100 || age < 0) { System.out.println("您设置的年龄不合法."); } else { this.age = age; } } public int getAge() { return this.age; }
} }}
复制代码

上面编写的类的 name 和 age 属性只能在类内才可以操作和访问,在 Person 类之外只能通过各自的对应的 setter 和 getter 方法来操作和访问。


编写测试代码如下


public class TestPerson {		public static void main(String[] args) {			Person p = new Person();				//属性已被隐藏,所以下面的语句将出现编译错误				//p.age = 100;				//下面的语句编译时不会出现错位,但运行时会提示age属性不合法				p.setAge(200);			//如果想要获取属性,必须通过暴漏的getter方法去访问p的age属性		//如果没有设置属性的值,int类型的成员变量默认初始化为0,String类型的成员变量默认初始化为Null				System.out.println("未设置属性变量age时: " + p.getAge());			p.setAge(50);				System.out.println("成功设置属性变量age后: " + p.getAge());			//同样要用setter()方法来设置p的name属性		p.setName("codevald");		System.out.println("成功设置属性变量 name 后:" + p.getName());		
}
}
复制代码

执行结果为



那么,总结一下,在使用 Java 的访问控制符时,应该遵循以下原则:


  • 类里的绝大部分属性都应该使用 private 来修饰,除了静态变量(用 static 修饰的),才考虑用 public 来修饰,除此之外,有一些方法是辅助所在的该类的一些其他方法,这些方法被其他方法调用实现功能,也应该使用 private 修饰

  • 如果一个类是其他类的父类,该类所包含的大部分方法仅仅希望被其他子类重写,而不想被外界直接调用的话,最好使用 protected 修饰这些方法。还有一些作为父类的抽象类,有时候希望使用多态操作,那么父类的方法则必须用 public 修饰

  • 暴露出来给其他类自由调用的方法应该使用 public 修饰,因此,类的构造器应该用 public 修饰,暴露出来给其他类创建该类的对象。

接口

在 Java 语言中,接口是一种和类十分相似的东西,定义接口的方法和定义类的方法差不多,在接口里面也可以包含方法,在接口里可以派生新的类。


- 接口的定义

接口里的方法和抽象类中的方法一样,它的方法也是抽象的,所以接口是不能具体化成对象的,他只是规定应该怎么做,而不管具体怎么做。定义完接口,任何类都可以实现这个接口。而且类不支持多继承类,但是类可以实现多个接口,在 Java 中创建接口的语法如下


[public] interface <接口名> {
常量; 抽象方法;}



复制代码
  • 接口的修饰符只能是 public,因为这样接口才能被任何包中的接口或者类访问

  • interface:Java 中接口的关键字

  • 接口名: 规则在 Java 中和类名一样

  • 常量: 在接口中不能声明变量,因为 i 接口具备三个特性:公有(public)、静态(static)、常量(final)

那么,在接口里,是使用 implement 用于接口的继承的,这和类不一样,类的继承是使用 extends 的,只要一个类没有声明为 final 或者这个类是 abstract 就能继承。Java 不支持多重继承,但是可以使用接口来实现,就用到了 implements,使用 implements 可以实现多个接口,只需要用逗号分开来。 接口的多继承语法如下:


class A extends B implements C,D,E {

}

复制代码

- 接口里的方法

在接口里,所有的方法都是公有的,抽象的,因此在方法声明的时候,可以省略关键字 public、abstract,来看个 Demo,看下是不是这样子。



class TestJieKou {
//测试类 public static void main(String[] args) { JieKou1 jiekou = new JieKou1(); jiekou.fun1(); jiekou.fun2(); jiekou.fun3(); jiekou.fun4(); jiekou.fun5();
}
}

interface JieKou { //定义接口 void fun1(); //在接口中定义方法fun1() public void fun2(); //在接口中定义方法fun2() abstract void fun3(); //在接口中定义方法fun3() public abstract void fun4(); //在接口中定义方法fun4() abstract public void fun5(); //在接口中定义方法fun5()


}

class JieKou1 implements JieKou {
//继承接口JieKou public void fun1() { System.out.println("接口里第一种方法没有修饰符"); }
public void fun2() { System.out.println("接口里第二种方法有修饰符public"); } public void fun3() { System.out.println("接口里第三种方法有修饰符abstract"); }
public void fun4() { System.out.println("接口里第四种方法有修饰符public、abstract"); }

public void fun5() { System.out.println("接口里第五种方法有修饰符abstract、public"); }
}



复制代码

编译运行程序后的结果



- 引用接口

在引用接口前需要先实现这个接口,在 Java 中实现接口的格式如下



<修饰符> class <类名> implements <接口名> {
... ... ...

}




复制代码

下面这个 Demo 实现计算器的基本功能加减乘除


class JiekouShiXian {
public static void main(String[] args) { Calculator cal = new Calculator(); System.out.println("1200 + 1200 = " + cal.add(1200,1200)); System.out.println("2400 - 1100 = " + cal.subtract(2400,1100)); System.out.println("1100 * 1100 = " + cal.multiply(1100,1100)); System.out.println("5200 / 2 = " + cal.divide(5200,2)); }
}
interface Add {
int add(int a,int b);
}
interface Subtract {
int subtract(int a,int b);
}
interface Multiply {
int multiply(int a,int b);
}
interface Divide {
int divide(int a,int b);

}
class Calculator implements Add,Subtract,Multiply,Divide {
public int add(int a,int b) { return a + b; } public int subtract(int a,int b) { return a - b; } public int multiply(int a,int b) { return a * b; } public int divide(int a,int b) { return a / b; }
}

复制代码

在上面的代码中,分别定义了四个接口,分别表示实现加、减、乘、除的功能,然后定义了一个类实现四个接口,重写四个接口的内置方法,并编写测试类测试功能


来看下运行结果



在编写程序的时候,用户可以建立接口类型的引用变量,接口的引用变量能够存储一个 指向对象的引用值,这个对象可以实现任何任何该接口类的实例,用户可以通过接口都调用该对象的方法。下面的代码演示了使用引用接口的过程



class JiekouYinYong {
public static void main(String[] args) {
Calculator cal = new Calculator(); Add add = cal; Subtract subtract = cal; Multiply multiply = cal; Divide divide = cal; //调用cal的方法 System.out.println("1200 + 1200 = " + cal.add(1200,1200)); System.out.println("2400 - 1100 = " + cal.subtract(2400,1100)); System.out.println("1100 * 1100 = " + cal.multiply(1100,1100)); System.out.println("5200 / 2 = " + cal.divide(5200,2)); //调用接口Add中的add方法 System.out.println("1200 + 1200 = " + add.add(1200,1200));
//调用接口Subtract中的subtract方法
System.out.println("2400 - 1100 = " + subtract.subtract(2400,1100));
//调用接口Multiply中的multiply方法
System.out.println("1100 * 1100 = " + multiply.multiply(1100,1100));
//调用接口Divide中的divide方法
System.out.println("5200 / 2 = " + divide.divide(5200,2));
}


}



interface Add {
int add(int a,int b);
}
interface Subtract {
int subtract(int a,int b);
}
interface Multiply {
int multiply(int a,int b);

}
interface Divide {
int divide(int a,int b);

}
class Calculator implements Add,Subtract,Multiply,Divide {
public int add(int a,int b) { return a + b; } public int subtract(int a,int b) { return a - b; } public int multiply(int a,int b) { return a * b; } public int divide(int a,int b) { return a / b; }


}

复制代码

运行结果



- 接口间的继承

接口间是完全支持多继承的,这和类不一样,即一个接口可以有多个直接父接口。和类的继承相似,子接口扩展某个父接口,并获得父接口里定义的所有抽象方法、常量属性、内部类和枚举类定义。


通过个具体的例子来看下



public class JiCheng { public static void main(String[] args) {
System.out.println(interfaceC.name_A); System.out.println(interfaceC.name_B); System.out.println(interfaceC.name_C);
}

}


interface interfaceA {
String name_A = "codevald";
void printA();
}

interface interfaceB {
String name_B = new String("codevald"); void printB();
}
interface interfaceC extends interfaceA,interfaceB {
String name_C = "code" + new String("vald"); void printC();

}

复制代码

在上面的代码中,接口 interfaceC 继承了 interfaceA 和 interfaceB,所以 interfaceC 获得了他们的常量。


看下运行结果



- 接口的私有方法

  • 在 Java7 或者更早的版本中,在一个接口里只能定义常量或者抽象方法这两种元素,不能再接口中提供方法的实现。如果要提供抽象方法和非抽象方法(方法与实现)的组合,只能使用抽象类

  • 在 Java8 版本中,在接口里引入了默认方法和静态方法这两个新功能。所以,在 Java8 版本的接口中,可以定义的成员有常量、抽象方法、默认方法和静态方法

  • 在 Java9 版本中,一个接口可以定义的成员有常量、抽象方法、默认方法、静态方法、私有方法和私有静态方法

来看下我编写的在 Java7、Java8、Java9 中接口方法的用法


在 7 版本或者更早的版本的接口中可能只包含抽象方法,这些接口方法必须由实现接口的类来实现


先定义 Interface7



public interface Interface7 { public abstract void method();}
复制代码

然后定义一个类实现接口



public class Class7 implements Interface7 {
public void method() { System.out.println("Hello,I am codevald."); }
public static void main(String[] args) { Interface7 instance = new Class7(); instance.method();
}
}
复制代码

执行结果



在 Java8 版本中,在接口中除了可以定义公共抽象方法外,还可以包含公共静态方法和公共默认方法


定义一个接口




public interface Interface8 {
public abstract void method1(); public default void method2() { System.out.println("Hello,I am default method."); } public static void method3() { System.out.println("Hello,I am static method."); }
}

复制代码

再定义一个类实现方法



public class Class8 implements Interface8 {
@Override public void method1() { System.out.println("public abstract method."); }

public static void main(String[] args) { Interface8 instance = new Class8(); instance.method1(); instance.method2(); Interface8.method3();
}
}

复制代码

看下运行结果



从 Java9 版本开始,就可以在接口中添加私有方法和私有静态方法了,这些私有方法可以提高代码的可重用性。方法之间需要共享代码,私有接口方法就允许这样做,但不能将私有方法暴露到它的实现类中。


先定义一个接口



public interface Interface9 {

public abstract void method1(); public default void method2() { method4(); method5(); System.out.println("Hello,I am default method.");
} public static void method3() { method5(); System.out.println("Hello,I am static method."); } private void method4() { System.out.println("Hello,I am private method."); } private static void method5() { System.out.println("Hello,I am private static method."); }

}

复制代码

再定义一个类



public class Class9 implements Interface9 {
public void method1() { System.out.println("Hello,I am abstract method."); } public static void main(String[] args) { Interface9 instance = new Class9(); instance.method1(); instance.method2(); Interface9.method3(); }

}

复制代码

看下运行结果



-接口和抽象类的区别联系

相同之处


  • 接口和抽象类都不能实例化

  • 接口和抽象类都可以包含抽象方法,实现接口或者继承抽象类的子类都必须实现这些抽象方法

不同之处


  • 接口中不包含构造器,抽象类可以包含构造器。抽象类里的构造器并不能创建对象,而是让子类调用这些构造器完成属于抽象类的初始化

  • 接口里不能包含初始化块,但抽象类可以包含初始化块

  • 一个类最多只能有一个父类,包含抽象类,但一个类可以实现多个接口



写到这里就结束啦,这些虽然是很基础的东西,不过后面设计模式,包括实际的项目实操,很多都会用到这些很基础的东西,如果基础不牢,很容易就半途而废,“泰山不让土壤,故能成其大;河海不择细流,故能就其深.”,一个想要走的更远的程序猿,更要注意平时底层基础的积累,这样子,遇到问题,才能不慌不忙的提出解决问题的方案。


如果觉得这篇文章不错的话,记得帮我 @codevald 点个赞哟,感谢您的支持!

发布于: 2021 年 02 月 01 日阅读数: 22
用户头像

codevald

关注

十年饮冰,难凉热血 2021.02.01 加入

想起来了没?看完就想起来了 公众号『CodeVald』

评论

发布
暂无评论
万字长文详细总结!关于继承、重写与重载、封装、接口的硬核干货