杰言杰语

以中有足乐者,不知口体之奉不若人也.


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

  • 搜索

第3章 使用字符串

发表于 2017-03-28 | 分类于 Python | 阅读次数

1. 基本字符串操作

字符串是不可变的,如下所示的项或分片赋值都是不合法的.

2. 字符串格式化 : 精简版

字符串格式化使用字符串格式化操作符即百分号%来实现的.

注意
%也可以用来做模运算(求余)操作符.

在%的左侧位置放置一个字符串(格式化字符串),而右侧则放置希望被格式化的值.可以使用一个值,如一个字符串或者数字,也可以使用多个值的元组或者字典.

格式化字符串的%s部分被称为转换说明符,它们标记了需要插入转换值的位置.s表示植会被格式化为字符串->如果不是字符串,则会用str将其转换为字符串.

3. 字符串方法

字符串从string模块中”继承”了很多方法.

3.1 find

find方法可以在一个较长的字符串中查找子串.它返回子串所在位置的最左端索引.如果没有找到返回-1.

1
2
3
4
5
6
>>> 'hello,my name is kevin'.find('kevin')
17
>>> title = "I love China."
>>> title.find(love)
>>> title.find('love')
2

这个方法还可以接收可选的起始点和结束点参数.

1
2
3
4
5
6
>>> subject = '$$$ Get rich now!!! $$$'
>>> subject.find('$$$',1)
20
>>> subject.find('!!',0,16) #操作起始点和终止点
-1
>>>

3.2 join

join方法是split方法的逆方法,用来连接序列中的元素 :

1
2
3
4
5
>>> seq=['1','2','3']
>>> sep = '+'
>>> sep.join(seq)
'1+2+3'
>>>

3.3 lower

lower方法返回字符串的小写字母版本,这个方法会忽略字母的小写状态.

1
2
3
>>> 'HELLO'.lower()
'hello'
>>>

如果想要在列表中查找一个用户名是否存在 : 列表包含字符串”kevin”,而用户输入的是”Kevin”,就能找到了.解决方法就是在存储和搜索时把所有名字都转换为小写.

1
2
3
4
5
6
>>> name = "Kevin"
>>> names = ['kevin','leona']
>>> if name.lower() in names: print 'Found it!'
Found it!
>>>

标题转换
和lower方法相关的是title方法,它会将字符串转换为标题(也就是所有的单词的首字母大写,而其他字母小写.)

3.4 replace

replace方法返回某字符串的所有匹配项均被替换之后的字符串,应用于文档的查找和替换.

1
2
3
>>> 'This is a apple'.replace('is a apple','are some apples')
'This are some apples'
>>>

3.5 split

它是join的逆方法,用来将字符串分割成序列.

1
2
3
4
5
>>> '1+2+3+4+5+6'.split('+')
['1', '2', '3', '4', '5', '6']
>>> 'hello hello'.split()
['hello', 'hello']
>>>

3.6 strip

strip方法返回除两侧(不包括内部)的空格字符串 :

1
2
3
>>> ' I say!'.strip()
'I say!'
>>>

它和lower方法一起使用可以很方便的对比输入和存储的值.

3.7 translate

translate方法和replace方法一样,可以替换字符串中的某些部分,但是translate方法只处理单个字符.它的优势在于可以同时进行多个替换,有时候比replace效率高很多.

在使用translate转换之前,需要一张转换表,因为这个表太大.我们使用string模板里面的maketrans函数就可.

4. 小结

  • 字符串格式 : 求模操作符(%)可以用来将其他值转换为包含转换标志的字符串,例如%s.它还能用来对值进行不同方式的格式化,包括左右对齐,设定字段宽度以及精度值.增加符号(正负号)或者左填充数字0等.

第2章 列表和元素

发表于 2017-03-28 | 分类于 Python | 阅读次数

数据结构是通过某种方式(例如对元素进行编号) 数据结构,在Python中,最基本的数据结构是序列(sequence).序列中的每个元素分配一个序号->元素的位置,也称为索引.第一个索引是0,第二个则是1.

注意 :
序列中的最后一个元素标记为-1,倒数第二个原始为-2,以此类推.

1. 序列概览

列表和元组的主要区别在于 : 列表可以修改,元组则不能.如 : 使用元组作为字典的键,在这种情况下,因为键不能修改,所以不能用列表.

在需要操作一组数值的时候,序列很好用.可以用序列表示数据库中的一个人信息 : 第1个元素是姓名,第2个元素是年龄.根据上述内容编写一个列表(列表的各个元素通过逗号分隔,写在方括号内),如 :

1
edward = ['kevin',20]

同时,序列也可以包含其他的序列,因此,构建如下的一个人员信息的列表也是可以的,这个列表就是你的数据库 :

1
2
husband = ['kevin',20]
wife =['leona',20]

注意 :
Python之中还有一种名为容器(container)的数据结构,容器基本上是包含其他对象的任意对象.序列(例如列表和元组)和映射(例如字典)是两类主要的容器.序列中的每个元素都有自己的编号,而映射中的每个元素则有一个名字(键).既不是序列也不是映射的容器类型,集合(set)就是一个例子.

2. 通用序列操作

所有序列类型都可以进行某些特定的操作,这些操作包括 : 索引(indexing) / 分片(slicing) / 加(adding) / 乘(multiplying) 以及检查某个元素是否属于序列的成员(成员资格).

迭代(ineration) : 依次对序列中的每个元素重复执行某些操作.

2.1 索引

序列中的所有元素都是有编号的(从0开始递增).这些元素是可以通过编号来访问的.

1
2
3
4
5
6
>>> greeting = 'hello'
>>> greeting[0]
'h'
>>> greeting[-1]
'o'
>>>

注意 :
字符串就是一个由字符组成的序列.索引0指向第1个元素.

我们可以通过索引获取元素,所有序列都可以通过这种方式进行索引.使用负数索引时,Python会从右边,也就是最后一个元素开始计数.最后1个元素的位置编号是-1.

还有一种办法是用'hello[1]',结果为e,效果是一样的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# Print out a date, given year, month, and day as numbers
months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
]
# A list with one ending for each number from 1 to 31
endings = ['st', 'nd', 'rd'] + 17 * ['th'] \
+ ['st', 'nd', 'rd'] + 7 * ['th'] \
+ ['st']
year = raw_input('Year: ')
month = raw_input('Month (1-12): ')
day = raw_input('Day (1-31): ')
month_number = int(month)
day_number = int(day)
# Remember to subtract 1 from month and day to get a correct index
month_name = months[month_number-1]
ordinal = day + endings[day_number-1]
print month_name + ' ' + ordinal + ', ' + year

2.2 分片

可以使用分片操作来访问一定范围内的元素,分片通过冒号隔开的两个索引来实现 :

1
2
3
4
5
>>> tag = '<a href="http://www.python.com">Python Web site</a>'
>>> tag[9:30]
'http://www.python.com'
>>> tag[32:-4]
'Python Web site'

简而言之,分片操作的实现需要提供两个索引作为边界,第1个索引的元素是包含在分片内的,而第2个则不包含在分片内.

1
2
3
4
5
6
7
8
>>> numbers = [1,2,3,4,5,6,7,8,9,10]
>>> numbers[3:6]
[4, 5, 6]
>>> numbers[0:1]
[1]
>>> numbers[7:10]
[8, 9, 10]
>>>

优雅的捷径 :

假设需要访问最后的3个元素,那么可以显示的操作:numbers[7:10],现在,索引10指向的是11个元素,但是这个元素是不存在的,却是在最后的一个元素之后.这种做法是可行的.如果需要从列表的结尾开始计数.

1
2
>>> numbers[-3:-1]
[8, 9]

实际上,只要分片中最左边的索引比它右边的晚出现在序列中,结果就是一个空序列.不过,可以使用一个捷径 : 如果分片所得部分包括序列结尾的元素,只需置空最后一个索引即可!

1
2
>>> numbers[-3:]
[8, 9, 10]

可以复制整个序列,可以将两个索引置空

1
2
>>> numbers[:]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

1
2
3
4
5
6
# Split up a URL of the form http://www.something.com
url = raw_input('Please enter the URL: ')
domain = url[11:-4]
print "Domain name: " + domain

更大的步长 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> numbers = [1,2,3,4,5,6,7,8,9,10]
# 分片操作就是按照这个步长逐个便利序列的元素,然后返回开始和结束之间的代码.
>>> numbers[0:10:1]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> numbers[0:10:2]
[1, 3, 5, 7, 9]
# 从索引3到6,取第3个
>>> numbers[3:6:3]
[4]
# 将每4个元素的第1个提取出来
>>> numbers[::4]
[1, 5, 9]
# 步长可以是负数,此时分片从右到左提取元素 :
>>> numbers[8:3:-1]
[9, 8, 7, 6, 5]

当使用一个负数作为步长时,必须让开始点大于结束点.在没有明确指定开始点和结束点的时候,正负数的使用可能会带来些混迹.在python明确规定 : 对于正数步长,python会从序列的头部开始向右提取元素;直到最后一个元素;而对于负数步长,则是从序列的尾部开始向左提取元素,直到最后一个元素.

2.3 序列相加

1
2
3
4
5
6
7
8
9
>>> [1,2,3]+[4,5,6]
[1, 2, 3, 4, 5, 6]
>>> 'hello;+;world!'
'hello;+;world!'
>>> [1,2,3]+'world'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "str") to list
>>>

两种相同的类型的序列才能连接操作.

2.4 乘法

1
2
3
4
5
>>> 'python'*5
'pythonpythonpythonpythonpython'
>>> [42]*10
[42, 42, 42, 42, 42, 42, 42, 42, 42, 42]
>>>

如果想要初始化一个长度为10的空值,可以使用None,None是Python的一个内建值.

1
2
3
4
>>> sum = [None]*10
>>> sum
[None, None, None, None, None, None, None, None, None, None]
>>>

2.5 成员资格

为了检查一个值是否在序列中,可以使用in运算符,这个运算符检查某个条件是否为真,然后返回相应的值.条件为真True,条件为假返回False.

1
2
3
4
5
6
7
8
9
>>> permissions = 'kevin'
>>> 'e'in permissions
True
>>> users = ['mlh','foo','bar']
>>> kevin = raw_input("请输入")
请输入f
>>> print kevin in users
False
>>>
1
2
3
4
5
database = ['kevin','1234']
username = raw_input("请输入用户名")
pin = raw_input("请输入密码")
if(username,pin)in database:
print '登录成功!'

2.6 长度,最小值和最大值.

len函数返回列表中所包含元素的数量,min函数和max函数则分别返回序列中最大和最小的元素.

1
2
3
4
5
6
7
8
9
10
>>> numbers = [100,56,89]
>>> len(numbers)
3
>>> max(numbers)
100
>>> min(numbers)
56
>>> max(7,9)
9
>>>

3. 列表

列表是可变的 : 可以改变列表的内容,并且列表有很多有用的,专门的方法.

3.1 list函数

1
2
>>> list('hello')
['h', 'e', 'l', 'l', 'o']

3.2 基本的列表操作

  • 改变列表 : 元素赋值
    1
    2
    3
    4
    >>> num = [1,2,3,4,5]
    >>> num[2]=22
    >>> num
    [1, 2, 22, 4, 5]

注意
不能为一个位置不存在的元素进行赋值.

  • 删除元素
    从列表中删除元素用del语句来实现 :

    1
    2
    3
    4
    >>> names = ["kevin","shy_kevin","yujiewong"]
    >>> del names[1]
    >>> names
    ['kevin', 'yujiewong']
  • 分片赋值

    1
    2
    3
    4
    5
    6
    >>> name = list('Kevin')
    >>> name
    ['K', 'e', 'v', 'i', 'n']
    >>> name[2:]=list('ar')
    >>> name
    ['K', 'e', 'a', 'r']

3.3 列表方法

方法是一个与某个对象有紧密联系的函数,对象可能是列表,数字,也可能是字符串或者其他的类型的对象.一般来说,方法可以这样进行调用 :
对象.方法(参数)

列表提供了几个方法,用于检查或者修改其中的内容.

  • append
    append方法用于在列表末尾追加新的对象 :

    1
    2
    3
    4
    >>> lst=[1,2,3]
    >>> lst.append(4)
    >>> lst
    [1, 2, 3, 4]
  • count
    count方法统计某个元素在列表中出现的次数 :

    1
    2
    3
    4
    5
    >>> ['what','be','am','is','are'].count('is')
    1
    >>> x=[[1,2],1,1,[2,1,[1,2]]]
    >>> x.count(1)
    2
  • extend
    extend方法可以在列表的末尾一次性追加另一个序列中的多个值.换句话说,可以用新列表扩展原有列表.

    1
    2
    3
    4
    5
    >>> a=[1,2,3,4,5,6]
    >>> b = [1,2,3]
    >>> a.extend(b)
    >>> a
    [1, 2, 3, 4, 5, 6, 1, 2, 3]

它与a+b的区别在于extend方法修改被扩展的序列,而原始的操作则不然,这是因为原始的操作创建了一个包含a和b的副本的新列表,那么连接操作的效率会比extend方法低.

  • index
    index方法用于从列表中找出某个值第一个匹配项的索引位置 :
    1
    2
    3
    >>> names = ['kevin','shy_kevin']
    >>> names.index('kevin')
    0

要注意的是 : 如果搜索到的字符串不存在,会抛出异常.

  • insert
    insert方法用于将对象插入到列表中 :
    1
    2
    >>> numbers=[1,2,3,4,5,6,7]
    >>> numbers,insert(3,'kevin')

与extend方法一样,insert方法的操作也可以用分片赋值来实现.

1
2
3
4
5
>>> numbers
[1, 2, 3, 'kevin', 4, 5, 6, 7]
>>> numbers[3:3] = ['four']
>>> numbers
[1, 2, 3, 'four', 'kevin', 4, 5, 6, 7]

  • pop
    pop方法会移除列表中的一个元素(默认是最后一个),并且返回该元素的值 :
    1
    2
    3
    4
    5
    6
    7
    8
    >>> x = [1,2,3]
    >>> x.pop()
    3
    >>> x.pop(0)
    1
    >>> pop
    >>> x
    [2]

注意
pop方法是唯一一个既能修改列表又返回元素值(除了None)的列表方法.

  • remove
    remove方法用于移除列表中的某个值的第一个匹配项 :

    1
    2
    3
    4
    >>> x = ['to','hello']
    >>> x.remove('hello')
    >>> x
    ['to']
  • reverse
    reverse方法将列表中的元素反向存放

    1
    2
    3
    4
    >>> k = [1,2,3]
    >>> k.reverse()
    >>> k
    [3, 2, 1]
  • sort
    sort方法用于在原位置对列表进行排序.在”原位置排序”意味着改变原来的列表,从而其中的元素能按一定的顺序排列,而不是简单地返回一个已排序的列表副本.

    1
    2
    3
    4
    5
    >>> k
    [3, 2, 1]
    >>> k.sort()
    >>> k
    [1, 2, 3]
  • 高级排序
    可以通过compare(x,y)的形式自定义比较函数,compare(x,y)函数会在xy时返回正数,如果x=y则返回0(根据自定义),定义好该函数后,就可以提供给sort方法作为参数.内建函数cmp提供了比较函数的默认实现方式 :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    >>> cmp(42,32)
    1
    >>> cmp(99,100)
    -1
    >>> cmp(10,10)
    0
    >>> numbers = [1,2,3,4,5,6]
    >>> numbers.sort(cmp)
    >>> numbers
    [1, 2, 3, 4, 5, 6]

sort方法有另外两个可选的参数(key和reverse),如果要使用它们,那么就要通过名字来指定(这个叫关键字参数),参数key和参数cmp类型(必须提供一个在排序过程中使用的函数).然而,该函数并不是用来确定对象的大小,而是为每个元素创建一个键,然后所有元素根据键来排序.因此,如果要根据元素的长度进行排序,那么可以使用len作为键函数.

1
2
3
4
>>> x=['aa','ab','ac']
>>> x.sort(key=len)
>>> x
['aa', 'ab', 'ac']

另一个关键字reverse是简单的布尔值,用来指明列表是否要进行反向排序.

1
2
3
4
5
>>> x=[4,671,2,78,21]
>>> x.sort(reverse=True)
>>> x
[671, 78, 21, 4, 2]
>>>

4. 元组 : 不可变序列

如果你用逗号来分隔开一些值,那么你就会自动创建了元组.

1
2
>>> 1,2,3
(1, 2, 3)

如果想要实现一个值得元组,必须加上逗号.如 : 42,

4.1 tuple函数

以一个序列作为参数并把它转换为元组.

1
2
3
4
5
6
7
>>> tuple([1,2,3,4])
(1, 2, 3, 4)
>>> tuple('abc')
('a', 'b', 'c')
>>> tuple((1,2,3))
(1, 2, 3)
>>>

4.2 元组的意义

  • 元组可以在映射(和集合的成员)中当做键使用,而列表则不行.
  • 元组作为很多内建函数和方法的返回值存在,也就是说你必须对元组进行处理.只要不尝试修改元组.那么,”处理”元组在绝大多数情况下就是把它们当做列表来进行操作.

5. 小结

函数 描述
cmp(x,y) 比较两个值
len(seq) 返回序列的长度
list(seq) 把序列转换成列表
max(args) 返回列表或者参数集合中最大值
min(args) 返回序列或者参数集合中的最小值
reversed(seq) 把序列进行反向迭代
sorted(seq) 返回已排序的包含seq所有元素的列表
tuple(seq) 把序列转换成元组

第1章 快速改造 : 基础知识

发表于 2017-03-28 | 分类于 Python | 阅读次数

1. 交互式解析器

当我们输入下面的指令 : print "Hello,world!",当按下回车按键后,会得到下面的输出 : Hello,world!.

注意 :
Python一行就是一行,不管多少.

2. 算法是什么

对于如何做某事的一份详细描述.

3. 数字和表达式

1/2 得到的结果为 0 ,因为一个整数被另外一个整数除,计算结果的小数部分被截除了,只剩下整数部分.所以结果为 0 .

实数在 Python 中被称为浮点数(Float),如果参与除法的两个数中有一个数为浮点数,则运算结果为浮点数. 1.0 / 2.0 = 0.5

Python提供了另外一个实现整除的操作符(双斜线).1 // 2 = 0 1.0 // 2.0 == 0.0

1 % 2 = 1这是取余运算符(x%y的结果为x除以y的余数)

幕(乘方)运算符 :

1
2
3
4
5
6
7
8
9
>>> 2**3
8
>>> 2**3
8
>>> -3**2
-9
>>> (-3)**2
9
>>>

注意 : 幕运算符比取反的优先级要高,所以-3**2等同于-(3**2),如果想计算(-3)**2,就需要显式说明.

3.1 长整数

普通整数不能大于2147483647(也不能小于-2147483648),如果需要跟大的数,可以使用长整数.长整数的书写方法和普通整数一样,但要在结尾加L.

3.2 十六进制和八进制

1
2
3
4
5
>>> 0xaf
175
>>> 010
8
>>>

十六进制和八进制的首位数字都是0.

4. 变量

如果希望用名字X代表3,只需要执行下面的语句 :

1
2
3
>>> x=3
>>> x*2
6

这样的操作称为赋值(assignment),数值3被赋给了变量x.另外的一种说法是 : 将变量x绑定到了值(或者对象)3上面.在变量被赋值之后,就可以在表达式中使用变量.

注意 :
变量名可以包括字母,数字和下划线(_).变量名不能以数字开头,所以Plan9是合法变量名,而9Plan不是.

5. 语句

表达式就是某件事情,而语句是做某件事情.变量就像临时的存储器,它的强大之处在于操作变量的时候并不需要知道它们存储了什么值.

6. 获取用户输入

1
2
3
4
>>> input("The meaning of life:")
The meaning of life:42
42
>>>

接着下面的内容 :

1
2
3
4
5
6
7
>>> x = input("x:")
x:34
>>> y = input("y:")
y:42
>>> print x*y
1428
>>>

7. 函数

可以用一个函数pow来代替幕运算符(**),来实现.我们通常会把pow等标准函数称为内建函数.

1
2
3
4
>>> 2**3
8
>>> pow(2,3)
8

上面的例子我们称之为调用函数,你可以给它提供参数,它会返回值给用户.

1
2
>>> 2 + pow(2,10)
1026

还有很多内建函数,比如用abs函数可以得到数的绝对值,round函数则会把浮点数四舍五入为最接近的整数值 :

1
2
3
4
5
>>> abs(-10)
10
>>> round(6.999)
7.0
>>>

8. 模板

模板可以想象为导入到python以增强其功能扩展.如 :

1
2
3
>>> import math
>>> math.floor(32.9)
32.0

用import导入模板,然后按照”魔板.函数”的格式使用这个模块的函数.

你确认自己不会导入多个同命函数(从不同模块导入)的情况下,那么可以使用import命令的另外一种方式 :

1
2
3
>>> from math import sqrt
>>> sqrt(9)
3.0

8.1 cmath和复数

sqrt函数用于计算一个数的平方根.

1
2
3
4
5
6
>>> sqrt(-2)
Traceback (most recent call last):
File "<pyshell#28>", line 1, in <module>
sqrt(-2)
ValueError: math domain error

因为负数的平方根是虚数又因为sprt只能处理浮点数,而虚数是完全不同的.因此,它们由另外一个叫做cmath(复数)的模板来处理.

1
2
3
>>> import cmath
>>> cmath.sqrt(-2)
1.4142135623730951j

9. 保存并执行程序

注释 : #打印圆的周长 :

10. 字符串

  • 单引号字符串和转义引号

  • 拼接字符串 : 建议使用+进行拼接

  • 字符串表示,str和repr
    所有通过python打印的字符串还是被引号括起来,这是因为python打印值的时候会保持该值在python代码中的状态,而不是你希望用户所看到的状态.如果使用print语句,结果就不一样了 :

1
2
3
4
>>> "Hello,world!"
'Hello,world!'
>>> print "hello,world!"
hello,world!
  • 一种是通过str函数,它会把值转换为合理形式的字符串,另外一种是通过repr函数,它会创建一个字符串,以合法的python表达式的形式来表示值.
1
2
3
4
5
>>> print repr(100000l)
100000L
>>> print str(1000l)
1000
>>>
  • input和raw_input的比较
1
2
3
4
5
6
7
8
>>> name = input("what is your name?")
what is your name?诸葛亮
Traceback (most recent call last):
File "<pyshell#36>", line 1, in <module>
name = input("what is your name?")
File "<string>", line 1
诸葛亮

出现错误的原因是用户必须输入的是合法的python表达式.所以我们必须要以字符串作为输入的名字.

1
2
3
4
5
>>> name = input("what is your name?")
what is your name?"诸葛亮"
>>> print "hello,"+name
hello,诸葛亮
>>>

因此,要求用户带入引号是不好的体验,我们需要raw_input函数,它会把所有的输入当做原始数据(raw data),然后将其放入字符串中.

1
2
3
4
>>> raw_input("hello!")
hello!hi
'hi'
>>>
  • 长字符串,原始字符串和Unicode
    • 长字符串 : 可以使用三个引号代替普通引号'''My name is 诸葛亮!'''. 也可以使用"""爽不爽!"""
    • 原始字符串 : 它不会把反斜线当作特殊字符.在原字符串中输入的每个字符都会与书写的方式保存一致 :
      1
      2
      >>> print r'c:\nowhere'
      c:\nowhere

可以看到,原始字符串以r开头.但不能以反斜线结尾!

  • Unicode字符串 : 在python3.0中,所有的字符串都是Unicode字符串.

11. 小结

  • 算法 : 算法是对如何完成一项任务的详尽描述,实际上,在篇写程序的时候,就是要使用计算机能够理解的语言来描述算法.这类对机器友好的描述就叫做程序.
  • 表达式 : 表达式是计算机程序的组成部分,它用于表示值.
  • 变量 : 变量是一个名字,它表示某个值.
  • 语句 : 语句是告诉计算机做某些事情的指令.
  • 函数 : Python中的函数就像数学中的函数,它们可以带有参数.并且具有返回值.
  • 模块 : 模块是一些对python功能的扩展,它可以被导入到python中.
函数 描述
abs(number) 返回数字的绝对值
cmath.sqrt(number) 返回平方根,也可以用于负数
float(object) 将字符串和数字转换为浮点数
help() 提供交互式帮助
input(prompt) 获取用户输入
int(object) 将字符串和数字转换为整数
long(objet) 将字符串和数字转换为长整型数
math.ceil(number) 返回数的上入整数,返回值的类型为浮点数
math.floor(number) 返回数的下舍整数,返回值的类型为浮点数
math.sqrt(number) 返回平方根,不适用于负数
pow(x,y[,z]) 返回x的y次幂(所有结果对z取模)
raw_input(prompt) 获取用户输入,结果被看做原始字符串
repr(object) 返回值的字符串表示形式
round(number[,ndigits]) 根据给定的精度对数字进行四舍五入
str(object) 将值转换为字符串

1.PS基础及选框工具

发表于 2017-03-28 | 分类于 Photoshop | 阅读次数

1. 软件界面

软件界面恢复到默认的标准状态 : 窗口——工作区——复位基本功能;

所有的控制面板都在窗口菜单中,可以对其进行隐藏和显示 : 按下TAB键可以隐藏或显示工具箱,属性栏,控制面板; / 按下SHIFT+TAB键,可以只进行控制面板的隐藏;

工作界面

2. 新建

新建

基于互联网设计 (屏幕显示) : 单位:像素,分辨率 : 72 , 颜色模式 : RGB
基于印刷设计时 : 单位 : 毫米 MM,分辨率 : 300,颜色模式 : CMYK

3. 工具

  • 矩形选框工具 (椭圆选框) M
    • 按 SHIFT 键可以强制为正方形 (正圆)
    • 按 ALT 键可以保持中心点不变
    • 同时按下 SHIFT+ALT 键,可保持中心不变强制为正方形 (正圆)
  • 前背景色
    • 前景色填充 : ALT+DELETE(删除)
    • 背景色填充 : CTRL+DELETE(删除)
    • 按 D 键 : 恢复到默认的黑白色
    • 按 X 键 : 前背景色的切换
      前后背景色
  • 移动工具

    • 功能 : 移动对象
    • 复制 : 按下 ALT 键用移动工具进行拖载
  • 图层

    • 新建图层 : CTRL+ALT+SHIFT+N
    • 图层编组 : CTRL+G
    • 图层复制 : CTRL+J
  • 保存与打开
    • 保存 : CTRL+S 可以把内容存储起来
    • 另存为 : CTRL+SHIFT+S,把文件重新保存一份
    • 默认的格式 : PSD(源文件格式)
    • 打开的方式 : CTRL+O
    • 把文档拖拽至软件中也可以打开

4. 移动选取与移动内容的区别

  • 移动选区 : 绘制选区后,用矩形选框工具制在选区内,会出现白色箭头,可以移动选区. (属性栏中必须选中的新选区)
  • 移动内容 : 绘制选区后,用移动工具指在选区内,会出现黑色箭头,可以移动选取内的内容.

5. 选区的修改

  • 边界 : 会得到有一定宽度的环形区域,会有羽化效果
  • 平滑 : 把直角选区变成圆角选区
  • 扩展 : 均匀的扩大选区
  • 收缩 : 均匀的缩小选区

6. 自由变换 CTRL+T

  • 按下 SHIFT 键,保持比例不变
  • 按下 ALT 键,保持中心不变
  • 调整四个角点可以调整整体比例,调整四个边点可以调整宽度和高度
  • 按下 SHIFT 加工具本身的快捷键,可以切换选中的工具
  • CTRL+K : 首选项

7. 羽化 SHIFT+F6

  • 羽化 : 让边缘变得柔和,半透明

8. 常用快捷键

  • 取消选区 : CTRL + D
  • 第一步撤销 CTRL+Z / 第二步以上的撤销 CTRL+ALT+Z 默认撤销步数为 20 步.
  • 放大 : CTRL+”+”
  • 缩小 : CTRL+”-”
  • 抓手工具 : 空格

1. 记忆单词的方法

发表于 2017-03-28 | 分类于 English | 阅读次数

1. 如何记忆单词

  • 阅读记忆
  • 词根/词缀/词源/字母学规律
  • 联想

2. 阅读记忆

  • www.economist.com
  • www.times.com
  • www.gardian.co.uk/

生词圈出,反复研读句子.勾画词组,了解替代,熟词辟意.

3. 词根词缀

  • 词根词缀记词法 prefix (a.肯定/否定 b.方向)
  • suffix (词性)
  • root (意义)
    • regress v.后退/退步 | gress->to go 向前 | re->back 向后
    • ingress n.进入 | into+go
    • improvise v.即兴创作 | vis->to see | im/in->to go 或 not

4. 词源

  • shelter n,v.避难者,保护
  • shield n.盾牌,保护
  • fetter n.束缚 | fet 脚

5. 词源的变化规则

  • 元音之间可以互换 a,e,i,o,u
  • 辅音之间 p\b, t\d, k\g\c\qu, f\v, s\z\x\th 可以互换
    • disburse v.支付/付款 | pay for
    • reimburse v.报销/偿还
    • kindle v.点燃 candle | 蜡烛
    • sunder v.分开,裂开 | thunder 雷声
    • miscellaneous adj.混杂的,各种各样的 | mix 混杂 | cell 细胞
    • masquerade n.假面舞会 | mask 面具 | ade 集合
  • 形近字母的互换, u/v/w, m/n 的互换
    • renovate v.翻新 | nov->new | re->再一次
    • lurk v.潜伏,埋伏 | lower->下降,降低 (才能潜伏)
  • 字母 g/h 的脱落 (不发音)
    • arduous a.辛苦费力的,困难的 | hard->困难的
    • hierarchy n.等级制度,阶级 | hie->higer | to rule
  • 固定转换, s/t/d, p/b/f/v/ph
    • assent v.同意 | sense
    • consent v.同意
    • dissent v.不同意
    • amorphous adj.无形状的,不定型的 | morph->form
  • 字母组合 (单音节)
    • sp 表示发出,散开,产生
      • speak | spout 喷出 | spur n.刺激
    • scr,cr 多数和手上的动作有关 (注意: s 在造词的时候无意义,只起到加强语气的作用)
      • scroll n.卷轴

6. 联想法记单词

  • 单音节词汇 (形近词)
    • fiend n.恶魔,恶人
  • 多音节词汇要拆词 (要拆成认识的词根词缀,拼音等)
    • chrysanthemum n.菊花 | mum 母亲 | the | cry
    • morose a.郁闷的 | rose 没有玫瑰郁闷
    • precarious a.不安的,不稳固的 | pre car ious
    • avalanche n.雪崩 | lan che 拦车

7. 复习 (短时多次)

  • D1 | L1(3)
  • D2 | L1 L2
  • D3 | L3 L2
  • D4 | L1 L3 L4
  • D5 | L5 L2 L4
  • D6 | L6 L5 L3
  • D7 | L1 L4 L6 L7
  • D8 | L2

第11章 持有对象

发表于 2017-03-28 | 分类于 Java | 阅读次数

如果一个程序只包含固定数量的且其生命期都是已知的对象,那么这是一个非常简单的程序.

其中基本的类型如Set Queue 和Map,这些称为集合类.

1. 泛型和类型安全的容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//: holding/ApplesAndOrangesWithoutGenerics.java
// Simple container example (produces compiler warnings).
// {ThrowsException}
import java.util.*;
class Apple {
private static long counter;
private final long id = counter++;
public long id() { return id; }
}
class Orange {}
public class ApplesAndOrangesWithoutGenerics {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
ArrayList apples = new ArrayList();
for(int i = 0; i < 3; i++)
apples.add(new Apple());
// Not prevented from adding an Orange to apples:
apples.add(new Orange());
for(int i = 0; i < apples.size(); i++)
((Apple)apples.get(i)).id();
// Orange is detected only at run time
}
} /* (Execute to see output) *///:~

当在使用Apple的对象时,得到的只是Object引用,必须将其转型为Apple.因此,在调用Apple的id()方法之前,强制执行转型.

通过使用泛型,可以在编译器防止将错误类型的对象放置到容器中.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//: holding/ApplesAndOrangesWithGenerics.java
import java.util.*;
public class ApplesAndOrangesWithGenerics {
public static void main(String[] args) {
ArrayList<Apple> apples = new ArrayList<Apple>();
for(int i = 0; i < 3; i++)
apples.add(new Apple());
// Compile-time error:
// apples.add(new Orange());
for(int i = 0; i < apples.size(); i++)
System.out.println(apples.get(i).id());
// Using foreach:
for(Apple c : apples)
System.out.println(c.id());
}
} /* Output:
0
1
2
0
1
2
*///:~

2. 基本概念

Java容器类类库的用途是”保存对象”.

  • Collection. 一个独立元素的序列,这些元素都服从一条或多条规则.List必须按照插入的顺序保存元素,而set不能有重复元素.Queue按照排队规则来确定对象产生的顺序.
  • Map.一组成对的”键值对”对象,允许你使用键来查找值.ArrayList允许你使用数字来查找值,从某种意义说,它将数字与对象关联在一起.映射表允许我们使用另一个对象来查找某个对象,它也被称为”关联数组”,因此它将某些对象与另外一些对象关联在一起;或者被称为”字典”,因此你可以使用键对象来查找值对象,就像在字典中使用单词来定义一样.
1
List<apple> apples = new ArrayList<Apple>();

ArrayList已经被向上转型为List.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//: holding/SimpleCollection.java
import java.util.*;
public class SimpleCollection {
public static void main(String[] args) {
Collection<Integer> c = new ArrayList<Integer>();
for(int i = 0; i < 10; i++)
c.add(i); // Autoboxing
for(Integer i : c)
System.out.print(i + ", ");
}
} /* Output:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
*///:~

只使用了Collection方法,因为任何继承自Collection的类的对象都可以正常工作.

add()方法的名称就表明它是要将一个新的元素防止到Collection中.注的是要确保Collection包含指定的元素.这是因为考虑到Set的含义,因为在Set中只有元素不存在的情况下才会添加.

3. 添加一组元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//: holding/AddingGroups.java
// Adding groups of elements to Collection objects.
import java.util.*;
public class AddingGroups {
public static void main(String[] args) {
Collection<Integer> collection =
new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4, 5));
Integer[] moreInts = { 6, 7, 8, 9, 10 };
collection.addAll(Arrays.asList(moreInts));
// Runs significantly faster, but you can't
// construct a Collection this way:
Collections.addAll(collection, 11, 12, 13, 14, 15);
Collections.addAll(collection, moreInts);
// Produces a list "backed by" an array:
List<Integer> list = Arrays.asList(16, 17, 18, 19, 20);
list.set(1, 99); // OK -- modify an element
// list.add(21); // Runtime error because the
// underlying array cannot be resized.
}
} ///:~

4. 容器的打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//: holding/PrintingContainers.java
// Containers print themselves automatically.
import java.util.*;
import static net.mindview.util.Print.*;
public class PrintingContainers {
static Collection fill(Collection<String> collection) {
collection.add("rat");
collection.add("cat");
collection.add("dog");
collection.add("dog");
return collection;
}
static Map fill(Map<String,String> map) {
map.put("rat", "Fuzzy");
map.put("cat", "Rags");
map.put("dog", "Bosco");
map.put("dog", "Spot");
return map;
}
public static void main(String[] args) {
print(fill(new ArrayList<String>()));
print(fill(new LinkedList<String>()));
print(fill(new HashSet<String>()));
print(fill(new TreeSet<String>()));
print(fill(new LinkedHashSet<String>()));
print(fill(new HashMap<String,String>()));
print(fill(new TreeMap<String,String>()));
print(fill(new LinkedHashMap<String,String>()));
}
} /* Output:
[rat, cat, dog, dog]
[rat, cat, dog, dog]
[dog, cat, rat]
[cat, dog, rat]
[rat, cat, dog]
{dog=Spot, cat=Rags, rat=Fuzzy}
{cat=Rags, dog=Spot, rat=Fuzzy}
{rat=Fuzzy, cat=Rags, dog=Spot}
*///:~

目前只需要知道HashMap是最快的存储方式.

5. List

有两种类型的List :

  • 基本的ArrayList,它长于随机访问元素,但是在List的中间插入和移除元素时较慢.
  • LinkedList,它通过代价较低的在List中间进行的插入和删除操作,提供了优化的顺序访问.LinkedList在随机访问方面比较慢,但是它的特性集较ArrayList更大.

6. 迭代器

Java的Iteratir只能单向移动,这个Iterator只能用来 :

  • 使用方法iterator()要求容器返回一个Iterator.Iterator将准备好返回序列的第一个元素.
  • 使用next()获得序列中下一个元素.
  • 使用hasNext()检查序列中是否还有元素.
  • 使用remove()将迭代器新近返回的元素删除.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//: holding/SimpleIteration.java
import typeinfo.pets.*;
import java.util.*;
public class SimpleIteration {
public static void main(String[] args) {
List<Pet> pets = Pets.arrayList(12);
Iterator<Pet> it = pets.iterator();
while(it.hasNext()) {
Pet p = it.next();
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
// A simpler approach, when possible:
for(Pet p : pets)
System.out.print(p.id() + ":" + p + " ");
System.out.println();
// An Iterator can also remove elements:
it = pets.iterator();
for(int i = 0; i < 6; i++) {
it.next();
it.remove();
}
System.out.println(pets);
}
} /* Output:
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 8:Cymric 9:Rat 10:EgyptianMau 11:Hamster
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 8:Cymric 9:Rat 10:EgyptianMau 11:Hamster
[Pug, Manx, Cymric, Rat, EgyptianMau, Hamster]
*///:~

Iterator还可以移除由next()产生的最后一个元素,这意味着在调用remove()之前必须先调用next().

6.1 ListIterator

ListIterator是一个更加强大的Iterator的子类型,它只能用于各种List类的访问.尽管Iterator只能想前移动,但是ListIterator可以双向移动.它还可以产生相对于迭代器在列表中指向的当前位子的前一个和后一个元素的索引,并且可以使用set()方法替换它访问过的最后一个元素.可以通过调用ListIterator()方法产生一个指向List开始处的ListIterator,并且还可以通过调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//: holding/ListIteration.java
import typeinfo.pets.*;
import java.util.*;
public class ListIteration {
public static void main(String[] args) {
List<Pet> pets = Pets.arrayList(8);
ListIterator<Pet> it = pets.listIterator();
while(it.hasNext())
System.out.print(it.next() + ", " + it.nextIndex() +
", " + it.previousIndex() + "; ");
System.out.println();
// Backwards:
while(it.hasPrevious())
System.out.print(it.previous().id() + " ");
System.out.println();
System.out.println(pets);
it = pets.listIterator(3);
while(it.hasNext()) {
it.next();
it.set(Pets.randomPet());
}
System.out.println(pets);
}
} /* Output:
Rat, 1, 0; Manx, 2, 1; Cymric, 3, 2; Mutt, 4, 3; Pug, 5, 4; Cymric, 6, 5; Pug, 7, 6; Manx, 8, 7;
7 6 5 4 3 2 1 0
[Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Manx]
[Rat, Manx, Cymric, Cymric, Rat, EgyptianMau, Hamster, EgyptianMau]
*///:~

7. LinkedList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//: holding/LinkedListFeatures.java
import typeinfo.pets.*;
import java.util.*;
import static net.mindview.util.Print.*;
public class LinkedListFeatures {
public static void main(String[] args) {
LinkedList<Pet> pets =
new LinkedList<Pet>(Pets.arrayList(5));
print(pets);
// Identical:
print("pets.getFirst(): " + pets.getFirst());
print("pets.element(): " + pets.element());
// Only differs in empty-list behavior:
print("pets.peek(): " + pets.peek());
// Identical; remove and return the first element:
print("pets.remove(): " + pets.remove());
print("pets.removeFirst(): " + pets.removeFirst());
// Only differs in empty-list behavior:
print("pets.poll(): " + pets.poll());
print(pets);
pets.addFirst(new Rat());
print("After addFirst(): " + pets);
pets.offer(Pets.randomPet());
print("After offer(): " + pets);
pets.add(Pets.randomPet());
print("After add(): " + pets);
pets.addLast(new Hamster());
print("After addLast(): " + pets);
print("pets.removeLast(): " + pets.removeLast());
}
} /* Output:
[Rat, Manx, Cymric, Mutt, Pug]
pets.getFirst(): Rat
pets.element(): Rat
pets.peek(): Rat
pets.remove(): Rat
pets.removeFirst(): Manx
pets.poll(): Cymric
[Mutt, Pug]
After addFirst(): [Rat, Mutt, Pug]
After offer(): [Rat, Mutt, Pug, Cymric]
After add(): [Rat, Mutt, Pug, Cymric, Pug]
After addLast(): [Rat, Mutt, Pug, Cymric, Pug, Hamster]
pets.removeLast(): Hamster
*///:~

8. Stack

“栈”通常是制”后进先出(LIFO)”的容器,有时栈也被称为叠加栈,因为最后”压入”栈的元素,第一个”弹出”栈.经常用来类比栈的事物是装有弹簧的储放器中的自助餐托盘,最后装入的托盘总是最先拿出使用的.

LinkedList具有能够直接实现栈的所有功能的方法,因此可以直接将LinkedList作为栈使用,不过,有时一个真正的”栈”更能把事情说清楚 :

1
2
3
4
5
6
7
8
9
10
11
12
13
//: net/mindview/util/Stack.java
// Making a stack from a LinkedList.
package net.mindview.util;
import java.util.LinkedList;
public class Stack<T> {
private LinkedList<T> storage = new LinkedList<T>();
public void push(T v) { storage.addFirst(v); }
public T peek() { return storage.getFirst(); }
public T pop() { return storage.removeFirst(); }
public boolean empty() { return storage.isEmpty(); }
public String toString() { return storage.toString(); }
} ///:~

通过使用泛型,引入了在栈的定义中最简单的可行示例.类名之后的告诉编译器这将是一个参数化类型,而其中的类型参数,即在类被使用时将会被实际类型替换的参数,就是T.

Set

Set不保存重复的元素,如果你试图将相同的对象的多个实例添加到Set中,那么它就会阻止这种重复现象.Set最常用被使用的是测试归数学,你可以很容易地询问某个对象是否在某个Set中.所以,查找就成为了Set中最重要的操作,因此可以选择一个Hashset的实现,它专门对快速查找进行了优化.

Set是基于对象的值来确定归属性的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//: holding/SetOfInteger.java
import java.util.*;
public class SetOfInteger {
public static void main(String[] args) {
Random rand = new Random(47);
Set<Integer> intset = new HashSet<Integer>();
for(int i = 0; i < 10000; i++)
intset.add(rand.nextInt(30));
System.out.println(intset);
}
} /* Output:
[15, 8, 23, 16, 7, 22, 9, 21, 6, 1, 29, 14, 24, 4, 19, 26, 11, 18, 3, 12, 27, 17, 2, 13, 28, 20, 25, 10, 5, 0]
*///:~

输出的顺序没有任何规则可循,着是因为出于速度,HashSet使用了散列.HashSet所维护的顺序与TreeSet或LinkedHashSet都不同,因为它们的实现具有不同的元素存储方式.TreeSet将元素存储在红-黑树数据结构中,而HashSet使用的是散列函数.

如想对结果排序,可以使用TreeSet来代替HashSet.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//: holding/SortedSetOfInteger.java
import java.util.*;
public class SortedSetOfInteger {
public static void main(String[] args) {
Random rand = new Random(47);
SortedSet<Integer> intset = new TreeSet<Integer>();
for(int i = 0; i < 10000; i++)
intset.add(rand.nextInt(30));
System.out.println(intset);
}
} /* Output:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
*///:~
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//: holding/SetOperations.java
import java.util.*;
import static net.mindview.util.Print.*;
public class SetOperations {
public static void main(String[] args) {
Set<String> set1 = new HashSet<String>();
Collections.addAll(set1,
"A B C D E F G H I J K L".split(" "));
set1.add("M");
print("H: " + set1.contains("H"));
print("N: " + set1.contains("N"));
Set<String> set2 = new HashSet<String>();
Collections.addAll(set2, "H I J K L".split(" "));
print("set2 in set1: " + set1.containsAll(set2));
set1.remove("H");
print("set1: " + set1);
print("set2 in set1: " + set1.containsAll(set2));
set1.removeAll(set2);
print("set2 removed from set1: " + set1);
Collections.addAll(set1, "X Y Z".split(" "));
print("'X Y Z' added to set1: " + set1);
}
} /* Output:
H: true
N: false
set2 in set1: true
set1: [D, K, C, B, L, G, I, M, A, F, J, E]
set2 in set1: false
set2 removed from set1: [D, C, B, G, M, A, F, E]
'X Y Z' added to set1: [Z, D, C, B, G, M, A, F, Y, X, E]
*///:~

10. Map

考虑一个程序,它将用来检查Java的Random类的随机性.Random可以将产生理想的数字分部,在本例中,键是由Random产生的数字,而值是该数字出现的次数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//: holding/Statistics.java
// Simple demonstration of HashMap.
import java.util.*;
public class Statistics {
public static void main(String[] args) {
Random rand = new Random(47);
Map<Integer,Integer> m =
new HashMap<Integer,Integer>();
for(int i = 0; i < 10000; i++) {
// Produce a number between 0 and 20:
int r = rand.nextInt(20);
Integer freq = m.get(r);
m.put(r, freq == null ? 1 : freq + 1);
}
System.out.println(m);
}
} /* Output:
{15=497, 4=481, 19=464, 8=468, 11=531, 16=533, 18=478, 3=508, 7=471, 12=521, 17=509, 2=489, 13=506, 9=549, 6=519, 1=502, 14=477, 10=513, 5=503, 0=481}
*///:~

在main()中,自动包装机将随机生成的int转换为HashMap可以使用Integer引用.如果键不在容器中,get()方法则返回null(这表示该数字第一次被找到).否则,get()方法将产生与该键相关联的Integer的值,然后这个值被递增(自动包装机制再次简化了表达式,但是确定发生了对Integer的包装和拆包).

11. Queue

队列是一个典型的先进先出(FIFO)的容器.即从容器的一端放入事物,从另一端取出,并请求事物放入容器的顺序与取出的顺序是相同的.队列常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径.队列在并发编程中特别重要.

LinkedList提供了方法以支持队列的行为,并且它实现了Queue接口,因此LinkedList可以用作Queue的一种实现.通过将LinkedList向上转型为Queue.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//: holding/QueueDemo.java
// Upcasting to a Queue from a LinkedList.
import java.util.*;
public class QueueDemo {
public static void printQ(Queue queue) {
while(queue.peek() != null)
System.out.print(queue.remove() + " ");
System.out.println();
}
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<Integer>();
Random rand = new Random(47);
for(int i = 0; i < 10; i++)
queue.offer(rand.nextInt(i + 10));
printQ(queue);
Queue<Character> qc = new LinkedList<Character>();
for(char c : "Brontosaurus".toCharArray())
qc.offer(c);
printQ(qc);
}
} /* Output:
8 1 1 1 5 14 3 1 0 1
B r o n t o s a u r u s
*///:~

offer()方法是与Queue相关的方法之一,它在允许的的情况下,将一个元素插入到队尾,或者返回false.peek()和element()都将在不移除的情况下返回队头,但是peek()方法在队列为空时返回null,而element()会抛出NoSuchElementException异常.poll()和remove()方法将移除并返回队头,但是poll()在队列为空时返回null,而remove()会抛出NoSuchElementException异常.

自动包装机制会自动将nextInt()方法的int结果转换为queue所需的Integer对象.将char 出转换为qc所需的Character对象.Queue接口窄化了对LinkedList的方法的访问权限,以使得只有适当的方法才可以使用.

11.1 PrionrityQueue

先进先出描述了最典型的队列规则.队列规则是指在给定一组队列中的元素的情况下,确定下一个弹出队列的元素的规则.先进先出声明的是下一个运算应该是等待时间最长的元素.

优先级队列声明下一个弹出元素是最需要的元素.如果构建了一个消息系统,某些信息比其他信息更重要,因而应该更快得到处理,那么它们何时得到处理就与它们何时到达无关.

当你在PriorityQueue上调用offer()方法来插入一个对象时,这个对象会在队列中被排序.默认的排序将使用对象在队列中的自然排序.但是你可以通过提供自己的Comparator来修改这个顺序.PriorityQueue可以确保当你调用peek(),poll()和remove()方法时,获取的元素将是队列中优先级别最高的元素.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//: holding/PriorityQueueDemo.java
import java.util.*;
public class PriorityQueueDemo {
public static void main(String[] args) {
PriorityQueue<Integer> priorityQueue =
new PriorityQueue<Integer>();
Random rand = new Random(47);
for(int i = 0; i < 10; i++)
priorityQueue.offer(rand.nextInt(i + 10));
QueueDemo.printQ(priorityQueue);
List<Integer> ints = Arrays.asList(25, 22, 20,
18, 14, 9, 3, 1, 1, 2, 3, 9, 14, 18, 21, 23, 25);
priorityQueue = new PriorityQueue<Integer>(ints);
QueueDemo.printQ(priorityQueue);
priorityQueue = new PriorityQueue<Integer>(
ints.size(), Collections.reverseOrder());
priorityQueue.addAll(ints);
QueueDemo.printQ(priorityQueue);
String fact = "EDUCATION SHOULD ESCHEW OBFUSCATION";
List<String> strings = Arrays.asList(fact.split(""));
PriorityQueue<String> stringPQ =
new PriorityQueue<String>(strings);
QueueDemo.printQ(stringPQ);
stringPQ = new PriorityQueue<String>(
strings.size(), Collections.reverseOrder());
stringPQ.addAll(strings);
QueueDemo.printQ(stringPQ);
Set<Character> charSet = new HashSet<Character>();
for(char c : fact.toCharArray())
charSet.add(c); // Autoboxing
PriorityQueue<Character> characterPQ =
new PriorityQueue<Character>(charSet);
QueueDemo.printQ(characterPQ);
}
} /* Output:
0 1 1 1 1 1 3 5 8 14
1 1 2 3 3 9 9 14 14 18 18 20 21 22 23 25 25
25 25 23 22 21 20 18 18 14 14 9 9 3 3 2 1 1
A A B C C C D D E E E F H H I I L N N O O O O S S S T T U U U W
W U U U T T S S S O O O O N N L I I H H F E E E D D C C C B A A
A B C D E F H I L N O S T U W
*///:~

12. Collection和Iterator

Collection是描述所有序列容器的共性的根接口,它可能会被认为是一个”附属接口”,即因为要表示其他若干个接口的共性而出现的接口.另外,java.util.AbstractCollection类提供了Collection的默认实现,使得你可以创建AbstractCollection的子类型,而其中没有不必要的代码重复.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//: holding/InterfaceVsIterator.java
import typeinfo.pets.*;
import java.util.*;
public class InterfaceVsIterator {
public static void display(Iterator<Pet> it) {
while(it.hasNext()) {
Pet p = it.next();
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
}
public static void display(Collection<Pet> pets) {
for(Pet p : pets)
System.out.print(p.id() + ":" + p + " ");
System.out.println();
}
public static void main(String[] args) {
List<Pet> petList = Pets.arrayList(8);
Set<Pet> petSet = new HashSet<Pet>(petList);
Map<String,Pet> petMap =
new LinkedHashMap<String,Pet>();
String[] names = ("Ralph, Eric, Robin, Lacey, " +
"Britney, Sam, Spot, Fluffy").split(", ");
for(int i = 0; i < names.length; i++)
petMap.put(names[i], petList.get(i));
display(petList);
display(petSet);
display(petList.iterator());
display(petSet.iterator());
System.out.println(petMap);
System.out.println(petMap.keySet());
display(petMap.values());
display(petMap.values().iterator());
}
} /* Output:
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
4:Pug 6:Pug 3:Mutt 1:Manx 5:Cymric 7:Manx 2:Cymric 0:Rat
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
4:Pug 6:Pug 3:Mutt 1:Manx 5:Cymric 7:Manx 2:Cymric 0:Rat
{Ralph=Rat, Eric=Manx, Robin=Cymric, Lacey=Mutt, Britney=Pug, Sam=Cymric, Spot=Pug, Fluffy=Manx}
[Ralph, Eric, Robin, Lacey, Britney, Sam, Spot, Fluffy]
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
*///:~
  • Foreach与迭代器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//: holding/ForEachCollections.java
// All collections work with foreach.
import java.util.*;
public class ForEachCollections {
public static void main(String[] args) {
Collection<String> cs = new LinkedList<String>();
Collections.addAll(cs,
"Take the long way home".split(" "));
for(String s : cs)
System.out.print("'" + s + "' ");
}
} /* Output:
'Take' 'the' 'long' 'way' 'home'
*///:~

下面的例子能显示所有操作系统的环境变量:

1
2
3
4
5
6
7
8
9
10
11
//: holding/EnvironmentVariables.java
import java.util.*;
public class EnvironmentVariables {
public static void main(String[] args) {
for(Map.Entry entry: System.getenv().entrySet()) {
System.out.println(entry.getKey() + ": " +
entry.getValue());
}
}
} /* (Execute to see output) *///:~

13.1 适配器方法惯用法

一种习惯用的方法是适配器方法的惯用法.”适配器”部分来自于设计模式,因为你必须提供特定接口以满足foreach语句.当你有一个接口并需要另外一个接口时,编写适配器就可以解决问题.这里,在默认的前向迭代器的基础上,添加产生反向迭代器的能力.而是添加一个能够产生Iterable对象的方法,该对象可以用于foreach语句.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//: holding/AdapterMethodIdiom.java
// The "Adapter Method" idiom allows you to use foreach
// with additional kinds of Iterables.
import java.util.*;
class ReversibleArrayList<T> extends ArrayList<T> {
public ReversibleArrayList(Collection<T> c) { super(c); }
public Iterable<T> reversed() {
return new Iterable<T>() {
public Iterator<T> iterator() {
return new Iterator<T>() {
int current = size() - 1;
public boolean hasNext() { return current > -1; }
public T next() { return get(current--); }
public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
};
}
};
}
}
public class AdapterMethodIdiom {
public static void main(String[] args) {
ReversibleArrayList<String> ral =
new ReversibleArrayList<String>(
Arrays.asList("To be or not to be".split(" ")));
// Grabs the ordinary iterator via iterator():
for(String s : ral)
System.out.print(s + " ");
System.out.println();
// Hand it the Iterable of your choice
for(String s : ral.reversed())
System.out.print(s + " ");
}
} /* Output:
To be or not to be
be to not or be To
*///:~

如果直接将ral对象置于foreach语句中,将得到默认的前向迭代器.但是如果在该对象上调用reversed()方法,就会产生不同的行为.

    1. 总结

Java提供了大量持有对象的方式 :

  • 数组将数字与对象联系起来.它保存类型明确的对象,查询对象时,不需要对结果做类型转换.它可以是多维的,可以保存基本类型的数据.但是,数组一旦生成,其容量就不可改变.
  • Collection保存单一的元素,而Map保存相关联的键值对.有了Java的泛型,就可以指定容器中存放的对象类型,因此不会将错误的类型放入到泛型中.容器不能持有基本类型,但是自动包装机制会仔细地执行基本类型到容器中所持有的包装器类型之间的双向转换.
  • 像数组一样,List也建立数字索引与对象的关联,因此,数组和List都是排好序的容器.List能够自动扩充容量.
  • 如果要进行大量的随机访问,就使用ArrayList,如果要经常从表中插入和删除元素,则需要使用LinkedList.
  • 各种Queue以及栈的行为,由LinkedList提供支持.
  • Map是一种将对象与对象相关联的设计.HaspMap设计用来快速访问,而TreeMap保存”键”始终处于排序状态,所以没有HashMap快.LinkedHashMap保存元素插入的顺序,也是通过散列提供了快速访问的能力.
  • Set不接受重复元素.HashSet提供最快的查询速度,而TreeSet保存元素处于排序的状态.LinkedHashSet以插入顺序保存元素.

简单的容器分类

第10章 内部类

发表于 2017-03-28 | 分类于 Java | 阅读次数

可以将一个类的定义放在另一个类的定义内部,这就是内部类.

1. 创建内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//: innerclasses/Parcel1.java
// Creating inner classes.
public class Parcel1 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
// Using inner classes looks just like
// using any other class, within Parcel1:
public void ship(String dest) {
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel1 p = new Parcel1();
p.ship("Tasmania");
}
} /* Output:
Tasmania
*///:~

2. 链接到外部类

当生成一个内部类对象时,此对象与制造它的外围对象(enclosing object)之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件.此外,内部类还拥有其外围类的所有元素的访问权.

内部类自动拥有对其外围类所有成员的访问权?
当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用.然而,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员.幸运的是 : 编译器会帮你处理所有的细节,但你现在可以看到 : 内部类的对象只能在与其外围类的对象相关联的情况下才会被创建.构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错!

3. 使用.this和.new

如果需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和this.这样产生的引用自动地具有正确的类型.

有时你可能想要告知某些其他对象,去创建某个内部类的对象.要实现此目的,你必须用new表达式中提供对其他外部类对象的引用,这就需要使用.new语法.

1
2
3
4
5
6
7
8
9
package itceo.net;
public class DotNew {
public class Inner{}
public static void main(String[] args) {
DotNew doNew = new DotNew();
DotNew.Inner inner = doNew.new Inner();
}
}

在拥有外部类对象之前是不可能创建内部类对象的,这是因为内部类对象会暗暗连接到创建它的外部类对象上.但是,如果你创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用.

4. 内部类向上转型

当将内部类向上转型为基类,尤其是转型为一个接口的时候,内部类就有了用武之地.(从实现了某个接口的对象,得到对此接口的引用,与向上转型为这个对象的基类,实质上效果是一样的.)

5. 在方法和作用域的内部类

第一个例子展示了在方法的作用域内(而不是在其他类的作用域内)创建一个完整的类.这被称为局部内部类.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//: innerclasses/Parcel5.java
// Nesting a class within a method.
public class Parcel5 {
public Destination destination(String s) {
class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
return new PDestination(s);
}
public static void main(String[] args) {
Parcel5 p = new Parcel5();
Destination d = p.destination("Tasmania");
}
} ///:~

6. 匿名内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//: innerclasses/Parcel7.java
// Returning an instance of an anonymous inner class.
public class Parcel7 {
public Contents contents() {
return new Contents() { // Insert a class definition
private int i = 11;
public int value() { return i; }
}; // Semicolon required in this case
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Contents c = p.contents();
}
} ///:~

contents()方法将返回值的生成与表示这个返回值的类的定义结合在一起!这种奇怪的语法指的是 : 创建一个继承自Contents的匿名类的对象.通过 new 表达式返回的引用被自动向上转型为Contents的引用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//: innerclasses/Parcel7b.java
// Expanded version of Parcel7.java
public class Parcel7b {
class MyContents implements Contents {
private int i = 11;
public int value() { return i; }
}
public Contents contents() { return new MyContents(); }
public static void main(String[] args) {
Parcel7b p = new Parcel7b();
Contents c = p.contents();
}
} ///:~

6.1 再访工厂方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//: innerclasses/Factories.java
import static net.mindview.util.Print.*;
interface Service {
void method1();
void method2();
}
interface ServiceFactory {
Service getService();
}
class Implementation1 implements Service {
private Implementation1() {}
public void method1() {print("Implementation1 method1");}
public void method2() {print("Implementation1 method2");}
public static ServiceFactory factory =
new ServiceFactory() {
public Service getService() {
return new Implementation1();
}
};
}
class Implementation2 implements Service {
private Implementation2() {}
public void method1() {print("Implementation2 method1");}
public void method2() {print("Implementation2 method2");}
public static ServiceFactory factory =
new ServiceFactory() {
public Service getService() {
return new Implementation2();
}
};
}
public class Factories {
public static void serviceConsumer(ServiceFactory fact) {
Service s = fact.getService();
s.method1();
s.method2();
}
public static void main(String[] args) {
serviceConsumer(Implementation1.factory);
// Implementations are completely interchangeable:
serviceConsumer(Implementation2.factory);
}
} /* Output:
Implementation1 method1
Implementation1 method2
Implementation2 method1
Implementation2 method2
*///:~

7. 嵌套类

如果不需要内部类对象与外围类对象之间有联系,可以将内部类声明为static.这通常称为嵌套类.我们需要明白 : 普通的内部类对象隐私地保存了一个引用,指向创建它的外围类对象.然而,当内部类是static时,就不是这样了.

  • 要创建嵌套类的对象,并不需要其外围类的对象.
  • 不能从嵌套类的对象中访问非静态的外围类对象.

嵌套类与普通的内部类还有一个区别.普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类.但是嵌套类可以包含这些东西 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//: innerclasses/Parcel11.java
// Nested classes (static inner classes).
public class Parcel11 {
private static class ParcelContents implements Contents {
private int i = 11;
public int value() { return i; }
}
protected static class ParcelDestination
implements Destination {
private String label;
private ParcelDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
// Nested classes can contain other static elements:
public static void f() {}
static int x = 10;
static class AnotherLevel {
public static void f() {}
static int x = 10;
}
}
public static Destination destination(String s) {
return new ParcelDestination(s);
}
public static Contents contents() {
return new ParcelContents();
}
public static void main(String[] args) {
Contents c = contents();
Destination d = destination("Tasmania");
}
} ///:~

7.1 接口内部的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//: innerclasses/ClassInInterface.java
// {main: ClassInInterface$Test}
public interface ClassInInterface {
void howdy();
class Test implements ClassInInterface {
public void howdy() {
System.out.println("Howdy!");
}
public static void main(String[] args) {
new Test().howdy();
}
}
} /* Output:
Howdy!
*///:~

7.2 从外层嵌套类中访问外部类的成员

一个内部类被嵌套多少层并不重要,它能透明地访问所有它所嵌入的外围类的所有成员.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//: innerclasses/MultiNestingAccess.java
// Nested classes can access all members of all
// levels of the classes they are nested within.
class MNA {
private void f() {}
class A {
private void g() {}
public class B {
void h() {
g();
f();
}
}
}
}
public class MultiNestingAccess {
public static void main(String[] args) {
MNA mna = new MNA();
MNA.A mnaa = mna.new A();
MNA.A.B mnaab = mnaa.new B();
mnaab.h();
}
} ///:~

可以看到在MNA.A.B中,调用方法g()和f()不需要任何条件(即使它们被定义为private),这个例子同时展示了如何从不同的类里创建多层嵌套的内部类对象的基本语法..new语法能产生正确的作用域,所以不必在调用构造器时限定类名.

8. 为什么需要内部类

一般来说,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象.所以可以认为内部类提供了某种进入某外围类的窗口.

每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响.

单一类与内部类的比较 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//: innerclasses/MultiInterfaces.java
// Two ways that a class can implement multiple interfaces.
package innerclasses;
interface A {}
interface B {}
class X implements A, B {}
class Y implements A {
B makeB() {
// Anonymous inner class:
return new B() {};
}
}
public class MultiInterfaces {
static void takesA(A a) {}
static void takesB(B b) {}
public static void main(String[] args) {
X x = new X();
Y y = new Y();
takesA(x);
takesA(y);
takesB(x);
takesB(y.makeB());
}
} ///:~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//: innerclasses/MultiImplementation.java
// With concrete or abstract classes, inner
// classes are the only way to produce the effect
// of "multiple implementation inheritance."
package innerclasses;
class D {}
abstract class E {}
class Z extends D {
E makeE() { return new E() {}; }
}
public class MultiImplementation {
static void takesD(D d) {}
static void takesE(E e) {}
public static void main(String[] args) {
Z z = new Z();
takesD(z);
takesE(z.makeE());
}
} ///:~
  • 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立.
  • 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类.
  • 创建内部类对象的时刻并不依赖于外围类对象的创建.
  • 内部类没有令人迷惑的”is-a”关系;它就是一个独立的实体.

8.1 闭包与回调

闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域.通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向对其外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括private.

8.2 内部类与控制框架

设计模式总是将变化的事物与保持不变的事物分离开,在这个模式中,模块方法是保持不变的事物,而可覆盖的方法就是变化的事物.

9. 内部类的继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//: innerclasses/InheritInner.java
// Inheriting an inner class.
class WithInner {
class Inner {}
}
public class InheritInner extends WithInner.Inner {
//! InheritInner() {} // Won't compile
InheritInner(WithInner wi) {
wi.super();
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
} ///:~

InheritInner只继承内部类,而不是外围类.但要生成一个构造器时,默认的构造器并不友好,而且不能只是传递一个指向外围类对象的引用.因此,必须在构造器内使用如下语法 : enclosingClassReference.super(),只有提供必要的引用,然后程序才能编译通过.

10. 内部类可以被覆盖吗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//: innerclasses/BigEgg2.java
// Proper inheritance of an inner class.
import static net.mindview.util.Print.*;
class Egg2 {
protected class Yolk {
public Yolk() { print("Egg2.Yolk()"); }
public void f() { print("Egg2.Yolk.f()");}
}
private Yolk y = new Yolk();
public Egg2() { print("New Egg2()"); }
public void insertYolk(Yolk yy) { y = yy; }
public void g() { y.f(); }
}
public class BigEgg2 extends Egg2 {
public class Yolk extends Egg2.Yolk {
public Yolk() { print("BigEgg2.Yolk()"); }
public void f() { print("BigEgg2.Yolk.f()"); }
}
public BigEgg2() { insertYolk(new Yolk()); }
public static void main(String[] args) {
Egg2 e2 = new BigEgg2();
e2.g();
}
} /* Output:
Egg2.Yolk()
New Egg2()
Egg2.Yolk()
BigEgg2.Yolk()
BigEgg2.Yolk.f()
*///:~

11. 局部内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//: innerclasses/LocalInnerClass.java
// Holds a sequence of Objects.
import static net.mindview.util.Print.*;
interface Counter {
int next();
}
public class LocalInnerClass {
private int count = 0;
Counter getCounter(final String name) {
// A local inner class:
class LocalCounter implements Counter {
public LocalCounter() {
// Local inner class can have a constructor
print("LocalCounter()");
}
public int next() {
printnb(name); // Access local final
return count++;
}
}
return new LocalCounter();
}
// The same thing with an anonymous inner class:
Counter getCounter2(final String name) {
return new Counter() {
// Anonymous inner class cannot have a named
// constructor, only an instance initializer:
{
print("Counter()");
}
public int next() {
printnb(name); // Access local final
return count++;
}
};
}
public static void main(String[] args) {
LocalInnerClass lic = new LocalInnerClass();
Counter
c1 = lic.getCounter("Local inner "),
c2 = lic.getCounter2("Anonymous inner ");
for(int i = 0; i < 5; i++)
print(c1.next());
for(int i = 0; i < 5; i++)
print(c2.next());
}
} /* Output:
LocalCounter()
Counter()
Local inner 0
Local inner 1
Local inner 2
Local inner 3
Local inner 4
Anonymous inner 5
Anonymous inner 6
Anonymous inner 7
Anonymous inner 8
Anonymous inner 9
*///:~

12. 内部类标识符

外围类的名字,加上$,再加上内部类的名字.

第9章 接口

发表于 2017-03-28 | 分类于 Java | 阅读次数

接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法.

1. 抽象类和抽象方法

Java提供了一个叫做”抽象方法”的机制,这种方法是不完整的,仅有声明而没有方法体.下面是抽象方法声明所采用的语法 :

1
abstract void f();

包括抽象方法的类叫做抽象类,如果一个类包含了一个或多个抽象方法,该类必须被限定为抽象的.

如果从一个抽象类继承,并想创建该新类的对象,那么就必须为基类中的所有抽象方法提供方法定义,如果不这样做,那么导出类也是抽象类,且编译器将会强制我们用abstract关键字来限定这个类.

既然使某个类成为抽象类并不需要所有的方法都是抽象的,所有仅需将某些方法声明为抽象的即可.

Instrument

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
//: interfaces/music4/Music4.java
// Abstract classes and methods.
package interfaces.music4;
import polymorphism.music.Note;
import static net.mindview.util.Print.*;
abstract class Instrument {
private int i; // Storage allocated for each
public abstract void play(Note n);
public String what() { return "Instrument"; }
public abstract void adjust();
}
class Wind extends Instrument {
public void play(Note n) {
print("Wind.play() " + n);
}
public String what() { return "Wind"; }
public void adjust() {}
}
class Percussion extends Instrument {
public void play(Note n) {
print("Percussion.play() " + n);
}
public String what() { return "Percussion"; }
public void adjust() {}
}
class Stringed extends Instrument {
public void play(Note n) {
print("Stringed.play() " + n);
}
public String what() { return "Stringed"; }
public void adjust() {}
}
class Brass extends Wind {
public void play(Note n) {
print("Brass.play() " + n);
}
public void adjust() { print("Brass.adjust()"); }
}
class Woodwind extends Wind {
public void play(Note n) {
print("Woodwind.play() " + n);
}
public String what() { return "Woodwind"; }
}
public class Music4 {
// Doesn't care about type, so new types
// added to the system still work right:
static void tune(Instrument i) {
// ...
i.play(Note.MIDDLE_C);
}
static void tuneAll(Instrument[] e) {
for(Instrument i : e)
tune(i);
}
public static void main(String[] args) {
// Upcasting during addition to the array:
Instrument[] orchestra = {
new Wind(),
new Percussion(),
new Stringed(),
new Brass(),
new Woodwind()
};
tuneAll(orchestra);
}
} /* Output:
Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
*///:~

创建抽象类和抽象方法非常有用,因为它们可以使类的抽象性明确起来,并告诉用户和编译器计划怎么来使用它.

2. 接口

interface关键字使抽象的概念更接近了一步,abstract关键字允许人们在类中创建一个或多个没有任何定义的方法(提供了接口的方法),但是没有提供任何相应的具体实现,这些实现是由此类的继承者创建.interface这个关键字产生一个完全抽象的类,它根本就没有提供任何具体实现.它允许创建者确定方法名,参数列表和返回类型,但是没有任何方法体,接口只提供了形式,而未提供任何具体实现.

一个接口表示 : 所有实现了该特定接口的类看起来都像这样.因此,任何使用某特定接口的代码都知道可以调用该接口的那些方法,而且仅需知道这些.因此,接口被用来建立类与类之间的协议.

但是,interface不仅仅是一个极度抽象的类,因为它允许人们通过创建一个能够被向上转型为多种基类的类型,来实现某种类似多重继承变种的特性.

想要创建一个接口,需要用interface关键字来替代class关键字.就像类一样,可以在interface关键字前面加public关键字,如果不添加public关键字,则它只具有包访问权限,这样它就只能在同一个包内可用.接口也可以包含域,但是这些域隐式地是static和final的.

要让类遵循某个特定接口,需要使用implements关键字,它表示 : interface只是它的外貌,但是现在我要声明它是如何工作的.除此之外,它看起来比较像继承.

interface
可以从Woodwind和Brass类中看到,一旦实现了某些接口,其实现就变成了一个普通的类,就可以按常规方法扩展它.

3. 完全解耦

只有一个方法操作的是类而非接口,那么你就只能使用这个类及其子类.如果你想要将这个方法应用于不在此继承结构中的某个类,那么就不行了.接口可以在很大程度上放宽这种限制.

例如,假设有一个Processor类,它由一个name()方法;另外还有一个Process()方法,该方法接受输入参数,修改它的值,然后产生输出.这个类作为基类而被扩展,用来创建各种不同类型的Processor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//: interfaces/classprocessor/Apply.java
package interfaces.classprocessor;
import java.util.*;
import static net.mindview.util.Print.*;
class Processor {
public String name() {
return getClass().getSimpleName();
}
Object process(Object input) { return input; }
}
class Upcase extends Processor {
String process(Object input) { // Covariant return
return ((String)input).toUpperCase();
}
}
class Downcase extends Processor {
String process(Object input) {
return ((String)input).toLowerCase();
}
}
class Splitter extends Processor {
String process(Object input) {
// The split() argument divides a String into pieces:
return Arrays.toString(((String)input).split(" "));
}
}
public class Apply {
public static void process(Processor p, Object s) {
print("Using Processor " + p.name());
print(p.process(s));
}
public static String s =
"Disagreement with beliefs is by definition incorrect";
public static void main(String[] args) {
process(new Upcase(), s);
process(new Downcase(), s);
process(new Splitter(), s);
}
} /* Output:
Using Processor Upcase
DISAGREEMENT WITH BELIEFS IS BY DEFINITION INCORRECT
Using Processor Downcase
disagreement with beliefs is by definition incorrect
Using Processor Splitter
[Disagreement, with, beliefs, is, by, definition, incorrect]
*///:~

Apply.process()方法可以接受任何类型的processor,并将其应用到一个Object对象上,然后打印结果.像本例这样,创建一个能够根据所传递参数对象的不同而具有不同行为的方法,被称为策略设计模式.这类方法包含所要执行的算法中固定不变的部分,而”策略”包含变化的部分.策略就是传递进去的参数对象,它包含要执行的代码.这里,Processor对象就是一个策略,在main()中可以看到有三种不同类型的策略应用到了String类型的s对象上.

split()方法是Stirng类的一部分,它接受String类型的对象,并以传递进来的参数作为边界,并将String对象分割开,然后返回一个数组String[],它在这里被用来当作创建String数组的快捷方式.

4. Java中的多重继承

接口不仅仅只是一种更纯粹形式的抽象类,因为接口是根本没有任何具体实现的,也就是说,没有任何与接口相关的存储;在C++中,组合多个类的接口被称作为多重继承.但在java中,你可以执行相同的行为,但是只有一个类可以有具体实现,因此,通过组合多个接口.

在导出类中,不强制要求必须有一个是抽象的或”具体的”基类.如果要从一个非接口的类继承,那么只能有一个类去继承.其余的基元素都必须是接口.需要将所有的接口名都置于implements关键字之后,用逗号将它们一一分割开.可以继承任意多个接口,并可以向上转型为每个接口.因为每一个接口都是一个独立的类型.

5. 通过继承来扩展接口

通过继承,可以很容易地在接口中添加新的方法声明,还可以通过继承在新接口中组合数个接口.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//: interfaces/HorrorShow.java
// Extending an interface with inheritance.
interface Monster {
void menace();
}
interface DangerousMonster extends Monster {
void destroy();
}
interface Lethal {
void kill();
}
class DragonZilla implements DangerousMonster {
public void menace() {}
public void destroy() {}
}
interface Vampire extends DangerousMonster, Lethal {
void drinkBlood();
}
class VeryBadVampire implements Vampire {
public void menace() {}
public void destroy() {}
public void kill() {}
public void drinkBlood() {}
}
public class HorrorShow {
static void u(Monster b) { b.menace(); }
static void v(DangerousMonster d) {
d.menace();
d.destroy();
}
static void w(Lethal l) { l.kill(); }
public static void main(String[] args) {
DangerousMonster barney = new DragonZilla();
u(barney);
v(barney);
Vampire vlad = new VeryBadVampire();
u(vlad);
v(vlad);
w(vlad);
}
} ///:~

DangerousMonster 是Monster的直接扩展,它产生了一个新接口.DragonZilla中实现了这个接口.

在Vampire中使用的语法仅适用于接口继承.一般情况下,只可以将extends用于单一类,但是可以引用多个基类接口.只需要用逗号将接口名一一分割开即可.

5.1 组合接口时的名字冲突

在打算组合的不同接口中使用相同的方法名通常会造成代码可读性的混乱,请尽量避免这种情况.

6. 适配接口

接口的一种常见用法就是策略设计模式,此时你编写一个执行某些操作的方法,而该方法将接受一个同样是你指定的接口.你主要的声明就是 : 你可以用任何你想要的对象来调用我的方法,只要你的对象遵循我的接口.

7. 接口中的域

因为我们放入接口中的任何域都自动是static和final的,所以接口就成为一种很便捷的用来创建常量组的工具.

7.1 初始化接口中的域

在接口中定义的域不能为”空final”,但是可以被非常量表达式初始化,如 :

1
2
3
4
5
6
7
8
9
10
11
12
//: interfaces/RandVals.java
// Initializing interface fields with
// non-constant initializers.
import java.util.*;
public interface RandVals {
Random RAND = new Random(47);
int RANDOM_INT = RAND.nextInt(10);
long RANDOM_LONG = RAND.nextLong() * 10;
float RANDOM_FLOAT = RAND.nextLong() * 10;
double RANDOM_DOUBLE = RAND.nextDouble() * 10;
} ///:~

既然域是static的,它们就可以在类第一次被加载时初始化,这发生在任何域首次被访问时.这就给了一个简单的测试 : 以下的例子证明了接口中的域隐式地是static和final

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//: interfaces/TestRandVals.java
import static net.mindview.util.Print.*;
public class TestRandVals {
public static void main(String[] args) {
print(RandVals.RANDOM_INT);
print(RandVals.RANDOM_LONG);
print(RandVals.RANDOM_FLOAT);
print(RandVals.RANDOM_DOUBLE);
}
} /* Output:
8
-32032247016559954
-8.5939291E18
5.779976127815049
*///:~

当然,这些域不是接口的一部分,它们的值被存储在该接口的静态存储区域内.

8. 嵌套接口

接口可以嵌套在类或其他接口中.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
//: interfaces/nesting/NestingInterfaces.java
package interfaces.nesting;
class A {
interface B {
void f();
}
public class BImp implements B {
public void f() {}
}
private class BImp2 implements B {
public void f() {}
}
public interface C {
void f();
}
class CImp implements C {
public void f() {}
}
private class CImp2 implements C {
public void f() {}
}
private interface D {
void f();
}
private class DImp implements D {
public void f() {}
}
public class DImp2 implements D {
public void f() {}
}
public D getD() { return new DImp2(); }
private D dRef;
public void receiveD(D d) {
dRef = d;
dRef.f();
}
}
interface E {
interface G {
void f();
}
// Redundant "public":
public interface H {
void f();
}
void g();
// Cannot be private within an interface:
//! private interface I {}
}
public class NestingInterfaces {
public class BImp implements A.B {
public void f() {}
}
class CImp implements A.C {
public void f() {}
}
// Cannot implement a private interface except
// within that interface's defining class:
//! class DImp implements A.D {
//! public void f() {}
//! }
class EImp implements E {
public void g() {}
}
class EGImp implements E.G {
public void f() {}
}
class EImp2 implements E {
public void g() {}
class EG implements E.G {
public void f() {}
}
}
public static void main(String[] args) {
A a = new A();
// Can't access A.D:
//! A.D ad = a.getD();
// Doesn't return anything but A.D:
//! A.DImp2 di2 = a.getD();
// Cannot access a member of the interface:
//! a.getD().f();
// Only another A can do anything with getD():
A a2 = new A();
a2.receiveD(a.getD());
}
} ///:~

特别注意的是,当实现某个接口时,并不需要实现嵌套在其内部的任何接口.而且,private接口不能在定义它的类之外被实现.

9. 接口与工厂

接口是实现多重继承的途径,而生成遵循某个接口的对象的典型方式就是工厂方法设计模式.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//: interfaces/Factories.java
import static net.mindview.util.Print.*;
interface Service {
void method1();
void method2();
}
interface ServiceFactory {
Service getService();
}
class Implementation1 implements Service {
Implementation1() {} // Package access
public void method1() {print("Implementation1 method1");}
public void method2() {print("Implementation1 method2");}
}
class Implementation1Factory implements ServiceFactory {
public Service getService() {
return new Implementation1();
}
}
class Implementation2 implements Service {
Implementation2() {} // Package access
public void method1() {print("Implementation2 method1");}
public void method2() {print("Implementation2 method2");}
}
class Implementation2Factory implements ServiceFactory {
public Service getService() {
return new Implementation2();
}
}
public class Factories {
public static void serviceConsumer(ServiceFactory fact) {
Service s = fact.getService();
s.method1();
s.method2();
}
public static void main(String[] args) {
serviceConsumer(new Implementation1Factory());
// Implementations are completely interchangeable:
serviceConsumer(new Implementation2Factory());
}
} /* Output:
Implementation1 method1
Implementation1 method2
Implementation2 method1
Implementation2 method2
*///:~

如果不用工厂方法,代码就必须在某处指定将要创建的Service的确切类型,以便调用合适的构造器.

10. 总结

“确定接口是理想选择,因而应该总是选择接口而不是具体的类”.这其实是一种引诱.当然,对于创建类,几乎在任何时刻,都可以替代为创建一个接口和工厂.

适当的原则应该是优先选择类而不是接口,从类开始,如果接口的必须性变得非常明确,那么就进行重构.接口是一种重要的工具,但是它非常容易被滥用.

第8章 多态

发表于 2017-03-28 | 分类于 Java | 阅读次数

在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本特征.

1. 再论向上转型

对象既可以作为它自己本身的类型使用,也可以作为它的基类使用.而这种把对某个对象的引用视为对其基类的引用的做法被称为”向上转型” – 因为在继承书的画法中,基类是放置在上方的.

首先,既然几个例子都要演奏乐符 (Note) ,我们就应该在包中单独创建一个Note类.

1
2
3
4
5
6
7
//: polymorphism/music/Note.java
// Notes to play on musical instruments.
package polymorphism.music;
public enum Note {
MIDDLE_C, C_SHARP, B_FLAT; // Etc.
} ///:~

在这里,Wind是一种Instrument,因此可以从Instrument类继承.

1
2
3
4
5
6
7
8
9
10
//: polymorphism/music/Instrument.java
package polymorphism.music;
import static net.mindview.util.Print.*;
class Instrument {
public void play(Note n) {
print("Instrument.play()");
}
}
///:~
1
2
3
4
5
6
7
8
9
10
11
//: polymorphism/music/Wind.java
package polymorphism.music;
// Wind objects are instruments
// because they have the same interface:
public class Wind extends Instrument {
// Redefine interface method:
public void play(Note n) {
System.out.println("Wind.play() " + n);
}
} ///:~
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//: polymorphism/music/Music.java
// Inheritance & upcasting.
package polymorphism.music;
public class Music {
public static void tune(Instrument i) {
// ...
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind flute = new Wind();
tune(flute); // Upcasting
}
} /* Output:
Wind.play() MIDDLE_C
*///:~

Music.tune()方法接受一个Instrument引用,同时也接受任何导出自Instrument 的类.在main()方法中,当一个Wind引用传递到tune()方法时,就会出现这种情况,而不需要任何类型转换.因为Wind到Instrument继承而来,所以Instrument的接口必定存在于Wind中.从Wind向上转型到Instrument可能会缩小接口,但不会比Instrument的全部接口更窄.

1.1 忘记对象类型

如果我们不管导出类的存在,编写的代码只是与基类打交道,会不会更好呢?

2. 转机

2.1 方法调用绑定

将一个方法调用同一个方法主体关联起来被称作绑定.若在程序执行前进行绑定(如果有的话,由编译器和连接程序实现),叫做前期绑定.

后期绑定的含义就是在运行时根据对象的类型进行绑定.后期绑定也叫做动态绑定或运行时绑定.如果一种语言想实现后期绑定,就必须具有某种机制,以便在运行时能判断对象的类型,从而调用恰当的方法.也就说说,编译器一直不知道对象的类型,但是方法调用机制能找到正确的方法体,并加以调用.

Java中除了static方法和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定.

对某个方法声明final,它可以防止其他人覆盖该方法.但更重要的一点是告诉编译器不需要对其进行动态绑定.这样,编译器就可以为final方法调用生成更有效的代码.

2.2 产生正确的行为

发送消息给某个对象时,让该对象去判定应该做什么事.

几何形状
向上转型可以像下面这条语句那么简单:

Shape s = new Circle();

这里,创建了一个Circle对象,并把得到的引用立即赋值给Shape,因为通过继承.Circle就是一种Shape.

假设你调用一个基类方法(它已在导出类中被覆盖)

s.draw();

由于后期绑定(多态),所以调用的是Circle.draw()方法.

1
2
3
4
5
6
7
//: polymorphism/shape/Shape.java
package polymorphism.shape;
public class Shape {
public void draw() {}
public void erase() {}
} ///:~
1
2
3
4
5
6
7
8
//: polymorphism/shape/Circle.java
package polymorphism.shape;
import static net.mindview.util.Print.*;
public class Circle extends Shape {
public void draw() { print("Circle.draw()"); }
public void erase() { print("Circle.erase()"); }
} ///:~
1
2
3
4
5
6
7
8
//: polymorphism/shape/Square.java
package polymorphism.shape;
import static net.mindview.util.Print.*;
public class Square extends Shape {
public void draw() { print("Square.draw()"); }
public void erase() { print("Square.erase()"); }
} ///:~
1
2
3
4
5
6
7
8
//: polymorphism/shape/Triangle.java
package polymorphism.shape;
import static net.mindview.util.Print.*;
public class Triangle extends Shape {
public void draw() { print("Triangle.draw()"); }
public void erase() { print("Triangle.erase()"); }
} ///:~
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//: polymorphism/shape/RandomShapeGenerator.java
// A "factory" that randomly creates shapes.
package polymorphism.shape;
import java.util.*;
public class RandomShapeGenerator {
private Random rand = new Random(47);
public Shape next() {
switch(rand.nextInt(3)) {
default:
case 0: return new Circle();
case 1: return new Square();
case 2: return new Triangle();
}
}
} ///:~
```java
//: polymorphism/Shapes.java
// Polymorphism in Java.
import polymorphism.shape.*;
public class Shapes {
private static RandomShapeGenerator gen =
new RandomShapeGenerator();
public static void main(String[] args) {
Shape[] s = new Shape[9];
// Fill up the array with shapes:
for(int i = 0; i < s.length; i++)
s[i] = gen.next();
// Make polymorphic method calls:
for(Shape shp : s)
shp.draw();
}
} /* Output:
Triangle.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Circle.draw()
*///:~

Shape基类为自它哪里继承而来的所有导出类建立了一个公用接口,所有形状都可以描绘和擦除.导出类通过覆盖这些定义,来为每种特殊类型的几何形状提供单独的行为.

RandomShapeGenerator是一种”工厂”,在我们每次调用next()方法时,它可以随机选择Shape对象产生一个引用.向上转型是在return语句里产生的.每个return语句取得一个指向某个Cricle,Square或者Triangle的引用,并将其以Shape类型从next()方法中发送出去.所以无论我们在什么时候调用next()方法,都不会知道具体类型到底是什么,因为我们总是只能获得一个通用的Shape引用.

2.3 可扩展性

因为可以从通用的基类继承出新的数据类型,从而新添加一些功能.那些操纵基类接口的方法不需要任何改动就可以应用于新类.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//: polymorphism/music3/Music3.java
// An extensible program.
package polymorphism.music3;
import polymorphism.music.Note;
import static net.mindview.util.Print.*;
class Instrument {
void play(Note n) { print("Instrument.play() " + n); }
String what() { return "Instrument"; }
void adjust() { print("Adjusting Instrument"); }
}
class Wind extends Instrument {
void play(Note n) { print("Wind.play() " + n); }
String what() { return "Wind"; }
void adjust() { print("Adjusting Wind"); }
}
class Percussion extends Instrument {
void play(Note n) { print("Percussion.play() " + n); }
String what() { return "Percussion"; }
void adjust() { print("Adjusting Percussion"); }
}
class Stringed extends Instrument {
void play(Note n) { print("Stringed.play() " + n); }
String what() { return "Stringed"; }
void adjust() { print("Adjusting Stringed"); }
}
class Brass extends Wind {
void play(Note n) { print("Brass.play() " + n); }
void adjust() { print("Adjusting Brass"); }
}
class Woodwind extends Wind {
void play(Note n) { print("Woodwind.play() " + n); }
String what() { return "Woodwind"; }
}
public class Music3 {
// Doesn't care about type, so new types
// added to the system still work right:
public static void tune(Instrument i) {
// ...
i.play(Note.MIDDLE_C);
}
public static void tuneAll(Instrument[] e) {
for(Instrument i : e)
tune(i);
}
public static void main(String[] args) {
// Upcasting during addition to the array:
Instrument[] orchestra = {
new Wind(),
new Percussion(),
new Stringed(),
new Brass(),
new Woodwind()
};
tuneAll(orchestra);
}
} /* Output:
Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
*///:~

新添加的方法what()返回一个带有类型描述的string引用;另一个新添加的方法adjust()则提供每种乐其的调用方法.

在main()中,当我们将某种引用置入orchestra 数组中,就会自动向上转型到Instrument.

多态是一项让程序员”将改变的事物与未知的事物分离开来”的重要技术.

2.4 缺陷 : “覆盖”私有方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//: polymorphism/PrivateOverride.java
// Trying to override a private method.
package polymorphism;
import static net.mindview.util.Print.*;
public class PrivateOverride {
private void f() { print("private f()"); }
public static void main(String[] args) {
PrivateOverride po = new Derived();
po.f();
}
}
class Derived extends PrivateOverride {
public void f() { print("public f()"); }
} /* Output:
private f()
*///:~

我们所期待的是输出public f(),但由于private方法被自动认为是final方法,而且对导出类是屏蔽的.因此,在这种情况下,Derived类中的f()方法就是一个全新的方法;既然基类的f()方法在子类中不可见,因此也不能被重载.

确切地说,在导出类中,对于基类的private方法,最好采用不同的名字.

2.5 缺陷 : 域与静态方法

如果你直接访问某个域,这个访问就将会在编译器进行解析.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//: polymorphism/FieldAccess.java
// Direct field access is determined at compile time.
class Super {
public int field = 0;
public int getField() { return field; }
}
class Sub extends Super {
public int field = 1;
public int getField() { return field; }
public int getSuperField() { return super.field; }
}
public class FieldAccess {
public static void main(String[] args) {
Super sup = new Sub(); // Upcast
System.out.println("sup.field = " + sup.field +
", sup.getField() = " + sup.getField());
Sub sub = new Sub();
System.out.println("sub.field = " +
sub.field + ", sub.getField() = " +
sub.getField() +
", sub.getSuperField() = " +
sub.getSuperField());
}
} /* Output:
sup.field = 0, sup.getField() = 1
sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0
*///:~

当Sub对象转型为Super引用时,任何域访问都将由编译器解析,因此不是多态.在本例中,为Super.field和Sub.field分配了不同的存储空间.这样,Sub实际上包含两个称为field的域:它自己的和它从Super处得到的.然而,在引用Sub中的field时所产生的默认域并非Super版本的field域.因此,为了得到Super.field,必须显式地指明super.field.

如果某个方法是静态的,它的行为就不具有多态性,静态方法是与类,而并非与单个的对象相关联.

3. 构造器和多态

构造器并不具有多态性(它们实际上是static方法,只不过static声明是隐式的).

3.1 构造器的调用顺序

基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上链接,已使每个基类的构造器都能得到调用.因为构造器具有一项特殊的任务:检查对象是否被正确地构造.导出类只能访问它自己的成员,不能访问基类中的成员(基类的成员通常是private类型).只有基类的构造器才具有恰当的知识和权限来对自己的元素进行初始化.因此,必须令所有构造器都得到调用.这正是编译器为什么要强制每个导出类部分都必须调用构造器的原因.在导出类的构造器主体中,如果没有明确指定调用某个基类构造器,它就会默默地调用默认构造器.如果没有明确构造器,编译器就会报错!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//: polymorphism/Sandwich.java
// Order of constructor calls.
package polymorphism;
import static net.mindview.util.Print.*;
class Meal {
Meal() { print("Meal()"); }
}
class Bread {
Bread() { print("Bread()"); }
}
class Cheese {
Cheese() { print("Cheese()"); }
}
class Lettuce {
Lettuce() { print("Lettuce()"); }
}
class Lunch extends Meal {
Lunch() { print("Lunch()"); }
}
class PortableLunch extends Lunch {
PortableLunch() { print("PortableLunch()");}
}
public class Sandwich extends PortableLunch {
private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();
public Sandwich() { print("Sandwich()"); }
public static void main(String[] args) {
new Sandwich();
}
} /* Output:
Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()
*///:~

用其他类创建了一个复杂的类,并且每个类都有一个声明它自己的构造器,其中最重要的类时Sandwich,它反映了三层继承(若将自Object的隐含继承地也算在内,就是四层)以及三个成员对象,当在main()里创建一个Sandwich对象后,就可以看到输出的结果.

  • 调用基类构造器.这个步骤还不断地反复递归下去,首先是构造这种层次结构的根,然后是下一层导出类,等等,直到最底层的导出类.
  • 按声明顺序调用成员的初始化方法.
  • 调用导出类构造器的主体.

3.2 继承和清理

通过组合和继承方法来创建新类时,永远不必担心对象的清理问题.子对象通过都会留给垃圾回收器进行处理.如果确实遇到清理的问题,那么必须用心为新类创建dispose()方法.并且由于继承的缘故,如果我们有其他作为垃圾回收器一部分的特殊清理功能,就必须在导出类中覆盖dispose()方法.当覆盖被继承的dispose()方法适,务必记住调用基类版本dispose()方法;否则,基类的清理动作就不会发生.

3.3 构造器内部的多态方法的行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//: polymorphism/PolyConstructors.java
// Constructors and polymorphism
// don't produce what you might expect.
import static net.mindview.util.Print.*;
class Glyph {
void draw() { print("Glyph.draw()"); }
Glyph() {
print("Glyph() before draw()");
draw();
print("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph {
private int radius = 1;
RoundGlyph(int r) {
radius = r;
print("RoundGlyph.RoundGlyph(), radius = " + radius);
}
void draw() {
print("RoundGlyph.draw(), radius = " + radius);
}
}
public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
}
} /* Output:
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
*///:~
  • 在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零.
  • 如前所述那有调用基类构造器.此时,调用被覆盖后的draw()方法(要在调用RoundGlyph构造器之前调用),由于步骤1的缘故,我们此时会发现radius的值为0.
  • 按照声明的顺序调用成员的初始化方法.
  • 调用导出类的构造器主体.

因此,在编写构造器时有一条有效的准则:用尽可能简单的方法使对象进入正常的状态,如果也可以的情况,避免调用其它方法.

4. 协变返回类型

它表示在导出类中的被覆盖方法可以返回基类方法的返回类型的某种导出类型:

5. 用继承进行设计

用继承表达行为间的差异,并用子弹表达状态上的变化.

5.1 纯继承与扩展

纯继承与扩展

这种被称作为纯粹的”is-a”关系,因为一个类的接口已经确定了它应该是什么,继承可以确保所有的导出类具有基类的接口,且绝对不会少.

5.2 向下转型与运行时类型识别

由于向上转型(在继承层中向上移动)会丢失具体的类型信息.所以,我们通过向下转型(也就是在继承层次中向下移动),然而,我们知道向上转型是安全的,因为基类不会具有大于导出类的接口.因此,我们通过基类接口发送的信息保证都能被接受.但是对于向下转型,例如:我们无法知道一个”几何形状”它确实是一个”圆”还是”别的三角形”.

在java中,所有的转型都会被检查,以便确保它的确是我们希望的那种类型.如果不是.则返回ClassCastException(类转型异常)

6. 总结

多态意味着”不同的形式”,在面向对象的程序设计中,我们持有从基类继承而来的相同接口,以及使用该接口的不同形式:不同版本的动态绑定方法.

第7章 复用类

发表于 2017-03-28 | 分类于 Java | 阅读次数

复用代码是Java众多因人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对之加以改变是不够的.它还必须能够做更多的事情.

1. 组合语法

假设你需要某个对象,它要具有多个String对象/几个基本类型数据,以及另一个类的对象.对于非基本类型的对象,必须将其引用置于新的类中,但可以直接定义基本类型数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//: reusing/SprinklerSystem.java
// Composition for code reuse.
class WaterSource {
private String s;
WaterSource() {
System.out.println("WaterSource()");
s = "Constructed";
}
public String toString() { return s; }
}
public class SprinklerSystem {
private String valve1, valve2, valve3, valve4;
private WaterSource source = new WaterSource();
private int i;
private float f;
public String toString() {
return
"valve1 = " + valve1 + " " +
"valve2 = " + valve2 + " " +
"valve3 = " + valve3 + " " +
"valve4 = " + valve4 + "\n" +
"i = " + i + " " + "f = " + f + " " +
"source = " + source;
}
public static void main(String[] args) {
SprinklerSystem sprinklers = new SprinklerSystem();
System.out.println(sprinklers);
}
} /* Output:
WaterSource()
valve1 = null valve2 = null valve3 = null valve4 = null
i = 0 f = 0.0 source = Constructed
*///:~

toString():每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而你却只有一个对象时,该方法便会被调用.所以在SprinklerSystem.toString()的表达式中:
"source = " + source;

编译器会得知你将要一个String对象同WaterSource相加.由于只能将一个String对象和另一个String对象相加,因此编译器会告诉你:我将调用toString(),把source转换为一个String!所以,每当想要所创建对象的类具备这样的行为时,仅需要编写一个toString()方法即可.

编译器并不是简单地为每一个引用都创建默认对象.如果想初始化这些引用,可以在代码中的下列位置进行:

  • 在定义对象的地方.这意味着它们总是能够在构造器被调用之前被初始化.
  • 在类的构造器中.
  • 就在正要使用这些对象之前,这种方式称为惰性初始化.在生成对象不值得及不必每次都生成对象的情况下.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//: reusing/Bath.java
// Constructor initialization with composition.
import static net.mindview.util.Print.*;
class Soap {
private String s;
Soap() {
print("Soap()");
s = "Constructed";
}
public String toString() { return s; }
}
public class Bath {
private String // Initializing at point of definition:
s1 = "Happy",
s2 = "Happy",
s3, s4;
private Soap castille;
private int i;
private float toy;
public Bath() {
print("Inside Bath()");
s3 = "Joy";
toy = 3.14f;
castille = new Soap();
}
// Instance initialization:
{ i = 47; }
public String toString() {
if(s4 == null) // Delayed initialization:
s4 = "Joy";
return
"s1 = " + s1 + "\n" +
"s2 = " + s2 + "\n" +
"s3 = " + s3 + "\n" +
"s4 = " + s4 + "\n" +
"i = " + i + "\n" +
"toy = " + toy + "\n" +
"castille = " + castille;
}
public static void main(String[] args) {
Bath b = new Bath();
print(b);
}
} /* Output:
Inside Bath()
Soap()
s1 = Happy
s2 = Happy
s3 = Joy
s4 = Joy
i = 47
toy = 3.14
castille = Constructed
*///:~

2. 继承语法

当创建一个类时,总是在继承,因此,除非已明确指出要从其它类中继承,否则就是在隐式地从Java的标准根类Object进行继承.

在继承的过程中,需要声明”新类与旧类相似”.需要用extends实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//: reusing/Detergent.java
// Inheritance syntax & properties.
import static net.mindview.util.Print.*;
class Cleanser {
private String s = "Cleanser";
public void append(String a) { s += a; }
public void dilute() { append(" dilute()"); }
public void apply() { append(" apply()"); }
public void scrub() { append(" scrub()"); }
public String toString() { return s; }
public static void main(String[] args) {
Cleanser x = new Cleanser();
x.dilute(); x.apply(); x.scrub();
print(x);
}
}
public class Detergent extends Cleanser {
// Change a method:
public void scrub() {
append(" Detergent.scrub()");
super.scrub(); // Call base-class version
}
// Add methods to the interface:
public void foam() { append(" foam()"); }
// Test the new class:
public static void main(String[] args) {
Detergent x = new Detergent();
x.dilute();
x.apply();
x.scrub();
x.foam();
print(x);
print("Testing base class:");
Cleanser.main(args);
}
} /* Output:
Cleanser dilute() apply() Detergent.scrub() scrub() foam()
Testing base class:
Cleanser dilute() apply() scrub()
*///:~

Java中用super关键字表示超类的意思,当前类就是从超类继承来的.为此,表达式super.scrub()将调用基类版本的scrub()方法.

2.1 初始化基类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//: reusing/Cartoon.java
// Constructor calls during inheritance.
import static net.mindview.util.Print.*;
class Art {
Art() { print("Art constructor"); }
}
class Drawing extends Art {
Drawing() { print("Drawing constructor"); }
}
public class Cartoon extends Drawing {
public Cartoon() { print("Cartoon constructor"); }
public static void main(String[] args) {
Cartoon x = new Cartoon();
}
} /* Output:
Art constructor
Drawing constructor
Cartoon constructor
*///:~

在构建过程是从基类”向外”扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化.即使你不为Catroon()创建构造器,编译器也会为你合成一个默认构造器,该构造器将调用基类的构造器.

2.2 带参数的构造器

如果没有默认的基类构造器,或者想调用一个带参数的基类构造器,就必须要用关键字super显式地编写调用基类构造器的语句,并且配以适当的参数列表.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//: reusing/Chess.java
// Inheritance, constructors and arguments.
import static net.mindview.util.Print.*;
class Game {
Game(int i) {
print("Game constructor");
}
}
class BoardGame extends Game {
BoardGame(int i) {
super(i);
print("BoardGame constructor");
}
}
public class Chess extends BoardGame {
Chess() {
super(11);
print("Chess constructor");
}
public static void main(String[] args) {
Chess x = new Chess();
}
} /* Output:
Game constructor
BoardGame constructor
Chess constructor
*///:~

如果不在BoardGame()中调用基类构造器,编译器会无法找到符合Game()形式构造器.而且,调用基类构造器必须是你在导出类构造器中要做的第一件事.
Implicit super constructor BoardGame() is undefined. Must explicitly invoke another constructor

3. 代理

因为我们将一个成员对象置于所有构造的类中(就像组合),但与此同时我们在新类中暴露了该成员对象的所有方法(就像继承).例如,太空船需要一个控制模块:

1
2
3
4
5
6
7
8
9
10
11
//: reusing/SpaceShipControls.java
public class SpaceShipControls {
void up(int velocity) {}
void down(int velocity) {}
void left(int velocity) {}
void right(int velocity) {}
void forward(int velocity) {}
void back(int velocity) {}
void turboBoost() {}
} ///:~

构造太空船的一种方式是使用继承:

1
2
3
4
5
6
7
8
9
10
11
//: reusing/SpaceShip.java
public class SpaceShip extends SpaceShipControls {
private String name;
public SpaceShip(String name) { this.name = name; }
public String toString() { return name; }
public static void main(String[] args) {
SpaceShip protector = new SpaceShip("NSEA Protector");
protector.forward(100);
}
} ///:~

然而,SpaceShip并非真正的SpaceShipControls类型,即便你可以”告诉”SpaceShip向前运动forward().更精确地讲,Spaceship包含SpaceShipControls,与此同时,SpaceShipControls的所有方法在SpaceShip中都暴露了出来.代理解决了此难题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//: reusing/SpaceShipDelegation.java
public class SpaceShipDelegation {
private String name;
private SpaceShipControls controls =
new SpaceShipControls();
public SpaceShipDelegation(String name) {
this.name = name;
}
// Delegated methods:
public void back(int velocity) {
controls.back(velocity);
}
public void down(int velocity) {
controls.down(velocity);
}
public void forward(int velocity) {
controls.forward(velocity);
}
public void left(int velocity) {
controls.left(velocity);
}
public void right(int velocity) {
controls.right(velocity);
}
public void turboBoost() {
controls.turboBoost();
}
public void up(int velocity) {
controls.up(velocity);
}
public static void main(String[] args) {
SpaceShipDelegation protector =
new SpaceShipDelegation("NSEA Protector");
protector.forward(100);
}
} ///:~

我们使用代理时可以拥有更多的控制力,因为我们可以选择只提供在成员对象中的方法的某个子集.

4. 结合使用组合和继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
//: reusing/PlaceSetting.java
// Combining composition & inheritance.
import static net.mindview.util.Print.*;
class Plate {
Plate(int i) {
print("Plate constructor");
}
}
class DinnerPlate extends Plate {
DinnerPlate(int i) {
super(i);
print("DinnerPlate constructor");
}
}
class Utensil {
Utensil(int i) {
print("Utensil constructor");
}
}
class Spoon extends Utensil {
Spoon(int i) {
super(i);
print("Spoon constructor");
}
}
class Fork extends Utensil {
Fork(int i) {
super(i);
print("Fork constructor");
}
}
class Knife extends Utensil {
Knife(int i) {
super(i);
print("Knife constructor");
}
}
// A cultural way of doing something:
class Custom {
Custom(int i) {
print("Custom constructor");
}
}
public class PlaceSetting extends Custom {
private Spoon sp;
private Fork frk;
private Knife kn;
private DinnerPlate pl;
public PlaceSetting(int i) {
super(i + 1);
sp = new Spoon(i + 2);
frk = new Fork(i + 3);
kn = new Knife(i + 4);
pl = new DinnerPlate(i + 5);
print("PlaceSetting constructor");
}
public static void main(String[] args) {
PlaceSetting x = new PlaceSetting(9);
}
} /* Output:
Custom constructor
Utensil constructor
Spoon constructor
Utensil constructor
Fork constructor
Utensil constructor
Knife constructor
Plate constructor
DinnerPlate constructor
PlaceSetting constructor
*///:~

4.1 确保正确清理

如果你想要某个类清理一些东西,就必须显式地编写一个特殊方法来做这件事,并要确认客户程序员知晓他们必须要调用这一方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//: reusing/CADSystem.java
// Ensuring proper cleanup.
package reusing;
import static net.mindview.util.Print.*;
class Shape {
Shape(int i) { print("Shape constructor"); }
void dispose() { print("Shape dispose"); }
}
class Circle extends Shape {
Circle(int i) {
super(i);
print("Drawing Circle");
}
void dispose() {
print("Erasing Circle");
super.dispose();
}
}
class Triangle extends Shape {
Triangle(int i) {
super(i);
print("Drawing Triangle");
}
void dispose() {
print("Erasing Triangle");
super.dispose();
}
}
class Line extends Shape {
private int start, end;
Line(int start, int end) {
super(start);
this.start = start;
this.end = end;
print("Drawing Line: " + start + ", " + end);
}
void dispose() {
print("Erasing Line: " + start + ", " + end);
super.dispose();
}
}
public class CADSystem extends Shape {
private Circle c;
private Triangle t;
private Line[] lines = new Line[3];
public CADSystem(int i) {
super(i + 1);
for(int j = 0; j < lines.length; j++)
lines[j] = new Line(j, j*j);
c = new Circle(1);
t = new Triangle(1);
print("Combined constructor");
}
public void dispose() {
print("CADSystem.dispose()");
// The order of cleanup is the reverse
// of the order of initialization:
t.dispose();
c.dispose();
for(int i = lines.length - 1; i >= 0; i--)
lines[i].dispose();
super.dispose();
}
public static void main(String[] args) {
CADSystem x = new CADSystem(47);
try {
// Code and exception handling...
} finally {
x.dispose();
}
}
} /* Output:
Shape constructor
Shape constructor
Drawing Line: 0, 0
Shape constructor
Drawing Line: 1, 1
Shape constructor
Drawing Line: 2, 4
Shape constructor
Drawing Circle
Shape constructor
Drawing Triangle
Combined constructor
CADSystem.dispose()
Erasing Triangle
Shape dispose
Erasing Circle
Shape dispose
Erasing Line: 2, 4
Shape dispose
Erasing Line: 1, 1
Shape dispose
Erasing Line: 0, 0
Shape dispose
Shape dispose
*///:~

关键字try表示,下面的块是保护区,其中一项特殊处理就是无论try块是怎样退出的,保护区后面的finally子句中的代码总是要被执行!

4.2 名称屏蔽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//: reusing/Hide.java
// Overloading a base-class method name in a derived
// class does not hide the base-class versions.
import static net.mindview.util.Print.*;
class Homer {
char doh(char c) {
print("doh(char)");
return 'd';
}
float doh(float f) {
print("doh(float)");
return 1.0f;
}
}
class Milhouse {}
class Bart extends Homer {
void doh(Milhouse m) {
print("doh(Milhouse)");
}
}
public class Hide {
public static void main(String[] args) {
Bart b = new Bart();
b.doh(1);
b.doh('x');
b.doh(1.0f);
b.doh(new Milhouse());
}
} /* Output:
doh(float)
doh(char)
doh(float)
doh(Milhouse)
*///:~

Java SE5新增加了@Override注解,当想要覆盖某个方法适,可以选择添加这个注解,在你不留心重载并非覆写了该方法时,编辑器会生成一条错误信息.

5. 在组合和继承之间选择

组合技术通常用于想在新类中使用现有类的功能而非它的接口这种形式.
在继承的时候,使用某个现有类,并开发一个它的特殊版本

6. protected 关键字

在实际项目中,经常会想要将某些事物尽可能对这个世界隐藏起来,但仍然允许导出类的成员访问它们.

关键字protected指明白”就类用户而言,这是private的,但对于任何继承于此类的导出类或其他任何位于同一个包内的类来说,它却是可以访问的.”(protected提供了包内访问权限)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//: reusing/Orc.java
// The protected keyword.
import static net.mindview.util.Print.*;
class Villain {
private String name;
protected void set(String nm) { name = nm; }
public Villain(String name) { this.name = name; }
public String toString() {
return "I'm a Villain and my name is " + name;
}
}
public class Orc extends Villain {
private int orcNumber;
public Orc(String name, int orcNumber) {
super(name);
this.orcNumber = orcNumber;
}
public void change(String name, int orcNumber) {
set(name); // Available because it's protected
this.orcNumber = orcNumber;
}
public String toString() {
return "Orc " + orcNumber + ": " + super.toString();
}
public static void main(String[] args) {
Orc orc = new Orc("Limburger", 12);
print(orc);
orc.change("Bob", 19);
print(orc);
}
} /* Output:
Orc 12: I'm a Villain and my name is Limburger
Orc 19: I'm a Villain and my name is Bob
*///:~

7. 向上转型

能够向基类发送的所有信息同样也可以向导出类发送.如果Instrument类具有一个play()方法,那么wind乐器也同样具备.我们可以准确地说wind对象也是一种类型的instrument.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//: reusing/Wind.java
// Inheritance & upcasting.
class Instrument {
public void play() {}
static void tune(Instrument i) {
// ...
i.play();
}
}
// Wind objects are instruments
// because they have the same interface:
public class Wind extends Instrument {
public static void main(String[] args) {
Wind flute = new Wind();
Instrument.tune(flute); // Upcasting
}
} ///:~

这种将Wind引用转换为Instrument引用的动作,我们称之为向上转型.

7.1 为什么称之为向上转型

由导出类转换为基类,在继承图上是向上移动的,因此一般称为向上转型.由于向上转型是从一个较专业类型向通用类型转换,所以总是很安全.在向上转型的过程中,类接口中唯一可能发生的丢失方法,而不是获取它们.

7.2 再论组合与继承

自己是否需要从新类向基类进行向上转型.如果必须向上转型,则继承是必要的;如果不需要,则应当好好考虑自己是否需要用到继承!

8. final 关键字

这是无法改变的.

8.1 final 数据

在Java中,这类常量必须是基本数据类型,并且以关键字final表示.在对这个常量进行定义的时候,必须对其进行赋值.

一个既是static又是final的域只占据一段不能改变的存储空间.

既是static又是final的域将用大写表示,并使用下划线分隔各个单词

定义为static则强调只有一份,定义为final则说明它是常量.并且static在装载的时候已经被初始化,并不是每次创建新对象时才初始化.

  • 空白final
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//: reusing/BlankFinal.java
// "Blank" final fields.
class Poppet {
private int i;
Poppet(int ii) { i = ii; }
}
public class BlankFinal {
private final int i = 0; // Initialized final
private final int j; // Blank final
private final Poppet p; // Blank final reference
// Blank finals MUST be initialized in the constructor:
public BlankFinal() {
j = 1; // Initialize blank final
p = new Poppet(1); // Initialize blank final reference
}
public BlankFinal(int x) {
j = x; // Initialize blank final
p = new Poppet(x); // Initialize blank final reference
}
public static void main(String[] args) {
new BlankFinal();
new BlankFinal(47);
}
} ///:~

必须在域的定义处或者每个构造器中用表达式对final进行赋值,这正是final域在使用前总是被初始化的原因.

  • final参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//: reusing/FinalArguments.java
// Using "final" with method arguments.
class Gizmo {
public void spin() {}
}
public class FinalArguments {
void with(final Gizmo g) {
//! g = new Gizmo(); // Illegal -- g is final
}
void without(Gizmo g) {
g = new Gizmo(); // OK -- g not final
g.spin();
}
// void f(final int i) { i++; } // Can't change
// You can only read from a final primitive:
int g(final int i) { return i + 1; }
public static void main(String[] args) {
FinalArguments bf = new FinalArguments();
bf.without(null);
bf.with(null);
}
} ///:~

方法f()和g()展示了当前类型的参数指明为final时所出现的结果:你可以读参数,但却无法修改参数.

8.2 final方法

使用final方法的原因有两个:

  • 把方法锁定,以防任何继承类修改它的含义.
  • 效率.

final和private关键字
类中所有的private方法都隐式地指定为final.由于无法取用private方法,所以也就无法覆盖它.可以对private方法添加final修饰词,但这不能给方法增加任何额外的意义.

1
private final void v(){}

8.3 final类

当将某个类的整体定义为final,就表明了你不打算继承该类,而且也不允许别人这样做.final是禁止继承的,所以final类中所有的方法都隐式指定为final的.

8.4 有关final的忠告

如果将一个方法制定为final,可能会妨碍其他程序员在项目中通过继承来复用你的类,而这只是因为你没有想到它会以这种方式被运用.

9. 初始化及类的加载

加载发生于创建类的第一个对象之时,但是当访问static域或static方法时,也会发生加载.构造器也是static方法.

9.1 继承与初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//: reusing/Beetle.java
// The full process of initialization.
import static net.mindview.util.Print.*;
class Insect {
private int i = 9;
protected int j;
Insect() {
print("i = " + i + ", j = " + j);
j = 39;
}
private static int x1 =
printInit("static Insect.x1 initialized");
static int printInit(String s) {
print(s);
return 47;
}
}
public class Beetle extends Insect {
private int k = printInit("Beetle.k initialized");
public Beetle() {
print("k = " + k);
print("j = " + j);
}
private static int x2 =
printInit("static Beetle.x2 initialized");
public static void main(String[] args) {
print("Beetle constructor");
Beetle b = new Beetle();
}
} /* Output:
static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
i = 9, j = 0
Beetle.k initialized
k = 47
j = 39
*///:~

在Beetle上运行java时,所发生的第一件事情就是试图访问Bettle.main()(一个static方法),于是加载器开始启动并找出Bettle类的编译代码.在对它进行加载的过程中,编译器注意到它由一个基类(这是由关键字extends得知),于是它继续进行加载.

10. 总结

继承和组合都能从现有的类型生成新的类型,组合一般是将现有的类型作为新类型底层实现的一部分来加以复用,而继承复用的是接口.

在使用继承时,由于导出类具有基类接口,因此它可以向上转型为基类.

1234…13
王裕杰

王裕杰

以中有足乐者,不知口体之奉不若人也.

130 日志
13 分类
24 标签
GitHub 微博 豆瓣 知乎
© 2016 - 2017 王裕杰
由 Hexo 强力驱动
主题 - NexT.Muse