抽象类,接口,枚举和内部类

一.抽象类

1.1简介

由于继承这个方法的存在,我们可以把子类设置的更加具体,父类设置的更加一般化。父类可以封装不同的子类的共同特征和共同行为。在某些情况下,父类中封装的方法无法满足子类的需求,因此我们可以把父类中的方法设置为抽象方法,使用abstract关键字进行修饰,而写了抽象方法的类,也必须设置为抽象类。

1.2抽象类的特点

1.abstract修饰的方法是抽象方法,抽象方法没有方法体,需要使用分号结尾;

2.如果类方法中有抽象方法,则必须把类也设置为抽象类,final不能修饰抽象类,因为final修饰词就不能有子类了,而抽象的意义就是为了让子类能更好的继承方法

3.抽象类中,可以没有抽象方法;

4.抽象类中,可以提供构造器,但是没有意义,因为构造器是给子类使用的,不能实例化对象;

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

我们可以看一个实例

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;
    }

在上述代码中,我们定义了一个抽象类Animal,并提供了私有化的静态变量name,age,color,并提供了一个全参构造器,一个无参构造器

public abstract void noise();

    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 String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

我们在抽象父类中定义了一个抽象的公有方法noise,并提供了父类各个属性的getter和setter方法

class  Dog extends Animal{
    //提供无参构造器
    public Dog() {}
    //提供全参构造器
    public Dog(String name, int age, String color) {
        super(name, age, color);
    }

    public  void noise(){
        System.out.println("---汪汪汪---");
    }
    public void lookHouse(){
        System.out.println("看家");
    }
}

我们在抽象父类外定义了一个子类Dog,并提供了全参构造器和无参构造器,此时还需要重写父类中的抽象方法noise(),并且在后面提供了一个子类独有的看家的方法;

class Cat extends Animal{
    public void noise(){
        System.out.println("---喵喵喵---");
    }
    public void getMouse(){
        System.out.println("抓耗子");
    }
}
//如果不想实现抽象类里的抽象方法,该类需要使用abstract修饰
abstract class Duck extends Animal{
    public void swim(){}
}

在子类外定义了一个新的子类Cat,默认这个子类调用了父类的无参构造器,同样重写了父类中的noise()方法,并在后面定义了一个子类独有的抓耗子的方法

如果定义了一个子类,但是不想使用父类中的抽象方法,就需要把子类也定义为抽象类,使用abstract来修饰

public class AnimalTest {
    public static void main(String[] args) {
        //直接定义一个Cat类型,调用功能
        Cat cat = new Cat();
        cat.noise();
        cat.getMouse();
        //使用多态的向上造型
        Animal a = new Dog("小黑",3,"black");
        a.noise();
        //下面代码编译错误,因为抽象类不能使用new关键字实例化;
        //Animal b = new Animal()
        System.out.println(a.getName());
        System.out.println(a.getAge());
        System.out.println(a.getColor());
    }
}

我们对上述定义的类进行一个测试,使用main方法,实例化一个Cat类型的对象cat,调用noise和抓耗子的方法;

之后使用了多态的向上造型,父类型的变量引用的是子类型Dog的对象;调用noise方法,并调用getter方法

根据返回的结果,我们可以判断是a是子类型的对象,所以会返回汪汪汪。

二.接口

2.1简介

在Java语言中,没有一个子类继承多个父类的语法,一个类想要同时继承两个类的属性和方法,所以这时我们需要使用接口 interface。

接口可以理解为一个特殊的抽象类,也可以理解成为一种规范;

需要注意:

1.接口的关键字:  interface

2.接口里也可以提供成员变量(属性),默认使用public static final 来修饰,即常量。

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

4.接口里可以提供成员方法,默认使用public abstract来修饰

5.一个类若想实现接口,就需要使用关键字implements

6.一个类在实现接口时,需要重写里面的抽象方法,或者使用abstract来修饰类。

public interface InterfaceA {
    double PI = 3.14;
    public static final  double NUM = 0.618;
    void showInfo();
    public abstract int sum(int a, int b);
}

首先定义了一个InterfaceA的接口,并在其中设定两个静态变脸PI和MUM,然后再其中继续写出一个抽象的方法showInfo

class ClassA implements InterfaceA{
    public void showInfo() {}
    public int sum(int a, int b) {
        return a+b;
    }
}
abstract class ClassB implements InterfaceA{
    public void showInfo() {}
}

接下来定义两个子类实现接口,需要使用implements关键字,然后在类中重写抽象方法showInfo

并在ClassA中提供一个属于子类的返回值类型是int类型的方法sum(),在ClassB中,我们设置为抽象类。

2.2接口与接口间的继承关系

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

2.子接口继承了父接口里的所有抽象方法

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

4.类具体实现接口时,需要重写接口里的抽象方法

public interface InterfaceB {
    void methodB();
}
interface InterfaceC{
    void methodC();
}
interface InterfaceD extends InterfaceC,InterfaceB{
    void methodD();
}

在上述代码中,我们设计了三个接口,分别是B,C,D其中接口D继承了接口B和接口C;因为我们设置了接口D继承了上面的两个接口,就不需要重写抽象方法了;

class classT implements InterfaceD{

    @Override
    public void methodD() {

    }

    @Override
    public void methodB() {

    }

    @Override
    public void methodC() {

    }
}

我们创建了一个类来实现接口D,需要使用implements关键字,还需要把接口中的抽象方法全部重写,包括父接口B,C的抽象方法

2.3 jdk1.8以后的接口新特性

1.提供了默认的方法:使用default修饰具有方法体的方法;  该方法使用public修饰,如果逻辑不能满足子类,在子类中可以被重写。

2.提供了静态的方法使用static修饰具有方法体的方法,默认使用public方法,该方法不能被重写

public interface InterfaceM {
    public default void print(){
        System.out.println("---欢迎来到中国,我的家---");
    }
    static void print2(){
        System.out.println("---地球只有一个,人人有责---");
    }
}

首先定义了一个接口M,定义了一个default修饰的print()方法,和一个静态的无返回值的方法

class classU implements InterfaceM{
    //重写接口里的默认方法,不加default
    @Override
    public void print(){
        System.out.println("---欢迎来到长春---");
    }
    //@Override  添加注解,报错,因此print2方法不能重写,此时是自己独有的静态方法
    static void print2(){
        System.out.println("---地球只有一个,人人有责---");
    }
}

在上述代码中,我们设计了一个classU类来实现接口,重写了print()方法,只需要去掉default即可,而对于第二个static修饰的方法,我们不能重写,新给出的print2()方法只能是子类独有的方法,该静态方法只能重载。

2.4 案例

1. USB接口
        充电功能  charge()
        获取信息  getInfo()
2. Computer类型:
        属性:  
            两个USB接口:   usb1,usb2
        方法:
            两个属性的setXXX方法
3. KeyBoard键盘类:
            实现USB接口
4. Mouse鼠标类:
            实现USB接口
5. Program测试类:        

根据上述的需求分析,我们需要设计一个Usb接口,一个父类Computer,一个键盘类,一个鼠标类,一个测试类

首先我们可以设计Usb接口:有两个功能是充电功能和获取信息:

public interface Usb {
    //充电
    void charge();
    //返回信息
    String getInfo();
}

充电功能是不需要返回值的,我们打印一个充电语句就可以,返回信息需要一个返回值String,然后我们就可以设计父类了,键盘和鼠标作为子类,共同特征就是通过USB连接,可以作为成员变量

package com.oop.day03.dInterface;

public class Computer {
     private Usb usb1;
     private Usb usb2;

    public void setUsb1(Usb devide) {
        this.usb1 = devide ;
        System.out.println(devide.getInfo()+"连接到电脑");
    }

    public void setUsb2(Usb devide) {
        this.usb2 = devide;
        System.out.println(devide.getInfo()+"连接到电脑");
    }
}

在上述代码中,我们提供了两个私有化Usb类型的变量usb1和usb2,并提供了两个set方法,分别设置成员变量u1和u2,通过打印语句输出是哪个设备连接到电脑了;接下来就可以设计键盘子类和鼠标子类了;

public class KeyBoard implements Usb{
    @Override
    public void charge() {
        System.out.println("--电脑可以给键盘充电--");
    }

    @Override
    public String getInfo() {
        return "键盘";
    }
}

可以发现,该子类实现了usb接口,重写了其中的charge()方法,又重写了getInfo()方法,返回字符串是键盘;

public class Mouse implements Usb{
    @Override
    public void charge() {
        System.out.println("--电脑可以给鼠标充电--");
    }

    @Override
    public String getInfo() {
        return "鼠标";
    }
}

上述代码与键盘类的格式几乎相同;

public class Program {
    public static void main(String[] args) {
        //具体电脑对象
        Computer c1 = new Computer();
        //实例化键盘
        KeyBoard kb = new KeyBoard();
        //实例化鼠标
        Mouse m1 = new Mouse();
        //连接到电脑上
        c1.setUsb1(m1);
        c1.setUsb2(kb);
        kb.charge();
        m1.charge();

    }
}

在上述代码中,我们实现了一个测试类,在main方法中实例化了电脑对象,键盘对象,鼠标对象,连接需要使用电脑类的setUsb1()方法,分别传入实参Usb类型的kb和m1,就完成了连接的需求,后面直接调用两个子类中重写的charge()方法即可实现充电的需求

2.5 常用接口

comparable接口,汉语翻译成: 可比的,可比较的,是一个形容词。 当一个类的多个对象之间想要进行比较时,比如排序等操作,那么类必须实现该接口,然后自定义比较规则。否则不能比较。

我们通过一个案例来理解这一部分的知识:

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 int getAge() {
        return age;
    }

    public int getHeight() {
        return height;
    }

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

在上述代码中,我们设计了一个一个Person类,继承接口Comparable<Person>,并提供私有属性姓名,年龄,身高,体重。在类体中提供一个全参构造器,并提供get方法,在最后重写一个toString方法,返回字符串的形式

 //如果想要进行比较,除了要实现Comparable接口外,还要实现里面的比较方法compareTo
    //升序: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;
    }
}

我们如果想进行比较,还需要重写接口中的compareTo()方法,形参是Person类型的变量;如果需要升序排序,就需要返回this.相关属性-传入o.相关属性,如果需要降序排列,就需要使用o.相关属性-this.相关属性;

比如若是想按照年龄升序排列:就需要return  this.age - o.age

若是想按照身高降序排列,就需要使用 return o.height - this.height

如果想先按照年龄升序,如果年龄相同,再按照身高降序的化:就需要先声明一个int 类型的变量r, 让r = this.age - o.age; 然后再使用判断语句,如果r ==0,则另r = o.height - this.height;然后再返回r的值,如果r不等于0,就直接返回r。

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);
        //不能比较,会报错:com.oop.day03.eInterface.Person cannot be cast to java.lang.Comparable
        Arrays.sort(ps);
        System.out.println(Arrays.toString(ps));

上述代码作为一个测试类,先实例化了Person类型的数组ps,长度是3,0位置,1位置,2位置分别调用构造器给属性赋值,然后对数组进行比较

返回结果小黑的年龄最小,排在最前面,小张和小明年龄一致,所以要排身高,小明比小张矮,所以排在后面

需求:

现在想要修改比较规则,按照体重进行升序排列,不能修改源代码,

因为Person类有可能其他人正在使用,并不是自己一个人使用

此时就可以使用Comparator比较器进行重新自定义比较规则

使用一个匿名内部类创建一个比较器对象

Comparator c1 =  new Comparator<Person>() {
            @Override
            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));

在上述的main方法里可以创建一个Comparatpor接口类型的变量c1,在类内部重写一遍compare方法按照体重升序,p1在前,p2在后;

数组工具类sort方法中重载了很多方法,我们选择一个sort(Object[ ] a,Comparator c)的类型,输入实参ps,c1;在排序完之后就可以打印该数组了

三. 枚举

3.1简介

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

3.2 枚举的定义

1.自定义类实现枚举

提供一堆可以向外界暴露的该类型的对象,需要是public final static修饰的,并把构造器私有化,不能让外界设置成员变量,可以提供属性,用来描述对象的信息,但是需要使用private final修饰,给属性提供get方法,但是不要提供set方法,因为外界只可以读但是不可以修改。

public class Season {
    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("冬天","白雪皑皑");

    private final String name;//对象的名字
    private final String desc;//对象的描述
    private Season(String name,String desc) {
        this.name = name;
        this.desc = desc;
    }
    public String getName() {
        return name;
    }
    public String getDesc() {
        return desc;
    }
    @Override
    public String toString() {
        return name;
    }
}

根据上述代码,我们定义了一个季节枚举类,提供了成员变量名字和描述,并提供了全参构造器和get方法,同时重写了toString()方法,以便于返回的是成员变量的名字而不是地址。

之后我们定义了一个枚举类型,使用public static final来修饰 后面加上类名和全部大写的对象名。

public class SeasonTest {
    public static void main(String[] args) {
        Season s = Season.AUTUMN;
        //直接输出,打印的是对象的地址
        System.out.println(s);
        //打印季节的名字
        System.out.println(s.getName());

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

根据上述代码,我们先定义一个Season类型的变量s 赋值为AUTUMN枚举,如果直接打印s的值而不重写toString方法的话,就会直接返回地址值,重写之后就可以直接打印name了,我们如果想打印季节的名字的话还可以使用对象调用getname方法,下面的获取信息同理

2.使用enum来定义一个枚举

我们可以先定义一个简单枚举,需要注意的是:
1. 第一行,必须是枚举类的对象.  名称自定义,应该符合常量的命名规则。
 2. 内部系统提供了一个无参构造器,因为创建枚举对象时,调用的是无参构造器,因此
    对象后面的小括号是可以省略的。
    注意:构造器是私有的。

public enum Week {
    MONDAY(), TUESDAY(), WEDNESDAY(), THURSDAY(), FRIDAY(), SATURDAY(), SUNDAY();
    private Week() {}

    public static void main(String[] args) {
        System.out.println(Week.FRIDAY);
    }
}

根据上述代码我们直接定义了一个枚举类Week,再{ }中创建枚举对象,然后提供一个无参构造器,因为调用的是无参构造器,所以说对象后面的( )可以省略,需要注意:构造器必须定义成私有的,然后创建一个main方法来测试,直接打印Week.FRIDAY即可。

3.使用构造器获取枚举对象:

使用构造器来获取一堆枚举对象:

 1.枚举类的第一行必须是枚举的对象

 2.如果提供构造器,构造器必须私有化。枚举对象必须显示调用构造器  

 3.可以提供属性,必须私有化

 4.自定义的枚举,默认继承了java.lang.Enum抽象类

public enum Season {
    SPRING("春天","春暖花开"),SUMMER("夏天","烈日炎炎"),
    AUTUMN("秋天","落叶归根"),WINTER("冬天","白雪皑皑");
    private String name;
    private String desc;
    private Season(String name, String desc){
        this.name = name;
        this.desc = desc;
    }
    public String getName(){
        return name;
    }
    public String getDesc(){
        return desc;
    }

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

根据上述代码,我们实例化了一个Season类,直接在第一行创建枚举对象,使用了构造器,我们必须再下面提供一个私有化的全参构造器,并提供公有的set方法来获取成员变量name

之后实例化一个season对象,赋值为枚举对象的SUMMER,然后打印名字和描述。返回的结果就是夏天  烈日炎炎。

4.重写方法形式

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

枚举类和普通类一样,可以实现接口

public enum Direction implements InterA{
    BEFORE("前"){
        public void showInfo() {
            System.out.println("向前进,");
        }
    },
    AFTER("后"){
        public void showInfo() {
            System.out.println("向后退,悬崖勒马");
        }
    },
    LEFT("左"){
        public void showInfo() {
            System.out.println("向左看齐");
        }
    },
    RIGHT("右"){
        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);
        direction.showInfo();
    }
}
interface InterA{
    void showInfo();
}

我们定义了一个A接口,方向枚举类就继承了A接口,然后我们定义了成员变量name并提供了全参构造器,重写了接口中的抽象方法,在定义枚举时直接调用构造器即可,并且在构造器中直接重写一遍showInfo方法,最后在main方法中实例化一个方向对象,赋值为BEFORE,直接打印方向的值,如果调用方法的话就会打印出向前进。

四.内部类

1.成员内部类

定义在一个类的里面,与类的其他成员是平级关系,没有static修饰

该内部类的访问权限可以是private,默认的,protected,public

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);
    }

我们先创建了一个外部类,提供成员变量年龄,姓名,并提供了全参构造器和方法showInfo()

//定义一个成员内部类
    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);
        }
    }

然后我们定义了一个内部类,也是提供了成员变量姓名和年龄,提供全参构造器,和方法showInfo() ,如果想要访问外部类的成员,需要使用外部类名.this.成员变量名

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

    }
}

在外部类中,我们在main方法里测试,先实例化一个外部类对象,调用构造器来赋值,然偶通过外部类对象来实例化一个内部类对象,之后调用内部类的showInfo()方法;

执行打印命令的就是内部类的showInfo方法;

4.2静态内部类

public class Outer {
    private String name;
    public Outer(String name) {
        this.name = name;
    }
    public void showInfo(){
        System.out.println(name);

首先定义一个外部类,成员变量只有名字,提供构造器和showInfo()方法;

 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);
        }
    }

然后我们定义一个静态内部类,用static修饰,和外部类提供相同的成员变量和方法,但是静态内部类不能直接访问外部类的成员了,即使使用    外部类名.this.成员变量名也不可以;

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

此时我们如果想要实例化内部类的对象,就需要使用   new.外部类名.内部类构造器  的方式

之后调用showInfo()方法;

4.3局部内部类

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

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());
    }
}

在上述代码中,我们先定义了外部类Outer,然后再main方法中建立了一个内部类,提供成员变量name,全参构造器,和get方法,我们此时如果像实例化内部类的对象,必须在方法中实例化,一旦出了方法体,就会报错。

4.4匿名内部类(重点)

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

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

方法的重写

};

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

首先定义一个外部类,调用main方法,然后直接创建一个匿名抽象类使用接口名加上变量的形式,可以在里面提供成员属性,也是需要重写父接口A的抽象方法,并提供一个子类独有的get方法,需要在类体后面加上“;”,然后调用showInfo()方法,可以调用,因为对象类型是A接口类型,但是getName()方法会在编译期间出错,因为编译期间父接口A中没有该方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值