百知-预热视频-08 面向对象高级特性-20210214

面向对象的核心是 继承 与 多态。

1. 继承性:extends

继承的说法不够准确,应该是将父类的访问权给子类,是给子类访问父类的指挥权,而不是将父类给子类。
继承性解决的是代码重用的问题,利用继承性可以从已有的类继续派生出新的子类,也可以利用子类扩展出更多的操作功能。
若父类的属性是私有的(private),继承的子类是没有访问权限的。 即private是无法被继承的;default是同包子类可以继承,public即使跨包也可以继承。
构造方法不可继承。
继承的范围
语法:class 子类 extends 父类{ } // 子类又称派生类,父类又称超类。

继承的限制

① Java不允许多重继承,但允许多层继承。
eg:class A extends B,C; // 一个子类继承了两个父类:错误
从开发角度讲,类直接的继承关系最多不要超过三层。
② 子类继承父类时,严格来讲会继承父类中的全部操作,对于所有的私有操作属于隐式继承,所有的非私有操作属于显示操作。
③ 在子类对象构造前一定会默认调用父类的构造(默认使用无参构造),以保证父类的对象先实例化,子类对象后实例化。自己理解:子类既然继承父类,那必须父类先存在才行,所以先将父类对象实例化,但主函数假设没有给父类对象,所以这时候先将它实例化但不给它参数(即无参构造),这样才能保证子类接下来的存在与使用。

super( ):调用父类构造方法

super()主要是调用父类的构造方法,所以必须放在子类构造方法的首行。
所以构造方法的第一行语句必须是super()。 如果不写,默认是super( ),无参的,即默认调用父类的无参构造方法。
super(); //父类中有无参构造时加于不加 无区别,若编写必须写在首行

class B extends A{     //B是子类 继承父类A
    public B(){        //定义子 不v类B的构造方法
        super();       //父类中有无参构造时加于不加无区别,若编写必须写在首行
        System.out.println("B类的构造方法");
    }
}

如果父类中 没有 无参构造方法,则必须明确地使用super()调用父类指定参数的构造方法。

class A{
    public A(String title){     //父类提供有参构造方法
        System.out.println("A类的构造方法,title = " + title);
    }
}
class B extends A{
    public B(String title){     //子类提供有参构造
        super(title);       //明确调用父类构造,否则将出现编译错误
        System.out.println("B类的构造方法" + title);
    }
}
public class class07{
    public static void main(String[] args){
        new B("hello" + " World");     //实例化子类对象,没有逗号相当于一个参数
    }
}
A类的构造方法,title = hello World
B类的构造方法 hello World

1.1 覆写

继承性的主要特征是子类可以根据父类已有的功能进行功能的扩展,但是在子类定义属性或方法时,有可能出现定义的属性或方法与父类同名的情况,这样的操作称之为覆写。

① 方法的覆写

覆写代码执行结果的分析:
1> 观察实例化的是哪个类;
2> 观察这个实例化的类里调用的方法是否已被覆写过,如果没有被覆写过则调用父类。
当子类发现父类方法功能不足时但又必须使用资这个方法名称时,就需要覆写。
被子类覆写的方法不能拥有比父类更严格的访问控制权限。 3种权限由宽到严的顺序是:public 、default 、 private 。(default 即 默认什么都不写) eg:父类方法使用private 声明不可被覆写。

class A{
    public void fun(){
        this.print();  //调用print()方法
    }
    private void print(){   // private 权限,无法被覆写
        System.out.println("更多课程访问:www.mldn.com");
    }
}
class B extends A{
    public void print(){    //不能覆写print()方法
       System.out.println("www.yootk.com");
    }
}

public class class07{
    public static void main(String[] args){
        B b = new B();     //实例化子类对象
        b.fun();
    }
}
更多课程访问:www.mldn.com
**因为子类无法覆写,所以子类的同名方法无法运行。**

super.方法( ): 调用被覆写的父类方法

默认情况下子类调用的方法是被覆写过的方法,但 super.方法()可以调用被覆写的父类方法:

class A{
    public void print(){
        System.out.println("购买域名:www.mldn.com");
    }
}
class B extends A{
    public void print(){
        super.print();
        System.out.println("课程:wwww.yookt.com");
    }
}

public class class07{
    public static void main(String[] args){
        B b = new B();     //实例化子类对象
        b.print();
    }
}
购买域名:www.mldn.com
课程:wwww.yookt.com

覆写 与 重载 的区别

重载覆写
发生在一个类里发生在继承关系中
方法名称相同、参数的类型及个数不同方法名称相同、参数的类型、个数相同、方法返回值相同
没有权限的限制被覆写的方法不能拥有比父类更为严格的访问控制权限

this 与 super 的区别

thissuper
功能:调用本类构造、本类方法、本类属性子类调用父类构造、父类方法、父类属性
形式: 先查找本类中是否存在有指定的调用结构,如果有直接调用,如果没有则调用父类定义不查找子类,直接调用父类操作–
特殊: 表示本类的当前对象-

在开发中,对于本类或父类的操作,加上“this”、“super”,以便于代码的维护。

② 属性的覆盖

如果子类定义了和父类完全相同的属性名称时,就称为属性的覆盖。
在开发中,类中的属性必须使用private封装,一旦封装,属性覆盖没有任何意义,因为父类定义的私有属性 子类根本看不见,更不会相互影响。(《第一行代码》p217)

1.2 继承 案例

现在要求定义一个整型数组的操作类,数组的大小由外部决定,用户可以向数组中增加数据、以及取得数组中的全部元素,随后在原本的数组上扩充指定的容量,另外在此类的基础上派生一下两个子类:

  1. 排序类:取得的数组内容是经过排序出来的结果;
  2. 反转类:取得的数组内容是反转出来的结果。
    分析: 本程序要求数组实现动态的内存分配,在本类的构造方法中应该为类中数组初始化。
class Array{
    private int data[];
    private int foot;      //表示数组的操作脚本
    /**
     * 构造本类对象需要设置大小,如果设置的长度小于0则维持一个大小
     * @param len 数组开辟时的长度
     */
    public Array(int len){//一旦存在有参构造,即不会默认存在无参构造
        if(len > 0){
            this.data = new int[len];//数组长度大于0,开辟一个数组
        }
        else{
            this.data = new int[1];//数组长度小于等于0,开辟有一个元素的数组
        }
    }
    public boolean add(int num){
        if(this.foot < this.data.length){   //有空间保存
            this.data[this.foot++] = num;   //保存数据,修改脚标
            return true;                    //保存成功
        }
        return false;                       //保存失败
    }
    public int[] getData(){
        return this.data;                   //取得数组内容
    }
}

/**
 * 开发排序类:只需要在数据取得时返回排序好的数据即可,所以覆写父类的个体Data()方法即可。
 */
class SortArray extends Array{  //定义排序子类
    public SortArray(int len){   //父类里没有无参构造方法
        super(len);              //调用父类的有参构造,为父类的data属性初始化
    }
    /**
     * 使用java.util.Array.sort()实现数组的排序操作
     */
    public int[] getData(){
        java.util.Arrays.sort(super.getData());
        return super.getData();
    }
}

/**
 * 开发反转类:反转类是指在进行数组数据取得时,可以实现数据的首尾交换。
 */
class ReverseArray extends Array{
    public ReverseArray(int len){
        super(len);
    }
    public int[] getData(){
        int center = super.getData().length / 2;   //计算反转次数
        int head = 0;                              //头部脚标
        int tail = super.getData().length - 1;     //尾部脚标
        for(int x = 0;x < center;x++){             //反转,数据交换
            int temp = super.getData()[tail];
            super.getData()[head] = super.getData()[tail];
            super.getData()[tail] = temp;
            head++;
            tail--;
        }
        return super.getData();                  //返回反转后的数据
    }
}


public class class07{
    public static void main(String[] args){
        System.out.println("开发排序类的输出结果:");
        SortArray arr = new SortArray(3);         //数组长度是3
        System.out.println(arr.add(20) + "、");
        System.out.println(arr.add(10) + "、");
        System.out.println(arr.add(30) + "、");
        System.out.println(arr.add(40) + "、");   //保存失败
        int[] temp = arr.getData();              //调用覆写方法,取得全部数组数据
        for(int x = 0;x < temp.length;x++){      //循环输出数据
            System.out.println(temp[x] + "、");
        }
        // 对两种类的功能操作代码一样
        System.out.println("开发反转类输出的结果:");
        ReverseArray arr1 = new ReverseArray(3);
        System.out.println(arr1.add(20) + "、");
        System.out.println(arr1.add(10) + "、");
        System.out.println(arr1.add(30) + "、");
        System.out.println(arr1.add(40) + "、");
        int[] temp1 = arr1.getData();            //调用覆写方法,取得全部数组数据
        for(int x = 0;x < temp1.length;x++){     //循环输出数据
            System.out.println(temp1[x] + "、");
        }
    }
}
开发排序类的输出结果:
truetruetruefalse102030、
开发反转类输出的结果:
truetruetruefalse301020、
想要对子类覆写方严格控制,就必须依靠抽象类与接口来解决。

1.3 final 关键字

在Java中final称为终结器,可以使用Java定义 类、方法、属性

① final 定义的类不能有子类

不要轻易使用final定义类:
如果只进行应用开发的话,大部分情况不需要使用final定义类;如果从事一些系统架构的代码开发的话,才有可能使用这样的代码。。String 也是使用final定义的类,所以String类不允许被继承。

final class A{
}
class B extends A{   // 不能继承
}

② final 定义的方法不能被子类覆写

在一些时候由于父类中的某些方法具备一些重要特征,并且这些特征不希望被子类破坏(不能被覆写),就可以在方法的声明处加上final,意思是子类不要破坏这个方法的原本作用。

class A{
public final void fun(){  }   // 此方法不允许子类覆写
}
class B extends A{
public void fun(){}          //此处覆写失败
}

③ final 定义的变量是常量

final 定义的变量是常量,常量必须在定义的时候设置好内容,并且不能修改。

class A{
final double GOOD = 100.0;
public final void main(){
         Good = 10.1;     // 错误:不能修改常量
    }
}

定义常量的最大意义:使常量可以利用字符串(常量名称)更直观地描述数据。

全局常量:public static final

public static final String MSG = “yook”;
public static fina:联合定义的公共常量,,定义后必须进行初始化赋值。

2. 多态性

多态性更合理的解释需要结合抽象类与接口。
掌握了多态性才可以编写出更加合理的面向对象程序。多态性在开发中可以体现一下两个方面:

  1. 方法的多态性:重载与覆写
    重载:同一个方法名称,根据不同的参数类型及个数可以完成不同的功能;
    覆写:同一个方法,根据实例化的子类对象不同,所完成的功能不同。
  2. 对象的多态性:父 子类对象的转换
    向上转型:子类对象变为父类对象:父类 父类对象 = 子类实例 (自动转换)
    向下转型:父类对象变为子类对象:子类 子类对象 = (子类)父类实例 (强制转换)
    对象的多态性与方法覆写是紧密联系在一起的。

1> 向上转型(对象):自动

对象向上转型的特点:不要看类名称,而要看实例化对象的类。 整个操作中根本不需要关心对象的声明类型,关键在实例化新对象时所调用的是哪个子类的构造,如果方法被子类覆写,调用的是被覆写过的方法,否则就调用父类中定义的方法,这一点与方法覆写的执行原则是完全一样的。

/**
 * Created by Administrator on 2021/2/19.
 */

class A{
    public void print(){
        System.out.println("A 类方法");
    }
}
class B extends A{
    public void print(){
        System.out.println("B 类方法");
    }
}

public class class08 {
    public static void main(String[] args) {
        A a = new B();     //实例化的是子类对象,对象向上转型
        a.print();         //调用被子类覆写过的方法
    }
}
B 类方法

2> 向下转型(对象): 强制

向下转型有前提条件的:必须先发生向上转型才可以发生向下转型 , 如果是两个没有关系的类对象发生强制转换,就会出现“ClassCastException”异常。所以向下转型存在安全隐患,开发中尽量避免此类操作。

// 子类与上一个程序一样
public class class08 {
    public static void main(String[] args) {
        A a = new B();     //实例化的是子类对象,对象向上转型
        B b = (B) a;       //对象需要强制性地向下转型
        a.print();         //调用被子类覆写过的方法
        b.print();
    }
}
B 类方法
B 类方法

对象多态性有什么用,难道只是为了无聊的转型吗: (对于对象多态性其本质是根据实例化对象所在的类是否覆写了父类中的指定方法来决定最终执行的方法体,而这种向上或向下的转型有什么意义)
在实际开发中,对象向上转型的主要意义在于参数统一,也是最为主要的用法,而对象的向下转型指的是调用子类的个性化操作方法。。下面是例子:
链表操作中由于数据类型不能统一,所以需要重复定义多个链表,而通过对象的向上转型可以轻松实现参数统一:

/**
 * Created by Administrator on 2021/2/19.
 */

class A{
    public void print(){
        System.out.println("A 类方法");
    }
}
class B extends A{
    public void print(){
        System.out.println("B 类方法");
    }
}
class C extends A{
    public void print(){
        System.out.println("C 类方法");
    }
}

public class class08 {
    public static void main(String[] args) {
        fun(new B());     // 相当于 A a = new B(),对象向上转型 
        fun(new C());     // 相当于 A a = new C(),对象向上转型

    public static void fun(A a) {
        a.print();
    }
    }
}
B 类方法
C 类方法

在本程序的 fun()方法上只是接受了一个A类的实例化对象,按照对象的向上转型原则,此时的 fun()方法可以接收A类对象或所有A类的子类对象,这样只需要一个A类的参数类型,此方法就可以处理一切A的子类对象(即使A类有几百个子类,fun()方法依旧可以接收)。而在 fun()方法中将统一调用 print()方法,如果此时传递的子类对象,并且覆写过 print()方法,就表现执行被子类所覆写过的方法。

instanceof 关键字

利用此关键字判断某一个对象是否是指定类的实例,格式:
对象 instanceof 类 返回boolean 型
如果某个对象是某个类的实例,就返回true,否则返回false。
在进行对象强制转换前进行判断,以保证安全可靠的向下转型操作。

public class class08 {
    public static void main(String[] args) {
        A a = new B();                       //向上转型
        System.out.println(a instanceof A);
        System.out.println(a instanceof B);
        System.out.println(null instanceof A);
}
true true false 

a 对象采用了向上转型进行实例化操作,所以a 是A 类或B 类的实例化对象,而null 在使用 instanceof 判断时返回的结果是false。

在进行向下转型时建议都使用 instanceof 判断:从实际的开发讲,向下转型的操作几乎很少见到,如果出现了,不确定此操作是否安全,一定先使用instanceof 关键字判断。 (《第一行代码》p230)

本程序中为了保证安全的向下转型操作,在将父类转换为子类对象时首先使用了instanceof进行判断,如果当前对象是子类实例,则进行强制转换,以调用子类的扩充方法。

/**
 * Created by Administrator on 2021/2/19.
 */

class A{
    public void print(){
        System.out.println("A 类方法");
    }
}
class B extends A{
    public void print(){   // 覆写父类的print()方法
        System.out.println("B 类方法");
    }
    public void fun(){
        System.out.println("B 扩充的方法");
    }
}

public class class08 {
    public static void main(String[] args) {
        fun(new B());     // 对象向上转型
    }
    public static void fun(A a){
        a.print();
        if(a instanceof B){  // 如果a 对象是B 类的实例
            B b = (B) a;     // 向下转型
            b.fun();         // 调用子类扩充方法
        }
    }
}
B 类方法
B 扩充的方法

3. 抽象类

利用抽象类可以明确定义子类需要覆写的方法,相当于在语法上对子类进行严格的定义限制,代码的开发会更加标准。

3.1 定义 抽象类 abstract

普通类可以直接产生实例化对象,并且在普通类中可以包含构造方法、普通方法、static 方法、常量、变量 的内容。抽象类是指在普通类的结构里面增加抽象方法的组成部分,抽象方法指的是没有方法体的方法,同时抽象方法还必须使用 abstract 关键字定义。拥有抽象方法的类一定属于抽象类,抽象类要使用 abstract 声明。
所有的普通方法上面都会有一个 { } ,来表示方法体,有方法体的方法一定可以被对象直接调用;抽象类中的抽象方法 没有方法体,声明中不需要加 { } ,但必须有abstract 声明。
抽象类只比普通类多了抽象方法的定义,其他结构与普通类完全一样。

abstract class A{        // 定义一个抽象类,使用abstract 声明
    public void fun(){   // 普通方法
        System.out.println("存在有方法体的方法");
    }
    public abstract void print();    // 抽象方法
}

抽象类不能直接进行对象实例化操作:A a = new A;// 错误的操作
原因: 当一个类的对象实例化后,意味着这个对象可以调用类中的属性或方法,但是抽象类里的抽象方法没有方法体,没有方法体的方法怎么可能被调用,所以不能直接实例化对象。

使用抽象类遵循的原则

① 抽象类必须有子类,即抽象类一定要被子类所继承,但是在Java中每个子类只能继承一个抽象类,具备但继承局限;
② 抽象类的子类(非抽象类)必须覆写抽象类中的全部抽象方法(强制子类覆写);抽象类继承子类里面会有明确的方法覆写要求。
③ 依靠对象的向上转型,可以通过抽象类的子类完成抽象类的实例化的对象操作。
注:虽然一个子类可以继承任意一个普通类,但从开发的实际要求来讲,普通类不要去继承另外一个普通类,而要继承抽象类:相比较开放的约定,开发者更愿意相信语法程度上给予的限定,很明显,强制子类去覆写父类的方法可以更好地进行操作上的统一。

/**
 * Created by Administrator on 2021/2/19.
 */
abstract class A{
    public void fun(){        // 普通方法
        System.out.println("A 类抽象父类");
    }
    public abstract void print();    // 抽象方法
}
class B extends A{               // 普通类
    public void print(){         // 强制覆写抽象类的方法
        System.out.println("B 类普通子类");
    }
}

public class class08 {
    public static void main(String[] args) {
       A a = new B();         // 向上转型
       a.print();
    }
}
B 类普通子类

3.2 抽象类的 限制 (6条)

① 抽象类里面可能会存在一些属性,那么在抽象类里一定会存在构造方法,目的是为属性初始化,并且子类对象实例化依然满足先执行父类构造再调用子类构造 的情况。
② 抽象类不能使用final定义,因为抽象类必须有子类,而final 定义的类不能有子类。
③ 抽象类可以没有任何的抽象方法,但只要是抽象类,就不能直接使用关键字new 实例化对象。

// 没有抽象方法的抽象类
abstract class A{          // 抽象类
    public void fun(){     // 普通方法
        System.out.println("A 类抽象父类");
    }
}
class B extends A{      // 抽象类 必须 有子类
}

public class class08 {
    public static void main(String[] args) {
       A a = new B();    // 向上转型,通过子类实例化抽象类对象
       a.fun();
    }
}

④ 抽象类中 可以定义抽象类,而实现的子类也可以根据需要选择是否定义内部类来继承抽象内部类。

abstract class A{                    // 定义一个抽象类
    abstract class B{                // 定义内部抽象类
        public abstract void print();  // 抽象方法
    }
}
class X extends A{                  // 继承static内部抽象类
    public void print(){     
        System.out.println("X 的类的普通方法");
    }
    class Y extends B{             // 定义内部抽象类的子类,此类表示必须编写
        public void print(){       // 方法覆写
        }
    }
}

⑤ 外部抽象类不允许使用 static 声明,而内部的抽象类则允许,使用static 声明的内部抽象类相当于是一个外部抽象类,继承的时候使用“外部类.内部类”的形式表示类名称。

/**
 * Created by Administrator on 2021/2/19.
 *               利用 static 定义的内部抽象类是外部抽象类
 */
abstract class A{                // 定义一个抽象类
    static abstract class B{     // static定义的内部类是外部类
        public abstract void print(); // 抽象方法
    }
}
class X extends A.B{               // 继承static的内部抽象类
    public void print(){
        System.out.println("X 类继承覆写A类中B类的抽象方法");
    }
}
public class class08 {
    public static void main(String[] args) {
       A.B ab = new X();           // 向上转型
       ab.print();
    }
}
   X 类继承覆写A类中B类的抽象方法

⑥ 在抽象类中,如果定义了static 属性或方法时,就可以在没有对象的时候直接调用。(由于 static 方法不受实例化对象的限制,所以可以由类名称调用)

abstract class A{
    public static void print(){
        System.out.println("A 类 static 方法");
    }
}
public class class08 {
    public static void main(String[] args) {
      A.print();
    }
}
A 类 static 方法

3.3 抽象类 案例—模块设计模式

抽象类最主要的特点是制约子类必须覆写的方法,同时抽象类也可以定义普通方法,这些普通方法可以直接调用类中的抽象方法,但是具体的抽象方法内容就必须由子类提供。

abstract class A{
    public void fun(){
        this.print();
    }
    public abstract void print();
}
class X extends A{
    public void print(){
        System.out.println("X 子类");
    }
}

public class class08 {
    public static void main(String[] args) {
      A a = new X();
      a.fun();
    }
}
X 子类
** fun 函数是动物的叫声,print方法是猫叫还是狗叫,而子类就是处理怎么叫,是触发操作。
eg:A指动物,a指猫,X指怎么叫 **

具体例子:
机器人(Robot):具备 充电(类似吃饭)、工作 两个基本操作
人类(Human):具备 吃饭、工作、睡觉 三个基本操作
猪(Pig):具备 吃饭、睡觉 两个基本操作

/**
 * Created by Administrator on 2021/2/19.
 */
abstract class Action{          // 定义一个抽象的行为类,行为不是具体的
    // 定义常量必须保证两个内容相加的结果不是其他行为
    public static final int EAT = 1;
    public static final int SLEEP = 5;  // 定义睡的命令
    public static final int WORK = 7;
    /**
     * 控制操作的行为,所以的行为都通过类中的常量描述
     * 或者进行命令的叠加使用,eg:边吃边工作:使用 EAT+WORK 描述
     * @param flag 操作的行为标记
     */
    public void command(int flag){
        switch(flag){      // switch 只支持数值判断,而if 只支持数值判断
            case EAT:       // 当前是吃的操作
                this.eat(); // 调用子类中具体“吃”的方法
                break;
            case SLEEP:
                this.sleep();
                break;
            case WORK:
                this.work();
                break;
            case EAT + WORK:   // 行为组合
                this.eat();
                this.work();
                break;
        }
    }
    public abstract void eat();  // 抽象方法,定义子类的操作标准
    public abstract void sleep();
    public abstract void work();
}
// 定义机器人行为类
class Robot extends Action{
    public void eat(){     // 覆写行为的操作
        System.out.println("机器人补充能量");
    }
    public void sleep(){    // 此操作不需要但必须覆写,抽象类的语法要求
    }                       // 子类必须覆写抽象父类的所有抽象方法
    public void work(){
        System.out.println("机器人在工作");
    }
}
// 定义人类行为类
class Human extends Action{
    public void eat(){
        System.out.println("人在吃饭");
    }
    public void sleep(){
        System.out.println("人在睡觉");
    }
    public void work(){
        System.out.println("人在工作");
    }
}
// 定义猪的行为类
class Pig extends Action{
    public void eat(){
        System.out.println("猪在吃食");
    }
    public void sleep(){
        System.out.println("猪在睡觉");
    }
    public void work(){           // 必须覆写该方法
    }
}

public class class08 {
    public static void main(String[] args) {
      fun(new Robot());           // 传递机器人行为子类
      fun(new Human());           // 传递人类行为子类
      fun(new Pig());             // 传递猪的行为子类
    }
    public static void fun(Action act){
        act.command(Action.EAT);   // 调用“吃”的操作
        act.command(Action.SLEEP);
        act.command(Action.WORK);
    }
}
机器人补充能量
机器人在工作
人在吃饭
人在睡觉
人在工作
猪在吃食
猪在睡觉

这些不同的类型最终都在行为上成功地进行了抽象,即如果要使用行为操作,就必须按照 Action 类的标准来实现子类。

4. 接口

抽象类可以实现对子类覆写方法的控制,但抽象类的子类具有单继承局限,为了打破局限,引入Java接口,接口还有着 将具体代码的实现细节对调用处进行隐藏的作用,也可以利用接口进行方法视图的描述。

  • 接口有三大主要功能:
  1. 表示不同层之间的操作标准 (最后的案例)
  2. 表示一种操作能力 (最后的案例)
  3. 将服务器的远程方法视图暴露给客户端,用在分布式开发

4.1 定义 接口 interface

如果一个类只是由抽象方法和全局常量组成,那么在这种情况下不会将其定义是抽象类,而是定义为接口,,所谓的接口严格讲是属于特殊类,即类里 只有抽象方法与全局常量。(在JDK1.8中接口可以定义更多操作,本节先不涉及)

interface A{                  // 定义接口
    public static final String MSM = "dadda"; // 全局常量
    public abstract void print();  // 抽象方法
}

对接口而言,由全局常量与抽象方法组成,接口里面只能使用一种访问权限即public,为了省事,接口也可以简化定义为:

interface A{                  // 定义接口
    String MSM = "dadda";    
    public void print(); //即使接口的方法没有public,访问权限也是public,为了准确定义最好写上。    
}

4.2 使用原则

因为接口存在抽象方法,所以不能使用new实例化操作。

  1. 接口必须有子类,此时一个子类可以使用 implements 关键字实现多个接口,避免但继承局限。
  2. 接口的子类(如果不是抽象类),必须覆写接口中的全部抽象方法。
  3. 接口的对象可以利用子类对象的向上转型进行实例化操作。
interface A{
    public static final String MSG = "加油";
    public abstract void print();
}
interface B{
    public abstract void get();
}
class X implements A,B{
    public void print(){
        System.out.println("覆写A接口的抽象方法");
    }
    public void get(){
        System.out.println("覆写B接口的抽象方法");
    }
}
public class class08 {
    public static void main(String[] args) {
     X x = new X();    // 实例化子类对象
     A a = x;           // 向上转型
     B b = x;           // 向上转型
     x.get();     // 子类对象调用本类方法
     a.print();         // 调用被覆写过的方法
     b.get();
     System.out.println(A.MSG); // 直接访问全局常量
    }
}
    覆写B接口的抽象方法
覆写A接口的抽象方法
覆写B接口的抽象方法
加油

① 如果一个子类既要继承抽象类,又要实现接口,应采用先继承后实现接口 的顺序完成:

interface A{
    public abstract void print();
}
interface B{
    public abstract void get();
}
abstract class C{
    public abstract void change();
}
class X extends C implements A,B{
    public void print(){
        System.out.println("A 接口的抽象方法");
    }
    public void get(){
        System.out.println("B 接口的抽象方法");
    }
    public void change(){
        System.out.println("C 抽象类的抽象方法");
    }
}

② 一个抽象类可以继承一个抽象类或(和)实现若干个接口,但是,一个接口不能继承抽象类,但是一个接口可以使用 extends 同时继承多个父接口。
:一个抽象类只能继承一个抽象类,而接口可以继承多个接口 ;
一个子类只能继承一个抽象类,却可以实现多个接口。

interface A{
    public abstract void print();
}
interface B{
    public abstract void get();
}
interface C extends A,B{
    public void funC();
}
class X implements C{
    public void print(){
        System.out.println("A 接口的抽象方法");
    }
    public void get(){
        System.out.println("B 接口的抽象方法");
    }
    public void funC(){
        System.out.println("C 接口的抽象方法");
    }
}

③ 可以在接口里定义抽象类。
(《第一行代码》p244)
④ 在一个接口内部若使用static 定义一个内部接口,该接口则表示一个外部接口。
(内部接口在 类集框架 知识点会用到)

interface A{
    public abstract void funA();
    static interface B{            // 外部接口
        public void funB();
    }
}
class X implements A.B{
    public void funB(){}
}

4.3 接口案例__标准

举例USB接口:

interface USB{               // 定义 USB 标准
    public void start();      // USB设备开始工作
    public void stop();       // USB设备停止工作
}
// 假设只要有设备插入计算机,就自动调用start、stop两个方法
class Computer{               // 定义计算机类
    public void plugin(USB usb){
        usb.start();
        usb.stop();
    }
}
// 定义 U 盘子类
class Flash implements USB{
    public void start(){
        System.out.println("U 盘开始使用");
    }
    public void stop(){
        System.out.println("U 盘停止使用");
    }
}
// 定义打印机
class Print implements USB{
    public void start(){
        System.out.println("打印机开始工作");
    }
    public void stop() {
        System.out.println("打印机停止工作");
    }
}

public class class08 {
    public static void main(String[] args) {
        Computer com = new Computer();  // 实例化计算机类
        com.plugin(new Flash());        // 插入USB接口设备
        com.plugin(new Print());        // 插入USB接口设备
    }  
}
U 盘开始使用
U 盘停止使用
打印机开始工作
打印机停止工作

4.4 接口案例__工厂设计模式(Factory)

工厂设计模式,是Java开发中使用最多的一种设计模式。
代码的编写风格是否良好,有两个标准:

  1. 客户端(主方法)调用简单,不需要关注具体的细节;
  2. 程序代码的修改,不影响客户端的调用。
    为什么称为工厂设计模式:
    为了使客户端只看见接口而看不见其子类,所以需要一个中间的工具类来取得接口对象,,这样客户端不再需要关心接口子类,只要通过类就可以取得接口对象。
    (同时也是spring开发框架的设计核心理念,解决这种代码耦合问题)
package package03;

/**
 * Created by Administrator on 2021/3/10.
 */

interface Fruit{
    public void eat();
}
class Apple implements Fruit{
    public void eat(){
        System.out.println("吃苹果");
    }
}
class Orange implements Fruit{
    public void eat(){
        System.out.println("吃橘子");
    }
}
// 增加一个工厂类进行过渡(回避关键字new在直接实例化接口上带来的问题)
class Factory{     // 定义工厂类,此类不提供属性
    /**
     * 取得指定类型的接口对象
     * @param className 要取得的类实例化对象标记
     * @param 如果指定标记存在,则Fruit接口的实例化对象,否则返回null
     */
    public static Fruit getInstance(String className){  //static作用是可以在没有对象的时候直接调用,即Factory.getInstance()
        if("apple".equals(className)){
            return new Apple();
        }else if("orange".equals(className)){
            return new Orange();
        }else{
            return null;
        }
    }
}

public class class09 {
    public static void main(String[] args){
        Fruit f = Factory.getInstance("orange");
        f.eat();
    }
}
吃橘子
本程序在客户端操作上取消关键字new的使用,而使用Factory.getInstance()方法根据指定子类
的标记取得接实例化对象,这时客户端不再需要关注具体子类,只需要关注如何取得接口对象并且操
作,,这样的设计在开发中称为工厂设计模式。

4.5 接口案例__代理设计模式(Proxy)

《第一行代码》p250

4.6 抽象类 与 接口 的区别

当抽象类和接口都可以使用时,优先考虑接口;
在进行某些公共操作时一定要定义出接口;
如果是自己写的接口,绝对不要使用关键字new直接实例化接口子类,应该使用工厂类完成。
在这里插入图片描述

5. Object 类

利用继承与对象多态性的概念可以解决子类对象与父类对象的自动转型操作,但是如果要想统一开发中的参数类型,就必须有一种类可以成为所有类的父类(可以接收全部类的对象,因为可以向上自动转型),这个类就是Object类。Object是所有类的父类,也就是说任何一个类在定义时如果没有明确地继承一个父类,那他就是Object类的子类:
class Book{ }
class Book extends Object{ }
这两种类定义的最终效果是完全相同的。
所以在设计代码时,如果不确定参数类型时,使用Object类是最好的选择。

案例__宠物商店

项目需求:实现一个宠物商店的模型,一个宠物商店可以保存多个宠物的信息(名字、年龄),可以实现宠物的上架、下架、模糊查询的功能。
《第一行代码》p264

6. 异常

异常 是程序中导致程序中断的一种指令流。为了让程序在出现异常后依然可以正常执行完毕,所以引入异常处理语句来完善代码编写。

6.1 关键字 try、catch、finally

异常格式的组合有3种:try…catch 、try…catch…finally 、try…finally
finally 往往在开发中进行一些资源释放操作的。

try {
     // 有可能出现异常的语句
     }[ catch(异常类型 对象){
      // 异常处理;
      }catch(异常类型 对象){
      // 异常处理;
      } ] [finally{
      ;不管是否出现异常,都执行统一代码
      } ]
public class class09 {
    public static void main(String[] args){
        System.out.println("1.除法计算开始");
        try{
            System.out.println("2.除法计算:"+(10 / 0)); //  产生异常
            System.out.println("异常产生,此句不会被运行");
        }catch(ArithmeticException e){   // 处理算术异常
            System.out.println("输出异常提醒");
        }
        System.out.println("3.除法计算结束");
    }
}
1.除法计算开始
输出异常提醒
3.除法计算结束

上面程序采用输出提示信息的方式进行处理的,但该方式不能明确描述异常类型,所以可以使用异常类提供的 printStackTrace() 方法进行异常信息的完整输出:

public class class09 {
    public static void main(String[] args){
        System.out.println("1.除法计算开始");
        try{
            System.out.println("2.除法计算:"+(10 / 0)); //  产生异常
            System.out.println("异常产生,此句不会被运行");
        }catch(ArithmeticException e){   // 处理算术异常
             // System.out.println("输出异常提醒");
            e.printStackTrace();             // 增加该方法方便代码调试
        }finally{
            System.out.println("不管是否有异常都执行该语句");
        }
        System.out.println("3.除法计算结束");
    }
}
1.除法计算开始
java.lang.ArithmeticException: / by zero
	at package03.class09.main(class09.java:5)
不管是否有异常都执行该语句
3.除法计算结束
Exception异常总类、printStackTrace()输出异常信息

所有的异常类型最高的继承类是 Throwable ,并且在 Throwable 下有两个子类:
Error:指的是JVM 错误,此时程序没有运行,无法处理;
Exception:指的是程序运行中产生异常,用户可以使用异常处理格式进行处理。
加入多个catch进行异常处理 + 父类总异常Exception处理

public class class09 {
    public static void main(String[] args){
        System.out.println("1.除法计算开始");
        try {
            int x = Integer.parseInt(args[0]);// 将括号里的内容转换为整形
            int y = Integer.parseInt(args[1]);
            System.out.println("2.除法计算" + (x / y));// 此处产生异常
            System.out.println("异常产生,此句不会被运行");
        }/*catch(ArrayIndexOutOfBoundsException e){// 处理参数不足异常
            e.printStackTrace();
        }catch(NumberFormatException e){// 处理数字转换异常
            e.printStackTrace();
        }catch(ArithmeticException e){// 处理算术异常
            e.printStackTrace(); // 输出异常的完整信息
        }*/catch(Exception e){
            e.printStackTrace();
        }finally{
            System.out.println("不管是否有异常都执行该语句");
        }
        System.out.println("3.除法计算结束");
    }
}
java.lang.ArrayIndexOutOfBoundsException: 0
1.除法计算开始
	at package03.class09.main(class09.java:11)
不管是否有异常都执行该语句
3.除法计算结束

以上程序的异常统一使用Exception进行处理,这样不管程序中出现何种异常问题,程序都可以捕获并处理。
处理多个异常时,捕获范围小的异常要放在捕获异常范围大的异常之前处理。
异常处理流程:在这里插入图片描述

6.2 关键字 throws(在调用处处理)

throws 关键字主要在方法定义上使用,表示此方法中不进行异常的处理,而是交给被调用处处理。(调用处可能是主函数也可能是其他函数),所以在调用此方法时必须明确处理可能会出现的异常。(抛出异常必须处理),,
主方法也可以使用throws抛出,此时表示主方法里面可以不用强制性地进行异常处理,如果出现了异常,将交给JVM进行默认处理,此时会导致程序中断执行。但是从实际开发讲,主方法上不建议使用throws,因为如果程序出现错误,也希望其可以正常结束调用。

class MyMath{
    public static int div(int x,int y)throws Exception{// 此方法不处理异常
        return x/y;
    }
}
public class class09 {
    public static void main(String[] args){
        try{  // div()方法抛出异常,必须明确进行异常处理
            System.out.println(MyMath.div(10,2));
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
5

6.3 关键字 throw

之前的所有异常类对象都是由JVM自动进行实例化操作的,而现在用户也可以手工抛出一个实例化对象(手工调用异常类的构造方法),就需要通过throw完成。

public class class09 {
    public static void main(String[] args){
        try{
            throw new Exception("自己定义的异常");
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
java.lang.Exception: 自己定义的异常
	at package03.class09.main(class09.java:12)

6.4 assert: 断言 (-ea 参数)

6.5 自定义异常

若想实现自定义异常,只需要继承Exception(强制性异常处理)或RuntimeException(选择性异常处理)父类即可。

package package03;

/**
 * Created by Administrator on 2021/3/10.
 */

class AddException extends Exception{
    public AddException(String msg){
        super(msg);
    }
}

public class class09 {
    public static void main(String[] args){
        int num = 20;
        try{
            if(num > 10){
                throw new AddException("数值传递过大");
            }
            }catch(Exception e){
            e.printStace();
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Spring Boot 中,可以通过实现 `ApplicationRunner` 接口,在应用程序启动时预热 DynamicServerListLoadBalancer。 以下是一个简单的示例代码: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.stereotype.Component; import java.util.List; @Component public class LoadBalancerPreheat implements ApplicationRunner { @Autowired private DiscoveryClient discoveryClient; @Autowired private SpringClientFactory springClientFactory; @Override public void run(ApplicationArguments args) throws Exception { // 获取服务列表 List<ServiceInstance> instances = discoveryClient.getInstances("your-service-name"); // 获取 RibbonLoadBalancerClient 实例 RibbonLoadBalancerClient loadBalancerClient = springClientFactory.getLoadBalancerClient("your-service-name"); // 更新服务列表 loadBalancerClient.updateListOfServers(instances); // 等待一段时间,以确保服务列表已经更新 Thread.sleep(5000); } } ``` 在这个示例中,`LoadBalancerPreheat` 类实现了 `ApplicationRunner` 接口,重写了 `run` 方法。在 `run` 方法中,首先通过 `DiscoveryClient` 获取指定服务的实例列表,然后通过 `SpringClientFactory` 获取 `RibbonLoadBalancerClient` 实例,并使用 `updateListOfServers` 方法更新服务列表。最后,等待一段时间,以确保服务列表已经更新。 需要注意的是,服务列表可能会发生变化,因此需要定期更新服务列表。可以使用定时任务或者监听服务注册中心的事件来实现服务列表的自动更新。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值