Head First Java

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

实例变量与局部变量之间的差别

  1. 实例变量是声明在
  2. 局部变量声明在方法
  3. 局部变量使用前必须初始化

局部变量没有默认值!如果再变量被初始前就要用的话,编译器会显示错误。

变量的比较

使用==来比较两个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类型的成员不会

你会善用继承吗?你滥用继承了吗?

  1. 当某个类会比其父元素更具有特定意义时使用继承。比如美国短毛猫是一种特定的猫。
  2. 在行为程序(实现程序代码)应该被多个相同级别类型所共享时,应该要考虑使用继承。
  3. 若两者的关系对比继承结构来说不合理,则不要只是因为打算要重用其他类的程序代码而运用继承。比如河马和钢琴,都有发声程序。
  4. 如果两者不能通过IS-A测试就不要应用继承关系。

继承到底有什么意义?

1.避免了重复的程序代码

改变某程序时,只要改动这个地方,子类会发生相同改变

2.定义出共同的协议

继承让你可以确保某个父类型下的所有类都会有父类所持有的全部(可继承的)方法(public)

也就是说,你会通过继承来定义相关类之间的共同协议

多态的运行

要观察多态是如何运行的,我们就先退回去看一般声明引用和创建对象的方法。

Dog myDog = new Dog();

1.声明一个引用变量 Dog myDog

2.创建对象 new Dog()

3.连接对象和引用 =

重点在于引用类型与对象的类型必须相同。此例子中,两者皆为Dog

但是在多态下,引用与对象可以是不同的类型

Animal myDog = new Dog();

运用多态时,引用类型可以是实际类型的父类

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值