接口
概念
接口是功能的集合,它同样是一种引用数据类型,可以把接口看作抽象类更为抽象的 "类"。
接口只描述所应该具备的功能方法,但是没有具体的方法实现,即接口中具有的都是抽象方法,这些抽象方法的实现是由接口的实现类(相当于接口的子类)来完成,这样就可以将功能的定义和具体实现相分离,从而可以优化程序的设计。
一切事物均有功能,即一切事物均有接口。
接口的定义
它与类的定义不同,类定义使用的是关键字 class,而接口的定义使用的是关键字 interface。
public interface 接口名{抽象方法1;抽象方法2;抽象方法3;....
}
注意:
(1)接口中无法定义声明普通的成员变量;
(2)接口中的方法全部是 public abstract 修饰的可以公共访问的抽象方法,但是 public abstract 可以省略。
类实现接口
类与接口之间不再是继承关系,类和接口的关系为实现关系,也就是类实现了接口。跟类与类之间的继承相似,但是使用的关键字不同,继承使用的关键字是 extends,但是实现使用的关键字是 implements。
实现类(实现接口的类)实现接口,其实就相当于声明:"这个类具备了接口中的功能",也就是实 现类会继承接口中方法,这样一来实现类要么是个抽象类,要么实现类实现(重写)接口中的全部的抽象方法。
格式:
修饰符 class 类名 implements 接口{//对接口中所有抽象方法的重写
}
在类实现了某个接口之后, 该类就会将接口中所有的抽象方法都继承过来,这个时候就需要对这些 继承而来的抽象方法进行重写,对这些功能是如何具体实现的进行完善,否则该类必须是一个抽象类 (抽象方法所在的类必定是抽象类)。
接口的注意事项
(1)接口中可以声明定义成员变量,但是必须使用固定的修饰符 -- public static final 修饰,而且改变 了在声明的时候就需要为其指定值。final 修饰的成员变量的值是不能够再改变,同时使用了 static 修 饰,因此我们也称接口中的变量为静态常量,与此同时变量名习惯性的使用全部大写的方式,如果这个 变量名是由多个单词构成的,为了区分多个单词,在多个单词之间使用下划线 "_" 连接。
public static final int PAGE_SIZE = 10;
(2)接口中定义的抽象方法都有固定的修饰符 -- public abstract,但是在实际声明接口中的功能方法的时候,我们可以把修饰符 public abstract 省略,或者省略其中的一部分,但是要注意的是,即使我 们省略了,它默认也是使用 public abstract 修饰符进行了修饰。
(3)接口跟抽象类一样不可以直接创建对象。
(4)接口的实现类必须对接口中所有的抽象方法进行重写,然后才能够通过接口的实现类创建接口的实例对象,否则由于实现类中该存在抽象方法,实现类是一个抽象类,还是无法创建实例对象。
接口的多实现
解决类无法多继承的弊端,将多继承这种机制在 Java 中通过接口的多实现来完成。
示例:
public interface Fu1{void show1();
}public interface Fu2{void show2();
}public class Zi implements Fu1,Fu2{ //Zi 类同时实现了 Fu1 和 Fu2 接口public void show1(){//对 show1() 方法的重写}public void show2(){//对 show2() 方法的重写}
}
为什么我们要定义接口?
接口是用来定义功能的,我们发现使用抽象也可以完成(使用抽象方法定义功能),但是由于类与类之间的单继承的局限性,如果我有一个类拥有多个功能,但是这多个功能定义在多个抽象类中,这个时候通过继承就无法实现了,因为我们无法让一个类同时继承多个抽象类,这个时候接口恰恰可以解决类单继承的弊端,因为接口可以多实现,我们可以将多个功能方法定义在多个接口中,然后让该类同时实现多个接口,即一个类可以同时实现多个接口。
Java中为什么不使用多继承?
如果 Java 中使用多继承,当某个类同时继承自多个父类,而多个父类都拥有同一功能的方法(方法 声明相同但是实现不同),这样一来在子类中调用该功能方法的时候,会现在子类中寻找是否存在该声明的方法,如果不存在则到父类中寻找,但是由于子类同时继承自多个父类,而这多个父类的每一个类 中都存在这么一个方法,这个时候方法调用的时候就产生了不确定性,不知道具体调用哪一个父类中的方法才是正确,但是对于接口而言就不会出现这样的问题,因为接口中的方法只有声明部分,非抽象子类必然会对接口中的方法进行重写,方法在实际调用的时候调用的是实现类中的具体方法,不会产生调用的不确定性。
类继承类同时实现接口
接口和类是同时实现产生关系的,而类与类之间是通过继承产生关系的,因此一个类在继承自一个 父类的同时,还可用实现多个接口,即子类继承自父类,拥有了父类一切可继承的属性和方法,同时还可以通过实现接口扩展一些额外的从父类那无法继承得到的功能方法,即让类继承自父类的同时,实现其他接口。
接口的出现避免了单继承的局限性。父类中定义的事物的基本功能。接口中定义的事物的扩展功能。
示例:
public class Fu(){public void method(){//.......}
}public interface MyInterface(){void show();
}public class Zi extends Fu implements MyInterface{public void show(); //注意实现接口必须重写接口中所有的抽象方法
}
接口的多继承
接口和接口之间也可以通过继承产生关系,即通过关键字 extends 进行继承,但是它跟类与类之间的 单继承又有所不同,接口和接口之间是可以多继承的。
public interface Fu1{void show1();
}public interface Fu2{void show2();
}public interface Fu3{void show3();
}public interface Zi extends Fu1,Fu2,Fu3{ //子接口同时继承自 Fu1,Fu2,Fu3这三个接口//该子接口同时拥有了 Fu1,Fu2,Fu3 这三个父接口的方法声明,但是没有具体实现
}
在实际的开发的过程中,如果在多个接口中存在相同的方法,这个时候即使某个类同时实现了这具有相同方法的多个接口,也表示该类具有该功能,在类中只需要对这功能进行重写即可,在实际调用的时 候,调用的是重写的方法,因此不会产生调用的不确定性。
接口在开发中的它好处
1、接口的出现扩展了功能;
2、接口其实就是暴漏出来的规则;
3、接口的出现降低了耦合性。
总结
类与类之间是继承关系 ----- 单继承
类与接口之间是实现关系 ----- 多实现
接口与接口之家是继承关系 ----- 多继承
接口和抽象类的区别和联系
1、相同点:
(1)都位于继承体系的顶端,都是用来被其他类继承或者实现的。
(2)都不能够直接创建实例对象。
(3)都可以包含抽象方法,但是其子类或者实现类必须对所有的抽象方法进行重写,否则这个子类或者实现类还是一个抽象类(因为还具有没有重写的抽象方法)。
2、区别:
(1)抽象类中可以有具体的方法,接口只能包含抽象方法。
(2)一个类只能够继承自一个直接父类(可能是抽象类,也可以是非抽象类),但是却可以实现多个接口(同时拥有多个功能)。
(3)抽象类是某个事物应该具备怎么样的内容,继承体系使用is .. a关系。
(4)接口是某个事物额外的内容,继承体系是一种like .. a关系。
很多场景接口和抽象类都可以使用,那么该如何选择?
(1)优先使用接口,尽量避免使用抽象类。使用接口之后,我还能够实现其他接口以及继承自其他类, 但是使用抽象类之后就不能够再继承自其他类了。
(2)如果某个类既要定义行为功能(方法),又要定义一些公共的属性(成员变量),这个时候就需要使用抽象类因为接口中无法随意的定义普通的成员变量。
多态
面向对象的三大特征:封装、继承、多态。(封装和继承前面已经说了)
概念
Java 中可以从多个角度以多种形态去描述一个事物或者个体,子类类型的对象不仅仅能够使用子类类型去描述,还可以使用父类类型描述,即可以用父类类型去描述一个子类类型的对象,从中可以发现,要想让一个事物或者个体呈现出不同的形态,那么多种形态之间必须是有联系。
在 Java 中多态的代码体现就是一个子类类型的对象既可以赋值给子类类型的变量,也可以赋值给父类类型的变量(使用父类类型的变量去引用一个子类类型的对象)。
多态最终体现: 父类类型的引用变量指向子类类型的对象。
多态的前提: 必须是父子关系的类或者类实现接口,否则就不能够又多态的说法。
多态的定义
(1)普通父子类之间的多态定义格式:
父类类型 变量名 = new 子类类型();
(2)抽象父类和子类之间的多态定义格式:
抽象父类类型 变量名 = new 子类类型();
(3)类实现接口之间的多态定义格式:
接口类型 变量名 = new 实现类类型();
多态的成员变量的特点
当子父类中出现同名的成员变量的时候,通过变量去调用对象的属性的规则如下:
编译时期: 引用变量能够调用哪些成员变量,参考的是引用变量类型所属的类中是否拥有可以被调用的成员变量,如果有,则可以正常编译,如果没有,则编译出错。
运行时期: 当编译成功之后,运行的时候,具体调用的是哪一个类中的成员变量,也是参考引用变量的类型。
总结: 编译看左边,运行也看左边,即多态中成员变量的编译和运行都看等号左边(变量的类型)。
示例:
public class Fu {String name = "爸爸";
}public class Zi extends Fu{String name = "儿子";
}public class Test {public static void main(String[] args) {//子类类型的变量指向子类类型的对象Zi zi = new Zi();//父类类型的变量指向子类类型的对象Fu fu = zi;System.out.println(zi.name);System.out.println(fu.name);}
}
运行结果:
多态的成员方法的特点
当子父类或者实现类和接口中出现了相同的方法的时候(重写、覆盖、@Override),使用变量调用方法的规则如下:
编译时期: 引用变量能够调用哪些成员方法,参考的是参考的是引用变量类型所属的类中是否拥有可以被调用的成员方法,如果有,则可以正常编译,如果没有,则编译出错。
运行时期: 当编译成功之后,运行的时候,具体调用的是哪一个类中的成员方法,参考的是引用变量指向的对象的具体类型。
总结: 编译看左边,运行看右边。
示例:
public class Fu {public void fu(){System.out.println("父类中的方法!");};
}public class Zi extends Fu{public void fu() {System.out.println("子类重写父类中的方法!");}public void zi() {System.out.println("子类中的方法!");}
}public class Test {public static void main(String[] args) {//子类类型的变量指向子类类型的对象Zi zi = new Zi();//父类类型的变量指向子类类型的对象Fu fu = zi;zi.zi();fu.fu();}
}
运行结果:
多态的总结
父类类型的变量可以指向子类类型的对象,能够调用哪些成员变量(属性)以及具体调用的是那个类中的成员变量都由变量的类型决定。
父类类型的变量可以指向子类类型的对象,能够调用哪些成员方法看变量的类型,具体调用哪一个方法由对象的具体类型决定。
对于成员变量我们以后都会使用关键字 private 修饰,因此对于成员变量我们不能够直接访问,因 此多态更多针对的是成员方法。
多态--转型
向上转型
把子类类型的对象赋值给父类类型的变量,这就是向上转型,而我们学习的多态本身就是向上转型 的过程。
示例:
Zi zi = new Zi();
Fu fu = Zi;
父类类型 变量名 = new 子类类型();
向下转型
把一个已经向上转换的子类类型的对象,使用强制转换的格式,将父类类型的引用转换为子类类型 的引用,这个过程我们称之为向下转型。
Fu fu = new Zi(); //将子类类型的对象赋值给父类类型的对象 -- 向上转型
Zi zi = (Zi)fu; //将符类型的引用转换为子类类型的引用 -- 向下转型
子类类型 变量名 = (子类类型)父类类型的变量; //前提是父类类型的变量指向的是子类类型对象
如果我们直接创建一个父类类型的对象并使用父类类型的变量去引用,然后通过强制转换的方式让 子类类型的变量引用指向父类类型的变量引用,这个时候不能够向下转型,因为子类类型的引用变量是 无法指向父类类型的对象的。
Fu fu = new Fu();
Zi zi = (Zi)Fu(); //错误的用法
了解instanceof 关键字
主要作用: 判断某个对象是否属于某一种引用数据类型,返回值是 boolean 类型的。
使用格式:对象 instanceof 数据类型。
示例:
Fu fu = new Zi(); //将子类类型的对象赋值给父类类型的对象 -- 向上转型
Zi zi = null;
//满足父类类型的变量指向的是子类类型的对象的时候,才进行向下转型
if(fu instanceof Fu){zi = (Zi)fu; //将符类型的引用转换为子类类型的引用 -- 向下转型
}
当父类类型的变量指向子类类型的对象的时候,会发生向上转型的过程,这个时候子类类型对象的引用就会有子类类型转换为父类类型。
使用 instanceof 关键字对对象的类型进行判断,判断满足条件之后再进行向下转型,它可以有效的避免发生转换异常:java.lang.ClassCastException。
多态的好处和弊端
向上转型的好处: 隐藏了子类类型,提高代码的扩展性。
向上转型的弊端: 只能够使用父类共性的内容,而无法使用子类独有的功能(编译看变量的类型)。
向下转型的好处: 可以使用子类特有的功能。
向下转型的弊端: 万一转换的时候类型不匹配,容易发生 java.lang.ClassCastException 类型的转换异常,因此建议在转换之前进行对象的类型判断。
什么时候使用向上转型
当我们不需要直接面对子类类型的时候,通过扩展性或者父类的功能就已经能够完成相应的逻辑操作了,这个时候就可以使用向上转型。
Person person = new Student();
//调用对象中的方法,会执行 Student 类中的 study() 方法,而不是 Person 类中的 study() 方法
person.study();
什么时候使用向下转型
当需要使用子类特有的功能的时候,就需要使用向下转型了。
Student student = (Student)person; //向下转型
student.study(); //此时就可以调用 Person 类中独有的 study() 方法
面向对象的三大特性总结
封装: 让对象的属性和方法的实现细节隐藏起来,只提供了对外的公共的访问方式,同时提高了代码的复用性。
继承: 让类与类之间产生了关系,子类会自动继承父类一切可以继承的属性和方法,提高了代码的复用性,同时为多态提供了前提。
多态: 配合继承(实现接口)和方法的重写,从而提高代码的复用性和扩展性,如果没有方法的重写,多 态则没有任何意义。