接口是Java语言中的一种引用类型,是方法的"集合",所以接口的内部主要就是定义方法,包含常量,抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法(jdk9)。接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。接口的使用,它不能创建对象,但是可以被实现(implements ,类似于被继承)。一个实现接口的类(可以看做是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的is-a关系,而接口实现则是 "能不能"的has-a关系。
定义格式
代码示例
public interface Demo {
// 常量(jdk7及其以前) 使用public static final关键字修饰,这三个关键字都可以省略
public static final int NUM1 = 10;
int NUM2 = 20;
// 抽象方法(jdk7及其以前) 使用public abstract关键字修饰,这2个关键字都可以省略
public abstract void method1();
void method2();
// 默认方法(jdk8) 使用public default关键字修饰,public可以省略,default不可以省略
public default void method3(){
System.out.println("默认方法 method3");
}
// 静态方法(jdk8) 使用public static关键字修饰,public可以省略,static不可以省略
public static void method4(){
System.out.println("静态方法 method4");
}
// 私有方法(jdk9) 使用private关键字修饰,private不可以省略
private static void method5(){
System.out.println("私有静态方法 method5");
}
private void method6(){
System.out.println("私有非静态方法 method6");
}
}
如何实现接口
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字
实现方式:类可以实现一个接口,也可以同时实现多个接口。类实现接口后,必须重写接口中所有的抽象方法,否则该类必须是一个“抽象类”。默认方法可以选择保留,也可以重写。 重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了
不能重写静态方法
代码示例
public interface IA {
public void show1();
}
public interface IB {
public void show2();
}
public class Zi implements IA, IB {
public void show1() {
}
public void show2() {
}
}
类可以在“继承一个类”的同时,实现一个、多个接口
public class Fu{}//父类
public interface IA{}//父接口
public interface IB{}//父接口
public class Zi extends Fu implements IA,IB{//一定要先继承,后实现
}
接口中成员访问特点
- 接口中的常量: 主要是供接口直接使用,推荐使用接口名直接调用
- 接口的抽象方法、默认方法:只能通过实现类对象才可以调用。接口不能直接创建对象,只能创建实现类的对象
- 接口中的静态方法: 对于接口的静态方法,直接使用“接口名.”进行调用即可。也只能使用“接口名."进行调用,不能通过实现类的对象进行调用
- 接口中的私有方法: 只能在接口中直接调用,实现类无法调用
interface IA {
// 常量
public static final int NUM = 10;
// 抽象方法
public abstract void method1();
// 默认方法
public default void method2(){
//method4();
//method5(); 私有方法: 只能在本接口中调用
System.out.println("IA 接口中的默认方法method2");
}
// 静态方法
public static void method3(){
//method5(); 私有方法: 只能在本接口中调用
System.out.println("IA 接口中的静态方法method3");
}
// 私有方法
private void method4(){
System.out.println("IA 接口中的私有方法method4");
}
private static void method5(){
System.out.println("IA 接口中的私有方法method5");
}
}
class Imp implements IA {
// 重写接口的抽象方法
@Override
public void method1() {
System.out.println("实现类重写IA接口中的抽象方法");
}
// 重写接口的默认方法
@Override
public void method2() {
System.out.println("实现类重写IA接口中的默认方法");
}
}
public class Test {
public static void main(String[] args) {
// 访问接口常量
System.out.println(IA.NUM);// 10 推荐
//System.out.println(Imp.NUM);// 10 不推荐 常量被实现类继承了
// 创建实现类对象调用方法
Imp imp = new Imp();
// 访问抽象方法
imp.method1();
// 访问默认方法
imp.method2();
// 接口名访问静态方法
IA.method3();
//Imp.method3();// 编译报错,没有继承
}
}
实现类实现多个接口,其中接口中发生了冲突怎么办?冲突就是说多个父接口拥有相同名字的方法或者属性
- 公有静态常量的冲突:实现类不继承冲突的常量
interface A{
public static final int NUM1 = 10;
}
interface B{
public static final int NUM1 = 20;
public static final int NUM2 = 30;
}
class Imp implements A,B{
}
public class Test {
public static void main(String[] args) {
//System.out.println(Imp.NUM1);// 编译报错,无法访问
System.out.println(Imp.NUM2);// 30
}
}
- 公有抽象方法的冲突:实现类只需要重写一个
interface A{
public abstract void method();
}
interface B{
public abstract void method();
}
class Imp implements A,B{
@Override
public void method() {
System.out.println("实现类重写");
}
}
public class Test {
public static void main(String[] args) {
}
}
- 公有默认方法的冲突:实现类必须重写一次最终版本
interface A{
public default void method(){
System.out.println("A 接口的默认方法method");
}
}
interface B{
public default void method(){
System.out.println("B 接口的默认方法method");
}
}
class Imp implements A,B{
@Override
public void method() {
System.out.println("实现类重写的默认方法");
}
}
public class Test {
public static void main(String[] args) {
Imp imp = new Imp();
imp.method();
}
}
- 公有静态方法的冲突:静态方法是直接属于接口的,不能被继承,所以不存在冲突
- 私有方法的冲突:私有方法只能在本接口中直接使用,不存在冲突
接口与接口之间的关系
接口可以“继承”自另一个“接口”,而且可以“多继承”。
代码示例
interface IA {//父接口
}
interface IB {//父接口
}
interface IC extends IA, IB {//是“继承”,而且可以“多继承”
}
接口多继承接口的冲突情况,我们应该怎么办?
- 公有静态常量的冲突:子接口无法继承父接口中冲突的常量
interface A{
public static final int NUM1 = 10;
}
interface B{
public static final int NUM1 = 20;
public static final int NUM2 = 30;
}
interface C extends A,B{
}
public class Test {
public static void main(String[] args) {
//System.out.println(C.NUM1);// 编译报错,说明无法继承
System.out.println(C.NUM2);// 30
}
}
- 公有抽象方法冲突 :子接口只会继承一个有冲突的抽象方法
interface A{
public abstract void method();
}
interface B{
public abstract void method();
}
interface C extends A,B{
}
class Imp implements C{
@Override
public void method() {
System.out.println("实现接口的抽象方法");
}
}
public class Test {
public static void main(String[] args) {
Imp imp = new Imp();
imp.method();
}
}
- 公有默认方法的冲突 :子接口中必须重写一次有冲突的默认方法
interface A{
public default void method(){
System.out.println("A 接口中的默认方法method");
}
}
interface B{
public default void method(){
System.out.println("B 接口中的默认方法method");
}
}
interface C extends A,B{
@Override
public default void method() {
System.out.println("重写父接口中的method方法");
}
}
class Imp implements C{
}
public class Test {
public static void main(String[] args) {
Imp imp = new Imp();
imp.method();// 重写父接口中的method方法
}
}
- 公有静态方法和私有方法:不冲突,因为静态方法是直接属于接口的,只能使用本接口直接访问,而私有方法只能在接口中访问,也没有冲突
实现类继承父类又实现接口时的冲突
- 父类和接口的公有静态常量的冲突:子类无法继承有冲突的常量
- 父类和接口的抽象方法冲突:子类必须重写一次有冲突的抽象方法
- 父类和接口的公有默认方法的冲突:优先访问父类的
- 父类和接口的公有静态方法:只会访问父类的静态方法
- 父类和接口的私有方法:不存在冲突
使用场景
额外的功能: 在接口中定义,让实现类实现
- 如果可以确定的通用功能,使用默认方法
- 如果不能确定的功能,使用抽象方法
共性的功能: 在父类中定义,让子类继承
- 如果可以确定的通用功能,使用默认方法
- 如果不能确定的功能,使用抽象方法
生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。程序中多态: 是指同一方法,对于不同的对象具有不同的实现.
前提条件
- 继承或者实现【二选一】
- 父类引用指向子类对象接口引用指向实现类对象【格式体现】
- 方法的重写【意义体现:不重写,无意义】
多态的体现:
父类的引用指向它的子类的对象
代码示例
class Animal{//父类
public void eat(){
System.out.println("吃东西");
}
}
class Dog extends Animal{//子类Dog
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
}
class Cat extends Animal{//子类Cat
@Override
public void eat() {
System.out.println("猫吃鱼...");
}
}
public class Test1 {
public static void main(String[] args) {
// 父类引用指向子类对象
Animal anl = new Dog();// 多态
anl.eat();// 狗吃骨头...
Animal anl1 = new Cat();
anl1.eat();// 猫吃鱼...
}
}
多态的几种形式
普通父类多态
public class Fu{}
public class Zi extends Fu{}
public class Demo{
public static void main(String[] args){
Fu f = new Zi();//左边是一个“父类”
}
}
抽象父类多态
public abstract class Fu{}
public class Zi extends Fu{}
public class Demo{
public static void main(String[] args){
Fu f = new Zi();//左边是一个“父类”
}
}
父接口多态
public interface A{}
public class AImp implements A{}
public class Demo{
public static void main(String[] args){
A a = new AImp();
}
}
多态情况下,创建子类对象,去访问成员,到底是访问父类的还是子类的??
成员变量的访问特点
-
编译看左边,运行看左边。简而言之:多态的情况下,访问的是父类的成员变量
成员方法的访问特点
- 非静态方法:编译看左边,运行看右边。简而言之:编译的时候去父类中查找方法,运行的时候去子类中查找方法来执行
- 静态方法:编译看左边,运行看左边。简而言之:编译的时候去父类中查找方法,运行的时候去父类中查找方法来执行
代码示例
class Animal {
int num = 10;
public void method1() {
System.out.println("Animal 非静态method1方法");
}
public static void method2() {
System.out.println("Animal 静态method2方法");
}
}
class Dog extends Animal {
int num = 20;
public void method1() {
System.out.println("Dog 非静态method1方法");
}
public static void method2() {
System.out.println("Dog 静态method2方法");
}
}
public class Test {
public static void main(String[] args) {
// 父类的引用指向子类的对象
Animal anl = new Dog();
System.out.println(anl.num);// 10
anl.method1();// Dog 非静态method1方法
anl.method2();// Animal 静态method2方法
}
}
多态的应用场景:
- 多态应用在形参实参:参数类型为父类类型,该参数就可以接收该父类类型的对象或者其所有子类对象
- 多态应用在数组:数组元素类型声明为父类类型,可以存储父类类型的对象或者子类的对象
- 多态应用在返回值:如果返回值类型为父类类型,那么就可以返回该父类类型的对象或者其所有子类对象
- 如果变量的类型为父类类型,该变量就可以接收该父类类型的对象或者其所有子类对象
多态的好处和弊端
- 好处:提高了代码的扩展性
- 弊端:多态的情况下,只能调用父类的共性内容,不能调用子类的特有内容。无法访问子类独有的方法或者成员变量,因为多态成员访问的特点是,编译看父类
引用类型转换
因为多态,我们就不能调用子类拥有,而父类没有的方法了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做类型转换。不管是向上转型还是向下转型,一定满足父子类关系或者实现关系
向上转型:当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型。此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了此时,一定是安全的,而且也是自动完成的。例如:
//父类是Animal 子类是Cat Aniaml anl = new Cat();
向下转型:当左边的变量的类型(子类)<右边对象/变量的类型(父类),我们就称为向下转型。此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了。
Aniaml anl = new Cat(); Cat c = (Cat)anl;//向下转型
不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常。
返回值:
- 如果前面变量指向的对象类型是属于后面的数据类型,那么就返回true
- 如果前面变量指向的对象类型不是属于后面的数据类型,那么就返回fals
所以,转换前,我们最好先做一个判断,代码如下:
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
}
总结一下
class Animal {//父类
public void eat() {
System.out.println("吃东西...");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
// 特有的功能
public void lookHome() {
System.out.println("狗在看家...");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼...");
}
// 特有的功能
public void catchMouse() {
System.out.println("猫抓老鼠...");
}
}
public class Test {
public static void main(String[] args) {
Dog d = new Dog();
method(d);
System.out.println("==========================");
Cat c = new Cat();
method(c);
}
// 形参多态: 如果父类类型作为方法的形参类型,那么就可以接收该父类类型的对象或者其所有子类的对象
public static void method(Animal anl) {
anl.eat();
//anl.lookHome();// 编译报错
// anl.catchMouse();// 编译报错
if (anl instanceof Dog) {
Dog d = (Dog) anl;// 向下转型 Dog类型
d.lookHome();
}
if (anl instanceof Cat) {
Cat c = (Cat) anl;// 向下转型 Cat类型
c.catchMouse();
}
}
}


