Java编程思想第五章学习笔记
Java编程思想第五章学习笔记
一、构造器
1.1、初始化
通过构造器,我们可以确保每个对象都会得到初始化。创建对象时,如果类具有构造器,
Java就会在用户有能力操作对象之前自动调用相应的构造器,从而保证初始化的进行。
1.2、命名
Java采取了和C++一样的做法:
构建器的名字与类名相同。这样一来,可保证象这样的一个方法会在初始化期间自动用。
以下就是一个带有构造器的类:
public class Rock {
Rock(){
System.out.println("Create Rock");
}
}
创建对象时:
new Rock();
将会为对象分配存储空间,并调用相应的构造器。
其中不接受任何参数的构造器叫做默认构造器
当然,构造器也是可以带参数的:
public class Rock {
Rock(int i) {
System.out.println("Create" + i);
}
}
构建器属于一种较特殊的方法类型,因为它没有返回值。这与 void 返回值存在着明显的区别。对于void 返回值,尽管方法本身不会自动返回什么,但仍然可以让它返回另一些东西。构建器则不同,它不仅什么也不会自动返回,而且根本不能有任何选择。
二、方法重载
2.1、何为方法重载
在Java中,同一个类中的多个方法可以有相同的方法名称,但是有不同的参数列表,这就称为方法重载(method overloading)。
参数列表又叫参数签名,包括参数的类型、参数的个数、参数的顺序,只要有一个不同就叫做参数列表不同。
在Java(和C ++)中,另一个因素导致方法名称的重载是构造函数。由于构造函数的名称是由类的名称预先确定的,因此只能存在一个构造函数名称。 但是,如果您想以多种方式创建对象怎么办? 对于例如,假设您构建了一个可以以标准方式或通过读取进行初始化的类文件中的信息。 您需要两个构造函数,默认的构造函数和一个接受一个String作为参数,这是从中初始化文件的文件名宾语。两者都是构造函数,因此它们必须具有相同的名称-类的名称。 从而,方法重载对于允许将相同的方法名称用于不同的方法是必不可少的参数类型。 尽管方法重载对于构造函数来说是必须的,但这是一个常规方便,可以与任何方法一起用。
以下为一个方法重载的一个简单例子:
public class Tree {
int height;
Tree(){
System.out.println("播种");
height = 0;
}
Tree(int i){
System.out.println("种新的树,树高" + i);
height = i;
}
}
2.2、区分方法重载
规则很简单:
每个重载的方法必须有一个独一无二的参数类型列表
说明:
-
参数列表不同包括:个数不同、顺序不同、类型不同。
-
仅仅参数变量名称不同是不可以的。
-
跟成员方法一样,构造方法也可以重载。
-
声明为final的方法不能被重载。
-
声明为static的方法不能被重载,但是能够被在此声明。
方法的重载的规则:
- 方法名称必须相同。
- 参数列表必须不同。
- 方法的返回类型可以相同也可以不相同。
- 仅仅返回类型不同不足以称为方法的重载。
三、默认构造器
默认构造函数(也称为无参构造函数)是没有形式参数的。如果你创建的类没有构造函数,编译器会自动为您创建一个默认构造函数。
如:
class A{
}
表达式:
new A();
上述创建一个新对象,并调用默认构造函数,即使该对象未明确
定义它。 没有它,您将无法调用任何方法来构建对象。 但是,如果你
定义任何构造函数(带有或不带有参数),编译器将不会为
你自动创建默认构造器了
class Bird {
Bird(int i) {}
Bird(double d) {}
}
如果你写:
new Bird();
编译器会报错。 当你不放入任何构造函数,编译器会认为:“你需要一个构造函数,因此,让我为你制作一个。”但是,如果您编写了一个构造函数,则编译器会说:“您已经编写了一个构造函数,这样您就知道自己在做什么; 如果您没有设置默认值,那是因为您打算把它排除在外。”
四、this关键字
首先,很重要的一点:
this关键字指向的是当前对象的引用
用法:
4.1、this:引用当前类的实例变量
this关键字可用于引用当前的类实例变量。如果实例变量和参数之间存在歧义,则此关键字可解决歧义问题。
(1)没用this:
public class Place {
String name;
public Place(String name) {
name = name;
}
public void f(){
System.out.println(name);
}
public static void main(String[] args) {
Place p1 = new Place("a");
p1.f();
}
}
最终输出:null
上面的例子问题在于参数的变量名字和实例变量一样,没办法进行区分,此时加上this:
public Place(String name) {
this.name = name;
}
便能解决问题
但如果参数变量名字和实例变量不一样,那么就无需使用:
public class Place {
String name;
public Place(String n) {
name = n;
}
public void f(){
System.out.println(name);
}
public static void main(String[] args) {
Place p1 = new Place("a");
p1.f();
}
}
4.1、this:调用当前的类方法
4.1.1
首先看例子:
public class A {
public void f(){
System.out.println("Hello");
}
public void g(){
// f();
this.f();
}
public static void main(String[] args) {
A a = new A();
a.g();
}
}
方法g中使用与没使用效果是一样的,也就是可以使用this关键字调用当前类的方法。如果不使用this关键字,编译器会在调用方法时自动添加此关键字。
4.1.2
this():调用当前的类构造函数
当您为一个类编写多个构造函数时,有时您会想在一个构造函数中调用另一个构造函数,为了避免重复代码。 您可以使用this这个关键字。
通常写this时候,它的意思是“这个对象”或“当前对象”,而且它本身会产生对当前对象的引用。 在构造函数中,this关键字采用如果为this添加了参数列表时,它就具有不同的含义。 它明确调用了与该参数列表匹配的构造函数。 因此,调用其他构造函数就有了直接的径。
直接上例子:
public class Flower {
private int petalCount = 0;
private String s = new String("null");
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(
"default constructor (no args)");
}
void print() {
// 细节问题
//! this(11); // Not inside non-constructor!
System.out.println(
"petalCount = " + petalCount + " s = "+ s);
}
// 其中,构建器Flower(String s,int petals)向我们揭示出这样一个问题:尽管可用this 调用一个构建器,
// 但不可调用两个。除此以外,构建器调用必须是我们做的第一件事情,否则会收到编译程序的报错信息。
// 这个例子也向大家展示了this 的另一项用途。由于自变量s 的名字以及成员数据s 的名字是相同的,所以会
// 出现混淆。为解决这个问题,可用 this.s来引用成员数据。经常都会在 Java 代码里看到这种形式的应用,
// 本书的大量地方也采用了这种做法。
// 在print()中,我们发现编译器不让我们从除了一个构建器之外的其他任何方法内部调用一个构建器。
public static void main(String[] args) {
Flower x = new Flower();
x.print();
}
五、成员初始化
5.1、默认初始化
只进行成员变量的定义,不进行主动的初始化操作,由编译器为成员变量赋一个默认值。
直接上例子:
public class Measurement {
boolean t;
char c;
byte b;
short s;
int i;
long l;
float f;
double d;
void print() {
System.out.println(
"Data type Inital value\n" +
"boolean " + t + "\n" +
"char " + c + "\n" +
"byte " + b + "\n" +
"short " + s + "\n" +
"int " + i + "\n" +
"long " + l + "\n" +
"float " + f + "\n" +
"double " + d);
}
}
对于基本类型,有各自不同的默认值如int、char、short等为0,boolean为false,对于对象,默认值为null。默认初始化主要是为了保证初始化过程一定会得到执行,只发生在成员变量身上,对于局部变量,不存在这种机制。
5.2、指定初始化
如果您想给变量一个初始值怎么办? 一种直接的方法是只需在您在类中定义变量的位置分配值即可。如:
public class InitialValues2 {
boolean bool = true;
char ch = ‘x’;
byte b = 47;
short s = 0xff;
int i = 999;
long lng = 1;
float f = 3.14f;
double d = 3.14159;
} /
5.2、构造器初始化
构造函数可用于执行初始化,这为您提供了更大的灵活性您的编程,因为您可以在运行时调用方法并执行操作以确定初始值。 但是,请记住一件事:无法阻止自动初始化的进行,它将在构造器被调用之前就发生。
public class Counter {
int i;
Counter() { i = 7; }
// ...
}
i首先被初始化为0,然后被初始化为7。这对于所有原始类型和对象引用,包括在定义时已经指定初值的变量,都是成立的。 因此,编译器不会尝试强制一定要在构造器的某个地方或在使用它们之前对元素进行初始化—因为初始化已经得到了保证。
5.2.1、初始化顺序
在一个类中,变量定义的先后顺序决定了初始化的顺序。在类中定义。即使变量定义分散在整个方法定义之间,但是仍然会在任何方法使用之前初始化,甚至是构造函数。 例如:
class Window {
Window(int marker) { print("Window(" + marker + ")"); }
}
class House {
Window w1 = new Window(1); // Before constructor
House() {
// Show that we’re in the constructor:
print("House()");
w3 = new Window(33); // Reinitialize w3
}
Window w2 = new Window(2); // After constructor
void f() { print("f()"); }
Window w3 = new Window(3); // At end
}
public class OrderOfInitialization {
public static void main(String[] args) {
House h = new House();
h.f(); // Shows that construction is done
}
}
5.3、静态数据初始化
无论创建多少个对象,静态存储只占用一份存储空间。不能将static关键字应用于局部变量,因此只能应用于域。 如果一个域是静态的基本类型域,且也没有对它进行初始化,那么它就会获得基本类型的标准初值。如果它是一个对象的引用,默认初始化值为null。
class Bowl {
Bowl(int marker) {
print("Bowl(" + marker + ")");
}
void f1(int marker) {
print("f1(" + marker + ")");
}
}
class Table {
static Bowl bowl1 = new Bowl(1);
Table() {
print("Table()");
bowl2.f1(1);
}
void f2(int marker) {
print("f2(" + marker + ")");
}
static Bowl bowl2 = new Bowl(2);
}
class Cupboard {
Bowl bowl3 = new Bowl(3);
static Bowl bowl4 = new Bowl(4);
Cupboard() {
print("Cupboard()");
bowl4.f1(2);
}
void f3(int marker) {
print("f3(" + marker + ")");
}
static Bowl bowl5 = new Bowl(5);
}
public class StaticInitialization {
public static void main(String[] args) {
print("Creating new Cupboard() in main");
new Cupboard();
print("Creating new Cupboard() in main");
new Cupboard();
table.f2(1);
cupboard.f3(1);
}
static Table table = new Table();
static Cupboard cupboard = new Cupboard();
}
Bowl允许可以查看到类的创建,并且在Table和Cupboard类中定了了Bowl类型的静态数据成员
注意,Cupboard先定义了一个非静态的Bowl类型的数据成员从输出中,您可以看到仅在必要时才进行静态初始化。 如果你不创建Table对象,也不引用Table.bowl1或Table.bowl2,那么静态的b1和b2永远不会创建。 它们仅在第一个Table时被初始化对象被创建(或第一次静态访问发生)时候才会被初始化,此外。静态对象不会再被初始化。
初始化的顺序是静态的(如果之前未进行过初始化的话),然后是非静态对象。 您可以在
输出观察到这一点。 要执行main()(静态方法),必须加载StaticInitialization类,然后其静态域table和cupboard被初始化,从而导致它们对应的类也被加载,并且它们都包含静态的Bowl对象,因此会加载Bowl。因此,该特定程序中的所有类都在main()开始之前被加载。
5.3、显示的静态初始化
Java允许将多个静态初始化动作组织成一个特殊的“静态子句”(有时称为静态块)在一个类中。 看起来像这样:
public class Spoon {
static int i;
static {
i = 47;
}
} ///
5.4、非静态实例初始化
class Mug {
Mug(int marker) {
print("Mug(" + marker + ")");
}
void f(int marker) {
print("f(" + marker + ")");
}
}
public class Mugs {
Mug mug1;
Mug mug2;
{
mug1 = new Mug(1);
mug2 = new Mug(2);
print("mug1 & mug2 initialized");
}
Mugs() {
print("Mugs()");
}
Mugs(int i) {
print("Mugs(int)");
}
public static void main(String[] args) {
print("Inside main()");
new Mugs();
print("new Mugs() completed");
new Mugs(1);
print("new Mugs(1) completed");
}
}
六、可变参数列表
- 可变参数列表其实是一个数组,作为函数f()的形参时,函数f()既可以接受一个数组,也可以接受可变参数列表,编译器自动将可变参数列表变为数组
- 可变参数列表声明如下 void f(int... is);