day10_接口丶多态丶内部类

接口

接口是Java语言中的一种引用类型,是方法的"集合",所以接口的内部主要就是定义方法,包含常量,抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法(jdk9)。接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。接口的使用,它不能创建对象,但是可以被实现(implements ,类似于被继承)。一个实现接口的类(可以看做是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的is-a关系,而接口实现则是 "能不能"的has-a关系。

定义格式

day10_接口丶多态丶内部类_第1张图片

代码示例

public interface Demo {
    // 常量(jdk7及其以前) 使用public static final关键字修饰,这三个关键字都可以省略
    public static final int NUM1 = 10;
    int NUM2 = 20;

    // 抽象方法(jdk7及其以前) 使用public abstract关键字修饰,这2个关键字都可以省略
    public abstract void method1();
    void method2();

    // 默认方法(jdk8) 使用public default关键字修饰,public可以省略,default不可以省略
    public default void method3(){
        System.out.println("默认方法 method3");
    }

    // 静态方法(jdk8) 使用public static关键字修饰,public可以省略,static不可以省略
    public static void method4(){
        System.out.println("静态方法 method4");
    }
    // 私有方法(jdk9) 使用private关键字修饰,private不可以省略
    private static void method5(){
        System.out.println("私有静态方法  method5");
    }

    private void method6(){
        System.out.println("私有非静态方法  method6");
    }
}

如何实现接口

类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字

实现方式:类可以实现一个接口,也可以同时实现多个接口。类实现接口后,必须重写接口中所有的抽象方法,否则该类必须是一个“抽象类”。默认方法可以选择保留,也可以重写。 重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了

注意:不能重写静态方法

day10_接口丶多态丶内部类_第2张图片

代码示例

public interface IA {

    public void show1();
}

public interface IB {

    public void show2();
}

public class Zi implements IA, IB {

    public void show1() {
    }

    public void show2() {
    }
}

类可以在“继承一个类”的同时,实现一个、多个接口

public class Fu{}//父类

public interface IA{}//父接口

public interface IB{}//父接口

public class Zi extends Fu implements IA,IB{//一定要先继承,后实现
}

接口中成员访问特点

  • 接口中的常量:可以被实现类继承, 主要是供接口直接使用,推荐使用接口名直接调用。不推荐使用实现类去访问。
  • 接口的抽象方法、默认方法:可以被实现类继承和重写,只能通过实现类对象才可以调用。接口不能直接创建对象,只能创建实现类的对象
  • 接口中的静态方法:不能被实现类继承, 对于接口的静态方法,直接使用“接口名.”进行调用即可。也只能使用“接口名."进行调用,不能通过实现类的对象进行调用
  • 接口中的私有方法: 不能被实现类继承,只能在接口中直接调用,实现类无法调用
interface IA {
    // 常量
    public static final int NUM = 10;

    // 抽象方法
    public abstract void method1();

    // 默认方法
    public default void method2(){
        //method4();
        //method5();       私有方法: 只能在本接口中调用
        System.out.println("IA 接口中的默认方法method2");
    }

    // 静态方法
    public static void method3(){
        //method5();    私有方法: 只能在本接口中调用
        System.out.println("IA 接口中的静态方法method3");
    }

    // 私有方法
    private void method4(){
        System.out.println("IA 接口中的私有方法method4");
    }

    private static void method5(){
        System.out.println("IA 接口中的私有方法method5");
    }
}



class Imp implements IA {
    // 重写接口的抽象方法
    @Override
    public void method1() {
        System.out.println("实现类重写IA接口中的抽象方法");
    }

    // 重写接口的默认方法
    @Override
    public void method2() {
        System.out.println("实现类重写IA接口中的默认方法");
    }

}


public class Test {
    public static void main(String[] args) {
        /*
            接口中成员的访问特点:
                常量:主要是供接口名直接访问
                抽象方法:就是供实现类重写
                默认方法:就是供实现类重写或者实现类对象直接调用
                静态方法: 只供接口名直接调用
                私有方法:只能在本接口中调用


         */
        // 访问接口常量
        System.out.println(IA.NUM);// 10  推荐
        //System.out.println(Imp.NUM);// 10 不推荐 常量被实现类继承了

        // 创建实现类对象调用方法
        Imp imp = new Imp();

        // 访问抽象方法
        imp.method1();

        // 访问默认方法
        imp.method2();

        // 接口名访问静态方法
        IA.method3();
        //Imp.method3();// 编译报错,没有继承
    }
}

实现类实现多个接口,其中接口中发生了冲突怎么办?冲突就是说多个父接口拥有相同名字的方法或者属性

  • 公有静态常量的冲突实现类不继承冲突的常量,编译器会报引用不明确
interface A{
    public static final int NUM1 = 10;
}
interface B{
    public static final int NUM1 = 20;
    public static final int NUM2 = 30;
}
class Imp implements A,B{

}
public class Test {
    public static void main(String[] args) {
        /*
            公有静态常量的冲突: 如果多个接口中有相同的常量,那么实现类就无法继承
         */
        //System.out.println(Imp.NUM1);// 编译报错,无法访问
        System.out.println(Imp.NUM2);// 30
    }
}
  • 公有抽象方法的冲突:实现类只需要重写一个
interface A{
    public abstract void method();
}
interface B{
    public abstract void method();
}
class Imp implements A,B{
    @Override
    public void method() {
        System.out.println("实现类重写");
    }
}
public class Test {
    public static void main(String[] args) {
        /*
            公有抽象方法的冲突:实现类只需要重写一个 
         */
    }
}
  • 公有默认方法的冲突:实现类必须重写一次最终版本
interface A{
    public default void method(){
        System.out.println("A 接口的默认方法method");
    }
}
interface B{
    public default void method(){
        System.out.println("B 接口的默认方法method");
    }
}
class Imp implements A,B{
    @Override
    public void method() {
        System.out.println("实现类重写的默认方法");
    }
}
public class Test {
    public static void main(String[] args) {
        /*
            公有默认方法的冲突:实现类必须重写一次最终版本 
         */
        Imp imp = new Imp();
        imp.method();
    }
}
  • 公有静态方法的冲突:静态方法是直接属于接口的,不能被继承,所以不存在冲突
  • 私有方法的冲突:私有方法只能在本接口中直接使用,不存在冲突

接口与接口之间的关系

接口可以“继承”自另一个“接口”,而且可以“多继承”。

day10_接口丶多态丶内部类_第3张图片

代码示例 

interface IA {//父接口
}

interface IB {//父接口
}

interface IC extends IA, IB {//是“继承”,而且可以“多继承”

}

接口多继承接口的冲突情况,我们应该怎么办?

  • 公有静态常量的冲突:子接口无法继承父接口中冲突的常量
interface A{
    public static final int NUM1 = 10;
}
interface B{
    public static final int NUM1 = 20;
    public static final int NUM2 = 30;
}
interface C extends A,B{

}
public class Test {
    public static void main(String[] args) {
        /*
            公有静态常量的冲突: 子接口无法继承父接口中冲突的常量
         */
        //System.out.println(C.NUM1);// 编译报错,说明无法继承
        System.out.println(C.NUM2);// 30
    }
}
  • 公有抽象方法冲突 :子接口只会继承一个有冲突的抽象方法  
interface A{
    public abstract void method();
}
interface B{
    public abstract void method();
}
interface C extends A,B{

}
class Imp implements C{
    @Override
    public void method() {
        System.out.println("实现接口的抽象方法");
    }
}

public class Test {
    public static void main(String[] args) {
        /*
            公有抽象方法的冲突:子接口只会继承一个有冲突的抽象方法
         */
        Imp imp = new Imp();
        imp.method();
    }
}
  • 公有默认方法的冲突  :子接口中必须重写一次有冲突的默认方法
interface A{
    public default void method(){
        System.out.println("A 接口中的默认方法method");
    }
}
interface B{
    public default void method(){
        System.out.println("B 接口中的默认方法method");
    }
}

interface C extends A,B{

    @Override
    public default void method() {
        System.out.println("重写父接口中的method方法");
    }
}

class Imp implements C{

}

public class Test {
    public static void main(String[] args) {
        /*
            公有默认方法的冲突:子接口中必须重写一次有冲突的默认方法
       
         */
        Imp imp = new Imp();
        imp.method();// 重写父接口中的method方法
    }
}
  • 公有静态方法和私有方法:不冲突,因为静态方法是直接属于接口的,只能使用本接口直接访问,而私有方法只能在接口中访问,也没有冲突

实现类继承父类又实现接口时的冲突

day10_接口丶多态丶内部类_第4张图片

  • 父类和接口的公有静态常量的冲突:子类无法继承有冲突的常量
  • 父类和接口的抽象方法冲突:子类必须重写一次有冲突的抽象方法
  • 父类和接口的公有默认方法的冲突:优先访问父类的
  • 父类和接口的公有静态方法:只会访问父类的静态方法,接口中的静态方法无被子类继承
  • 父类和接口的私有方法:不存在冲突

使用场景

额外的功能: 在接口中定义,让实现类实现

  • 如果可以确定的通用功能,使用默认方法
  • 如果不能确定的功能,使用抽象方法

共性的功能 在父类中定义,让子类继承

  • 如果可以确定的通用功能,使用默认方法
  • 如果不能确定的功能,使用抽象方法

多态

生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。程序中多态: 是指同一方法,对于不同的对象具有不同的实现.

前提条件

  • 继承或者实现【二选一】
  • 父类引用指向子类对象\接口引用指向实现类对象【格式体现】
  • 方法的重写【意义体现:不重写,无意义】

多态的体现:

父类的引用指向它的子类的对象

day10_接口丶多态丶内部类_第5张图片

代码示例

class Animal{//父类
    public void eat(){
        System.out.println("吃东西");
    }
}

class Dog extends Animal{//子类Dog
    @Override
    public void eat() {
        System.out.println("狗吃骨头...");
    }
}

class Cat extends Animal{//子类Cat
    @Override
    public void eat() {
        System.out.println("猫吃鱼...");
    }
}

public class Test1 {
    public static void main(String[] args) {
        /*
            多态: 同一种行为,不同的事物具有不同的表现形态
            实现多态:
                1.继承或者实现
                2.父类引用指向子类对象\接口引用指向实现类对象
                3.方法重写
         */
        // 父类引用指向子类对象
        Animal anl = new Dog();// 多态
        anl.eat();// 狗吃骨头...

        Animal anl1 = new Cat();
        anl1.eat();// 猫吃鱼...
    }
}

多态的几种形式

普通父类多态

public class Fu{}
public class Zi extends Fu{}
public class Demo{
    public static void main(String[] args){
        Fu f = new Zi();//左边是一个“父类”
    }
}

抽象父类多态

public abstract class Fu{}
public class Zi extends Fu{}
public class Demo{
    public static void main(String[] args){
        Fu f = new Zi();//左边是一个“父类”
    }
}

 父接口多态

public interface A{}
public class AImp implements A{}
public class Demo{
    public static void main(String[] args){
        A a = new AImp();
    }
}

多态情况下,创建子类对象,去访问成员,到底是访问父类的还是子类的??

成员变量的访问特点

  • 编译看左边,运行看左边。简而言之:多态的情况下,访问的是父类的成员变量

成员方法的访问特点

  • 非静态方法:编译看左边,运行看右边。简而言之:编译的时候去父类中查找方法没有就报错,运行的时候先去子类中查找方法来执行,没有就去执行父类的方法
  • 静态方法:编译看左边,运行看左边。简而言之:编译的时候去父类中查找方法,运行的时候去父类中查找方法来执行

代码示例


class Animal {
    int num = 10;

    public void method1() {
        System.out.println("Animal 非静态method1方法");
    }

    public static void method2() {
        System.out.println("Animal 静态method2方法");
    }
}

class Dog extends Animal {
    int num = 20;

    public void method1() {
        System.out.println("Dog 非静态method1方法");
    }

    public static void method2() {
        System.out.println("Dog 静态method2方法");
    }
}

public class Test {
    public static void main(String[] args) {
        /*
            多态时访问成员的特点:
                成员变量:编译看父类,运行看父类(编译看左边,运行看左边)
                成员方法:
                    非静态方法:编译看父类,运行看子类(编译看左边,运行看右边)
                    静态方法: 编译看父类,运行看父类(编译看左边,运行看左边)
                结论:除了非静态方法是编译看父类,运行看子类,其余都是看父类
         */
        // 父类的引用指向子类的对象
        Animal anl = new Dog();
        System.out.println(anl.num);// 10

        anl.method1();// Dog 非静态method1方法

        anl.method2();// Animal 静态method2方法

    }
}

多态的应用场景:

  • 多态应用在形参实参:参数类型为父类类型,该参数就可以接收该父类类型的对象或者其所有子类对象
  • 多态应用在数组:数组元素类型声明为父类类型,可以存储父类类型的对象或者子类的对象
  • 多态应用在返回值:如果返回值类型为父类类型,那么就可以返回该父类类型的对象或者其所有子类对象
  • 如果变量的类型为父类类型:该变量就可以接收该父类类型的对象或者其所有子类对象

多态的好处和弊端

  • 好处:提高了代码的扩展性
  • 弊端:多态的情况下,只能调用父类的共性内容,不能调用子类的特有内容。无法访问子类独有的方法或者成员变量,因为多态成员访问的特点是,编译看父类

引用类型转换

多态,无法访问子类独有的方法或者成员变量,因为多态成员访问的特点是,编译看父类。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做类型转换。不管是向上转型还是向下转型,一定满足父子类关系或者实现关系

向上转型当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型。此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了此时,一定是安全的,而且也是自动完成的。例如:

//父类是Animal 子类是Cat
Aniaml anl = new Cat();  

向下转型当左边的变量的类型(子类)<右边对象/变量的类型(父类),我们就称为向下转型。此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了。

 Aniaml anl = new Cat();  
 Cat c = (Cat)anl;//向下转型

不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常。

返回值:

  •  如果前面变量指向的对象类型是属于后面的数据类型,那么就返回true
  •  如果前面变量指向的对象类型不是属于后面的数据类型,那么就返回fals

所以,转换前,我们最好先做一个判断,代码如下:

  // 向上转型  
        Animal a = new Cat();  
        a.eat();               // 调用的是 Cat 的 eat

        // 向下转型  
        if (a instanceof Cat){
            Cat c = (Cat)a;       
            c.catchMouse();        // 调用的是 Cat 的 catchMouse
        } else if (a instanceof Dog){
            Dog d = (Dog)a;       
            d.watchHouse();       // 调用的是 Dog 的 watchHouse
        }
    }  

 总结一下

class Animal {//父类
    public void eat() {
        System.out.println("吃东西...");
    }
}

class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃骨头...");
    }

    // 特有的功能
    public void lookHome() {
        System.out.println("狗在看家...");
    }
}

class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼...");
    }

    // 特有的功能
    public void catchMouse() {
        System.out.println("猫抓老鼠...");
    }
}

public class Test {
    public static void main(String[] args) {
        Dog d = new Dog();
        method(d);

        System.out.println("==========================");

        Cat c = new Cat();
        method(c);
    }

    // 形参多态: 如果父类类型作为方法的形参类型,那么就可以接收该父类类型的对象或者其所有子类的对象
    public static void method(Animal anl) {
        anl.eat();
        //anl.lookHome();// 编译报错
        // anl.catchMouse();// 编译报错
        if (anl instanceof Dog) {
            Dog d = (Dog) anl;// 向下转型 Dog类型
            d.lookHome();
        }

        if (anl instanceof Cat) {
            Cat c = (Cat) anl;// 向下转型 Cat类型
            c.catchMouse();
        }

    }
}

注意:

  • 不管是向上转型还是向下转型,一定满足父子类关系或者实现关系 

内部类

什么是内部类

  • 将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类

成员内部类

定义在类中方法外的类就是成员内部类

格式:

day10_接口丶多态丶内部类_第6张图片

在描述事物时,若一个事物内部还包含其他事物,就可以使用内部类这种结构。比如,汽车类Car 中包含发动机类Engine ,这时,Engine就可以使用内部类来描述,定义在成员位置。

代码举例:

class Car { //外部类
    class Engine { //内部类

    }
}

访问特点

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

创建内部类对象格式:

 代码示例

package demo02;

// 外部类
public class Body {
    public void methodW1() {
        // 访问内部类的成员
        Heart bh = new Heart();
        System.out.println(bh.numN);// 10
        bh.methodN1();// 内部类的成员方法 methodN1
    }

    // 成员变量
    private int numW = 100;

    // 成员方法
    private void methodW2() {
        System.out.println("外部类的成员方法 methodW2");
    }


    // 内部类
    public class Heart {
        // 成员变量
        int numN = 10;

        // 成员方法
        public void methodN1() {
            System.out.println("内部类的成员方法 methodN1");
        }

        public void methodN2() {
            // 访问外部类的成员
            System.out.println(numW);
            methodW2();
        }
    }

}

定义测试类

package demo02;

public class Test {
    public static void main(String[] args) {
        /*
            - 什么是内部类:将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,外面的那个B类则称为外部类。
            - 成员内部类的格式:
                 public class 外部类{
                     public class 内部类{

                    }
                 }
            - 成员内部类的访问特点:
                在其他类中,访问内部类的成员,得先创建内部类对象:
                    外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
                在外部类中,访问内部类的成员,得先创建内部类对象:
                    外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
                    内部类名 对象名 = new 内部类名();

                在内部类中,可以直接访问外部类的一切成员(包含私有的):

         */
        // 创建内部类的对象
        Body.Heart bh = new Body().new Heart();
        System.out.println(bh.numN);// 10
        bh.methodN1();// 内部类的成员方法 methodN1


        // 创建外部类对象
        Body b = new Body();
        b.methodW1();


        bh.methodN2();// 100    外部类的成员方法 methodW2

    }
}

私有成员内部类

  • 将一个类,设计为内部类的目的,大多数都是不想让外界去访问,所以内部类的定义应该私有化,私有化之后,再提供一个可以让外界调用的方法,方法内部创建内部类对象并调用。

静态成员内部类

  • 静态成员内部类访问格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
  • 静态成员内部类中的静态方法:外部类名.内部类名.方法名( );
class Outer {
    static class Inner {
        public void show(){
            System.out.println("inner..show");
        }

        public static void method(){
            System.out.println("inner..method");
        }
    }
}

public class Test3Innerclass {
    /*
        静态成员内部类演示
     */
    public static void main(String[] args) {
        // 外部类名.内部类名 对象名 = new 外部类名.内部类名();
        Outer.Inner oi = new Outer.Inner();
        oi.show();

        Outer.Inner.method();
    }
}

局部内部类

  • 定义位置:局部内部类是在方法中定义的类

局部内部类使用方式:

  • 局部内部类,外界是无法直接使用,需要在方法内部创建对象并使用
  • 该类可以直接访问外部类的成员,也可以访问方法内的局部变量

匿名内部类

匿名内部类属于局部内部类中的一种它的本质是一个带具体实现的 父类或者父接口的 匿名的 子类对象

day10_接口丶多态丶内部类_第7张图片

代码示例

package Demo03;

/*
    游泳接口
 */
interface Swimming {
    void swim();
}

public class TestSwimming {
    public static void main(String[] args) {
        //匿名内部类作为参数传递
        goSwimming(new Swimming() {
            @Override
            public void swim() {
                System.out.println("铁汁, 我们去游泳吧");
            }
        });
    }

    /**
     * 使用接口的方法
     */
    public static void goSwimming(Swimming swimming){
        swimming.swim();
    }
}

引用类型使用小结

实际的开发中,引用类型的使用非常重要,也是非常普遍的。我们可以在理解基本类型的使用方式基础上,进一步去掌握引用类型的使用方式。基本类型可以作为成员变量、作为方法的参数、作为方法的返回值,那么当然引用类型也是可以的。他们传递的都是地址值。

  • 类名作为方法参数和返回值:可以直接传入该类的对象或者,返回该类的对象。
  • 抽象类作为方法参数和返回值:只能传入,返回该类的子类对象
  • 接口作为方法参数和返回值:只能传入该接口的实现类对象,返回该接口的实现类对象
  • 类名作为成员变量:对它进行赋值的操作,实际上,是赋给它该类的一个对象。
  • 抽象类作为成员变量:为此成员变量赋值时,可以是任何它的子类对象
  • 接口作为成员变量  : 赋该接口的实现类对象

你可能感兴趣的:(#,一:JavaSE,java)