首页 > 代码库 > Java超简明入门学习笔记(三)
Java超简明入门学习笔记(三)
Java编程思想第4版学习笔记(三)
第五章 初始化与清理(构造器和垃圾回收机制)
Java有和C++类似的构造函数来为新创建的对象执行初始化及完成一些特殊的操作,有的类数据成员可能会被初始化几次,它们的初始化次序和次数是根据程序决定的,可以用重载的构造函数以不同的形式对一个对象初始化,重载的构造函数之间可以通过this互相调用。最后,本章讲了finalize()函数和简单的GC机制,也提到了如何创建一个数组。
知识点1:P76,5.1,定义构造函数
当对象被创建时,构造函数会自动被调用,构造器的函数名和类名相同,无返回值类型(也不是void类型,就是不需要任何类型),可以有任意个参数,在函数体里写上你想让该类对象被创建时会发生的事情。创建对象时要给对象符合构造器(构造函数的另一种说法)要求的参数。不需要任何参数的构造器被称为“无参构造器”或“默认构造器”。
如果你写的类没有任何构造函数,Java会自动帮你创建出一个无参构造器,它做的事只是把类内成员初始化为0或那个类型默认的初始值。如果你写了一个或多个构造器(无论带不带参数),系统将不再自动生成一个无参构造器。
一个含有显式构造器的类的实例被初始化的顺序是这样的:
在构造函数被调用之前,静态变量首先被初始化为默认值或类内初始值(这一行为被这本书称为指定初始化,具体做法是在类字段定义时就用字段=对象;的方式初始化字段),然后一般变量首先被初始化为默认值或类内初始值。之后,会执行接下来会提到的“初始化子句”,最后,构造函数如果有初始化类数据成员的语句,则,这些语句依次对数据成员初始化。另外,静态类数据成员只要被访问,哪怕没有实例存在,也会完成初始化。
知识点2:P77,5.2,方法重载
同一个类里可以有多个同名函数,这些函数的参数类型或参数数量不一样使使用者可以通过不同的参数组合调用一个名字的参数,这种形式叫方法重载。定义一个重载的函数,只需要通过正常的定义函数的形式,把函数名设定为想要重载的函数名,把参数列表变成不一样的就行了。
重载之后,如果传入的实参类型并非任意一个重载函数需要的类型,但经过非窄化转换仍能匹配所有的重载函数需要的参数类型,这个参数就会类型提升为更接近它的那一个函数需要的参数类型,参与运算。
知识点3:P84,5.4,this
this可以出现在非静态函数体内,代表当前函数所属对象的一个引用。在一个构造器中调用另一个构造器,必须使用this(构造器参数);的形式,一个构造器中只能通过这种方法调用一次别的构造器,且这个调用要放在函数最开始。
知识点4:P87,5.5,垃圾回收与finalize()函数
垃圾回收器要回收对象的时候,首先要调用这个类的finalize方法),一般的纯Java编写的Class不需要重新覆盖这个方法,因为Object已经实现了一个默认的,除非我们要实现特殊的功能。如果想覆盖Object的finalize方法,只需声明一个 protected void finalize( ) { } 这样的函数。在函数里写一些你想要Java的GC回收对象之前你想做的事情。
Java中有一个有趣的函数,叫System.gc();,这个函数的作用是提醒JVM:程序员觉得应该进行一次垃圾回收了。至于到底JVM真的开始了垃圾回收,还是没有理你的这个意愿,认为根本没必要浪费时间和资源进行垃圾回收,那就是JVM自己的事情了。
Java垃圾回收的机理在于遍历对象和对象的引用,只要发现某个对象不可达,即在代码执行的某一过程中无法通过任何引用(或者这个对象目前根本就没有引用)访问这个对象,这个对象就失去的存在的意义,会被回收。不过具体的回收时机,不同的JVM实现上也不尽相同。
知识点5:P87,5.7.3,静态初始化子句和实例初始化子句
如果你需要在构造函数里额外执行一些语句,又实在懒得写构造函数,你可以使用如下所示的方法(静态/实例初始化子句)实现这个目的。红色的部分就是初始化子句。初始化子句会在默认初始化和构造器初始化之间被调用。
class staticClass{
static int i = 0;
static{
i = 9;
System.out.println("i = 9");
}
}
class sampleClass{
int i =0;
{
i = 89;
System.out.println("i = 89");
}
static int i = 0;
static{
i = 9;
System.out.println("i = 9");
}
}
class sampleClass{
int i =0;
{
i = 89;
System.out.println("i = 89");
}
}
知识点6:P98,5.8,数组
数组是一种能够容纳多个元素的重要结构。要定义一个某种类型的数组,只需这样做:类型名[] 数组名; 或者 类型名 数组名[]; ,比如 int[] a1; 和一些语言不同,不能够用类似C/C++那样的方式指定数组的大小,只能创建一个数组的引用(即数组名)来操控它代表的真正数组对象。使用一个空(值为null)的数组引用也将出现错误,因此要在这个数组引用初始化之后再使用。可以使用初始化表达式,即一对大括号包围起来的一个元素的列表来初始化数组,比如int[] a = { 1,2,3,4 };,这种初始化方法只能够用于数组定义之处。也可以把另一个数组的引用所指代的对象传递给这个引用,比如int[] a2 = a;。最后,还可以通过new关键字创建数组对象和其引用,比如int[] c = new int[30]; 或者 int[] a = new int[]{1,2,3,4}; 来初始化。
正如上一章所说,数组可被用于foreach循环。
数组对象有一个叫做toString的方法,这个方法不需要参数,可以把数组转换成合适的字符串。也可以使用Array.toString(数组名)的静态方法达到同样的目的。
知识点7:P102,5.8.1,可变参数列表
我们有时候可能需要这么一种函数,这个函数需要传入一些参数,这些参数类型是固定的,但我们并不知道会传入几个这个类型的参数。这时我们就需要这个特性。我们可以在参数列表里放一个数组来解决这个问题。
比如 void func(int 1, char[] c); 我们想要使用这个函数,就可以这么调用它:func(1, new char[]{‘a‘,‘b‘,‘c‘}); 不过这种写法需要显式创建一个数组对象并且传入,我们可以用这种写法代替原来的函数定义void func(int i, char... c){}; ,这种定义方式可以允许我们这么调用它:func(1,‘a‘,‘b‘,‘c‘); ,大大简略了语法。Java的函数重载时,即使实参可以转化为多种函数需要的形参类型,不过Java有粒度很细的优先转换的规则,可以使这个调用总能匹配到一个“最容易转化”的,因此不会产生二义性调用(C++函数匹配则只有5个等级,每个等级间的各个调用形式是优先度一致的,因此可能产生二义性调用)。
可变参数列表必须放在参数列表的最后且每个函数只能有一个可变形参。
知识点8:P105,5.9,枚举类型
和其他主流语言一样,Java也提供了枚举类型,它的关键字是enum,用来方便的定义枚举类型,定义枚举类型的方式类似与定义一个类:enum testSize{ SMALL, MIDIUM, LARGE }; ,使用时,可以为testSize枚举类型创建一个实例:testSize s = testSize.SMALL; 枚举类型的对象有toString,ordinal等方法,ordinal()返回一个从0开始的当前枚举值在枚举类型中被定义时的次序。此外,枚举类型还提供values()静态函数,返回所有枚举情况的集合,使编程者可以遍历每个枚举值。
第五章 练习题
练习1、2:创建一个类,它包含一个未初始化的String域,一个在定义时就初始化的String域,一个构造器初始化的String域。验证第一个String域被默认初始化为了null,探究第二和第三个String域的差异。
1 class StringTest{ 2 StringTest(){ 3 s3 = "4567"; 4 } 5 6 String s1; 7 String s2 = "1234"; 8 String s3; 9 } 10 11 public class MainTest { 12 public static void main(String[] args) { 13 StringTest st = new StringTest(); 14 System.out.println(st.s1); 15 System.out.println(st.s2); 16 System.out.println(st.s3); 17 } 18 }
练习3、4:创建一个带无参构造器的类,在构造器中打印一条信息,为这个类创建一个对象。在为这个类创建一个重载构造器,这个构造器需要一个字符串做参数,并把接收的字符串也打印出来。
1 class Test{ 2 Test(){ 3 System.out.println("Test"); 4 } 5 6 Test(String s){ 7 System.out.println(s+"test"); 8 } 9 } 10 11 public class MainTest { 12 public static void main(String[] args) { 13 Test t = new Test(); 14 Test t2 = new Test("1234"); 15 } 16 }
练习5、6:创建一个名为Dog的类,它具有重载的bark()方法,此方法根据不同的基本数据类型进行重载,并根据被调用的版本,打印出不同类型的barking,howling等信息。编写对应的主函数调用所有不同版本的方法。再试着写两个构造函数,它们都需要两个不同类型的参数,但是这两个构造函数需要的参数类型顺序正好相反,试试调用它们。
1 class Dog{ 2 Dog(int i){ 3 System.out.println("barking"); 4 } 5 6 Dog(double d){ 7 System.out.println("howling"); 8 } 9 10 Dog(char c){ 11 System.out.println("Meow~"); 12 } 13 14 Dog(int i, boolean b){ 15 System.out.println("I B"); 16 } 17 18 Dog(boolean b, int i){ 19 System.out.println("B I"); 20 } 21 } 22 23 public class MainTest { 24 public static void main(String[] args) { 25 Dog d1 = new Dog(1); 26 Dog d2 = new Dog(1.5); 27 Dog d3 = new Dog(‘c‘); 28 29 Dog bi1 = new Dog(1,true); 30 Dog bi2 = new Dog(true,1); 31 } 32 }
练习7:创建一个没有构造器的类,并在main()中创建其对象,用以验证编译器是否真的自动加入了默认构造器。
1 class Test{ 2 int i; 3 double d; 4 } 5 6 public class MainTest { 7 public static void main(String[] args) { 8 Test t = new Test(); 9 System.out.println(t.i); 10 System.out.println(t.d); 11 } 12 }
练习8:编写具有两个方法的类,在第一个方法内调用第二个方法两次:第一次调用时不使用this关键字,第二次调用使用关键字,来验证this关键字的作用。
1 class Test{ 2 void method1(){ 3 method2(); 4 this.method2(); 5 } 6 7 void method2(){ System.out.println("Ah,be called!!"); } 8 } 9 10 public class MainTest { 11 public static void main(String[] args) { 12 Test t = new Test(); 13 t.method1(); 14 } 15 }
练习9:编写具有两个(重载)构造器的类,并在第一个构造器中通过this调用第二个构造器。
1 class Test{ 2 Test(String s, double i){ 3 this(i); 4 ss = s; 5 6 System.out.println("name: "+ss); 7 System.out.println("Area: "+ii); 8 } 9 10 Test(double i){ 11 ii = i*i*3.14; 12 } 13 14 String ss; 15 double ii; 16 } 17 18 public class MainTest { 19 public static void main(String[] args) { 20 Test t = new Test("Circle",2); 21 } 22 }
练习10、11:编写具有finalize()方法的类,并在方法中打印消息,在main()中为该类创建一个对象,研究finalize和System.gc()和finalize()的联系。
1 class Test{ 2 protected void finalize(){ 3 //super.finalize(); 4 System.out.println("Ah!!NO!!!"); 5 } 6 } 7 8 public class MainTest { 9 public static void main(String[] args) { 10 Test t = new Test(); 11 System.gc(); 12 } 13 }
练习12:编写名为Tank的类,此类的状态可以是“满的”或者“空的”,其终结条件是:对象是空的,编写finalize()函数以在gc之前检验对象状态。
1 class Tank{ 2 protected void finalize(){ 3 //super.finalize(); 4 if(isFull){ 5 System.out.println("Not Good"); 6 } 7 else{ 8 System.out.println("Good!"); 9 } 10 } 11 12 boolean isFull = false; 13 } 14 15 public class MainTest { 16 public static void main(String[] args) { 17 Tank t = new Tank(); 18 System.gc(); 19 } 20 }
练习13:调试书上代码,略。
练习14:编写一个类,拥有两个静态字符串域,其中一个在定义处初始化,另一个在静态块中初始化。现在加入一个静态方法打印这两个字段的值,查看它们是否都会在被使用之前完成初始化动作。
1 class Test{ 2 static String s1 = "1234"; 3 static String s2; 4 static{ 5 s2 = "4567"; 6 } 7 8 static void func(){ 9 System.out.println(Test.s1); 10 System.out.println(Test.s2); 11 } 12 } 13 14 public class MainTest { 15 public static void main(String[] args) { 16 Test.func(); 17 } 18 }
练习15:编写一个含有字符串域的类,并采用实例初始化方式进行初始化。
1 class Test{ 2 String s1; 3 { 4 s1 = new String("1234"); 5 } 6 }
练习16:创建一个String对象数组,并为每一个元素都赋值一个String,用for循环来打印此数组。
1 public class MainTest { 2 public static void main(String[] args) { 3 String[] arrayString = {"1111","2222","3333","4444"}; 4 for(String s:arrayString) 5 { 6 System.out.println(s); 7 } 8 } 9 }
练习17、18:创建一个类,它有一个构造器,这个构造器接收一个String类型的参数。在构造阶段,打印此参数。创建一个该类对象的引用数组,但是不实际地创建对象赋值给该数组。试着运行程序。再试着通过创建对象,再赋值给引用数组,从而完成程序。
1 class Test{ 2 Test(String s){ 3 System.out.println(s); 4 } 5 } 6 7 public class MainTest { 8 public static void main(String[] args) { 9 Test[] arrayTest = new Test[]{ 10 new Test("123"), 11 new Test("456") 12 }; 13 } 14 }
练习19:创建一个类,它的构造函数接受一个可变参数的String数组。验证你可以向该方法传递一个用逗号分隔的String实参列表,或是一个String[]。
1 class Test{ 2 Test(String... s){ 3 } 4 } 5 6 public class MainTest { 7 public static void main(String[] args) { 8 Test t1 = new Test("111","222"); 9 Test t2 = new Test(new String[]{"333","444"}); 10 } 11 }
练习20:创建一个使用可变参数列表而不是用普通main()语法的主函数main(),打印args数组的传入的命令行参数。
1 public class MainTest { 2 public static void main(String... args) { 3 for(String s:args) 4 { 5 System.out.println(s); 6 } 7 } 8 }
练习21、22:创建一个enum,它包含纸币中最小面值的6种类型,通过values()循环并打印每一个值以及其ordinal()。最后再试着使用带enum类型的switch语句。
1 enum CNY{ 2 CNY1,CNY5,CNY10,CNY20,CNY50,CNY100; 3 } 4 5 public class MainTest { 6 public static void main(String[] args) { 7 for (CNY temp : CNY.values()) { 8 System.out.println(temp + " " + temp.ordinal()); 9 } 10 11 CNY cnyTemp = CNY.CNY5; 12 switch (cnyTemp){ 13 case CNY1: 14 System.out.println("1 Yuan"); 15 break; 16 17 case CNY5: 18 System.out.println("5 Yuan"); 19 break; 20 21 case CNY10: 22 System.out.println("10 Yuan"); 23 break; 24 25 case CNY20: 26 System.out.println("20 Yuan"); 27 break; 28 29 case CNY50: 30 System.out.println("50 Yuan"); 31 break; 32 33 case CNY100: 34 System.out.println("100 Yuan"); 35 break; 36 37 default: 38 System.out.println("Error : Fake Money"); 39 } 40 } 41 }
Java超简明入门学习笔记(三)
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。