《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());newCircle(5)对象是一次性使用的,没有引用变量指向该对象,所以称为匿名对象。

 

null是引用变量的一个值,如truefalseBoolean类型的值一样。

引用变量初始化为null

 

Java不会为局部变量指定默认值。

 

NullPointerException是常见的运行时错误,当使用一个值为null的引用变量调用方法时会发生该错误,所以当调用方法时,必须确保确保引用变量已经指向一个对象。

 

当一个对象不再有引用变量指向其时,Java的垃圾收集程序将会回收该内存。

 

如果一个对象确定不再需要时,可以使指向其的引用变量赋值null,当没有引用变量指向该对象时,JVM会自动地回收该对象的空间。

 

P274 java.util.Date类(创建该对象后可用getTimetoString方法获取时间)

 

P275 java.util.Random类 (创建该对象后可以用对应的nextInt()等方法获取intlongdoublefloatboolean类型的随机值)

使用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)

 

我们可以在setget方法中添加对数据的限制,如我们只想把变量设为正数,则我们可以再set方法中先判断输入的数据是否符合要求。在set方法和get方法中添加限制代码可以简化其他类中的重复限制代码,使用private修饰、set方法、get方法不仅提高类的封装性,还提高了类的可维护性。

 

我们可以传递一个对象给方法,同数组一样,我们传递的是一个指向该对象的引用,如果我们对该对象修改将会是真正的修改。

 

Java只用一种参数传递模型:pass-by-value(值传递),如果形参是一个引用变量,我们修改形参所指向的对象将会有实则上的修改,但如果是对形参进行赋值,则同原始数据类型的变量形参一样不会改变实参。

如有

public static void testCircle(Circle3 c) {

  c.setRadius(1); //会修改c指向的对象的半径

  c = null;  //只修改形参cnull,不会修改实参的值。

}

 

我们可以创建对象数组,没有赋值的对象数组初始化为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包含的引用值是不同的,即使他们指向的对象包含的字符串内容相同。

 

可以用Stringequals实例方法判断两个字符串是否相等(如果用“==”判断的话仅仅是判断引用值是否相等,如上边例子一样,即使字符串内容相同但引用值未必相同)。

 

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,  FloatDouble分别对应char, Boolean, byte, short, int, long, floatdouble

这些类中包含一些实用的方法。

 

StringBuilder/StringBuffer类是String类的另一个选择。StringBuffer多使用在多任务并发运行中,StringBuilder则在单任务时效率更高。StringBufferStringBuilder的构造器和方法几乎一致。使用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实例的文件是否存在,还可以用isFileisDirectory()方法判断是文件还是目录。

 

在程序中不要使用绝对路径,应该使用相对路径,这样才能使程序兼容不同的机器。写路径时可以用正斜杠(/),这个写法跟Unix是相同的,如new File(“image/us.gif”)

 

java.io.PrintWriter类可以用来创建文件和把数据写入文件。

PrintWriter的构造方法:

PrintWriter(file: File)

PrintWriter(filename: String)

可以调用PrintWriter对象的print, printlnprintf方法向文件中写入数据。

调用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类的设计指导

一个实体不应该拥有太多的职责。(例如,虽然StringStringBuilderStringBuffer都是用来处理字符串,但是他们拥有不同的职责。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的数据域不能在类之外访问,所以它们在子类中不能直接的使用,它们只能通过setget方法访问。

 

不是所有的 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类默认的生成一个无参构造器,因为AppleFruit的一个子类,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的实际类型决定,这叫做动态绑定。

 

动态绑定的工作过程如下:

假设对象oC1,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

-

-

-

defaultprotected感觉是相同的?

 

类能用作两个用途:用来创建实例和用来定义子类。如果类中的成员不打算对其他类可见,则把它们设定为private;如果这些成员要对其他使用者可见则设定为public;如果这些成员只想被继承该类的子类可见则设定为protected

 

privateprotected只能修饰类中的成员,而publicdefault能用来修饰类的成员和类。

 

子类重写方法时可以增加方法的可见度,但不能削弱。例如在父类中一个方法定义为public,则其子类中重写该方法时只能是定义为public

 

如果要使一个类不能被继承可以添加final修饰符,例如Math类就是定义为final类。

 

我们也能把一个方法定义为final以使该方法不能被子类重写。

 

修饰符都是使用在类成员或类,但是fianl可以用来修饰局部变量,final用来修饰局部变量时该变量作为方法中的常量。


0 0