Java编程思想第4版学习笔记(一)
第二章 一切都是对象(Hello World)
这个笔记本主要记录了我在学习Java编程思想(第4版,中文版)的过程中遇到的重难点及其分析。主要参考了C++11版本的C++语言,对比了它们不同的部分。
知识点0:第一章概括
在探讨第二章的内容之前,先简要地概括一下第一章说了什么,第一章的标题叫做“对象导论”,第二章叫做“一切都是对象”,这种令人混淆的说法或许让人乍一看很难区分第一二章讲的内容有什么区别。
不过显然,这两章主要讲述的内容是不同的——第一章主要讲了面向对象编程中一些重要的概念,这些概念在任何一门适合配合面向对象编程的思想进行开发的语言上我们都能看到——就是 对象的概念、接口(类方法)的概念、用对象组合程序的概念、访问控制的概念、继承和派生的概念、多态的概念、容器的概念 和 泛型的概念。
第一章里提到了一些Java和这些概念的关联和几个细节,比如设计类的内聚性和耦合性、派生类对基类方法的覆盖、编译时的前期绑定和后期绑定、Java的一切基于Object的单根继承结构、内存分配、并发 和 网络编程等概念。第一章涵盖了这些也许要把整本书阅读完再回过头看才能明白一些的基础和复杂的概念,它们并不完整,有很多点都需要在实践中不断的补充完善。因此也许初学者不需要一开始纠结于弄明白第一章,而是要在学会C/C++之类的语言的一些基础后,起码编过程序,再来学习这本书,粗读第一章,然后从第二章开始。
第二章主要讲了Java中对象的表现形式——引用、基本类型和其包装器类型、简单的对象生命周期/作用域、创建类类型、类字段(数据成员)和方法、初始化、类内函数、包、静态成员、HelloWorld、编译运行、注释和Javadoc。(这里面提到的概念比第一章少多了而且更具体),总之,第二章就是一个HelloWorld式的章节,浅显的讲了一下为了你能够不那么迷惑的写出Java语言的HelloWorld而需要知道的Java最重要的一些知识,虽然这些概念也仍然没有得到充分的讲解,不过第二章使你对Java中对象是怎么存在的有了一个基本的认识。
下面就梳理一下第二章中比较重要和难懂的知识点。
知识点1:P21,2.1,创建和操纵对象
创建一个可以操控的对象
Java创建对象的语句形如这样:new 类型(构建对象的参数-可选);,其中new为创建对象的关键字,类型可以是基本类型,包装器类型以及各种类类型,()里是参数列表,代表调用了构造函数,分号代表语句的结尾。这个语句创建了指定类型的对象。要想使用这个对象,必须要声明一个对象类型的引用,引用就是一个别名,Java里引用的概念和C++中引用的概念类似。比如String s = new String("1234");或者是String s = "1234";
初始化
第一句话定义并初始化了一个String类型的引用s,s被赋值为一个String类型的对象,这个对象被初始化为字符串字面值“1234”。
这里发生了两次初始化,引用s的初始化,以及一个不具名的String类型对象的初始化。
一个引用必须在使用(除了赋值操作的其他操作)之前初始化,一个对象创建的时候就一定被构造函数初始化过了,一个未经初始化的引用,其初值为null,使用值为null的引用会导致报错。
内存布局
引用和基本类型的对象都被存储到堆栈中。
其他对象被存储到堆中。
常量储存在代码内部。
作用域和生命周期
基本类型 和 对象引用 的作用域在它所在的语句块内,生命周期从它被定义到它所在的语句块结束。
Java对象的作用域在它所在的语句块内,生命周期从它被定义到它被Java的自动垃圾回收机制回收。
知识点2:P23,2.2.2,基本类型及其包装器类型
基本类型有哪些
boolean(布尔值),char(字符),byte(很小的整数),short(比较小的整数),int(整形),long(长整形),float(浮点数),double(高精度浮点数),void(空类型)。
什么是包装器
基本类型没有一个函数,不方便按照面向对象的思想向其传递信息,因此Java给每一个基本类型都对应了一个包装器类型,它们是一些类类型,在堆中被创建,可以执行各种方法,更方便使用。
以上类型对应的包装器类型有哪些
Boolean(布尔值),Character(字符),Byte(很小的整数),Short(比较小的整数),Integer(整形),Long(长整形),Float(浮点数),Double(高精度浮点数),Void(空类型)。
基本类型的特点
大小固定,存在堆栈中,过作用域就被释放,不是对象,不需要用引用操控,没有可以调用的函数,按值传递。
包装器类型的特点
大小固定,存在堆中,由GC释放,本身是对象,需要依赖引用操控,有函数可以调用,按引用传递。
初始化基本类型的方法
可以一开始就用字面值初始化,比如char c = 'c';
也可以用另一个基本类型的值初始化,比如char ch = c;
还可以用这个基本类型的包装器类型初始化基本类型的值,比如char ch2 = new Character('c');
如果不在创建之初就初始化一个作为类成员的基本类型,则这个基本类型的变量会得到一个默认值,比如0。
初始化 基本类型的包装器类型的引用 的方法
可以一开始就用字面值初始化,比如Character c = 'c';
也可以 先用字面值初始化这个包装器类型的对象,再用这个对象初始化这个包装器类型的引用,比如Character ch = new Character('c');
还可以 先用基本类型的值初始化这个包装器类型的对象,再用这个对象初始化这个包装器类型的引用,比如Character ch = new Character(c);
知识点3:P25,2.4,类类型
什么是类类型
类类型是一种区别于基本类型的类型分类,它允许Java使用者自己创造一些新的类型和这些类型的细节。
创建和使用类类型
创建类类型的代码看起来如下:class 类型名{ 类体 },其中,class是创建新类型的关键字,类型名是一个大写字母开头的名字,类体包括数据成员(属性)和函数成员(方法)。
要想创建这个自定义类型的对象,需要使用下述语句:类型名 对象的引用名 = new 类型名(构造对象的参数-可选);
可见自定义的类类型和Java已经存在的类类型的用法基本一致。
什么是类属性/字段/数据成员/域(在特定语境下)
字段(或者属性,数据成员,域)基本都是一个意思,是组成类的两个元素之一,它可以由一个或几个,可以是基本类型或引用类型,是用来存储类实例数据的类成员(或称“实例域”)。
什么是类方法/类函数/函数成员
类方法(method)是组成类的两个元素之一,可以有一个或多个,是用来操控修改数据成员的子程序(可复用程序片段),在Java中,没有全局的函数,一个函数(方法)必定属于某个类。
知识点4:P27,2.5,类方法
定义和使用类方法
类定义一个类方法的语句形如 返回值类型 类方法名(参数列表){ 方法体 },必须定义在class后面的大括号里,其中,参数列表形如(类型1 参数1,类型2 参数2,etc)。如果返回类型不为void/Void,方法体中需显式地写出return语句来结束这个函数,形如return 返回值; 其中返回值可以为对象的引用或者基本类型的变量,也可以是一个字面值,一个常量。这个值的类型需和方法定义中的返回值类型一致或能非窄化地转换为返回值类型。比如double func(){return 1;},其中返回的1是int类型字面值,它转换到定义的double类型精度无损失(非窄化转换)。因此这样做可行。
使用一个类方法,需要用点运算符。形如 对象引用.类方法名(想传入的参数);
静态方法有特殊的调用方式,本章稍后就能看到。
知识点5:P28,2.6.1,import是什么,怎么用
包(Java类库)和名字冲突
包就是一个Java类库,是程序员写好、打包好的一些类、方法的集合。
我们在写Java程序中往往要根据一些已经实现好的功能做我们的新功能,而不是什么都自己开发。所以可以在你的Java编程IDE中进行设置来导入一个别人打包好的功能,通过在代码中使用import 这个包的名.具体的类; 来使用你导入的包中一个具体的类,或者使用import 这个包的名.*; 把这个语句放在一个Java文件的开头,来导入这个包中所有的类。大部分编译器都有编译优化,会只编译你用到的,不过仍然要说明这么一句,让编译器知道你可能会用到的内容。
Java自己就提供了丰富的类库,其中java.lang类库是基础类库,每次都会被自动导入,而java.uitl非常常用,类似于C++中的SL,常用的写法是import java.util.*; 来导入java.util中的所有类。
我们可能导入了多个包,不同的包中可能声明了同名的类,不过同一个文件中不能用同名的类,甚至在同一个方法下的不同作用域里,变量名都不能相同,所以显然无论你导入了几个包,它们中有几个你想用的同名的类,你都只能使用其中的一个。
用import 这个包的名.具体的类; 就可以明确的告诉编译器,你想要使用哪个包里的这个名字的类。
很多包名好像一个网站的网址倒过来写,比如com.google.xxxx(我随便举的例子)。这是因为网址是不重复的,不同的类库的开发者很难拥有同一个网址,所以Java希望你给包起名时用自己的网站倒过来的网址。试想你和另一个人都使用了com.baidu.useless作为包名,里面都有一个叫做Date的类,这两个类其实是不同的(但导入都要写成import com.baidu.useless.Date;),别的开发者拿到你们两个人的包想使用Date类时就会头疼了。
知识点6:P29,2.6.3,static关键字
静态成员的定义、性质和使用
static关键字用于放在定义语句的类型前面,说明正在定义的字段/方法是静态的,被static修饰的类方法和类字段统称为静态成员。
比起同一个类型的其他成员,所有这个类型的实例中的静态成员都是同一个对象,占用一个存储空间,它不依赖某个具体实例化的对象而存在,即使没有这种类型的对象,这种类型的静态成员也能被访问。
举个例子,class StaticTest{ static int i = 47; } 在这个类中,i就是静态成员,而且被预初始化为47,直接在类体中写类似赋值的形式就行了,即使创建了两个StaticTest类的实例,比如StaticTest st1 = new StaticTest();StaticTest st2 = new StaticTest();,st1和st2中的i仍是同一个变量。
你可以试着把st2中的i赋值成88,写下这个语句 st2.i = 88; ,之后你再检查st1.i的值,就会发现它也变成了88。
访问StaticTest的静态数据成员有两种方法,一种是上述例子中的st1.i,通过点运算符,链接对象引用和对象内成员,这个常见的访问成员的方式对静态成员同样适用。还有一种访问方法是静态成员独有的,就是 类名.静态成员名 ,通过这种简单的方法你也可以访问到它。在StaticTest例子中想访问静态成员i还可以这么写:StaticTest.i,效果和 st1.i 或者 st2.i 是没有区别的。
静态方法也可以这么访问,比如class StaticTest{ static void Func(){} },这个新的StaticTest类里定义了什么都不做的静态方法(静态函数成员)Func,可以使用StaticTest.Func();,在不创建StaticTest对象的情况下访问这个类内静态的函数。
知识点7:P30,2.6.3,主函数
主函数和HelloWorld
在目前的很多IDE下,Java可以有多个主函数,不过你要选择其中的一个进行执行。主函数的写法很简单,不过因为Java的所有函数必须以类方法的形式存在,因此你需要先随便创建一个单独的类:
public class MainTest{}
class前面的public代表一种公开状态的访问权限,具体使用方法不在这章讲,总之要写成这样。MainTest可以起成任何别的你喜欢的名字,不过一个Java文件里只能有一个public修饰的类,而且这个Java文件的文件名必须和public修饰的类名保持一致。
然后,在类中写一个特殊的方法:
public class MainTest{
public static void main(String[] s){
//主函数第一行,程序执行从这里开始
}
}
就像这样,类必须是publc的,方法必须是public static的(如你所见,它们并列修饰一个函数时,public关键字要在static前面),返回值为void,参数类型是一个String数组s,用来存储命令行参数,就和其他的语言差不多。
这样,一个主函数就写好了,书上给出了一个很简单的HelloWorld:
import java.util.*;
public class MainTest{
public static void main(String[] s){
System.out.println("Hello World!");
System.out.println(new Date());
}
}
让我们稍微分析一下这个HelloWorld,首先是import java.util.*;,导入java.util包中的所有类,我们下面用到了Date类,因此需要引入这个包。
主函数里,System是一个类,out是System类的静态对象,println是out对象的静态方法,因此即使不创建System类,也能用点运算符访问它的对象,再用点运算符访问其对象的方法。
println是一种很常见的方法,println可以向控制台输出它接收到的信息,比如一个字符串字面值"HelloWorld!"或者new Date(); 创建出的一个对象。
知识点8:P32,2.8,三种注释
C风格注释
你可以把注释内容写在/* */里,可跨多行,一旦标记了/*,它遇到下一个*/就会结束注释。
单行注释
你可以把注释内容写在//标记之后,知道本行结束,不可跨多行。
用于提取文档的注释(注释文档)
Java提供了Javadoc机制从源码的注释中提取和生成html文档,不过这个注释要符合特定格式,
用于提取文档的注释以/**开头,以*/结束。这样的注释也可跨多行,注释间每行开始如果有一个*或任意空白都可被忽略,为了更方便的提取文档,可以在这种注释里直接加入html代码,比如:
/**
*具体类信息可以到这里查找<br>
*<a href = "https://www.baidu.com"></a>
*/
把这段注释文档放在某个你想注释的类,字段 或者 方法前面一行,再用javadoc提取文档你就能看到你对这个类/字段或者方法的注释,后面我们会提到,只能对标记为public或protect的类/域/方法做注释。
也可以通过@符号加一些标记让javadoc快速自动帮你生成符合情境的html文档,比如:
/**
*@author TiriSane
*/
提取文档后,这个注释对应的类/域/方法介绍上会有作者这一项。除了author还有很多标记可以用:
@see 类名
@see 类名#类成员名
@see可以在你介绍一个类成员时,链接到其他类/类成员的文档。
{@link 类名#类成员名 你想要显示的标签名}
@link作用类似域@see,不过文档中超链接处会显示标签名而不是类名。
@version
用于说明版本信息。
@since
用于说明代码使用JDK的最早版本,比如@since JDK 1.8
@author
用于说明作者信息。
@param 参数名 描述
带@param的注释需放在一个类方法之前,用于说明这个方法的其中一个参数的意义。
@return 描述
带@return的注释需放在一个类方法之前,用于说明这个方法的返回值。
@throws 异常类 异常说明
目前还没讲到异常,简言之,就是用来 放在方法前 描述这个方法在何种情况下可能会产生这种异常。
@deprecated
用于指出注释文档所注释的类/域/方法是过时的,下个版本可能就删掉了,建议使用你代码的人不要使用这个标记的类/域/方法。
{@docRoot}
这个标签往往配合一些注释文档里的html代码使用,指出了文档的根目录,是一种相对路径标记。
{@inheritDoc}
这个标记所在的注释文档要放在类前面,生成javadoc时会把它的直接继承类的注释复制到这个类里。
练习题:P37
练习1:创建一个类,它包含一个int域(字段)和一个char域(字段),他们都没有被(显示地)初始化,将它们的值打印出来,以验证Java(对类内字段)执行了默认初始化。
1 class Test 2 { 3 int i; 4 char c; 5 } 6 7 public class MainTest { 8 public static void main(String[] args){ 9 Test t = new Test(); 10 System.out.println(t.i); 11 System.out.println(t.c); 12 } 13 }
练习2:创建一个输入"Hello, World"的程序,编译和运行它。
1 public class MainTest { 2 public static void main(String[] args){ 3 System.out.println("Hello, World"); 4 } 5 }
练习3:找出本章含有ATypeName的代码段,将其改成完整的程序。
1 class ATypeName{} 2 3 public class MainTest { 4 public static void main(String[] args){ 5 ATypeName a = new ATypeName(); 6 } 7 }
练习4、5:找出本章含有DataOnly的代码段,将其改写成一个程序,并把DataOnly的数据打印出来。
1 class DataOnly{ 2 int i; 3 double d; 4 boolean b; 5 } 6 7 public class MainTest { 8 public static void main(String[] args){ 9 DataOnly data = new DataOnly(); 10 data.i = 47; 11 data.d = 1.1; 12 data.b = false; 13 } 14 }
练习6:编写一个程序,使它含有本章定义的storage方法的代码段,并调用之。
1 class Storage{ 2 int Storage(String s){ 3 return s.length()*2; 4 } 5 } 6 7 public class MainTest { 8 public static void main(String[] args){ 9 Storage st = new Storage(); 10 st.Storage("1234"); 11 } 12 }
练习7:将和Incrementable相关的代码段改写成一个完整的可编译源代码。
1 class StaticTest { 2 static int i = 47; 3 } 4 5 class Incrementable{ 6 static void increment(){ StaticTest.i++; } 7 } 8 9 public class MainTest { 10 public static void main(String[] args){ 11 Incrementable icmt = new Incrementable(); 12 icmt.increment(); 13 14 Incrementable.increment(); 15 } 16 }
练习8:编写一个程序,展示无论你创建了某个特定类型的多少个对象,这个类中的某个特定的static域只有一个实例。
1 class StaticTest { 2 static int i; 3 } 4 5 public class MainTest { 6 public static void main(String[] args){ 7 StaticTest st0 = new StaticTest(); 8 StaticTest st1 = new StaticTest(); 9 StaticTest st2 = new StaticTest(); 10 11 st0.i = 1; 12 st1.i = 2; 13 st2.i = 3; 14 15 System.out.println(st0.i); 16 System.out.println(st1.i); 17 System.out.println(st2.i); 18 } 19 }
练习9:编写一个程序,展示自动包装功能对所有的基本类型和包装器类型都起作用。
1 public class MainTest { 2 public static void main(String[] args){ 3 boolean b = true; 4 char c = '0'; 5 byte by = 1; 6 short s = 2; 7 int i = 3; 8 long l = 4; 9 float f = 3.14F; 10 double d = 2.17; 11 12 Boolean B = true; 13 Character C = '0'; 14 Byte By = 1; 15 Short S = 2; 16 Integer I = 3; 17 Long L = 4L; 18 Float F = 3.14F; 19 Double D = 2.17; 20 Void V = null; 21 } 22 }
练习10:编写一个程序,打印出从命令行获得的三个参数。为此,需要确定命令行数组中String的下标。
1 public class MainTest { 2 public static void main(String[] args) { 3 System.out.println(args[0]); 4 System.out.println(args[1]); 5 System.out.println(args[2]); 6 } 7 }
练习11:将AllTheColorsOfTheRainbow这个示例改写成一个程序,然后编译、运行。
1 class AllTheColorsOfTheRainbow { 2 int anIntegerRepresentingColors; 3 void changeTheHueOfTheColor(int newHue) { } 4 } 5 6 public class MainTest{ 7 public static void main(String[] args){ 8 AllTheColorsOfTheRainbow acr = new AllTheColorsOfTheRainbow(); 9 acr.changeTheHueOfTheColor(1); 10 } 11 }
练习12、13、14:略,javadoc操作相关。
练习15:使用练习2的程序,加入注释文档,并用Javadoc提取出html文件查看效果。
1 /** 2 * @author TiriSane 3 * @version 1.0 4 * @since JDK 1.8 5 */ 6 public class MainTest { 7 /** 8 * @author TiriSane 9 * @version 1.0 10 * @since JDK 1.8 11 * @param args 用于接收命令行参数 12 * @return 没有返回值 13 */ 14 public static void main(String[] args){ 15 System.out.println("Hello, World"); 16 } 17 }
练习16:找到第5章的Overloading.java示例,并为它加入javadoc文档,之后用Javadoc提取出html文件查看效果。
1 /** 2 * @author Bruce Eckel 3 * @version 1.0 4 * @since JDK 1.5 5 */ 6 class Tree{ 7 int height; 8 Tree(){ 9 System.out.println("Planting a seeding"); 10 height = 0; 11 } 12 13 Tree(int initialHeight){ 14 height = initialHeight; 15 System.out.println("Creating new Tree that is " + height + " feet tall"); 16 } 17 18 void info(){ 19 System.out.println(" Tree is " + height + " feet tall"); 20 } 21 22 void info(String s){ 23 System.out.println(s + ": Tree is " + height + " feet tall"); 24 } 25 } 26 27 public class Overloading { 28 public static void main(String[] args){ 29 for(int i = 0;i<5;i++){ 30 Tree t = new Tree(i); 31 t.info(); 32 t.info("overloaded method"); 33 } 34 new Tree(); 35 } 36 }