继承(inheritance)背后的核心思想是:可以在现有类的基础上创建自己的新类,在新类中继承原来类的方法和数据域,并添加适合当前应用场景的新的数据和方法。
1. 类,超类,子类 (class,superclass,subclass):
Java 中的inheritance都是public inheritance,并不想C++中存在public,protected和private inheritance的分类。
class
subclass
extends
superclass;
这里有两个要点:
- 子类是父类的一种"具体类型",子类是父类的子集,所以,任何子类的对象都属于父类的类型;
- "子类具有父类的全部特征,并且在此基础上具有更多的特征",子类继承父类的所有字段和方法,同时子类又扩展了父类的能力;
将通用的方法放到父类当中,而将特有的方法放到子类当中,这是面向对象程序设计的要点之一,父类和子类的合理的功能划分是优秀设计的关键。
Java中的多态(polymorphism)和 C++ 的不同,在Java中不用将一个函数声明为虚函数,而在Java中要取消某个函数的多态功能,只要在函数声明前加上final关键字。
下面一段代码总结了类继承机制:
1
/**
2
@version
1.20 1998-09-17
3
@author
Cay Horstmann
4
*/
5
6
import java.util.*;
7
8
public
class ManagerTest
9 {
10
public
static
void main(String[] args)
11 {
12
//
construct a Manager object
13
Manager boss =
new Manager("Carl Cracker", 80000,
14 1987, 12, 15);
15 boss.setBonus(5000);
16
17 Employee[] staff =
new Employee[3];
18
19
//
fill the staff array with Manager and Employee objects
20
21 staff[0] = boss;
22 staff[1] =
new Employee("Harry Hacker", 50000,
23 1989, 10, 1);
24 staff[2] =
new Employee("Tommy Tester", 40000,
25 1990, 3, 15);
26
27
//
print out information about all Employee objects
28
for (
int i = 0; i < staff.length; i++)
29 {
30 Employee e = staff[i];
31 System.out.println("name=" + e.getName()
32 + ",salary=" + e.getSalary());
33 }
34 }
35 }
36
37
class Employee
38 {
39
public Employee(String n,
double s,
40
int year,
int month,
int day)
41 {
42 name = n;
43 salary = s;
44 GregorianCalendar calendar
45 =
new GregorianCalendar(year, month - 1, day);
46
//
GregorianCalendar uses 0 for January
47
hireDay = calendar.getTime();
48 }
49
50
public String getName()
51 {
52
return name;
53 }
54
55
public
double getSalary()
56 {
57
return salary;
58 }
59
60
public Date getHireDay()
61 {
62
return hireDay;
63 }
64
65
public
void raiseSalary(
double byPercent)
66 {
67
double raise = salary * byPercent / 100;
68 salary += raise;
69 }
70
71
private String name;
72
private
double salary;
73
private Date hireDay;
74 }
75
76
class Manager
extends Employee
77 {
78
/**
79
@param
n the employee's name
80
@param
s the salary
81
@param
year the hire year
82
@param
month the hire month
83
@param
day the hire day
84
*/
85
public Manager(String n,
double s,
86
int year,
int month,
int day)
87 {
88
super(n, s, year, month, day);
89 bonus = 0;
90 }
91
92
public
double getSalary()
93 {
94
double baseSalary =
super
.getSalary();
95
return baseSalary + bonus;
96 }
97
98
public
void setBonus(
double b)
99 {
100 bonus = b;
101 }
102
103
private
double bonus;
104 }
Java没有像C++那样提供多继承机制,但提供了接口机制,在后面我们将详细探究接口机制的实现。
2. Java 多态机制的具体实现:
Java中的父类和子类之间的"is-a"关系可以用一个substitution principle(替代原则来描述)。所谓"替代原则"是,在程序中任何一个期望出现父类对象地方都可以用一个子类对象
来代替!
Employee e;
e =
new Employee(...);
//
Employee object expected
e =
new Manager(...);
//
ok, Manager can be used as well
上面的一个Employee型的引用变量可以引用一个Manager型的对象。
Java中对象变量是多态的。
Java中的动态绑定,当对一个对象应用一个方法时,我们需要搞清楚到底这个过程是如何发生的,以一段代码开始:
1
public
class TestDynamic
2 {
3
public
static
void main(String[] args)
4 {
5 BaseClass base =
new DerivedClass();
6 base.printMsg();
7 }
8 }
9
10
class BaseClass
11 {
12
void printMsg()
13 {
14 System.out.println("This is the base class");
15 }
16 }
17
18
class DerivedClass
extends BaseClass
19 {
20
void printMsg()
21 {
22 System.out.println("This is the derived class");
23 }
24 }
这段代码在main函数中用父类对象变量引用了一个子类对象,然后调用父类和子类都有的printMsg方法,实验结果是:
This is the derived
class
下面是与上面对象的C++代码:
1 #include <iostream>
2
3
using
namespace std;
4
5
class BaseClass
6 {
7
public:
8
void printMsg()
9 {
10 cout<<
"
This is the base class
"<<endl;
11 }
12 };
13
14
class DerivedClass :
public BaseClass
15 {
16
public:
17
void printMsg()
18 {
19 cout<<
"
This is the derived class
"<<endl;
20 }
21 };
22
23
int main()
24 {
25 BaseClass *
base =
new DerivedClass();
26
base->printMsg();
27
return
0;
28 }
这段代码的实验结果是:
This is the base class
若将base->printMsg(); 改成 ((DerivedClass *)base) -> printMsg(); 那么对应的结果是:
This is the derived class
可见java中的动态绑定要比C++中来的更加直接,根本不需要强制类型转换。
下面我们详述一个Java对象变量调用一个方法的整个过程:
- x.f(param)这样的一个调用,x的实际类型是C,那么Java编译器就会找出所有C类型中和C的所有父类中f(...)的函数,这样编译器就知道的所有候选调用方法;
- 编译器要确定参数param的类型,期间可能会用到类型的转换。这样编译器就知道需要调用的方法的名称和参数类型了。
- 如果方法使用的是private,static或final修饰符来修饰的,那么变异器就知道这个方法,因为这是静态绑定。
- 如果本类中找不到适合方法那么在父类中寻找。
动态绑定的特性使得Java具有了执行目前尚未实现的代码的可能,同样是Java EE的容器构建模型实现的重要基础.
3. 对继承的禁止: final类和方法
不能被继承的类被成为final类,在类的定义前加上final修饰符来表明这个类是一个final类。同样也可以将类中的一个方法声明为final的,这样任何子类都不能够重载这个方法。
注意,一个类中的数据域也可以被声明成final的,意思是一旦这个数据域被初始化之后,这个域将不能被修改。final修饰符对一个类的作用范围只是在类中的方法上,而不会在类
中的数据域上。
4. 强制类型转换:
使用强制类型转换的唯一理由是为了使用一个对象的全部功能,当我们暂时忘记了这个对象的类型的时候。
在java中任何一个对象变量都有一个类型,这个类型描述了变量指向的的是何种对象以及可以在上面进行的操作。
在进行强制类型转换的时候首先检查这个强制类型转换是否会成功是一个很好的主意!
下面对比Java和C++中强制类型转换机制:
1
public
class TestDynamic
2 {
3
public
static
void main(String[] args)
4 {
5 DerivedClass drive =
new DerivedClass();
6 BaseClass base = (BaseClass)drive;
7 base.printMsg();
//
由于动态绑定的作用,这条语句还是打印"This is the derived class"
8
9 BaseClass mybase =
new BaseClass();
10 DerivedClass myderived = (DerivedClass)mybase;
11 }
12 }
13
14
class BaseClass
15 {
16
void printMsg()
17 {
18 System.out.println("This is the base class");
19 }
20 }
21
22
class DerivedClass
extends BaseClass
23 {
24
void printMsg()
25 {
26 System.out.println("This is the derived class");
27 }
28 }
结果输出:
This is the derived
class
Exception in thread "main" java.lang.ClassCastException: BaseClass cannot be cast to DerivedClass
at TestDynamic.main(TestDynamic.java:10)
将父类对象强制转换成子类对象的时候就会出现上述的java.lang.ClassCastException的异常,为了避免上述异常的出现,可以
使用instanceof运算来检测对象的实际类型!
1
if
(mybase
instanceof
DerivedClass)
2 {
3 DerivedClass myderived = (DerivedClass)mybase;
4 }
5 else
6 {
7 System.out.println("Is not a instance of type DerivedClass");
8 }
结果:
This is the derived
class
Is not a instance of type DerivedClass
关于C++中的强制类型转换请参考:http://www.cnblogs.com/jiangheng/p/3748051.html
5.抽象类(Abstract Class):(Java中的抽象类和C++中的抽象类对比)
抽象类的两个最大的意义在于:一是强制要求之子类中必须实现所要求的abstract方法,否则不能实例化; 而是限制对abstract类的实例化。这两个方面
可以形成相应的约束,从而减少出错的机会。
1
/*
*
2
@version 1.00 1999-12-17
3
@author Cay Horstmann
4
*/
5
6 import java.text.*;
7
8
public
class PersonTest
9 {
10
public
static
void main(String[] args)
11 {
12 Person[] people =
new Person[
2];
13
14
//
fill the people array with Student and Employee objects
15
people[
0]
16 =
new Employee(
"
Harry Hacker
",
50000);
17 people[
1]
18 =
new Student(
"
Maria Morris
",
"
computer science
");
19
20
//
print out names and descriptions of all Person objects
21
for (
int i =
0; i < people.length; i++)
22 {
23 Person p = people[i];
24 System.
out.println(p.getName() +
"
,
"
25 + p.getDescription());
26 }
27 }
28 }
29
30
abstract
class Person
31 {
32
public Person(String n)
33 {
34 name = n;
35 }
36
37
public
abstract String getDescription();
38
39
public String getName()
40 {
41
return name;
42 }
43
44
private String name;
45 }
46
47
class Employee extends Person
48 {
49
public Employee(String n,
double s)
50 {
51
//
pass name to superclass constructor
52
super(n);
53 salary = s;
54 }
55
56
public
double getSalary()
57 {
58
return salary;
59 }
60
61
public String getDescription()
62 {
63 NumberFormat formatter
64 = NumberFormat.getCurrencyInstance();
65
return
"
an employee with a salary of
"
66 + formatter.format(salary);
67 }
68
69
public
void raiseSalary(
double byPercent)
70 {
71
double raise = salary * byPercent /
100;
72 salary += raise;
73 }
74
75
private
double salary;
76 }
77
78
class Student extends Person
79 {
80
/*
*
81
@param n the student's name
82
@param m the student's major
83
*/
84
public Student(String n, String m)
85 {
86
//
pass n to superclass constructor
87
super(n);
88 major = m;
89 }
90
91
public String getDescription()
92 {
93
return
"
a student majoring in
" + major;
94 }
95
96
private String major;
97 }
6. equals 方法:
Object类中的equals方法使用非常简单的方式来判断两个对象是否相等,即只判断两个对象是否指向同一个内存区域,也就是只有两个对象
变量指向同一个对象是才判断为相等。这在很多情况下是不能满足需要的。这就需要在类中自定义equals方法。
Java中的equals方法需要满足下面五个条件:
a) 反身性(reflexive): 对于每个non-null的引用x,x.equals(x)返回true;
b) 交换性(symmetric): x.equals(y) 是true,那么y.equals(x)也是true;
c) 传递性(transitive): x.equals(y) 是true,y.equals(z)是true,那么 x.equals(z)也是true;
d) 一致性(consistent): 如果x,y指向的对象没有改变,那么每次调用x.equals(y)方法得到的结果都是相同的;
e) 对于任何一个non-null的对象x, x.equals(null)的结果都是false;
编写Java类中的equals函数应该注意的事项:
1).将显示的参数命名为otherObject,以后将其转换为other;
2).if(this == otherObject) return true;
3).if(otherObject == null) return false;
4).比较this和otherObject的类型,如果equals的语义在子类中得到了改变,那么用getclass()进行测试:
if(getclass() != otherObject.getclass()) return false;
如果所有的子类都和父类遵循同样的equals语义,那么用instanseof测试:
if(!(otherObject instanceof ClassName)) return false;
5) .将otherObject转换成主体类的形式:
ClassName other = (ClassName)otherObject;
6). 进行每个数据域的比较。原生数据类型用==符号来进行比较,类数据类型用每个类定义的equals方法来进行比较。
return field1 == other.field1 && field2.equals(other.field2) && ...
如果在子类中重定义equals方法,那么在子类的equals中super.equals(other).
1 public class TestEquals
2 {
3
public
static
void main(String[] args)
4 {
5 Manager man1 =
new Manager("Bruce Lee",23,30000);
6 Manager man2 =
new Manager("Bruce Lee",23,30000);
7
if(man1.equals(man2))
8 System.out.println("Are you kidding me, There are two same manager in our company!");
9 }
10 }
11
12
class Employee
13 {
14
public Employee(String aName,
int aAge)
15 {
16 name = aName;
17 age = aAge;
18 }
19
20
public
boolean equals(Object otherObject)
21 {
22
//
The otherObject is the same object of current object
23
if(
this == otherObject)
return
true;
24
25
if(otherObject ==
null)
return
false;
26
27
//
If the class don't match, they can not be equal
28
if(getClass() != otherObject.getClass())
29
return
false;
30
31 Employee other = (Employee)otherObject;
32
33
return name.equals(other.name) && age == other.age;
34 }
35
36
private String name;
37
private
int age;
38 }
39
40
class Manager
extends Employee
41 {
42
public Manager(String aName,
int aAge,
int aBonus)
43 {
44
super(aName,aAge);
45 bonus = aBonus;
46 }
47
48
public
boolean equals(Object otherObject)
49 {
50
if(!
super.equals(otherObject))
return
false;
51
52 Manager other = (Manager)otherObject;
53
return bonus == other.bonus;
54 }
55
private
int bonus;
56 }
上面的代码中父类和子类判断相等的方式不相同,
if
(getClass() != otherObject.getClass())
29 return false;
上面的语句将equals操作限定在了相同类型的对象之间;
7.HashCode方法:
下面是计算一个字符串的hashcode的代码:
1
public
class
TestHash
2 {
3 public static void main(String[] args)
4 {
5 String str = "Hello";
6 int hash = 0;
7 for(int i = 0; i < str.length(); i++)
8 {
9 hash = 31*hash + str.charAt(i);
10 }
11 System.out.println("The hashcode of string \""+str+"\" is "+hash);
12 }
13 }
14
hashCode 函数定义在Object类中,因此每一个对象都有一个默认的hashCode,并且这个hashCode是由这个对象
在内存中的地址推导出来的。
注意hashCode方法要和equals方法保持一致性!
8. 自动装箱和拆箱:
1) Java中的基本数据类型如int,double等都不是对象数据类型,不继承自Object;
2) 如果需要使用Object类型的数字,则需要使用包装类Interger,Long,Double等,
但他们一旦被创建就不可改变,并且这些包装类是不可派生出子类的。
ArrayList<Type>中的Type不能是原生数据类型,而应该是一个派生自Object的对象类型。
import
java.util.*;
public
class TestArrayList
{
public
static
void main(String[] args)
{
ArrayList<Person> per_list =
new ArrayList<Person>();
ArrayList<int> int_list = new ArrayList<int>();
}
}
class Person
{
public Person(String aName,
int aAge)
{
name = aName;
age = aAge;
}
private String name;
private
int age;
}
上面红色标记的代码导致了下面的错误:
TestArrayList.java:8: 错误: 意外的类型
ArrayList<int> int_list = new ArrayList<int>();
^
需要: 引用
找到: int
TestArrayList.java:8: 错误: 意外的类型
ArrayList<int> int_list = new ArrayList<int>();
^
需要: 引用
找到: int
2 个错误