类和面向对象
面向对象程序设计(OOP) 就是使用对象进行程序设计。对象(object) 代表现实世界中可以明确标识的一个实体。
一个对象的狀态(state,也称为特征(property) 或属性(attribute)) 是由具有当前值的数据域来表示的。— 个对象的行为(behavior, 也称为动作(action))是由方法定义的。调用对象的一 个方法就是要求对象完成一个动作。
使用一个通用类来定义同一类型的对象。类是一个模板、蓝本或者说是合约,用来定义对象的数据域是什么以及方法是做什么的。一个对象是类的一个实例。可以从一个类中创建 多个实例。创建实例的过程称为实例化(instantiation)。
Java 类使用变量定义数据域,使用方法定义动作。除此之外,类还提供了一种称为构造方法(constructor) 的特殊类型的方法,调用它可以创建一个新对象。构造方法本身是可以完成任何动作的,但是设计构造方法是为了完成初始化动作。
构造方法在使用 new 操作符创建对象的时候被调用。
构造方法是一种特殊的方法。它们有以下三个特殊性:
- 构造方法必须具备和所在类相同的名字。
- 构造方法没有返回值类型,甚至连 void 也没有。
- 构造方法是在创建一个对象使用 new 操作符时调用的。构造方法的作用是初始化对象。
和所有其他方法一样,构造方法也可以重 (也就是说,可以有多个同名的构造方法,但它们要有不同的签名),这样更易于用不同 的初始数据值来构造对象。
通常,一个类会提供一个没有参数的构造方法(例如:Circle() )。这样的构造方法称为无参构造方法(no arg 或 no argument constructor)。— 个类可以不定义构造方法。在这种情况下,类中隐含定义一个方法体为空的无参构造方法。这个构造方法称为默认构造方法(default constructor),当且仅当类中没有明确定义任 何构造方法时才会自动提供它。
对象是通过对象引用变量(reference variable)来访问的,该变量包含对对象的引用, 用如下语法格式声明这样的变量:
ClassName objectRefVar;
本质上来说,一个类是一个程序员定义的类型。类是一种引用类型(reference type), 意味着该类类型的变量都可以引用该类的一个实例。采用如下所示的语法,可以写一条包括声明对象引用变量、创建对象以及将对象的引用賦值给这个变量的语句。
ClassName objectRefVar = new ClassName();
数据域也可能是引用型的。例如:下面的 Student 类包含一个 String 类型的 name 数据 ,String是一个预定义的 Java 类。
class Student {
String name; // has default value null
int age; // has default value 0
boolean isScienceMajor; // has default value false
char gender; // has default value '\u0000'
}
如果一个引用类型的数据域没有引用任何对象,那么这个数据域就有一个特殊的 Java 值null 。null 同true和false — 样都是一个直接量。true和false是boolean 类型直接量, null 是引用类型直接量。
引用类型数据域的默认值是 null, 数值类型数据域的默认值是 0, boolean 类型数据域的默认值是false, 而 char 类型数据域的默认值是 ’ \u0000‘。但是,Java 没有给方法中的局部变置赋默认值。
基本类型变量和引用类型变量的区别
每个变量都代表一个存储值的内存位置。声明一个变量时,就是在告诉编译器这个变量可以存放什么类型的值。对基本类型变量来说,对应内存所存储的值是基本类型值。对引用类型变量来说,对应内存所存储的值是一个引用,是对象的存储地址。
将一个变量赋值给另一个变量时,另一个变量就被赋予同样的值。对基本类型变量而言 ,就是将一个变量的实际值赋给另一个变量。对引用类型变量而言,就是将一个变量的引用赋给另一个 变量。
Java库中的类
Date类
除了使用 System.currentTimeMillis() 来获得当前 时间。Java在java.util.Date 类中提供了与系统无关的对日期和时间的封装。
Random类
除了使用Math.random() 获取一个 0.0到1.0(不包括1.0) 之间的随机 double 型值。还可以使用java.util.Random ,产生一个 int、long、double、float和boolean 型值。
静态变量,常量和方法
如果想让一个类的所有实例共享数据,就要使用静态变量(static variable), 也称为类变 ( class variable)。静态变量将变量值存储在一个公共的内存地址。因为它是公共的地址, 所以如果某一个对象修改了静态变量的值,那么同一个类的所有对象都会受到影响。Java 支持静态方法和静态变量,无须创建类的实例就可以调用静态方法(static method)。
要声明一个静态变量或定义一个静态方法,就要在这个变量或方法的声明中加上修饰符 static。静态变量 numberOfObjects 和静态方法 getNumberOfObjects 可以如下声明:
static int numberOfObjects;
static int getNumberObjects() {
return numberOfObjects;
}
类中的常量是被该类的所有对象所共享的。因此,常量应该声明为 final static, 例如,Math 类中的常量 PI 是如下定义的:
final static double PI = 3.14;
静态方法(例如: getNumberOfObjects ) 和静态数据(例如:numberOfObjects) 可以通过引用变量或它们的类名来调用。使用 “类名.方法名(参数)” 的方式调用静态方法,使用 “类名.静态变量” 的方 式访问静态变量。这会提高可读性,因为可以很容易地识别出类中的静态方法和数据。
可见性修饰符
可见性修饰符可以用于确定一个类以及它的成员的可见性。
可以在类、方法和数据域前使用 public 修饰符,表示它们可以被任何其他的类访问。 如果没有使用可见性修饰符,那么则默认类、方法和数据域是可以被同一个包中的任何一个 类访问的。这称作包私有(package private)或包内访问(packageaccess )。
包可以用来组织类。为了完成这个目标,需要在程序中首先出现下面这行语句, 这行语句之前不能有注释也不能有空白:
package packageName;
如果定义类时没有声明包,就表示把它放在默认包中。 Java 建议最好将类放入包中,而不要使用默认包。
private 修饰符限定方法和数据域只能在它自己的类中被访问。
修钸符 private 只能应用在类的成员上。修饰符 public 可以应用在类或类的成员 。在局部变量上使用修饰符 public private 都会导致编译错误。
大多数情况下,构造方法应该是公共的。但是,如果想防止用户创建类的实例, 该使用私有构造方法。
将數据域设为私有保护數据,并且使类易于维护。为了避免对数据域的直接修改,应该使用 Private 修饰符将数据域声明为私有的,这称 为數据域封装(data fied encapsulation)。
可以将对象传递给方法。同传递数组一样,传递对象实际上是传递对象的引用。
数组既可以存储基本类型值,也可以存储对象。对象的数组实际上是引用变量的数组。
可以定义不可变类来产生不可变对象。不可变对象的内容不能被改变。
如果一个类是不可变的,那么它的所有数据域必须都是私有的,而且没有对任何一个数 据域提供公共的 set 方法,并且没有一个返回指向可变数据域的引用的访问器方法。
— 个类的实例变量和静态变量称为类变量(class’s variables)或教据域( data field)。 方法内部定义的变量称为局部变量。无论在何处声明,类变量的作用域都是整个类。类的变量和方法可以在类中以任意顺序出现。但是当一个数据域是基于对另一个 数据域的引用来进行初始化时则不是这样。在这种情况下,必须首先声明另一个数据域。
类变量只能声明一次,但是在一个方法内不同的非嵌套块中,可以多次声明相同的变 量名。如果一个局部变量和一个类变量具有相同的名字,那么局部变量优先. 而同名的类变量将被隐藏(hidden)
为避免混淆和错误,除了方法中的参數,不要将实例变量或静态变量的名字作为局 部变量名。
关键字 this 是指向调用对象本身的引用名。可以用 this 关键字引用对象的实例成员。
它也可以在构造方法内部用于调用同一个类的其他构造方法(this(arg))。Java 要求在构造方法中,语句 this( 参数列表)应在任何其他可执行语句之前出现。如果一个类有多个构造方法,最好尽可能使用 this( 参数列表)实现它们。通常,无参數或参数少的构造方法可以用 this( 参数列表)调用参数多的构造方法。这样做通常可以简化代码,使类易于阅读和维护。
this 关键字可以用于引用类的隐藏數据域。例如,在数据域的 set 方法中,经常将数据域名用作参数名。在这种情况下,这个数据域在 set 方法中被隐藏。为了给它设置新值, 需要在方法中引用隐藏的数据域名。隐藏的静态变量可以简单地通过 “类名.静态变量” 方式引用。隐藏的实例变量就需要使用关键字 this 来引用。
面向对象的思考
面向过程的范式重点在于设计方法。面向对象的范式将数据和方法耦合在一起 构成对象。使用面向对象范式的软件设计重点在对象以及对对象的操作上。
为了设计类,需要探究类之间的关系。类中间的关系通常是关联、聚合、组合 以及继承。
关联是一种常见的二元关系,描述两个类之间的活例如,学生选取课程是 Student 类和 Course 类之间的一种关联,而教师教授课程是 Faculty 类和 Course 类之间的关联。关联中涉及的每个类可以给定一个多重性(multiplicity),每个学生可以选取任意数量的课程数,每门课程可以有至少 5 个最多 60 个学生。每门课程只由一位教师教授,并且每位教师每学期可以教授 0 到 3门课程。
聚集是关联的一种特殊形式,代表了两个对象之间的归属关系。聚集建模 has-a 关系。 所有者对象称为聚集对象,它的类称为聚集类。而从属对象称为被聚集对象,它的类称为被 聚集类。— 个对象可以被多个其他的聚集对象所拥有。如果一个对象只归属于一个聚集对象,那么它和聚集对象之间的关系就称为组合(composition)。例如:“一个学生有一个名字” 是学生类 Student 与名字类 Name 之间的一个组合关系, “一个学生有一个地址” 是学生 Student 与地址类 Address 之间的一个聚集关系,因为一个地址可以被几个学生所共享。
设计栈类
public class StackOfIntegers {
private int[] elements;
private int size;
public static final int DEFAULT_CAPACITY = 16;
public StackOfInteger() {
this(DEFAULT_CAPACITY);
}
public StackOfInteger(int capacity) {
elements = new int[capacity];
}
public void push(int value) {
if (size >= elements.length) {
int[] temp = new int[elements.length * 2];
System.arrcopy(elements, 0, temp, 0, elements.length);
elements = temp;
}
elements[size++] = value;
}
public int pop() {
return elements[--size];
}
public int peek() {
return elements[size - 1];
}
public boolean empty() {
return size == 0;
}
public int getSize() {
return size;
}
}
将基本数据类型值作为对象处理
基本数据类型值不是一个对象,但是可以使用 Java API中的包装类来包装成一 个对象。Java 为基本数据类型提供了 Boolean、Character、Double、Float、 Byte、Short、Integer和 Long 等包装类。这些包装类都打包在 java.lang 包里。
数值包装类相互之间都非常相似。每个都包含了doubleValue() 、floatValue() , intValue()、longValue()、shortValue()、byteValue()方法。这些方法将对象 “转换” 为基本类型值。
既可以用基本数据类型值也可以用表示数值的字符串来构造包装类。
数值包装类有一个有用的静态方法 valueOf(String s)。该方法创建一个新对象,并将它初始化为指定字符串表示的值。
Double doubleObject = Double.valueOf("12.4");
Integer (Double) 类中的 parselnt (parseDouble) 方法将一个数值字符串转换为一个 int(Double) , 每个 数值包装类都有两个重载的方法,将数值字符串转换为正确的以 10(十进制)或指定值为基 (例如,2 为二进制,8为八进制,16 为十六进制)的数值。
将基本类型值转换为包装类对象的过程称为装箱(boxing), 相反的转换过程称为开箱 ( unboxing)。Java 允许基本类型和包装类类型之间进行自动转换。
Integer[] intArray = {1, 2, 3};
System.out.println(intArray[0] + intArray[1] + intArray[2]);
// 在第一行中,基本类型值1、2、3 被自动箱成对象new Integer(1)、new Integer(2) new Integer(3)。第二行中,对象 intArray[0]、intArray[1] intArray[2]被自动转换为int ,然后进行相加。
Biglnteger 和BigDecima类
如果要进行非常大的数的计算或者高精度浮点值的计算,可以使用 java.math 包中的 Biglnteger 类和 BigDecimal 。它们都是不可变的。
可以使 new Biglnteger(String)和new BigDecimal(String)来创建 Biglnteger和BigDecimal 的实例,使用 add、subtract、multiple、divide和remainder 方法完成算术运算,使用 compareTo 方法比较两个大数字。
BigDecimal 对象的精度没有限制。如果结果不能终止,那么divide 方法会抛出 ArithmeticException 异常。但是,可以使用重载的 divide(BigDecimal d, int scale, int roundingMode)方法来指定尺度和舍入方式来避免这个异常,这里的 scale 是指小数点后最 小的整数位数。例如,下面的代码创建两个尺度为 20、舍入方式为 BigDecimal .R0UND_UP的BigDecimal 对象。
BigDecimal a = new BigDecimal(1.0);
BigDecimal b = new BigDecimal(3);
BigDecimal c = a.divide(b, 20, BigDecimal.ROUND_UP);
System.out.println(c); // 输出0.33333333333333333334
String类
String 对象是不可改变的。字符串一旦创建,内容不能再改变。String 类中有 13 个构造方法以及 40 多个处理字符串的方法。
// 构造字符串
// 用字符串直接量创建一个字符串:
String message = new String("Welcome to Java");
// Java 将字符串直接量看作 String 对象:
String message = "Welcome to Java"; // 合法的
// 还可以用字符数组创建一个字符串:
char[] charArray = {'G','o', 'o' , 'd' , ' ' , 'D', 'a', 'y'};
String message = new String(charArray);
String 变量存储的是对 String 对象的引用,String 对象里存储的才是字符串的 。严格地讲,术语 String 变量、String 对象和字符串值是不同的。但在大多教情况下,它们之间的区别是可以忽略的。
字符串在程序设计中是不可变的,但同时又会频繁地使用,所以 Java 虚拟机为了 提高效率并节约内存,对具有相同字符序列的字符串直接量使用同一个实例。这样的实例称为限定的(interned)字符串。
String s1 = "Welcome to Java";
String s2 = new String("Welcome to Java");
String s3 = "Welcome to Java";
// s1 != s2
// s1 == s3
字符串的替换,分隔和匹配
String类提供字符串替换,分隔和匹配方法:replace(oldChar: char, newChar: char): String;
replaceFirst(oldString: String, newString: String): String;
replaceAll(oldString: String, newString: String): String;
split(delimiter: String): String[];
match(regr: String): boolean
字符串不是数组,但是字符串可以转换成数组,反之亦然。为了将字符串转换成一个字符数组,可以使用 toCharArray 方法。
char[] chars = "Java".toCharArray();
还可以使用方法 getChars(int srcBegin, int srcEnd, char[] dst,int dstBegin)将下标从 srcBegin 到 srcEnd - 1的子串复制到字符数组 dst 中下标从 dstBegin 开始的位置。
为了将一个字符数组转换成一个字符串,应该使用构造方法 String(Char[]) 或者方法 valueOf(char[])。
String str = new String(new char[]{'J', 'a', 'v', 'a'});
String str = String.valueOf(new char[]{'J', 'a', 'v', 'a'});
可以使用字符串的连接操作符来将字符或者数 字转换为字符串。另外一种将数字转换为字符串的方法是使用重载的静态 valueOf 方法。 该方法可以用于将字符和数值转换成字符串。例如:
String.valueOf(5.44);
将double值5.44转换为字符串。
String 类包含静态 format 方法,它可以创建一个格式化的字符串。调用该方法的语法是:
String.format(format, item1, item2, ..., itemk)
String s = String.format("%7.2f%6d%-4s", 45.556, 14, "AB");
System.out.println(s);
StringBuilder StringBuff类
— 般来说,只要使用字符串的地方,都可以使用StringBuilder/StringBuffer类。 StringBuilder/StringBuffer 类比 String类更灵活。可以给一个 StringBuilder 或StringBuffer 中添加、插入或追加新的内容,但是 String 对象一旦创建,它的值就确定了。
除了 StringBuffer 中修改缓冲区的方法是同步的,这意味着只有一个任务被允许执 行方法之外,StringBuilder 类与 StringBuffer 类是很相似的。
StringBuilder 类有 3 个构造方法和 30多个用于管理构建器或修改构建器内字符串的方 。可以使用构造方法创建一个空的构建器或从一个字符串创建一个构建器。
StringBuilder 类提供了几个重载方法,可以将 boolean、char、char 数组、double、 float、int、long 和String 类型值追加(append)到字符串构建器,也可以从构建器中插入(insert),设置(setCharAt),替换(replace),删除(delete, deleteCharAt)字符串,还可以反转(reverse)字符串。
StringBuilder 类提供了许多其他处理字符串构建器和获取它的属性的方法,toString()返回一个字符串对象,capacity()返回构建器容量,charAt(index)返回指定索引位置字符,length()返回该构建器中的字符数,setLength(newLength: int) 设置构建器的新的长度,substring(startIndex, [endIndex])返回切片的子字符串;trimToSize()减少用于字符串构建器的存储大小。
字符串的长度总是小于或等于构建器的容量。长度是存储在构建器中的字符串的实 际大小,而容量是构建器的当前大小。如果有更多的字符添加到字符串构建器,超出它的 容量,则构建器的容量就会自动增加。通过仔细选择初始容量能够使程序更有效。如果容量总是超过构建器的实际使用长度, JVM 将永远不需要为构建器重新分配内存。另一方面,如果容量过大将会浪费内存空间。 可以使用 trimToSize() 方法将容量降到实际的大小。