该读书笔记是我看java编程思想4的一些摘抄,这个不一定适合你,所以题目叫做我的java编程思想读书笔记,如果对于该书感兴趣,我建议还是自己看看。
1.所有的类最终都继承自单一的基类-Object类,这就是单根继承结构,那么所有的接口是不是都继承自单一的基接口呢??
2.java数据的存储:java对象主要存在于RAM中的堆中,堆不同于堆栈的好处是:编译器不需要知道存储的数据在堆里存活多长时间,因此,在堆里分配存储有很大的灵活性。但这种灵活性的代价是:用堆进行存储分配和清理可能比用堆栈进行存储分配需要更多时间。
常量存储:在嵌入式系统中,常量存放在ROM中,那么java的常量存储在哪??
3.java对象是通过引用来操作的,通过new来为引用创建一个对象,而对于java的基本类型,不是用new来创建变量的,而是创建一个并非引用的“自动”变量。这个变量直接存储“值”,并置于堆栈中,因此效率更高。
但基本类型具有包装器类,使得可以在堆中创建一个非基本对象,用来表示对应的基本类型。
4.BigInteger支持任何经度的整数。也就是说,在运算中,可以准确地表示任何大小的整数值而不会丢失任何信息。BigDecimal支持任何精度的定点数。
5.当创建一个数组对象时,实际上就是创建一个引用数组,并且每个引用都会自动被初始化为一个特定值,该值拥有自己的关键字null。一旦java看到null,就知道这个引用还没有指向某个对象。在使用任何引用前,必须为其指定一个对象;如果试图使用一个还是null的引用,在运行时将会报错。因此,常犯的数组错误在java中就可以避免。
6.java是一种自由格式(free-form)的语言,所以,空格、制表符、换行都不会影响程序的执行结果。
7.代码如下:
{
String s = new String("a string");
}
引用s在作用域终点就消失了。然而,s指向的String对象仍然继续占据内存空间。这时候,就需要java的垃圾回收机制,这样可以避免内存溢出的问题。
8.若某个类的数据成员是基本数据类型,即使没有进行初始化,java也会确保它获得一个默认值。注:当变量作为类的成员使用时,java才确保给定其默认值。然而上述确保初始化的方法并不适用“局部”变量(即并非某个类的字段),如果是这种情况,变量没初始化,java会报错。
在类里定义一个对象引用(类的数据成员)时,如果不将其初始化,此引用就会获得一个特殊值null。通过此对象引用调用对象方法就会出现运行时错误。
9.方法的基本组成部分包括:名称、参数、返回值和方法体。
方法名和参数列表(它们合起来被称为“方法签名”)唯一地标识出某个方法。
10.java中的方法若返回类型是void,这时return关键字的作用只是用来退出方法。
11.java消除了所谓的“向前引用”问题,但javascript没有消除这个问题。
12.在java对象中,执行new来创建对象时,数据存储空间才被分配,其方法才供外界调用。
13.操作符作用于操作数,生成一个新值。另外,有些操作符可能会改变操作数自身的值,这被称为“副作用”。那些能改变其操作数的操作符,最普遍的用途就是用来产生副作用;但要记住,使用此操作符生成的值,与使用没有副作用的操作符生成的值,没有什么区别。
几乎所有的操作符只能操作“基本类型”。例外的操作符是“=”、“==”和“!=”,这些操作符能操作所有的对象(这也是对象令人糊涂的地方)。除此之外,String类支持“+”和“+=”。
14.基本类型存储了实际的数值,而并非指向一个对象的引用,所以在为其赋值的时候,是直接将一个地方的内容复制到了另一个地方。例如,对基本数据类型使用a=b,那么b的内容就复制给a。若接着又修改了a,而b根本不会受这种修改的影响。
但是在对象“赋值”的时候,情况却发生了变化。对一个对象进行操作时,我们真正操作的是对对象的引用。所以倘若“将一个对象赋值给另一个对象”,实际是将“引用”从一个地方复制到另一个地方。这意味着假若对对象使用c=d,那么c和d都指向原本只有d指向的那个对象。
15.java中的“别名现象”是由于通过引用操作对象引起的。
16.Random rand = new Random(47);
int i = rand.nextInt(100)+1;
其中如果在创建Random对象时没有参数,那么java就会将当前时间作为随机数生成器的种子,并由此在程序每一次执行时产生不同的输出。但如果通过在创建Random对象时通过种子(47)(用于随机数生成器的初始化值,随机数生成器对于特定的种子值总是产生相同的随机数序列),就可以在每一次执行程序时生成相同的随机数,因此其输出是可验证的。
17.关系操作符包括>、<、<=、>=、==、!=。等于和不等于适用于所有的基本数据类型,而其他比较符不适用于boolean类型。因为boolean值只能为true或false,“大于”和“小于”没有实际的意义。详见java编程思想(4)3.7
?思考:3.7中的最后一个程序,为什么结果为false?因为equals()默认比较的是引用,但是为什么String类中的equals比较的是内容呢?这是因为重写了从Object继承来的equals()方法。
18.一元减号用于转变数据的符号,而一元加号只是为了与一元减号相对应,但是它唯一的作用仅仅是将较小类型的操作数提升为int。
19.如果想比较两个对象的实际内容是否相同,此时,必须使用所有对象都适用的特殊方法equals()。但这个方法不适用于“基本类型”,基本类型直接适用==和!=即可。
20.逻辑操作符“与”、“或”、“非”操作只可应用于布尔值。与在C及C++中不同的是:不可将一个非布尔值当作布尔值在逻辑表达式中使用。在前面代码中“//!”注释掉的语句,就是错误的用法(这种注释语法使得注释能够被自动移除以方便测试)。见3.8节
注:如果在应该使用String值的地方使用了布尔值,布尔值会自动转换成适当的文本形式。
21.短路问题。当使用逻辑操作符时,我们会遇到一种“短路”现象。即一旦能够明确无误地确定整个表达式的值,就不再计算表达式余下部分。因此,整个逻辑表达式靠后的部分有可能不会被运算。
22.移位操作符只可用来处理整数类型。有符号移位操作符<<和>>。“有符号”右移位操作符使用“符号扩展”:若符号为正,则在高位插入0;若符号为负,则在高位插入1。java中新增了一种“无符号”右移位操作符(>>>),它使用“零扩展”:无论正负,都在高位插入0。这一操作符是c和c++中所没有的。
23.类型转换包括窄化转换(narrowing conversion)和扩展转换(widening conversion)。窄化转换(也就是说,将能容纳更多信息的数据类型转换成无法容纳那么多信息的类型),就有可能面临信息丢失的危险。此时,编译器会强制我们进行类型转换。而扩展转换,则不必显式地进行类型转换,因为新类型肯定能容纳原来类型的信息,不会造成任何信息的丢失。
java允许我们把任何基本数据类型转换成别的基本数据类型,但布尔型除外,后者根本不允许进行任何类型的转换处理。
“类”数据类型不允许进行类型转换。为了将一种类转换成另一种,必须采用特殊的方法。(本书后面会讲到,对象可以在其所属类型的类族之间进行类型转换;例如,“橡树”可转型为“树”;反之亦然。但不能把它转换成类族以外的类型,如“岩石”)。
24.在将float或double转型为整型时,总是对该数字执行截尾。如果想要得到舍入的结果,就需要使用java.lang.Math中的round()方法。
25.如果对基本数据类型执行算术运算或按位运算,主要类型比int小(即char,byte或者short),那么在运算之前,这些值会自动转换成int。这样一来,最终生成的结果就是int类型。如果想把结果赋值给较小的类型,就必须使用类型转换,这可能出现信息丢失。通常,表达式中出现的最大的数据类型决定了表达式最终结果的数据类型。如果一个float与一个double值相乘,结果就是double。
26.java没有sizeof。在c和c++中,sizeof()操作符可以告诉你为数据项分配的字节数。在c和c++中,需要使用sizeof()的最大原因是为了“移植”。不同的数据类型在不同的机器上可能有不同的大小,所以在进行一些与存储空间有关的运算时,程序员必须获悉那些类型具体有多大。例如,一台计算机可用32位保存整数,而另一台只用16位保存。
27.能够对布尔型值进行的运算非常有限。我们只能赋予它true和false值,并测试它为真还是为假,而不能将布尔值相加,或对布尔值进行其它任何运算。
除boolean以外,任何一种基本类型都可通过类型转换变为其它基本类型。
28.java并不支持goto语句(该语句引起许多反对的意见,但它仍是解决某些特殊问题的最有效的方法),在java中,仍然可以进行类似goto那样的跳转,但比起典型的goto,有了很多的限制。
29.注意java不允许我们将一个数字作为布尔值使用,虽然这在c和c++里是允许的(在这些语言里,“真”是非零,而“假”是零)。如果想在布尔测试中使用一个非布尔值,比如在if(a)中,那么首先必须用一个条件表达式将其转换成布尔值,例如if(a!=0)。
30.while、do-while和for用来控制循环,有时将它们划分为迭代语句(iteration statement)。
31.逗号操作符,注意不是逗号分隔符,逗号用作分隔符时用来分隔函数的不同参数,java里唯一用到逗号操作符的地方就是for循环的控制表达式。在控制表达式的初始化和步进控制部分,可以使用一系列由逗号分隔的语句;而且那些语句均会独立执行。
通过使用逗号操作符,可以在for语句内定义多个变量,但是它们必须具有相同的类型。
for(int i=1,j=i+10;i<5;i++,j=i*2){
System.out.println("i="+i+"j="+j);
}
32.for(initialization;boolean-expression;step)
statement
初始化表达式、布尔表达式或者步进运算,都可以为空。
33.如果在返回void的方法中没有return语句,那么在该方法的结尾处会有一个隐式的return,因此在方法中并非总是必须要有一个return语句。但是,如果一个方法声明它将返回void之外的其他东西,那么必须确保每一条代码路径都将返回一个值。
34.无穷循环的两种形式:while(true)和for(;;),这两种形式是同一回事。
35.标签和break、continue的使用。
label1:
outer-iteration{
innter-iteration{
//...
break;//(1)
//...
continue;//(2)
//...
continue label1;//(3)
//...
break label1;//(4)
}
}
在(1)中,break中断内部迭代,回到外部迭代。在(2)中,continue使执行点移回内部迭代的起始处。在(3)中,continue label1同时中断内部迭代以及外部迭代,直接转到label1处;随后,它实际上是继续迭代过程,但却从外部迭代开始。在(4)中,break label1也会中断所有迭代,并回到label1处,但并不重新进入迭代。也就是说,它实际上是完全中止了两个迭代。
36.要记住的重点:在java里需要使用标签的唯一理由就是因为有循环嵌套存在,而且想从多层嵌套中break或continue
37.switch(integral-selector){
case integral-value1:statement;break;
case integral-value2:statement;break;
.
.
default:statement;
}
其中,integral-selector(整数选择因子)是一个能够产生整数值的表达式,switch能将这个表达式的结果与每个integral-value(整数值)相比较。若发现相符的,就执行对应的语句(单一语句或多条语句,其中并不需要括号)。
switch要求的选择因子,必须是int或char那样的整数值。对于非整数类型,则必须使用一系列if语句。在下一章的末尾,将看到java se5的新特性enum,它可以帮助我们减弱这种限制,因为enum可以和switch协调工作。
38.请注意,由于构造器的名称必须与类名完全相同,所以“每个方法首字母小写”的编码风格并不适用于构造器。
39.从概念上讲,“初始化”与“创建”是彼此独立的,然而在上面的代码中,你却找不到对initialize()方法的明确调用。在java中,“初始化”和“创建”捆绑在一起,两者不能分离。
40.构造器是一种特殊类型的方法,因为它没有返回值。这与返回值为空(void)明显不同。对于空返回值,尽管方法本身不会自动返回什么,但仍可选择让它返回别的东西。构造器则不会返回任何东西,你别无选择(new表达式确实返回了对新建对象的引用,但构造器本身并没有任何返回值)。假如构造器具有返回值,并且允许人们自行选择返回类型,那么势必得让编译器知道该如何处理此返回值。
41.区分重载方法。
每个重载的方法都必须有一个独一无二的参数类型列表。甚至参数顺序的不同也足以区分两个方法。不过,一般情况下别这么做,因为这会使代码难以维护。
42.常数,比如5,会被当做是int类型的。如果某个重载方法接受int型参数,它就会被调用。至于其它情况,如果传入的数据类型(实际参数)小于方法中声明的形式参数类型,实际数据类型就会被提升。char型略有不同,如果无法找到恰好接受char参数的方法,就会把char直接提升至int型。
如果传入的实际参数较大,就得通过类型转换来进行窄化转换。如果不这样做,编译器就会报错。
详见java编程思想四5.22.
43.以返回值区分重载方法是行不通的。比如:
void f(){}
int f(){return 1;}
如果像下面这样调用方法:
f();
此时java如何才能判断该调用哪个f()呢?别人该如何理解这种代码呢?因此,根据方法的返回值来区分重载方法是行不通的。
44.如果类中没有构造器,则编译器会自动帮你创建一个默认构造器,没有连默认构造器都没有的话,就无法创建对象了。但是,如果已经定义了一个构造器(无论是否有参数),编译器就不会帮你自动创建默认构造器了。
45.在构造器中调用构造器。例子:
public class Flower {
int petalCount=0;
String s="initial value";
Flower(int petals) {
// TODO Auto-generated constructor stub
System.out.println("Constructor w/ int arg only,petalCount="+petalCount);
}
Flower(String ss) {
// TODO Auto-generated constructor stub
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;
System.out.println("String & int args");
}
Flower(){
this("hi",47);
System.out.println("default constructor(no args)");
}
void printPetalCount(){
//!this(11);
System.out.println("petalCount="+petalCount+",s="+s);
}
public static void main(String[] args) {
Flower x = new Flower();
x.printPetalCount();
}
}
构造器Flower(String s,int petals)表明:尽管可以用this调用一个构造器,但却不能调用两个(这是因为构造器的调用要放在构造器中的第一行,如果调用2个的话,就有一个构造器引用处于非第一行,这时编译器报错)。此外,必须将构造器调用置于最起始处,否则编译器会报错。
printPetalCount()方法表明,除构造器之外,编译器禁止在其它任何地方中调用构造器。
注:this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用。
46.static方法没有this的方法。在static方法的内部不能调用非静态方法①,反过来倒是可以。static很像全局方法。java中禁止使用全局方法。使用static方法时,由于不存在this,所以不是通过“向对象发送消息”的方式来完成的。
注:①这不是完全不可能。如果你传递一个对象的引用到静态方法里(静态方法可以创建其自身的对象),然后通过这个引用(和this效果相同),你就可以调用非静态方法和访问非静态数据成员了。但通常要达到这样的效果,你只需写一个非静态方法即可。
47.java的垃圾回收器只知道释放那些经由new分配的内存,所以假定你的对象(并非使用new)获得了一块“特殊”的内存区域,这时java的垃圾回收器不知道如何释放该对象的这块“特殊”内存。为了应对这种情况,java允许在类中定义一个名为finalize()的方法。
48.构造器初始化。
牢记:无法阻止自动初始化的进行,它将在构造器被调用之前发生。例如:
public class Counter{
int i;
Counter(){i=7};
}
那么i首先会被置为0,然后变成7。对于所有基本类型和对象引用,包括在定义时已经指定初值的变量,这种情况都是成立的;因此,编译器不会强制你一定要在构造器的某个地方或在使用它们之前对元素进行初始化-因为初始化早已得到了保证。总之,定义会发生在构造器执行前。
49.初始化顺序。
在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。
50.静态数据的初始化。
static关键字不能应用于局部变量,因此它只能作用于域。如果一个域是静态的基本类型域,且也没有对它进行初始化,那么它就会获得基本类型的标准初值;如果它是一个对象引用,那么它的默认初始值就是null。如果想在定义处初始化,采取的方法与非静态数据没什么不同。
①静态初始化只有在必要时刻才会进行。如果不创建对象(静态变量所在的类的对象),也不引用静态变量,那么静态变量永远不会被创建。只有在第一个对象被创建(或者第一次访问静态数据)的时候,它们才会被初始化。
②初始化的顺序是先静态对象(如果它们尚未因前面的对象创建过程而被初始化),而后是非静态对象。
51.总结一下对象的创建过程,假设有个名为Dog的类:
①即使没有显式地使用static关键字,构造器实际上也是静态方法。因此,当首次创建类型为Dog的对象时(构造器可以看成静态方法),或者Dog类的静态方法/静态域首次被访问时,java解释器必须查找类路径,以定位Dog.class文件。
②然后载入Dog.class(后面会学到,这将创建一个Class对象),有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次。
③当用new Dog()创建对象的时候,首先将在堆上为Dog对象分配足够的存储空间。
④这块存储空间会被清零,这就自动地将Dog对象中的所有基本类型数据都设置成了默认值(对数字来说就是0,对布尔型和字符型也相同),而引用则被设置成了null。
⑤执行所有出现于字段定义处的初始化动作。
⑥执行构造器。正如将在第7章所看到的,这可能会牵涉到很多动作,尤其是涉及继承的时候。
52.数组初始化
数组只是相同类型的、用一个标识符名称封装到一起的一个对象序列或基本类型数据序列。
编译器不允许指定数组的大小。这又把我们带回到有关“引用”的问题上。现在拥有的只是对数组的一个引用(你已经为该引用分配了足够的存储空间),而且也没给数组对象本身分配任何空间。为了给数组创建相应的存储空间,必须写初始化表达式。对于数组,初始化动作可以出现在代码的任何地方,但也可以使用一种特殊的初始化表达式,它必须在创建数组的地方出现。这种特殊的初始化是由一对花括号括起来的值组成的。在这种情况下,存储空间的分配(等价于使用new)将由编译器负责。例如:int[] a={1,2,3,4,5};
所有的数组(无论它们的元素是对象还是基本类型)都有一个固有成员,可以通过它获知数组内包含了多少个元素,但不能对其修改。这个成员就是length。
如果在编写程序时,并不能确定在数组里需要多少个元素,那么该怎么办呢?可以直接用new在数组里创建元素。尽管创建的是基本类型数组,new仍然可以工作(不能用new创建单个的基本类型数据)。数组的创建确实是在运行时刻进行的。如下代码:
int[] a;
Random rand = new Random(47);
a = new int[rand.nextInt(20)];
如果你创建了一个非基本类型的数组,那么你就创建了一个引用数组。如果创建的是一个基本类型的数组,那么创建的是一个引用。
53.枚举类型
枚举类型的实例是常量,因此按照命名惯例它们都用大写字母表示。
尽管enum看起来像是一种新的数据类型,但是这个关键字只是为enum生成对应的类时,产生了某些编译器行为,因此在很大程度上,你可以将enum当作其他任何类来处理。事实上,enum确实是类,并且具有自己的方法。