coding……
但行好事 莫问前程

Java编程拾遗『对象和类』

要讲Java中对象和类,Java面向对象的特性是不可避免的,Java中的对象和类其实就来自面向对象的编程思想。在之前的文章Java编程拾遗『Java概述』中,简述了Java面向对象的特性,本篇文章将重新介绍一下Java面向对象的编程思想及Java中对象和类的一些概念和使用。

1. 面向对象思想

1.1 面向对象 VS 面向过程

面向对象是一种编程思想,是当今软件开发方法中的主流方法之一,它把数据及对数据的操作方法整合在一起,作为一个相互依存的整体,即对象。对同类食物抽象出其共性,即类,类中的大多数数据,只能被本类的方法进行处理,类通过外部接口和外界发生关系。比如,站在抽象的角度,人具有身高、年龄、体重、血型等特征,同时具有会劳动、会直立行走、会吃饭等这些方法,人仅仅是一个抽象的概念,并不是一个存在的实体,所有具备人这个群体的属性与方法的对象都可以叫人。这个对象是实际存在的实体,每个人实体都是人这个群体的一个对象。

面向过程是一种一时间为中心的开发方法,就是自上到下的顺序执行,逐步求精,其程序结构是按照功能划分成若干个基本模块,这些基本模块形成一个属性结构,各模块之间的关系也非常简单,在功能上相对独立,每一个模块内部一般都是由顺序、选择和循环三种基本结构组成。面向过程的模块化是由子程序实现的,程序流程在写程序时就已经决定了。以五子棋为例,面向过程的设计思路就是首先分析问题的步骤:第一步,开始游戏。第二步,黑子先走。第三步,绘制画面。第四步,判断输赢。第五步,白子走棋。第六步,绘制画面。第七步,判断输赢。第八步,返回第二步。第九步,输出最后结果。如果把上面每个步骤都分别用一个函数来实现,就是一个面向过程的开发方法。

1.2 面向对象和面向过程的不同点

  1. 出发点不同。面向对象的方法符合常规的思维方式,强调把问题域的要领直接映射到对象和对象之间的接口上。面向过程则强调过程的抽象化和模块化,它是以过程为中心处理问题的。
  2. 层次逻辑不同。面向对象的方法是用计算机逻辑来模拟客观世界的物理存在,以对象的集合类作为处理问题的基本单位,尽可能的使计算机世界想客观世界靠拢,以使问题的处理更加清晰直接,还可以使用类的层次结构来体现类之间的继承和发展。面向过程处理问题的基本单位是能清晰准确表达过程的模块,用模块的层次结构概括模块或者模块间的关系和功能,把客观世界的问题抽象成计算机可以处理的过程。
  3. 数据处理方式与控制程序方式不同。面向对象方法将数据与对应的代码封装成一个整体,原则上其他对象不能直接修改其数据,即对象的修改只能由自身的成员函数来完成,控制程序方式上通过事件驱动来激活和运行程序。面向过程方法是直接通过程序来处理数据,处理完毕后即可显示处理结果,在控制程序方式上是按照设计调用或返回程序,不能自由导航,各模块之间存在着控制与被控制、调用与被调用的关系。
  4. 分析设计和编码的方式不同。面向对象方法贯穿于软件生命周期的分析、设计及编码中,是一种平滑过程,从分析到设计再到编码采用一致性模型表示,即实现的是一种无缝连接。面向过程方法强调分析、设计及编码之间按照规则进行转换,贯穿于软件生命周期的分析、设计及编码中,实现的是一种有缝连接。

1.3 面向对象的特征

面向对象主要包括抽象、继承、封装、多态四个特征,这里简单讲一下。

  1. 抽象。抽象是指目标性地忽略一些无关地方面,以便更方便地突出与目标有关地方面。上节讲述的人就是这样一个抽象概念,比如我们比较关注人的身高体重之类的特性,就可以将其抽象为属性,对于非通用的一些特性,比如打篮球,就不会对该行为进行抽象(但是假如抽象为篮球运动员,这种行为必然是通用的,需要进行抽象)。
  2. 继承。继承是一种联结类的而层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类中继承方法和实例变量,并且派生类可以修改或者增加新的方法使之更适合特殊的需求。
  3. 封装。封装是指将客观事物抽象成类,每个类对于自身的数据和方法进行保护。类可以把自己的数据和方法只让自己新人的类或者对象操作,对不可信的类或者对象进行隐藏。
  4. 多态。多态是指允许不同类的对象对同一消息作出响应,同一操作作用于不同的对象时,会有不同的语义,从而产生不同的结果。比如,存在乐器这一基类,钢琴和小提琴都是其派生类,对于同一操作播放,钢琴对象播放的是钢琴曲,而小提琴对象播放的是小提琴曲。

1.4 面向对象的优点

  1. 提高软件开发效率。面向对象的开发方式,可以将现实事物进行抽象,进而把现实事物直接映射为开发对象,与人类的思维方式非常相似。同时,面向对象的开发方式可以通过继承或者组合的方式来复用代码,可以大大提高开发效率。
  2. 保证软件的健壮性。由于面向对象的开发方式可以提高代码的重用性,在开发过程中可以很便捷地重用已有并且在相关领域经过长期测试地代码(各种现有的轮子),可以打打提高软件的健壮性。
  3. 保证软件的高可维护性。面向对象的开发方式,使得代码的可读性非常好,同时也使代码的结构更加清晰明了。同时面向对象的开发方式,有很多成熟的设计模式,这些设计模式可以使程序在面对需求变更时,只需要修改部分模块就可以满足需求,维护起来非常方便。

2. 类

上面讲述了面向对象开发思想的一些概念,Java中面向对象实现的载体就是类,类其实就是对于现实事物抽象的封装,并且Java语言赋予了类继承、多态等面向对象的特性。Java中类的基本格式:

[类定义修饰符] class <类名> {
	[类变量声明]
	[成员变量声明]
	[构造函数]
	[类方法]
	[成员方法]
	[成员变量的get/set方法]
}
  • 类定义修饰符:类定义修饰符定义了类的访问权限,可以省略,也可以是public, protected, private, static, final,其中public、protected , private三个最多只能出现其中之一,static和final可一起出现。
    • public:公有权限,该类在任何地方都可访问。
    • protected:受保护权限,只允许修饰内部类,表示在子类和本包中可见
    • private:私有权限,只允许修饰内部类,表示只在本类中可见
    • 缺省:包访问权限,同一package可用
    • static:静态类,如果一个类要被声明为static的,只有一种情况,就是静态内部类。如果在外部类声明为static,程序会编译都不会过。
    • final:修饰class时,表示类为不可变类,类的对象一旦被创建就不能被修改了,比如String类。
  • 类名:类名使用UpperCamelCase风格。
  • 类变量:static修饰的变量,变量为类持有,所有对象共享该变量。
  • 实例变量:非static修饰的变量,变量为对象持有,每个对象持有一个该成员变量。
  • 构造函数:构造对象实例的方法。
  • 类方法:static修饰的方法,也叫静态方法。属于整个类的,不是属于某个实例的,只能处理static域或调用static方法。
  • 实例方法:非static修饰的方法,属于对象的方法,由对象来调用。

2.1 类定义修饰符

上面讲过public, protected, private, static, final都可以用来修饰class,其中public、protected , private三个最多只能出现其中之一,static和final可一起出现,下面分别讲一下这几种修饰符。

2.1.1 public

public权限表示共有权限,时日常开发中最常见也是最简单的一种权限。因为时公有权限,所以在任何地方使用都不受限制,这里不多解释了。

2.1.2 protected

受保护权限,在修饰class时只允许修饰内部类,表示在子类和本包中可见。这里要注意的时,不是本包下,protected内部类不可见,即使是子类也不行。

package com.zhuoli.service.thinking.java.clazz;

@Getter
@Setter
public class ProtectedClass {
    private String name;

    private Integer age;

    protected void print() {
        System.out.println("protected method");
    }

    @Getter
    @Setter
    protected class InnerProtectedClass {
        private String innerName;
    }

    @Test
    public void test() {
        //protected内部类在本类中可见
        InnerProtectedClass innerProtectedClass = new InnerProtectedClass();
        innerProtectedClass.setInnerName("k8s");
        System.out.println(innerProtectedClass.getInnerName());
    }
}
package com.zhuoli.service.thinking.java.clazz.other;

public class ProtectedClassTest3Sub extends ProtectedClass {

    @Test
    public void test(){
        //父类protected方法子类可见,无论子类是不是与父类在同一个包下
        print();

        //子类与父类不在同一个包下,子类实例可以访问其从基类继承而来的protected方法
        ProtectedClassTest3Sub protectedClassTest3Sub = new ProtectedClassTest3Sub();
        protectedClassTest3Sub.print();

        //子类与父类不在同一个包下,则父类实例不能访问父类的protected方法,以下编译报错
        ProtectedClass protectedClass = new ProtectedClass();
        protectedClass.print();

        //注意protected内部类的继承规则跟protected成员方法略有不同,protected内部类严格遵守同包和子类可见条件,非同包即使时子类也不可见,以下报错
        InnerProtectedClass innerProtectedClass1 = new InnerProtectedClass();

        //这种方式也不行,因为不同包
        ProtectedClassTest3Sub protectedClassTest3Sub1 = new ProtectedClassTest3Sub();
        ProtectedClassTest3Sub.InnerProtectedClass innerProtectedClass2 = protectedClassTest3Sub1.new InnerProtectedClass();
    }
}

以上可以看出,父类protected内部类,访问权限是本包、子类,但是子类必须与父类在同一个包下,否则也是不可见的。而对于父类protected成员,访问权限也是本包及子类,但是不要求子类必须与父类在同一包下,若子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法,基类实例不能访问父类的protected方法

2.1.3 private

跟protected一样,在修饰class时,只允许修饰内部类,表示只在本类中可见。对于可见性没什么好讲的,只允许在本类中出现。这里借助private内部类讲一下使用内部类使用的一个好处——实现多重继承。在Java中是不允许多重继承的,但是可以使用内部类变相实现多重继承。

public class Father {
    public int strong(){
        return 9;
    }
}

public class Mother {
    public int kind(){
        return 8;
    }
}

如上存在两个类Father和Mother类,对于一个子类Son,如果想同时继承这两个类,显然是不可以的,但是可以通过内部类实现一种折中的多重继承,如下:

public class Son {

    private class Father_1 extends Father{
        public int strong(){
            return super.strong() + 1;
        }
    }

    private class Mother_1 extends  Mother{
        public int kind(){
            return super.kind() - 2;
        }
    }

    public int getStrong(){
        return new Father_1().strong();
    }

    public int getKind(){
        return new Mother_1().kind();
    }

    public static void main(String[] args) {
        Son son = new Son();
        System.out.println("Son's Strong:" + son.getStrong());
        System.out.println("Son's kind:" + son.getKind());
    }
}

这一点在《Thinking in Java》中有讲到,有兴趣的同学可以取了解一下。

2.1.4 缺省

缺省表明权限是包访问权限,只在本包内可访问。

package com.zhuoli.service.thinking.java.clazz.default0;

@Getter
@Setter
class DefaultClass {
    private String name;

    private Integer age;

    void print() {
        System.out.println("protected method");
    }

    @Getter
    @Setter
    class InnerDefaultClass {
        private String innerName;
    }
}
package com.zhuoli.service.thinking.java.clazz.default0;

public class DefaultClassTest {
    DefaultClass defaultClass = new DefaultClass();
    //同包中可见
    private DefaultClass.InnerDefaultClass innerDefaultClass = defaultClass.new InnerDefaultClass();

    @Test
    public void test(){
        innerDefaultClass.setInnerName("inner class");
        System.out.println(innerDefaultClass.getInnerName());

        //包访问权限,父类实例可以在同包下调用自己的protected方法
        defaultClass.print();
    }
}
package com.zhuoli.service.thinking.java.clazz.default0.other;

public class OtherPackageTest {

    @Test
    public void test() {
        /*DefaultClass是包访问权限,在不同包下不可见,以下编译报错*/
        DefaultClass defaultClass = new DefaultClass();
    }
}

本包外,DefaultClass都是不可见的。其实Java看以上三种访问权限可以发现,public和private最干净,一个是不受限,另一个只在本类中可见。protected在修饰类时,只能修饰内部类,表明类权限是子类可见及同包可见,通过上述示例代码可以发现,protected内部类的可见性其实就是在本包内(子类可见的前提也是子类父类在同一个包下)类定义修饰符缺省的情况下,类只能在本包中可见。其实通常讲public、protected、default、private修饰权限其实都是在讲修饰成员变量时,成员变量的可见性,权限如下表:

当前类 同一package 子类 其它package
public
protected ×
default × ×
private × × ×

对于protected权限,这里单独讲一下其他package的说明,如果子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法,父类实例不能访问其protected方法,也就是说其它package不可见不是绝对的。对于protected访问权限,可以参考这篇文章,讲的很清楚。

2.1.5 static

在Java世界里,经常被提到静态这个概念,static作为静态成员变量和成员函数的修饰符,意味着它为该类的所有实例所共享,也就是说当某个类的实例修改了该静态成员变量,其修改值为该类的其它所有实例所见。如果一个类要被声明为static的,只有一种情况,就是静态内部类。如果在外部类声明为static,如果外部类被定义为static,则会编译报错。Oracle官方给出了内部类和静态内部类的区别:

Nested classes are divided into two categories: static and non-static. Nested classes that are declared static are called static nested classes. Non-static nested classes are called inner classes.

从字面上看,一个被称为静态嵌套类,一个被称为内部类。嵌套的意思,是可以完全独立存在意思,只是想借用外部类的壳用一下,隐藏一下自己。它是一个独立的类,完全是形式上的“内部”,和外部类没有本质上“内外”的关系,如下:

  1. 静态内部类跟静态方法一样,只能访问静态的成员变量和方法,不能访问非静态的方法和属性,但是普通内部类可以访问任意外部类的成员变量和方法。
  2. 静态内部类可以声明普通成员变量和方法,而普通内部类不能声明static成员变量和方法。
  3. 静态内部类可以单独初始化,而普通内部类只能通过外部类实例初始化
    //静态内部类
    Inner i = new Outer.Inner();
    
    //pu普通内部类
    Outer o = new Outer();
    Inner i = o.new Inner();

静态内部类使用场景一般是当外部类需要使用内部类,而内部类无需外部类资源,并且内部类可以单独创建的时候会考虑采用静态内部类的设计,一个常见的用法就是通过静态内部类实现builder模式:

public class Outer {
    private String name;
    private int age;

    public static class Builder {
        private String name;
        private int age;

        public Builder() {
            return this;
        }

        public Builder withName(String name) {
            this.name = name;
            return this;
        }

        public Builder withAge(int age) {
            this.age = age;
            return this;
        }

        public Outer build() {
            return new Outer(this);
        }
    }

    private Outer(Builder b) {
        this.age = b.age;
        this.name = b.name;
    }
}

静态内部类调用外部类的构造函数,来构造外部类,由于静态内部类可以被单独初始化,所以可以如下调用:

Outer outer = new Outer.Builder().withName("zhuoli").withAge(18).build();
return outer;

2.1.6 final

当一个类被final修饰时,表明此类是个不可变类,不能被继承,所有的方法都不能被重写。但这并不表示final类的成员变量也是不可改变的,要想做到final类的成员变量不可改变,必须给成员变量增加final修饰。

2.2 类名

码出高效 阿里巴巴Java开发手册》中讲到,类名使用 UpperCamelCase 风格,但以下情形例外:DO/BO/DTO/VO/AO/PO/UID 等。

正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion

2.3 类变量

类变量也叫静态变量,是指类中被static修饰的成员变量,实质上就是一个全局变量。如果某个内容是被所有对象所共享,那么该内容就应该用static修饰,没有被static修饰的内容,其实是属于对象的特殊描述。类变量在类加载时,在堆中分配内存,类变量的生命周期一直持续到类卸载,并且被所有对象所共享

2.4 实例变量

实例变量也叫非static成员变量,是指类中没有被static修饰的成员变量,用于描述对象特征。实例变量是属于特定的对象的,每个对象持有的实例变量都有可能是不同的,在创建对象时在堆上分配内存,当对象被回收时,实例变量的生命周期也相应结束

2.5 构造函数

构造函数是一种特殊的函数,用来在对象实例化时初始化对象的成员变量。在Java中,构造函数具有以下特点:

  1. 构造函数必须与类的名字相同,并且不能有返回值(void也不行)。
  2. 每个类可以由多个构造函数,当代码中没有提供构造函数时,编译器在把源代码编译成字节码的过程中会提供一个默认的无参构造函数,如果代码中提供了构造函数,那么编译器就不会在创建默认的无参构造函数了。
  3. 构造函数总是伴随着new操作一起调用,且不能由程序的编写者直接调用,必须要由系统调用。构造函数在对象实例化是会被自动调用,且只运行一次。普通方法在程序执行到它的时候被调用,可以被对象调用多次。
  4. 构造函数不能被继承,所以它也不能被覆盖,但是构造函数能够被重载,可以使用不同的参数个数或者参数类型来定义多个构造函数。
  5. 子类可以通过super关键字来显式地调用父类的构造函数,当父类没有提供无参构造函数时,子类的构造函数中必须显式地调用父类的构造函数。如果父类提供了无参构造函数,此时子类的构造函数就可以不用显式地调用父类的构造函数,这种情况下编译器会默认调用父类提供的无参构造函数。当有父类时,在实例化对象时会先执行父类的构造函数,然后执行子类的构造函数
  6. 如果父类和子类中都没有定义构造函数,则编译器会为父类和子类都生成一个默认的构造函数。此外构造函数的修饰符只跟当前类的修饰符有关,如果一个类被定义为public,那么它的构造函数也是public。

2.6 类方法

类方法也叫静态方法,是指通过static修饰的成员方法,用于描述类行为,在调用方式上可以直接使用类名调用,而不用通过实例调用。在同一个类中,实例方法内部可以直接使用静态方法,但是静态方法中则无法直接调用这个实例方法,相应的静态方法也只能访问静态成员。关于静态方法,相信很多人都有一种熟悉又陌生的感觉,下面简单讲一下关于静态方法和非静态方法的一些误解:

  1. 大家都以为“静态方法常驻内存,实例方法不是,所以静态方法效率高但占内存
    事实上,他们都是一样的,在加载时机和占用内存上,静态方法和实例方法是一样的,在被使用时加载到栈中,也就是栈帧,在方法调用结束,从栈中弹出,调用的速度基本上没有差别。
  2. 大家都以为“静态方法在堆上分配内存,实例方法在堆栈上
    事实上所有的方法都不可能在堆或者堆栈上分配内存,方法作为代码是被加载到特殊的代码内存区域,这个内存区域是不可写的。方法占不占用更多内存,和它是不是static没什么关系。  
    因为字段是用来存储每个实例对象的信息的,所以字段会占有内存,并且因为每个实例对象的状态都不一致(至少不能认为它们是一致的),所以每个实例对象的所有字段都会在内存中有一分拷贝,也因为这样你才能用它们来区分你现在操作的是哪个对象。但方法不一样,不论有多少个实例对象,它的方法的代码都是一样的,所以只要有一份代码就够了。因此无论是static还是non-static的方法,都只存在一份代码,也就是只占用一份内存空间。  
    同样的代码,为什么运行起来表现却不一样?这就依赖于方法所用的数据了。主要有两种数据来源,一种就是通过方法的参数传进来,另一种就是使用class的成员变量的值……
  3. 大家都以为“实例方法需要先创建实例才可以调用,比较麻烦,静态方法不用,比较简单
    事实上如果一个方法与他所在类的实例对象无关,那么它就应该是静态的,而不应该把它写成实例方法。所以所有的实例方法都与实例有关,既然与实例有关,那么创建实例就是必然的步骤,没有麻烦简单一说。当然你完全可以把所有的实例方法都写成静态的,将实例作为参数传入即可,一般情况下可能不会出什么问题。从面向对象的角度上来说,在抉择使用实例化方法或静态方法时,应该根据是否该方法和实例化对象具有逻辑上的相关性,如果是就应该使用实例化对象 反之使用静态方法。这只是从面向对象角度上来说的。如果从线程安全、性能、兼容性上来看 也是选用实例化方法为宜。

那么为什么还要区分静态方法和实例化方法 ?

如果我们继续深入研究的话,就要脱离技术谈理论了。早期的结构化编程,几乎所有的方法都是“静态方法”,引入实例化方法概念是面向对象概念出现以后的事情了,区分静态方法和实例化方法不能单单从性能上去理解,创建c++,java,c#这样面向对象语言的大师引入实例化方法一定不是要解决什么性能、内存的问题,而是为了让开发更加模式化、面向对象化。这样说的话,静态方法和实例化方式的区分是为了解决模式的问题。

2.7 实例方法

实例方法就是类中那些没有被static修饰的方法,属于对象行为,在讲静态方法时,已经讲了静态方法和实例方法的区别,这里不多阐述了。

3. 基础概念答疑

当涉及到类和概念时,总有一些基础概念,当遇到时感觉很简单,但要表述时往往又不能完全表述清楚,这里总结以下一些基本概念。

3.1 继承

继承是面向对象的一个重要特性,通过继承,子类可以使用父类中的一些成员变量和方法,从而提高代码的复用性,提高开发效率。Java中继承主要有以下几个特性:

  1. Java中不支持多继承,一个子类至多只能有一个父类。
  2. 子类只能继承父类的非私有成员变量和方法。
  3. 当子类中定义的成员变量和父类中定义的成员变量同名时,子类中的成员变量会覆盖父类的成员变量,而不会继承。
  4. 当子类中的方法和父类中的方法有相同的方法签名(方法名相同,参数个数相同,顺序类型也相同)时,子类中的方法会覆盖父类中的方法,而不会继承。

3.2 类之间的关系

Java中,类之间最常见的关系有以下三种:

  • 依赖(“uses-a”)
  • 聚合(“has-a”)
  • 继承(“is-a”)

依赖(dependence),即”uses-a”关系,是一种最明显、最常见的关系。如下:

public class Order{

	public OrderVO getOrder(GetOrderRequest request) {
		Account account = AccountService.getAccount(**);
		……
	}
}

Order类中使用了Account类,这时候Order类与Account就是依赖关系(“uses-a”)。开发中应该该尽可能地将相互依赖地类减至最少。如果类A不知道B地存在,它就不会关心类B地任何改变(类B地改变不会导致A产生任何bug)。用途软件工程地属于来说,就是让类之间地耦合度最小。

聚合(aggregation),即”has-a”关系,是一种具体且易于理解的关系。如下:

public class Order{

	private Account account;
	public Order(Account account){
		this.account = account;
	}
	public OrderVO getOrder(GetOrderRequest request) {
		 Long accountNo = account.getAccountNo();
		……
	}
}

Order类中持有了一个Account对象,这时候Order类和Account类就是”has-a”的关系。

继承(inheritance),即”is-a”关系,是一种表示特殊与一般的关系,其实即时父类与子类的关系。如下:

public class Order{
	private Long orderNo;

	private Long price;

	//getter and setter
}

public class Xorder extends Order{
	……
}

Xorder类继承了Order类,Xorder类与Order类就是”is-a”关系。

继承和组合是开发中最常见的两种代码常用方式,在开发中,一般遵循以下规则:

  1. 除非两个类之间是”is-a”关系,狗则不要轻易使用继承,不要单纯地为了实现代码的重用而使用继承,因为过多的使用继承会破坏代码的可维护性,当父类被修改时,会影响到所有继承它的子类,从而增加程序的维护难度与成本。
  2. 不要仅仅为了实现多态而使用继承,如果类之间没有”is-a”关系,可以通过实现接口与组合的方式来达到相同的目的。设计模式中的策略模式可以很好地说明这一点,采用接口与组合的方式比采用继承的方式具有更好的可扩展性。在Java中能使用组合就尽量不要使用继承。

3.3 多态

多态是面向对象程序设计中代码重用的一个重要机制,他表示当一个操作作用在不同的对象时,会有不同的语义,从而产生不同的结果。在Java中,多态主要有以下两种实现方式:

  1. 方法重载(overload),重载是指同一类中有多个同名的方法,但这些方法有着不同的参数,因此在编译时就可以确定到底调用哪个方法,这是一种编译时多态,可以看作是一个类中方法的多态性。
  2. 方法覆盖(override),子类可以覆盖从父类继承的方法,因此同样的方法会在子类和父类中有着不同的表现形式。Java中,基类的引用变量不仅可以指向基类的实例对象,也可以指向其子类的实例对象。同样,接口的引用变量也可以指向其实现类的实例对象。当使用接口的引用变量进行方法调用时,并不是直接调用父类的方法,而是在方法运行时,通过引用变量的具体指向,决定具体方法调用(指向子类实例对象,那么调用具体子类的方法;指向父类实例对象就调用父类的方法)。也就是方法在程序运行才动态绑定,因此通过覆盖实现的多态也被称为运行时多态。

重载:

  1. 重载可以通过不同的方法参数来区分,比如不同的参数个数、不同的参数类型、不同的参数顺序。
  2. 不能通过方法的访问权限、返回值类型和跑出的异常来进行重载。
  3. 对于继承来说,如果基类方法的访问权限是private,那么子类是无法继承该方法的,那么也就不能对该方法进行重载了。如果派生类也定义了一个同名的函数,这只能苏啊是一个新的方法,不会达到重载的效果。

覆盖:

  1. 派生类中的覆盖方法必须要和基类中被覆盖的方法具有相同的函数名和参数。
  2. 派生类中覆盖的方法的返回值必须和基类中被覆盖方法的返回值类型必须相同。
  3. 派生类中覆盖的方法所抛出的异常必须和基类中方法抛出的异常相同或者是基类方法抛出异常的子类,也就是说子类抛出的异常类型不能比父类抛出的异常类型更宽泛。
  4. 派生类中覆盖的方法的访问权限应比基类方法的访问权限更大或相等
  5. 覆盖是基于方法继承的,如果基类中方法的访问权限是private,那么子类是无法继承该方法的,也就不存在覆盖了。

参考链接:

  1. 深入理解Java对象的创建过程:类的初始化与实例化
  2. Java 访问权限控制:你真的了解 protected 关键字吗?
  3. Java静态类
  4. Java中类,对象,方法的内存分配
  5. Java内部类详解
  6. 静态方法和实例化方法的区别

赞(0) 打赏
Zhuoli's Blog » Java编程拾遗『对象和类』
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

zhuoli's blog

联系我关于我

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏