151. Java relative
优秀的讲解
类与对象
表述
类描述了一系列具有相同特征(即数据元素)和行为(即功能方法)的对象
一个上佳的方法是将对象想象成“服务提供者” 。你的程序本身也是为用户提供服务的,它通过使用其他对象提供的服务来做到这一点。所以,你的任务是创建(更好的情况是,从已有的库中找到)一些提供对应服务以解决问题的对象。
内存方面
创建一个类即描述了其对象的外观和行为。直到使用 new 关键字时,你才会真正创建一个对象,以及为该对象分配内存,并使得其方法可以被调用。
消息
调用方法
调用方法的行为被描述为 “==向一个对象发送一条消息==”,如:
1 | int x = a.f(); |
f()
代表消息a
代表对象
面向对象编程
面向对象编程描述为“==向对象发送消息==”
接口与类的关系
是抽象与更抽象的关系。
比如,定义人这个类,但是在人之外还有各种动物,或者说,人就是高级动物。但是有一个问题是,人有学历之类的属性,而其他动物一定要有学历这一属性吗?答案是否。人有自己的行为动作,那么其他动物是否也有相同或者类似的行为动作呢?答案是是的。那么我们是不是就可以设计一个动物类,而不添加任何属性呢?那么这个类就可以称作接口了。
引用
引用的作用是关联对象。
引用未必会关联某个对象,如:String s
引用的个人理解
引用就是一个地址或者指针,通过地址来操作相关对象
创建对象
new
使用 new
关键字来创建对象: 我要一个这种类型的对象
1 | String s = new String9("asdf"); |
初始化
数组
- 放置对象的数组:初始化为
null
- 放置基本类型的数组:初始化为
0
对象
基本类型 | 默认值 |
---|---|
boolean | false |
char | \u0000(null) |
byte | (byte)0 |
short | (short)0 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0d |
引用 | null 调用引用的方法会==得到异常==,但仍然可以打印一个 null 引用而不会抛出异常 |
初始化顺序
- 自动初始化
- 构造初始化
静态数据
- 字段是基本类型:基本类型的标准初始值
- 字段是对象引用:默认初始值为 null
初始化引用
- 在定义对象时。这意味着它们将始终在调用构造器之前被初始化。
- 在该类的构造器中。
- 在对象实际使用之前。这通常称为延迟初始化(lazy initialization) 。在对象创建成本高昂且不需要每次都创建的情况下,它可以减少开销。
- 使用实例初始化
示例
1 | // reuse/Bath.java |
与C不同点
同名变量
1 | { |
Java 编译器会提示说,变量 x 已经定义过了。
==类似 C 和 C++ 那样在外围的作用域中“隐藏”变量的方式在 Java 中是不被允许的==
类
- 文件中必须存在一个与该文件同名的类(如果你没有这么做,则编译器会报错)
入口
如果你需要创建一个能够独立运行的程序,那么与文件同名的类中还必须包含一个程序启动的入口方法。这个特殊的方法叫作 main(),具有以下的格式和返回值:
1 | public static void main(String[] args) |
编译
javac <filename>.java
java <filename>
赋值
基本类型
基本类型存储了实际的值,而非只想一个对象的引用,所以在为其赋值的时候,你直接将一个地方的==内容复制到了另一个地方==。
对象赋值
当操作一个对象的时候,我们==真正操作的是这个对象的引用==。当“将一个对象赋值给另一个对象”时,你是将这个引用从一个地方复制到了另一个地方。这意味着对对对象而言,将两个变量同时指向同一个对象。
方法调用中的别名
将一个对象作为参数传递给方法时,也会产生别名
格式化处理
符号 | 含义 |
---|---|
%n |
换行 |
%e |
表示以科学记数法显示结果 |
整数值对象
创建方式
自动转化为
Integer
通过对
Integer.valueOf()
的==自动调用==来完成的1
2int value = 0;
Integer i = value;使用标准的对象创建语法
new
。 这是以前创建 “包装/装箱” Integer 对象的首选方法1
2int value = 0;
Integer r = new Integer(value);从 Java 9 开始,valueOf() 优于 [2]。如果尝试在 Java 9 中使用方式 [2],你将收到警告,并被建议使用 [3] 代替。很难确定 [3] 是否的确优于 [1],不过 [1] 看起来更简洁
1
2int value = 0;
Integer v = Integer.valueOf(value);基本类型 int 也可以当作整数值对象使用
1
int value = 0;
equal()
equals()
方法的默认行为是比较引用。- 如果只想比较内容,你必须重写
equals()
方法 - 大多数标准库会重写
equals()
方法来比较对象的内容而不是它们的引用
boolean
- 把 int 类型当作 boolean 类型并==不合法==
- 如果在应该使用字符串的地方使用了布尔值,布尔值会自动转换成合适的文本格式
- boolean 类型是有限制的:
- 我们只能赋予它 true 和 false 值,并测试它是真还是假
- 不能将 boolean 值相加,或对 boolean 值执行其他任何运算
字面值
大写(或小写)的字符 L 表示 long 类型(不过使用小写的 l 容易让人迷惑,因为它看起来就像数字 1)
大写(或小写)的字符 F 表示 float 类型
大写(或小写)的字符 D 表示 double 类型
Java 7 引入了==二进制字面量==,通过前缀
0b
或0B
来表示,它适用于所有整数类型。可以在数字字面量里使用下划线,这样更易于阅读
如:
341_435_936.445_667
规则:
- 只能使用单个下划线,不能连续使用多个;
- 数字的开头或结尾不能有下划线;
- 像
F
、D
或L
这样的后缀周围不能有下划线; - 在二进制或十六进制标识符
b
和x
的周围不能有下划线。
e
表示 “10 的幂次”e
大小写都可以,含义相同编译器一般会将指数作为 double 类型处理,所以如果没有尾部的 f, 我们会收到一条出错提示, 告诉我们必须将 double 类型转换成 float 类型
布尔类型位运算
- 可以对它执行按位“与” 、按位“或”和按位“异或”运算,但==不能执行按位“非”==
- 对于布尔值,按位操作符和逻辑操作符具有相同的效果,但它们==不会“短路”==
- 针对布尔值的按位运算还比逻辑操作符多了一个“异或”运算
移位操作
- 移位操作符也操纵二进制位,它们==只能用来处理基本类型里的整数类型==。
- 左移位操作符(==<<==)会将操作符左侧的操作数向左移动,移动的位数在操作符右侧指定(==低位补 0==)
- “有符号”的右移位操作符(==>>==)则按照操作符右侧指定的位数将操作符左侧的操作数向右移动。“有符号”的右移位操作符使用了“符号扩展” :
- 如果符号为正,则在==高位插入 0==
- 否则在==高位插入 1==
- 种“无符号”的右移位操作符(==>>>==) ,它使用“零扩展”: 无论符号为正还是为负,都在==高位插入 0==
- 无符号”右移位操作符结合赋值操作符可能会遇到一个问题:如果对 byte 或 short 值进行移位运算,得到的可能不是正确的结果。它们会先被提升为 int 类型,进行右移操作,然后在被赋回给原来的变量时==被截断==, 这时得到==结果是 -1==
字符串操作符 + 和 +=
如果表达式以一个字符串开头,则其后的所有操作数都必须是字符串类型的(编译器会自动把双引号里的字符序列转换成字符串)
字符串转换并不取决于先后顺序
类型转换
自动转换
Java 中无法自动将 int 类型转为 boolean 类型
强制转换
既可以对数值进行类型转换, 也可以对变量进行类型转换
窄化转型
有可能面临信息丢失的危险。窄化转型就是说,将能容纳更多信息的数据类型==转换成无法容纳那么多信息的数据类型==。此时,编译器会要求我们进行强制类型转换,意在提醒我们: “这可能是一个危险的操作,如果的确要这么做,你必须显式地进行类型转换。”
宽化转型
不必显式地进行类型转换, 因为新类型可以容纳比原来的类型更多的信息, 而不会造成任何信息的丢失
截尾与舍入
在执行窄化转型时,会出现截尾与舍入
处理
float 或 double 转型为整型值时: 截尾
java.lang.Math 中的
round()
: 舍入
提升
- 如果对小于 int 类型的基本数据类型(即 char、byte 或者 short)执行算术运算或按位运算,运算执行前这些值就会被自动提升为 int,结果也是 int 类型。
- 如果要把结果赋值给较小的类型,就==必须使用强制类型转换==(由于把值赋给了较小的类型,可能会出现信息丢失) 。
- 表达式里出现的最大的数据类型决定了表达式最终结果的数据类型
不允许转换
boolean
Java 可以把任何基本类型转换成别的基本类型,但 boolean 除外,它不允许进行任何类型的转换处理。
“类”类型(class type)也不允许进行类型转换
将一种类型转换成另一种类型需要采用特殊的方法:对象可以在它的类型所属族群里进行类型转换,但不能把它转换成外部的类型。
Java 没有 sizeof()
略
逗号操作符
逗号操作符不是 ==逗号分隔符==
逗号分隔符:分割生命和函数的不同参数
逗号操作符:Java 里==唯一==用到逗号操作符的地方就是 for 循环的控制表达式。
- 使用地方:初始化和步进部分,这些语句会按先后顺序执行
- 注意:可以在for语句里定义多个变量,但它们必须是 ==相同的类型==
循环
for-in
用于数组和容器,也叫做 foreach 语法或 for-in 语法
说明
for-in 会==自动为你生成每一项元素==,这样你就不需要创建 int 变量来对这个元素构成的序列进行计数。
使用
任何反悔了数组的方法都可以使用 for-in
range()
range() 方法可以让我们在更多的地方使用 for-in 语法,从而提高了可读性
总结
不仅方便代码的编写,更重要的是让代码更容易阅读,它表明了你打算做什么(获取数组中的每一个元素) , 作者更提倡使用 for-in 语法
无限循环
for(;;)
while(true)
goto
Java中没有goto
标签
与goto使用了相同的机制
说明
标签是以冒号结尾的标识符
1 | label: |
用法
在 Java 中,放置标签的==唯一地方==是正好在迭代语句之前。 “正好”的意思就是,==不要在标签和迭代之间插入任何语句==
break 和 continue 通常只会中断当前循环,但和标签一起用时,它们可以中断这个嵌套的循环,直接跳转到标签所在的位置
注意
- 使用标签的==唯一理由==就是你用到了嵌套循环,而且你需要使用 break 或 continue 来跳出多层的嵌套。
switch
语法
1 | switch(integral-selector) { |
整数选择器(integral-selector)是一个能生成整数值的表达式
int
char
字符串
enum
初始化
静态块
Java 允许在一个类里将多个静态初始化语句放在一个特殊的“静态子句”里(有时称为静态块)
1 | public class Spoon { |
只执行一次:第一次创建该类的对象时,或第一次访问该类的静态成员时(即使从未创建过该类的对象) 。
实例初始化
Java 提供了一种称为实例初始化(instance initialization)的类似语法,==用于初始化每个对象的非静态变量==。
可以用来保证无论调用哪个显式的构造器,某些操作都会发生
实例初始化子句在==构造器之前执行==
1 | public class Mugs { |
数组
定义
方法1
1
int[] a1;
方法2
1
int a1[];
注意
编译器不允许指定数组的大小
所拥有的只是对数组的引用(你已经为该引用分配了足够的存储空间) ,并没有为数组对象本身分配任何空间
创建了一个非基本类型数组,其实就是创建了一个引用数组
即便使用了 new 来创建数组之后,它还只是一个引用数组,直到通过自动装箱为数组里的每个引用本身初始化了一个Integer 对象之后,这个数组的初始化才真正结束
初始化
特殊的初始化方式
只能在创建数组的地方出现,编译器负责存储的分配(相当于使用 new)
1 | int[] a1 = {1, 2, 3, 4, 5} |
默认
自动初始化为空值(对于数值类型和 char 是 0, 对于 boolean 则是 false)
花括号包围列表
使用花括号保卫列表来初始化对象数组
方法1:局限性更大,它只能用在定义数组的时候
1
Integer[] a = {1, 2, 3, };
方法2: 在任何地方使用第二种形式(定义了 b) ,甚至在方法调用中也可以
1
Integer[] b = new Integer[]{ 1, 2, 3, };
注意
- 初始值列表的最后一个逗号都是可选的(此功能可以让维护长列表更容易)
运算
赋值
可以将一个数组赋值给另一个数组
1 | a2 = a1 |
其实真正做的知识复制了一个引用
查询数组有多少个元素
成员变量: length
可变参数列表
如果列表中没有任何内容,则它会转变成一个大小为零的数组
函数
printBinaryInt() 和 printBinaryLong()
分别接受 int 类型和 long 类型的参数,然后输出其二进制格式
%n
Java 用 %n
来实现的功能,它会根据程序运行的平台生成合适的换行符,不过这仅会在使用 System.out.printf()
或 System.out.format()
时起作用。对于 System.out.println()
,你仍然必须使用 \n
;如果使用了 %n
, println()
只会输出 %n
而不是将其当作换行符。
toBinaryString()
使用 Integer 和 Long 类的静态方法 toBinaryString() 可以很容易实现获取二进制
如果将比较小的类型传递给Integer.toBinaryString() 方法,则该类型会自动被转换为 int 类型
Integer.toString()
1 | // Integer.toString() 的简化版: |
Array.toString(a)
1 | int[] a = new int[10]; |
getClass()
生成一个对象的类,当打印这个类时,你会看到一个表示该类类型的编码字符串
[
表示这是后面紧随的类型的数组
ordinal()
表示特定 enum 常量的声明顺序
一个静态的values() 方法
它按照声明顺序生成一个 enum 常量值的数组
自动类型推断
1 | // housekeeping/TypeInference.java |
static
static
修饰的字段或方法(也称“类数据 class data” 和 “类方法class method”),只服务于类,而非特定的对象。这些数据或方法只会占用同一块内存空间,即会被由该类创建所有的对象所共享。
注意
调用 static 方法并不依赖于是否提前创建对象。正因为如此,我们也==不能在没有具体对象的情况下, 使用 static 方法直接调用非 static 成员或方法==(因为非 static 成员和方法必须基于特定的对象才能 运行) 。
调用方法
通过对象调用:
<对象>.<static 成员>
通过类名调用:
<类名>.<static 成员>
(==推荐使用该方法==)注意:==非 static 成员则不能这样使用==
生成方式
static 字段
- 是基于类创建的
- 即便你没有创建对象,也可以调用该方法
非 static 字段
- 是基于对象创建的
包
见下
访问控制
- 每个编译单元(文件)都只能有一个public类,但可以根据需要拥有任意数量的包访问权限的类
- public 类的名称必须与包含编译单元的文件名完全匹配,包括大小写
- 编译单元里可以没有 public 类。这时你可以随意命名文件
- 类访问权限, 只有两种选择: 包访问权限和 public
- 若防止对该类的访问,可以将其所有的构造器都设为 private
库
Math
函数 | 功能 |
---|---|
random() |
生成一个范围为0~1(包括 0,但不包括 1)的 double 值 |
java.lang.Character 包装器类
函数 | 功能 |
---|---|
isLowerCase() |
检测出相关字符是否为小写字母 |
问题
包与库的关系
不清楚
类
toString()
每个非基本类型的对象都有一个 toString() 方法
它会在一些特殊情况下被调用,比如当编译器需要一个字符串,但有的是一个对象时。
注解
注解 @Override
用在 toString() 方法上
@Override
是可选的
final
final 数据
- 定义常量
空白 final
final 执行赋值的操作只能发生在两个地方
- 在字段定义处使用表达式进行赋值
- 在每个构造器中
final 参数
在方法内部不能更改参数引用指向的内容
可以读取这个参数, 但不能修改它
final 方法
原因
- 在方法上放置一个“锁” ,这样就可以防止继承类通过重写来改变该方法的含义
- 效率:编译器可以将任何对该方法的调用转换为内联调用 (现在可以不用考虑这个因素了,主要考虑 “锁” 的因素)
final 和 private
类中的任何 private 方法都是隐式的 final
final 类
将整个类定义为 final 时(通过在其定义前加上 final 关键字) ,就阻止了该类的所有继承