This website requires JavaScript.

Core Java 读书笔记一:Java 的基本程序设计结构

数据类型

Unicode 类型

在 Unicode 标准中,码点(code point)使用16进制表示,并加上前缀 U+,比如 U+0041 是子母A的码点。 Unicode 的码点被分为17层面(code plane)。第一层为基本多语种层(basic multilingual plane),从 U+0000 到 U+FFFF。剩余的16个层面为 U+10000 到 U+10FFFF 为补充字符(supplementary characters)。

UTF-16 编码采用不同长度的编码表示所有 Unicode 码点。在基本多语种层采用16位记录,称之为代码单元(code unit)。而辅助字符采用一对连续的代码单元进行编码。 这样构成的编码值落人基本的多语言级别中空闲的 2048 字节内, 通常被称为替代区域(surrogate area)(比如 U+D800 ~ U+DBFF 用于第一个代码单元,U+DC00 ~ U+DFFF 用于第二个代码单元)。 这样设计十分巧妙, 我们可以从中迅速地知道一个代码单元是一个字符的编码,还是一个辅助字符的第一或第二部分。

在 Java 中,char 类型描述了 UTF-16 编码中的一个代码单元。我们强烈建议不要在程序中使用 char 类型, 除非确实需要处理 UTF-16 代码单元。最好将字符串作为抽象数据类型处理。

枚举类型

有时候,变量的取值只在一个有限的集合内。例如: 销售的服装或比萨饼只有小、中、 大和超大这四种尺寸。当然,可以将这些尺寸分别编码为 1、2、3、4 或 S、 M、 L、X。但 这样存在着一定的隐患。在变量中很可能保存的是一个错误的值(如 0 或 m )。 针对这种情况 可以自定义枚举类型 枚举类型包括有限个命名的值例如:

enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARCE };

实际上, 这个声明定义的类型是一个类, 它刚好有 4 个实例, 在此尽量不要构造新对象。

现在,可以声明这种类型的变量:

Size s = Size.MEDIUM;

Size 类型的变量只能存储这个类型声明中给定的某个枚举值,或者 null 值, null 表示这个变量没有设置任何值。

如果需要的话, 可以在枚举类型中添加一些构造器、 方法和域。 当然, 构造器只是在构造枚举常量的时候被调用。 下面是一个示例:

public enum Size
{
  SMALLfS") , MEDIUMC'M") , LARGEfL") , EXTRA_LARGE("XL") ;
  private String abbreviation;
  
  private Size(String abbreviation) { this, abbreviation = abbreviation; }
  public String getAbbreviation() { return abbreviation; }
}

字符串

概念上讲,Java 字符串就是 Unicode 字符序列。

Java 字符串由 char 值序列组成。 char 数据类型是一个采用 UTF-16 编码表示 Unicode 码点的代码单元。大多数的常用 Unicode 字符使用一个代码单元就可以表示,而辅助字符需要一对代码单元表示。 length 方法将返回采用 UTF-16 编码表示的给定字符串所需要的代码单元数量。 例如:

String greeting = "Hello";
int n = greeting.length(); // is 5

构建字符串

有些时候, 需要由较短的字符串构建字符串, 例如, 按键或来自文件中的单词。 采用字 符串连接的方式达到此目的效率比较低。 每次连接字符串,都会构建一个新的 String 对象,既耗时,又浪费空间。使用 StringBuilder 类就可以避免这个问题的发生。

如果需要用许多小段的字符串构建一个字符串, 那么应该按照下列步骤进行。 首先,构建一个空的字符串构建器:

StringBuilder builder = new StringBuilderO;

当每次需要添加一部分内容时, 就调用 append 方法。

builder.append(ch); // appends a single character
bui1der.append(str); // appends a string

在需要构建字符串时就凋用 toString 方法, 将可以得到一个 String 对象, 其中包含了构建器 中的字符序列。

String completedString = builder.toStringO;

在 JDK5.0 中引入 StringBuilder 类。 这个类的前身是 StringBuffer, 其效率稍有些低, 但允许采用多线程的方式执行添加或删除字符的操作。如果所有字符串在一个单线程中编辑 (通常都是这样) ,则应该用 StringBuilder 替代它。 这两个类的 API是相同的。

大数值

如果基本的整数和浮点数精度不能够满足需求, 那么可以使用 jaVa.math 包中的两个 很有用的类: Biglnteger 和 BigDecimaL 这两个类可以处理包含任意长度数字序列的数值。 Biglnteger 类实现了任意精度的整数运算, BigDecimal 实现了任意精度的浮点数运算。 使用静态的 valueOf 方法可以将普通的数值转换为大数值:

Biglnteger a = Biglnteger.valueOf(100);

遗憾的是, 不能使用人们熟悉的算术运算符(如: + 和 * ) 处理大数值。 而需要使用大数 值类中的 add 和 multiply 方法。

Biglnteger c = a.add(b); // c = a + b
Biglnteger d = c.nultipiy(b.add(Biglnteger.valueOf(2))); // d = c * (b + 2)

数组

在声明数组变量时, 需要指出数组类型 (数据元素类型紧跟 [] ) 和数组变量的名字。 下面声明了整型数组 a:

int[] a;
或
int a[]

不过,这条语句只声明了变量 a,并没有将 a 初始化为一个真正的数组。 应该使用 new 运算 符创建数组。

int[] a = new int[100];

创建一个数字数组时, 所有元素都初始化为 0。boolean 数组的元素会初始化为 false 对象数组的元素则初始化为一个特殊值null, 这表示这些元素(还)未存放任何对象。

String[] names = new String[10];

for each 循环

for (int i = 0; i < a.length; i++) System,out.println(a[i]);

直接打印数组中的所有值

System.out.println(Arrays.toString(a)) ;

数组初始化以及匿名数组

初始化,不需要用 new

int[] anonymous = { 17, 19, 23, 29, 31, 37 };
smallPrimes = anonymous;
//匿名数组,使用这种语法形式可以在不创建新变量的情况下重新初始化一个数组。
new intD { 17, 19, 23, 29, 31, 37 }
small Primes = new int[] { 17, 19, 23, 29, 31, 37 };

数组拷贝

在 Java 中, 允许将一个数组变量拷贝给 另一个数组变量。这时, 两个变量将引用同 一个数组:

intQ luckyNumbers = smallPrimes;
1uckyNumbers[S] = 12; // now smallPrimes[5] is also 12

如果希望将 一个数组的所有值拷贝到一个新的数组中去, 就要使用 Arrays 类的 copyOf 方法:

int[] copiedLuckyNumbers = Arrays.copyOf(luckyNumbers, luckyNumbers.length);
// 第 2 个参数是新数组的长度。 这个方法通常用来增加数组的大小。如果数组元素是数值型, 那么多余的元素将被赋值为 0 ; 如果数组元素是布尔型, 则将赋值
为 false。 相反, 如果长度小于原始数组的长度, 则只拷贝最前面的数据元素。
luckyNumbers = Arrays.copyOf(luckyNumbers, 2 * luckyNumbers.length);

多维数组

声明一个二维数组

double[][] balances;

与一维数组一样, 在调用 new 对多维数组进行初始化之前不能使用它。 在这里可以这样 初始化:

balances = new double[NYEARS][NRATES]:

另外, 如果知道数组元素, 就可以不调用 new, 而直接使用简化的书写形式对多维数组 进行初始化。 例如:

int[][] magicSquare = {
{16, 3, 2, 13},
{5, 10, 11, 8},
{9, 6, 7, 12},
{4, 15, 14, 1}
};

UML

UML(Unified Modeling Language,统一建模语言)的格式。每个类由一个框表示,框的顶部有类型名称,框中间部分是要描述的任何数据成员,方法(属于此对象的方法,它们接收任何发送到该对象的消息)在框的底部。通常,只有类的名称和公共方法在 UML 设计图中显示,因此中间部分未显示,如下所示例:

  • 组合(Composition)经常用来表示“拥有”关系(has-a relationship)。例如,“汽车拥有引擎”。
  • 聚合(Aggregation)动态的组合。

上图中实心三角形指向“ Car ”表示 组合 的关系;如果是 聚合 关系,可以使用空心三角形。

  • 继承

这个图中的箭头从派生类指向基类。正如你将看到的,通常有多个派生类。类型不仅仅描述一组对象的约束,它还涉及其他类型。两种类型可以具有共同的特征和行为,但是一种类型可能包含比另一种类型更多的特征,并且还可以处理更多的消息(或者以不同的方式处理它们)。继承通过基类和派生类的概念来表达这种相似性。基类包含派生自它的类型之间共享的所有特征和行为。创建基类以表示思想的核心。从基类中派生出其他类型来表示实现该核心的不同方式。

上面是常见的“形状”例子,可能用于计算机辅助设计系统或游戏模拟。基类是“形状”,每个形状都有大小、颜色、位置等等。每个形状可以绘制、擦除、移动、着色等。由此,可以派生出(继承出)具体类型的形状——圆形、正方形、三角形等等——每个形状可以具有附加的特征和行为。

  • "是一个"与"像是一个"的关系

对于继承可能会引发争论:继承应该只覆盖基类的方法(不应该添加基类中没有的方法)吗?如果这样的话,基类和派生类就是相同的类型了,因为它们具有相同的接口。这会造成,你可以用一个派生类对象完全替代基类对象,这叫作"纯粹替代",也经常被称作"替代原则"。在某种意义上,这是一种处理继承的理想方式。我们经常把这种基类和派生类的关系称为是一个(is-a)关系,因为可以说"圆是一个形状"。判断是否继承,就看在你的类之间有无这种 is-a 关系。

有时你在派生类添加了新的接口元素,从而扩展接口。虽然新类型仍然可以替代基类,但是这种替代不完美,原因在于基类无法访问新添加的方法。这种关系称为像是一个(is-like-a)关系。新类型不但拥有旧类型的接口,而且包含其他方法,所以不能说新旧类型完全相同。

以空调为例,假设房间里已经安装好了制冷设备的控制器,即你有了控制制冷设备的接口。想象一下,现在空调坏了,你重新安装了一个既制冷又制热的热力泵。热力泵就像是一个(is-like-a)空调,但它可以做更多。因为当初房间的控制系统被设计成只能控制制冷设备,所以它只能与新对象(热力泵)的制冷部分通信。新对象的接口已经扩展了,现有控制系统却只知道原来的接口,一旦看到这个设计,你就会发现,作为基类的制冷系统不够一般化,应该被重新命名为"温度控制系统",也应该包含制热功能,这样的话,我们就可以使用替代原则了。上图反映了在现实世界中进行设计时可能会发生的事情。

访问权限

  • public(公开)表示任何人都可以访问和使用该元素;
  • private(私有)除了类本身和类内部的方法,外界无法直接访问该元素。private 是类和调用者之间的屏障。任何试图访问私有成员的行为都会报编译时错误;
  • protected(受保护)类似于 private,区别是子类可以访问 protected 的成员,但不能访问 private 成员;
  • default(默认)如果你不使用前面的三者,默认就是 default 访问权限。default 被称为包访问,因为该权限下的资源可以被同一包(库组件)中其他类的成员访问。`
0条评论
avatar