抽象类、接口、枚举(Java)

1.抽象类

(1).简介

父类可以封装不同子类的共同特征或者共同行为.而有的时候,父类中封装的方法无法具体完成子类中需要的逻辑,因此我们可以将此方法设计成抽象方法,即使用关键字abstract进行修饰。而有抽象方法的类,也必须使用abstract关键字进行修饰,因此我们称之为抽象类

(2)抽象类的特点  

1)由abstract修饰的方法为抽象方法,抽象方法没有方法体,即没有{ },需要使用分号结尾

public abstract void noise();

2)若类中包含抽象方法,那么该类必须使用关键字abstract声明成抽象类。final不能修饰抽象类,因为抽象类就是用来被继承的,而final修饰的类是不能被继承的。

public abstract class Animal {
    private String name;
    private int age;
    private String color;

    // 抽象方法
    public abstract void noise();
}

3)抽象类里,可以没有抽象方法

public abstract class Animal {
    private String name;
    private int age;
    private String color;

   
    // 抽象方法
    public abstract void noise();
    
    public  void  zouLu(){
        System.out.println("正在走路");
    }
}

4)抽象类里可以提供构造器,但是不能实例化,没有意义。因为不能使用new关键字调用构造器

public abstract class Animal {
    private String name;
    private int age;
    private String color;

    public Animal() {}

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

    public static void main(String[] args) {
        //抽象类不能实例化,因此不能使用new关键字调用构造器,虽然可以提供构造器
        //Animal animal = new Animal() ;//会报错
    }
   
}

5)一个类继承了抽象类,那么必须重写里面的所有抽象方法,除非该子类也声明为抽象类。

 Dog继承了Animal类,不重写父类的抽象方法noise(),就会出现如下错误

 第一种解决方法重写父类的抽象方法noise()

class Dog extends Animal{

    public Dog() {
    }
    
    public Dog(String name, int age, String color) {
        super(name, age, color);
    }
   //子类自己的方法
    public void lookHouse(){
        System.out.println("~~看家~~");
    }
   // 重写父类里的抽象方法
    public void noise(){
        System.out.println("~~汪汪汪~~");
    }
}

 第二种是:把子类也声明成抽象类

abstract class  Dog extends Animal{

   
}

 6)抽象类里可以没有抽象方法

 (3)抽象类的意义

1)为其子类提供一个公共的父类型

2) 封装子类中重复的内容,如成员变量和方法

3) 定义抽象方法,子类虽然有不同的实现逻辑,但该方法的定义却是一致的。

2.接口 

 (1)接口的特点

 有的时候,我们需要从几个不相关的类中派生出一个子类,继承他们的所有成员变量和方法,但是java不支持多继承。此时,我们可以使用接口,来达到多继承的效果

java没有多继承的语法,而有些时候需要使用这种形式,  比如一个类想要拥有两个类的属性或者方法时,可以使用另一个知识点来达到这种目的,就是接口

接口也可以理解为是特殊的抽象类, 也可以理解为是一种规范。

 1.接口的关键字:interface

2. 接口里可以提供成员属性默认使用public static final修饰的,即常量

public interface IntefaceA {
    double PI = 3.14;
    public static final double NUM = 0.618;
}

3. 接口里不能提供构造器,更不能使用new实例化,没有意义。

4. 接口里可以提供成员方法,默认使用public abstract修饰,(抽象方法),所以可以省略不写

public interface IntefaceA {

    void showInfo();
    public abstract int sum(int a, int b);

}

 (2)实现接口

1) 使用关键字implements进行实现,必须实现接口中的所有抽象方法


 class ClassB implements IntefaceA {
    @Override
    public void showInfo() {}

     @Override
     public int sum(int a, int b) {
         return 0;
     }
 }

2)若一个类中没有全部实现接口中的抽象方法,那么该类需要使用abstract声明成抽象类

abstract class ClassB implements IntefaceA {
    public void showInfo() {}
    
}

 3)  1.与继承不同,一个类可以实现多个接口。接口间使用逗号分开。并且子类里要重写两个接口里的抽象方法。

public interface InterA {
    void showInfo();
}
interface InterB{
    void showInfo();
    int  calculate(int a, int b);
}

/**
 * 第一种情况:一个子类可以实现多个接口,从而达到多继承的效果。接口之间使用逗号隔开。
 */
class ClassA implements InterA,InterB{

    @Override
    public void showInfo() {
        System.out.println("--ClassA--");
    }

    @Override
    public int calculate(int a, int b) {
        return a + b;
    }
}

实现多接口后,方法的调用:

public class InterATest {
    public static void main(String[] args) {
        //因为ClassA是InterA和InterB的子类型,因此可以向上造型。
        InterA  x = new ClassA();
        x.showInfo();
        // 此时,想要使用x指向的对象,调用其里面的计算功能。编译时看变量类型,x里没有计算功能。
        //所以针对于这道题来说: 可以向下转型成ClassA,也可以强制转换成InterB
        if(x instanceof ClassA){
            ClassA c = (ClassA)x;
            int result = c.calculate(3,5);
            System.out.println("result="+result);
        }
        //第二种方式:
        if(x instanceof InterB){
            InterB b = (InterB)x;
            int result = b.calculate(3,5);
            System.out.println("result="+result);
        }
    }
}

       2.一个类可以在继承一个父类的情况下,同时实现多个接口。也可以达到多继承的效果。

 父类中有抽象方法,子类必须重写该方法,同时也还要重写实现的接口里的抽象方法。

interface InterM{
    void showInfo();
}
interface InterN{
    void sum(int a, int b);
}
class ClassX{

    public  abstract  void  sub(int a,int b);
    public void run(){
        System.out.println("--跑步中--");
    }
}
//设计一个子类型,同时具备上面三个引用类型的功能
// 第二种方式:一个类可以在继承一个父类的情况下,同时实现多个接口。也可以达到多继承的效果。
class ClassY extends ClassX implements InterM,InterN{

    @Override
    public void showInfo() {
        System.out.println("--ClassY--");
    }

    @Override
    public void sum(int a, int b) {
        System.out.println("两个数的和:"+(a+b));
    }

    @Override
    public void sub(int a, int b) {
        System.out.println("两个数的差:"+(a-b));
    }

}

 进行方法调用测试:

public class Program {
    public static void main(String[] args) {
        //因为ClassY,同时继承和实现了ClassX,InterM,InterN. 因此可以理解为有多个父亲。
        ClassY y = new ClassY();
        y.run();
        y.showInfo();
        y.sum(10,20);
        //向上造型成不同的父亲
        ClassX x = y; //只能调用父类里的方法
        x.run();
        InterM m = y;//只能调用接口里的抽象方法
        m.showInfo();
        InterN n = y;
        n.sum(100,200);

    }
}

(3)接口间的继承

1. 接口可以继承多个接口,使用extends关键字  ,

2. 子接口拥有了父接口里的所有抽象方法,子接口不用重写父接口里的抽象方法方法。

3.如果继承多个接口,且父类接口里有重名的带有方法体的方法,子接口必须也写一个相同的方法。

4. 子接口可以提供自己独有的抽象方法   

5. 类实现子接口时,要重写里面所有的抽象方法。

public interface InterfaceB {
    void methodB();
}
interface InterfaceC{
    void methodC();
}
interface InterfaceD extends InterfaceC,InterfaceB{//继承InterfaceB、InterfaceC,所以继承两个接口里的所有抽象方法,并且提供了自己的抽象方法。
    void methodD();
}
class classT implements InterfaceD{   //实现InterfaceD接口,要重写InterfaceD接口继承的所有抽象方法,也要重写他自己的抽象方法。

    @Override
    public void methodD() {

    }

    @Override
    public void methodB() {

    }

    @Override
    public void methodC() {

    }
}

 (4)接口在1.8之后的新特性

 1. 提供了默认方法:使用default修饰词修饰的具有方法体的方法。

                 --1)该方法,默认使用public修饰,

                 --2)该方法,逻辑不能满足子类时,子类可以重写

2. 提供了静态方法:使用static修饰的具有方法体的方法

                 --1)该方法,默认使用public修饰

                --2)不能再子类中重写。但是如果子类中有一样的方法,那只能是子类自己独有的静态方法,不是重写的。

public interface InterfaceM {
     default void print(){
         System.out.println("--欢迎来到中国,我的家--");
     }
     static void print2(){
         System.out.println("--地球只有一个,人人有责---");
     }
}
class classU implements InterfaceM{
    //重写接口里的默认方法
    @Override
    public void print(){
        System.out.println("--欢迎来到长春,我的家--");
    }

    //@Override   添加注解报错,因此print2方法不能重写,此时是自己独有的静态方法。
    static void print2(){
        System.out.println("--地球只有一个,人人有责---");
    }
}

 (5)常用接口

        1.Serializable序列化接口

 系统类库提供好的一个接口。当涉及到数据传输时,比如将内存的对象保存到磁盘,磁盘上的文件变成内存中的对象,或者对象在两台电脑之间传输。那么该对象的类必须实现序列化接口。否则传输失败。

源码:

public interface Serializable {
   
}

 比如String类就是实现了Serializable序列化接口

String类

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {


        

}

 2)Comparable接口

当一个类的多个对象之间想要进行比较时,比如排序等操作,那么类必须实现该接口,然后自定义比较规则。否则不能比较,会报如下错误:

Exception in thread "main" java.lang.ClassCastException: xxx.类型名 cannot be cast to java.lang.Comparable

源码如下:

public interface Comparable<T> {
   public int compareTo(T o);
}

 实现Comparable接口,然后自定义比较规则,以Person类为例

如果想要进行比较,那么除了要实现comparable接口,还要实现里面的比较方法compareTo

升序: this的相关属性-传入的o的相关属性

降序: 传入的o的相关属性-this的相关属性

public class Person  implements Comparable<Person>{
    private String name;
    private int age;
    private int height;
    private int weight;
    public Person(String name, int age, int height, int weight) {
        this.name = name;
        this.age = age;
        this.height = height;
        this.weight = weight;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

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

    /**
     * 如果想要进行比较,那么除了要实现comparable接口,还要实现里面的比较方法compareTo
     * @param o the object to be compared.
     * @return
     *  升序: this的相关属性-传入的o的相关属性
     *  降序: 传入的o的相关属性-this的相关属性
     */
    @Override
    public int compareTo(Person o) {
        //按照年龄比较,升序:   返回负数,证明this小,返回0,证明一样,返回正数,证明this大
        //return this.age - o.age;
        //按照身高比较,降序:
        //return o.height - this.height;
        //先按照年龄升序,如果年龄相同,按照身高降序
        int r = this.age - o.age;
        if(r == 0){
            r = o.height - this.height;
        }
        return r;
    }
}

3)Comparator接口

 用于在compareble的基础上去修改比较规则。

        现在想要修改比较规则,按照体重进行升序。不能修改源代码,因为Person类可能有人已经使用了。并不是自己一个人用。
        此时就可以使用Comparator比较器进行重新自定义比较规则
        使用匿名内部类创建一个比较器对象

public class PersonTest {
    public static void main(String[] args) {
        Person[] ps = new Person[3];
        ps[0] = new Person("小明",19,176,70);
        ps[1] = new Person("小黑",18,175,65);
        ps[2] = new Person("小张",19,177,64);

        //现在想要修改比较规则,按照体重进行升序。不能修改源代码,因为Person类可能有人已经使用了。并不是自己一个人用。
        //此时就可以使用Comparator比较器进行重新自定义比较规则
        //使用匿名内部类创建一个比较器对象
        Comparator c1 = new Comparator<Person>(){
            public int compare(Person p1, Person p2) {
                return p1.getWeight() - p2.getWeight();
            }
        };
        //数组工具类sort方法,重载了很多个方法,包含一个sort(Object[] a,Comparator c);
        Arrays.sort(ps,c1);
        System.out.println(Arrays.toString(ps));

    }
}

3.枚举

 (1)简介

在Java中,枚举是一种特殊的引用数据类型,是一个被命名的整型常数的集合,用于声明一组带标识符的常数,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。

背景:枚举是在JDK1.5以后引入的。

主要用途是:将一组常量,也可以说成是一组离散值组织起来。

 (2)枚举的定义            

1)自定义类实现枚举

  • 类内部创建一组对象,通常使用public static final关键字共同修饰,对外进行暴露

  • 枚举对象名通常全部都会大写,这是常量的命名规范

  • 可以提供属性,属性应使用private final共同修饰

  • 将构造器私有化

  • 属性,可以提供getXXX方法,但是不需要提供setXxx方法,属性应该是只读的。

public class Season {

    //可以提供属性,用来描述对象信息, 使用private final修饰
    private final String name;  //对象的名字
    private final String desc;  //对象的描述

    //构造器私有化 ,使外界不能使用关键字new对象
    private Season(String name,String desc){
        this.name = name;
        this.desc = desc;
    }

    //提供一堆可以向外界暴露的该类型的对象
    public static final Season SPRING = new Season("春天","春暖花开");
    public static final Season SUMMER = new Season("夏天","烈日炎炎");
    public static final Season AUTUMN = new Season("秋天","落叶归根");
    public static final Season WINTER = new Season("冬天","白雪皑皑");

    //给属性提供getXXX方法,向外界暴露,不要提供setXXX方法,因为只读。
    public String getName(){
        return name;
    }
    public String getDesc(){
        return desc;
    }
    @Override
    public String toString(){
        return name+","+desc;
    }
}

   测试:

public class SeasonTest {
    public static void main(String[] args) {
        Season s = Season.AUTUMN;
        //直接输出。 如果没有重写toString()方法打印的是对象的地址 ,提供toString()方法打印的是里面的内容。
        System.out.println(s);
        //打印这个季节的名字
        System.out.println(s.getName());

        //获取这个季节的描述信息
        System.out.println(s.getDesc());

    }
}

2)enum关键字实现枚举 

  • 使用enum关键字定义一个枚举,默认会继承java.lang.Enum类,而且是一个final类,因此不能再继承其他类

  • 必须在枚举类的第一行声明枚举类对象。有多个枚举对象时,使用逗号隔开,最后一个用分号结尾,没有枚举对象也必须添加一个分号才不会报错。

  • 可以提供私有的属性

  • 可以提供构造器,必须是私有的,如果构造器有形参,定义对象时必须显式调用构造器

public enum Season {
    //枚举类里的第一行,必须是枚举的对象,提供了构造器,枚举对象必须显式调用构造器
    SPRING("春天","春暖花开"),SUMMER("夏天","烈日炎炎"),
    AOTUMN("秋天","落叶归根"),WINTER("冬天","白雪皑皑");

    //提供属性,必须私有化。
    private String name;
    private String desc;
    //提供了构造器,构造器必须私有化。
    private Season(String name,String desc){
        this.name = name;
        this.desc = desc;
    }
    //提供get方法
    public String getName(){
        return name;
    }
    public String getDesc(){
        return desc;
    }
  

    public static void main(String[] args) {
        Season season = Season.SUMMER;
        System.out.println(season);
        System.out.println(season.name);
        System.out.println(season.desc);
    }
}

  • 如果使用无参构造器创建枚举对象,则定义对象时,小括号可以省略,调用时,直接使用

                类名.枚举类的对象

public enum Week {
    //第一行,必须是枚举类的对象.  名称自定义,应该符合常量的命名规则。
    //MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
    周一,周二,周三,周四,周五,周六,周日;
    //内部系统提供了一个无参构造器,因为创建枚举对象时,调用的是无参构造器(构造器是私有的。),因此对象后面的小括号是可以省略的。
    
    public static void main(String[] args) {
        System.out.println(Week.周五);  //直接用类名.调用
    }
}

        重写方法形式

  • 1.使用enum关键字后,就不能再继承其它类,因为enum会隐式继承Enum,而Java是单继承机制

  • 2.枚举类和普通类一样,可以实现接口,必须重写接口里的抽象方法。可以在枚举对象里重写

interface InterA{
    void showInfo();
}

public enum Direction implements InterA {

    BEFORE("前"){
        @Override
        public void showInfo() {
            System.out.println("向前进,如箭离弦,永不回头");
        }
    },
    AFTER("后"){
        @Override
        public void showInfo() {
            System.out.println("向后退,悬崖勒马");
        }
    },
    LEFT("左"){
        @Override
        public void showInfo() {
            System.out.println("向左看齐");
        }
    },
    RIGHT("右"){
        @Override
        public void showInfo() {
            System.out.println("向右看齐");
        }
    }
    ;

    private String name;
    private Direction(String name){
        this.name = name;
    }

    @Override
    public void showInfo() {
        System.out.println("--方向--");
    }

    public static void main(String[] args) {
        Direction direction = Direction.BEFORE;
        System.out.println(direction);//BEFORE
        direction.showInfo();//输出语句
    }

}

3)enum的常用方法

toString得到当前枚举常量的名称。子类可以通过重写这个方法来使结果更易读
name返回当前对象名(常量名),子类中不能重写
ordinal返回当前对象的位置号(编号),默认从0开始
values返回当前枚举类中所有的常量
valueOf将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常!
compareTo比较两个枚举常量,比较的就是位置号(编号)

代码演示:

public static void main(String[] args) {
        Season season = Season.SUMMER;
        System.out.println(season);
        System.out.println(season.name);
        System.out.println(season.desc);

        //enum的常用方法
        Season season1 = Season.AOTUMN;
        //输出枚举对象的名字
        System.out.println(season1.name());//AOTUMN

        2.ordinal()输出的是该枚举对象的次序(编号),从0开始编号
        System.out.println(season1.ordinal());//2

        //从反编译可以看出values方法,返回的是Season1[],该数组含有定义的所有枚举对象
        Season[] values = Season.values();//使用类名调用values()方法可以获取所有的枚举对象
        //遍历取出枚举对象
        for (Season season2:values){
            System.out.println(season2);
        }

        //4.valueOf:将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常
        //执行逻辑:根据输入的常量到Season2的枚举对象去查找,如果找到了就返回,如果没有找到就报错
        Season aotumn1 = Season.AOTUMN;
        Season aotumn = Season.valueOf("AOTUMN");//AOTUMN,找到就返回
        System.out.println(aotumn);
        System.out.println(aotumn1==aotumn);//true 地址相同

        //compareTo:比较两个枚举常量,比较的就是编号
        /**
         * 源码:public final int compareTo(E o) {
         *                 return self.ordinal - other.ordinal;
         *              }
         */
        System.out.println(Season.SPRING.compareTo(Season.SUMMER));//-1

    }

4.内部类

定义在一个类内部的类,就是内部类。内部类可以分为:成员内部类、静态内部类、局部内部类、匿名内部类。

1)成员,静态,局部内部类(了解)

1.成员内部类

定义在一个类的内部,与这个类的成员(属性、方法)平级,并且没有用static修饰的类。
1、访问权限可以是任意的权限,类似于一个类中的成员。
2、实例化的过程,需要先实例化外部类对象,再使用外部类对象进行内部类的实例化

外部类名    外部变量名 =  new   外部类名()

内部类名    内部变量名  =  外部变量名.new    内部类名()
3、内部类编译后,也会生成.class字节码文件。格式:外部类$内部类 .class

4.可以访问外部类的成员: 外部类名.this.成员

public class Outer {
     private String name;
     private int age;
    public Outer(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void showInfo(){
        System.out.println(name+","+age);
    }
    //定义一个成员内部类
    class Inner{
        private String name;
        private int age;
        public Inner(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public void showInfo(){
            System.out.println(name+","+age);
            //访问外部类的成员: 外部类名.this.成员
            System.out.println(Outer.this.name+","+Outer.this.age);
        }
    }

    public static void main(String[] args) {
        //先创建一个外部类对象
        Outer outer = new Outer("妈妈",29);
        //然后通过外部类对象,来实例化一个内部类对象
        Inner inner = outer.new Inner("儿子", 1);
        inner.showInfo();
    }
}

2)静态内部类

定义在一个类的内部,与这个类的成员(属性、方法)平级,并且使用static修饰的类。
1、访问权限可以是任意的权限,类似于一个类中的成员。
2、实例化的过程中,直接使用 new实例化一个外部类 .内部类对象即可。

内部类名   变量  =   new 外部类名.内部类构造器

3、内部类编译后,也会生成.class字节码文件。格式:外部类$内部类 .class

4.不能直接访问外部类的成员

public class Outer {
    private String name;
    public Outer(String name) {
        this.name = name;
    }
    public void showInfo(){
        System.out.println(name);
    }
    static class Inner{
        private String name;
        public Inner(String name) {
            this.name = name;
        }
        public void showInfo(){
            System.out.println(name);
            //不能直接访问外部类的成员
            //System.out.println(Outer.this.name);
        }
    }

    public static void main(String[] args) {
        //创建内部类的对象:  new 外部类名.内部类构造器
        Inner i = new Outer.Inner("儿子");
        i.showInfo();
    }
}

3)局部内部类

定义在某一个代码段中的中。
1、没有访问权限修饰符。
2、在当前方法中,直接实例化即可,直接使用关键字new对象(只能在当前方法中

3.在方法中定义的内部类,和局部变量的用法一样。出了作用域就失效了。

4、内部类编译后,也会生成.class字节码文件。格式:外部类$序号内部类 .class

public class Outer {
    public static void main(String[] args) {
        int a = 10;
        System.out.println(a);

        class Inner{
            private String name;
            public Inner(String name) {
                this.name = name;
            }
            public String getName() {
                return name;
            }
        }

        Inner inner = new Inner("小明");
        System.out.println(inner.getName());

    }

}

 4)匿名内部类  (重点)

匿名内部类: 就是没有名字的子类型对象

                 接口名|抽象类名|父类名 变量 = new 接口名|抽象类名|父类名(){

                 * 方法的重写 *       }; (分号不能丢)

        注意:要重写接口或者抽象类里面的抽象方法

public class Outer {
    public static void main(String[] args) {
         A a = new A(){
             //匿名内部类,提供成员属性
             private String name = "--你好,欢迎来到中国--";
             //重写接口A里的抽象方法
             public void showInfo(){
                 System.out.println(name);
             }
             //子类提供了独有的getXXX方法
             public String getName(){
                 return name;
             }
         };
         // 编译期间,看变量类型,因此能调用到showInfo. 运行期间看对象类型
         a.showInfo();
         //a.getName();  //编译期间,a里根本没有getName方法,编译都不会通过的
    }
}
interface A{
    public void showInfo();
}

在匿名内部类中,一般情况下不去添加新的成员(属性、方法),因为即便进行了添加,得到的对象也是向 上转型后的对象,不能访问子类中的成员。在匿名内部类中,一般是用来做方法的重写实现的。
匿名内部类也会生成 .class字节码文件,命名格式 : 外部类$序号 .class

匿名内部类可以访问外部类的成员,但需要将外部类成员声明为final。

如下书写是正确的 ,输出test

interface TestA {
    String toString();
}
public  class Test {
    public static void main(String[] args) {
        System.out.println(new TestA() {
            public String toString() {
                return "test";
            }
        });
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值