【Java】—— 抽象类和接口

1.抽象类

1.1 抽象类的概念

前文讲过的普通的类,就是现实事物在 Java 代码中的一种 “抽象的表现形式”,类里包含着属性,方法。

抽象方法:将方法的实现部分忽略掉,只保留方法的名字,参数等信息(没有方法的实现逻辑)

包含抽象方法的类,就称为抽象类

抽象类的存在,更多是为了做出 “强制限制”,与 final 类似。

1.2 抽象类的语法

abstarct : 通过这个关键字修饰的方法,就叫做抽象方法;

                通过这个关键字修饰的类,就叫做抽象类。

//抽象类
public abstract class Shape {
    //抽象方法(不能写具体的实现方法)
    public abstract void draw();
    //普通属性
    private double area;
    //普通方法
    public double getArea(){
        return area;
    }
    //静态属性
    private static int n = 0;
    //静态方法
    public static int getCount(){
        return n;
    }
}

这里写的抽象方法就是为了交给子类来重写

1.3 抽象类的特性

1、一个抽象类中可以包含普通的属性和方法,也可以包含抽象方法

2、有了抽象方法就一定要有抽象类

3、抽象类不可以被 private 和 static 修饰

4、当父类提供了抽象方法后,子类必须重写这个抽象方法,或者把这个子类也修饰成抽象方法

5、抽象类不能直接实例化对象(抽象类自身不能表示一个“完整的概念”)

6、抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量(构造方法,不一定是在 new 自身的时候触发,new 子类实例也是能触发父类的构造的)

public Shape(){
    area = 100;
    System.out.println("父类的构造方法");
}
public class Test {
    public static void main(String[] args) {
        Shape shape = new Rect();
    }
}

 

2.接口(interface)

2.1 接口的定义

接口的定义非常广泛,不局限于 Java

1、在C语言中,一个函数,也可以称为一个接口

2、Java 中的 interface 也是接口

3、工作中,把一个大的项目,分成多个模块,把模块之间,相互调用的部分,也称为接口

4、服务器,能够处理各种请求的,称为“服务器提供的接口”

5、图形化界面(GUI)也是接口

..........

在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。

2.2 接口的定义

接口的定义格式与定义类的格式基本相同,将 class 关键字换成 interface 关键字,就定义了一个接口

public interface 接口名称 {
}

 对于接口来说,里面不能包含属性,包含的方法也只能是抽象方法 

[public abstract] void draw();

 在接口内部定义的抽象方法可以省略 pubile 和 abstract ,这两个关键字都是被默认添加的

提示(不强制要求):

  1. 创建接口时, 接口的命名一般以大写字母 I 开头.
  2. 接口的命名一般使用 "形容词" 词性的单词.
  3. 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性.

从Java8开始允许往 interface 中放属性,但是有严格要求,会在属性前默认添加 public static final

2.3 接口的使用

和抽象类类似,接口也是不能直接实例化的,要想使用接口,就需要创建类,“实现” 接口

对于实现接口的类来说,接没有什么可以继承的,没有可以重用的内容,而且需要严格按照接口中提供的抽象方法在子类中进行重写

public class 实现类 implements 接口{
    .......
}

代码示例:

public interface USB {
    //打开
    void openDevice();
    //关闭
    void closeDevice();
}

public class Mouse implements USB{
    @Override
    public void openDevice() {
        System.out.println("插入鼠标");
    }

    @Override
    public void closeDevice() {
        System.out.println("拔出鼠标");
    }
}

public class Test {
    public static void main(String[] args) {
        USB usb = new Mouse();
        usb.openDevice();
        usb.closeDevice();
    }
}

这里虽然不使用 “继承术语”,此处仍然视为 “向上转型”

调用下面两个方法时也可以触发 “多态”

2.4 接口特性

对前面内容进行一个小结

1、接口类型是一种 引用类型,但是不能直接 new 接口的对象(比抽象类更抽象)

2、接口中每一个方法都是public的抽象方法, 即接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)

3、接口中的方法是不能在接口中实现的,只能由实现接口的类来实现

4、重写接口中方法时,不能使用默认的访问权限

5、接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量

6、接口中不能有静态代码块和构造方法

7、接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class

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

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

2.5 实现多个接口

接口和抽象类都,都提供了抽象方法

直观上感觉,抽象类功能更强一些,接口限制更严格

Java 的继承体系是 “单继承” 体系

抽象类意味着,该类只能继承自一个抽象类,但是一个类可以实现多个接口

//定义一个接口 表示会跑的
public interface IRunning {
    void run();
}

//定义一个父类,用来表示动物和动物的名字
public class Animal {
    private String name;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

//定义子类,继承父类实现 IRunning 接口
// Cat 是会跑的 (具有xxx特性)
public class Cat extends Animal implements IRunning{
    public Cat(String name){
        super(name);
    }
    public void run(){
        System.out.println(getName() + "使用四条腿跑");
    }
}

//最后在 Test 类中进行测试
public class Test {
    public static void main(String[] args) {

        Animal animal = new Cat("小猫");
        System.out.println(animal.getName());
        //通过不同的实例化,进行对应的操作
        IRunning iRunning = new Cat("小猫");
        iRunning.run();
    }
}

最后的 Test 的类也可以写成

public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat("小猫");
        System.out.println(cat.getName());
        cat.run();
    }
}

可以同时对两个方法进行调用

当要实现多个接口,使用 “,” 将接口分隔开,

public class 子类 extends 父类 implements 接口1,接口2,...,接口n{
    ......
}

最终 类 里要把所有接口里的抽象方法都实现出来

public class Frog extends Animal implements IRunning,ISwimming{


    public Frog(String name) {
        super(name);
    }

    @Override
    public void run() {

    }
    @Override
    public void swim() {

    }
}

2.6接口间的继承

在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。(把多个小的接口合并成一个大的接口)

接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字

这里仍然以上面代码为例

//定义一个两栖(既可以跑也可以游泳的动物)类,直接继承前面写的 IRunning,ISwimming
public interface IAmphibious extends IRunning,ISwimming{
    
}

有了这个接口,青蛙类就可以修改为

public class Frog extends Animal implements IAmphibious{

    public Frog(String name) {
        super(name);
    }

    @Override
    public void run() {

    }
    @Override
    public void swim() {

    }
}

在接口拆的比较零碎的时候,这种写法可读性,易写性都会更高

2.7接口使用实例

2.7.1 Comparable 接口

对象之间进行大小关系比较

有些属性不能直接使用 > < 来进行比较,但是比较的需求是客观存在的,所以需要明确一个比较规则,再进行比较。

Java 标准库中比较规则的指定内置了一个接口 Comparable

后面跟着的尖括号是 泛型参数 涉及到数据结构这里不作介绍,只需要保证前后一致即可

在这里接口里就提供了一个 compareTo 的方法

@Override
public int compareTo(Student o) {
    return 0;
}

这个方法是一个实例方法,内涵了 this,当前对象,参数还有一个 o ,表示另一个要比较的对象,这里就是 this 和 o (other)来记性比较

如果 this 比 o 小,返回 < 0 的整数 ;

如果 this 比 o 大,返回 > 0 的整数 ;

如果 this 和 o 相等,返回 0 

如果希望先比较学生的成绩,成绩高的就是大

如果成绩相同就比较学生的 id ,学生的名字不影响

    public static void main(String[] args) {
        Student s1 = new Student(1,"张三",90.0);
        Student s2 = new Student(2,"李四",80.0);
        System.out.println(s1.compareTo(s2));
    }

    @Override
    public int compareTo(Student o) {
        if(this.score < o.score){
            return -1;
        }else if(this.score > o.score){
            return 1;
        }else {
            if(this.id < o.id){
                return -1;
            } else if (this.id > o.id) {
                return 1;
            }else {
                return 0;
            }
        }
    }

就可以通过返回值来进行判断大小

System.out.println(s1.compareTo(s2));

这里是我们手动调用的 compareTo 方法,而不是使用的 < >。

在 Java 中不支持运算符重载的写法

2.7.2 Comparator 接口

标准库中除了 Comparable,还提供了另一个接口, Comparator 也能进行比较操作,Comparator 是通过创建一个单独的类,来实现 Comparator 接口

import java.util.Comparator;

public class StudentComparator implements Comparator<Student2> {

    @Override
    public int compare(Student2 o1, Student2 o2) {
        return 0;
    }
}

Comparator 是拿着两个参数来进行比较

返回值规则与 Comparable 类似

import java.util.Comparator;

public class StudentComparator implements Comparator<Student2> {

    @Override
    public int compare(Student2 o1, Student2 o2) {
        if(o1.getScore() > o2.getScore()){
            return 1;
        }
        if (o1.getScore() < o2.getScore() ) {
            return -1;
        }
        if(o1.getId() < o2.getId()){
            return -1;
        }
        if(o1.getId() > o2.getId()){
            return 1;
        }
        return 0;
    }
}

public class Student2 {
    private int id;     //学号
    private String name;    //姓名
    private double score;   //成绩

    public Student2 (int id, String name, double score) {
        this.id = id;
        this.name = name;
        this.score = score;
    }

    public int getId() {
        return id;
    }

    public double getScore() {
        return score;
    }

    public static void main(String[] args) {
        Student2 s1 = new Student2(1,"张三",90.0);
        Student2 s2 = new Student2(2,"李四",80.0);
        StudentComparator comparator = new StudentComparator();
        System.out.println(comparator.compare(s1,s2));
    }

}

当比较规则有多种,或者被比较的类的源码不能修改

就可以使用 Comparator 了。

2.7.3 Cloneable 接口

允许对象进行克隆 

public class Test {
    public static void main(String[] args) {
        Student s = new Student(1,"张三",90);
        Student s2 = s;
    }
}

这段代码只是创建一个新的引用,此时 对象 本身仍然是只有一个

克隆 是创建一个新对象,新对象的内容要和旧对象的一样

实现接口

public class 类名 implements Cloneable{
    ....
}


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

 此处 父类的 clone 方法,不是 Cloneable 本身提供的,而是 Object 这个类提供的

当前写 Student 看起来没有父类,其实隐式继承自 Student

这里有一个 native 关键字,标记这个关键字的方法,称为 “本地方法”,也就是在 JVM 内部,通过 C++ 代码来实现的逻辑,在 Java 中的很多对象,往底层深究,大部分会进入到 C++ 逻辑中

 如果希望使用就需要对这段代码进行一个修改

@Override
public Student clone() throws CloneNotSupportedException {
    return (Student) super.clone();
}

修改了返回值类型,只需要与原类型相容即可,再在下面进行强制转换

有了这个克隆方法就可以在下面进行调用

这里会出现一个报错,这是因为clone 方法后跟着一个异常声明,这里不作介绍

只需要对着 s.clone() 按 alt + 回车,选择第一个,把异常声明加到 main 后就可以了,这时我们就得到了一个新的对象,通过 s2 来指向这个对象

public class Student implements Cloneable{
    private int id;     //学号
    private String name;    //姓名
    private double score;   //成绩

    public Student(int id, String name, double score) {
        this.id = id;
        this.name = name;
        this.score = score;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public double getScore() {
        return score;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setScore(double score) {
        this.score = score;
    }

    @Override
    public Student clone() throws CloneNotSupportedException {
        return (Student) super.clone();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        Student s = new Student(1,"张三",90);
        Student s2 = s.clone();

        System.out.println(s2.id);
        System.out.println(s2.name);
        System.out.println(s2.score);
        //可以使用 == 来判断两个对象地址是否一样
        System.out.println(s == s2);
    }
}

运行结果

虽然 clone 是 Object 提供的,不是 Cloneable 接口提供的,仍然需要实现 Cloneable 接口,才能使用 Clone 方法(JVM 的硬性规定)

2.8 深拷贝与浅拷贝

clone 操作是否要递归进行:是,就是 深拷贝,不是,就是 浅拷贝

(如果类中的属性都是内置类型,不涉及深浅拷贝问题)

public class Money {
    private double amount = 1000;

    public double getAmount() {
        return amount;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }
}

public class Person implements Cloneable{
    private int id;

    private Money money = new Money();

    @Override
    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        Person p1 = new Person();
        p1.id = 1;
        p1.money.setAmount((1000));

        Person p2 = p1.clone();
        System.out.println(p2.id);
        System.out.println(p2.money.getAmount());
    }
}

这里通过 2.7.3 的方式 将 p1 拷贝到了 p2

接下来修改一下 p2 的 id 

可以看到结果是分开的,再接着修改一下 money

结果却变成一样的了,这是因为拷贝操作,只是把 Person 对象拷贝了一下

而 Person 内部有的 Money 对象没有真正拷贝

这就我们就叫做 浅拷贝

深拷贝,则是表示,不光要把 Person 对象本身进行拷贝,也要把 Person 内部持有的引用类型的属性,也去进行 拷贝。如果引用类型属性中还有引用类型,就要持续的递归拷贝进去,保证所有内部持有的引用类型,都对应拷贝一份新的

//Person 中的 clone 方法
@Override
public Person clone() throws CloneNotSupportedException {
    //这里不着急返回,先把拷贝的值记录下来赋值给 p
    Person p = (Person) super.clone();
    //再利用 money 的引用类型去拷贝出 money 的值
    p.money = p.money.clone();
     p;
}

//在 Money 中添加 clone 方法
public Money clone() throws CloneNotSupportedException {
    return (Money) super.clone();
}

这样就是深拷贝了

此处 Person 中只有一个引用类型的成员,如果 Person 还有其他引用类型,有几个就要 clone几个

包括 Money 内部,可以也有一些引用类型的成员,也是需要对应 clone。

2.9 抽象类与接口的区别

3.内部类

把一个类定义到另一个内里面(少见的情况)

3.1 普通内部类

几乎见不到,主要介绍语法规则

//最普通的,把类直接写到 文件 中,也可以称为 "外部类"
public class Test {
    //类里可以有属性的和方法
    private int n = 10;

    public void printN(){
        System.out.println(n);
    }
    //正确写法:
    public void func(){
        Inner inner = new Inner();
        inner.printM();
    }
    //这是一个普通的内部类
    //普通内部的创建实例时依赖外部类的 this 引用
    //内部类前不能写 public
    class Inner{
        private int m = 20;
        public void printM(){
            System.out.println(m);
            //内部类可以访问外部类的属性,即使是被 private 修饰
            //也可以访问外部类的方法
            //这也是内部类的实例需要外部类的 this
            System.out.println(n);
        }
    }

    public static void main(String[] args) {
        //创建内部类的实例
        //此处不能这样写,static 方法中没有 外部类的 this
        //Test.Inner inner = new Test.Inner();
        Test test = new Test();
        test.func();
    }
}

3.2 静态内部类

(内部类前面添加 static 关键字)

同样是不常见

public class Test2 {
    private int n = 10;
    private static int staticN = 100;
    //静态内部类
    static class Inner{
        private int m = 10;
        public void printM(){
            System.out.println("m="+ m);
            //静态内部类无法访问非静态的属性
            //编译错误(System.out.println("n+",n);)
            //静态成员是可以访问的
            System.out.println("staticN = " + staticN);
    }

        public static void main(String[] args) {
            //此时可以创建这个类的实例
            Inner inner = new Inner();
            inner.printM();
        }
    }
}

3.3 局部内部类

(方法里面定义的类)

不像是人能写出来的

public class Test3 {
    public void func(){
        //方法执行到时,才会创建这个类
        class Inner{
            private int m = 10;

            public void printM(){
                System.out.println("m=" + m);
            }
        }
        Inner inner = new Inner();
        inner.printM();
    }
}

3.4 匿名内部类(最重要的)

(局部内部类的特殊情况)

匿名内部类的写法

import java.util.Comparator;

public class Test4 {
    public static void main(String[] args) {
        //需要给 Test4 这个类,创建 Comparator 比较器
        Comparator<Test4> comparator = new Comparator<Test4>() {
            @Override
            public int compare(Test4 o1, Test4 o2) {
                return 0;
            }
        };

        Test4 t1 = new Test4();
        Test4 t2 = new Test4();
        int ret = comparator.compare(t1,t2);
        System.out.println(ret);
    }
}

4.Object 类

标准库中内置的类,所有 Java 类的祖宗

所有的 Java 的类都是 直接/间接 继承自 Object

Object 里面就提供了所有 Java 所以类共性的属性方法

4.1 获取对象信息

如果要打印对象中的内容,可以直接重写Object类中的toString()方法,在【类和对象】中已经介绍过,这里不再重复

4.2 对象比较equals方法

== 针对内置类型,确实是比较相等

对于引用类型,是比较两个引用是否指向同一个对象(引用内部存储的地址)

equals 就是给所有类都提供的 自定义的 比较规则

比较两个字符串就需要用到 equals

public class Test {
    public static void main(String[] args) {
//        String s1 = "hello";
//        String s2 = "hello";
//        //针对上述这样的两个 String 对象,他们的地址值时相同的
//        //即 s1 和 s2 指向同一块内存空间,因此输出True
//        System.out.println(s1==s2);

        //换一种写法
        String s1 = new String("hello");
        String s2 = new String("hello");
        //这是比较两个引用的指向是否是相同的
        System.out.println(s1 == s2);
        //比较内容,就可以使用 equals 方法
        System.out.println(s1.equals(s2));
    }
}

如果是自己定义的类,就需要对 equals 方法进行重写(Student 类的定义可以参考2.7.2中的定义)

    @Override
    public boolean equals(Object o) {
        //比较特殊情况
        if(o == null){
            // this 肯定不是 null
            return false;
        }
        if(this == o){
            //如果两个引用指向一个对象,值肯定是相等的
            return true;
        }
        if(!(o instanceof Student)){
            //传入的参数类型和当前 Student 不匹配
            return false;
        }
        //比较两个学生的学号是否相同
        //对 o 进行强转
        Student other = (Student) o;
        return this.id == other.id;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值