【3-4】《Java面向对象高级知识》——继承、重写和重载、final关键字、抽象类、接口、多态、Object类、内部类、包装类、可变参数、递归

  • Post author:
  • Post category:java




一、继承格式

非常重要的部分!!!

  • 格式:先描述父类,再描述子类,子类通过extends继承父类。

    class Person() {
        
    }
    class Student() extends Person {
        
    }
    

继承了父类的属性和方法,自己也可以拥有属性和方法。



二、子类实例化内存分析

继承的概念:继承是子类继承父类的特征和行为(属性和方法),使得子类对象(实例)具有父类的实例域和方法,使子类具有父类相同的行为。

  • 继承的规则:

    Java中只有单继承和多重继承,没有多继承

  • 生活例子理解:

    • 单继承:一个孩子有一个父亲,继承了父亲的行为和特征;
    • 多重继承:一个孩子有一个父亲,他的父亲还有一个父亲,孩子继承了爷爷的行为和特征;
    • 多继承:一个孩子有多个父亲(错误示范),继承哪个父亲的行为和特征?继承会发生错乱。

注意:

一个子类只能有一个父类

  • 子类和父类的创建过程:当我们创建子类对象时,内部会首先创建父类对象,父类对象创建完毕之后才会创建子类对象,并且父类对象会作为子类对象中的super存在。
  • 简单来说就是子类拥有了一个父类的内存地址,两个类互相持有对象名称,可以操作父类中public和protected修饰的属性和方法,私有的无法直接操作,可以使用get、set方法获取和设置。



三、super详解

通过supper操作内存中的父对象,创建子对象时自动创建父对象。通过supper可以访问:

  • 父类构造方法

    • 调用

      super构造方法

      的代码,必须写在子类构造方法的第一行。
  • 父类属性
  • 父类的方法

默认通过无参构造方法来访问,如果父类没有无参构造方法,那子类继承时必须明确写出super来进行调用。



四、重写



1、重写的概念

方法的重写,在类的继承中发生。当继承的某方法不适用子类执行程序时,就把该方法重写一下,当程序执行时,就运行的是重写的这段逻辑。


重写(override)规则:


  1. 参数列表:必须完全与被重写方法的相同

    class Person {
        public void say() {
            System.out.println("父类中的内容");
        }
    }
    class Student extends Person {
        public void say() {
            System.out.println("子类中的内容");
        }
    }
    

    此时在main方法中调用say()方法:

    public class Test {
        public static void main(String[] args) {
            Student s  = new Student();
            s.say();
        }
    }
    

    输出结果为:

    在这里插入图片描述

  • 子类父类中存在同样的方法,运行的是子类,这种情况就是重写。

    参数列表完全一样


  1. 返回类型:必须完全与被重写方法相同


  2. 访问权限:子类不能比父类权限更低

    子>=父的访问权限


  3. 父类的成员方法只能被它的子类重写

    必须存在继承关系


  4. 声明为static和private的方法不能被重写,但是能再次声明

    静态的跟对象没有关系,本身不存在继承,可以再次声明。

    私有的方法不能重写,不能继承更不能重写。

  • 作用:设计了一个父类,包含了程序的执行流程,但另一个类对服务器进行的指令不同,此时这个类就需要继承父类,并重写子类中的方法。



2、重写和重载的区别


  • 面试题:Java中重写(override)和重载(overload)的区别?


    1. 发生的位置

      重载:一个类中

      重写:子父类中


    2. 参数列表限制

      重载:必须不同

      重写:必须相同


    3. 返回值类型

      重载:与返回值类型无关

      重写:返回值类型必须一致


    4. 访问权限

      重载:与访问权限无关

      重写:子的访问权限必须不小于父的访问权限


    5. 异常处理

      重载:与异常无关

      重写:异常范围可以更小,但是不能抛出新的异常



五、final关键字

  • final用于修饰属性、变量

    • 变量成为了常量,无法对其再次进行赋值。

      • final修饰的局部变量只能赋值一次(可以先声明后赋值)

      • final修饰的是成员属性,必须在声明时赋值

      • 全局常量(public static final)

        任何位置都可以通过类名直接访问的常量。


        常量的命名规范:

        • 由1个或多个单词组成,单词与单词之间用_隔开,单词中所有字母大写
        • 例如:SQL_INSERT
  • final用于修饰类

    • final修饰的类,不可以被继承。
  • final用于修饰方法

    • final修饰的方法,不能被子类重写。



六、抽象类



1、抽象类的概念

一个类中有一个抽象方法(不确定的方法),就称其类为抽象类。

  • 抽象类格式:不写修饰符默认是public,default效果不存在。

    abstract class 类名{  
        // 抽象类
    }
    
  • 抽象方法格式:abstract修饰且没有方法体,就是抽象方法。

    public abstract void 方法名();//抽象方法,只声明而未实现
    
  • 编码规范:一个.java文件只包含一个类,且类必须通过public修饰。


  • 子类继承抽象父类

    1. 将子类变成抽象类

      public abstract class Student extends Person {
          
      }
      
    2. 给抽象的、不确定的方法确定下来,给其写具体的执行逻辑

      • 自己编写继承

      • new的时候直接指定superClass,自动生成重写方法

        @override
        public class Student extends Person {
            public void say() {
                //如何执行
            }
        }
        
  • 抽象类也可以包含不抽象的部分:

    假设我们要做一个仓库进销存系统,描述如何获取存储数据及数据显示时的读取,问题是不确定采用什么存储方式,为不能耽误开发进度,将不确定的先编写抽象部分,将确定的先实现。

    (后续会学到接口,抽象类就较少使用了,接口比抽象类更抽象。)



2、抽象类常见问题

  1. 抽象类不能直接创建对象(不能被实例化)


  2. 抽象类不能使用final声明

    final修饰的属性不能被继承,final修饰的方法不能被重写,抽象类不被继承就没有意义,所以直接不能使用final,使用final会报错。

  3. 抽象类能否有构造方法?


    能有构造方法

    ,抽象类只是我们不能通过new来创建对象,但是Java虚拟机会自动创建super构造方法。



3、抽象类和普通类的区别

  1. 抽象类必须要使用public(公共的)或protected(受保护的)修饰,不能用private修饰的原因是私有后子类无法继承。
  2. 抽象类不能通过new对象被实例化,但在子类创建对象时,抽象父类会被JVM实例化。
  3. 如果子类继承了抽象父类,在子类中必须实现父类中所有的抽象方法,除非子类是抽象类。



七、接口

接口中只能存在

抽象方法



全局常量

  1. 接口的语法格式:

    interface 接口名称{
        全局常量;
        抽象方法;
    }
    


接口必须得依赖于一个子,也就是实现类。

没有子类就无意义。子类必须全部实现接口中的抽象方法,除非这个子类是抽象类。

  1. 实现类语法格式:可以多实现

    class 子类 implements 父接口1,父接口2...{
    } 
    
  2. 接口允许多继承:

     interface C extends A, B{
     }
    



1、面向接口编程思想

调用接口,定义和实现分离,又称名实分离的原则。就是先定好规范,把程序先设计好,后续按照这个规范对其功能进行实现。某一处出现bug只需要在某一处查找,将那一处进行修改就好了。


优点

  1. 降低程序的耦合性
  2. 易于程序的扩展
  3. 有利于程序的维护


建议写程序时先写接口。



2、接口的编写

  • 接口:

    public interface Person {
        int a = 10;//默认是全局常量,省略了public static final
        void say();//默认为抽象方法,省略了abstract,不能加方法体,否则会报错
    }
    
  • 实现类:

    public class Student implements Person {
        @override
        public void say() {
            System.out.println("显示内容");
        }
    }
    
  • 测试类:

    public calss Test {
        public static void main(String[] args) {
            Student s = new Student();
            s.say();//通过学生调用say方法
        }
    }
    



3、接口和抽象类的区别

  1. 抽象类要被子类

    继承

    ,接口要被类

    实现

  2. 接口

    只能声明抽象方法

    ,抽象类中

    可以声明抽象方法,也可以写非抽象方法

  3. 接口里定义的变量

    只能是公共的静态的常量

    ,抽象类中的变量是

    普通变量

  4. 抽象类使用

    继承

    来使用,

    无法多继承

    。 接口使用

    实现

    来使用, 可以

    多实现
  5. 抽象类中可以包含

    static方法

    ,但是

    接口中不允许

    (静态方法不能被子类重写,因此接口中不能声明静态方法)
  6. 接口不能有

    构造方法

    ,但是抽象类可以有



八、多态

对象的多种形态,多种表现形式。利于我们更好地设计程序。

  • 向上转型格式

    父类 父类对象 = 子类实例 ;

  • 向下转型格式:父类要转子类,需要加子类类型进行强转。

    子类 子类对象 = (子类)父类实例 ;



1、多态的体现

对象的多态性:子类是父类的一种形态

  1. 学生对象被称作

    学生

    ,这个

    学生

    是人的一种形态。

    Preson p = null;
    Student s = new Student();
    p = s;
    
  • 此时,虽然p是Person类型,但具体创建的实例对象是Student,指向的内容是s中的,调用的方法也是s中的方法。

    • 总结:父类引用指向子类对象

      理解:s是p的一种形态
  1. 学生对象被称作



    ,这个



    是学生的一种形态。

    护士对象被称作



    ,这个



    不是学生的一种形态。


有这样一段代码:

 Student a = new Student();
 Nurse b = new Nurse();
  • 例1:

    Person p1 = a;
    Person p2 = b;
    
    • 此时p1可以正常调用Student类中的方法;p2可以调用Nurse类中方法。
  • 但是,例2,

    错误示范

    Student a2 = (Student)p1;
    Student a3 = (Student)p2;
    
    • 此时,a2调用方法时没有问题,但是a3调用时就会报错,提示护士类无法转为学生类。

    理解:学生转为学生类没问题,而护士不能转为学生类。

ps: 方法的重载和重写也是多态的一种,不过是方法的多态(相同方法名的多种形态)。

重载:一个类中方法的多态性体现;

重写: 子父类中方法的多态性体现。



2、instanceof

如上错误示范,为避免将护士类转为学生类这种错误发生,可以使用instanceof判断传入类型是哪个子类对象。

  • 语法格式:

    实例化对象 instanceof//此操作返回boolean类型的数据
    



九、Object类概述

如果没有明确声明一个类的父类,那么此时Object就是这个类的父类,默认省略了extends Object。

Java中所有内容都是Object类的对象。


Object的多态

使用Object可以接收任意的引用数据类型。Object本身包含了许多实用方法。可以下个JDK 11 API查看对应类里的方法。

在这里插入图片描述



1、toString

Object的toString方法, 返回对象的内存地址。

返回对象的字符串表示形式,建议所有子类都重写toString方法,便于阅读。



2、equals

==比较对象的内存地址,内存地址相同,比较结果为true。

equals比较对象内容,内容相等结果为true,与地址无关。

  • 建议

    所有子类都重写equals方法

    ,重写时有五个特性:

    1. 自反性:自己与自己相比,结果为true
    2. 对称性:比较对象顺序可以颠倒
    3. 传递性:一个对象与两个对象比较结果为true,那两个对象比较结果也为true
    4. 一致性:比较时对象内容无法修改
    5. 非空性:比较对象与空值相比结果返回false
  • 总结:符合常理。

    public boolean equals(Object o) {
    	if (this == o) {
            return true;
        }
        if (o == null) {
            return false;
        }
        if (o instanceof Person) {
            Person p2 = (Person)o;
            //比较操作
        } 
        return false;
    }
    



十、内部类概述

概念较好理解,但是实际操作中较难应用,使用不多,理解即可。

在一个类中又定义了一个类,在里面的这个类就称作内部类。

分类:

  1. 成员内部类
  2. 局部内部类
  3. 匿名内部类(局部内部类的一种)
  4. 静态内部类



1、成员内部类(了解)

在一个类中像写属性一样再写一个类,

外部需要创建对象才可以使用

  • 语法格式:

    class Outer {
        private double x = 0;
        public Outer(double x) {
            this.x = x;
        }
        class Inner {
            //内部类
            public void say() {
                System.out.println("x="+x);
            }
        }
    }
    


成员内部类可以无条件使用外部类中的成员属性和方法

,不过出现同名的成员变量或者方法时,会发生隐藏现象,会访问最近的,即默认情况下访问的是成员内部类的成员。

  • 如果要访问外部类的同名成员,可以通过:

    外部类.this.成员变量

    外部类.this.成员方法

    Outter outter = new Outter();
    Outter.Inner inner = outter.new Inner(); 
    



2、局部内部类

定义在一个方法、作用域里面的类,就像一个局部变量,

不能使用public等权限修饰符进行修饰

class Person{
    public Person() {

    }
}
class Man{
    public Man(){

    }

    public People getPerson(){
        class Student extends People{
            //局部内部类
            int age =0;
        }
        return new Student();
    } 
}



3、匿名内部类

只使用一次的内部类。

  • 格式比较奇怪,没名字,直接用new就行:

    new 父类构造器(参数列表)|实现接口(){
        //匿名内部类的类体部分
    }
    
  • 使用匿名内部类的限制:

    1. 必须继承一个类

      或者

      实现一个接口。也只能继承一个类或实现一个接口。
    2. 匿名内部类中是

      不能定义构造函数

      的。
    3. 匿名内部类中

      不能存在任何的静态成员变量和静态方法

    4. 匿名内部类为

      局部内部类

      ,所以局部内部类的所有限制同样对匿名内部类生效。

    5. 匿名内部类不能是抽象的

      ,它必须要实现继承的类或者实现的接口的所有抽象方法。

    6. 只能访问final型的局部变量

      • 所有的局部内部类都只能访问final型的局部变量
      • 内部类会单独编译成一个字节码文件,为保证单独文件中用到的变量a与外部值绝对是一致的,系统从规则限制这个值不能更改



4、静态内部类

与普通成员内部类很像,只是加了一个static修饰。

通过类名.操作

public class Test {
    public static void main(String[] args)  {
        Outter.Inner inner = new Outter.Inner();
    }
}
 
class Outter {
    public Outter() {
    }
    static class Inner {
        public Inner() {
        }
    }
}


强调:内部类应用非常少,不是重点!



十一、包装类

Java为8种基本数据类型都提供了包装类。利于基本数据类型和引用数据类型之间的转换。



1、8种基本数据类型

序号 基本数据类型 包装类
1 int Integer
2 char Character
3 float Float
4 double Double
5 boolean Boolean
6 byte Byte
7 short Short
8 long Long

这些类在类型上分为两大类:


  • Number:Integer、Short、Long、Double、Float、Byte都是Number的子类,表示是一个数字。
  • Object:Character、Boolean都是Object的直接子类。



2、装箱和拆箱操作

  • 装箱:从基本数据类型变成包装类
  • 拆箱:从包装类变为基本数据类型


    Number类种定义了许多拆箱操作的方法:
序号 方法 描述
1 public byte byteValue() 用于Byte->byte
2 public abstract double doubleValue() 用于Double->double
3 public abstract float floatValue() 用于Float->float
4 public abstract int intValue() 用于Integer->int
5 public abstract long longValue() 用于Long->long
6 public short shortValue() 用于Short->short
//手动装箱
Integer i = new Integer(200);
//手动拆箱
int a = i.intValue();

  • 在JDK1.4之前,还是手动装箱和手动拆箱。
  • 在JDK1.5,Java新增了自动装箱和自动拆箱,而且可以直接通过包装类进行四则运算和自增自建操作。例如:
Float f = 10.3f ;   // 自动装箱
float x = f ;       // 自动拆箱
System.out.println(f * f) ;     // 直接利用包装类完成
System.out.println(x * x) ;     // 直接利用包装类完成 



3、字符串转换

使用包装类可以将一个字符串变为指定的基本数据类型,此点一般在接收输入数据上使用较多。

  • parseInt(String s) :将String变为int型数据
  • parseFloat(String s):将String变为Float
  • parseBoolean(String s) :将String变为boolean



十二、可变参数

方法的重载。当不确定传入的参数有几个,在JDK1.5之后,有一个可变参数的功能,可以根据需要自动传入任意个数的参数。

语法格式:

返回值类型 方法名称(数据类型… 参数名称) {
    //参数在方法内部 , 以数组的形式来接收
}

  • 注意

    :可变参数只能出现在参数列表的最后。
public static int sum(int... nums) {
    int n = 0;
    for (int i = 0; i < nums.length; i++) {
        n += nums[i];
    }
    return n;
}
  • int…nums:表示可变参数,调用时能传入0~n个参数。方法内部可变参数以数组体现。



十三、递归

递归,在数学与计算机科学中,是指

在方法的定义中使用方法自身

。也就是说,递归算法是一种

直接或者间接调用自身方法

的算法。

递归流程图如下:递归实现五的阶乘:public static int

fact(int n) {
    if (n == 1) {
        return 1;
    } else {
       return n * fact(n - 1);
    }
}

能用循环的不用递归,效率太低。



总结

Java是一门纯面向对象的编程语言,万物皆可对象。这是一个十分重要的知识点,所以分为了三个章节——基础、进阶、高级,这是最后一章节,一定要多多复习。



版权声明:本文为qq_41306364原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。