//
Thinking Java
Search
Try Notion
Thinking Java
一.对象基础
描述问题空间所需成分
所有东西都是对象
单根结构
对象有自己空间,可和包含其他对象(嵌套)
存在类型-对象之分
同类对象接受同样信息
访问控制
原因1:防止外部干扰
原因2:方便修改实现
public,private, protected 以及暗示性的friendly
方法复用
在现有类上封装新类称作“组织”
由于继承的重要性,所以在面向对象的程序设计中,它经常被重点强调。作为新加入这一领域的程序员,或 许早已先入为主地认为“继承应当随处可见”。沿这种思路产生的设计将是非常笨拙的,会大大增加程序的 复杂程度。相反,新建类的时候,首先应考虑“组织”对象;这样做显得更加简单和灵活。利用对象的组 织,我们的设计可保持清爽。一旦需要用到继承,就会明显意识到这一点。
在现有类上添加新方法成员“继承”
关键字extends
继承也可改善原类的方法
“尽管使用的函数接口未变, 但它的新版本具有不同的表现”。
如果继承添加了新的接口元素,则称之为类似
将衍生类调用基础类的方法称之为 UpCast上溯造型
多形性,动态绑定
将一条消息发给对象时,如果并不知道对方的具体类型是什么,但采取的行动同样是正确的,这种情况就叫 作“多形性”(Polymorphism)。对面向对象的程序设计语言来说,它们用以实现多形性的方法叫作“动态 绑定”。编译器和运行期系统会负责对所有细节的控制;我们只需知道会发生什么事情.
有些语言要求我们用一个特殊的关键字来允许动态绑定。在C++中,这个关键字是 virtual。在Java 中,我 们则完全不必记住添加一个关键字,因为函数的动态绑定是自动进行的。所以在将一条消息发给对象时,我 们完全可以肯定对象会采取正确的行动,即使其中涉及上溯造型之类的处理。
抽象接口
abstract关键字
若有人试图创建抽象类的一个对象,编译器就会阻止他们。这种工具 可有效强制实行一种特殊的设计。亦可用 abstract 关键字描述一个尚未实现的方法——作为一个“根”使用
interface(接口)关键字将抽象类的概念更延伸了一步,它完全禁止了所有的函数定义。“接口”是一种相 当有效和常用的工具。另外如果自己愿意,亦可将多个接口都合并到一起(不能从多个普通 class 或 abstract class 中继承)。
存在时间
集合
—只是简单地创建另一种类型的对象。用于解决特定问题的新型对象容纳了指向其他对象的句柄。称作集合
如:其中包括集、队列、散列表、树、堆栈等等
所有集合都提供了相应的读写功能。将某样东西置入集合时,采用的方式是十分明显的。有一个叫作“推” (Push)、“添加”(Add)或其他类似名字的函数用于做这件事情。但将数据从集合中取出的时候,方式却 并不总是那么明显。如果是一个数组形式的实体,比如一个矢量(Vector),那么也许能用索引运算符或函 数。但在许多情况下,这样做往往会无功而返。此外,单选定函数的功能是非常有限的。如果想对集合中的 一系列元素进行操纵或比较,而不是仅仅面向一个,这时又该怎么办呢?办法就是使用一个“继续器”(Iterator),它属于一种对象,负责选择集合内的元素,并把它们提供给继 承器的用户。作为一个类,它也提供了一级抽象。利用这一级抽象,可将集合细节与用于访问那个集合的代 码隔离开。 首先,集合提供了不同的接口类型以及外部行为。堆栈的接口与行为与队列的不同,而队列 的接口与行为又与一个集(Set)或列表的不同。利用这个特征,我们解决问题时便有更大的灵活性。 其次,不同的集合在进行特定操作时往往有不同的效率。最好的例子便是矢量(Vector)和列表(List)的 区别。它们都属于简单的序列,拥有完全一致的接口和外部行为。但在执行一些特定的任务时,需要的开销 却是完全不同的。对矢量内的元素进行的随机访问(存取)是一种常时操作;无论我们选择的选择是什么, 需要的时间量都是相同的。但在一个链接列表中,若想到处移动,并随机挑选一个元素,就需付出“惨重” 的代价。而且假设某个元素位于列表较远的地方,找到它所需的时间也会长许多。但在另一方面,如果想在 序列中部插入一个元素,用列表就比用矢量划算得多。这些以及其他操作都有不同的执行效率,具体取决于 序列的基础结构是什么。在设计阶段,我们可以先从一个列表开始。最后调整性能的时候,再根据情况把它 换成矢量。由于抽象是通过继承器进行的,所以能在两者方便地切换,对代码的影响则显得微不足道。 最后,记住集合只是一个用来放置对象的储藏所。如果那个储藏所能满足我们的所有需要,就完全没必要关 心它具体是如何实现的(这是大多数类型对象的一个基本概念)。如果在一个编程环境中工作,它由于其他 因素(比如在Windows 下运行,或者由垃圾收集器带来了开销)产生了内在的开销,那么矢量和链接列表之 间在系统开销上的差异就或许不是一个大问题。我们可能只需要一种类型的序列。甚至可以想象有一个“完 美”的集合抽象,它能根据自己的使用方式自动改变基层的实现方式。
集合库
单根结构
二.对象的特征
对象创建标准流程
String s;s=new String("abc");
快速流程
String s = new String("abc"); //或者String s = "abc" //特殊方法
保存位置
寄存器(无法控制)
堆栈
但可通过它的“堆栈指针”获得处理的直接支持。堆 栈指针若向下移,会创建新的内存;若向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存 方式,仅次于寄存器
堆(Heap)
“堆”(Heap)最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要 在堆里停留多长的时间
静态储存
常数存储
非RAM
主要类型(无句柄设计)
boolean 1位
byte 8位
short char 16位
int/float 32位(IEEE 754)
long/double 64(IEEE 754)
void
默认值
Boolean false Char ’
作用域
自动回收
括号下规定了作用域,嵌套括号不能复用变量(不存在覆盖)
{ int x = 12; { int x = 96; /* illegal */ }}
但 Java 以后,情况却发生了改观。Java 有一个特别 的“垃圾收集器”,它会查找用new创建的所有对象,并辨别其中哪些不再被引用。随后,它会自动释放由 那些闲置对象占据的内存,以便能由新对象使用。这意味着我们根本不必操心内存的回收问题。只需简单地 创建对象,一旦不再需要它们,它们就会自动离去。这样做可防止在 C++里很常见的一个编程问题:由于程 序员忘记释放内存造成的“内存溢出”。
字段方法
数据成员–字段 数据函数–方法
如果数据成员是主类型需要考虑默认值,局部变量不适用
Java 的方法只能作为类的一部分创建
标识符
修饰符
static关键字
一种情形是只想用一个存储区域来保存一个特定的数据——无论要创建多少个对象,甚至根本不创建对象。另一种情形是我们需要一个特殊的方法,它没有与这个类的任何对象关联。也就是说,即使没有创建对象,也需要一个能调用的方法。为满足这两方面的要求,可使用 static(静态)关键字。一旦将什么东西设为static,数据或方法就不会同那个类的任何对象实例联系到一 起。
三.运算符
仅能够操作主类型
“+ - * / =”
例外
唯一的例外是“=”、“==”和“!=”,它们能操作 所有对象(也是对象易令人混淆的一个地方)。除此以外,String 类支持“+”和“+=”。
赋值
对主数据类型的赋值是非常直接的。由于主类型容纳了实际的值,而且并非指向一个对象的句柄,所以在为 其赋值的时候,可将来自一个地方的内容复制到另一个地方。例如,假设为主类型使用“A=B”,那么 B处的 内容就复制到A。若接着又修改了 A,那么 B 根本不会受这种修改的影响但在为对象“赋值”的时候,情况却发生了变化。对一个对象进行操作时,我们真正操作的是它的句柄。所 以倘若“从一个对象到另一个对象”赋值,实际就是将句柄从一个地方复制到另一个地方。这意味着假若为 对象使用“C=D”,那么C 和 D最终都会指向最初只有 D 才指向的那个对象。
前缀后缀
返回值为副作用发生前(后缀)
返回值为副作用发生后(前缀)
关系运算符== != >= <=
主类型比较值
对象比较句柄
等于和不等于适用于所有内建的数据类型,但其他比较不适用于boolean 类型。
对象使用obj.equals(Object obj2)来比较对象
新类的equals方法需要自己实现
逻辑运算
AND(&&) OR(||) NOT(!)
仅能用于布尔值
存在短路运算,注意副作用
按位运算符
AND(&) OR(|) NOT(~) XOR(^)
可搭配&= |= ~= ^= 使用
移位运算符
<< 低位补0
>> 符号拓展右移
值为正补0
值为负补1
>>> 0扩展右移
可搭配=号使用(同上)
造型运算符
Java 允许我们将任何主类型“造型”为其他任何一种主类型但布尔值(bollean)要除外,后者根本不允 许进行任何造型处理“类”不允许进行造型。为了将一种类转换成另一种,必须采用特殊的方法(字串是 一种特殊的情况,本书后面会讲到将对象造型到一个类型“家族”里;例如,“橡树”可造型为“树”;反 之亦然。但对于其他外来类型,如“岩石”,则不能造型为“树”)
字面值
public class Literals { public static void main(String[] args) { int i1 = 0x2f; // Hexadecimal (lowercase) print("i1: " + Integer.toBinaryString(i1)); int i2 = 0X2F; // Hexadecimal (uppercase) print("i2: " + Integer.toBinaryString(i2)); int i3 = 0177; // Octal (leading zero) print("i3: " + Integer.toBinaryString(i3)); char c = 0xffff; // max char hex value print("c: " + Integer.toBinaryString(c)); byte b = 0x7f; // max byte hex value print("b: " + Integer.toBinaryString(b)); short s = 0x7fff; // max short hex value print("s: " + Integer.toBinaryString(s)); long n1 = 200L; // long suffix long n2 = 200l; // long suffix (but can be confusing) long n3 = 200; float f1 = 1; float f2 = 1F; // float suffix float f3 = 1f; // float suffix double d1 = 1d; // double suffix double d2 = 1D; // double suffix // (Hex and Octal also work with long) }} /* Output:i1: 101111i2: 101111i3: 1111111c: 1111111111111111b: 1111111s: 111111111111111///:~
Copy
JavaScript
四.流程控制
对Java 来说,唯一用到标签的地方是在循环语句之前。进一步说,它实际需要紧靠在循环语句的前方——在 标签和循环之间置入任何语句都是不明智的。而在循环之前设置标签的唯一理由是:我们希望在其中嵌套另 一个循环或者一个开关。这是由于 break和 continue 关键字通常只中断当前循环,但若随同标签使用,它们 就会中断到存在标签的地方。
label1:外部循环{ 内部循环{ //... break; //1 //... continue; //2 //... continue label1;//3 //... break label1;//4 }}