paivate关键字

  • private关键字特点
    • 是一个权限修饰符
    • 可以修饰成员变量和成员方法
    • 被其修饰的成员只能在本类中被访问
  • private最常见的应用
    • 把成员变量用private修饰,提供对应的getXxx()和setXxx()方法

单例模式

  • 保证创建的对象 始终是同一个

懒汉式

  • 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Test2{
    private static Test2 t2 = null;
    private Test2(){

    }

    pbulic static Test2 getInstance(){
    if(t2==null){
    t2 = new Test2();
    }
    return t2;
    }
    }

饿汉式

  • 代码如下;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Test2 {
    private static Test2 t2 = new Test2();
    private Test2(){

    }

    public static Test2 getInstance(){
    return t2;
    }
    }

二者区别

  1. 饿汉类加载时对象就创建了,多线程访问,安全;
  2. 懒汉在使用时,才会创建对象;多线程访问,不妾全。

继承

  • 根据已有的类 派生出 新类的技术

  • 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。

  • 优点:

    • 代码重用;
    • 方便维护,高耦合。
  • 缺点

    • 破坏封装
  • 继承的注意事项:

    • private 修饰的不能继承,
      • private类型的方法默认是final类型
  • 构造器不能继承,但能通过super调用

  • 不在同一包下的子类可以使用父类使用默认修饰符的方法

  • 继承遵循的原则:里氏替换原则(LSP):将父类用子类替换,程序不会出错

  • 继承仅支持单继承,不支持多继承

  • 继承的格式:通过extends关键字可以实现类与类的继承

    • class 子类名 extends 父类名 {}
    • 父类也叫基类或者超类
    • 子类也叫派生类。
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class Parent{
    public Parent(){
    System.out.println("父类的无参构造被调用了。。。");
    }
    }

    class Son extends Parent{
    public Son(){
    System.out.println("子类的无参构造被调用了。。。");
    }
    }

    class Test{
    public static void main(String[] args) {
    new Son();
    }
    }


    父类的无参构造别调用了。。。
    子类的无参构造别调用了。。。

子类继承的成员

  • 子类不能继承父类的构造方法,但是可以通过super(待会儿讲)关键字去访问父类构造方法。
  • 子类和父类在同一个包中时,子类可以继承父类的除private属性的所有方法和成员变量
  • 当子类与父类不在同一个包中时,子类只能继承父类的protected和public属性的成员变量和方法。
  • 子类中定义的成员变量和父类中的成员变量同名时,子类就隐藏了继承的成员变量;
  • 子类中定义一个方法,并且这个方法的名字、返回类型、参数个数和类型与从父类继承的方法完全相同,子类就隐藏从父类继承来的方法(方法的重写)。
  • final类不能被继承,没有子类,final类中的方法默认是final的。final方法不能被子类的方法覆盖,但可以被继承。final不能用于修饰构造方法。

子类访问父类的构造方法

  • 创建子类对象时 一定会先调用父类的无参构造。必须确保在执行派生类的构造函数时,从基类继承的私有数据成员已经被初始化了。

  • 在子类的构造器中 ,如果没有显示的调用父类的构造器super(),或者没有显示的调用本类构造 this(),那么 编译器 一定会分配一个 super() 调用父类的无参构造。

  • super() 位置在子类构造中 ,而且是第一行代码处。

    • super(); // 无参
  • super(参数); //带参

  • 父类没有无参构造,只有带参构造,解决办法:

    • 类通过super去显示调用父类其他的带参的构造方法
    • 在父类写上无参构造
  • 子类通过this去调用本类的其他构造方法(本类其他构造也必须首先访问了父类构造)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    class Parent {
    public int age = 18;
    public Parent(int age) {
    this.age = 20;
    System.out.println("父类的无参构造被调用了。。。");
    }

    void hello() {
    System.out.println("hello:我是父类");
    }
    }

    class Son extends Parent {
    public Son() {
    super(1); // 可以调用带参构造,否则编译报错
    System.out.println("子类的无参构造被调用了。。。");
    }

    @Override
    public void hello() {
    //调用父类的hello方法
    // super.hello();
    System.out.println("hello:我是子类");
    }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

### 不支持多继承影响

- Java 不支持多继承影响
- Java 相比于其他面向对象语言,如 C++,设计上有一些基本区别,比如Java 不支持多继承。这种限制,在规范了代码实现的同时,也产生了一些局限性,影响着程序设计结构。Java 类可以实现多个接口,因为接口是抽象方法的集合,所以这是声明性的,但不能通过扩展多个抽象类来重用逻辑。
- 在一些情况下存在特定场景,需要抽象出与具体实现、实例化无关的通用逻辑,或者纯调用关系的逻辑,但是使用传统的抽象类会陷入到单继承的窘境。
- 为什么是单继承而不能多继承呢?
- 若为多继承,那么当多个父类中有重复的属性或者方法时,子类的调用结果会含糊不清,因此用了单继承。
- 多继承虽然能使子类同时拥有多个父类的特征,但是其缺点也是很显著的,主要有两方面:
- 如果在一个子类继承的多个父类中拥有相同名字的实例变量,子类在引用该变量时将产生歧义,无法判断应该使用哪个父类的变量。
- 如果在一个子类继承的多个父类中拥有相同方法,子类中有没有覆盖该方法,那么调用该方法时将产生歧义,无法判断应该调用哪个父类的方法。
- Java是从C++语言上优化而来,而C++也是面向对象的,为什么它却可以多继承的呢?首先,C++语言是1983年在C语言的基础上推出的,Java语言是1995年推出的。其次,在C++被设计出来后,就会经常掉入多继承这个陷阱,虽然它也提出了相应的解决办法,但Java语言本着简单的原则舍弃了C++中的多继承,这样也会使程序更具安全性。
- 为什么是多实现呢?
- 通过实现接口拓展了类的功能,若实现的多个接口中有重复的方法也没关系,因为实现类中必须重写接口中的方法,所以调用时还是调用的实现类中重写的方法。

## 抽象类

- 在面向对象的概念中,所有的对象都是通过类来描绘的。但并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类(Abstract)。

- 只约定类所具有的抽象行为,没有具体实行对应的行为,提供一个模板的作用。

- 用abstract修饰的类,即抽象类

```java
// 这就是一个抽象类
abstract class Animal {
String name;
int age;

// 动物会叫
// 不确定动物怎么叫的。定义成抽象方法,来解决父类方法的不确定性。
public abstract void cry();
}

格式

  • 因为抽象类需要子类继承,子类创建对象的时候会默认调用父类的无参数构造函数。所以抽象类中必须有构造函数

  • 格式如下:

    1
    2
    3
    4
    5
    6
    7
    8
    abstract class 类名{
    构造方法
    常量;
    变量;
    访问修饰符 abstract 返回类型 方法名();//抽象方法
    普通方法;
    静态方法();
    }

注意事项

  1. 抽象类不能创建对象
  2. 抽象方法不是必须定义
  3. 带有抽象方法类的必须是抽象类
  4. 构造方法不能是抽象的;
  5. 子类继承抽象类必须重写所有的抽象方法,除非子类是抽象类
  6. abstract 不能与 private ,final,static 使用
    • private 不能被继承,就不能重写
      • final 不能被重写
      • static 方法没有方法体,无意义

抽象方法

  • 用abstract修饰的方法,即抽象方法。

  • 抽象方法在父类中不能实现,所以没有函数体{}

    • public abstract void cry();
  • 子类必须实现父类的所有抽象方法

    • 重写父类的抽象方法
    • 抽象子类除外
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
abstract class Animal {
String name;
int age;

// 动物会叫
public abstract void cry();
}

// 抽象类可以被继承
// 当继承的父类是抽象类时,需要将抽象类中的所有抽象方法全部实现。
class cat extends Animal {
// 实现父类的cry抽象方法
@Override
public void cry() {
System.out.println("猫叫:");

}
}

构造方法

  • 构造方法不能是抽象的,

    • 构造方法不能被继承
    • 抽象的方法要子类重写,矛盾
  • 虽然抽象类不能被实例化,但可以有构造函数。由于抽象类的构造函数在实例化派生类之前发生,所以,可以在这个阶段初始化抽象类字段或执行其它与子类相关的代码。

抽象类名作为形参

  • 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    /*抽象类作为参数的时候如何进行调用*/
    abstract class Animal {
    // 定义一个抽象方法
    public abstract void eat() ;
    }

    // 定义一个类
    class Cat extends Animal {
    public void eat(){
    System.out.println("吃.................") ;
    }
    }


    // 定义一个类
    class AnimalDemo {
    public void method(Animal a) {
    a.eat() ;
    }
    }

    // 测试类
    class ArgsDemo2 {
    public static void main(String[] args) {
    // 创建AnimalDemo的对象
    AnimalDemo ad = new AnimalDemo() ;
    // 对Animal进行间接实例化
    // Animal a = new Cat() ;
    Cat a = new Cat() ;
    // 调用method方法
    ad.method(a) ;
    }
    }

抽象类名作为返回值类型

  • 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    /*抽象类作为返回值类型*/
    abstract class Animal {
    public abstract void eat() ;
    }

    // 定义一个子类
    class Cat extends Animal {
    public void eat(){
    System.out.println("吃..........................") ;
    }
    }

    // 定义一个类
    class AnimalDemo {
    public static Animal getAnimal() {
    // Animal a = new Cat() ;
    // return a;
    return new Cat() ;
    }
    }

    // 测试类
    class ReturnDemo2 {
    public static void main(String[] args) {
    // 调用AnimalDemo的getAnimal这个方法
    Animal a = AnimalDemo.getAnimal() ;
    // 调用方法
    a.eat() ;
    }
    }

getSuperclass

  • 返回直接继承的父类(由于编译擦除,没有显示泛型参数

    1
    Class<? super T> getSuperclass()
  • 返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class

    • 如果此 Class 表示 Object 类、一个接口、一个基本类型或 void,则返回 null。
    • 如果此对象表示一个数组类,则返回表示该 Object 类的 Class 对象。

getGenericSuperclass

  • 返回直接继承的父类(包含泛型参数

    1
    Type getGenericSuperclass()
  • 返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type

  • 如果超类是参数化类型,则返回的 Type 对象必须准确反映源代码中所使用的实际类型参数。

  • 如果以前未曾创建表示超类的参数化类型,则创建这个类型。有关参数化类型创建过程的语义,请参阅 ParameterizedType 声明。

  • 如果此 Class 表示 Object 类、接口、基本类型或 void,则返回 null。

  • 如果此对象表示一个数组类,则返回表示 Object 类的 Class 对象。

  • 抛出:

    • GenericSignatureFormatError 如果常规类签名不符合 Java Virtual Machine Specification, 3rd edition 规定的格式
    • TypeNotPresentException - 如果常规超类引用不存在的类型声明
    • MalformedParameterizedTypeException - 如果常规超类引用的参数化类型由于某种原因无法实例化
  • 代码实例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    public class Test {

    public static void main(String[] args) {
    System.out.println("Student.class.getSuperclass()\t"
    + Student.class.getSuperclass());
    System.out.println("Student.class.getGenericSuperclass()\t"
    + Student.class.getGenericSuperclass());

    System.out.println("Test.class.getSuperclass()\t"
    + Test.class.getSuperclass());
    System.out.println("Test.class.getGenericSuperclass()\t"
    + Test.class.getGenericSuperclass());

    System.out.println("Object.class.getGenericSuperclass()\t"
    + Object.class.getGenericSuperclass());
    System.out.println("Object.class.getSuperclass()\t"
    + Object.class.getSuperclass());

    System.out.println("void.class.getSuperclass()\t"
    + void.class.getSuperclass());
    System.out.println("void.class.getGenericSuperclass()\t"
    + void.class.getGenericSuperclass());

    System.out.println("int[].class.getSuperclass()\t"
    + int[].class.getSuperclass());
    System.out.println("int[].class.getGenericSuperclass()\t"
    + int[].class.getGenericSuperclass());
    }

    }

    class Person<T> {

    }

    class Student extends Person<Test> {

    }
  • 输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Student.class.getSuperclass()			class cn.test.Person
    Student.class.getGenericSuperclass() cn.test.Person<cn.test.Test>
    Test.class.getSuperclass() class java.lang.Object
    Test.class.getGenericSuperclass() class java.lang.Object
    Object.class.getGenericSuperclass() null
    Object.class.getSuperclass() null
    void.class.getSuperclass() null
    void.class.getGenericSuperclass() null
    int[].class.getSuperclass() class java.lang.Object
    int[].class.getGenericSuperclass() class java.lang.Object

方法重写

  • @override 注解,给编译器提示信息,会检查重写规则的正误
  • 重写是建立在java类的三大特性之一:继承性的基础之上的,没有继承性也就不能谈方法的重写。方法重写又称方法覆盖。
  • 方法的重写是当程序中父类的某一个方法并不能满足子类的需求时,子类可以重新定义该方法的内容与功能来满足子类的需求的一种操作
  • 如需父类中原有的方法,可使用super关键字,该关键字引用了当前类的父类。

重写注意事项

  • 子类中的方法与父类中的方法,有相同的返回类型、相同的方法名称、相同的参数列表

  • 子类中的方法的访问级别,不能低于父类中该方法的访问级别

    • 如果Child类的 outPut() 方法的访问级别为 private ,将会与 java 的多态机制发生冲突

      1
      2
      3
      4
      5
      6
      Parent parent = new Child();
      // 编译器会认为以上是合法的,
      // 但在运行时,根据动态绑定规则,Java虚拟机会调用 parent 变量所引用的 Child 实例的 outPut() 方法,
      parent.outPut();
      //而 Child 的 outPut() 方法为 private,Java虚拟机无法访问。
      //为了避免这样的矛盾, Java 虚拟机不允许子类方法缩小父类中被覆盖方法的访问权限。
  • 子类中方法抛出的异常范围,不能大于父类中方法抛出的异常的范围

  • 不能重写的情况

    • 如果父类中含有一个静态方法,且在子类中也含有一个返回类型、方法名、参数列表均与之相同的静态方法,那么该子类实际上只是将父类中的该同名方法进行了隐藏,而非重写。换句话说,父类和子类中含有的其实是两个没有关系的方法,它们的行为也并不具有多态性。
    • static方法和final方法(private方法属于final方法)是前期绑定,而其他所有的方法都是后期绑定了。
    • private 方法 可以被隐藏 不可以被重写
    • static 方法 可以被隐藏 不可以被重写
    • final 方法 不可以被重写
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    class Parent {
    public int age = 18;
    public Parent() {
    System.out.println("父类的无参构造被调用了。。。");
    }

    void hello() {
    System.out.println("hello:我是父类");
    }
    }

    class Son extends Parent {
    public Son() {
    System.out.println("子类的无参构造被调用了。。。");
    }

    @Override
    public void hello() {
    //调用父类的hello方法
    // super.hello();
    System.out.println("hello:我是子类");
    }
    }

    class Test {
    public static void main(String[] args) {
    Parent parent = new Son();
    System.out.println(parent.age);
    parent.hello();
    }
    }
    父类的无参构造被调用了。。。
    子类的无参构造被调用了。。。
    18
    hello:我是子类

父类的静态方法能否被子类重写

  • 思考一下:父类的静态方法能否被子类重写?
    • 父类的静态方法是不能被子类重写的,其实重写只能适用于实例方法,不能用于静态方法,对于上面这种静态方法而言,我们应该称之为隐藏。
    • Java静态方法形式上可以重写,但从本质上来说不是Java的重写。因为静态方法只与类相关,不与具体实现相关。声明的是什么类,则引用相应类的静态方法(本来静态无需声明,可以直接引用)。并且static方法不是后期绑定的,它在编译期就绑定了。换句话说,这个方法不会进行多态的判断,只与声明的类有关。

接口

  • 接口(Interface)在Java语言中是一个抽象类型,是服务提供者和服务使用者之间的一个协议,在JDK1.8之前一直是抽象方法的集合,一个类通过实现接口从而来实现两者间的协议
  • 接口可以定义字段和方法。在JDK1.8之前,接口中所有的方法都是抽象的,从JDK1.8开始,也可以在接口中编写默认的和静态的方法。除非显式指定,否则接口方法都是抽象的
  • 简单解释:
    • 是一组公开的规则,是功能的组合。
  • 好处:
    • 降低了类之间的耦合连接
    • 扩充了功能实现
  • 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
  • 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。

注意

  • 从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。
  • 接口的特点有哪些呢?
    • 接口没有构造方法
    • 接口不能用于实例化对象
    • 接口中的字段必须初始化,并且隐式地设置为公有的、静态的和final的。因此,为了符合规范,接口中的字段名要全部大写
    • 接口不是被类继承,而是要被类实现,接口不能实现接口
    • 当类实现接口时,类要实现接口中所有的方法。否则,类必须声明为抽象的
    • 接口中每一个方法默认是公有和抽象的,即接口中的方法会被隐式的指定为 public abstract。从JDK 1.8开始,可以在接口中编写默认的和静态的方法。声明默认方法需要使用关键字default。并且不允许定义为 private 或者 protected。
    • 接口支持多重继承,即可以继承多个接口

格式

  • 在 jdk1.8里面引入了新的关键字default,通过使用default修饰方法,可以让我们在接口里面定义具体的方法实现。Java 9 以后,甚至可以定义 private default method。Default method 提供了一种二进制兼容的扩展已有接口的办法。比如,我们熟知的 java.util.Collection,它是 collection 体系的 root interface,在 Java 8 中添加了一系列 default method,主要是增加 Lambda、Stream 相关的功能。

    1
    2
    3
    4
    5
    public interface Collection<E> extends Iterable<E> {
    default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
    }
    }
    • 方法的定义

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      interface CustomInterface {

      public abstract void abstractMethod(); //抽象方法不能是私有的

      // 下面1.8之前报错
      public default void defaultMethod() {
      privateMethod(); //可以调用接口中的私有方法
      privateStaticMethod(); //可以调用接口中的私有静态方法
      System.out.println("普通方法被调用");
      }

      public static void staticMethod() {
      privateStaticMethod(); //public静态方法可以调用private静态方法
      System.out.println("静态方法被调用");
      }

      // 下面1.9之前报错
      private void privateMethod() {
      System.out.println("private私有方法被调用");
      }

      private static void privateStaticMethod() {
      System.out.println("private私有静态方法被调用");
      }
      }
  • 也可以有static修饰的实现方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    访问修饰符 interface 接口名{
    //java1.8之前,interface接口里面是只能有抽象方法和常量,不能有任何方法的实现
    静态的常量;public static final
    抽象方法;public abstract
    // JDK 1.8 以后,接口里可以有静态方法和方法体了。
    默认方法;(1.8后支持)default,可以有方法体
    静态方法;(1.8后支持)static
    静态内部类型;public static 内部类,内部接口, 内部枚举enum
    }
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public interface IFood {
    void eatDinner();

    default void feedPet(){
    System.out.println("记得给小泰迪吃晚餐");
    }

    static void feedFood(){
    System.out.println("小泰迪晚上要吃肉");
    }
    }


    public interface IFruit {

    void dessert();

    default void feedPet(){
    System.out.println("小泰迪不喜欢吃水果");
    }

    static void feedFood(){
    System.out.println("小泰迪晚晚餐不需要甜点");
    }

    }

Marker Interface

  • 接口的职责也不仅仅限于抽象方法的集合,其实有各种不同的实践。
  • 有一类没有任何方法的接口,通常叫作 Marker Interface,顾名思义,它的目的就是为了声明某些东西,比如我们熟知的 Cloneable、Serializable 等。这种用法,也存在于业界其他的 Java 产品代码中。

默认方法

  • 之前开发中,接口实现了就不能更改,有新的需求,

    • 写一个新的接口,
    • 继承之前的接口,
    • 添加新功能,
    • 实现该类,
    • 调用新类。
  • 1.8之后,可以在原来的接口中,

    • 写入新的默认方法,
    • 调用默认方法。

静态方法

  • 只能通过接口名调用,不可以通过实现类的类名或者实现类的对象调用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    interface A {
    public default void f2(){
    System.out.println("A接口中的默认方法");
    }
    public static void f() {
    System.out.println("A_静态方法!");
    }
    }

    interface B {
    public default void f2(){
    System.out.println("B接口中的默认方法");
    }
    public static void f() {
    System.out.println("B_静态方法!");
    }
    }
    //==============重写同名的默认方法====================
    interface C extends A,B{
    @Override
    default void f2() {
    B.super.f2(); //重写B接口的f2()方法
    B.super.f2(); //重写A接口的f2()方法
    //可以使用内部类的分别重写A,B
    }
    }
    ==============测试================
    public class Test implements C{
    public static void main(String[] args) {
    A.f(); //接口静态方法 通过接口名调用
    B.f(); //接口静态方法 通过接口名调用
    Test d1 = new Test(); //接口默认方法 通过接口实现类的对象调用
    d1.f2();
    }
    }

接口的继承

  • 一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。

  • 下面的Sports接口被Hockey接口继承:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public interface Sports
    {
    public void setHomeTeam(String name);
    public void setVisitingTeam(String name);
    }

    // 文件名: Football.java
    public interface Football extends Sports
    {
    public void homeTeamScored(int points);
    public void visitingTeamScored(int points);
    public void endOfQuarter(int quarter);
    }

接口的多继承

  • 在Java中,类的多继承是不合法,但接口允许多继承。

  • 在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示:

    public interface Hockey extends Sports, Event

  • 以上的程序片段是合法定义的子接口,与类不同的是,接口允许多继承,而 Sports及 Event 可能定义或是继承相同的方法。

接口的多实现

  •  ...implements 接口名称[, 其他接口名称, 其他接口名称..., ...] ...

  • 接口可以多实现,为解决这种多继承关系,Java8提供了下面三条规则:

    1. 类中的方法优先级最高,类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
    2. 如果第一条无法判断,那么子接口的优先级更高:方法签名相同时,优先选择拥有最具体实现的默认方法的接口, 即如果B继承了A,那么B就比A更加具体。
    3. 最后,如果还是无法判断,继承了多个接口的类必须通过显式覆盖和调用期望的方法, 显式地选择使用哪一个默认方法的实现。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    public interface A {
    default void hello() {
    System.out.println("hello from A");
    }
    }
    public interface B extends A {
    default void hello() {
    System.out.println("hello from B");
    }
    }
    public class C implements A, B {
    public static void main(String[] args) {
    // 因为B继承了A,所以B比A更具体
    // 所以应该选择B的hello()方法。
    // 所以,程序会打印输出"hello from B"。
    new C().hello();
    }
    }
    //============================================
    // C像下面这样继承了D
    public class D implements A {
    }

    public class C extends D implements A, B {
    public static void main(String[] args) {
    // C虽然继承了D,但D中未覆盖A的默认方法。
    // 会在A和B中做选择,由于B更具体
    // 所以,程序会打印输出"hello from B"。
    new C().hello();
    }
    }
    //============================================
    // 将上面的D稍作修改:
    public class D implements A {
    public void hello() {
    //父类中声明的方法具有更高的优先级,所以程序会打印输出"hello from D"。
    System.out.println("hello from D");
    }
    }
  • 当一个实现类实现了多个接口,多个接口里都有相同的默认方法时,编译器无法识别谁的实现更加具体实现类必须重写该默认方法,否则编译错误,覆写有两种方式:

    • 实现类自己实现方法逻辑
    • 采用super关键字来调用指定接口的默认方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public  class FoodFruit implements IFood,IFruit {

    @Override
    public void eatDinner(IFoodTime foodTime) {
    System.out.println("晚餐"+foodTime.getFoodTime()+"点吃土豆炖牛肉");
    }

    @Override
    public void dessert() {
    System.out.println("饭后甜点吃点啥呢?葡萄还是草莓");
    }

    @Override
    public void feedPet() {
    // 显式地选择调用接口IFood中的方法
    // 同理,要调用接口IFruit中的方法,可以这样:IFruit.super.feedFood()
    IFood.super.feedFood();
    //通过接口调用静态方法
    IFood.feedFood();
    }
    }

接口名作为形式参数

  • 代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    /*接口作为参数的时候我们如何进行调用*/
    interface Jump {
    // 跳高接口
    public abstract void jump() ;
    }

    // 定义一个子类
    class JumpImpl implements Jump {
    public void jump(){
    System.out.println("jump.............................") ;
    }
    }

    // 定义一个类
    class JumpDemo {
    public void method(Jump jump) {
    jump.jump();
    }
    }

    // 测试类
    class ArgsDemo3 {
    public static void main(String[] args) {
    // 1. 创建JumpDemo对象
    JumpDemo jd = new JumpDemo() ;
    // 2. 调用method方法
    // 对Jump进行间接实例化
    Jump jump = new JumpImpl() ;
    jd.method(jump) ;
    }
    }

接口名作为返回值类型

  • 代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    /*接口作为返回值类型*/
    interface Jump {
    public abstract void jump() ;
    }

    // 定义一个子类
    class JumpImpl implements Jump {
    public void jump(){
    System.out.println("jum....................") ;
    }
    }

    // 定义一个类
    class JumpDemo {
    public static Jump getJump() {
    return new JumpImpl() ;
    }
    }

    // 测试类
    class ReturnDemo3 {
    public static void main(String[] args) {
    // 调用getJump方法
    Jump jump = JumpDemo.getJump() ;
    // 调用
    jump.jump() ;
    }
    }

接口和抽象类的区别

  • 相同

    1. 都是引用数据类型
    2. 抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
    3. 都能定义抽象方法
    4. 都是 用 子类继承 或 实现类 实现(重写) 来 使用。
  • 不同

    1. 抽象类只能被单继承。
    2. 接口的实现类可以实现多个接口的功能
    3. 接口之间可以多继承
  1. 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
  2. 抽象类里可以没有抽象方法。
  3. 接口中没有 this 指针,没有构造函数,不能拥有实例字段(实例变量)或实例方法。
  4. 抽象类不能在Java 8 的 lambda 表达式中使用。
  5. 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。
  6. 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
  • 二者的理解
    • 抽象类:为所有子类提供一个模板,半成品。子类在父抽象类的基础上扩展。
    • 接口:提供一组公开的规则。同一个应用程序,接口将各个类(模块)耦合连接,(不使用继承)
    • 接口是对动作的抽象,抽象类是对根源的抽象。从设计理念上,接口反映的是 “like-a” 关系,抽象类反映的是 “is-a” 关系。
    • 比如鸟有fly()
      • 鸵鸟extends 鸟:高耦合,父类有的子类也要有,鸵鸟也能飞,设计不合理
      • 写一个flyInterface接口,提供fly();,如果需要鸵鸟飞就可以实现这个接口。
      • 鸵鸟 extends 鸟 implements flyInterface;

抽象和接口编程角度不同

  • abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。

  • 其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,不过在JDK1.8中可以使用default关键字实现默认方法。

    1
    2
    3
    4
    5
    interface InterfaceA {
    default void foo() {
    System.out.println("InterfaceA foo");
    }
    }
  • 在 Java 8 之前,接口与其实现类之间的 耦合度 太高了(tightly coupled),当需要为一个接口添加方法时,所有的实现类都必须随之修改。默认方法解决了这个问题,它可以为接口添加新的方法,而不会破坏已有的接口的实现。这在 lambda 表达式作为Java 8 语言的重要特性而出现之际,为升级旧接口且保持向后兼容(backward compatibility)提供了途径。

多态

概述

  • 多态是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,

    • 即一个引用变量倒底会指向哪个类的实例对象,
    • 该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
    • 因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
  • 简单解释:

    • 多态是同一个行为具有多个不同表现形式或形态的能力。
    • 多态就是同一个接口,使用不同的实例而执行不同操作
  • 多态实现条件?

    • Java实现多态有三个必要条件:继承、重写、向上转型。
    • 继承:在多态中必须存在有继承关系的子类和父类。
    • 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
    • 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
  • 定义格式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 普通类多态就是父类的引用变量指向子类对象
    父类类型 变量名 = new 子类类型();
    变量名.方法名();

    // 抽象类多态定义格式
    抽象类 变量名 = new 抽象类子类();

    // 接口多态定义的格式
    接口 变量名 = new 接口实现类();
  • 多态的运行

    • 同一个父类的方法会被不同的子类重写。在调用方法时,调用的为各个子类重写后的方法。
    1
    2
    3
    4
    5
    6
    1. 成员方法:编译看左边 运行看右边
    父类有,子类没有,调用父类
    父类有,子类有,调子类

    2. 成员变量:编译运行都看 左边
    int a = f.num;

多态的划分情况

  • 多态即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的哪个方法,必须在由程序运行期间才能决定。
  • 要注意,对于面向对象而言,多态分为编译时多态和运行时多态这两个内容。
    • 其中编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编译之后会变成两个不同的函数。这个时候,在编译时候已经知道要运行哪个函数了。
    • 而运行时多态(其实就是动态绑定)是动态的。他是指在执行期间(而不是编译期间)判断所引用对象的实际类型,并且根据其实际类型调用相应实际使用的方法。我们其实一般习惯上所说的多态,大部分时候都指的是运行时多态。在Java中,有两种形式可以实现多态,继承和接口。
  • (1)编译时多态
    • 是通过方法重载实现的。
    • 重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同(就算是在一个继承链上下的类型,也认为是不同的),或许两者都不同)。其实严格来说,重载的概念并不属于“面向对象编程”,
    • 重载的实现是:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。
  • (2)运行时多态
    • 是通过覆盖(重写)实现的,也就是override。
    • 覆盖,是指子类重新定义父类的函数。
      • 方法覆盖需要子类方法和父类方法的名称、参数类型和返回类型都完全一致(其实返回类型不一定要一致,子类的方法返回类型比父类缩小也允许)。
      • 一般可以在子类的覆盖的方法前面加上@override来保证这个方法确实是覆盖。
    • 使用父类引用指向子类对象,再调用某一父类中的方法时,不同子类会表现出不同结果。
      • 如果通过一个父类的引用来调用某方法,实际上他会对应到内存中真正的对象,他会判断内存中真正的对象是子类对象还是父类对象,然后判断要调用哪个方法。
      • 查找顺序是先在子类中找,有就使用,没有就在父类中找,有就使用,再没有就报错了。

多态实现方式

  • 多态作用:多态性就是相同的消息使得不同的类做出不同的响应。‘

基于继承实现的多态

  • 基于继承的实现机制主要表现在父类和继承该父类的一个或多个子类对某些方法的重写,多个子类对同一方法的重写可以表现出不同的行为。多态的表现就是不同的对象可以执行相同的行为,但是他们都需要通过自己的实现方式来执行,这就要得益于向上转型了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    public class MainJava {
    public static void main(String[] args) {
    //定义父类数组
    Wine[] wines = new Wine[2];
    //定义两个子类
    Test1 test1 = new Test1();
    Test2 test2 = new Test2();
    Wine win e = new Wine();
    //父类引用子类对象
    wines[0] = test1;
    wines[1] = test2;
    for(int i = 0 ; i < 2 ; i++){
    System.out.println(wines[i].toString() + "--" + wines[i].drink());
    }
    System.out.println("-------------------------------");
    System.out.println(test1.toString() + "--" + test1.drink());
    System.out.println(test2.toString() + "--" + test2.drink());
    }
    public static class Wine {
    private String name;
    public String getName() {
    return name;
    }
    public void setName(String name) {
    this.name = name;
    }
    public String drink(){
    return "喝的是 " + getName();
    }
    public String toString(){
    return null;
    }
    }

    public static class Test1 extends Wine{
    public Test1(){
    setName("Test1");
    }
    public String drink(){
    return "喝的是 " + getName();
    }
    public String toString(){
    return "Wine : " + getName();
    }
    }

    public static class Test2 extends Wine{
    public Test2(){
    setName("Test2");
    }
    public String drink(){
    return "喝的是 " + getName();
    }
    public String toString(){
    return "Wine : " + getName();
    }
    }
    }

基于接口实现的多态

  • 继承是通过重写父类的同一方法的几个不同子类来体现的,那么就可就是通过实现接口并覆盖接口中同一方法的几不同的类体现的。
  • 在接口的多态中,指向接口的引用必须是指定这实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。
  • 继承都是单继承,只能为一组相关的类提供一致的服务接口。但是接口可以是多继承多实现,它能够利用一组相关或者不相关的接口进行组合与扩充,能够对外提供一致的服务接口。所以它相对于继承来说有更好的灵活性。

多态中的类型转换

  • 子类转父类: 向上类型转换

  • 向下类型转换:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用转为子类引用,这个过程是向下转型。

    • 需要强制转换
    • 如果是直接创建父类对象,是无法向下转型的!
    • 注意类型不匹配:java.lang.ClassCastException
    • 子类类型 变量名 = (子类类型) 父类类型的变量;

关键字:instanceof

  • 可以通过instanceof关键字来判断某个对象是否属于某种数据类型。

  • 如学生的对象属于学生类,学生的对象也属于人类

    1
    boolean b = pet instanceof Cat//判断传递的pet对象是不是Cat类类型

示例

  • 定义一个宠物类,宠物有名字和吃的动作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Pet {
    private String name;

    public Pet(String name) {
    this.name = name;
    }

    public String getName() {
    return name;
    }

    public void eat() {
    System.out.println("吃东西");
    }
    }
  • 编写二个子类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    class Dog extends Pet {
    public Dog(String name) {
    super(name);
    }

    @Override
    public void eat() {
    System.out.println("吃骨头");
    }

    public void run() {
    System.out.println("小狗跑了");
    }
    }
    =======================================================
    class Cat extends Pet {
    public Cat(String name) {
    super(name);
    }

    public void eat() {
    System.out.println("吃鱼");
    }

    public void play() {
    System.out.println("小猫去玩儿了");
    }
    }

  • 测试多态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public class TestHospatil {
    public static void main(String[] args) {
    PetHospatil hos = new PetHospatil();
    Dog wangwang = new Dog("旺旺");
    Cat xiaohua = new Cat("小花");
    //Pet pet = wangwang; 向上类型转换
    hos.treatment(wangwang);
    hos.treatment(xiaohua);

    }

    }
    // =======宠物医院==============================
    class PetHospatil {
    // 类与类依赖关系
    public void treatment(Pet pet) {
    System.out.println("给" + pet.getName() + "看病");
    pet.eat();
    // 向下类型转换:对类型进行判断
    if (pet instanceof Dog) {
    Dog dog = (Dog) pet;
    dog.run();
    }
    if (pet instanceof Cat) {
    Cat cat = (Cat) pet;
    cat.play();
    }
    }
    }