【Java】抽象类和接口

一、抽象类

1、抽象类概念

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类
像这种没有实际工作的方法,我们可以把它设计成一个抽象方法(abstract
method)
,包含抽象方法的类我们称为抽象类(abstract class)

2、抽象类语法

在Java中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。

// 抽象类:被abstract修饰的类
public abstract class Shape {
	protected double area; // 面积 -->属性
	
    // 抽象方法:被abstract修饰的方法,没有方法体
    abstract public void draw();
    
    abstract void calcArea(); // 抽象类也是类,也可以增加普通方法和属性
    
    // 普通方法
    public double getArea(){
        return area;
    }   
}

注意:抽象类也是类,内部可以包含普通方法和属性,甚至构造方法

3、抽象类特性

(1)抽象类不能直接实例化对象,但可以发生向上转型,进一步发生多态

abstract class Shape {
    public abstract void draw();
}

class Cycle extends Shape{
    @Override
    public void draw() {
        System.out.println("○");
    }
}

public static void main(String[] args) {
    Shape shape = new Cycle();
}

(2)抽象方法不能是 private

注意:抽象方法没有加访问限定符时,默认是public.

(3)抽象方法不能被 finalstatic 修饰,因为所有抽象方法都要被子类重写

(4)抽象类必须被继承,并且继承后子类要重写父类中的所有抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰;若此时抽象类A继承抽象类Shape,在此基础上抽象类B继承抽象类A,那么抽象类B必须重写Shape和A中所有抽象方法。

abstract class Shape {
    public abstract void draw();
}

class Cycle extends Shape{
    @Override
    public void draw() {
        System.out.println("○");
    }
}

abstract class A extends Shape {
    // 一个抽象类继承另一个抽象,不需要重写方法
    public abstract void func();
}

class B extends A {
// Alt + Enter 重写所有方法
    @Override
    public void draw() {

    }

    @Override
    public void func() {

    }
}

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

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

二、接口

1、接口概念

接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。

2、语法规则

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

public interface 接口名称{
    // 抽象方法
    public abstract void method1(); // public abstract 是固定搭配,可以不写
    public void method2();
    abstract void method3();
    void method4();
	// 注意:在接口中上述写法都是抽象方法,但更推荐方式4,代码更简洁
}

3、接口使用

(1)接口当中的成员方法只能是抽象方法,默认是 public abstract 修饰,且只能是 public abstract

(2)接口当中的成员变量默认是 public static final ,必须初始化

(3)接口当中的方法如果要实现,需要使用 default 修饰

(4)接口当中的静态方法,可以有具体的实现

(5)接口不能实例化,不能 new 接口,只能使用一个普通的类用 implements 实现接口当中的方法。接口中的方法:抽象的方法必须重写(重写的方法必须是public),默认方法可以重写也可以不重写,静态方法直接类调用。

interface IShape {
    // 成员变量
    public static final int a = 10;
    int b = 20; // 默认是 public static final ,必须初始化

    // 接口当中的方法如果要实现,需要使用 default 修饰
    default void func() {
        System.out.println("默认的方法");
    } // 默认方法可以重写也可以不重写

    // 接口当中的静态方法,可以有具体的实现
    public static void func2() {
        System.out.println("静态的方法");
    }

    void draw(); // 抽象方法必须重写
}

class A implements IShape {

    @Override
    public void func() {
        System.out.println("func()重写");
    }

    @Override
    public void draw() {
        System.out.println("draw()重写");
    }
}

public class Test {
    public static void main(String[] args) {
        A a = new A();
        a.draw();
        a.func();
        IShape.func2(); // 静态的方法直接类调用
    }
}


注意:子类和父类之间是 extends 继承关系,类与接口之间是 implements 实现关系。

(6)接口中不能有静态代码块和构造方法
在这里插入图片描述

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

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

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

4、 实现多个接口

class Animal {
    public String name;
    public int age;

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

    public void eat() {
        System.out.println(this.name + "吃饭!");
    }

    // 跑、飞、游泳不能写到这里
}

interface IFlying {
    void fly();
}

interface IRunning {
    void run();
}

interface ISwimming {
    void swim();
}

class Dog extends Animal implements IRunning, ISwimming{

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

    @Override
    public void run() {
        System.out.println(this.name + "is dog, 正在跑!");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "is dog, 正在游泳!");
    }
}

class Cat extends Animal implements IRunning {

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

    @Override
    public void run() {
        System.out.println(this.name + "is cat, 正在跑");
    }
}

public class Test {

    public static void walk(IRunning iRunning) {
        iRunning.run();
    }

    public static void main(String[] args) {
        Cat cat = new Cat("咪咪");
        //cat.run();
        walk(cat);

        Dog dog = new Dog("旺财");
        //dog.run();
        walk(dog);
    }
}

从上述代码可以看出:一个类可以继承抽象类,并实现多个接口,每个接口用逗号隔开

5、接口间的继承

在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。

interface IRunning {
    void run();
}

interface ISwimming {
    void swim();
}

// 两栖的动物, 既能跑, 也能游
interface IAmphibious extends IRunning, ISwimming {
    
}

class Frog implements IAmphibious {
    @Override
    public void run() {
        System.out.println("正在跑");
    }

    @Override
    public void swim() {
		System.out.println("正在游泳");
    }
}

通过接口继承创建一个新的接口 IAmphibious 表示 “两栖的”,此时实现接口创建的 Frog 类,就继续要实现 run 方法,也需要实现 swim 方法。ctrl + i 可以实现快速重写。
接口间的继承相当于把多个接口合并在一起。

6、接口使用实例

给对象数组排序

class Student implements Comparable<Student>{
    String name;
    int age;
    double score;

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

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

    // 以年龄排序
//    @Override
//    public int compareTo(Student o) {
//        if (this.age > o.age) {
//            return 1;
//        }else if (this.age == o.age) {
//            return 0;
//        }else {
//            return -1;
//        }
//    // return this.age - o.age;
//    // return (int)(this.score - o.score);
//    }

    // 以名字排序
    @Override
    public int compareTo(Student o) {
//        if (this.name.compareTo(o.name) > 0) {
//            return 1;
//        }else if (this.name.compareTo(o.name) == 0) {
//            return 0;
//        }else {
//            return -1;
//        }
        return this.name.compareTo(o.name);
    }
}

public static void main(String[] args) {
	Student[] students = new Student[3];
    students[0] = new Student("zhangsan", 38, 98.4);
    students[1] = new Student("lisi", 73, 42);
    students[2] = new Student("wangwu", 18, 10.5);

	// 以名字排序
    Arrays.sort(students);
    System.out.println(Arrays.toString(students)); 
}

上述代码对类改动太大,可以传比较器指定用年龄/名字/分数排序:

class Student {
    String name;
    int age;
    double score;

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

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

// 比较器
class AgeComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
}

class ScoreComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return (int) (o1.score - o2.score);
    }
}

class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.name.compareTo(o2.name);
    }
}

public static void main(String[] args) {
	Student[] students = new Student[3];
    students[0] = new Student("zhangsan", 38, 98.4);
    students[1] = new Student("lisi", 73, 42);
    students[2] = new Student("wangwu", 18, 10.5);

	// 以年龄排序
    AgeComparator ageComparator = new AgeComparator();
    Arrays.sort(students,ageComparator);
    System.out.println(Arrays.toString(students));
}

7、Clonable 接口和深拷贝

Java 中内置了一些很有用的接口, Clonable 就是其中之一.
Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 “拷贝”. 但是要想合法调用 clone 方法, 必须要先实现 Clonable 接口, 否则就会抛出CloneNotSupportedException 异常

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

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

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

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person();
        person1.age = 12;
        person1.name = "frost";
        Person person2 = (Person)person1.clone();
        System.out.println(person2);
    }
}

Cloneable 拷贝出的对象是一份 “浅拷贝”:

class Money {
    public double money = 19.9;
}

class Person implements Cloneable{
    public String name;
    public int age = 123;
    public Money m = new Money();

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

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

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person();
        person1.age = 12;
        person1.name = "frost";
        Person person2 = (Person)person1.clone();
       
        System.out.println(person1.m.money);
        System.out.println(person2.m.money);
        System.out.println("======================");
        person1.m.money = 29.1;
        System.out.println(person1.m.money);
        System.out.println(person2.m.money);
    }
}


深拷贝:

class Money implements Cloneable{
    public double money = 19.9;

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

class Person implements Cloneable{
    public String name;
    public int age = 123;
    public Money m = new Money();

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

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

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person();
        person1.age = 12;
        person1.name = "frost";
        Person person2 = (Person)person1.clone();
        
        System.out.println(person1.m.money);
        System.out.println(person2.m.money);
        System.out.println("======================");
        person1.m.money = 29.1;
        System.out.println(person1.m.money);
        System.out.println(person2.m.money);
    }
}

8、抽象类和接口的区别

核心区别:抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法.如之前写的 Animal 例子. 此处的 Animal 中包含一个 name 这样的属性, 这个属性在任何子类中都是存在的. 因此此处的 Animal 只能作为一个抽象类, 而不应该成为一个接口。

No区别抽象类(abstract)接口(interface)
1结构组成普通类 + 抽象方法抽象方法 + 全局常量
2权限各种权限public
3子类使用使用 extends 关键字继承抽象类使用 implements 关键字实现接口
4关系一个抽象类可以实现若干接口接口不能继承抽象类,但是接口可以使用 extends 关键字继承多个父接口
5子类限制一个子类只能继承一个抽象类一个子类可以实现多个接口

三、Object类

Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收。

1、获取对象信息

如果要打印对象中的内容,可以直接重写Object类中的toString()方法。

2、对象比较equals方法

在Java中,=/=进行比较时:
(1)如果=/=左右两侧是基本类型变量,比较的是变量中值是否相同
(2)如果==左右两侧是引用类型变量,比较的是引用变量地址是否相同
(3)如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认也是按照地址比较的:

Student类重写equals方法后,然后比较:

class Student {
    String name;
    int age;

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

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }
}

public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("frost", 10);
        Student student2 = new Student("frost", 10);

        System.out.println(student1.equals(student2));
    }
}


结论:比较对象中内容是否相同的时候,一定要重写equals方法。

3、hashcode方法

hashcode方法用来算对象在内存中存储的位置,与equals一样,我们认为两个名字相同,年龄相同的对象,将存储在同一个位置,如果不重写hashcode()方法,则

重写之后:

class Student {
    String name;
    int age;

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

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

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("frost", 10);
        Student student2 = new Student("frost", 10);

        System.out.println(student1.hashCode());
        System.out.println(student2.hashCode());
    }
}


结论:
(1)hashcode方法用来确定对象在内存中存储的位置是否相同
(2)事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。

4、接收引用数据类型

在之前已经分析了Object可以接收任意的对象,因为Object是所有类的父类,但是Obejct并不局限于此,它可以接收所有数据类型,包括:类、数组、接口。

范例:使用Object来接受数组对象

public static void main(String[] args) {
    // Object接收数组对象,向上转型
    Object obj = new int[]{1,2,3,4,5,6} ;
    // 向下转型,需要强转
    int[] data = (int[]) obj ;
    for(int i :data){
        System.out.println(i+"、");
    }
}

而Object可以接收接口是Java中的强制要求,因为接口本身不能继承任何类。

范例:使用Object接收接口对象

interface IMessage {
    public void getMessage() ;
}

class MessageImpl implements IMessage {
    @Override
    public String toString() {
        return "I am small biter" ;
    }
    
    public void getMessage() {
        System.out.println("比特欢迎你");
    }
}

public class Test {
    public static void main(String[] args) {
        IMessage msg = new MessageImpl() ; // 子类向父接口转型
        Object obj = msg ; // 接口向Obejct转型
        System.out.println(obj);
        IMessage temp = (IMessage) obj ; // 强制类型转换
        temp.getMessage();
    }
}

Object真正达到了参数的统一,如果一个类希望接收所有的数据类型,就是用Object完成,在Java中,泛型就是底层就是通过Object来实现的。

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值