随着计算机革命的发展,“不安全”的编程方式已逐渐成为编程代价高昂的主因之一。
初始化和清理正是涉及安全的两个问题。
1. 用构造器确保初始化
在Java中,通过构造器,类的设计者可以确保每个对象都会得到初始化。创建对象时,如果类具有构造器,Java就会在用户操作对象之前自动调用相应的构造器,从而保证了初始化的进行。
构造器的特点
- 构造器名称和类名完全相同
- 不接受任何参数的构造器叫做“默认构造器”或者“无参构造器”
- 如果没有创建任何构造器,编译器会默认创建一个“无参构造器”
- 可以自己创建构造器(有参无参都可以),但是这样编译器就不会创建“无参构造器”,如果需要使用,则必须自己创建
2. 方法重载
Java中构造器的名称必须和类名一致,也就是说只能有一个构造器名称,那么如果想用多种方式创建一个对象应该怎么办? 这就要用到方法重载。同时,尽管方法重载是构造器所必需的,但它也可用于其他方法。例如:
1 /** 2 * 重载 3 */ 4 public class Tree { 5 Tree() { 6 System.out.println("Tree"); 7 } 8 Tree(int height) { 9 System.out.println("Tree height = " + height); 10 } 11 12 void f() { 13 System.out.println("f()"); 14 } 15 16 void f(String s) { 17 System.out.println("f(String)"); 18 } 19 20 public static void main(String[] args) { 21 Tree t1 = new Tree(); 22 Tree t2 = new Tree(1); 23 t1.f(); 24 t1.f("test"); 25 26 } 27 } 28 输出: 29 Tree 30 Tree height = 1 31 f() 32 f(String)
2.1 区分重载的方法
通过参数列表:每个重载的方法都一个独一无二的参数类型列表,甚至不同的参数顺序也可以区分(这个要求所有的参数类型不能完全相同,尽量不要这么弄,会使代码难以维护)
疑问:是否可以通过返回值区分重载方法呢?
答案:不可以
如下面的重载函数,如果直接调用f(),编译器如何知道调用哪个方法。所以通过返回值区分重载方法是行不通的。
1 void f() { } 2 int f() {return 1;}
2.2 涉及基本类型的重载
如果传入的数据类型小于方法中声明的参数类型,实际数据类型会被提升。通过下面的代码可以看出基本类型向上转型的顺序:char->short->int->long->float->double;byte->short->int->long->float->double
1 /** 2 * 基本类型的重载 3 */ 4 public class PrimitiveOverloading { 5 6 void f1(char x) { System.out.print("f1(char) "); } 7 void f1(byte x) { System.out.print("f1(byte) "); } 8 void f1(short x) { System.out.print("f1(short) "); } 9 void f1(int x) { System.out.print("f1(int) "); } 10 void f1(long x) { System.out.print("f1(long) "); } 11 void f1(float x) { System.out.print("f1(float) "); } 12 void f1(double x) { System.out.print("f1(double) "); } 13 14 void f2(byte x) { System.out.print("f2(byte) "); } 15 void f2(short x) { System.out.print("f2(short) "); } 16 void f2(int x) { System.out.print("f2(int) "); } 17 void f2(long x) { System.out.print("f2(long) "); } 18 void f2(float x) { System.out.print("f2(float) "); } 19 void f2(double x) { System.out.print("f2(double) "); } 20 21 void f3(short x) { System.out.print("f3(short) "); } 22 void f3(int x) { System.out.print("f3(int) "); } 23 void f3(long x) { System.out.print("f3(long) "); } 24 void f3(float x) { System.out.print("f3(float) "); } 25 void f3(double x) { System.out.print("f3(double) "); } 26 27 void f4(int x) { System.out.print("f4(int) "); } 28 void f4(long x) { System.out.print("f4(long) "); } 29 void f4(float x) { System.out.print("f4(float) "); } 30 void f4(double x) { System.out.print("f4(double) "); } 31 32 void f5(long x) { System.out.print("f5(long) "); } 33 void f5(float x) { System.out.print("f5(float) "); } 34 void f5(double x) { System.out.print("f5(double) "); } 35 36 void f6(float x) { System.out.print("f6(float) "); } 37 void f6(double x) { System.out.print("f6(double) "); } 38 39 void f7(double x) { System.out.print("f7(double) "); } 40 41 void testChar() { 42 System.out.print("char: "); 43 char x = 'x'; 44 f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x); 45 System.out.println(); 46 } 47 void testByte() { 48 System.out.print("byte: "); 49 byte x = 0; 50 f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x); 51 System.out.println(); 52 } 53 void testShort() { 54 System.out.print("short: "); 55 short x = 0; 56 f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x); 57 System.out.println(); 58 } 59 void testInt() { 60 System.out.print("int: "); 61 int x = 0; 62 f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x); 63 System.out.println(); 64 } 65 void testLong() { 66 System.out.print("long: "); 67 long x = 0; 68 f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x); 69 System.out.println(); 70 } 71 void testfloat() { 72 System.out.print("float: "); 73 float x = 0; 74 f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x); 75 System.out.println(); 76 } 77 void testDouble() { 78 System.out.print("double: "); 79 double x = 0; 80 f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x); 81 System.out.println(); 82 } 83 public static void main(String[] args) { 84 PrimitiveOverloading p = new PrimitiveOverloading(); 85 p.testChar(); 86 p.testByte(); 87 p.testShort(); 88 p.testInt(); 89 p.testLong(); 90 p.testfloat(); 91 p.testDouble(); 92 } 93 } 94 95 输出: 96 char: f1(char) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double) 97 byte: f1(byte) f2(byte) f3(short) f4(int) f5(long) f6(float) f7(double) 98 short: f1(short) f2(short) f3(short) f4(int) f5(long) f6(float) f7(double) 99 int: f1(int) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double) 100 long: f1(long) f2(long) f3(long) f4(long) f5(long) f6(float) f7(double) 101 float: f1(float) f2(float) f3(float) f4(float) f5(float) f6(float) f7(double) 102 double: f1(double) f2(double) f3(double) f4(double) f5(double) f6(double) f7(double)
如果传入的实际参数**大于**重装方法声明的形式参数,需要通过类型转换,否则编译器会报错:
1 void f8(short x) { System.out.print("f8(short) "); } 2 // p.f8(5); //编译报错 3 p.f8((short)5);
3. this关键字
this表示“调用方法的那个对象”的引用,只能在方法内部使用:
- 可以在构造器中使用,调用其他的构造器
- 当方法的参数和成员变量名字相同时,可使用this关键字区分成员变量和传入参数
3.1 在构造器中调用构造器
可能为了一个类写了多个构造器,有时想在一个构造器中调用另一个构造器,以避免重复代码,可使用this关键字做到这点。在构造器中,如果为this添加了参数列表(空列表也可以),那么就会对符合此参数列表的某个构造器进行调用。
使用this调用构造器限制条件:
- 只能在构造器中使用this调用其他的构造器,其他方法不行
- 只能调用一次
- 必须是第一个被执行的语句
1 public class Flower { 2 int petalCount = 0; 3 String s = "initial value"; 4 5 Flower(int petals) { 6 petalCount = petals; 7 System.out.println("Constructor int arg only, petalCount = " + petalCount); 8 } 9 10 Flower(String s, int petals) { 11 12 this(petals); 13 // this(s); // 编译报错,只能调用一次 14 this.s = s; // 使用this关键字代表数据成员,防止和参数s混淆 15 System.out.println("String & int args"); 16 } 17 18 Flower() { 19 this("hi", 47); 20 System.out.println("default constructor (no args)"); 21 } 22 23 void pringPetalCount() { 24 // this(11); // 编译报错,只能在构造器中使用 25 System.out.println("petalCount = " + petalCount + " s = " + s); 26 } 27 28 public static void main(String[] args) { 29 Flower x = new Flower(); 30 x.pringPetalCount(); 31 } 32 } 33 34 输出: 35 Constructor int arg only, petalCount = 47 36 String & int args 37 default constructor (no args) 38 petalCount = 47 s = hi
3.2 static方法的含义
static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来可以。并且在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。
4. 清理
java中有垃圾回收器负责回收无用对象占据的内存,但是什么时候会执行垃圾清理不确定,有可能你创建的对象在程序运行过程中永远不会被清理:
- 对象可能不被垃圾回收
- 垃圾回收并不等于析构
- 垃圾回收只与内存有关
几种常见的垃圾清理机制:
垃圾清理机制 | 简要描述 | 缺陷 |
引用计数 | 简单但速度慢。每个对象都有一个引用计数,当有引用连接到对象时,引用计数加1。当引用离开作用域或者被置为null时,引用及时减一。当对象的引用计数为0时,释放其占用的空间。 | 对象之间存在循环引用,可能出现“对象应该被回收,但是引用计数却不为0” |
停止-复制 | 从堆栈和静态存储区出发,遍历所有对象引用,进而找出所有存活的对象。暂停程序的运行,将所有存活的对象拷贝到另一个堆,当对象被复制到新堆时,它们是一个挨着一个的。 | 效率低。首先需要两个堆,需要多维护一倍的空间。程序运行过程中可能只产生少量的垃圾,这种拷贝属于浪费 |
标记-清扫 | 从堆栈和静态存储区出发,遍历所有对象引用,进而找出所有存活的对象。每当找到一个存活对象会给对象一个标记,这个过程中不会回收任何对象,当全部标记工作方程时,才会清理。没有标记的对象被释放。 | 清理后剩余的空间是不连续的 |
5. 成员初始化
方法的局部变量不会给默认值,必须自己进行初始化;类的成员变量,会给默认值:
1 /** 2 * 3 */ 4 class Base { 5 6 } 7 public class DataInit { 8 boolean t; 9 char c; 10 byte b; 11 short s; 12 int i; 13 long l; 14 float f; 15 double d; 16 String ss; 17 DataInit reference; 18 19 void f() { 20 int j; 21 // System.out.println(j); // 编译报错 22 j = 1; 23 System.out.println("j = " + j); 24 } 25 26 void printInitValues() { 27 System.out.println("数据类型 默认初始化值"); 28 System.out.println("boolean " + t); 29 System.out.println("char #" + c + "#"); // 是个空格 30 System.out.println("byte " + b); 31 System.out.println("short " + s); 32 System.out.println("int " + i); 33 System.out.println("long " + l); 34 System.out.println("float " + f); 35 System.out.println("double " + d); 36 System.out.println("String " + ss); 37 System.out.println("reference " + reference); 38 } 39 public static void main(String[] args) { 40 DataInit d = new DataInit(); 41 d.printInitValues(); 42 } 43 } 44 45 输出: 46 数据类型 默认初始化值 47 boolean false 48 char # # 49 byte 0 50 short 0 51 int 0 52 long 0 53 float 0.0 54 double 0.0 55 String null 56 reference null