《java语言程序设计》笔记(二)
来源:互联网 发布:王圆箓 知乎 编辑:程序博客网 时间:2024/06/05 05:56
Chapter8 Objects and Classes
对象由状态与行为构成,定义的数据的当前值对应类的状态,定义的方法对应类的行为。
类中有一个特殊的方法称为构造器(构造方法),当创建一个对象时会自动执行构造方法的代码。构造器中可以执行任何动作,但构造器只设计用来执行初始化动作,如初始化对象的数据的值。
我们可以把两个或多个类放在一个文件中,但只能有一个类是public的。另外被修饰为public的类的名字必须与文件名相同。
在一个非主类的类中临时添加main方法是临时测试一个类的一种好方法。
构造器是一种特殊的方法,它有三个特性:
1、构造器的方法名必须跟它的类名相同;
2、构造器没有返回类型,包括void;
3、当使用new操作创建一个对象时才会调用构造器,构造器担任初始化对象的角色。
像一般方法一样,构造器也能被重载。
在构造方法前加上void关键字是常见的错误,如在Circle类中有
public void Circle() {
}
在这里,Circle()仅仅是一个方法,而不是构造器。(当new该类的对象时不会自动执行Circle()方法。)
对象的访问是通过一个包含对对象的引用的引用变量。
严格的来说,一个对象引用变量和一个对象时不同的,但是大多时候,我们忽略这个区别,所以对于Circle myCircle = new Circle(),我们直接说myCircle是一个对象。
在Java中,数组是被看作对象的。
对象中的变量称为实例变量,对象中的方法称为实例方法。
如果类中的方法是静态方法(有static关键字修饰),则可以使用className.methodName(arguments)调用该方法(可以创建对象后objectRefVar.methodNmae(arguments)调用方法,但不建议这样做,编译器会有警告提示);如果类中不是静态的方法则是实例方法,必须先创建实例对象后才能调用该方法,调用的格式是objectRefVar.methodName(arguments)。
匿名对象
如System.out.println(“Area is “ + new Circle(5).getArea());中new的Circle(5)对象是一次性使用的,没有引用变量指向该对象,所以称为匿名对象。
null是引用变量的一个值,如true和false是Boolean类型的值一样。
引用变量初始化为null。
Java不会为局部变量指定默认值。
NullPointerException是常见的运行时错误,当使用一个值为null的引用变量调用方法时会发生该错误,所以当调用方法时,必须确保确保引用变量已经指向一个对象。
当一个对象不再有引用变量指向其时,Java的垃圾收集程序将会回收该内存。
如果一个对象确定不再需要时,可以使指向其的引用变量赋值null,当没有引用变量指向该对象时,JVM会自动地回收该对象的空间。
P274 java.util.Date类(创建该对象后可用getTime和toString方法获取时间)
P275 java.util.Random类 (创建该对象后可以用对应的nextInt()等方法获取int、long、double、float、boolean类型的随机值)
使用Random对象时要指定一个种子,如果不指定将使用默认种子,默认种子是使用当前的时间作为种子。Random类的构造方法有Random()和Random(seed: long)两个。
如果使用相同的种子则产生的随机数序列是相同的,有时为了方便调试,我们先使用固定的种子产生固定的随机数序列,待最终确认后再更改种子。
静态变量存储在一个一般的内存位置(可以称为数据区),由于是存储在一般的内存位置,如果一个对象改变静态变量的值,则所有由该类创建出的对象都会受到影响。
静态方法与静态变量一样,都是存储在一般的内存位置。
常量是被所有由该类创建的对象所共享的,所以常量应该定义为final static。
如果没有定义可视性,则该类/方法/数据会定义为默认,默认定义的可视范围为包内,这也称作包私有或者包访问。
包可用来区别类,包的写法是 package packageName, 包的定义必须放在文件的开头,包定义前边不能有其他的任何语句,包括空白和注释。
如果没有包定义java会自动定义为默认包,虽然可以使用默认包,但是不建议这样做。
被修饰为private的变量和方法只能在该类内访问。
如果给局部变量加上public 或private关键字会产生编译错误。
大多情况下,构造器应该修饰为public,但是,如果你想限制用户创建一个类的对象,可以把构造方法修饰为private。如java.lang.Math类,该类提供了很多静态方法供调用,但是我们不能创建Math类的对象,因为Math类的构造方法定义如下:
private Math() {
}
对于类中的private 变量,我们可以定义get方法和set方法来获取和修改该变量。
get方法的命名习惯如下:
public returnType getPropertyName()
如果返回的类型是boolean则如下:
public boolean isPropertyName()
set方法的命名习惯:
public void setPropertyName(dataType propertyValue)
我们可以在set和get方法中添加对数据的限制,如我们只想把变量设为正数,则我们可以再set方法中先判断输入的数据是否符合要求。在set方法和get方法中添加限制代码可以简化其他类中的重复限制代码,使用private修饰、set方法、get方法不仅提高类的封装性,还提高了类的可维护性。
我们可以传递一个对象给方法,同数组一样,我们传递的是一个指向该对象的引用,如果我们对该对象修改将会是真正的修改。
Java只用一种参数传递模型:pass-by-value(值传递),如果形参是一个引用变量,我们修改形参所指向的对象将会有实则上的修改,但如果是对形参进行赋值,则同原始数据类型的变量形参一样不会改变实参。
如有
public static void testCircle(Circle3 c) {
c.setRadius(1); //会修改c指向的对象的半径
c = null; //只修改形参c为null,不会修改实参的值。
}
我们可以创建对象数组,没有赋值的对象数组初始化为null。
Chapter9 Strings and Text I/O
在很多的语言中,字符串时被看作一个字符数组,但是在Java中字符串时作为一个对象。
定义一个字符串的格式为
String newString = new String(stringLiteral);
如String message = new String(“Welcome to Java”);
Java把字符串常量看作对象,所以字符串也可以如下定义
String message = “Welcome to Java”
我们也可以用字符数组创建字符串,如创建字符串”Good Day”:
char[] charArray = {‘G’, ‘o’, ‘o’, ‘d’, ‘ ‘, ‘D’, ‘a’, ‘y’};
String message = new String(charArray);
也如一般对象一样,字符串变量和字符串对象是不同的概念,但是我们一般情况不在意,都叫做字符串。
字符串对象是不可变的。
因为字符串是不可变的且在编程时随处可以出现,所以JVM对于由相同字符序列构成的字符串常量只创建唯一的一个实例,这样可以提高效率和节省内存。
对以下代码:
String s1 = “Welcome to Java”;
String s2 = new String(“Welcome to Java”);
String s3 = “Welcome to Java”;
System.out.println(“s1 == s2 is “ + (s1 == s2));
System.out.println(“s1 == s3 is “ + (s1 == s3));
显示的结果是
s1 == s2 is false
s1 == s3 is true
s1 和 s2包含的引用值是不同的,即使他们指向的对象包含的字符串内容相同。
可以用String的equals实例方法判断两个字符串是否相等(如果用“==”判断的话仅仅是判断引用值是否相等,如上边例子一样,即使字符串内容相同但引用值未必相同)。
String中的compareTo方法用来比较两个字符串,当不同时会返回两字符的Unicode编码的差值的负数,字符串不能使用>,>=,<, <=进行比较,所以可以用compareTo方法进行类似的比较。
在数组中是用arrayName.length获取长度,而在字符串中要使用length()方法获取长度,如s.length();
String中的charAt(i)方法可以获取第i个字符,下标从0开始。
字符串不是数组,但是一个字符串可以转换为数组,反之亦然。把字符串转换为字符数组可以用toCahrArray方法,字符数组转换为字符串可以用valueOf(char[])方法。
Java.lang.String中的静态方法valueOf被重载为多个版本,可以把char, double, long, int,float这些原子类型转换成字符串。
使用Double.parseDouble(str) , Integer.parseInt(str)方法可以把字符串转换为double类型、Int类型。
String类中包含一个静态方法format,可以格式化定义字符串,如
String s = String.format(“%5.2f”, 45.556);
创建的字符串为”45.56”。
Java为每个原子数据类型提供了对应的类,Character, Boolean, Byte, Short, Integer, Long, Float和Double分别对应char, Boolean, byte, short, int, long, float和double。
这些类中包含一些实用的方法。
StringBuilder/StringBuffer类是String类的另一个选择。StringBuffer多使用在多任务并发运行中,StringBuilder则在单任务时效率更高。StringBuffer和StringBuilder的构造器和方法几乎一致。使用StringBuilder/StringBuffer可以对字符串执行在末尾添加内容、随意位置插入、删除和替换字符等操作。
如果字符串不需要更改的话,使用String而不要使用StringBuilder。
StringBuilder的构造方法有:
StringBuilder() //创建一个空的string builder,容量是16
StringBuilder(capacity: int) //创建一个空的string builder,指定容量capacity
StringBuilder(s:String) //创建一个指定字符串的string builder。
StringBuilder中的capacity()方法返回string builder 当前的容量
StringBuilder中的length()方法返回string builder中字符串的实际长度。
当添加的内容将超出string builder的容量时,string builder的容量会自动增加。
内部结构中,string builder是一个字符数组,string builder的容量就是数组的大小,如果string builder的容量溢出,则数组会被新的数组代替,新的数组的长度是2 * (先前的数组长度+1)。
File类不包含读写的方法。
在写路径时因为反斜杠(\)在java中是特殊符号,所以要写成\\。
创建一个File实例并不会在机器中创建一个文件,我们可以为任何的路径创建File实例,无论该路径是否真实存在。我们可以用exists()方法判断一个File实例的文件是否存在,还可以用isFile和isDirectory()方法判断是文件还是目录。
在程序中不要使用绝对路径,应该使用相对路径,这样才能使程序兼容不同的机器。写路径时可以用正斜杠(/),这个写法跟Unix是相同的,如new File(“image/us.gif”)。
java.io.PrintWriter类可以用来创建文件和把数据写入文件。
PrintWriter的构造方法:
PrintWriter(file: File)
PrintWriter(filename: String)
可以调用PrintWriter对象的print, println和printf方法向文件中写入数据。
调用PrintWriter的构造器时,如果该文件不存在则会创建一个新的文件,如果该文件已存在,则该文件的原内容将被删去。
文件使用结束后必须调用close()方法,如果不调用该方法,写到文件的数据很可能没有被保存。
从控制台中输入数据的Scanner定义:
Scanner input = new Scanner(System.in);
从文件中输入数据的Scanner定义:
Scanner input = new Scanner( new File(filename)));
Scanner访问完文件后调用close()方法不是必须的,但是作为良好的习惯,我们应该调用close()方法释放对文件的占用。
nextByte(), nextShort(), nextInt(), nextLong(), nextFloat(), nextDouble(), next()是token-reading 方法,因为他们根据分隔符读取记号。默认的是以空格作为分隔符,可以通过useDelimiter-(Stringregex)方法设定新的分隔符。
P328 nextInt()与nextLine()连用时容易产生的错误。
Chapter10 Thinking in Objects
类中的实例变量和静态变量称为类的变量或数据域。方法中的变量称为局部变量。类的变量的可用范围是整个类,无论变量是在哪儿声明,除非一个变量是用另一个变量作初始化,此时被用作初始化的变量必须先声明。如:
public class Foo {
private int i;
private int j = i + 1; //i必须定义在j前边
}
一个类的变量只能声明一次,但是在方法中相同的名字可以多次定义在非嵌套的区域。
如果局部变量与类的变量拥有相同名字,则局部变量会被识别而相同名字的类的变量会被隐藏。如:
public class Foo {
private int x = 0; //实例变量
private int y = 0;
public Foo() {
}
public void p () {
int x = 1; //局部变量
System.out.println(“x = “ + x);
System.out.println(“y = “ + y);
}
}
结果为
x=1
y=0;
为了避免造成疑惑,不要使用实例变量或静态变量的名字作为局部变量的名字,方法的参数除外。
The this keyword is the name of a reference that refers to a calling object itself.
一种常用情况是用来引用类中隐藏的数据域,如:
public class Foo {
int i = 5; //实例变量i
void setI(int i) { //局部变量i
this.i = i; //实例变量i被隐藏,所以必须用this.i得到实例变量i的引用
}
}
另一种常用情况是使一个构造器能调用同一个类中其他的构造器。如:
public class Circle {
private double radius;
public Circle(double radius) {
this. radius = radius;
}
public Circle() {
this(1.0); //调用Circle(double radius)构造方法
}
public double getArea() {
return this. radius * this. radius * Math.PI;
}
}
如果一个类拥有多个构造器,应尽可能的采用this(arg-list)的形式。
通常没有或拥有更少参数的构造器能使用this(arg-list)调用拥有更多参数的构造器,这能简化编码和使类更简易阅读和维护。
java要求this(arg-list)语句必须出现在构造器中任何的语句之前。
过程方式注重方法的设计,而面向对象方式则对象的数据和方法都考虑。
P353~P355 组合和聚集
组合实际上是聚集关系的一种特殊案例。、
如果一个对象只能被一个聚集对象拥有,则两者之间的关系成为组合,如“一个学生拥有一个名字”,学生和名字是组合关系,而“一个学生拥有一个地址”,学生和地址是聚集关系,因为同一个地址可以对应多个学生。
P362类的设计指导
一个实体不应该拥有太多的职责。(例如,虽然String,StringBuilder和StringBuffer都是用来处理字符串,但是他们拥有不同的职责。String用来处理不变的字符串,StringBuilder用来创建可修改的字符串,StringBuffer也是用来创建可修改的字符串,但是StringBuffer用来处理修改字符串的同步处理的情况。)
一种受欢迎的编程风格是把数据声明定义在构造器之前,而构造器放在方法之前。
一个静态变量或方法能够被实例方法调用,但是一个实例变量或方法不能被静态方法调用。
Chapter11 Inheritance and Polymorphism
In Java terminology, a class C1 extended from another class C2 is called a subclass, and C2 is called a superclass. A superclass is also referred to as a parent class, or a base class, and a subclass as a child class, an extended class, or a derived class.
父类中private的数据域不能在类之外访问,所以它们在子类中不能直接的使用,它们只能通过set和get方法访问。
不是所有的 is-a 关系都使用继承模型,例如,一个正方形是一个矩形,但是我们不应该定义正方形类继承矩形类,因为他们没有什么应该继承的。我们应该定义正方形类继承形状类。对于类A继承类B, A应该比B包含更多的细节。
继承是用作为is-a关系建模, 不要盲目的仅仅为了方法重用而去继承类。例如,我们不可能把Tree类继承Person类,即使他们都拥有共同的属性高度和重量。子类和它的父类必须拥有is-a关系。
Java不允许多重继承,Java只允许从一个父类中直接继承,这种限制叫做单继承。如果使用extends关键字去定义一个子类,则只允许一个父类,多重继承可以用过接口实现。
super关键字可以用在两种地方:
1、调用父类的构造器。
2、调用父类的方法。
super关键字用作调用父类构造器时格式为:
super() 或 super(parameters);
super语句必须出现在子类构造器的第一行,使用super关键字是调用父类构造器唯一的方法,在子类中如果使用父类构造器的名字去调用会导致语法错误。super的使用例子:
public Circle4(double radius, Sting color, Boolean filled) {
super(color, filled);
this. radius = radiues;
}
构造器是用来构造一个类的实例。不像性质和方法,子类不会继承父类的构造器,它们只能使用super关键字去调用。
一个构造器能够调用一个重载的构造器(使用this关键字)或者父类的构造器(使用super关键字),如果没有显式的调用, 编译器会自动的添加super()作为构造器的第一条语句。如:
public ClassName() { public ClassName() {
// some statements =========== super();
} //some statements
}
public ClassName(double d) { public ClassName(double d) {
//some statements ========= super();
} // some statements
}
如果一个类是设计用来被继承的话,最好提供无参的构造器去避免编程时的错误,考虑如下代码:
public class Apple extends Fruit {
}
class Fruit {
public Fruit(String name) {
System.out.println(“Fruit’s constructor is invoked”);
}
}
因为Apple类中没有显式的定义构造器,所以Apple类默认的生成一个无参构造器,因为Apple是Fruit的一个子类,Apple的无参构造器自动地调用父类的构造器,相当于代码:
public class Apple extends Fruit {
Apple() {
super();
}
但是由于Fruit类中显式定义了public Fruit(String name)构造器,所以编译器没有为Fruit类添加无参构造器,于是Fruit类中不存在无参构造器,因此程序会编译出错。
使用super调用方法的格式:
super.method(parameters);
在子类中对父类的方法改写称为重写(override)。要重写一个方法,该方法必须使用与父类中的方法相同的名字、参数和返回类型。
子类中如果重写了一个方法,则只能通过super.method(parameters) 调用父类的该方法。
private方法不能被重写,如果一个子类的方法跟父类中的一个private方法具有相同的名字、参数和返回类型,这两个方法是完全没有联系的。
像实例方法一样,静态方法也能够被继承。但是,一个静态方法不能被重写。如果一个父类的静态方法在子类中被重复定义,则父类的方法会被隐藏,被隐藏的方法可以通过SuperClassName.staticMethodName()调用。
java中所有的类都是从java.lang.Object类中继承下来的。当定义一个类时如果没有指定的继承,则父类会被默认地指定为Object。例如,下边的两个类的定义是相同的:
熟悉Object类中的方法是很重要的,这样我们才能运用在我们的类当中。如toString方法就是Object中的一个实例方法。
Object类中的toString方法返回一个描述该对象的字符串,默认地,它返回的字符串由该对象对应的类名,接上一个@字符,再加上该对象的十六进制内存地址组成。
(使用System.out.print(object)能得到与调用默认的toString方法相同的结果。)
通常我们应该重写toString方法去返回一个描述该对象的字符串。
面向对象编程的三个支柱是封装(encapsulation),继承(inheritance)和多态(polymorphism)。
用子类定义的类型称为子类型(subtype),用父类定义的类型称为父类型(supertype)。
所有子类的实例也是其父类的实例,但是反之不然。因此我们能够把一个子类的实例传给父类类型的参数。
一个子类的对象能够用在其父类对象使用的任何地方,这就是多态。
简单的说,多态就是指父类型的变量能够指定一个子类型对象。
(个人理解为,一个父类型可以根据指定的子类型对象而确定其实际类型(形态),故称为多态(简单理解为一个父类型可以根据实际使用时拥有多种形态))。
一个变量必须声明一个类型,这个变量的类型称为声明类型。
变量的实际类型是指引用变量指向的对象的实际类。
考虑以下代码:
Object o = new GeometricObject();
System.out.println(o.toString());
这里o的声明类型是Object,实际类型是GeometricObject,调用哪个toString()方法是由o的实际类型决定,这叫做动态绑定。
动态绑定的工作过程如下:
假设对象o是C1,C2,C3,…,Cn-1和Cn的实例,而C1是C2的子类, C2是C3的子类,…,Cn-1是Cn的子类,如下图所示:
Cn是最概括的类,而C1是最明确的类。在Java中,Cn是Object类。如果o调用一个方法p, JVM会按C1,C2,…,Cn-1和Cn的顺序搜索在这些类中实现的方法p,直到被找到为止。一旦一个实现被找到,搜索将会结束,并且第一个找到的实现方法会被调用。(例如o的实际类型是C2,则会只调用C2中的方法p)
动态绑定的出现需要三个条件:
1、继承
2、重写
3、父类引用指向子类对象
匹配一个方法(关于重载)和绑定一个方法的实现(关于重写)是两个分开的事件。在编译阶段,声明的引用变量的类型决定哪个方法将被匹配,编译器根据参数类型、参数个数和参数的顺序来匹配一个方法。而一个方法可能会被多个子类实现,JVM会在运行时动态的绑定一个方法的实现,绑定的方法由变量的实际类型决定。
把一个子类实例强制转换为父类的变量总是可以的,因为子类的实例总是父类的实例,而且这种强制转换将会自动转换,如:
m(new Student()); //把Student的一个实例传给方法m
等同于:
Object o = new Student();
m(o);
这里Student类型会自动强制转换为Object类型。
当把一个父类实例强制转换为子类变量时必须有(SubclassName)标记显式指明强制转换,转换时必须要确保该对象能转换为子类对象。如果父类对象不是子类的一个实例,运行时会抛出ClassCastExcepation异常。
我们可以用instanceof关键字判断一个对象是否为一个类的实例如:
if(myObject instanceof Circle) {
//some statement
}
Java的关键字都是小写的。
为了能够一般化编程,把变量定义为可以接收任何子类型的父类型是一个好的惯例。
对象成员访问操作符(.)优先于强制转换操作符,所以要加上括号确定运算顺序,如:
((Circle)object).getArea());
Object类的equals方法的实现默认如下:
public Boolean equals(Object obj) {
return (this == obj);
}
这个实现是判断两个引用变量的值是否相等,我们应该重写该方法去判断我们自己写的类的对象是否相等。
重写equals方法时常见的错误是写成equals(SomeClassName obj)),我们应该使用equals(Object obj)。
java提供ArrayList类去存储数量没有限制的对象(P390~P393)
继承是模拟is-a关系,组合是模拟has-a关系,对于书中MyStack(P393)类的实现我们可以把它作为ArrayList的子类,但是我们使用组合更好,因为这样能使我们定义一个新的栈类而不需要继承ArrayList中不必要的方法。
修饰符的可见范围
类中
包内
子类
不同的包
public
√
√
√
√
protected
√
√
√
-
(default)
√
√
-
-
private
√
-
-
-
(default与protected感觉是相同的?)
类能用作两个用途:用来创建实例和用来定义子类。如果类中的成员不打算对其他类可见,则把它们设定为private;如果这些成员要对其他使用者可见则设定为public;如果这些成员只想被继承该类的子类可见则设定为protected。
private和protected只能修饰类中的成员,而public和default能用来修饰类的成员和类。
子类重写方法时可以增加方法的可见度,但不能削弱。例如在父类中一个方法定义为public,则其子类中重写该方法时只能是定义为public。
如果要使一个类不能被继承可以添加final修饰符,例如Math类就是定义为final类。
我们也能把一个方法定义为final以使该方法不能被子类重写。
修饰符都是使用在类成员或类,但是fianl可以用来修饰局部变量,final用来修饰局部变量时该变量作为方法中的常量。
- 《java语言程序设计》笔记(二)
- C语言程序设计笔记(二)
- 《c++语言程序设计》笔记(二)
- 《java语言程序设计基础篇》笔记二
- 《java语言程序设计》笔记(一)
- 《java语言程序设计》笔记(三)
- JAVA语言程序设计二
- 《JAVA语言程序设计》(二)
- 《JAVA语言程序设计》上课笔记
- 《Java语言程序设计(基础篇)》读书笔记(二)
- chapter1(JAVA语言程序设计)
- Java语言程序设计(序)
- 《java语言程序设计》(第二版)——笔记(一)
- Java语言程序设计(基础篇)- 笔记(持续更新中)
- c语言程序设计入门(二)
- C语言程序设计笔记(一)
- C语言程序设计笔记(三)
- C语言程序设计笔记(四)
- linux内核之文件系统
- JavaScript语言基础知识点图示(转)
- 面向对象----浅述对象之间的关系
- xcode5下资源本地化详解
- Mysql启动关闭
- 《java语言程序设计》笔记(二)
- js实现类似php中strtotime函数和timetostr的日期转换/互换功能
- PHPer必看的几份文档
- AM335X开发板产品板IAC-335X-Kit
- 工作中自我管理
- Failed to load session “ubuntu”
- SQL中内连接和外连接
- CArchive 详解
- 联想笔试2011,顺时针输出矩阵