4.1 操作对象状态的方法
同一类型的每个对象能够有不同的方法行为,任一类的每个实例都带有相同的方法,但是方法可以根据实例变量的值来表现不同的行为。
play()会播放title值表示的歌曲,调用某个实例的play()可能会播放“Politik”而另一个会播放“Darkstar”,然而方法却是相同的
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");
小型犬的叫声与大型犬不同
Dog类中的bark()方法会用size实例变量来决定使用哪一种声音
class Dog {int size;String name;void bark() {if (size > 60) {System.out.println("Woof! Woof!");} else if (size > 14) {System.out.println("Ruff! Ruff!");} else {System.out.println("Yip! Yip!");}}
}
class DogTestDrive {public static void main(String[] args) {Dog one = new Dog();one.size = 70;Dog two = new Dog();two.size = 8;Dog three = new Dog();three.size = 35;one.bark();two.bark();three.bark();}
}
4.2 方法参数与返回类型
可以传值给方法
方法会运用形参(parameter)。调用的一方会传入实参(argument)。
实参是传给方法的值。当它传入方法后就成了形参。参数跟局部(local)变量是一样的。它有类型与名称,可以在方法内运用。
重点是:如果某个方法需要参数,你就一定得传东西给它。那个东西得是适当类型的值。
1.调用Dog上的bark()方法,并传入“3”这个值(作为此方法的参数)
2.以int类型表示的值3会传递给bark()
3.此值会传给numOfBarks这个参数(int类型的变量)
4.把numOfBarks当作一般的变量使用
可以从方法中取返回值
方法可以有返回值。每个方法都声明返回的类型,但目前我们都是把方法设成返回void类型,这代表并没有返回任何东西。
void go() {
}
但我们可以声明一个方法,回传给调用方指定的类型值
int giveSecret() {
return 42;
}
如果你将一个方法声明有返回值,你就必须返回所
声明类型的值! (或是与声明类型兼容的值)
可以向方法中传入一个以上的参数
方法可以有多个参数。在声明的时候要用逗号分开,传入的时候也是用逗号分开。最重要的是,如果方法有参数,一定要以正确数量、类型和顺序来传递参数。
4.3 值传递
Java是通过值传递的,也就是说通过拷贝传递
1.声明一个int类型的变量并赋值为7。代表7的字节组合会被放进称为x的变量
2.声明一个有int参数的方法,参数名称为z
3.以x为参数传入go()方法中。x的字节组合会被拷贝并装进z中
4.在方法中改变z的值。此时z的值不会改变,传给z的只是个拷贝
方法无法改变调用方所传入的参数
4.4 Getters与Setters
运用参数与返回类型
Getter与Setter。如果要很正式地讨论,你会称他们为Accessor与Mutator。不过这样只是更饶舌而已,由于Getter与Getter较为符合Java的命名习惯,所以我们接下来都会这么叫它们。
Getter与Setter可让你执行get与set。Getter的目的只有一个,就是返回实例变量的值。毫无意外的,Setter的目的就是要取用一个参数来设定实例变量的值。
public class ElectricGuitar {String brand;int numOfPickups;boolean rockStarUsesIt;String getBrand() {return brand;}void setBrand(String aBrand) {brand = aBrand;}int getNumOfPickups() {return numOfPickups;}void setNumOfPickups(int num) {numOfPickups = num;}boolean getRockStarUsesIt() {return rockStarUsesIt;}void setRockStarUsesIt(boolean yesOrNo) {rockStarUsesIt = yesOrNo;}
}
4.5 封装(Encapsulation)
不封装可能会很难堪
在此之前我们已经犯了一个在面向对象界最糟糕的错误
泄露资料!
我们并没有注意到数据会被全世界的人看到,甚至还可以被改动。
你可能经历过暴露出实例变量的不愉快感觉。
暴露的意思是可通过圆点运算符来存取,像是:
tehCat.height = 27;
你可以把这件事情看做是直接通过远程控制修改Cat的实例变量。若远程控制落入不当之人的手上,变量就可能会成为杀伤力强大的武器。因为你无法防止下面的操作:
theCat.height =0; //千万不能让这种事情发生
这一定会很糟糕.
我们需要创建Setter这个方法给所有的实例变量,并寻求某种方法强制其他程序都必须通过Setter来设定变量而不是直接的存取。
数据隐藏
要将程序的实现从不良数据改成可以保护数据且让你还能修改数据的方式是很简单的。所以要如何隐藏数据呢?答案是使用公有与私有这两个存取修饰符(access modifier)。
封装的基本原则:将你的实例变量标记为私有的,并提供公有的getter与setter来控制存取动作。或许在你有了更多的Java设计与编写经验之后会有些许不同的做法,但是目前这种做法可以维持住安全性。
将实例变量标记为private。
将getters与setters标记为public。
封装GoodDog
public class GoodDog {private int size;public int getSize() {return size;}public void setSize(int s) {size = s;}void bark() {if (size > 60) {System.out.println("Woof! Woof!");} else if (size > 14) {System.out.println("Ruff! Ruff!");} else {System.out.println("Yip! Yip!");}}
}
public class GoodDogTestDrive {public static void main(String[] args) {GoodDog one = new GoodDog();one.setSize(70);GoodDog two = new GoodDog();two.setSize(8);System.out.println("Dog one: " + one.getSize());System.out.println("Dog two: " + two.getSize());one.bark();two.bark();}
}
任何有值可以被运用到的地方,都可用调用方法的方式来取得该类型的值
int x = 3 + 24;
int x = 3 + one.getSize();
数组中的对象就如同其他的对象一样,唯一的差别就
是如何取得而已。换言之,不同处在于你如何取得遥
控器。让我们先来尝试调用数组中的Dog对象。
4.6 数组中的引用
数组中对象的行为
数组中的对象就如同其他的对象一样,唯一的差别就
是如何取得而已。换言之,不同处在于你如何取得遥
控器。让我们先来尝试调用数组中的Dog对象。
1.声明一个装载7个Dog引用的Dog数组
Dog[] pets;
pets = new Dog[7];
2.创建两个Dog对象并赋值为数组的前两项元素
pets[0] = new Dog();
pets[1] = new Dog();
3.调用这两个Dog对象的方法
pets[0].setSize(30);
int x = pets[0].getSize();
pets[1].setSize(8);
声明与初始化实例变量
你已经知道变量的声明至少需要名称与类型:
int size;
String name;
并且你也知道可以同时初始化-(赋值)变量:
int size = 420;
String name ="Donny”;
但如果你没有初始实例变量时,调用getter会发生什么事?也就是说实例变量在初始之前的值是什么?
实例变量永远都会有默认值,若没有明确的赋值给实力并不了,或者没有调用setter,实例变量还是会有值
integers 0
floating points 0.0
booleans false
reference null
null代表没有操作对象的远程控制,是个引用而不是对象
实例变量与局部变量之间的差别
1.实例变量是声明在类内而不是方法中
class Horse {
private double height = 15.2;
private String bread;
// more code...
}
2.局部变量是声明在方法中的
class AddThing {
int a;
int b = 12;
public int add() {
int total = a + b;
return total;
}
}
3.局部变量在使用前必须初始化
class Foo {
public void go() {
int x;
int z = x + 3; //无法编译
}
}
局部变量没有默认值,若在变量被初始化前就要使用的话,编译器会显示错误
变量的比较
有时你需要知道两个primitive主数据类型是否相等。很简单,只要使用==这个运算符就可以。有时你想要知道两个引用变量是否引用到堆上的同一个对象。这也很容易,也是使用==运算符。但有时你会需要知道两个对象是否真的相等。此时你就得使用equals()这个方法。相等的意义要视对象的类型而定。举例来说,如果两个不同的String带有相同的字符,它们在涵义上是相等的。但对Dog来说,你认为尺寸大小或名字一样的Dog是相等的吗?所以说是否被视为相等要看对象类型而定。我们会在后面的章节继续探讨对象相等性的部分,但现在我们要知道的是==只用来比对两个变量的字节组合,实质所表示的意义则不重要。字节组合要么就是相等,要么就是不相等。
使用==来比对primitive主数据类型
这个运算式可以用来比较任何类型的两个变量,它只是比较其中的字节组合
int a = 3;
byte b = 3;
if (a == b) {// true}
使用==来判别两个引用是否都指向同一对象
这只是在比较字节组合的模样。此规则适用于引用与primitive主数据类
型。因此==运算符对参照相同对象的引用变量会返回值。在此情况下我们还是无
法得知字节组合的样式,但可以确定的是所参照的相同的对象。
Foo a = new Foo();
Foo b = new Foo();
Foo c = a;
if (a == b) {// false}
if (a == c) {// true}
if (b == c) {// false}
使用==来比较两个primitive主数据类型,或者判断两个引用是否引用同一个对象。
使用equals()来判断两个对象是否在意义上相等。
(像是两个String对象是否带有相同的字节组合)