JAVA学习第六天之接口与抽象类

学习目的

  1. 掌握static关键字和final关键字的含义及用法
  2. 掌握抽象类与接口的概念及用法
  3. 了解抽象类与接口的区别
  4. 了解使用UML图表达Java类之间的关系
  5. 掌握is-a、is-like-a、has-a的含义

一.static关键字

  1. 定义
    static是一个关键字,表示"静态的",可以用来修饰变量、方法、代码块等,使用static修饰的变量叫做静态变量,修饰的方法叫做静态方法,修饰的代码块叫做静态代码块。
  2. 特点
  • 凡是static 修饰的都是类级别相关的,不需要创建对象,通过类名.即可访问;
  • static修饰过的静态变量、静态方法、静态代码块等都在类加载时初始化,存储在方法区当中;
  1. 常见问题
  • 在static修饰的静态上下文中无法访问非静态(无static修饰)的方法或变量;
  • 原因:static修饰的相关,在类加载时就已经加载进去JVM的方法区内存,而非static修饰的变量、方法是存放在堆内存或者栈内存中,必须要对象先创建出来了才能调用。

1.1 静态代码块

  1. 特点
  • 一次性:静态代码块在类加载时执行,并且只执行一次;
  • 静态代码块当中的代码 总是在 main 方法执行之前执行;
  • 一个类当中可以编写多个静态代码块,并且静态代码块遵循自上而下的顺序依次执行。
  1. 作用
  • 在类加载时、在main方法执行前,想只执行一次某段代码;
    如:在类加载时解析某个文件,并且要求该文件只解析一次,此时可以把解析该文件的代码写到静态代码块中。
  1. 语法格式
类{
  //静态代码块
    static{
      java 语句; 
    } 
}

二.Final关键字

  1. 定义
    final关键字表示最终的,不可变的。用于声明属性、方法和类,分别表示属性不可变,方法不可重写,类不可继承。
  2. 特点
  • final 修饰的变量必须显示初始化,且只能初始化一次,后续不能修改;
  • final修饰的类不可以被继承,final修饰的方法可以被继承但不能被重写;
  • final修饰的变量是常量,与static一起使用表示该变量不可改变;
  • final修饰的引用不可以重新赋值,只能指向初始对象的地址,不能重新指向新对象的地址;
  • final修饰的引用不能重新指向地址,但可以对其类属性进行重新赋值;

三.Abstract类

3.1 抽象类

  1. 概念
    抽象类是采用 abstract关键字定义的类,属于引用数据类型。抽象类是源自生活中高度抽象,是现实中不存在但是可以"具象化"的意识物体。抽象类无法实例化和创建对象,但可以被类继承,且只能被继承。
  2. 实质
    java类 是由现实中的对象抽象出来的,是现实物体具有共同特征的抽象集合;抽象类则是在 类上的再抽象,属于意识形态的、虚构的。
  3. 定义格式
  • 类名:抽象类使用abstract修饰class,但不能与final、private一起使用(抽象类天生用来继承和方法重写,而final修饰的类天生不能被继承,private修饰的方法不能被重写);
  • 属性:抽象类中建议定义一些非抽象的、公共的方法和属性,供子类直接继承使用,不需要子类重复定义,从而满足开闭原则的扩展
  • 方法:抽象类中的方法可以是abstract修饰的,也可以不是abstract修饰的(如构造器),但使用了abstract修饰方法的类一定是抽象类。
  1. 抽象类无法实例化原因
  • 实质原因:抽象类是 由本身已经抽取出共同特征的类,再对多个类的共同部分进行抽象的抽象;
  • 过程原因:由类到对象的过程称为实例化,实例化意为产生现实的例子,抽象类是抽象的抽象,不存在现实例子;
  • 不可跨级实现:对象 → 类 → 抽象类 → 上层抽象类,现实 → 抽象 → 抽象。
  1. 现实与抽象类图解


    实例解析抽象类.png

3.2 抽象方法

  1. 概念
    抽象方法即采用了abstract关键字修饰的方法。抽象方法是一类对现实行为抽取出共同部分的方法,抽象方法并没有具体实现(无方法体),只能由子类继承重写才有具体的实现。如游泳是蛙泳、蝶泳、自由泳、混合泳、接力泳的一个共性部分,怎么游没有具体实现。
  2. 特点
  • 必须有abstract修饰,无方法体,无具体实现,只能被重写实现;
  • 接口中的方法都是抽象方法,而抽象类中的方法不一定全都是抽象方法;
  • 如果有一个方法为抽象的,那么此类必须为抽象的;如果一个类是抽象的,并不要求该类一定具有抽象的方法;
  • 若一个子类继承了抽象类(包括接口),必须重写所有的抽象方法;
  • 抽象方法不能直接调用。
  1. 特殊点
  • 抽象类有构造方法 且 必须要有无参构造方法(不管是手动无参还是默认无参);
  • 在子类继承抽象类的时候,会默认有super()调用抽象类的构造方法(有参/无参),因此 若抽象类没有构造方法时,出现编译不通过错误。

3.3 注意点(重点)

  1. 子类与抽象父类
    继承了抽象类的子类不是必须重写父类的所有方法,分以下两种情况:
  • 父类方法为抽象方法时,子类必须重写(实现)所有父类的抽象方法;
  • 父类方法为普通方法时,子类可以重写父类方法,也可以不重写;
  • 父类方法为抽象方法,但当子类也为抽象类时,也可以不用重写父类的抽象方法。
  1. 子类与接口
    接口为完全抽象(接口方法全部为抽象方法),子类实现接口时,必须重写覆盖接口的所有方法。
  2. 抽象类与抽象方法
  • 抽象类中可以没有抽象方法,但抽象方法必须出现在抽象类中<接口也是抽象类>;
  • 子类继承并重写抽象父类的抽象方法,但由于子类不是抽象类,重写后的方法也就不是abstract抽象的,而是"具实现"的;
  • 抽象方法不能是abstract和final共同修饰,但被子类继承并重写了的abstract方法,可以使用final重新修饰

四.Interface类

4.1 接口

  1. 概念
    接口是使用interface 关键字修饰的特殊"抽象类",取代了abstract关键字的修饰,但是接口中的方法都是抽象的。接口是现实中具有共性部分的行为集合,接口里只存在抽象方法和常量。
  2. 组成
    接口属于完全抽象的,因此在接口中只能定义抽象方法(无方法体实现);并且接口是现实的共性部分,因此接口的属性都是常量,不可改变的。
    接口 = 常量 + 抽象方法
  3. 实质
    接口属于"引用数据类型",但完全抽象的引用类。
  4. 特点
  • public修饰:抽象的属性和方法都是可以提供给所有类使用,因此接口的元素都是public公开的;
  • 属性变量:接口中的变量默认都是 public static final修饰的,都是常量不可更改,必须显示的初始化;
  • 方法体:接口中的方法默认都是 public abstract修饰的,不能更改;
  • 多态:接口的方法可以在不同的地方被不同的类实现,而这些不同的具体实现是不同子类的特性行为。
  1. 作用
  • 明确功能:采用通俗的接口名,可以明确的声明接口所能提供的服务;
  • 解耦:将方法抽取到接口中实现,降低类与类之间的耦合度,降低继承的使用;
  • 接口隔离原则:将不同性质的方法行为抽取到不同的接口中,避免堆积在一个类中;增强类本身的实质,同时提高扩展性;
  • 面向抽象编程,面向接口编程,满足开闭原则。

4.2 接口语法

  1. 接口语法结构
[修饰符列表] interface 接口名{
     public static final  数据类型 常量名;//public static final 可以省略
     public abstract  数据类型 方法名(参数类型 args);//public abstract可以省略
}
  1. 接口实现
  • 接口只能通过implements 关键字被类实现;
  • 一个类实现了接口,接口中所有的方法也必须重写实现;
  • 接口与接口之间支持多继承,接口与实现类之间支持多实现;
  • 接口不是具体类,不能new 接口。但可以接口名 变量名 = new 实现类();
  • 接口就是父类,将接口当作"抽象的父类"可以实现多态。
//实现与继承同时存在:extends在前,implements在后
interface Flyable{  
  void fly();  
}
class Animal{  
}
class Cat extends Animal implements Flyable{
    public void fly(){
        System.out.println("飞猫起飞,翱翔太空的一只猫!!");
    }
}
class Pig extends Animal implements Flyable{
    public void fly(){
        System.out.println("我是一只会飞的猪!!!");
    }
}
// 鱼没extends动物类,但默认继承Object
class Fish implements Flyable{ 
    public void fly(){
        System.out.println("我是六眼飞鱼(流言蜚语)!!!");
    }
}

public static void main(String[] args){
        // 使用多态--创建对象(表面看Animal类没起作用!)
        Flyable f = new Cat(); 
        f.fly();
        Flyable f2 = new Pig();
        f2.fly();// 调用同一个fly()方法,最后的执行效果不同。

        //没有继承animal,但默认继承object,可以实现fly()方法
        Flyable f3 = new Fish();
        f3.fly();//调用Fish的fly()方法
    }
  1. 注意点
  • 在普通的类继承中,不允许没有继承关系的三个或以上的类进行强转(父亲,哥哥,弟弟,堂弟;动物,猫,狗,鱼)
  • 在接口中,允许没有继承关系的类进行强转,起码在编译期时不会出现错误,运行期可能报错也可能不报错。
interface K{  }
interface M{  }
class E implements M{  }

    M m = new E();
    //接口和接口之间没有继承关系也可以进行强制类型转换 
    //但一定要注意,(编译通过)运行时出现ClassCastException异常
    //K k = (K)m;// ClassCastException异常?
    //避免异常,最好使用instanceof 判断再强转
    if(m instanceof K){
       K k = (K)m;
    }

interface A{  void m1();  }
interface B{  void m2();  }
interface C{  void m3();  }
// 实现多个接口,变向实现多继承。
class D implements A,B,C{
    // 实现A接口并重写m1()方法
    public void m1(){  }
    // 实现B接口中并重写m2()方法
    public void m2(){
        System.out.println("m2 ....");
    }
    // 实现接口C中并重写m3()方法
    public void m3(){  }
}
     A a = new D();
     B b2 = new D();
    //编译和运行可以通过,但要调用其他接口的方法需要强制转型(接口转型)
     B b2 = (B)a;
     b2.m2();

4.3 接口作用

  1. 继承
    间接完成"多继承",类之间不能实现多继承,但接口之间允许多继承,而类又可以实现多个接口,利用"等同关系"。
  2. 耦合度
    解决类与类之间的耦合度,将本来直接联系的类,抽取出一个共同联系的接口,通过接口进行连接。如(顾客-菜单-厨师<菜式>、电脑-槽口-硬件设备)
  3. 简洁开发步骤
    面向接口的开发步骤:项目经理负责编写接口,项目接口的组1编写调用者类,项目组2编写接口的具体实现类,互不干扰
  4. 功能粒度
  • 细分:把接口粒度划分细了,使功能定义的含义更明确
  • 粗分:采用一个大的接口定义所有功能,替代多个小的接口

4.4 接口调用

调用--接口--实现类的关系.png
//测试接口的创建、调用、与实现
public class Test{
    public static void main(String[] args){
        // 创建厨师对象,利用面向接口的多态
        FoodMenu cooker1 = new ChinaCooker();//中餐厨师
        FoodMenu cooker2 = new AmericCooker();//西餐厨师
        // 创建顾客对象
        Customer customer = new Customer();
      
        // 顾客点菜--点中餐
        customer.order(cooker1);
        // 顾客点菜--点西餐
        customer.order(cooker2);
    }
}

4.5 接口与JDK新特性

  1. JDK8新特性
  • defalut默认实现:在 JDK8之前,接口是一个完全抽象的类,不能有任何的方法实现。从JDK8开始,接口提高defalut方法实现;
    这是因为不支持默认方法的接口的维护成本太高了 --在 Java 8之前,如果一个接口想要添加新的方法,需要修改所有实现了该接口的类。

4.6 接口与实际开发

  1. 接口抽取
    架构师/开发经理:根据需求分析,定义好接口(如菜单接口)
  2. 接口使用
    项目组A:根据规范,面向接口,编写接口的调用类(如顾客类)
    项目组B:根据规范,面向接口,编写接口的实现类(如初始类)
    项目组C:根据规范,面向接口,编写测试类
  3. 接口实现
    开发互助:接口定义好后,项目组A与B可以同时开发编写,A与B都只关心接口,互不关心各自的具体实现,因此同时开发可以提高工作效率。
  4. 项目实现
    最终项目也是通过接口完成拼接(积木:模块 → 接口 → 模块→ 接口 → 模块→ 接口)

五.类与类之间的关系

  1. 概念
    UML又叫统一建模语言,是一种为面向对象的产品进行说明、可视化和编制文档的一种标准语言,是面向对象设计的建模工具,独立于任何具体程序设计语言。
  2. 表现形式
    UML独立于程序设计语言存在,主要是通过** 图表/图形 **的形式来表现出一个面向对象的软件产品中,类与类之间、模块与模块之间、功能与功能之间的关系。通过图表可以让一个产品更加容易读懂。

5.1 UML类图

  1. 泛化关系(继承关系)
    泛化关系又叫继承关系,指的是子类与父类之间,接口与接口之间的关联关系。


    继承关系.png
  2. 实现关系
    实现关系描述的是类对接口的实现。


    实现关系.png
  3. 关联关系
    类与类之间的连接,一个类可以作为另一个类的属性和方法,类作为成员变量的形式体现
    关联关系.png
  4. 聚合关系
    聚合关系是关联关系的一种,是程度较强的关联关系。与关联关系不同,关联关系的类处在同一个层次上,而聚合关系的类处在不平等的层次上。
  • 强调整体和部分的关系,整体和部分使用实例变量体现<生命周期-不同步>。如:汽车<类> 和 轮胎<实例变量,属性>,汽车没了,轮胎拆出来还能用。
    聚合关系.png
  1. 合成关系(组合关系)
    组合关系,是关联关系的一种,程度比聚合关系强的关联关系。整体决定部分的生命周期,部分对象每一时刻只与一个对象发生合成关系。
  • 强调整体-决定-部分,使用实例变量体现<生命周期同步>。如:人和四肢,人和五官,人没了,四肢五官也就没了。
    组合关系.png
  1. 依赖关系
    依赖关系,关联关系的一种,程度较弱的关联关系,体现为返回值,参数,局部变量和静态方法调用。


    依赖关系.png

5.2 is-a<继承>

  1. 概念
    is-a表现的是一种继承关系,描述为"A是一个B,A是B的一个孩子"。
  2. 代码示例
  public class A {
    public void method1() {}
  }
  public class B extends A {
      //重写继承于A的方法
    public void method1() {
    } 
  }

5.2 like-a<接口>

  1. 概念
    like-a表现的是一种实现关系,描述为"A像一个B,A和B拥有一样的普遍性特征"
  2. 代码示例
  public interface I {
      public void method1() ;
  }
  public class A implements I {
      public void method1() {
         //具体实现
      } 
}

5.3 has-a<成员变量>

  1. 概念
    has-a表现的是一种拥有关系,描述为"A拥有B,B是A的一部分组成"。
  2. 代码示例
  public class A {
    private B b;
  }
  public class B {
  }

常见面试题

  1. java语言中凡是没有方法体的方法都是抽象方法,对吗?
    答:
    不对,错误的。
    Object类中就有很多方法都没有方法体,都是用native关键字修饰,且以“;”结尾的,但都不是抽象方法。例如:public native int hashCode();方法底层调用了C++写的动态链接库程序。修饰符列表中没有abstract而是有一个native,表示调用JVM本地程序。
  2. 接口为什么离不开多态?
    答:
    面向接口编程和面向抽象编程的实现原理及实质就是多态。
    多态的实质就是父类型的引用指向子类型的对象,而当父类型又是抽象类 或 接口时,既变成了面向抽象编程。面向抽象编程可以让代码拥有更高的可扩展性,满足开闭原则(对扩展开放,对修改关闭)。
    现实世界万物以对象的形式存在,而java则是以秉承"对象-做-什么事情"进行编程。而不是面向做事情的具体实现过程。因此,java具有更高的耦合性,耦合性的关键则在于抽象。
abstract Animal a1 = new Cat();//父类为抽象类的引用 指向子类实现类的对象
interface Action a2 = new Move();//接口类型的引用 指向子类实现类的对象
  1. 谈一谈接口和抽象类的区别?
    答:
  • 抽象类是半抽象的<构造方法 + 抽象方法>,接口是完全抽象的<常量 + 抽象方法>。
  • 抽象类中有构造方法(类都有构造方法)。接口中没有构造方法(只有无方法体的抽象方法)。
  • 接口和接口之间支持多继承。类和类之间只能单继承(抽象类也是类)。类和接口之间支持多实现。
  • 一个类可以同时实现多个接口。一个抽象类只能继承一个类(单继承)。
  • 接口中只允许出现常量和抽象方法(public static final和public abstract);抽象类中允许出现抽象和非抽象的变量或方法。
  • 接口描述了方法的特征,不给出具体实现<抽象方法都没有具体实现>,一方面解决 java 的单继承问题,实现了强大的可接插性。
  • 抽象类提供了部分实现<构造方法 + 非抽象的方法>,抽象类是不能实例化的,抽象类的存在--主要是可以把公共的代码移植到抽象类中。
  • 优先级:优先选择接口(继承抽象类后,此类将无法再继承,会丧失此类的灵活性)

你可能感兴趣的:(JAVA学习第六天之接口与抽象类)