抽象类和接口详解

一、抽象类

1.抽象类的概念

如果一个类不适合用来创建一个具体的对象,这个类就可以设置成抽象类

比如我们经常使用的Animal类,它不适合实例化一个对象,我们通常定义一个具体的类比如Dog类继承Animal类,再来实例化对象。

2.抽象类的语法

Java 中,一个类如果 被 abstract 修饰称为抽象类 ,抽象类中 被 abstract 修饰的方法称为抽象方法 ,抽象方法 不用给出具体的实现体
abstract class Animal{
    //抽象类也可以有构造方法
    public Animal(int age, String name) {
        this.age = age;
        this.name = name;
    }

    int age;
    String name;
    abstract void show();//抽象方法,可以没有方法体
}

注意:抽象类也是类,除了不能用来实例化一个对象、可以含有抽象方法,其余都和普通类一样

!!!虽然抽象类不能实例化一个对象,但是它可以被向上转型

public class Test{
    //向上转型
    public static void animalTest(Animal animal){
        animal.show();
    }
    public static void main(String []args){
        Animal animal=new Dog(1,"小狗");//向上转型
        Animal animal1=new Dog(1,"小猫");//向上转型
        animalTest(animal);
        animalTest(animal1);
    }
}
abstract class Animal{
    public Animal(int age, String name) {
        this.age = age;
        this.name = name;
    }

    int age;
    String name;
    abstract void show();
}
class Dog extends Animal{
    public Dog(int age, String name) {
        super(age, name);
    }

    void show(){
        System.out.println("汪汪叫");
    }
}
class Cat extends Animal{
    public Cat(int age, String name) {
        super(age, name);
    }

    void show(){
        System.out.println("喵喵叫");
    }
}

 

3.抽象类的特性

1.抽象类不能直接实例化对象

2.抽象方法不能被private修饰

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

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

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

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

4.抽象类的作用

抽象类本身不能被实例化 , 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法 ,也就是说抽象类就是用来被继承的。

二、接口

1.接口的概念

Java中的接口与我们生活中的接口有类似的地方,比如USB接口,每个人的电脑都有USB接口,不论是谁的接口都可以插上同一个U盘,Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型

2.接口的语法

接口的定义格式与定义类的格式基本相同,将 class 关键字换成 interface 关键字,就定义了一个接口。
public interface Ifly {
     public abstract void fly();
     void run();//每个方法默认由public abstract修饰,因此可以不写
    public static final int a=5;
    int b=10;//每个成员变量默认由public static final 修饰,也可以不写
}
注意:
1. 创建接口时 , 接口的命名一般 以大写字母 I 开头 .
2. 接口的命名 一般使用 "形容词" 词性的单词 .
3. 阿里编码规范中约定 , 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性

 

3.接口的使用

接口不能直接使用,必须要有一个 " 实现类 " " 实现 " 该接口, 实现接口中的所有抽象方法
接口的 使用方法
public class 类名称 implements 接口名称{
//......
}
//实现Iable接口
class Cat extends Animal implements Iable{
    public Cat(int age, String name) {
        super(age, name);
    }
    void show(){
        System.out.println("喵喵叫");
    }
    //实现接口的方法是,它的访问权限修饰符必须为public!!!
    public void able(){
        System.out.println("虽然是狗,但它有飞行能力");
    }
}
interface Iable{
    void able();
}

4.接口的特性

1. 接口类型是一种引用类型,但是 不能直接new接口的对象(即不能实例化)
public class TestUSB {
    public static void main(String[] args) {
        USB usb = new USB();
    }
}
// Error:(10, 19) java: day20210915.USB是抽象的; 无法实例化

2.接口中每一个方法都是public的抽象方法, 即接口中的方法会被隐式的指定为 public abstract使用其它修饰符会报错

public interface USB {
    // Error:(4, 18) java: 此处不允许使用修饰符private
    private void openDevice();

    void closeDevice();
}

3.接口中的方法是不能在接口中实现的(default,static修饰的方法除外),只能由实现接口的类来实现

public interface USB {
    void openDevice();
    // 编译失败:因为接口中的方式默认为抽象方法
// Error:(5, 23) java: 接口抽象方法不能带有主体
    void closeDevice(){
        System.out.println("关闭USB设备");
    }
    static void fly(){//static修饰的方法可以由有方法体
        System.out.println("起飞了");
    }
    default void run(){//default修饰的方法也可以有方法体
        System.out.println("跑起来");
    }
}

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

public interface USB {
 
    void openDevice(); // 默认是public的
    void closeDevice(); // 默认是public的
}
public class Mouse implements USB {
    @Override
    void openDevice() {
        System.out.println("打开鼠标");
    }
// ...
}
// 编译报错,重写USB中openDevice方法时,不能使用默认修饰符
// 正在尝试分配更低的访问权限; 以前为public

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

public interface USB {
    double brand = 3.0; // 默认被:final public static修饰
    void openDevice();
    void closeDevice();
}
public class TestUSB {
    public static void main(String[] args) {
        System.out.println(USB.brand); // 可以直接通过接口名访问,说明是静态的
// 编译报错:Error:(12, 12) java: 无法为最终变量brand分配值
        USB.brand = 2.0;// 说明brand具有final属性
    }
}

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

public interface USB {
    // 编译失败
    public USB(){
    }
    {} // 编译失败
    void openDevice();
    void closeDevice();
}
7. 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
8. 如果类没有实现接口中的所有的抽象方法,则类必须设置为 抽象类
9. jdk8 中:接口中还 可以包含default方法
10.虽然接口不可以一用来实例化一个对象,但是 接口可以被向上转型
public class Test{
    //接口的向上转型
    public static void testIable(Iable iable){
        iable.able();//注意:这里与普通多态一样,只能调用接口中有的方法
    }
    public static void main(String []args){
           Iable iable=new Dog(1,"小狗");
           Iable iable1=new Cat(1,"小猫");
           testIable(iable);
           testIable(iable1);
    }
}
class Animal{
    public Animal(int age, String name) {
        this.age = age;
        this.name = name;
    }

    int age;
    String name;
}
class Dog extends Animal implements Iable{
    public Dog(int age, String name) {
        super(age, name);
    }
    public void able(){//重写接口的方法
        System.out.println("虽然是猫,但是它会狗叫");
    }
}
class Cat extends Animal implements Iable{
    public Cat(int age, String name) {
        super(age, name);
    }
    public void able(){//重写接口的方法
        System.out.println("虽然是狗,但它有飞行能力");
    }
}
interface Iable{
    void able();
}
//运行结果
//虽然是猫,但是它会狗叫 
//虽然是狗,但它有飞行能力

5.实现多个接口

Java 中,类和类之间是单继承的,一个类只能有一个父类,即 Java 中不支持多继承 ,但是 一个类可以实现多个接口
我们先创建一个动物类:
class Animal{
    public Animal(int age, String name) {
        this.age = age;
        this.name = name;
    }

    int age;
    String name;
}

再创建一些接口,分别表示“会飞的”、“会跑的”、“会游泳的”。

interface IRunning{
    void running();
}
interface ISwimming{
    void swimming();
}
interface IFlying{
    void fly();
}

再创建鸟类和狗类继承Animal并实现相应能力的接口:

class Dog extends Animal implements IRunning,ISwimming{
    @Override
    public void running() {
        System.out.println(this.name+"会跑");
    }

    @Override
    public void swimming() {
        System.out.println(this.name+"会游泳");
    }

    public Dog(int age, String name) {
        super(age, name);
    }
    public void able(){
        System.out.println("虽然是猫,但是它会狗叫");
    }
}
class Brid extends Animal implements IFlying{
    public void fly() {
        System.out.println(this.name+"会飞");
    }
    
    public Brid(int age, String name) {
        super(age, name);
    }
    public void able(){
        System.out.println("鸟会飞");
    }
}

最后,在main方法里进行使用

 public static void run(IRunning iRunning){
        iRunning.running();
    }
    public static void swim(ISwimming iSwimming){
        iSwimming.swimming();
    }
    public static void fly(IFlying iFlying){
        iFlying.fly();
    }
    public static void main(String []args){
         IRunning iRunning=new Dog(1,"狗");
         iRunning.running();
         ISwimming iSwimming=new Dog(1,"狗");
         iSwimming.swimming();
         IFlying iFlying=new Brid(2,"鸟");
         iFlying.fly();
    }

6.接口间的继承

Java 中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。
接口可以继承一个接口 , 达到复用的效果 . 使用 extends 关键字 .
interface IRunning1{
    void running();
}
interface  ISwimming1{
    void swimming();
}
//接口之间的继承
interface IAble extends IRunning1,ISwimming1{
    void able();
}
//那么以后在类中实现Iable这个接口时,就必须重写running,swimming,able三个方法

7.接口使用实例

8.Clonable接口和深拷贝

Java 中内置了一些很有用的接口 , Clonable 就是其中之一 .
Object 类中存在一个 clone 方法 , 调用这个方法可以创建一个对象的 " 拷贝 ". 但是要想合法调用 clone 方法 , 必须要 先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常 .
class Animal implements Cloneable {
    private String name;
    @Override
    public Animal clone() {
        Animal o = null;
        try {
            o = (Animal)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return o;
    }
}
public class Test {
    public static void main(String[] args) {
        Animal animal = new Animal();
        Animal animal2 = animal.clone();
        System.out.println(animal == animal2);
    }
}
// 输出结果
// false

 

浅拷贝 VS 深拷贝
Cloneable 拷贝出的对象是一份 " 浅拷贝"
观察以下代码 :
class Money {
    public double m = 99.99;
}
class Person implements Cloneable{
    public Money money = new Money();
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class TestDemo3 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person();
        Person person2 = (Person) person.clone();
        System.out.println("通过person2修改前的结果");
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
        person2.money.m = 13.6;
        System.out.println("通过person2修改后的结果");
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
    }
}
// 执行结果
通过person2修改前的结果
        99.99
        99.99
        通过person2修改后的结果
        13.6
        13.6
如上代码,我们可以看到,通过 clone ,我们只是拷贝了 Person 对象。但是 Person 对象中的 Money 对象,并没有拷贝。通过person2 这个引用修改了 m 的值后, person1 这个引用访问 m 的时候,值也发生了改变。这里就是发生了浅拷贝。

9.抽象类和接口的区别

核心区别 : 抽象类中可以包含普通方法和普通字段 , 这样的普通方法和字段可以被子类直接使用 ( 不必重写 ), 而 接口中不能包含普通方法, 子类必须重写所有的抽象方法

 

10.Object类

Object Java 默认提供的一个类。 Java里面除了Object类,所有的类都是存在继承关系的 默认会继承Object父类 。即 所有类的对象都可以使用Object的引用进行接收
10.1.获取对象信息
如果 要打印对象中的内容,可以直接重写Object类中的toString()方法 ,之前已经讲过了,此处不再累赘。 (如果不清楚,请看博客《类和对象详解》的对象的打印)
 // Object类中的toString()方法实现:
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
10.2.比较equals方法
Java 中, == 进行比较时:
a. 如果 == 左右两侧是 基本类型变量 比较的是变量中值是否相同
b. 如果 == 左右两侧是 引用类型变量 比较的是引用变量地址是否相同
c. 如果 要比较对象中内容 必须重写Object中的equals方法 ,因为 equals 方法默认也是 按照地址 比较的:
public class Test{
    public static void main(String []args){
       Animal animal1=new Animal(1,"动物");
       Animal animal2=new Animal(1,"动物");
       System.out.println(animal2==animal1);
       System.out.println(animal2.equals(animal1));
    }
}
class Animal{
    public Animal(int age, String name) {
        this.age = age;
        this.name = name;
    }

    int age;
    String name;
}
//打印结果
//false
//false

我们看看equals方法的实现

 
 

可以看到,equals方法依然比较的是地址,因此我们需要对equals方法进行重写

public class Test{
    public static void main(String []args){
       Animal animal1=new Animal(1,"动物");
       Animal animal2=new Animal(1,"动物");
       System.out.println(animal2==animal1);
       System.out.println(animal2.equals(animal1));
    }
}
class Animal{
    public Animal(int age, String name) {
        this.age = age;
        this.name = name;
    }
    //对equals方法进行重写
    public boolean equals(Object obj) {
        Animal tmp=(Animal)obj;
        return tmp.name.equals(this.name) && tmp.age==this.age;
    }
    int age;
    String name;
}
//打印结果
//false
//true

10.3.hashCode方法
我们认为两个名字相同,年龄相同的对象,将存储在同一个位置,如果不重写 hashcode() 方法,我们可以来看示例代码:
class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class TestDemo4 {
    public static void main(String[] args) {
        Person per1 = new Person("gaobo", 20) ;
        Person per2 = new Person("gaobo", 20) ;
        System.out.println(per1.hashCode());
        System.out.println(per2.hashCode());
    }
}
//执行结果
460141958 
1163157884
注意事项:两个对象的 hash 值不一样。
像重写 equals 方法一样,我们也可以重写 hashcode() 方法。此时我们再来看看。
class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
public class TestDemo4 {
    public static void main(String[] args) {
        Person per1 = new Person("gaobo", 20) ;
        Person per2 = new Person("gaobo", 20) ;
        System.out.println(per1.hashCode());
        System.out.println(per2.hashCode());
    }
}
//执行结果
460141958
460141958
注意事项:哈希值一样。
结论:
1 hashcode 方法用来确定对象在内存中存储的位置是否相同
2 、事实上 hashCode() 在散列表中才有用,在其它情况下没用。在散列表中 hashCode() 的作用是获取对象的
散列码,进而确定该对象在散列表中的位置。

三、内部类

当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么这个内部的完整结构最好使用内部类。在 Java 中, 可以将一个类定义在另一个类或者一个方法的内部, 前者称为内部类,后者称为外部类 。内部类也是封装的一种体现。
public class OutClass {
  class InnerClass{
  }
}
// OutClass是外部类
// InnerClass是内部类

注意:

1. 定义在 class 类名 {} 花括号外部的,即使是在一个文件里,都不能称为内部类
2. 内部类和外部类共用同一个java源文件 ,但是经过编译之后, 内部类会形成单独的字节码文件

内部类的分类

public class OutClass {
    // 成员位置定义:未被static修饰 --->实例内部类
    public class InnerClass1{
    }
    // 成员位置定义:被static修饰 ---> 静态内部类
    static class InnerClass2{
    }
    public void method(){
// 方法中也可以定义内部类 ---> 局部内部类:几乎不用
        class InnerClass5{
        }
    }
}

1.静态内部类

static修饰的内部成员类称为静态内部类。

public class OutClass {
    private int a;
    static int b;

    public void methodA() {
        a = 10;
        System.out.println(a);
    }

    public static void methodB() {
        System.out.println(b);
    }

    // 静态内部类:被static修饰的成员内部类
    static class InnerClass {
        public void methodInner() {
// 在内部类中只能访问外部类的静态成员
// a = 100; // 编译失败,因为a不是类成员变量
            b = 200;
// methodA(); // 编译失败,因为methodB()不是类成员方法
            methodB();
        }
    }

    public static void main(String[] args) {
// 静态内部类对象创建 & 成员访问
        OutClass.InnerClass innerClass = new OutClass.InnerClass();
        innerClass.methodInner();
    }
}
注意事项
1. 在静态内部类中只能访问外部类中的静态成员
如果确实想访问,我们该如何做?
2. 创建静态内部类对象时, 不需要 先创建外部类对象

2.实例内部类

未被static修饰的成员内部类
public class OutClass {
    private int a;
    static int b;
    int c;

    public void methodA() {
        a = 10;
        System.out.println(a);
    }

    public static void methodB() {
        System.out.println(b);
    }

    // 实例内部类:未被static修饰
    class InnerClass {
        int c;

        public void methodInner() {
// 在实例内部类中可以直接访问外部类中:任意访问限定符修饰的成员
            a = 100;
            b = 200;
            methodA();
            methodB();
// 如果外部类和实例内部类中具有相同名称成员时,优先访问的是内部类自己的
            c = 300;
            System.out.println(c);
// 如果要访问外部类同名成员时候,必须:外部类名称.this.同名成员名字
            OutClass.this.c = 400;
            System.out.println(OutClass.this.c);
        }
    }

    public static void main(String[] args) {
// 外部类:对象创建 以及 成员访问
        OutClass outClass = new OutClass();
        System.out.println(outClass.a);
        System.out.println(OutClass.b);
        System.out.println(outClass.c);
        outClass.methodA();
        outClass.methodB();
        System.out.println("=============实例内部类的访问=============");
// 要访问实例内部类中成员,必须要创建实例内部类的对象
// 而普通内部类定义与外部类成员定义位置相同,因此创建实例内部类对象时必须借助外部类
// 创建实例内部类对象
        OutClass.InnerClass innerClass1 = new OutClass().new InnerClass();
// 上述语法比较怪异,也可以先将外部类对象先创建出来,然后再创建实例内部类对象
        OutClass.InnerClass innerClass2 = outClass.new InnerClass();
        innerClass2.methodInner();
    }
}
注意事项
1. 外部类中的任何成员都可以在实例内部类方法中直接访问
2. 实例内部类所处的位置与外部类成员位置相同,因此也 受public、private等访问限定符的约束
3. 在实例内部类方法中访问同名的成员时, 优先访问自己的 ,如果要 访问外部类同名的成员,必须: 外部类名称.this.同名成员 来访问
4. 实例内部类对象 必须在先有外部类对象前提下才能创建
5. 实例内部类的非静态方法中包含了一个指向外部类对象的引用
6. 外部类中,不能直接访问实例内部类中的成员,如果要访问必须 先要创建内部类的对象

3.匿名内部类

//普通类的匿名内部类
class Out{
    public void test1(){
        System.out.println("haha");
    }
}
public class Test{
    public class void main(String[]args){
        new Out(){
        //也可以对test1重写
        }.test1();
    }
}
//通过接口实现内部类
interface  IA{
    void test1();
}
public class Test{
    public static void main(String[]args){
        //这个类实现了IA接口,并且重写了test1方法
        IA a=new IA(){
            public void test1(){
                System.out.println("haha");
            }
        };
        a.test1();
    }
}

4.局部内部类

定义在外部类的方法体或者 {} 中,该种内部类只能在其定义的位置使用,一般使用的非常少,此处简单了解下语法格式
public class OutClass {
    int a = 10;

    public void method() {
        int b = 10;
// 局部内部类:定义在方法体内部
// 不能被public、static等访问限定符修饰
        class InnerClass {
            public void methodInnerClass() {
                System.out.println(a);
                System.out.println(b);
            }
        }
// 只能在该方法体内部使用,其他位置都不能用
        InnerClass innerClass = new InnerClass();
        innerClass.methodInnerClass();
    }

    public static void main(String[] args) {
// OutClass.InnerClass innerClass = null; 编译失败
    }
}
注意事项
1. 局部内部类只能在所定义的方法体内部使用
2. 不能被 public static 等修饰符修饰
3. 编译器也有自己独立的字节码文件,命名格式:外部类名字 $ 数字内部类名字 .class
4. 几乎不会使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值