Head First Java 三 四 五

本文深入探讨Java中的关键概念,包括方法的参数与实参、返回值传递方式、封装原则、成员变量初始化、变量比较机制、增强for循环、ArrayList使用技巧、访问控制规则、final关键字用途、方法重写与重载、抽象类与方法、多态应用、接口设计及super关键字的应用。
摘要由CSDN通过智能技术生成
第三章 object have state and behavior

术语规定

A method uses parameters. A caller passers arguments.

这么规定是有原因的,也是为了区分parameters 和 arguments的不同,即传值调用是复制一份参数传出去。

java中,全部都是传值调用,即复制一份参数,传给函数。

reference引用也是传值的,传的是遥控器的bits。

注意:函数的返回也是传值的。

一个类中方法的调用过程:
1、d.bark(3) 调用对象d下的方法bark(),并传argument,值为3;
2、表示3这个值的二进制码,一堆bits,被传递给bark()方法。
3、bark()方法的parameter,numOfBarks,即一个int类型的杯子,收到这堆bits后,存入这个杯子。
4、bark()方法,就可以使用这个叫做numOfBarks的杯子了, 当做本地变量使用。

方法的返回值

方法声明的返回值,和实际返回的值必须匹配,不一定非要一样,但要 匹配,意思是说对于基本类型,比如声明的int,返回值为byte也行,不要出现溢出就行。但引用不行,必须一样。

注意:返回值也是传值的,返回的是bits,扔到杯子里,看下面。

封装

每一个instance variable,就是类的成员变量,都最好设置为private,并用getters 和 setters封装起来。为了安全和保护。

setters有什么用?
setters能够检测设置的值是否合法,比如桌子的长度都该是正数等。

有时候setters下并没检测值是否合法啊?
setters主要是提供了一个检测设置值是否合法的位置,现在不去检测这个值是否合法,是否有界,不代表将来不会检测,有个这么位置也方便以后更改。

成员变量的初始化

成员变量如果没有显示初始化,他们都会有一个默认值。

基本数字类型比如short  int byte 都是0, boolean 是 false,references 是 null,浮点数是0.0。

注意:成员变量有默认值,本地变量没有初始值,不初始化使用就报错!
本地变量是声明在方法中的变量。
注意:方法的parameters,也属于本地变量。

变量比较

==运算符,只关心bits是不是一样,即如果是基本类型相比较,那就是看他们的bits是不是完全相同,也就是值一不一样。

对于引用reference,两个引用求==,是求他们两个是不是指向同一个内存对象,也就是两个杯子里面的遥控器一不一样,遥控器一样,即指向同一个对象。

有些引用有 equal()方法,可以比较连个引用内容是否相同,即指向的object内,存的值一步一样。比如:String 就有 equal()方法,两个String可以通过equal()方法比较。

增强型for 

for (String name : nameArray) { }
就是以name 为迭代器,迭代nameArray中的所有元素。: 表示 in 的意思。
这里nameArray的声明为,比如:String[] nameArray = {"Fred", "Tom" , "Dick"};
迭代器name的类型,必须与后面数组,或者集合的类型匹配。

第五章 写一个实际程序

本章要求写一个小游戏,主要是写程序的步骤,非常好。自己看书跟走一遍效果会很好。

写程序主要分三个过程, prep code    ->    test code    ->   real code

1、首先,设计所需要的类,里面要什么方法,和有哪些成员变量。
2、根据要求写出方法的prep code,即伪码,伪码表达出意思即可。
3、根据伪码和这个程序要完成的工作,写出test code。
注意:非常关键的思想!test code不是真的要立即进行测试,很多时候根本测不成,只是写出来帮助你思考,看你的程序要完成什么功能。写好测试程序后,再写真正代码,然后再根据需要补充测试代码,以测试代码来刺激真实代码的迭代,逐步完善程序。
先写测试程序还一个原因就是,人都比较懒,如果开始不写测试程序,写好真实程序后,还想添加更多更好玩的功能,就不情愿写测试程序了,所以,还是先写为好。
4、根据伪码,和测试代码,完成真实代码。

字符串 转 int

Integer.parseInt("3") ,和C的atoi类似。

ArrayList

在java.util.包下,使用的时候要包含java.util.ArrayList

有以下方法:
add(Object elem)

remove(int index) remove(Object elem)

contains(Object elem) 看ArrayList中是否包含某个对象元素

isEmpty()

indexOf(Object elem) 查找

size() 返回大小

get(int index) 根据序号返回元素。
等等等等

ArrayList<Egg> myList = new ArrayList<Egg>(); //最后括号可以有大小,但用处不大
注意:ArrayList是必须要有类型的,且添加到ArrayList的元素必须和它声明的一致。

注意:数组的[]这个运算符,也就只有数组在使用。

注意:ArrayList不能直接放入基本类型元素。必须声明为基本类型的对象形式。如:
          ArrayList<Integer> aa = new ArrayList<Integer>();

注意:java的 && || 和C一样,也能短路求值。对于布尔类型,& 和 &&,| 和||,即逻辑运算和位运算结果一样,并且位运算没有短路求值。

访问控制

不能被继承的类:
1、非public的class,只能被同一个包的类继承,别的包不能继承它。
2、被打了关键字final标签的类,别的类不能继承它。
3、一个class,他只有被声明为private的构造函数,那这个class不能被继承。

声明为final的用途

一个类被声明为final,是为了保证它不会被继承,其内部的方法也不会被覆盖。即,如果我要求一个类必须按照我想的方式工作,不允许出现任何修改,那我就声明为final。

如果一个方法被声明为final,与类被声明为final的目的是一样的。只是说,方法被声明为final,那只有这个方法不会被覆盖,如果一个class被声明为final,那整个类内的所有方法都不能被覆盖。

override重写,覆盖

注意:
1、如果要重写一个方法,就是要同意一个协议,即父类函数声明的形式的协议。
子类重写父类方法,parameters类型 必须相同,返回值类型 必须匹配

必须匹配的意思就是说,重写的方法的返回值类型,可以是子类。比如我一个父类方法返回值为Animal,那我一个子类继承该父类,然后重写这个方法,返回值写为Tiger是可以的。

2、重写一个方法,它的访问权限不能被降低。比如父类该方法权限为public,那子类必须也为public,不能降低,比如子类重写的方法,访问权限为private,就是不行的。

注意:重写overwrite时候,两个方法的访问权限不一样,无论扩大或缩小都行。没影响。

重载 overload

注意:重载,仅参数顺序不同也是可以的。比如 public Duck(int size, String name) 和 public Duck(String name , int size) 是可以构成重载的。overload。当然两个参数类型一样时候怎么换顺序都没用了。
下面我重复了下这个注意。

虚类

当一个类太抽象了,以至于无法进行实例化,即实例化后无法定义这个对象的行为。我们需要一种方法去防止这种极为抽象类的实例化,这方法就是虚类。

比如下面例子,Canine犬科。狗、狼实例化后都由实际行为,但犬科是什么无法说明。所以定义为虚类, 虚类声明方式如下:
为了保证Canine不会被实例化,即不会出现 Canine aCanine = new Canine(); 会报错的。

注意:虚类也是有构造函数的,他如果有成员变量的话也是要在自己的构造函数中初始化的。虽然没有办法通过 new AClass();的方法来调用这个构造函数。它的调用是在其子类的构造函数调用时,被调用的。

注意:虚类是可以继承于一个非虚类的!Object类就是个非虚类,所有类都继承于它。

以下这张图又是个典型例子,各种动物的行为都好定义,在脑海里都有具体的形象出现,但是单纯的 Animal 就不知道是什么了,所以是虚类。

虚方法

虚方法与虚类 类似,即必须被重写(overridden)才能使用,单纯的虚方法不能被用。
声明方法如下,即没有函数体,直接以分号结尾:
注意:如果声明了一个虚方法,那这个 类必须是虚类!!
          但是如果是个虚类,它里面的方法可以是虚方法,也可以是普通的方法,可混合存在。

虚方法的意义是什么?
答:虚方法虽然没有函数体,但是就像上面讲的 overridden,是一种契约。约束了后面各个子类中的这个方法必须这么写。主要目的还是在于多态。我们想让一个父类的引用,根据多态,去调用实际子类的函数,那这个函数首先必须要在这个父类中也有才行,不然调用不成。猫继承于Animal类,Animal下有个eat的方法,但是没有 爬树的方法,那我用Animal的引用 Animal aAnimal; 是无法写aAnimal.climbTree() 这样的代码的,必须写父类中有的函数,aAnimal.eat() 这个才能用到多态。
在下面Objcet部分,对这个问题还有解释。那个为什么不把所有返回值都写为Object类型的问题。

注意:父类如果有虚方法(即父类必须为虚类),那第一个具体类(即正常的非虚类),必须把父类中的所有虚方法全部实现!!如果实现不了,那自己也得为虚类。其实还是上面那个注意,即有虚方法的类,必须为虚类。

一个多态示例

编写一个自己的List,为了让这个List能够通用写,装下任意一个动物,编写的时候用的Animal引用。

注意:仔细看下面图片中的注释文字。Animal[] animals = new Animal[5]; 并没有对Animal进行实例化,而是创建了一个数组对象,这个对象用于装纳Animal对象。


如果想要用这个List容纳更多种类的对象,那么就要把Animal基类换成Object类了。

注意:java中,所有我们写的类都继承于Object类。你没有显式写出基类的类,编译器会自动给你加上extends object。比如上面的public class MyAnimalList 声明的时候没有写基类,因为它自己就是基类,但编译的时候,编译器会自动让它变成public class MyAnimalList extends Object。

Object具有的方法 method

Object基类内有许多方法,其中有四个我们比较关心的这里提出一下,但并不只有这四个。


Object class不是个虚类。其下有些方法能够被override,但是有些被标为final,不能重写。

object 类为什么不是虚类?
答:Object能够实例化,似乎很奇怪,就像把Animal实例化一样,但是 object实例化在线程同步上很有用

Object类存在的目的?
答:第一是为了给所有类提供一个父类,好使用多态。二是提供一些所有类都能使用的通用的方法,不然每个类写一个就太重复了。比如和线程相关的一些方法,就在Object中,非常重要。

为什么不把所有返回值类型都写为Object?
答:不这么做是因为java会严格类型检查,什么意思?就是比如,Object o = new Ferrari();把一个法拉利的实例放入object引用,然后写o.goFast(); 这句话是非法的。因为Object的引用,所能调用的方法,必须真的在Object中才行。不然编译器是过不去的。于是,如果所有返回类型都写成Object,那返回值所能调用的方法就大大缩小了,也失去这个引用的作用了。
注意:编译器允许一个reference 调用一个方法,是根据这个reference的类型,而不是根据它所指向的引用的实际对象的类型。

reference要调用一个方法,那这个reference的类型,必须真正含有这个方法。

根据上面问题,又能得到一个问题,如下:
ArrayList<Dog> aList = new ArrayList<Dog>();
aList.add(new Dog());
一个ArrayList声明类型为Dog,然后给里面放入个Dog,取出来的时候:
Dog aDog = aList.get(0);
它还是个Dog。

但是如果声明为,ArrayList<Object> aList = new ArrayList<Object>();
再放入个Dog ,aList.add(new Dog());
再拿出来的时候,Dog aDog = aList.get(0);  是非法的!
拿出来的是object类型,已经失去了原来的形态,所有Dog本来有的方法都调用不成了。

如果你十分确信你拿到的Object的引用,本来是个Dog类型,那可以 通过类型转换,还原Object本来面目。
Object o = aList.get(0);
Dog aDog = (Dog)o;
o.bark(); 这样这句话就合法了。

如果不确定一个Object引用原型是什么,可以用 instanceof 操作符。
if (o instanceof Dog) {
     Dog d = (Dog)o;
}
注意:如果没有用instanceof操作符检查,且o的原型不是Dog,那强转后会报ClassCastException 异常。

接口

java为什么不允许多继承?
答:先看多继承引入的一个问题,Deadly Diamond Death。
多继承就会引入一些复杂的问题,比如C++在遇到上面问题的时候,就在写代码的时候要仔细构思了,而java是为了简单,一切从简,直接去除了多继承,也让你不再有考虑多继承可能引入的特殊情况。

多继承的替代品就是接口。

接口就是定义一个游戏规则,各个不同继承线上的类,要想一起玩游戏,即获得一定功能,能力,就要遵守游戏规则。

接口内的所有方法,都是public abstract的,而且都是默认的,写不写出来都可以。
注意:接口内, 对于方法,访问控制 只有public 和 abstract 两个能写,别的都不行。什么static、final的,都别想了,都不行。

注意:接口内,可以有成员变量。 对于成员变量前面的标号 只能为public 、static 、final,这三个,单独或组合都行,别的都不能有!

一个类可以加多个接口。
public interface Pet {
     public abstract void beFriendly();  注意虚方法写法。
}
public class Dog extends Canine  implements Pet{
     .........
}

接口是为了极端的自由,多态。几个不在同一条继承线上的类,可以共同承载同一个接口,然后声明该接口的引用,可以把这些类的对象都投到这个引用中,极端的多态。

注意:接口和虚类一样,不能进行实例化。能声明引用其实就够了。

注意:父类implements 一个接口后,子类自动具有接口方法。

注意:接口是可以继承的,一个接口可以继承另一个接口。

Super的使用

如下所示,假如一个子类,一个父类。子类重写了父类的runReport()函数,但是子类的这个runReport()函数仅仅比父类的多了一点点功能,大同小异。于是,子类的runReport()方法编写时,可以先调用父类的runReport()方法,然后再编写与父类不同的部分执行,这样可以大大节省代码,不然子类 父类 重复部分还要再写一遍代码,非常麻烦。
这时,在子类方法中,要调用父类的某个方法,就可以用super关键字。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值