Java抽象类与接口

目录

一、抽象类

1.1 抽象类的概念

1.2 抽象类语法

1.3 抽象类特性

1.3.1 抽象类不能实例化对象

1.3.2 抽象方法不能是被private修饰的

1.3.3 抽象方法不能被final和static修饰,因为抽象方法要被子类重写

1.3.4 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰

1.3.5 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类 

1.3.6 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量

1.4 抽象类的作用

二、接口

2.1 接口的概念

2.2 语法规则

2.3 接口特性

2.3.1接口是使用interface修饰的

2.3.2 接口当中只能存在抽象方法,除了被 static 或 default 修饰的方法

2.3.3 接口当中的抽象方法默认都是public abstract 修饰的

2.3.4 接口中的成员变量默认都是public static final修饰的

2.3.5 接口不能直接实例化

2.3.6 类和接口之间的关系可以使用 implements 来

2.3.7 接口也有对应的字节码文件

2.3.8 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类

2.3.9 jdk8中:接口中还可以包含default方法。

2.4 实现多个接口

2.5 接口间的继承

2.6 使用接口实现对象的比较

2.7 Clonable 和 深拷贝

2.7.1 浅拷贝

2.7.2 深拷贝

2.8 抽象类和接口的区别

3.内部类

3.1 实例内部类

​编辑

3.2 静态内部类

3.3 局部内部类

3.4 匿名内部类


一、抽象类

1.1 抽象类的概念

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描
绘对象的。
如果 一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类
比如
在打印图形例子中, 我们发现 , 父类 Shape 中的 draw 方法好像并没有什么实际工作 , 主要的绘制图
形都是由 Shape的各种子类的 draw 方法来完成的 .
像这种没有实际工作的方法 , 我们可以把它设计成一个 抽象方法 (abstract method) , 包含抽象方法
的类我们称为 抽象类 (abstract class)

1.2 抽象类语法

Java 中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方
,抽象方法不用给出具体的实现体。
abstract public class Shape {
    abstract void draw();
}
注意:抽象类也是类,内部可以包含普通方法和属性,甚至构造方法

1.3 抽象类特性

1.3.1 抽象类不能实例化对象

1.3.2 抽象方法不能是被private修饰的

1.3.3 抽象方法不能被finalstatic修饰,因为抽象方法要被子类重写

1.3.4 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰

 

1.3.5 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类 

1.3.6 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量

1.4 抽象类的作用

使用抽象类相当于多了一重编译器的校验
使用抽象类的场景就如上面的代码 , 实际工作不应该由父类完成 ,
而应由子类完成 . 那么此时如果不小心误用成父类了,
使用普通类编译器是不会报错的.
但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题 .

二、接口

2.1 接口的概念

在现实生活中,接口的例子比比皆是
电脑的 USB 口上,可以插: U 盘、鼠标、键盘 ... 所有符合 USB 协议的设备
电源插座插孔上,可以插:电脑、电视机、电饭煲 ... 所有符合规范的设备
可以看出: 接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用
Java 中,接口可以看成是:多个类的公共规范,是一种引用数据类型。

2.2 语法规则

接口的定义格式与定义类的格式基本相同,将 class 关键字换成 interface关键字
publicinterface接口名称{
//抽象方法
  publicabstractvoidmethod1();  //public abstract是固定搭配,可以不写
  publicvoidmethod2();
  abstractvoidmethod3();
  voidmethod4();
  
  //注意:在接口中上述写法都是抽象方法,跟推荐方式4,代码更简洁
}
1. 创建接口时 , 接口的命名一般以大写字母 I 开头(非硬性)
2. 接口的命名一般使用 " 形容词 " 词性的单词 .(非硬性)
3.阿里编码规范中约定 , 接口中的方法和属性不要加任何修饰符号 , 保持代码的简洁性 .(非硬性)

2.3 接口特性

2.3.1接口是使用interface修饰的

2.3.2 接口当中只能存在抽象方法,除了被 staticdefault 修饰的方法

2.3.3 接口当中的抽象方法默认都是public abstract 修饰的

2.3.4 接口中的成员变量默认都是public static final修饰的

2.3.5 接口不能直接实例化

2.3.6 类和接口之间的关系可以使用 implements 来

interface  Shape {

    void draw();

}

public class Triangle implements Shape{
    public void draw(){
        System.out.println("画了一个三角形");
    }
}

public class Rect implements Shape {
    public void draw(){
        System.out.println("画了一个矩形");
    }
}

public class Circle implements Shape{
    public void draw(){
        System.out.println("画了一个圆形");
    }
}

public class Main {

    public static void main(String[] args) {
          Shape[] shapes = new Shape[]{new Circle(),new Triangle(),new Rect()};
          for (Shape shape: shapes){
              shape.draw();
          }
    }
}

2.3.7 接口也有对应的字节码文件

2.3.8 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类


2.3.9 jdk8中:接口中还可以包含default方法。

2.4 实现多个接口

Java 中,类和类之间是单继承的,一个类只能有一个父类,即 Java 中不支持多继承 ,但是 一个
类可以实现多个接 。下面通过类来表示一组动物 .
package Demo2;

abstract public class Animal {
String name;
int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public abstract void eat();
}

interface  IRUN{
    void run();
}

interface  ISWIM{
    void swim();
}
interface IFLY{
    void fly();
}
package Demo2;

public class Dog extends Animal implements IRUN {
    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(this.name + "正在吃狗粮");
    }

    @Override
    public void run() {
        System.out.println(this.name + "正在飞速地跑");
    }
}
package Demo2;

public class Cat extends Animal implements IRUN {
    public Cat(String name, int age) {
        super(name, age);
    }
    public void eat(){
        System.out.println(this.name + "正在吃猫粮");
    }

    public void run(){
        System.out.println(this.name + "正在轻快地跑");
    }
}
package Demo2;

public class Duck extends Animal implements IRUN,IFLY,ISWIM{
    public Duck(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(this.name + "正在吃虫子");
    }

    @Override
    public void run() {
        System.out.println(this.name + "正在用鸭脚跑");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "正在用鸭脚游泳");
    }

    @Override
    public void fly() {
        System.out.println(this.name + "正在用翅膀飞");
    }
}

需要注意的是

顺序不能改变

不能说是具备了某个功能是某个事物

而是是某个事物才具备某个功能

package Demo2;

public class Main {

    public static void func(Animal animal) {
        animal.eat();
    }

    public static void running(IRUN irun) {
        irun.run();
    }

    public static void flying(IFLY ifly) {
        ifly.fly();
    }

    public static void swimming(ISWIM iswim) {
        iswim.swim();
    }

   
    public static void main(String[] args) {
        func(new Duck("小黄", 2));
        func(new Dog("大黄", 2));
        func(new Cat("花花", 2));
        swimming(new Duck("小黄", 2));
        running(new Duck("小黄", 2));
        running(new Dog("大黄", 2));
        running(new Cat("花花", 2));
        flying(new Duck("小黄", 2));
    }
}

甚至只要是任意一个类就可以实现接口就可以使用

package Demo2;

public class Robot implements IRUN{
    public void run(){
        System.out.println("机器人正在跑!");
    }
}

  public static void main(String[] args) {
        running(new Robot());
    }

2.5 接口间的继承

Java 中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:
用接口可以达到多继承的目的。
interface IRUN {
    void run();
}

interface ISWIM {
    void swim();
}

interface IFLY {
    void fly();
}

interface IALL extends ISWIM, IRUN, IFLY {

}
package Demo2;

public class Main {

    public static void running(IRUN irun) {
        irun.run();
    }

    public static void flying(IFLY ifly) {
        ifly.fly();
    }

    public static void swimming(ISWIM iswim) {
        iswim.swim();
    }

    public static void main(String[] args) {
        swimming(new Duck("小黄", 2));
        running(new Duck("小黄", 2));
        flying(new Duck("小黄", 2));

    }
}

2.6 使用接口实现对象的比较

如果直接对对象进行大小比较

是不允许进行比较的

因为这里的 dog1 dog2 属于引用数据类型,是地址,无法进行比较

而如果想要进行比较,需要通过他们的属性进行比较

Java中提供了 Comparable  这个接口

那么这里可以看到的是需要重写 compareTo 这个方法

abstract public class Animal implements Comparable<Animal> {
    String name;
    int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compareTo(Animal o) {
        return this.age-o.age;
    }

    public abstract void eat();
}

interface IRUN {
    void run();
}

interface ISWIM {
    void swim();
}

interface IFLY {
    void fly();
}

interface IALL extends ISWIM, IRUN, IFLY {

}

这里假设要按年龄进行比较

那么就可以通过这个方法进行比较

 public static void main(String[] args) {
        Dog dog1 = new Dog("1",2);
        Dog dog2 = new Dog("2",2);
        System.out.println(dog1.compareTo(dog2));
    }


 

这说明 在一个类中实现 Comparable 就说明这个类是可以进行比较的

但是这样写确实可以进行比较,但是对类的入侵性比较强

如果需求更改了,不按照这一属性进行比较

通俗点来说,就是写死了

那么可以采用比较器(Comparator)来进行比较

package Demo2;

import java.util.Comparator;

public class AgeComparator implements Comparator<Animal> {
    @Override
    public int compare(Animal o1, Animal o2) {
        return o1.age - o2.age;
    }
}
public static void main(String[] args) {
        AgeComparator ageComparator = new AgeComparator();
        Dog dog1 = new Dog("1",2);
        Dog dog2 = new Dog("2",3);
        System.out.println(ageComparator.compare(dog1, dog2));
    }

 

对于name 来说就不能这样 ,因为他是引用数据类型

 直接使用  compareTo 这个方法,那么这里并没有重写这个方法,为什么可以直接使用呢

是因为这里name属于String类,而String类中实现了 compareTo

    public static void main(String[] args) {
        AgeComparator ageComparator = new AgeComparator();
        NameComparator nameComparator = new NameComparator();
        Dog dog1 = new Dog("1", 2);
        Dog dog2 = new Dog("2", 3);
        System.out.println(ageComparator.compare(dog1, dog2));
        System.out.println(nameComparator.compare(dog1,dog2));
    }

Animal中的 compareTo 方法实现后也可以让Array.sort 方法成功实现

  public static void main(String[] args) {
        Animal[] animals = new Animal[3];
        animals[0] = new Dog("cc",3);
        animals[1] = new Dog("bb",2);
        animals[2] = new Dog("aa",1);

        System.out.println(Arrays.toString(animals));
        Arrays.sort(animals);
        System.out.println("======================");
        System.out.println(Arrays.toString(animals));
    }

还可以使用比较器模拟 sort 排序 

public static void main(String[] args) {
        Animal[] animals = new Animal[3];
        animals[0] = new Dog("cc", 3);
        animals[1] = new Dog("bb", 2);
        animals[2] = new Dog("aa", 1);

        System.out.println(Arrays.toString(animals));
        for (int i = 0; i < animals.length - 1; i++) {
            AgeComparator ageComparator = new AgeComparator();
            for (int j = 0; j < animals.length - 1 - i; j++) {

                if (ageComparator.compare(animals[j], animals[j + 1]) >= 1) {
                    Animal temp = animals[j];
                    animals[j] = animals[j + 1];
                    animals[j + 1] = temp;
                }
            }
        }
        System.out.println(Arrays.toString(animals));
    }

2.7 Clonable 和 深拷贝

这里有一个 Person

public class Person {
    public String name;
    public int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

想要将一个 Person 对象拷贝给另一个 Person 对象可以使用 clone 方法

而在这个 Person 类 中并没有这个方法,需要自己写吗

在之前的文章中说到,Java中所有类的父类都是 Object

Object 类中 正好实现了 clone 方法

那么 Person 类中肯定也继承了这个方法,就可以使用

但是直接使用的时候编译器报错了

在 Object 类中该方法是被 protected 修饰的,需要通过 super 调用

那么这里在 Person 类中重写一下这个方法 

public class Person {
    public String name;
    public int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

需要注意的是

此时这里的返回类型是 Object 

在进行对象拷贝的时候 , 返回值类型需要进行类型转换

public class Main  {
    public static void main(String[] args)  throws CloneNotSupportedException{
      Person person1 = new Person("张三",18);
      Person person2 = (Person) person1.clone();
    }
}

为什么会加上这个会在以后的文章中讲到

但是这里在代码跑起来后,编译器仍然报错了

需要在 Person 类这里加上 implements Cloneable

public class Person implements Cloneable {
    public String name;
    public int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

此时就可以跑了

2.7.1 浅拷贝

在2.7的例子上做些改动

public class Money {
    double m = 20.0;
}
public class Person implements Cloneable {
    public String name;
    public int age;

    Money money = new Money();
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class Main  {
    public static void main(String[] args)  throws CloneNotSupportedException{
      Person person1 = new Person("张三",18);
      Person person2 = (Person) person1.clone();

        System.out.println(person1);
        System.out.println(person2);
        System.out.println("========================");
        System.out.println("person1:"+person1.money.m);
        System.out.println("person2:"+person2.money.m);
        person1.money.m = 99.99;
        System.out.println("========================");
        System.out.println("person1:"+person1.money.m);
        System.out.println("person2:"+person2.money.m);
    }
}

这里可以看到的是,只对 person1money 进行了修改

但是 person2money 的值也被修改了

原因是

clone 方法只是将 person1 指向的对象进行克隆

money 这个对象的地址并没有改变,就说明 person1 person2 中的 money 对象是同一个

这种情况就成为浅拷贝

2.7.2 深拷贝

那么原因分析出来了,想避免这种情况其实就很简单了,把 money 变成两个对象不就好了

即对 money 再单独克隆一份

public class Money implements Cloneable{
    double m = 20.0;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

  @Override
    protected Object clone() throws CloneNotSupportedException {
//        return super.clone();
        Person temp = (Person) super.clone();
        temp.money = (Money) this.money.clone();
        return temp;
    }

此时运行之前的代码

public class Main  {
    public static void main(String[] args)  throws CloneNotSupportedException{
      Person person1 = new Person("张三",18);
      Person person2 = (Person) person1.clone();

        System.out.println(person1);
        System.out.println(person2);
        System.out.println("========================");
        System.out.println("person1:"+person1.money.m);
        System.out.println("person2:"+person2.money.m);
        person1.money.m = 99.99;
        System.out.println("========================");
        System.out.println("person1:"+person1.money.m);
        System.out.println("person2:"+person2.money.m);
    }
}

 

对 person1money 的修改就不会影响到 person2money 的值

总而言之是将每一个对象都进行拷贝

这种拷贝方式就叫做深拷贝

2.8 抽象类和接口的区别

抽象类和接口都是 Java 中多态的常见使用方式
核心区别 : 抽象类中可以包含普通方法和普通字段 , 这样的普通方法和字段可以被子类直接使用 (
必重写 ), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法
抽象类存在的意义是为了让编译器更好的校验 , 这样的类我们并不会直接使用 , 而是使用它的子类.
万一不小心创建了抽象类的实例, 编译器会及时提醒 .
区别抽象类(abstract)接口(intertface)
1结构组成普通类 + 抽象方法抽象方法+全局常量
2权限各种权限public
3子类使用extends关键字implements关键字
4关系一个抽象类可以实现若干接口接口不能继承抽象类,可以使用extends关键字继承多个父接口
5

3.内部类

在外部类中,内部类定义位置与外部类成员所处的位置相同,因此称为成员内部类
 

3.1 实例内部类

public class Out {
    class Inner{
        public int a ;
        public int b ;
        
        public void test(){
            System.out.println("调用了内部类方法");
        }
    }
    public void test(){
        System.out.println("调用了外部类方法");
    }
}

这样就在一个类当中创建了一个内部类

这个内部类不能直接进行实例化

原因是内部类本质上属于外部类的成员

需要依赖外部类对象进行调用

首先需要实例化外部类对象,再通过外部类对象的引用来实例化内部类

public class Main {
    public static void main(String[] args) {
        Out out = new Out();
        Out.Inner inner = out.new Inner();
//        通过外部类对象调用内部类实例
        inner.test();
    }
}

一般情况下,内部类中不能使用 static 进行修饰

原因是静态的变量不依赖实例化

这就与内部类的实例相冲突了

如果想使用 static 修饰 就还需要加上 final 修饰 

直接让内部类的该成员变量成为常量

public class Main {
    public static void main(String[] args) {
        Out out = new Out();
        Out.Inner inner = out.new Inner();
//        通过外部类对象调用内部类实例
        inner.test();
        System.out.println(Out.Inner.c);
    }
}

 

存在

public class Out {
    public int a = 1;
    class Inner{
        public int a = 2;
        public int b ;

        public static final  int c = 5;
        public void test(){
            System.out.println("调用了内部类方法");
            System.out.println(a);
        }
    }
    public void test(){
        System.out.println("调用了外部类方法");
    }
}

若存在同名成员变量,在内部类中调用成员变量优先调用内部类自己的成员变量

  public static void main(String[] args) {
        Out out = new Out();
        Out.Inner inner = out.new Inner();
//        通过外部类对象调用内部类实例
        inner.test();
    }

 

 如果想使用外部类的同名成员变量

就得使用外部类类名.this.成员变量

 public void test(){
            System.out.println("调用了内部类方法");
            System.out.println(a);
            System.out.println(Out.this.a);
        }

1. 外部类中的任何成员都可以在实例内部类方法中直接访问
2. 实例内部类所处的位置与外部类成员位置相同,因此也受 public private 等访问限定符的约束
3. 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必
须:外部类名 称.this.同名成员 来访问
4. 实例内部类对象必须在先有外部类对象前提下才能创建
5. 实例内部类的非静态方法中包含了一个指向外部类对象的引用
6. 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象。

3.2 静态内部类

static 修饰的内部类被称为静态内部类

public class Out2 {
    public int a;
    static class Inner2{
        public int b;
        public int c;
        public static int d = 10;

        public static void test(){
            System.out.println("调用了静态内部类方法");
        }
    }
    public void test(){
        System.out.println("调用了外部类方法");
    }

}

静态内部类不需要依赖外部类对象的引用就可以使用

即不需要实例话一个外部类对象就可以直接使用外部类名.内部类名 类名 

实例化一个内部类对象

public class Out2 {
    public int a;
    static class Inner2{
        public int b;
        public int c;
        public static int d = 10;

        public  void test(){
            System.out.println("调用了静态内部类方法");
        }
    }
    public void test(){
        System.out.println("调用了外部类方法");
    }

}
public class Main {
    public static void main(String[] args) {
        Out2.Inner2 inner2 = new Out2.Inner2();
        inner2.test();
    }

而在静态类中想要调用外部的非静态成员变量

则需要外部类对象的引用

public class Out2 {
    public int a = 1;
    static class Inner2{
        public int b;
        public int c;
        public static int d = 10;

        public  void test(){
            Out2 out2 = new Out2();
            System.out.println("调用了静态内部类方法");
            System.out.println(out2.a);
        }
    }
    public void test(){
        System.out.println("调用了外部类方法");
    }

}
  public static void main(String[] args) {
        Out2.Inner2 inner2 = new Out2.Inner2();
        inner2.test();
    }

总的来说

静态内部类相比于实例内部类

不需要外部类对象的引用,使用起来更加方便

3.3 局部内部类

定义在外部类的方法体或者 {} 中,该种内部类只能在其定义的位置使用,一般使用的非常少
这里只简单说一下

public class Test2 {
    public void fun(){
        class Demo{
            public  int a = 2;

        }
        Demo demo = new Demo();
        System.out.println("调用了局部内部类!");
        System.out.println(demo.a);

    }

    public static void main(String[] args) {
       Test2 test2 = new Test2();
       test2.fun();
    }
}

1. 局部内部类只能在所定义的方法体内部使用
2. 不能被 public static 等修饰符修饰

3.4 匿名内部类

与接口有关

interface Demo{
    void test();
}
public class Test3 {
    public static void main(String[] args) {
        new Demo(){
            @Override
            public void test() {
                System.out.println("匿名内部类实现Demo接口");
            }
        };
    }
}

接口是不能进行实例化的

但是此处就可以理解为有一个类实现了Demo这个接口,而这个类的名字是被隐藏起来的

调用方法有两种

public static void main(String[] args) {
        Demo demo = new Demo(){
            @Override
            public void test() {
                System.out.println("匿名内部类实现Demo接口");
            }
        };
        demo.test();
        System.out.println("===========================");
        new Demo(){
            @Override
            public void test() {
                System.out.println("匿名内部类实现Demo接口");
            }
        }.test();
    }

  • 22
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值