JavaSE 学习笔记03丨继承、接口、多态、内部类

Chapter. 5 继承

继承作为面向对象的三大特征之一,它是多态的前提。它主要解决的问题是共性抽取

Java中的继承,是单继承、多级继承的。

已存在的类,被称为超类、基类、父类(parent class);新类,被称为子类(subclass)、派生类。

每一个子类的直接父亲是唯一的,但一个父亲可拥有多个子类。子类比父类拥有的功能更加丰富。

5.1 继承的格式

定义父类的格式(普通类的定义)

public class 父类名称{
    //...
}

定义子类的格式

public class 子类名称 extends 父类名称{
    //...
}

5.2 继承中成员的访问特点

优先使用本类的成员变量与成员方法,若没有,则往上寻找。

当局部变量名、本类与父类的成员变量名均相同时,可以用以下语法进行区分使用。

  • 局部变量:直接写该变量名
  • 本类的成员变量:this.成员变量名
  • 父类的成员变量:super.成员变量名

5.2.1 覆写(Override)

由于,父类中的有些方法对于子类并不一定适用。因此需要一个新的方法来覆盖父类中的这个方法。

覆写(或称覆盖、重写),发生在继承关系当中,方法的名称一样,参数列表也一样

区分:重载(Overload),方法的名称一样,参数列表不一样

特点:覆写创建的是子类对象,则优先使用子类方法。

@Override:注解(annotation),写在方法前面,用于检测是否为有效的正确覆写。(属于安全检测手段)

注意事项:

  1. 子类方法的返回值必须小于等于父类方法的返回值范围。

    java.long.Object类是所有类的公共最高父类(祖宗类)

  2. 子类方法的权限必须大于等于父类方法的权限修饰符。

    public > protected > (default) > private

    (default)代表什么都不写,直接留空。

  3. 若父类抛出多个异常,子类覆写父类方法时,应该抛出 与父类相同的异常 或者 父类异常的子类 或者 不抛出异常;若父类没有抛出异常,子类覆写时不能抛出异常,此时子类产生该异常,只能捕获处理而不能声明抛出。

/* Phone.java文件中 */
public class Phone {
    public void show(){
        System.out.println("显示号码");
    }
}

/* NewPhone.java文件中 */
public class NewPhone extends Phone {
    @Override
    public void show(){
        super.show();
        System.out.println("显示头像");
        System.out.println("显示号码归属地");
    }
}

5.3 继承中构造方法的访问特点

  1. 子类构造方法当中有一个默认隐含的super()调用,所以一定是先调用父类无参构造方法,后执行子类的构造方法。

  2. 子类构造中可通过super关键字,来调用父类重载的构造方法。

    /* fa.java文件中 */
    public class fa{
        public fa(){
            System.out.println("父类无参构造!");
        }
        public fa(int num){
            System.out.println("父类构造方法!");
        }
    }
    /* son.java文件中 */
    public class son{
        public son(){
            super(20); //调用父类重载的构造方法
            System.out.println("子类构造方法!");
        }
    }
    
  3. super的父类构造调用,必须为子类构造方法中的第一个只能为第一条语句。因此,super()this()这两种构造调用,不能同时使用。

5.4 抽象类

如果父类当中的方法不确定如何进行{}方法体实现,那么这应该是一个抽象方法,相应地,这个父类也必然是抽象类。人们只将它作为派生其他类的基类,而不作为想使用的特定的实例类。

  • 抽象方法:在方法返回值之前加上abstract关键字,然后去掉大括号,直接分号结束。

  • 抽象类:抽象方法所在的类,必须是抽象类。需在class之前利用abstract关键字去声明。

public abstract class Animal{ //抽象类
    public abstract void eat(); //抽象方法
    public void normalMethod(){ //普通的成员方法
        //...
    }
}

注意事项:

  1. 不能创建抽象类对象,必须用一子类去继承抽象父类。

  2. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

  3. 抽象类中,不一定包含抽象方法,但是具有抽象方法的类必定是抽象类。

  4. 子类必须覆写抽象父类当中所有的抽象方法(除非该子类也是抽象类),即子类中,去掉抽象方法的abstract关键字,然后补上方法体大括号。

    public class Cat extends Animal{
        @Override
        public void eat(){
            System.out.println("The cat eats fish");
        }
    }
    

Chapter 6. 接口

接口interface)技术,主要用来描述类具有什么功能,而并不给出每个功能的具体实现。一个类可以实现implement)一个或多个接口,并在需要接口的地方,随时使用实现了相应接口的对象。

6.1 接口概述

在Java中,接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。重要的是,接口不能提供哪些功能,绝不能含有实例域,也不能在接口中实现方法、静态代码块、构造方法。提供实例域和方法实现的任务,应该由实现接口的那个类(可以看做为接口的子类)来完成,实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。

举个例子,Arrays类中的sort方法承诺可以对对象数组进行排序,但要求满足前提——对象所属的类必须实现了Comparable接口。

接口是一种引用数据类型,它的内部主要封装了方法,包含抽象方法(JDK7以前),默认方法和静态方法(JDK8),私有方法(JDK9)。

6.2 定义格式与基本实现

接口的定义格式:(使用interface关键字)

public interface 接口名称{ 
    //抽象方法
    //默认方法
    //静态方法
    //私有方法
}

在使用IDEA创建interface时,应该新建interface而不是class

但是,换成关键字interface之后,.java文件编译生成的字节码文件仍然为.class

类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可称为接口的子类。实现使用implements关键字(注意s

class 类名 implements 接口名{
    //重写接口中抽象方法(必须!)
    //重写接口中默认方法(可选)
}

6.2.1 含有抽象方法

抽象方法: 使用 abstract 关键字修饰,没有方法体。该方法供子类实现使用

定义接口:

public interface LiveAble{ 
    public abstract void eat();//定义抽象方法
    public abstract void sleep();
}

定义实现类:

public class Animal implements LiveAble{
    @Override
    public void eat(){
        System.out.println("获取食物");
    }
    @Override
    public void sleep(){
        System.out.println("地上睡觉");
    } 
}

TIPS:对于IDEA而言,鼠标选中整个接口名称,按alt + Enter键就能方便覆写:

定义测试类:

public class InterfaceDemo{
    public static void main(String[] args){
        Animal a = new Animal(); //创建子类对象
        a.eat(); a.sleep();
    }
}

6.2.2 含有默认方法

默认方法:使用default修饰,不可省略,供子类调用或者子类覆写

定义接口:

public interface LiveAble{
    public default void fly(){
        System.out.println("天上飞");
    }
}

实现类可以继承,可以重写,二选一,但是只能通过实现类的对象来调用。

  1. 继承默认方法来定义实现类:

    public class Animal implements LiveAble{
        //继承,什么都不用写,直接调用
    }
    
  2. 重写默认方法来定义实现类:

    public class Animal implements LiveAble{
        @Override
        public void fly(){
            System.out.println("自由自在地飞");
        }
    }
    

定义测试类:

public class InterfaceDemo{
    public static void main(String[] args){
        Animal a = new Animal(); //创建子类对象
        a.fly(); //1 的输出结果为:天上飞;2 的输出结果为:自由自在地飞。
    }
}

6.2.3 含有静态方法

静态方法:使用static修饰,供接口直接调用。

定义接口:

public interface LiveAble{
    public static void run(){
        System.out.println("跑起来!");
    }
}

静态与.class文件相关,只能使用接口名调用,不可通过实现类的类名或者实现类的对象调用。

定义实现类:

public class Animal implements LiveAble{
    //无法重写静态方法!
}

定义测试类:

public class InterfaceDemo{
    public static void main(String[] args){
        //Animal.run(); //错误写法,无法继承方法也不能调用。
        LiveAble.run();
    }
}

6.2.4 含有私有方法

若一个接口中有多个默认方法,且方法中有重复的内容,那么可以抽取出来,封装到私有方法中,供默认方法去调用。从设计的角度来讲,私有的方法是对默认方法和静态方法的辅助。

  • 普通私有方法:只有默认方法可以调用。

    private 返回值类型 方法名称(参数列表){
        方法体
    }
    
  • 静态私有方法:默认方法和静态方法可以调用。

    private static 返回值类型 方法名称(参数列表){
        方法体
    }
    

举例定义接口:

public interface LiveAble{
	public default void eat(){
        System.out.println("Eat something...");
        methodCommon();
    }
    public default void sleep(){
        System.out.println("Go to sleep...");
        methodCommon();
    }
    private void methodCommon(){ //该方法是专门为了上面两个方法所抽取出来的,不应被实现类访问。
        System.out.println("AAA");
        System.out.println("BBB");
        System.out.println("CCC");
    }
}

6.2.5 含有常量

接口当中其实也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰,从效果上看就是接口的“常量”。

定义接口:

public interface LiveAble{
    public static final int num = 233;
    //public 说明接口内外部都可使用;static 使得num与对象无关;final 使得num不可变
}

注意,接口当中的常量必须进行赋值!

6.3 实现多个接口

6.3.1 接口与抽象类的比较

使用抽象类表示通用属性会存在问题——每个类只能拓展于一个类。不同于C++的多继承特性,为了避免将语言变得非常复杂或者效率降低,Java语言利用接口机制,来实现多继承的大部分功能,由此每个类可以同时实现多个接口

6.3.2 定义格式

public class 实例类名 implements 接口A, 接口B{
    //覆盖所有抽象方法
}

注意事项:

  1. 若实现类所实现的多个接口中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
  2. 若实现类所实现的多个接口中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆写。(也就说,不能直接继承这些默认方法)
  3. 一个类中若直接父类当中的方法,与接口当中的默认方法产生了冲突,优先使用父类当中的方法。

6.4 接口之间的多继承

类与类之间是单继承的,而接口与接口之间是多继承的。

接口的继承也是使用extends关键字。

定义父接口:

public interface A(){
	public default void method(){
        System.out.println("AAA");
    }
} 
public interface B(){
    public default void method(){
        System.out.println("BBB");
    }
}

定义子接口:

public interface C extends A, B{
    @Override
    public default void method(){
        System.out.println("23333");
    }
}

注意事项:

  1. 如果父接口中的默认方法有重名的,那么子接口需要重写一次。
  2. 子接口重写默认方法时,default关键字可以保留。但是,子类重写默认方法时,default关键字不可以保留。

Chapter 7. 多态

多态:指同一行为,具有多个不同表现形式。

"is-a"规则的另一种表述法是置换法则,它表明程序中出现超类对象的任何地方都可以用子类对象置换。在Java中,对象变量是多态的,一个Employee父类变量既可引用一个Employee类对象,也可以引用一个Employee类的任何一个子类的对象。

多态的好处在于,使得程序编写得更简单,并具有良好的拓展。

7.1 多态的体现

定义格式为:

父类名称 变量名 = new 子类对象; // Parent a = new Son();
变量名.方法名(); //a.method();

父类类型:指子类对象继承的父类类型,或者实现的父接口类型。

7.2 多态中访问成员变量的两种方式

  1. 通过对象名称直接访问成员变量,看new等号的左边是谁,优先用谁,没有则向上找。

    如果子类定义个age成员变量,但父类没有定义,则编译器无法找到age变量

  2. 通过成员方法间接访问成员变量,看该方法属于谁,优先用谁,没有则向上找。

    子类没有覆盖重写,则向父类找;子类如果覆盖重写,则向子类找。

7.3 多态中访问成员方法的规则

new的是谁,就优先使用谁,若没有该方法则向上找。

Parent obj = new Son();
obj.method(); //父子都有该方法,优先使用子类
obj.methodOfParent(); //子类没有,但父类有,则向上找到父类。

7.4 向上转型与向下转型

  • 对象的向上转型(较安全)

    父亲引用指向子类对象,然而缺点是,对象一旦向上转型为父类,则无法调用子类原本特有的内容。

    格式为:父类名称 对象名 = new 子类名称;

  • 对象的向下转型

    实际上是一个还原动作,将父类对象还原为原来的子类对象。如果对象创建的时候不是某个类,但此时若要向下转型成该类,则会报错。

    格式为:子类名称 对象名 = (子类名称) 父类对象;

    Animal animal = new Cat(); //创建一个子类Cat对象,然后将它作为父类使用
    Cat cat = (Cat) animal; //本来是Cat,已经被当做Animal,现在还原回来成为本来的Cat
    

案例实现:笔记本具备使用USB设备的功能,且都预留可插入USB设备的USB接口。定义USB接口,具备基本的开启与关闭功能。鼠标和键盘若要在电脑上正常使用,则必须遵守USB规范,实现USB接口,否则无法使用。

定义USB接口:

public interface USB {
    void open(); //打开某个设备
    void close(); //关闭某个
}

定义鼠标类:(实现接口)

public class Mouse implements USB{
    public void open(){
        System.out.println("鼠标连接成功!红灯闪烁!");
    }
    public void close(){
        System.out.println("鼠标连接断开!红灯熄灭!");
    }
    public void click(){
        System.out.println("鼠标单击!");
    }
}

定义键盘类:(实现接口)

public class KeyBoard implements USB{
    public void open(){
        System.out.println("键盘连接成功!绿灯常亮!");
    }
    public void close(){
        System.out.println("键盘连接断开!绿灯熄灭!");
    }
    public void type(){
        System.out.println("键盘打字!");
    }
}

定义笔记本类:(使用接口)

public class Laptop {
    public void run(){
        System.out.println("笔记本运行中....");
    }
    public void useUSB(USB usb){ //usb已经发生向上转型了
        //当笔记本对象调用该功能时,必须给其传递一个符合USB规则的USB设备
        if( usb != null){
            usb.open();
            if(usb instanceof Mouse){
                Mouse m = (Mouse) usb; //将usb向下转型(还原)为鼠标类
                m.click(); //调用特定方法
            }
            else if(usb instanceof KeyBoard){
                KeyBoard kb = (KeyBoard) usb;//将usb向下转型(还原)为键盘类
                kb.type(); //调用特定方法
            }
            usb.close();
        }
    }
    public void shutDown(){
        System.out.println("笔记本关闭....");
    }
}

Chapter 8. 内部类

如果一个事物内部包含另一个事物,那么这就是一个类内部包含另一个类。如类A定义在另一个类B里面,那么内部的类A称为内部类inner class),类B称为外部类。

而内部类有两种:成员内部类 与 局部内部类(包含匿名内部类)。

8.1 成员内部类的定义与使用

一、定义

成员内部类:定义在类中方法外的类。

定义格式为

修饰符 class 外部类名称{
    修饰符 class 内部类名称{
        //...
    }
}

访问特点:

  • 内部类可以直接访问外部类的数据域(包括私有成员
  • 外部类要访问内部类的成员,必须要建立内部类的对象。

编译成文件时,外部类为XXX.class,其内部类为XXX$YYY.class

二、使用

  1. 间接方式:在外部类的方法中,使用内部类;然后main只是调用外部类的方法;

  2. 直接方式:外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称;

    Body.Heart myheart = new Body().new Heart();
    myheart.beat();
    

如果外部类成员变量、内部类成员变量、内部类成员方法中局部变量发生了重名现象,要访问外部类的成员变量需用:外部类名称.this.外部类成员变量名。举例使用如下:

public class Outer{
    int num = 666;
    
    public class Inner{
        int num = 233;
        
        public void methodInner(){
            int num = 30; //内部类方法的局部变量
            System.out.println(num); //局部变量,就近原则
        	System.out.println(this.num); //内部类成员变量
        	System.out.println(Outer.this.num); //外部类成员变量
        }
    }
}

8.2 局部内部类的定义与使用

一、定义

局部内部类:一个类定义在一个方法(包括main)内部

只有当前所属的方法才能够使用它,超出该方法则不能再使用。

普通局部内部类的定义格式:

修饰符 class 外部类名称{
    
    修饰符 返回值类型 外部类方法名称(参数列表){
        class 局部内部类名称{
            //...
        }
    }
}

二、使用

public class Outer{
    
    public void methodOuter(){
        class Inner{ //局部内部类,注意没有任何权限修饰符修饰!
            int num = 233;
            public void methodInner(){
                System.out.println(num);
            }
        }
        Inner inner = new Inner();
        inner.methodInner();
    } 
}

注意,局部内部类倘若希望访问其所在方法的局部变量,那么该局部变量必须是“有效final”(要么被final修饰,要么只被赋一次值)

局部变量是跟随方法走的,在栈内存当中,故方法运行结束之后立刻出栈,局部变量就立刻消失。

new出来的对象在堆内存当中且持续存在,直到垃圾回收消失。

8.3 匿名内部类的定义与使用

一、定义

匿名内部类anonymous inner class)(开发中,最常用的内部类):局部内部类的简化写法,它本质为一个带具体实现的、父类或者父接口的、匿名的 子类对象

如果接口的实现类(或父类的子类)只需使用一次,那么可以省略掉该实现类(或子类)的定义,无需再创建个.java文件,而直接改为匿名内部类。

定义格式:(前提是该匿名内部类必须继承一个父类或者实现一个父接口)

接口名称 对象名 = new 接口名称(){
  	//方法覆写
    @Override
    public void 成员方法名称{
        //...
    }
}; //别忘了分号

二、使用(以接口为例)

public class DemonMain{
    public static void main(String[] args){
        /* 普通写法:
        MyInterface obj = new MyInterfaceImpl();
        obj.method();
        */
        //使用匿名内部类,但不属于匿名对象。
        //等号左边:多态赋值,接口类型引用该子类对象
        MyInterface obj = new MyInterface(){ //等号右边:定义并创建该接口的子类对象
            @Override
            public void method(){
                System.out.println("匿名内部类实现了方法!");
            }
        }; //大括号内是匿名内部类的内容。
        obj.method(); //调用覆写的方法
        
        //使用匿名内部类,而且省略了对象名称,故也属于匿名对象
        new MyInterface(){
            @Override
            public void method2(){
                System.out.println("匿名内部类实现了方法!");
            }
        }.method2(); //注意此处调用了方法。
    }
}

匿名内部类在创建对象的时候,只使用唯一一次(若希望多次创建对象,且类的内容一样的话,那么就必须使用单独定义的实现类)。

注意区分,匿名对象,在调用方法时,只能调用唯一一次(若希望对同一对象调用多次方法,那么就必须为该对象起名字)。

hero.setSkill(new Skill(){
	@Override
    public void use(){
        System.out.println("匿名内部类与匿名对象的同时使用!")
    }
});
posted @ 2020-10-25 22:00  J_StrawHat  阅读(128)  评论(0编辑  收藏  举报