[java] 抽象类、接口、内部类篇

目录

抽象类

抽象类的细节

接口

接口成分的特点(JDK7以前)

抽象方法

常量

JDK8新增的方法

默认方法

静态方法

JDK9新增的方法

基本的实现

类与接口

接口与接口

接口的应用

适配器设计模式

扩展:接口的细节

内部类

成员内部类

面试题

内存图

静态内部类

局部内部类

匿名内部类【重点】

匿名内部类的特点

匿名内部类的使用场景


抽象类

abstract是抽象的意思,用于修饰方法和类,修饰的方法是抽象方法,修饰的类是抽象类。

  • 抽象方法:将共性的行为(方法)抽取到父类之后,由于每一个子类执行的内容是不一样,所以,在父类中不能确定具体的方法体。该方法就可以定义为抽象方法。
  • 抽象类:如果一个类中存在抽象方法,那么该类就必须声明为抽象类

使用abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体

定义格式:

修饰符 abstract 返回值类型 方法名 (参数列表);

public abstract void run();

如果一个类包含抽象方法,那么该类必须是抽象类。

抽象类不一定有抽象方法,但是有抽象方法的类必须定义成抽象类。

定义格式:

abstract class 类名字 {

}

public abstract class Animal {
    public abstract void run();
}

抽象类的使用

要求:继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。

代码举例:

// 父类,抽象类
abstract class Employee {
    private String id;
    private String name;
    private double salary;

    public Employee() {
    }

    public Employee(String id, String name, double salary) {
        this.id = id;
        this.name = name;
        this.salary = salary;
    }

    // 抽象方法
    // 抽象方法必须要放在抽象类中
    abstract public void work();
}

// 定义一个子类继承抽象类
class Manager extends Employee {
    public Manager() {
    }
    public Manager(String id, String name, double salary) {
        super(id, name, salary);
    }
    // 2.重写父类的抽象方法
    @Override
    public void work() {
        System.out.println("管理其他人");
    }
}

// 定义一个子类继承抽象类
class Cook extends Employee {
    public Cook() {
    }
    public Cook(String id, String name, double salary) {
        super(id, name, salary);
    }
    @Override
    public void work() {
        System.out.println("厨师炒菜多加点盐...");
    }
}

// 测试类
public class Demo10 {
    public static void main(String[] args) {
        // 创建抽象类,抽象类不能创建对象
        // 假设抽象类让我们创建对象,里面的抽象方法没有方法体,无法执行.
        //所以不让我们创建对象
//      Employee e = new Employee();
//      e.work();

        // 3.创建子类
        Manager m = new Manager();
        m.work();

        Cook c = new Cook("ap002", "库克", 1);
        c.work();
    }
}

此时的方法重写,是子类对父类抽象方法的完成实现,将这种方法重写的操作,也叫做实现方法。

抽象类的细节

不需要背,只要当idea报错之后,知道如何修改即可。

  1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

    理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

  2. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

    理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

  3. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

    理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

  4. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错。

    理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

  5. 抽象类存在的意义是为了被子类继承。

    理解:抽象类中已经实现的是模板中确定的成员,抽象类不确定如何实现的定义成抽象方法,交给具体的子类去实现。

抽象类和抽象方法的注意事项

  • 抽象类不能实例化,不能创建对象(抽象类的方法没有方法体)
  • 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
  • 可以有构造方法(无参构造,有参构造),当创建子类对象时,给变量进行赋值
  • 抽象类的子类
  • 要么重写抽象类中的所有抽象方法
  • 要么是抽象类

抽象类存在的意义是为了被子类继承,否则抽象类将毫无意义。抽象类可以强制让子类,一定要按照规定的格式进行重写。

抽象类的作用是什么样的?

  • 抽取共性时,无法确定方法体,就把方法定义为抽象的。
  • 强制让子类按照某种格式重写
  • 抽象方法所在的类,必须是抽象类。

接口

接口是更加彻底的抽象,JDK7之前,包括JDK7,接口中全部是抽象方法。接口同样不能创建对象

//接口的定义格式:
public interface 接口名称{
    // 抽象方法
}

// 接口的声明:interface
// 接口名称:首字母大写,满足“驼峰模式”

接口的定义和使用

  • 接口用关键字interface来定义
  • public interface 接口名{}
  • 接口不能实例化
  • 接口和类之间是实现关系,通过implements关键字表示
  • public class 类名 implements 接口名{}
  • 接口的子类(实现类)
  • 要么重写接口中的所有抽象方法
  • 要么是抽象类
  • 注意1:接口和类的实现关系,可以单实现,也可以多实现。
  • public class 类名implements接口名1,接口名2{}
  • 注意2:实现类还可以在继承一个类的同时实现多个接口。
  • public class 类名 extends父类implements接口名1,接口名2{}

接口成分的特点(JDK7以前)

  • JDK7以前:接口中只能定义抽象方法
  • JDK8的新特性:接口中可以定义有方法体的方法
  • JDK9的新特性:接口中可以定义私有方法

在JDK7,包括JDK7之前,接口中的包含:抽象方法和常量(没有构造方法)

抽象方法

注意:接口中的抽象方法默认会自动加上public abstract修饰程序员无需自己手写!! ​

按照规范:以后接口中的抽象方法建议不要写上public abstract。因为没有必要啊,默认会加上。

常量

在接口中定义的成员变量默认会加上: public static final修饰。也就是说在接口中定义的成员变量实际上是一个常量。这里是使用public static final修饰后,变量值就不可被修改,并且是静态化的变量可以直接用接口名访问,所以也叫常量。常量必须要给初始值。常量命名规范建议字母全部大写,多个单词用下划线连接。

public interface InterF {
    // 抽象方法!
    //    public abstract void run();
    void run();

    //    public abstract String getName();
    String getName();

    //    public abstract int add(int a , int b);
    int add(int a , int b);


    // 它的最终写法是:
    // public static final int AGE = 12 ;
    int AGE  = 12; //常量
    String SCHOOL_NAME = "黑马程序员";

}

JDK8新增的方法

默认方法

允许在接口中定义默认方法,需要使用关键字default 修饰
作用:解决接口升级的问题

接口中默认方法的定义格式:
格式:public default 返回值类型方法名(参数列表){}

public default void show() { }

接口中默认方法的注意事项:

  • 默认方法不是抽象方法,所以不强制被重写。但是如果被重写,重写的时候去掉default关键字
  • public可以省略,default不能省略,没有default就会被当成抽象方法
  • 如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写,不重写就不知道该用哪个方法

静态方法

允许在接口中定义定义静态方法,需要用static修饰

接口中静态方法的定义格式:
格式:public static 返回值类型方法名(参数列表){}

public static void show() { }

接口中静态方法的注意事项:

  • 在实现类中不能重写,但可以写一个同名的方法,区别是没有注解@Override,子类把从父类继承下来的虚方法表中的方法覆盖,才叫重写
  • 静态方法只能通过接口名调用,不能通过实现类类名或者对象名调用
  • public可以省略,static不能省略

JDK9新增的方法

接口中私有方法的定义格式:
普通的私有方法,给默认方法使用

//格式1: private 返回值类型方法名(参数列表){}
private void show(){ }

静态的私有方法,给静态方法使用

//格式2:private static 返回值类型方法名(参数列表){ }
private static void method(){ }

基本的实现

接口和类之间的关系

类和类的关系

  • 继承关系,只能单继承,不能多继承,但是可以多层继承
  • 将顶层父类定义为抽象类,不想让外界直接创建这个类的对象,创建对象没有意义

类和接口的关系

  • 实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口

接口和接口的关系

  • 继承关系,可以单继承,也可以多继承,实现类需要重写继承的接口的方法,以及接口继承的父类接口的方法

类与接口

类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字。

/**接口的实现:
    在Java中接口是被实现的,实现接口的类称为实现类。
    实现类的格式:*/
class 类名 implements 接口1,接口2,接口3...{

}

实现多个接口,如果不同的接口中出现相同的方法名,实现类只用重写一次方法

类实现接口的要求和意义

  1. 必须重写类实现的全部接口中所有抽象方法。

  2. 如果一个类实现了接口,但是没有重写完全部接口的全部抽象方法,这个类也必须定义成抽象类。

  3. 意义:接口体现的是一种规范,接口对实现类是一种强制性的约束,要么全部完成接口申明的功能,要么自己也定义成抽象类。这是一种强制性的规范。

定义一个运动员的接口(规范),代码如下:

/**
   接口:接口体现的是规范。
 * */
public interface SportMan {
    void run(); // 抽象方法,跑步。
    void law(); // 抽象方法,遵守法律。
    String compittion(String project);  // 抽象方法,比赛。
}

定义一个乒乓球运动员类,实现接口,实现接口的实现类代码如下:

package com.itheima._03接口的实现;
/**
 * 接口的实现:
 *    在Java中接口是被实现的,实现接口的类称为实现类。
 *    实现类的格式:
 *      class 类名 implements 接口1,接口2,接口3...{
 *
 *
 *      }
 * */
public class PingPongMan  implements SportMan {
    @Override
    public void run() {
        System.out.println("乒乓球运动员稍微跑一下!!");
    }

    @Override
    public void law() {
        System.out.println("乒乓球运动员守法!");
    }

    @Override
    public String compittion(String project) {
        return "参加"+project+"得金牌!";
    }
}

测试代码

public class TestMain {
    public static void main(String[] args) {
        // 创建实现类对象。
        PingPongMan zjk = new PingPongMan();
        zjk.run();
        zjk.law();
        System.out.println(zjk.compittion("全球乒乓球比赛"));

    }
}

类与接口之间的关系是多实现的,一个类可以同时实现多个接口

定义两个接口,代码如下:

/** 法律规范:接口*/
public interface Law {
    void rule();
}

/** 这一个运动员的规范:接口*/
public interface SportMan {
    void run();
}

定义一个实现类:

/**
 * Java中接口是可以被多实现的:
 *    一个类可以实现多个接口: Law, SportMan
 *
 * */
public class JumpMan implements Law ,SportMan {
    @Override
    public void rule() {
        System.out.println("尊长守法");
    }

    @Override
    public void run() {
        System.out.println("训练跑步!");
    }
}

类与接口之间是可以多实现的,可以理解成实现多个规范

接口与接口

Java中,接口与接口之间是可以多继承的:也就是一个接口可以同时继承多个接口。

类与接口是实现关系

接口与接口是继承关系

接口继承接口就是把其他接口的抽象方法与本接口进行了合并。

案例演示:

public interface Abc {
    void go();
    void test();
}

/** 法律规范:接口*/
public interface Law {
    void rule();
    void test();
}

 *
 *  总结:
 *     接口与类之间是多实现的。
 *     接口与接口之间是多继承的。
 * */
public interface SportMan extends Law , Abc {
    void run();
}

接口的应用

1.接口代表规则,是行为的抽象。想要让哪个类拥有一个行为,就让这个类实现对应的接口就可以了。
2.当一个方法的参数是接口时,可以传递接所有实现类的对象,这种方式称之为接口多态

适配器设计模式

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
简单理解:设计模式就是各种套路。
适配器设计模式:解决接口与接口实现类之间的矛盾问题

1.当一个接口中抽象方法过多,但是只要使用其中一部分的时候,就可以适配器设计模式
2. 书写步骤:
编写中间类XXXAdapter实现对应的接口,对接口中的抽象方法进行空实现,让真正的实现类继承中间类,并重写需要用的方法
为了避免其他类创建适配器类的对象,中间的适配器类用abstract进行修饰

扩展:接口的细节

不需要背,只要当idea报错之后,知道如何修改即可。

1.当两个接口中存在相同抽象方法的时候,该怎么办?

只要重写一次即可。此时重写的方法,既表示重写1接口的,也表示重写2接口的

2.实现类能不能继承A类的时候,同时实现其他接口呢?

可以,但要把接口里面所有的抽象方法,全部实现。

3.实现类能不能继承一个抽象类的时候,同时实现其他接口呢?

实现类可以继承一个抽象类的同时,再实现其他多个接口,只不过要把里面所有的抽象方法全部重写。

4.实现类Zi,实现了一个接口,还继承了一个Fu类。假设在接口中有一个方法,父类中也有一个相同的方法。子类如何操作呢?

办法一:如果父类中的方法体,能满足当前业务的需求,在子类中可以不用重写。

办法二:如果父类中的方法体,不能满足当前业务的需求,需要在子类中重写。

5.如果一个接口中,有10个抽象方法,但是我在实现类中,只需要用其中一个,该怎么办?

可以在接口跟实现类中间,新建一个中间类(适配器类) 让这个适配器类去实现接口,对接口里面的所有的方法做空重写。 让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。 因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象

内部类

在一个类里面再定义一个类,在A类的内部定义B类,B类就被称为内部类,A类称为外部类

类的五大成员:属性,方法,构造方法,代码块,内部类

  • 内部类表示的事物是外部类的一部分
  • 内部类单独出现没有任何意义

内部类的访问特点
内部类可以直接访问外部类的成员,包括私有
外部类要访问内部类的成员,必须创建对象

按定义的位置来分(前三个了解就行)

  1. 成员内部类,类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)

  2. 静态内部类,类定义在了成员位置 (类中方法外称为成员位置,有static修饰的内部类)

  3. 局部内部类,类定义在方法内

  4. 匿名内部类(重要),没有名字的内部类,可以在方法中,也可以在类中方法外。

成员内部类

写在成员位置,属于外部类的成员,类中 方法外

成员内部类可以被一些修饰符修饰,比如:private、默认、protected、public、static等

在成员内部类中,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量

成员内部类特点

无static修饰的内部类,属于外部类对象的。

内部类的使用格式

 外部类.内部类。 // 访问内部类的类型都是用 外部类.内部类

获取成员内部类对象的两种方式

方式一:外部直接创建成员内部类的对象(内部类非私有

外部类.内部类 变量 = new 外部类().new 内部类();

//方式一:
public class Test {
    public static void main(String[] args) {
        //  宿主:外部类对象。
       // Outer out = new Outer();
        // 创建内部类对象。
        Outer.Inner oi = new Outer().new Inner();
        oi.method();
    }
}

class Outer {
    // 成员内部类,属于外部类对象的。
    // 拓展:JDK16之前成员内部类不能定义静态成员。
    public class Inner{
        // 这里面的东西与类是完全一样的。
        public void method(){
            System.out.println("内部类中的方法被调用了");
        }
    }
}

方式二:在外部类中定义一个方法提供内部类的对象(内部类私有

//方式二:
//用private修饰内部类
public class Outer {
    String name;
    private class Inner{
        static int a = 10;
    }
    public Inner getInstance(){
        return new Inner();
    }
}

public class Test {
    public static void main(String[] args) {
        Outer o = new Outer();
        System.out.println(o.getInstance());
    }
}

编写成员内部类的注意点:

  1. 成员内部类可以被一些修饰符所修饰,比如: private,默认,protected,public,static等

  2. 在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量。

  3. 创建内部类对象时,对象中有一个隐含的Outer.this记录外部类对象的地址值

  • 内部类被private修饰,外界无法直接获取内部类的对象,只能通过方式二获取内部类的对象
  • 被其他权限修饰符修饰的内部类一般用方式一直接获取内部类的对象
  • 内部类被static修饰是成员内部类中的特殊情况,叫做静态内部类
  • 内部类如果想要访问外部类的成员变量,外部类的变量必须用final修饰,JDK8以前必须手动写final,JDK8之后不需要手动写,JDK默认加上。

面试题

内部类访问外部类对象的格式是:外部类名.this

public class Test {
    public static void main(String[] args) {
        Outer.inner oi = new Outer().new inner();
        oi.method();
    }
}

class Outer {   // 外部类
    private int a = 30;

    // 在成员位置定义一个类
    class inner {
        private int a = 20;

        public void method() {
            int a = 10;
            System.out.println(???);    // 10   答案:a
            System.out.println(???);    // 20   答案:this.a
            System.out.println(???);    // 30   答案:Outer.this.a
        }
    }
}

内存图

外部类和内部类是两个独立的字节码文件,用$来隔开外部类和内部类 

创建的内部类对象会有一个Outer this,存放外部类对象的地址值001,指向外部类对象的堆内存地址

this.a就是002.a

Outer.this.a就是001.a(Outer.this=001)

静态内部类

静态内部类特点

  • 静态内部类是一种特殊的成员内部类。
  • 有static修饰,属于外部类本身的。

  • 总结:静态内部类与其他类的用法完全一样。只是访问的时候需要加上外部类.内部类。

  • 拓展1:静态内部类可以直接访问外部类的静态成员

  • 拓展2:静态内部类不可以直接访问外部类的非静态成员,如果要访问需要创建外部类的对象。(类似静态方法不能直接访问非静态方法,需要先创建对象,再访问)

  • 拓展3:静态内部类中没有Outer.this。

内部类的使用格式

外部类.内部类。

静态内部类对象的创建格式

外部类名.内部类名  变量名 = new  外部类名.内部类名();

 Outer.Inner in  = new Outer.Inner();

调用方法的格式:

  • 调用非静态方法的格式:先创建对象,用对象调用

  • 调用静态方法的格式:外部类名.内部类名.方法名();

案例演示

// 外部类:Outer01
class Outer01{
    private static  String sc_name = "程序";
    // 内部类: Inner01
    public static class Inner01{
        // 这里面的东西与类是完全一样的。
        private String name;
        public Inner01(String name) {
            this.name = name;
        }
        public void showName(){
            System.out.println(this.name);
            // 拓展:静态内部类可以直接访问外部类的静态成员。
            System.out.println(sc_name);
        }
    }
}

public class InnerClassDemo01 {
    public static void main(String[] args) {
        // 创建静态内部类对象。
        // 外部类.内部类  变量 = new  外部类.内部类构造器;
        Outer01.Inner01 in  = new Outer01.Inner01("张三");
        in.showName();
    }
}

局部内部类

  • 局部内部类 :定义在方法中的类。

定义格式:

class 外部类名 {
    数据类型 变量名;

    修饰符 返回值类型 方法名(参数列表) {
        // …
        class 内部类 {
            // 成员变量
            // 成员方法
        }
    }
}

1.将内部类定义在方法里面就叫做局部内部类,类似于方法里面的局部变量。
2.外界是无法直接使用,需要在方法内部创建对象并使用
3.该类可以直接访问外部类的成员,也可以访问方法内的局部变量。

匿名内部类【重点】

隐藏了名字的内部类,可以写在成员位置,也可以写在局部位置

格式:

new 类名或者接口名() {
     重写方法;
};

包含:

  • 继承或者实现关系

  • 方法重写

  • 创建对象

什么时候用到匿名内部类

当方法的参数是接口或者类时,如果希望定义一个只要使用一次的类,就可考虑使用匿名内部类。匿名内部类的本质作用是为了简化代码。

之前使用接口时,如下几步操作:

  1. 定义子类

  2. 重写接口中的方法

  3. 创建子类对象

  4. 调用重写后的方法

//接口
public interface Swim {
    public abstract void swimming();
}


// 1. 定义接口的实现类
class Student implements Swim {
    // 2. 重写抽象方法
    @Override
    public void swimming() {
        System.out.println("狗刨式...");
    }
}

//1,把前面的class删掉,剩余的内容就变成了一个没有名字的类
//2,这个没有名字的类想要实现Swim接口。
//把Swim写在了大括号的前面,表示这个没有名字的类实现了Swim接口
//所以需要在类中重写接口里面所有的抽象方法。

//匿名内部类的对象是下面代码的整体
//匿名内部类是Swim()后面的{}
//new是创建Swim()后面的{}的对象
//而{}前的Swim表示实现Swim接口
new Swim() {
    // 2. 重写抽象方法
    @Override
    public void swimming() {
        System.out.println("狗刨式...");
    }
};


//接口多态
Swim s=new Swim() {
    // 2. 重写抽象方法
    @Override
    public void swimming() {
        System.out.println("狗刨式...");
    }
};
//编译看左边,运行看右边
s.swimming();

//直接调用重写之后的方法
new Swim() {
    // 2. 重写抽象方法
    @Override
    public void swimming() {
        System.out.println("狗刨式...");
    }
}.swimming();

匿名内部类把以上四步合成一步

匿名内部类前提和格式

匿名内部类必须继承一个父类或者实现一个父接口

匿名内部类格式

new 父类名或者接口名(){   
 // 方法重写   
 @Override     
public void method() {      
  // 执行语句   
}};

使用方式

以接口为例,匿名内部类的使用,代码如下:

interface Swim {
    public abstract void swimming();
}

public class Demo07 {
    public static void main(String[] args) {
        // 使用匿名内部类
        new Swim() {
            @Override
            public void swimming() {
                System.out.println("自由泳...");
            }
        }.swimming();

        // 接口 变量 = new 实现类(); // 多态,走子类的重写方法
        Swim s2 = new Swim() {
            @Override
            public void swimming() {
                System.out.println("蛙泳...");
            }
        };

        s2.swimming();
        s2.swimming();
    }
}

匿名内部类的特点

  1. 定义一个没有名字的内部类

  2. 这个类实现了父类,或者父类接口

  3. 匿名内部类会创建这个没有名字的类的对象

匿名内部类的使用场景

通常在方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递。代码如下:

interface Swim {
    public abstract void swimming();
}

public class Demo07 {
    public static void main(String[] args) {
        // 普通方式传入对象
        // 创建实现类对象
        Student s = new Student();

        goSwimming(s);
        // 匿名内部类使用场景:作为方法参数传递
        Swim s3 = new Swim() {
            @Override
            public void swimming() {
                System.out.println("蝶泳...");
            }
        };
        // 传入匿名内部类
        //编译看左边,运行看右边    
        goSwimming(s3);

        // 完美方案: 一步到位
        goSwimming(new Swim() {
            public void swimming() {
                System.out.println("大学生, 蛙泳...");
            }
        });

        goSwimming(new Swim() {
            public void swimming() {
                System.out.println("小学生, 自由泳...");
            }
        });
    }

    // 定义一个方法,模拟请一些人去游泳
    public static void goSwimming(Swim s) {
        s.swimming();
    }
}

本文是转载文章,点击查看原文
如有侵权,请联系 lx@jishuguiji.net 删除。