Java中构造器内部的多态方法的行为实例分析

本文实例讲述了Java中构造器内部的多态方法的行为操作。分享给大家供大家参考,具体如下:

这篇文章主要讨论的是,若在一个构造器中调用正在构造的对象的某个动态绑定的方法时会出现的情况。在此之前,我们需要知道构造器是如何在复杂的层次结构中运作的,尽管构造方法并不具有多态性,因为它们实际上是static方法,只不过是隐式声明的static。

复杂层次结构中构造器的调用顺序

基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上链接,以使每个基类的构造器都能得到调用。这样做是因为,在Java类中,我们通常将字段设置为private类型,也就是说,在子类中通常无法直接访问基类的字段,那么只有通过调用基类的构造器才能对基类对象的元素进行初始化,那么就必须保证所有的构造器都得到调用,这样才能正确地构造完整的对象。下面的例1展示了包含有组合与继承关系的各类中构造器的调用顺序:

例1:

class Meal {
   Meal() { System.out.println("Meal()"); }
}
class Bread {
   Bread() { System.out.println("Bread()"); }
}
class Cheese {
   Cheese() { System.out.println("Cheese()"); }
}
class Lettuce {
   Lettuce() { System.out.println("Lettuce()"); }
}
class Lunch extends Meal {
   Lunch() { System.out.println("Lunch()"); }
}
class PortableLunch extends Lunch {
   PortableLunch() { System.out.println("PortableLunch()");}
}
public class Sandwich extends PortableLunch {
   private Bread b = new Bread();
   private Cheese c = new Cheese();
   private Lettuce l = new Lettuce();
   public Sandwich() { System.out.println("Sandwich()"); }
   public static void main(String[] args) {
    new Sandwich();
   }
}

例1反映了关于Meal、Lunch和Sandwich之间三层继承关系(不包含Object类),以及Bread、Cheese和Lettuce与Sandwich的组合关系。在main函数中创建一个Sandwich对象后,我们就可以看到输出结果:

这说明在复杂的层次结构中构造器的调用遵从的顺序为:

** 1、调用基类构造器。这个步骤会不断地反复递归下去,首先是构造这种层次结构的根,然后是下一层导出类,等等,直到最低层的导出类;
2、按声明的顺序调用成员的初始化方法;
3、调用导出类构造器的主体。**

构造器内部的多态方法的行为

那么,现在我们回到文章开头提到的问题,若在一个构造器中调用正在构造的对象的某个动态绑定的方法,会出现什么情况呢?我们知道,动态绑定(或后期绑定)的方法的调用是在运行时才决定的,因为对象在程序运行之前无从得知它自己到底是基类的对象,还是某个导出类的对象。如果在基类的构造器内部调用某个动态绑定方法,该方法是被导出类覆盖的,那么这便可能产生难以预料的后果,因为该导出类的对象还未被完全构造,但它的方法却被调用了。我们可以通过例2看到问题所在:

例2:

class Glyph {
   void draw() { System.out.println("Glyph.draw()"); }
   Glyph() {
    System.out.println("Glyph() before draw()");
    draw();
    System.out.println("Glyph() after draw()");
   }
}
class RoundGlyph extends Glyph {
   private int radius = 1;
   RoundGlyph(int r) {
    radius = r;
    System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius);
   }
   void draw() {
    System.out.println("RoundGlyph.draw(), radius = " + radius);
   }
}
public class PolyConstructors {
   public static void main(String[] args) {
    new RoundGlyph(5);
   }
}

运行结果:

在运行结果中,我们看到,基类Glyph的构造器中调用了被子类RoundGlyph覆盖的draw()方法,并且输出了radius=0,这显然是一个错误,因为这个“0”是在其他任何事物发生之前,系统分配给对象的存储空间的初始值——二进制的零,而非我们想要设定的初始值“1”。这是因为,我们在创建子类(RoundGlyph)对象时会先调用基类(Glyph)的构造器构造基类对象,而在基类的构造器中却调用了被子类覆盖的动态绑定的方法(draw()),而这个方法所操纵的可能是子类中的还未进行初始化的成员(radius),这便会招致灾难,尽管编译器并没有报错。

因此,在编写构造器中有一条有效的准则:“用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其他方法”。在构造器中,唯一能够安全调用的是基类中的final方法(包括private方法),因为这些方法不能被子类覆盖,也就不会出现上述的问题。

更多java相关内容感兴趣的读者可查看本站专题:《Java面向对象程序设计入门与进阶教程》、《Java数据结构与算法教程》、《Java操作DOM节点技巧总结》、《Java文件与目录操作技巧汇总》和《Java缓存操作技巧汇总》

希望本文所述对大家java程序设计有所帮助。

(0)

相关推荐

  • Java构造器使用方法及注意事项

    Java构造器使用方法及注意事项 超类的构造器在子类的构造器运行之前运行,也就是说,在创建对象时,先运行超类中的构造器,然后再运行子类中的构造器,此时,如果在执行超类构造器的时候,构造器执行了可覆盖的方法,那么就会去调用子类中的该方法,而此时子类还未被实例化,此时就有可能出问题. 以一个例子说明: public class Super { int age = 10; protected void say() { System.out.println("super"); } public

  • 实例解析Java中的构造器初始化

    1.初始化顺序 当Java创建一个对象时,系统先为该对象的所有实例属性分配内存(前提是该类已经被加载过了),接着程序开始对这些实例属性执行初始化,其初始化顺序是:先执行初始化块或声明属性时制定的初始值,再执行构造器里制定的初始值. 在类的内部,变量定义的先后顺序决定了初始化的顺序,即时变量散布于方法定义之间,它们仍就会在任何方法(包括构造器)被调用之前得到初始化. class Window { Window(int maker) { System.out.println("Window(&quo

  • Java私有构造器使用方法示例

    构造器作为Java类的一个特殊的成员同样可以设置关键字来控制其访问权限.在大多数情况下,我们一般把构造器设置为公有成员,即public的,在默认情况下,如果不写任何关键字,其访问权限也是public.这样,在我们新建一个类的对象的时候,构造器会创建对象后被自动调用,发生在其他类成员被设置为默认初始值之后,当然如果有字段初始器和初始化块的话,构造器的调用会发生在此之后.构造器的主要作用是在new将对象的引用返回之前初始化对象. 言归正传,什么叫私有构造器.所谓私有构造器,就是用private关键字

  • Java 7菱形语法与泛型构造器实例分析

    本文实例讲述了Java 7菱形语法与泛型构造器.分享给大家供大家参考,具体如下: 一 实战--泛型构造器 1 代码 class Foo { public <T> Foo(T t) { System.out.println(t); } } public class GenericConstructor { public static void main(String[] args) { // 泛型构造器中的T参数为String. new Foo("疯狂Java讲义"); //

  • Java 构造器原理及用法解析

    导读 构造器是编程的强大组件.使用它们来释放 Java 的全部潜力. 在开源.跨平台编程领域,Java 无疑(?)是无可争议的重量级语言.尽管有许多伟大的跨平台框架,但很少有像 Java 那样统一和直接的. 当然,Java 也是一种非常复杂的语言,具有自己的微妙之处和惯例.Java 中与构造器 constructor有关的最常见问题之一是:它们是什么,它们的作用是什么? 简而言之:构造器是在 Java 中创建新对象object时执行的操作.当 Java 应用程序创建一个你编写的类的实例时,它将检

  • Java Lambda表达式的方法引用和构造器引用实例分析

    本文实例讲述了Java Lambda表达式的方法引用和构造器引用.分享给大家供大家参考,具体如下: 一 点睛 如果Lambda表达式的代码块只有一条代码,还可以在代码块中使用方法引用和构造器引用,以使得Lambda表达式更加简洁. 种类 示例 说明 对应的Lambda表达式 引用类方法 类名::类方法 函数式接口中被实现方法的全部参数传给该类方法作为参数. (a,b,...) -> 类名.类方法(a,b, ...) 引用特定对象的实例方法 特定对象::实例方法 函数式接口中被实现方法的全部参数传

  • Java基础教程之构造器与方法重载

    在方法与数据成员中,我们提到,Java中的对象在创建的时候会初始化(initialization).初始化时,对象的数据成员被赋予初始值.我们可以显式初始化.如果我们没有给数据成员赋予初始值,数据成员会根据其类型采用默认初始值. 显式初始化要求我们在写程序时就确定初始值,这有时很不方便.我们可以使用构造器(constructor)来初始化对象.构造器可以初始化数据成员,还可以规定特定的操作.这些操作会在创建对象时自动执行. 定义构造器 构造器是一个方法.像普通方法一样,我们在类中定义构造器.构造

  • Java中构造器内部的多态方法的行为实例分析

    本文实例讲述了Java中构造器内部的多态方法的行为操作.分享给大家供大家参考,具体如下: 这篇文章主要讨论的是,若在一个构造器中调用正在构造的对象的某个动态绑定的方法时会出现的情况.在此之前,我们需要知道构造器是如何在复杂的层次结构中运作的,尽管构造方法并不具有多态性,因为它们实际上是static方法,只不过是隐式声明的static. 复杂层次结构中构造器的调用顺序 基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上链接,以使每个基类的构造器都能得到调用.这样做是因为,在Jav

  • java中构造器内部调用构造器实例详解

    可能为一个类写了多个构造器,有时可能想在一个构造器里面调用另外一个构造器,为了减少代码的重复,可用this关键字做到这一点. public class Flower { private String string; private int age; public Flower() { // 先调用public Flower(String string, int age) this("leon", 120); // 先调用public Flower(String string, int

  • 浅谈Java中的重载,重写,多态,静态绑定、动态绑定

    本文主要研究的是关于Java中重载,重写,多态,静态绑定.动态绑定的相关内容,具体如下. 重载,英文名是overload,是指在一个类中定义了一个以上具有相同名称的方法,这些方法的参数个数.参数类型和顺序不能相同.返回类型可以相同,也可以不同. public class TstaticOverload { static int height; TstaticOverload() { System.out.println ("Planting a seedling"); height =

  • Java中Thread.join()的使用方法

    概要 本文分三个部分对Thread.join()进行分析: 1. join() 的示例和作用 2. join() 源码分析 3. 对网上其他分析 join() 的文章提出疑问 1. join() 的示例和作用 1.1 示例 // 父线程 public class Parent { public static void main(String[] args) { // 创建child对象,此时child表示的线程处于NEW状态 Child child = new Child(); // child

  • java中Object类4种方法详细介绍

    目录 Object(四大方法): hashCode()方法: equals()方法: getClass()方法: toString()方法: 总结 Object(四大方法): 文章干货满满,耐性看完~~何为Object?首先先来看看官方对Object的介绍:在这里附上Java官方的查阅工具:https://docs.oracle.com/en/java/javase/17/docs/api/index.html 由官方介绍可见,object属于Java.lang包内的一个类,而且提供了很多种方法

  • Java 中不全部使用 Static 方法的理由

    前阵子在知乎上看到这个问题,一开始觉得还挺无厘头的,但细细一想却发觉这里面很考验对语言的理解.于是写下了我的理解,今天把回答搬运到这里,与大家一起分享. 这个问题看起来有点无厘头,但仔细想想却非常考验答主对 Java 这门语言的理解.如果没有什么 Java 开发经验,那估计是想不出所以然来的.作为一个工作了 8 年,写了 12 年 Java 代码的研发老兵,我来简单聊聊这个问题. 题目说的是为什么不全部使用 static 方法? 我们尝试着想想:如果全部用 static 方法,世界会是怎样? 举

  • 深入了解Java中Synchronized的各种使用方法

    目录 Synchronized关键字 Synchronized修饰实例方法 Synchronized修饰静态方法 Sychronized修饰多个方法 Synchronized修饰实例方法代码块 Synchronized修饰静态代码块 应该用什么对象作为锁对象 Synchronized与可见性和重排序 可见性 重排序 总结 在Java当中synchronized通常是用来标记一个方法或者代码块.在Java当中被synchronized标记的代码或者方法在同一个时刻只能够有一个线程执行被synchr

  • java中的可变参数使用方法

    java中的可变参数使用方法 可变参数时Java 1.5新增的方法,可变参数方法接收0个或者多个指定类型的参数,可变参数机制通过先创建一个数组,数组的大小为在调用位置所传递的参数数量,然后将参数值传到数组中,最后将数组传递给方法.如: public class Test{ public int sum(int... args) { int sum = 0; for (int arg : args) { sum += arg; } return sum; } } 可变参数提供了方便,但是使用可变参

  • Java中对话框的弹出方法

    最近在做学校的课程设计,java编程需要用到对话框弹出,第一反应是js中的alert和confirm,java的话瞬间懵,查阅学习总结如下,用以以后的学习 1.显示一个错误对话框,该对话框显示的 message 为 'alert': JOptionPane.showMessageDialog(null, "alert", "alert", JOptionPane.ERROR_MESSAGE); 2.显示一个内部信息对话框,其 message 为 'informati

  • Java中区别.toString() ,(String),valueOf()方法

    在java项目的实际开发和应用中,常常需要用到将对象转为String这一基本功能.本文将对常用的转换方法进行一个总结.常用的方法有Object.toString(),(String)要转换的对象,String.valueOf(Object)等.下面对这些方法一一进行分析. 方法1:采用 Object.toString()方法请看下面的例子: Object object = getObject(); System.out.println(object.toString()); 在这种使用方法中,因

随机推荐