为了保证正确的初始化与清理。Java提供构造器、垃圾回收器。
对象在创建时,系统会自动调用构造器,可以在构造器中完成初始化工作。对不再使用的内存资源,垃圾回收器会自动将其释放。
一、用构造器保证初始化
示例代码:
public class Exercise1_constructor {
String str1 = "str1";
String str2;
public Exercise1_constructor() {
// TODO Auto-generated constructor stub
str2 = "str2";
}
public static void main(String[] args) {
Exercise1_constructor constructor = new Exercise1_constructor();
System.out.println(constructor.str1);
System.out.println(constructor.str2);
}
}
// "str1"
// "str2"
二、方法重载
1、在JAVA中,拥有相同名称的方法,可以通过参数类型,个数,顺序来对方法加以区分。2、不能通过返回值来对方法进行重载。因为编译器无法识别。
3、基本类型的重载:基本类型能从一个“较小”的类型自动提升为一个“较大”的类型。
基本类型的提升顺序为:char<bety<short<int<long<float<double。
char类型是一个例外,如果没能找到恰好接收char类型的参数,会自动转换为int。
当从”较大“的类型转向”较小“的类型时,必须用强制类型转换来执行窄化转换,否则编译器会报错。
三、默认构造器
默认构造器,又称无参数构造器,如果类中没有定义构造器,则编译器会自动创建一个默认的构造器。如果已定义构造器,则编译器不会再创建默认构造器。
下面的代码展示了构造器、重载。
public class Exercise3_Dog {
public Exercise3_Dog() {
// TODO Auto-generated constructor stub
System.out.println("constructor dog");
}
public Exercise3_Dog(String str) {
System.out.println(str + "constructor with paramter");
}
void bark(String str) {
System.out.println("barking " + str);
}
void bark(int i) {
System.out.println("howling " + i);
}
public static void main(String[] args) {
Exercise3_Dog dog = new Exercise3_Dog();
Exercise3_Dog dog2 = new Exercise3_Dog("haha ");
dog2.bark(2);
dog2.bark("mark");
}
}
// constructor dog
// haha constructor with paramter
// howling 2
// barking mark
四、this关键字
this关键字只能在方法内部使用,表示“对调用此方法的那个对象的引用”。需要注意的是,如果在方法内调用同一个类的其他方法,则不必加上this引用,虽然语法上是可行的。
package com.tij.ch5;
public class Exercise8 {
void function1(int i) {
System.out.println("function 1 be called : " + i);
}
void function2() {
this.function1(2);
function1(6);
}
public static void main(String[] args) {
Exercise8 exercise = new Exercise8();
exercise.function2();
}
}
// function 1 be called : 2
// function 1 be called : 6
1,在构造器中调用构造器
在构造器中,如果对this添加了参数列表,则视为对符合此参数列表的构造器的调用。
a、尽管可以使用this在构造器中调用构造器,但是只能调用一次,并且将调用置于起始处。
b、除构造器外,编译器禁止其他方法调用构造器。
示例代码
public class Exercise8_constructor {
String name;
int age;
Exercise8_constructor(String name) {
// 使用this.name来消除岐义
this.name = name;
}
Exercise8_constructor(int age) {
this.age = age;
}
public Exercise8_constructor(String name, int age) {
// 构造器中,使用this带参数,可以调用另外一个构造器
this(name);
// ERROR: Constructor call must be the first statement in a constructor
// this(age);
}
}
2、static方法
static方法就是没有this的方法,可以在不创建对象的情况下调用static方法,因此在static方法内部无法调用非static方法。
调用static方法,一般用classname.method(),而不用对象。否则编译器会有warnning。
public class Exercise9_static {
void sayNonStatic() {
//OK
sayStatic();
System.out.println("non static!");
}
static void sayStatic() {
// ERROR :Cannot make a static reference to the non-static method
// sayNonStatic() from the type Exercise9_static
// sayNonStatic();
System.out.println("static!");
}
public static void main(String[] args){
//static方法调用直接用class name,否则编译会有报警
Exercise9_static.sayStatic();
Exercise9_static exercise = new Exercise9_static();
exercise.sayNonStatic();
}
}
//static!
//static!
//non static!
//为什么会有2个"static!"打印呢,后面再讨论
五、清理:垃圾回收与清理
垃圾回收并不等于“析构”,对象可能不会被回收,因此在finalize()里执行的清理动作不一定会被调用,finalize是不可预料的。
1,finalize()用途
比如java用本地方法调用了一段c的代码,在c代码中用malloc在堆中分配了空间,则可以在finalize()中调用c的方法进行free()。
finalize不是常规的清理内存的方法,我们一般不应该调用它。
2,必须实施清理的场景
因此,必须要清理一个对象时,需要在清理它时,调用相应的方法,类似于C++中的析构。
3,终结条件
由于finalize()的调用无法预料,因此比较危险,建议不要使用。不过有一种用法:假定垃圾回收前,对象应该处于某种状态,就可以在finalize()中进行验证。
关于finalize()相关的示例代码如下:
public class Exersice10_Tank{
private String name;
private String status;
protected void finalize(){
System.out.println("clean with finalize : " + this.name);
// 终结条件,tank在被释放前,必须为空的,否则会提示错误信息
if(this.status.equals("full")){
System.out.println("Tank will not be full, must be empty");
}
}
Exersice10_Tank(String name, String status){
this.name = name;
this.status = status;
System.out.println("constructor : " + this.name);
}
public static void main(String[] args){
// System.gc(),不会被回收,引用有效
Exersice10_Tank test = new Exersice10_Tank("has name", "full");
// 没有引用接收对象,会被回收
new Exersice10_Tank("no name", "full");
System.gc();
}
}
// constructor : has name
// constructor : no name
// clean with finalize : no name
// Tank will not be full, must be empty
4,垃圾回收器如何工作
a,“暂停-复制”模式,stop-and-copy:暂停正在运行的程序,把活动对象从当前堆复制到另外一个堆。复制到新堆中的内存是连续分部的,复制完成后,恢复程序,清理旧的堆。这样会有两个问题:一是需要的空间会增大(用于复制),再者如果只产生了少量需要清理的内存,也会把所有活动对象复制一次,效率很低。b,“标记-清扫”,mark-and-sweep:这种方法会遍历所有的引用,标记出活动对象。标记完成后,开始执行清理工作。把没标记的对象清理,因此这种方式产生的内存是不完整的。
c,自适应:JAVA虚拟机会进行监视,如果只产生了少量需要清理的内存,就会把回收机制转化到“标记-清扫”模式,如果产生的碎片较多,就会切换回“停止-复制”模式。
六、成员初始化
对于方法内的局部变量,Java用编译器来保证其必须被初始化,如果操作未被初始化的量,编译器会报错。
自动初始化:对于类的数据成员,如果没有显式的初始化,则系统会赋默认值。
七、构造器初始化
1、初始化顺序:
类成员自动初始化 - 构造器中的初始化 - 方法调用中的变量初始化。见下例:
class window{
window(int index){
System.out.println("window index : " + index);
}
}
public class Exercise11_Order{
window w1 = new window(1);
Exercise11_Order(){
System.out.println("order constructor");
this.w1 = new window(10);
}
void f(){
window w3 = new window(3);
}
window w2 = new window(2);
public static void main(String[] args){
Exercise11_Order ex1 = new Exercise11_Order();
ex1.f();
}
}
// window index : 1
// window index : 2
// order constructor
// window index : 10
// window index : 3
2、静态数据初始化:
无论创建多少个静态对象,静态数据都只占用一份存储区域。static关键字不能作用于方法内部的局数变量,只能作用于类的一个域,即成员变量(或成员方法)。 虽然没有显式使用“static"关键字,但是构造器是静态方法。
3、显式的静态初始化:
Java可以将多个静态初始化组织成一个静态块,在块中执行初始化。无论使用哪种初始化,他们都会在使用之前完成初始化动作。见下例:
public class Exercise14_StaticBlock{
static String str1 = "init str1";
static String str2;
// 使用静态块显式初始化
static{
str2 = "init str2";
}
public static void speak(){
System.out.println(str1);
System.out.println(str2);
}
public static void main(String[] args){
Exercise14_StaticBlock.speak();
}
}
// init str1
// init str2
4、非静态实例初始化:
实例初始化,用来初始化非静态变量。和静态初始化块语法相似,少了static关键字。在匿名内部类中,必须用这种方法来初始化。见下例:
public class Exercise15_NonStaticInit{
private String name;
private String addr;
// 非静态成员的实例初始化
{
name = "init name";
addr = "init addr";
}
public static void main(String[] args){
Exercise15_NonStaticInit exer = new Exercise15_NonStaticInit();
System.out.println(exer.name);
System.out.println(exer.addr);
}
}
// init name
// init addr
八、数组初始化
public class Execise_Array{
public static void main(String[] args){
// 1,定义数组的语法,'[]'放在变量前,变量后都可以
int[] iArray, iArray2[];
Integer[] intArray;
// 2,语法错误:不能在编译期给定数组大小
// int iArray3[3];
// 3,可以在运行期分配大小
iArray = new int[3];
intArray = new Integer[3];
// 4,iArray.length表示数组内元素个数,不可以对其修改
for(int i = 0; i < iArray.length; i++){
// 5,基本数据类型会初始化为默认值,int为0
System.out.println(iArray[i] + ", ");
}
for(Integer i : intArray){
// 6,对象数据必须被初始化,分配空间,否则不能引用其对象,其值为'null'
System.out.println(i + ", ");
// 运行时异常:因为i未初始化
// System.out.println(i.intValue());
}
iArray = new int[] { 1, 2, 3 };
intArray = new Integer[] { new Integer(1), new Integer(2) };
for(int i = 0; i < iArray.length; i++){
System.out.println(iArray[i]);
}
for(Integer i : intArray){
System.out.println(i);
System.out.println(i.intValue());
}
}
}
// 0,
// 0,
// 0,
// null,
// null,
// null,
// 1
// 2
// 3
// 1
// 1
// 2
// 2
习题16,创建一个String对象数组,为每个元素赋值一个String对象,并打印出来:
public class Exercise16_StringArray{
public static void main(String[] args){
String[] strArray = { "I", "love", "you" };
String strArray2[] = new String[] { new String("I"), new String("miss"), new String("you"), };
for(String str : strArray){
System.out.print(str + " ");
}
System.out.println();
// Arrays.toString方法产生一个一维数组的可打印版本
System.out.println(Arrays.toString(strArray));
for(String str : strArray2){
System.out.print(str + " ");
}
}
}
// I love you
// [I, love, you]
// I miss you
习题17、18:创建一个类,它有一个接受一个String参数的构造器,在构造器中打印出该参数。创建一个该类对象的引用数组,运行程序,看构造器中的打印是否打印出来了,对引用数组中的对象赋值,再次观察。
public class Exercise17_ObjectArray{
private String name;
Exercise17_ObjectArray(String name){
this.name = name;
System.out.println(this.name);
}
public static void main(String[] args){
System.out.println("no init object Array");
Exercise17_ObjectArray[] objectArray = new Exercise17_ObjectArray[4];
System.out.println("init object Array");
Exercise17_ObjectArray[] objectArray2 = new Exercise17_ObjectArray[] { new Exercise17_ObjectArray("You"),
new Exercise17_ObjectArray("Get") };
}
}
// no init object Array
// init object Array
// You
// Get
1、可变参数列表
package com.study.ch5;
public class Execise19_Variable{
// 语法:变量名 + "..." + 参数名
static void printString(String... args){
// 语法:可以像数组一样用Foreach方法来对可变参数取值
for(String str : args){
System.out.print(str + ", ");
}
System.out.println();
}
public static void main(String[] args){
printString("I", "miss", "you");
printString(new String[] { "Hellow", "World", "!" });
}
}
// I, miss, you,
// Hellow, World, !,
public class Excise20_VariableMain{
public static void main(String... args){
// 调用可变参数语法的main
test.main("I", "have", "a test");
}
}
class test{
public static void main(String... args){
for(String str : args){
System.out.println(str);
}
}
}
// I
// have
// a test
九、枚举
编译器会自动为enum添加一些特性,如:
toString()方法,可以显示某个enum实例的名字;
ordinal()方法,表示某个enum常量的声明顺序;
static values(),按照enum的声明顺序,产生由这些常量构成的数组。
package com.study.ch5;
public class Excercise21_Money{
public static void main(String[] args){
// ?问题 不用import Enum_Money也行啊
Enum_Money money2 = Enum_Money.FIFTY_YUAN;
// values():产生由这些常量构成的数组
for(Enum_Money money : Enum_Money.values()){
// ordinal(),常量声明顺序
System.out.println(money + ", ordinal : " + money.ordinal());
switch(money){
// 不能用 Enum_Money.ONE_YUAN
case ONE_YUAN:{
System.out.println("this is 1 yuan.");
break;
}
case FIVE_YUAN:{
System.out.println("this is 5 yuan.");
break;
}
case TEN_YUAN:{
System.out.println("this is 10 yuan.");
break;
}
case FIFTY_YUAN:{
System.out.println("this is 50 yuan.");
break;
}
default:{
System.out.println("error");
}
}
}
}
}
// ONE_YUAN, ordinal : 0
// this is 1 yuan.
// FIVE_YUAN, ordinal : 1
// this is 5 yuan.
// TEN_YUAN, ordinal : 2
// this is 10 yuan.
// FIFTY_YUAN, ordinal : 3
// this is 50 yuan.