1. 继承
(1) 继承格式
class Manager extends Employee{
}
Java用关键字代替C++中的:,在Java中,所有的继承都是公有继承,而没有C++中的私有继承和保护继承。
(2) 子类不能直接访问超类的私有成员,调用超类的方法时,可以使用关键字super,如super.getSalary()。this和super不是类似的概念,因为super不是对象的引用,不能将super赋值给一个对象变量,它只是一个指示编译器调用超类方法的特有关键字。
C++中调用超类的方法为超类名加上::操作符,如Employee::getSalary。
(3) super可以用来调用超类的构造器,如super(n, s, month, day);使用super调用构造器的语句必须是子类构造器的第一条语句。如果子类的构造器没有显示地挑用超类的构造器,则将自动地调用超类默认(没有参数)的构造器。如果此时超类没有默认的构造器,则Java编译器将报告错误。
在C++中,使用初始化列表语法调用超类的构造函数,而不调用super,如:
Manager::Manager(String n, double s):Employ(n, s){
bonus=0;
}
(4) 一个对象变量可以引用多种实际类型(如Employee对象引用Employee和Manager对象),在运行时它能够自动地选择调用的是适当的方法。Java不支持多继承。可以将一个子类对象的引用赋值给一个超类变量,但是不能用这个引用调用子类的方法,不能将一个超类的引用赋给子类变量。
(5) Manager[] managers=new Manager[10];
staff[0]=new Employee(…);编译器能够接纳这个赋值操作,此时,staff[0]和managers[0]引用的是用一个对象,当调用managers[0].setBonus(1000)的时候,将会导致访问一个不W在的实例域,进而搅乱邻近存储空间的内容。W
(6) 过程在重载解析时,由于存在类型转化(如int可以转化为double型,Manager可以转化成Employee等),所以,如果编译器没有找到与参数类型匹配的方法,或者发现经过类型转化后又多个方法与之匹配,就会报告错误。如果在子类中定义了一个与超类签名相同的方法,那么子类中的这个方法就覆盖了超类的这个同签名的方法,但是子类方法的返回类型必须是原返回类型的子类型。如:
父类:public Employee getBuddy(){
…
}
子类:public Manager getBuddy(){
…
}
(6) 动态绑定
如果是private、static、final或者构造器,编译器可以准确地知道应该调用哪个方法即静态绑定。采用动态绑定时,虚拟机一定调用与x所引用对象的实际类型最合适的那个类方法。如果调用super.f(param),编译器将对隐式参数超类的方法表进行搜索。
Employee e;
当e引用的是它的子类的一个实例时,如果子类覆盖了父类的方法,则用e调用这个方法时,调用的将是相应子类的方法。
2. 阻止继承:final类和final方法
(1) 声明格式如:
final class Executive extends Manager{
…
}
final类不允许扩展,其中的方法自动成为final方法,不包括域。对于final域,创建对象后酒不孕需改变它们的值。类中的方法可以被声明为final,子类就不能覆盖这个方法。
(2) 如果一个方法没有被覆盖并且很短,编译器就能够对它进行优化处理及内联,如e.getName()被替换为e.name。
3. 将一个子类的引用赋值给一个超类变量编译器是允许的,但是将一个超类的引用赋值给一个子类变量,必须进行强制转换。在进行类型转换之前,先察看以下是否能够成功地转化,如:
if(staff[1] instanceof Manager){
boss=(Manager) staff[1];
}
如果x为null,那么 x instanceof C不会产生异常,而是返回false。
其处理过程类似于C++中的dynamic_cast操作,如:
Manager boss=(Manager) staff[1];等价于
Manager * boss=(dynamic_cast)<Manager *>(staff[1]);
当类型转化失败时,Java不会生成一个null对象,而是抛出一个异常。
4. 抽象类
(1) 格式如:
abstract class Person{
public abstract String getDescription();
}
抽象类除了抽象方法之外,还可以包含具体数据和具体方法。在抽象类的子类中,仍然可以不定义抽象方法,而仍保持抽象性。即使不含抽象方法,也可以将类声明为抽象类。抽象类不能被实例化,但可以定义一个抽象类的对象变量,但是它只能饮用非抽象子类的对象,如:
Person p=new Student(“Vince Vu”);
在C++中,只要有一个纯虚函数,这个类就是抽象类,没有提供用于表示抽象类的特殊关键字。纯虚函数的表示方法如:
class Person{
public:
public string getDescription()=0;
}
(2) 由于不能构造抽象类Person的对象,所有变量p永远不会指向Person对象,而是引用诸如Employ这样的具体子类对象。如果Person超类中不定义抽象函数getDescription,则p就不能调用该方法了。
5. Java用于控制可见性的4个访问修饰符
1) 仅对本类可见:private
2) 对所有类都可见:public
3) 对本包和所有子类都可见:protected
4) 对本报可见:默认。
6. Object类型的变量只能用于作为各种值得通用持有者,要想对他们进行特定操作,需要清楚对象的原始类型,并进行相应得类型转换:Employee e=(Employee) obj.
Java中,只有基本类型不是对象,而所有的数组类型都扩展于Object类的类型,如:
Employee[] staff=new Employee[10];
obj=staff;
obj=new int[10];
在C++中,没有根类,但是每个指针都可以转化为void。
7. Object
(1) equals:在Object类中,这个方法将判断两个对象是否具有相同的引用。
class Employee{
public boolean equals(Object otherObject){
if(this==otherObject) return true;
if(otherObject==null) return false;
if(getClass()!=otherObject.getClass())
return false;
Employee other=(Employee)otherObject;
return name.equals(other.name) && salary==other.salary
&& hireDay.equals(other.hireday)
}
}
getClass方法返回一个对象所属的类。比较子类是否相等的方法:
class Manager extends Employee{
...
public boolean equals(Object otherObject){
if(!super.equals(otherObject)) return false;
Manager other=(Manager) otherObject;
return bonus==other.bonus;
}
}
一个完美的equals方法的建议
(1) 检测this和otherObject是否引用同一个对象
if(this==otherObject) return true;
(2) 检测otherObject是否为null
if(otherObject==null) return false;
比较this与otherObject是否属于同一个类,如果equals的语义在每个类种有所改变,则使用getClass检测,如:
if(getClass()!=otherObject.getClass()) return false;
如果所有的子类都拥有同一的语义,就是用instanceof检测,如:
if(!(otherObject instanceof ClassName)) return false;此时,可以将这个equals方法设置为final,因为它有超类确定相等概念,子类不拥有自己的相等概念。
(3) 将otherObject转换位相应得类型变量,对所需要比较的域进行比较,如:
ClassName other=(ClassName) otherObject;
return filed=other.fild&&field.equals(other.field)…)
如果子类重新定义equals,就要在其中包含调用super.equals(other)。
使用@Override对覆盖超类的方法进行标记,如:
@Override public Boolean equals(Object other)
如果子类定义了一个新的方法,编译器就会给出错误报告。
(2) hashCode方法:
散列码:由对象导出的一个整形数。
String类的散列码计算方法:
int hash=0;
for(int i=0;i<length();i++){
hash=31*hash+charAt(i);
}
String s=”OK”;
StringBuffer sb=new StringBuffer(s);
System.out.println(s.hashCode()+” ”+sb.hashCode());
String t=new String(“OK”);
StringBuffer tb=new StringBuffer(t);
System.out.println(t.hashCode() + “” + tb.hashCode());
s和t拥有相同的散列码,缓冲sb和tb却有着不同的散列码,因为StringBuffer类中没有定义hashCode方法,它的散列码是由Object类的默认hashCode方法导出的对象存储地址。
equals与hashCode的定义必须一致,如果x.equals(y)返回true,那么x.hashCode就必须与y.hashCode具有相同的值。
(3) toString()
绝大多数(但不是全部)的toString方法都遵循这样的格式:类的名字,随后是一对方括号括起来的域值,如Employee的toString的实现:
public String toString(){
return getClass().getName()
+”[name=” +name
+”,salary=”+salary
+ “,hireDay=”+hireDay
+ “]”;
}
其中,getClass().getName()得到类名的字符串。
子类可以通过调用super.toString()来写自己的toString方法。随处可见toString方法的原因是:只要对象与一个字符串通过“+”链接起来,Java编译就会自动调用toString方法,以便获得这个对象的字符串描述。在调用x.toString()的地方可以用“”+x替代。System.out.println(x);println方法将直接调用x.toString()方法。
Object类定义的toString方法,用来打印数出对象所属的类名和散列码,如System.out.println(System.out);输出内容为:java.io.PrintStream@2f6684
(4) Class getClass() 返回包含对象信息的类对象,Java提供了类运行时的描述,它的内容被封装在Class类中。
Class类的两个方法为:getName()返回该类的名字,getSupperclass()返回该类的超类信息。
(5) Object clone() 创建一个对象的副本。
8. 泛型数组列表:
(1) 在Java中,允许在运行时确定数组的大小,如:
int actualSize= …;
Employee[] staff=new Employee[actualSize];
(2) 构造一个保存Employee对象的数组类表:ArrayList<Employee> staff=new ArrayList<Employee>();
在5.0后,没有后缀<…>仍然可以使用,它将被认为是一个删去了类型参数的“原始”类型。
ArrayList比Vector更加有效。
数组列表的容量与数组的大小有一个非常重要的区别,数组具有实际的空间,而数组列表只是由相应空间的潜力,完成初始化构造之后,数组列表可能不含有任何元素。
C++中的赋值操作是值拷贝,而Java是引用拷贝。
常用方法:
<1> 构造方法
构造一个空数组列表 ArrayList<T>()
构造一个指定容量的空数组列表 ArrayList<T> (int initialCapacity)
<2> 添加元素 add(T obj)
add(int index, T obj);在指定索引处插入值
<3> 设置指定索引处的元素 void set(int index, T value);
<4> 获得指定索引处的元素值 T get(int index);
<5> 删除指定索引处的元素值,后面的元素前移 T remove(int index);
<6> 返回存储在数组列表中的当前的元素数量 int size()
<7> ensureCapacity(int capacity) 确保数组列表在不重新分配内部存储空间的情况下就能够保存给定数量的元素
如:访问数组类表的元素
Employee e=staff.get(i);
staff.set(i,harry)
将ArrayList转换成数组,如:
X[]a =new X[list.size()];
list.toArray(a);
(3) 类型化参数与原始列表的兼容性
如下类:
public class EmployeeDB{
public void update(ArrayList list){…}
pubic ArrayList find(String query){…}
}
可以将一个类型化的数组列表传递给update方法,而不需要任何类型转换,如:
ArrayList<Employee> staff=…;
employeeDB.update(staff); 此时编译器不会给出任何错误信息或警告。
将一个原始ArrayList赋值个一个类型化ArrayList会得到一个警告,如:
ArrayList<Empployee> result=(ArrayList<Employee>) employeeDb.find(query);此时会得到一个警告。
编译器在对类型进行检查后,如果没有发现违反规则的现象,就将所有的类型化数组类编转换成原始ArrayList对象,在程序运行时,所有的数组列表都是一样的。
8. 对象包装器
(1) 泛型变量要求传入的是对象类型,因此需要将基本类型转换成相应的对象类型,如:
ArrayList<Integer> list=new ArrayList<Integer>();
list.add的参数可以传入基本类型int,编译器将自动打包成Integer对象。
将一个Integer对象赋给一个int值时,将会自动地拆包,如:
int n=list.get(i);将自动翻译成int n=list.get(i).intValue();
如果将经常出现的值包装到同一个对象中,那么这种比较就有可能相等。但是这种比较应该用equesl函数。Integer对象是不可变的,包含在包装器中的内容不会改变。
(2) 基本方法:
1) 以int的形式返回Interger对象的值,如int intValue();
2) 将int类型转化为String类型,如:
static String toString(int i);
static String toString(int i, int radix);其中radix为进制数
3) 将String类型转化为int型,如:
static int parseInt(String s);
static int parseInt(String s);
4) 将String类型转化为Integer类型
static Integer valueOf(String s);
static Integer valueOf(String s, int radix);
9. 参数数量可变的方法,如:
public class PrintStream{
public PrintStream printf(String fmt, Object… args){
return format(fmt, args);
}
}
允许将一个数组传递给可变参数方法的最后一个参数,如:
System.out.println(“%d%s”, new Object[]{new Integer(1),“widgets”});
public static double max(double… values){
}
调用方法为:double m=max(3.1,6.2);