一、接口
1.1 概述
接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么 接口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法 (JDK 9)。
接口的定义和类的定义差不多,但是使用interface
关键字。他是不同于类的另一种引用类型。
引用数据类型:数组、类、接口
接口的使用步骤:
1.接口不能直接使用,必须有一个“实现类”来“实现”该接口 -> implements
格式:
public class 实现类名称 implements 接口名称{
//....
}
2.接口的实现类必须覆盖重写(实现)接口中所有的抽象方法。
实现:去掉abstract关键字,加上方法体大括号。
3.创建实现类的对象,进行使用。
需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。
public class Demo01Interface {
public static void main(String[] args) {
//错误写法!不能直接new接口对象使用
//MyInterfaceAbstract inter=new MyInterfaceAbstract();
//要使用接口的实现类的对象使用
MyInterfaceAbstractImpl impl=new MyInterfaceAbstractImpl();
impl.methodAbs();
impl.methodAbs1();
}
}
1.2 定义格式
public interface 接口名称{
//抽象方法
//默认方法
//静态方法
//私有方法
}
含有抽象方法
抽象方法:使用abstract
关键字修饰,可以省略,没有方法体,该方法供子类实现使用。
注意事项:
1.接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract
2.这两个关键字修饰符,可以选择性地省略。
3.方法的三要素可以随意定义。
public interface InterFaceName{
//这是抽象方法
public abstract void method();
//这些也是抽象方法
abstract void methodAbs1();
public void methodAbs2();
void methodAbs3();
}
含有默认方法和静态方法
默认方法:使用default
修饰,不可省略,供子类调用或者子类重写。
接口当中的默认方法,可以解决接口升级的问题。
默认方法例子:
当接口中有一个抽象方法,然后有了两个实现类,他们覆盖重写了接口的抽象方法,那么现在想要在接口中再添加几个方法,并且能让已存在的实现类也能调用方法,如果是抽象方法的话,需要重新将实现类进行覆盖重写新的抽象方法,所以不可取,这里我们运用默认方法,定义默认方法后,接口的实现类对象可以直接调用`b.methodDefault();`,**默认方法被调用时,如果实现类对象中没有该方法,他会向上到接口中去调用该方法**
- 接口的默认方法,可以通过接口实现类对象,直接调用
- 接口的默认方法,也可以被接口实现类进行覆盖重写
public class Demo02Interface {
public static void main(String[] args) {
MyInterfaceDefaultA a=new MyInterfaceDefaultA();
a.methodAbs();//调用抽象方法,实际运行的是右侧的实现类
//调用默认方法,如果实现类中没有,会向上找接口的默认方法
a.methodDefault();
MyInterfaceDefaultB b=new MyInterfaceDefaultB();
b.methodAbs();
b.methodDefault();//实现类B覆盖重写了接口的默认方法
}
}
输出结果:
实现了抽象方法,AAA
这是新添加的默认方法
实现了接口,BBB
实现类B覆盖重写了接口的默认方法
静态 :使用static
修饰,供接口直接调用。
静态方法使用:
注意:不能通过接口实现类的对象来调用接口当中的静态方法
正确用法:通过接口名称,直接调用其中的静态方法
格式: 接口名称.静态方法名(参数);
public class Demo03Interface {
public static void main(String[] args) {
//创建实现类对象(静态方法不需要对象)
MyInterfaceStaticImpl impl=new MyInterfaceStaticImpl();
//错误写法
//impl.methodStatic();
//直接通过接口名称调用静态方法(不需要new 对象)
MyInterfaceStatic.methodStatic();
}
}
含有私有方法和私有静态方法
私有方法:使用 private 修饰,供接口中的默认方法或者静态方法调用。
例子引入:
public interface MyInterfacePrivateA {
public default void methodDefault1(){
System.out.println("默认方法1");
// System.out.println("AAA");
// System.out.println("BBB");
methodCommon();
}
public default void methodDefault2(){
System.out.println("默认方法2");
// System.out.println("AAA");
// System.out.println("BBB");
methodCommon();
}
//发现默认方法1和默认方法2中有重复代码->输出AAA和BBB
//如果多添加一个默认方法来调用重复代码则会影响实现类
//所以创建一个私有方法来调用
private void methodCommon(){
System.out.println("AAA");
System.out.println("BBB");
}
}
1.普通私有方法:
解决多个默认方法之间重复代码问题
2.静态私有方法:
解决多个静态方法之间重复代码问题
//私有静态方法,只能在接口类中访问
private static void methodCommon(){
System.out.println("AAA");
System.out.println("BBB");
}
接口中定义常量
接口当中也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰
从效果上看,这骑士就是接口的【常量】
格式
public static final 数据类型 常量名称 = 数据值;
注意:
一旦使用final关键字进行修饰,说明不可改变。
接口的当中的常量可以省略 public static final,但是默认有这三个关键字
接口中常量的名称,使用完全大写的字母,并且用下划线分隔(推荐命名)
public interface MyInterfaceConst {
//这就是一个常量,一旦赋值,不可修改
public static final int NUM=10;
}
1.3 接口小结
1.4 接口使用过程的注意事项!
使用接口时,需注意:
1.接口是没有静态代码块或者构造方法的。
2.一个类的直接父类是唯一的,但是一个类可以同时实现多个接口
3.如果实现类所实现的多个接口当中,存在重复的抽象方法那么只需要覆盖重写一次即可
重复抽象方法需一次覆盖
public class MyInterfaceImpl implements MyInterfaceA,MyInterfaceB{
@Override
public void methodA() {
}
@Override
public void methodB() {
}
//覆盖重写了AB两个接口重复的方法(只需一次)
@Override
public void methodAbs() {
}
}
4.如果实现类没有覆盖重写所有接口当中的抽象方法,那么实现类就必须是一个抽象类
5.如果实现类所实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写(不管实现类是不是抽象类)
冲突默认方法必重写
public interface MyInterfaceA {
public default void methodDefault(){
System.out.println("默认方法AAA");
}
}
public interface MyInterfaceB {
public default void methodDefault(){
System.out.println("默认方法BBB");
}
}
public class MyInterfaceImpl implements MyInterfaceA,MyInterfaceB{
//必须覆盖重写冲突的默认方法
@Override
public void methodDefault(){
System.out.println("对多个接口当中冲突的默认方法进行了覆盖重写");
}
}
6.一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先用父类当中的方法
父类优先
public class Fu {
public void method1(){
System.out.println("父类方法");
}
}
public interface MyInterface {
public default void method1(){
System.out.println("接口的默认方法");
}
}
public class Zi extends Fu implements MyInterface{
}
public class Demo01Interface {
public static void main(String[] args) {
Zi zi=new Zi();
zi.method1();
//输出结果:父类方法
}
}
1.5 多继承[了解]
1.类与类之间是单继承的,直接父类只有一个。
2.类和接口之间是多实现的,一个类可以实现多个接口
3.接口和接口之间是多继承的
一个接口能继承另一个或者多个接口,这和类之间的继承比较相似。接口的继承使用 extends
关键字,子接口继 承父接口的方法。如果父接口中的默认方法有重名的,那么子接口需要重写一次。
必须进行默认方法的覆盖重写【而且带着default关键字】
interface A{
public default void method(){
System.out.println("AAAAAAAA");
}
}
interface B{
public default void method(){
System.out.println("BBBBBBBB");
}
}
定义子接口:
interface D extends A,B{
@Override
public default void method()
{
System.out.println("DDDDDDDDDD");
}
}
小贴士: 子接口重写默认方法时,default关键字可以保留。 子类重写默认方法时,default关键字不可以保留
接口中,无法定义成员变量,但是可以定义常量,其值不可以改变,默认使用public static final修饰。
接口中,没有构造方法,不能创建对象。
接口中,没有静态代码块
二、多态
2.1 概述
多态是继封装、继承之后,面向对象的第三大特性。
生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也 是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。
多态性指的是对象—>一个对象又是子类又是父类(小明是一个学生也是一个人类)
- 多态:是指同一种行为,具有多个不同表现形式
前提【重点】
1.继承或者实现【要产生上下关系】
2.方法的重写【意义体现:不重写,无意义】
3.父类引用指向子类对象【格式体现】
2.2 多态的体现
格式:
父类名称 对象名 = new 子类名称();
//或者:
接口名称 对象名 = new 实现类名称();
Fu f = new Zi();
f.method();
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写 后方法
代码如下:
定义父类
public abstract class Animal{
public abstract void eat();
}
定义子类
class Cat extends Animal{
public void eat(){
System.out.println("吃鱼");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("吃骨头");
}
}
定义测试类
public class Test{
public static void main(String[] args){
//多态形式,创建对象
Animal a1=new Cat();
//调用的Cat 的eat
a1.eat(); //吃鱼
//多态形式,创建对象
Animal a2=new Dog();
//调用的Dog的eat
a2.eat(); //吃骨头
}
}
2.3 多态的好处
测试代码:
public class Test{
public static void main(String[] args){
//多态形式,创建对象
Cat c=new Cat();
Dog d=new Dog();
//调用showCatEat
showCatEat();
//调用showDogEat
showDogEat();
/*
以上两个方法,均可以被showAnimalEat(Animal a)方法所替代
而执行效果一致
*/
showAnimalEat(c);
showAnimalEat(d);
}
public static void showCatEat (Cat c){
c.eat();
}
public static void showDogEat (Dog d){
d.eat();
}
public static void showAnimalEat (Animal a){
a.eat();
}
}
由于多态特性的支持,showAnimalEat方法的Animal类型,是Cat和Dog的父类类型,父类类型接收子类对象,当 然可以把Cat对象和Dog对象,传递给方法
当eat方法执行时,多态规定,执行的是子类重写的方法,那么效果自然与showCatEat、showDogEat方法一致, 所以showAnimalEat完全可以替代以上两方法。
所以,多态的好处,体现在,可以使程序编写的更简单,并有良好的扩展
2.4 引用类型转换
多态的转换分为向上转换和向下转换两种
向上转换
多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。
当父类引用指向一个子类对象时,便是向上转型。
类似于 double num=100;//正确,int—>double,自动类型转换
向上转型一定是安全的
使用格式:
父类类型 变量名 = new 子类类型();
如:Animal a = new Cat();
向下转型
多态本身是父类类型向子类类型向下转型的过程,这个过程是强制的。
一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
使用格式:
子类类型 变量名 = (子类类型) 父类变量名;
如:Cat c=(Cat) a;
对象的向下转型其实是一个还原的动作 将(向上转型后的)父类对象【还原】为本来的子类对象
为什么要转型
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥 有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点”小麻烦”。所以,想要调用子 类特有的方法,必须做向下转型(还原)。
abstract class Animal{
abstract void eat();
}
class Cat extends Animal{
public void eat(){
System.out.println("吃鱼");
}
public void catchMouse(){
System.out.println("抓老鼠");
}
}
class Dog extends Animals{
public void eat(){
System.out.println("吃骨头");
}
public void watchHouse(){
System.out.println("看家");
}
}
定义测试类:
public class Test{
public static void main(String[] args){
//向上转型
Animal a= new Cat();
a.eat(); //调用的是Cat的eat
//向下转型
Cat c=(Cat)a;
c.cathMouse();
//这样才能调用Cat类独有的catchMouse方法(Animal类中没有)
}
}
转型的异常
转型的过程中,一不小心就会遇到这样的问题,请看如下代码
public class Test{
public static void main(String[] args){
//向上转型
Animal a= new Cat();
a.eat();
//向下转型
Dog d=(Dog) a;
d.watchHouse(); //调用的是Dog的watchHouse【运行报错】
}
}
所以说向下转型是一个还原的过程,原来是Cat只能还原为Cat;
报出ClassCastException
异常,
为了避免ClassCastException
的发生,Java提供了 instanceof
关键字,给引用变量做类型的校验,格式如下:
变量名 instanceof 数据类型
如果变量属于该数据类型,返回true。
如果变量不属于该数据类型,返回false。
转型前先做判断
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
}