C++引入了构造器的概念,这是一个特殊的方法,每创建一个对象,这个方法就会被自动调用,Java沿用了c++中构造器的概念。

一、利用构造器保证初始化

我们可以为每个类创建一个initialize()方法,该方法名暗示着在使用类之前先调用它进行初始化,但这样做的缺点是必须得记得去调用它。在 Java 中,类的设计者通过构造器保证每个对象的初始化。如果一个类有构造器,那么 Java 会在用户使用对象之前(即对象刚创建完成)自动调用对象的构造器方法,从而保证初始化。使用构造器的一个问题是如何命名构造器方法。这个问题有两个方面:第一是任何命名都可能与类中其他已有元素的命名冲突;第二是编译器必须始终知道构造器方法名称,从而调用它。C++ 的解决方法看起来是最简单且最符合逻辑的,所以 Java 中使用了同样的方式:构造器名称与类名相同。

下述的代码例子包含了一个构造器的类:

// housekeeping/SimpleConstructor.java
// Demonstration of a simple constructorclass Rock {Rock() { // 这是一个构造器System.out.print("Rock ");}
}public class SimpleConstructor {public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Rock();}}
}

输出:

Rock Rock Rock Rock Rock Rock Rock Rock Rock Rock

现在,每当创建一个对象时,即new Rock(),内存被分配,构造器被调用。构造器保证了对象在被使用之前进行了正确的初始化。

跟其他方法一样,构造器方法也可以传入参数来定义如何创建一个对象。将之前的例子稍作修改,使得构造器接收一个参数:

// housekeeping/SimpleConstructor2.java
// Constructors can have argumentsclass Rock2 {Rock2(int i) {System.out.print("Rock " + i + " ");}
}public class SimpleConstructor2 {public static void main(String[] args) {for (int i = 0; i < 8; i++) {new Rock2(i);}}
}

输出:

Rock 0 Rock 1 Rock 2 Rock 3 Rock 4 Rock 5 Rock 6 Rock 7

构造器消除了一类重要的问题,使得代码更易读。例如,在上面的代码块中,没有对initialize()方法的显式调用,而从概念上来看,initialize()方法应该与对象的创建分离。在 Java 中,对象的创建与初始化是统一的概念,二者不可分割。

构造器没有返回值,它是一种特殊的方法。但它和返回类型为 void 的普通方法不同,普通方法可以返回空值,你还能选择让它返回别的类型;而构造器没有返回值,却同时也没有给你选择的余地(new 表达式虽然返回了刚创建的对象的引用,但构造器本身却没有返回任何值)。如果它有返回值,并且你也可以自己选择让它返回什么,那么编译器就还得知道接下来该怎么处理那个返回值(这个返回值没有接收者)。

二、方法重载

重载是在一个类里面,方法名字相同,而参数类型列表不同,返回类型可以相同也可以不同,最常用的是构造器的重载。

2.1 重载的规则

  • 被重载的方法必须改变参数列表(参数个数或类型或顺序不一样)
  • 被重载的方法可以改变返回类型
  • 被重载的方法可以改变访问修饰符
  • 被重载的方法可以声明新的或更广的检查异常
  • 方法能够在一个类中或者在一个子类中被重载
  • 无法以返回值类型作为重载函数的区分标准

代码例子:

public class Overloading {public int test(){System.out.println("test1");return 1;}public void test(int a){System.out.println("test2");}//以下两个参数类型顺序不同public String test(int a,String s){System.out.println("test3");return "return test3";}public String test(String s,int a){System.out.println("test4");return "return test4";}public static void main(String[] args){Overloading o = new Overloading();System.out.println(o.test());o.test(1);System.out.println(o.test(1,"test3"));System.out.println(o.test("test4",1));}
}

输出:

test1
1
test2
test3
return test3
test4
return test4

2.2 重载与基本类型

基本类型可以自动从较小的类型转型为较大的类型(强制类型转换)。当这与重载结合时,这会令人有点困惑,下面是一个这样的例子:

// housekeeping/PrimitiveOverloading.java
// Promotion of primitives and overloadingpublic class PrimitiveOverloading {void f1(char x) {System.out.print("f1(char)");}void f1(byte x) {System.out.print("f1(byte)");}void f1(short x) {System.out.print("f1(short)");}void f1(int x) {System.out.print("f1(int)");}void f1(long x) {System.out.print("f1(long)");}void f1(float x) {System.out.print("f1(float)");}void f1(double x) {System.out.print("f1(double)");}void f2(byte x) {System.out.print("f2(byte)");}void f2(short x) {System.out.print("f2(short)");}void f2(int x) {System.out.print("f2(int)");}void f2(long x) {System.out.print("f2(long)");}void f2(float x) {System.out.print("f2(float)");}void f2(double x) {System.out.print("f2(double)");}void f3(short x) {System.out.print("f3(short)");}void f3(int x) {System.out.print("f3(int)");}void f3(long x) {System.out.print("f3(long)");}void f3(float x) {System.out.print("f3(float)");}void f3(double x) {System.out.print("f3(double)");}void f4(int x) {System.out.print("f4(int)");}void f4(long x) {System.out.print("f4(long)");}void f4(float x) {System.out.print("f4(float)");}void f4(double x) {System.out.print("f4(double)");}void f5(long x) {System.out.print("f5(long)");}void f5(float x) {System.out.print("f5(float)");}void f5(double x) {System.out.print("f5(double)");}void f6(float x) {System.out.print("f6(float)");}void f6(double x) {System.out.print("f6(double)");}void f7(double x) {System.out.print("f7(double)");}void testConstVal() {System.out.print("5: ");f1(5);f2(5);f3(5);f4(5);f5(5);f6(5);f7(5);System.out.println();}void testChar() {char x = 'x';System.out.print("char: ");f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);System.out.println();}void testByte() {byte x = 0;System.out.print("byte: ");f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);System.out.println();}void testShort() {short x = 0;System.out.print("short: ");f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);System.out.println();}void testInt() {int x = 0;System.out.print("int: ");f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);System.out.println();}void testLong() {long x = 0;System.out.print("long: ");f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);System.out.println();}void testFloat() {float x = 0;System.out.print("float: ");f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);System.out.println();}void testDouble() {double x = 0;System.out.print("double: ");f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);System.out.println();}public static void main(String[] args) {PrimitiveOverloading p = new PrimitiveOverloading();p.testConstVal();p.testChar();p.testByte();p.testShort();p.testInt();p.testLong();p.testFloat();p.testDouble();}
}

输出:

5: f1(int)f2(int)f3(int)f4(int)f5(long)f6(float)f7(double)
char: f1(char)f2(int)f3(int)f4(int)f5(long)f6(float)f7(double)
byte: f1(byte)f2(byte)f3(short)f4(int)f5(long)f6(float)f7(double)
short: f1(short)f2(short)f3(short)f4(int)f5(long)f6(float)f7(double)
int: f1(int)f2(int)f3(int)f4(int)f5(long)f6(float)f7(double)
long: f1(long)f2(long)f3(long)f4(long)f5(long)f6(float)f7(double)
float: f1(float)f2(float)f3(float)f4(float)f5(float)f6(float)f7(double)
double: f1(double)f2(double)f3(double)f4(double)f5(double)f6(double)f7(double)

2.3 重载与重写的关系

2.3.1 重写概念介绍

重写是子类对父类的允许访问的方法的实现过程进行重新编写,返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为,也就是说子类能够根据需要实现父类的方法。重写方法不能抛出新的检查异常或者比重写方法申明更加宽泛的异常。例如:父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。

在面向对象里面,重写是实现多态的方式:

class Animal{public void move(){System.out.println("动物可以移动");}
}class Dog extends Animal{public void move(){System.out.println("狗可以跑和走");}
}public class TestDog{public static void main(String args[]){Animal a = new Animal(); // Animal 对象Animal b = new Dog(); // Dog 对象a.move();// 执行 Animal 类的方法b.move();//执行 Dog 类的方法}
}

以上代码运行结果如下:

动物可以移动
狗可以跑和走

可以看到上面的运行结果体现了多态,尽管b属于Animal类型,但是它运行的是Dog类的move方法。

这是由于在编译阶段,只是检查参数的引用类型。但是在运行时,Java虚拟机(JVM)指定对象的类型并且运行该对象的方法。因此在上面的例子中,之所以能编译成功,是因为Animal类中存在move方法,然而运行时,运行的是特定对象的方法。

接着考虑下面的例子:

class Animal{public void move(){System.out.println("动物可以移动");}
}class Dog extends Animal{public void move(){System.out.println("狗可以跑和走");}public void bark(){System.out.println("狗可以吠叫");}
}public class TestDog{public static void main(String args[]){Animal a = new Animal(); // Animal 对象Animal b = new Dog(); // Dog 对象a.move();// 执行 Animal 类的方法b.move();//执行 Dog 类的方法b.bark();}
}

以上例子编译运行结果如下:

TestDog.java:30: cannot find symbol
symbol  : method bark()
location: class Animalb.bark();^

该程序将抛出一个编译错误,因为b的引用类型Animal没有bark方法。

2.3.2 重写的规则

  • 参数列表与被重写方法的参数列表必须完全相同
  • 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected
  • 父类的成员方法只能被它的子类重写
  • 声明为static的方法不能被重写,但是能够再次被声明
  • 子类和父类在同一个包中,那么子类可以重写父类的所有方法,除了声明为private和final的方法。
  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
  • 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
  • 构造方法不能被重写。
  • 如果不能继承一个类,则不能重写该类的方法。

2.3.3 super关键字的使用

class Animal{public void move(){System.out.println("动物可以移动");}
}class Dog extends Animal{public void move(){super.move(); // 应用super类的方法System.out.println("狗可以跑和走");}
}public class TestDog{public static void main(String args[]){Animal b = new Dog(); // Dog 对象b.move(); //执行 Dog类的方法}
}

以上代码的编译运行结果如下所示:

动物可以移动
狗可以跑和走

2.3.4 重写和重载的区别

区别点 重载方法 重写方法
参数列表 必须修改 一定不能修改
返回类型 可以修改 一定不能修改
异常 可以修改 可以减少或删除,一定不能抛出新的或者更广的异常
访问 可以修改 一定不能做更严格的限制(可以降低限制)

2.3.5 重写和重载之间的联系

方法的重写和重载是Java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。

  • 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,称为方法的重载
  • 方法重写是在子类存在方法与父类的方法名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写。
  • 方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。


三、无参构造器

一个无参构造器就是不接收参数的构造器,用来创建一个"默认的对象"。如果你创建一个类,类中没有构造器,那么编译器就会自动为你创建一个无参构造器。例如:

// housekeeping/DefaultConstructor.java
class Bird {}
public class DefaultConstructor {public static void main(String[] args) {Bird bird = new Bird(); // 默认的}
}

表达式new Bird()创建了一个新对象,调用了无参构造器,尽管在 Bird 类中并没有显式的定义无参构造器。但是,一旦你显式地定义了构造器(无论有参还是无参),编译器就不会自动为你创建无参构造器。如下:

// housekeeping/NoSynthesis.java
class Bird2 {Bird2(int i) {}Bird2(double d) {}
}
public class NoSynthesis {public static void main(String[] args) {//- Bird2 b = new Bird2(); // No defaultBird2 b2 = new Bird2(1);Bird2 b3 = new Bird2(1.0);}
}

如果你调用了new Bird2(),编译器会提示找不到匹配的构造器。当类中没有构造器时,编译器会说"你一定需要构造器,那么让我为你创建一个吧"。但是如果类中有构造器,编译器会说"你已经写了构造器了,所以肯定知道你在做什么,如果你没有创建默认构造器,说明你本来就不需要"。

四、this关键字

this 关键字只能在非静态方法内部使用,this会生成一个对象引用。你可以像对待其他引用一样对待这个引用。如果你在一个类的方法里调用该类的其他方法,不需要使用 this,直接调用即可,this 会自动地应用于其他方法上。可以像这样进行调用:

// housekeeping/Apricot.javapublic class Apricot {void pick() {/* ... */}void pit() {pick();/* ... */}
}

pit()方法中,你可以使用this.pick(),但是没有必要。编译器会自动补全。this 关键字只用在一些必须显式使用当前对象引用的特殊场合。例如,用在 return 语句中返回对当前对象的引用。

// housekeeping/Leaf.java
// Simple use of the "this" keywordpublic class Leaf {int i = 0;Leaf increment() {i++;return this;}void print() {System.out.println("i = " + i);}public static void main(String[] args) {Leaf x = new Leaf();x.increment().increment().increment().print();}
}

输出:

i = 3

因为 increment() 通过 this 关键字返回当前对象的引用,因此在相同的对象上可以轻易地执行多次操作。

this 关键字在向其他方法传递当前对象时也很有用:

// housekeeping/PassingThis.javaclass Person {public void eat(Apple apple) {Apple peeled = apple.getPeeled();System.out.println("Yummy");}
}public class Peeler {static Apple peel(Apple apple) {// ... remove peelreturn apple; // Peeled}
}public class Apple {Apple getPeeled() {return Peeler.peel(this);}
}public class PassingThis {public static void main(String[] args) {new Person().eat(new Apple());}
}

输出:

Yummy

Apple 因为某些原因(比如说工具类中的方法在多个类中重复出现,你不想代码重复),必须调用一个外部工具方法Peeler.peel() 做一些行为。必须使用 this 才能将自身传递给外部方法。

在构造器中调用构造器:

当你在一个类中写了多个构造器时,如果想在一个构造器中调用另一个构造器来避免代码重复,就可以通过 this 关键字实现这样的调用。具体用法是给this一个参数列表,这样就可以调用匹配参数列表的当前对象的构造器。

如下是一个代码例子:

// housekeeping/Flower.java
// Calling constructors with "this"public class Flower {int petalCount = 0;String s = "initial value";Flower(int petals) {petalCount = petals;System.out.println("Constructor w/ int arg only, petalCount = " + petalCount);}Flower(String ss) {System.out.println("Constructor w/ string arg only, s = " + ss);s = ss;}Flower(String s, int petals) {this(petals);//- this(s); // Can't call two!this.s = s; // Another use of "this"System.out.println("String & int args");}Flower() {this("hi", 47);System.out.println("no-arg constructor");}void printPetalCount() {//- this(11); // Not inside constructor!System.out.println("petalCount = " + petalCount + " s = " + s);}public static void main(String[] args) {Flower x = new Flower();x.printPetalCount();}
}

输出:

Constructor w/ int arg only, petalCount = 47
String & int args
no-arg constructor
petalCount = 47 s = hi

从构造器 Flower(String s, int petals) 可以看出,其中只能通过 this 调用一次构造器。另外,必须首先调用构造器,否则编译器会报错。这个例子同样展示了 this 的另一个用法。参数列表中的变量名 s 和成员变量名 s 相同,会引起混淆。你可以通过 this.s 表明你指的是成员变量 s,从而避免重复。你经常会在 Java 代码中看到这种用法,同时本书中也会多次出现这种写法。在 printPetalCount() 方法中,编译器不允许你在一个构造器之外的方法里调用构造器。

this()和super()的区别:
从上面的例子可以看出,this(para)是调用本类中的另一种形式的构造函数(通过参数个数等进行区分),而另外一个关键字,super(para)的含义则是调用父类的构造函数,代码例子如下:

class Father{public static void print(String s){System.out.println(s);}Father(){print("调用了父类的无参构造方法");}Father(String s){print("调用了父类的有参构造方法");}}public class Son extends Father{Son(){super();//这里调用了父类的无参构造方法print("子类调用了父类的无参构造方法");}Son(String s){super(s);print("子类调用了父类的有参构造方法");}Son(String s,int i){this(s);//调用了垒中的只有一个参数的构造方法,不同于这个有两个参数的构造方法print("子类测试this的用法");}public static void main(String[] args) {Son s=new Son();System.out.println("");s=new Son("test");System.out.println("");s=new Son("test",1);}
}

输出:

调用了父类的无参构造方法
子类调用了父类的无参构造方法调用了父类的有参构造方法
子类调用了父类的有参构造方法调用了父类的有参构造方法
子类调用了父类的有参构造方法
子类测试this的用法

static的含义:

记住了 this 关键字的内容,你会对 static 修饰的方法有更加深入的理解:static 方法中不会存在 this。你不能在静态方法中调用非静态方法(反之可以)。静态方法是为类而创建的,不需要任何对象。事实上,这就是静态方法的主要目的,静态方法看起来就像全局方法一样,但是 Java 中不允许全局方法,一个类中的静态方法可以访问其他静态方法和静态属性。一些人认为静态方法不是面向对象的,因为它们的确具有全局方法的语义。使用静态方法,因为不存在 this,所以你没有向一个对象发送消息。的确,如果你发现代码中出现了大量的 static 方法,就该重新考虑自己的设计了。然而,static 的概念很实用,许多时候都要用到它。至于它是否真的"面向对象",就留给理论家去讨论吧。

初始化和清理(构造器+重载/重写+this关键字)相关推荐

  1. 一起读Java编程思想(2)---构造器的初始化与清理

    初始化与清理 用构造器确保初始化 每个类都要定义一个initialize()方法,提醒在使用对象之前必须调用这个方法,使得类的设计者可以确保每个对象都可以被初始化. 构造函数是没有返回类型的函数,用于 ...

  2. C++阶段03笔记02【类和对象(封装、对象的初始化和清理、对象模型和this指针、友元、运算符重载、继承、多态)】

    C++| 匠心之作 从0到1入门学编程[视频+课件+笔记+源码] 目录 1.内存分区模型 2.引用 3.函数提高 4.类和对象 4.1.封装 4.1.1.封装的意义 --实例1:设计圆类 --实例2: ...

  3. 【黑马程序员 C++教程从0到1入门编程】【笔记4】C++核心编程(类和对象——封装、权限、对象的初始化和清理、构造函数、析构函数、深拷贝、浅拷贝、初始化列表、友元friend、运算符重载)

    黑马程序员C++教程 文章目录 4 类和对象(类属性[成员属性],类函数[成员函数]) 4.1 封装 4.1.1 封装的意义(三种权限:public公共.protected保护.private私有)( ...

  4. java 析构函数_《JAVA编程思想》5分钟速成:第5章(初始化和清理)

    第五章.初始化和清理 前言 1.初始化顺序(静态成员.非静态成员,构造器.父类构造器)的排序: 2.构造器(constructor)是否可被重写(override)? 3.final, finally ...

  5. 读书笔记-内存初始化和清理

    本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 在Java开发中,对象创建.数据关系.垃圾回收始终是不变的话题,今天我们来简单说一下初始化和清理的问 ...

  6. [Java] 初始化与清理

    1. 构造器 初始化和清理是涉及安全的两个问题,许多C程序的错误都源于忘记初始化变量,当使用完一个元素时,也很容易忘记清理它.C++引入了构造器的概念,这是一个在创建对象时被自动调用的特殊方法,Jav ...

  7. java编程思想 初始化_《java编程思想》_第五章_初始化与清理

    初始化和清理是涉及安全的两个问题,java中采用了构造器,并额外提供了"垃圾回收器",对于不再使用的内存资源,垃圾回收器能自动将其释放. 一.用构造器确保初始化 java中,通过提 ...

  8. Java编程思想学习录(连载之:初始化与清理)

    注: 本文首发于 My 公众号 CodeSheep ,可 长按 或 扫描 下面的 小心心 来订阅 ↓ ↓ ↓ 关于构造器与初始化 无参构造器 = 默认构造器 = 自己未写编译器帮忙自动创建的 若自行定 ...

  9. 20190816 On Java8 第六章 初始化和清理

    第六章 初始化和清理 利用构造器保证初始化 在 Java 中,类的设计者通过构造器保证每个对象的初始化. 构造器名称与类名相同. 在 Java 中,对象的创建与初始化是统一的概念,二者不可分割. 方法 ...

  10. Java编程笔记2:初始化和清理

    Java编程笔记2:初始化和清理 图源:Java Switch语句(用法详解)-java教程-PHP中文网 构造器 构造器,在编程领域也会被称作构造函数.事实上我觉得这个名称并不是很恰当,可能相当一部 ...

最新文章

  1. 对比电机和舵机中的PWM信号的作用
  2. feign响应拦截_[Spring cloud 一步步实现广告系统] 11. 使用Feign实现微服务调用
  3. 分层设计 --java中的几种包
  4. virtualbox 命令
  5. 高性能Web动画和渲染原理系列(3)——transform和opacity为什么高性能
  6. @param注解什么意思_Java反射是什么?看这篇绝对会了!
  7. 云服务器上找不到指定的加密锁,请问电脑上的系统文件打不开 桌面上显示找不到指定的加密锁 该怎么办 求大神指点...
  8. 好用的电子书网站 Z-library
  9. c语言课后练习题第三章
  10. kali自定义分辨率
  11. Attention注意力机制
  12. php组合图片代码,使用php shell命令合并图片的代码
  13. python图片显示文本框_Python3 tkinter基础 Text image 文本框中插入图片
  14. linux配置SSH
  15. 分解质因数 C语言实现
  16. 如何在library中使用productFlavors
  17. 实车采集的数据重建场景_苹果地图经过新一轮数据采集,重建后的它会颠覆果粉想象吗?...
  18. C#生成随机数100次都是一样的数
  19. 白话空间统计二十一:密度分析(七) Python实现
  20. 圈粉年轻人的“机票盲盒”,爆款的逻辑是什么?

热门文章

  1. windows下Dos命令行设置代理
  2. 实习成长之路:设计模式一:为什么你明明使用面向对象设计语言总写面向过程的程序?
  3. Android类加载器和热修复原理
  4. 红帽企业linux7 u盘安装,RedHat企业版7 “yum”重装 CentOS7 “yum”
  5. AS解决在导入library之后lable/icon/theme合并出现bug
  6. Android音视频开发之-WebRTC技术实践
  7. python可能导致异常的代码_Python程序可能导致文件系统错误?
  8. java切割文件出现1k_java实现把一个大文件切割成N个固定大小的文件
  9. matlab中如何画零线,不接零线的教训好惨 - 通信工程设计与建设 - 通信人家园 - Powered by C114...
  10. linux常用命令V1.1