1 基本概念
Java工作方式
目的是让人写出程序,并且能够在拥有的任何设备上执行
1.源代码
编写源代码文件Party.java
2.编译器
用编译器运行源代码,编译器会检查错误,有错误要改正,才能产生正确输出
javac Party.java
3.输出
编译器会产出字节码。任何支持java的装置都能把它转译成可执行的内容,编译后的字节码与平台无关
Party.class
4.java虚拟机(JVM)
读取与执行字节码
java Party
Java的程序结构
类在源文件中
方法 在 类中
语句statement在方法中
1.源文件
扩展名为.java,带有类的定义。类用来标识程序的一个组件,类的内容必须在花括号内。
2.类
类中带有一个或多个方法,方法必须在类的内部声明。
3.方法
在方法的花括号中编写方法应该执行的指令,方法由一组指令组成,方法是已个函数或过程
// 源文件
public class Dog {
// 类
void bark() {
// 方法
statement1
statement2
}
}
剖析类
Java虚拟机启动执行时,它会寻找你在命令列锁指定的类。然后它会锁定像下面这样一个特定的方法:
public static void main (String[] args) { // 程序代码 }
main()中可以做什么
1.做某件事
声明、设定、调用方法等普通语句
2.反复做某件事
for和while循环
3.在适当条件下做某件事
if/else的条件分支测试
2 类与对象
程序规格
在图形接口画出四方形,圆形,三角形。当用户点选图形时,图形需要顺时钟转360°并依据形状的不同播放不同的AIF音效文件。
现在规格改了,加上了阿米巴原虫形状,用户点选时也是旋转并播放.hif声音文件
原来的旋转都是:
1.找出指定形状的外接四边形
2.计算出四边形的中心点,以此点为轴做旋转
老板娘认为阿米巴原虫应该要绕着一端旋转,类似秒针那样
面向对象的继承
以对象来思考
在设计类时,要记得对象是靠类的模型塑造出来的。可以这样看:
- 对象已知的事物 - 实例变量 instance variable
- 对象会执行的动作 - 方法 methods
对象本身已知的事物称之为实例变量。它们代表对象的状态(数据),且该类型的每一个对象都会独立的拥有一份该类型的值。
所以也可以把对象当成实例。
对象可以执行的动作称为方法。在设计类时,你也会设计出操作对象数据的方法。对象带有读取或者操作实例变量的方法是很常见的情形。(set get)
因此说对象带有实例变量和方法,但是它们都是类设计中的一部分。
类和对象的不同
类是对象的蓝图。它会告诉虚拟机如何创建某种类型的对象。根据某类创建出的对象都会有自己的实例变量。
创建你的第一个对象
要做出哪些东西才会运用对象?你需要两个类。一个是要被操作于对象的类(比如说Dog、AlarmClock和Television等),另一个是用来测试该类的类。测试用的类带有main()并且你会在其中建立与存取被测的对象。
// 1.编写类
class Dog {
int size;
String breed;
String name;
void bark() {
System.out.println("ruff!ruff!");
}
}
// 2.编写测试用的类
class DogTestDrive {
public static void main(String[] args) {
// 3.在测试用的类中建立对象并且存取对象的变量和方法
Dog d = new Dog();
d.size = 40;
d.bark();
}
}
要点
- 面向对象设计扩展程序功能不需要改动之前已经测试好的程序代码
- 所有的Java程序都定义在类中
- 类如同蓝图描述该类型的对象要如何创建
- 对象自治,你无须在意它如何完成对象任务
- 对象有已知的事物,并能执行工作
- 对象本身已知道的事物称为实例变量,它代表对象的状态
- 对象可执行的动作称为方法,它代表对象的行为
- 创建类时,可能同时会需要创建独立、测试用的类
- 类可以继承自较为抽象的父类
- Java的程序在执行期是一组会互相交谈的对象
3 primitve主数据类型和引用
声明变量
为了让类型安全能发挥作用,必须声明所有变量的类型。
变量包括primitive主数据类型和引用类型。
primitive主数据类型包括整数、浮点数、布尔等。
而对象引用保存的是对象的引用。
变量必须拥有类型,另一条规则是必须拥有名称。
int count // int->类型 count->名称
primitive主数据类型和引用
我要大杯摩卡,不,中杯好了
变量就像是杯子,是一种容器,承装某些事物。
primitive主数据类型如同咖啡馆的杯子,他们有不同的大小,而每种大小都有个名称
每种primitive主数据类型变量有固定的位数,存放数值的有6种大小
byte 8
short 16
int 32
long 64
float 32
double 64
小心别溢出来
要确保变量能存下所保存的值。
你无法用小杯子装大值。
好吧,其实可以,但是会损失某些信息,也就是所说的溢位。
int x = 24; byte b = x; // no!
为什么不行呢?毕竟byte绝对装得下24这个值。但是对编译器来说,你正在将大物体装进小容器中,所以会有溢位的可能。
你可以用几种办法来给变量赋值
- 在等号后面直接打出(x=12,isGood = true)
- 指派其他变量的值(x=y)
- 上述两种方式的组合(x=y+43)
避开关键字keyword
你可以根据以下规则来帮助类、方法或变量命名
- 名称必须以字母、下划线(_)或符号$开头,不能用数字开头
- 除了第一个字符外,后面可以用数字
- 只要符合上述两条规则,就可以随意命名,但还要避开Java保留字
你在编写main的时候就见过几个保留字了:
public static void
primitive主数据的保留字如下:
boolean char byte short int long float double
保留字一览表
boolean | byte | char | double | float | int | long | short | public | private |
protected | abstract | final | native | static | strictfp | synchronized | transient | volatile | if |
else | do | while | switch | case | default | for | break | continue | assert |
class | extends | implements | import | instanceof | interface | new | package | super | this |
catch | finally | try | throw | throws | return | void | const | goto | enum |
对象引用
控制Dog对象
你已经知道如何声明primitive主数据类型的变量并赋值给它。但非primitive主数据类型的变量又该如何处理?换句话说,对象怎么处理?
- 事实上没有对象变量这样的东西存在
- 只有引用reference到对象的变量
- 对象引用保存的是存取对象的方法
- 它并不是对象的容器,而是类似对象的指针。或者可以说是地址。但在Java中我们不会也不知道引用变量中实际装载的是什么,它只是用来代表单一的对象。只有Java虚拟机才会知道如何使用引用来取得对象。
Dog d = new Dog();
d.bark();
// 把它想成遥控器
// Dog的引用变量想成是Dog的遥控器。你可以通过它来执行工作
并没有超巨型的杯子可以放大到能够装载所有的对象。对象只会存在于可回收垃圾的堆上。
对象引用也只是个变量值
还是会有东西放入杯子中,只是引用所放进去的是遥控器。
Dog = myDog = new Dog();
// 1 声明一个引用变量 Dog myDog 要求Java虚拟机分配空间给引用变量
// 此引用变量将永远被固定为Dog类型
// 2 创建对象 new Dog() 要求Java虚拟机分配堆空间给新建立的Dog对象
// 3 链接对象和引用
// 将新的Dog赋值给myDog这个引用变量。换言之就是设定遥控器
问答
问:引用变量有多大?
答:不知道。除非和Java虚拟机开发团队的人有交情,不然你是不会知道引用是如何表示的。其内部有指针,但你无法也不需要存取。如果你要讨论内存分配的问题,最需要关心的是需要建立多少个对象和引用,以及对象的实际大小。
问:既然这样,那是否意味着所有的对象引用都具有相同的大小,而不管它实际上所引用的对象大小?
答:是的,对于任意一个Java虚拟机来说,所有的引用大小都一样。但不同的Java虚拟机可引用大小可能不同。
问:我可以对引用变量进行运算吗,就像c语言那样?
答:不行。
在垃圾收集堆上的生活
Book b = new Book();
Book c = new Book();
// 引用 2
// 对象 2
Book d = c;
// 引用 3
// 对象 2
c = b;
// 引用 3
// 对象 2
堆上的生与死
Book b = new Book();
Book c = new Book();
// 引用 2
// 对象 2
b = c;
// b原先指向的对象1没有引用,无法存取,被抛弃,能够做垃圾收集器
// 引用 2
// 对象 2
// 被抛弃对象 1
c = null;
// 作用中的引用数 1
// null引用 1
// 可存取对象 1
// 被抛弃对象 1
数组犹如杯架
// 声明一个int数组变量
int[] nums;
// 创建大小为7的数组,并赋值给nums
nums = new int[7];
// 赋予数组每一个元素int值
nums[0] = 6;
nums[1] = 19;
nums[2] = 44;
nums[3] = 42;
nums[4] = 10;
nums[5] = 20;
nums[6] = 1;
数组也是对象
如果需要快速、有序、有效率地排列元素时,数组是不错的选择。
存引用类型时,存的是引用,而不是对象本身。
不管存的是primitive主数据类型还是对象引用,数组永远是对象。
要点
- 变量有两种:primitive主数据类型和引用
- 变量的声明必须有类型和名称
- primitive主数据类型变量值是该值得字节所表示的
- 引用变量的值代表位于堆之对象的存取方法
- 引用变量如同遥控器,对引用变量使用圆点运算符,如同按下遥控器般的,存取它的方法或变量
- 没有引用到任何对象的引用变量的值为null
- 数组一定是对象,不管声明的元素是否为primitive主数据类型
4 方法操作实例变量
状态影响行为,行为影响状态。
类描述的是对象知道什么和执行什么。
任一类的每个实例可能带有相同的方法,但是方法可以根据实例变量来表现不同的行为。
void play() {
soundPlayer.playSound(title);
}
Song t2 = new Song();
t2.setArtist("Travis");
t2.setTitle("Sing");
Song s3 = new Song();
s3.setArtist("Sex Pistols");
s3.setTitle("My Way");
方法的参数
你可以传值给方法
d.bark(3);
// 举例来说,你可能会告诉Dog对象叫几声
方法会运用形参。调用的一方会传入实参。
实参是传给方法的值。当它传入方法后,就成了形参。参数和局部变量是一样的。它有类型和名称,可以在方法内运用。
重点是:如果某个方法需要参数,你就一定得传东西给它。那个东西得是适当类型的值。
你可以从方法中取返回值
void go() {
}
int giveSecret() {
return 42;
}
可以向方法传入一个以上的参数
doSth(2,3)
Java通过值传递的,也就是说通过拷贝传递
int x = 7
// 声明int类型变量并赋值为7
void go (int z) {}
// 声明有int的方法,参数名z
foo.go(x)
// x为参数传入go方法,x的字节组合会被拷贝并装进z
// 在方法中改变z的值,此时x的值不会改变,传给z的只是个拷贝
// 方法无法改变调用方所传入的参数
要点
- 类定义对象所知及所为
- 对象所知者是实例变量
- 对象所为者是方法
- 方法可依据实例变量来展现不同的行为
- 方法可使用参数,这代表你可以传入一个或多个值给方法
- 传给方法的参数必须符合声明时的数量,顺序和类型
- 传入与传出方法的值类型可以隐含地放大或是明确地缩小
- 传给方法的参数值可以是直接指定的文字或数字,或者是与所声明参数相同类型的变量
- 方法必须声明返回类型,void类型代表方法不返回任何东西
- 如果方法声明了非void的返回类型,那就一定要返回与声明类型相同的值
运用参数与返回类型
Getter的目的只有一个,就是返回实例变量的值。Setter的目的,就是要取用一个参数来设定实例变量的值。
public class ElectricGuitar {
String brand;
String getBrand() {
return brand;
}
void setBrand(String aBrand) {
brand = aBrand;
}
}
封装
暴露的意思是可以通过圆点运算符来存取,这会很糟糕。因为无法防止别人的操作。
tehCat.height = 27;
tehCat.height = 0;
换成setter
public void setHeight(int ht) {
if(ht > 9) {
height = ht;
}
}
数据隐藏
所以如何隐藏数据呢?答案是使用公有私有两个存取修饰符。
以下是封装的基本原则:
将你的实例变量标记为私有的,并提供公有的getter和setter来控制存取动作。
声明与初始化实例变量
public class PoorDog {
// 声明但是不赋值
private int size;
private String name;
// 会返回什么?
public int getSize() {
return size;
}
public String getName() {
return name;
}
}
实例变量永远会有默认值。如果声明了没赋值或者调用setter,实例变量还是会有值。
int 0
float 0.0
boolean false
reference null
实例变量与局部变量之间的差别
- 实例变量是声明在类内
- 局部变量声明在方法中
- 局部变量使用前必须初始化
局部变量没有默认值!如果再变量被初始前就要用的话,编译器会显示错误。
变量的比较
使用==来比较两个primitive主数据类型,或者判断两个引用是否引用同一个对象。
使用equals()来判断两个对象是否在意义上相等。(像是两个string对象是否带有相同的字节组合)
5 编写程序
超强力方法
开发类
- 找出类应该做的事情
- 列出实例变量和方法
- 编写方法的伪码
- 编写方法的测试用程序
- 实现类
- 测试方法
- 除错或重新设计
要点
- 你的Java程序应该从高层的设计开始
- 你通常会在创建新的类时写出下列三种东西:
- 伪码
- 测试码
- 真实代码
- 伪代码应该描述要做什么事情而不是如何做
- 使用伪代码来帮助测试代码的设计
- 实现方法之前要编写测试代码
- 如果知道要执行多少次,应该使用for循环而不是while
转换primitive主数据类型
// String -> int
Integer.parseInt("3");
long y = 42;
int x = y; // 不能通过编译
long y = 42;
int x = (int) y;
long y = 40002;
short x = (short) y; // x的值是-25534
float f = 3.14f;
int x = (int) f; // x的值是3
6 认识Java的API
使用Java函数库
Java内置有数百个类
数组不够用的时候
ArrayList
- add(Object elem) 添加元素
- remove(int index) 根据索引移除元素
- remove(Object elem) 移除该元素
- contains(Object elem) 如果匹配,true
- isEmpty() 如果为空,true
- indexOf(Object elem) 返回对象的索引,否则-1
- size() 查询大小
- get(int index) 获取当前索引的对象
比较ArrayList与一般数组
- 一般数组在创建时就必须确定大小
- 存放对象给一般数组时必须指定位置
- 一般数组使用特殊语法
- ArrayList是参数化的
// 确定大小
new String[2]
// 指定位置
myList[1] = b;
myList.add(b);
// 特殊语法
myList[1]
// 参数化的
ArrayList<String>
使用函数库
在Java的API中,类是被包装在包中。
要使用API中的类,你必须知道它被放在哪个包中。
import java.util.ArrayList;
public class MyClass {}
// else
java.util.ArrayList<Dog> list = new java.util.ArrayList<Dog>();
继承与多态
对象村的优质生活
了解继承
SuperHero
suit
tights
specialPower
userSpecialPower()
putOnSuit()
// ------------子类
FriedEggMan
// -------------子类
PantnerMan
userSpecialPower()
putOnSuit()
PantnerMan是SuperHero的子类,则会自动继承SuperHero的实例变量和方法。但是PanterMan可以加入自己的实例变量和方法,也可以覆盖掉继承自SuperHero的方法
设计动物仿真程序的继承树
1.找出具有共同属性和行为的对象
用继承来防止子类中出现重复的代码
Animal
picture
food
hunger
boundaries
location
makeNoise()
eat()
sleep()
roam()
2.设计代表共同行为与状态的类
动物都以同样的方式进食吗?
3.决定子类是否需要让某项行为(也就是方法的实现)有特定不同的运行方式
观察Animal这个类后,我们认为eat() makeNoise()应该由各个子类自行覆盖
寻找更多抽象化的机会
4.通过寻找使用共同行为的子类来找出更多抽象化的机会
"是一个"与"有一个"
如果想知道某物是否应该继承另一物时,可以用IS-A测试来检验,
三角形是一个多边形。
等一下!还有!
如果类Y是继承类X,且类Y是类Z的父类,
那么Z应该能通过IS-A-X的测试
Animal {
makeNoise()
eat()
sleep()
roam()
}
Canine extends Animal{
roam()
}
Wolf extends Canine {
makeNoise()
eat()
}
比如这种继承关系
Wolf是一个Animal,那么Animal能做的Wolf都能做。至于它是怎么做的,中间有无覆盖,都不重要。
如何知道子类能够继承下来哪些东西?
父类可以通过存取权限决定子类能否继承某些特定的成员。
private default protected public
左边是最受限制的,越往右,限制越小。
public类型的成员会被继承,
private类型的成员不会
你会善用继承吗?你滥用继承了吗?
- 当某个类会比其父元素更具有特定意义时使用继承。比如美国短毛猫是一种特定的猫。
- 在行为程序(实现程序代码)应该被多个相同级别类型所共享时,应该要考虑使用继承。
- 若两者的关系对比继承结构来说不合理,则不要只是因为打算要重用其他类的程序代码而运用继承。比如河马和钢琴,都有发声程序。
- 如果两者不能通过IS-A测试就不要应用继承关系。
继承到底有什么意义?
1.避免了重复的程序代码
想改变某程序时,只要改动这个地方,子类会发生相同改变
2.定义出共同的协议
继承让你可以确保某个父类型下的所有类都会有父类所持有的全部(可继承的)方法(public)
也就是说,你会通过继承来定义相关类之间的共同协议
多态的运行
要观察多态是如何运行的,我们就先退回去看一般声明引用和创建对象的方法。
Dog myDog = new Dog();
1.声明一个引用变量 Dog myDog
2.创建对象 new Dog()
3.连接对象和引用 =
重点在于引用类型与对象的类型必须相同。此例子中,两者皆为Dog
但是在多态下,引用与对象可以是不同的类型
Animal myDog = new Dog();
运用多态时,引用类型可以是实际类型的父类。