类之间的关系
一、类之间的关系
1.类的六种关系
继承:一个类继承另一个类,也称为泛化,体现共性与特性的关系。
实现:一个类实现接口中声明的方法。
依赖:在一个类的方法中操作另一个类的对象,称为第一个类依赖于第二个类。
关联:在一个类中使用另一个类的对象作为该类的成员变量,体现两个类之间的强依赖关系。
聚合:关联的一种特例,体现整体与部分的关系(has-a)。
组成:关联的一种特例,体现整体与部分的关系(constains-a)。
在类与类之间的这六种关系中,继承和实现体现了类与类之间的一种纵向的关系,其余四种体现了类与类之间的横向关系。其中,关联、聚合、组成这三种关系更多体现的一种语义上的区别,而在代码上则是无法分开的。
2.依赖关系
class Car{
void run(String city){
System.out.println(“汽车开到”+city);
}
}
class Person{
//Car类的对象作为方法的参数
void travel(Car car){
car.run(“青岛”);
}
}
public class DependentDemo{
public static void main(String[] args){
Car car = new Car();
Person p = new Person();
p.travel(car);
}
}
Person类的travel()方法需要Car类的对象作为参数,并且在该方法中调用了Car的方法,因此Person类依赖于Car类。
实际意义是:一个人旅游依赖于一辆车。
3.关联关系
class Car{
void run(String city){
System.out.println(“汽车开到”+city);
}
}
class Person{
//Car类的对象作为成员变量
Car car;
Person(Car car){
this.car = car;
}
void travel(Car car){
car.run(“青岛”);
}
}
public class AssociationDemo{
public static void main(String[] args){
Car car = new Car();
Person p = new Person(car);
p.travel(car);
}
}
Person类中存在Car类型的成员变量,并在构造方法和travel()方法中都是使用该成员变量,因此两者具有关联关系。
4.聚合关系
AggregationDemo.java:
class Employee{
String name;
Employee(String name){
this.name = name;//函数的参数与成员变量同名时,用this表示成员变量name.
}
}
class Department{
Employee[] emps;//声明对象数组
Department(Employee[] emps){
this.emps = emps;
}
void show(){
for(Employee emps:emps){
System.out.println(emps.name);
}
}
}
public class AggregationDemo{
public static void main(String[] args){
Employee[] emps = {new Employee("张三"),new Employee("李四"),new Employee("王二麻子")};
Department dept = new Department(emps);
dept.show();
}
}
上述代码中,部门类Department中的Employee数组代表此部门的员工。两者关系:部门由员工组成,同一个员工也可以属于多个部门,并且部门解散,员工依然存在,并不会随之消亡。
聚合关系是关联关系的一种特例,体现的是整体与部分的关系,通常表现为**一个类(整体)由多个其他类的对象(部分)作为该类的成员变量。聚合关系中的整体和部分是可分离的。**例如:一个部门由多个员工组成,部门和员工是整体与部分的关系(has-a),即聚合关系。
5.组成关系
CompositionDemo.java:
class Engine{
}
class Chassis{
}
class Bodywork{
}
class Circuitry{
}
class Car{
Engine engine;
Chassis chassis;
Bodywork bodywork;
Circuitry circuitry;
Car(Engine engine,Chassis chassis,Bodywork bodywork,Circuitry circuitry){
this.engine = engine;
this.chassis = chassis;
this.bodywork = bodywork;
this.circuitry = circuitry;
}
}
public class CompositionDemo{
public static void main(String[] args){
Engine engine = new Engine();
Chassis chassis = new Chassis();
Bodywork bodywork = new Bodywork();
Circuitry circuitry = new Circuitry();
Car car = new Car(engine,chassis,bodywork,circuitry);
System.out.println("Success!");
}
}
上述代码中的五个类,其中Engine、Chassis、Bodywork、Circuitry都是Car的成员变量,它们之间构成了一种部分与整体的组成关系,如果汽车消亡后,这些设备也将不复存在。
组成关系体现的也是整体与部分的关系(constains-a) ,但组成关系中的整体和部分是不可分离的,如汽车和设备之间的关系。
二、继承(重点)
1.继承的概念
继承是面向对象编程技术的重要基石,被继承的类称为父类(又称超类、基类),继承父类的类称为子类(又称派生类)。
继承的方式有单继承和多继承,java中只支持单继承。采用接口技术实现多继承。
执行继承后,**子类将获得父类的所有属性和方法,并具有自身特有的属性。**但父类可以隐藏某些数据,只对子类提供可访问的属性和方法(如私有属性和方法)。
2.继承的语法
java中采用关键字extends表示继承。
声明继承的语法格式为:
class subClass extends baseClass{
//类体
}
只能有一个父类,即单继承
在没有指明extends时,默认的父类是Object类。
3.子类对象的创建
创建过程:先分配父类空间,再分配子类空间,并初始化为此类型的默认值。
先执行父类的初始化和构造函数,再执行派生类的初始化和构造函数。
class SuperClass{
int a;
SuperClass(){
System.out.println(“调用父类构造方法”);
a=10;
}
}
public class SubClass extends SuperClass{
int b;
public SubClass(){
System.out.println(“调用子类构造方法”);
b=20;
}
public static void main(String args[]){
SubClass obj = new SubClass();
System.out.println(“a=”+obj.a+”;b=”+obj.b);//a=10,b=20
}
}
用子类的对象可以调用父类的变量及部分函数。
4.域的隐藏
解决子类和父类的变量重名问题。
在子类中定义了与父类中具有相同名称的域,此时,子类的域将屏蔽父类的域,这种现象称为子类对父类的域的隐藏。
子类中发生隐藏后,如果要访问被隐藏的域,必须在域前面增加前缀super,以此表示为父类的域。
发生域的隐藏后,父类的数据依然存放在内存中,只是优先访问子类的域而已。
class SuperClass {
int a;
SuperClass(){
System.out.println("调用父类构造方法");
a=10;
}
}
public class SubClass extends SuperClass{
int a;
public SubClass(){
System.out.println("调用子类构造方法");
a=20;
}
public void superout() {
System.out.println("a="+super.a);//不能在静态函数中调用
}
public static void main(String args[]){
SuperClass mm=new SuperClass();//父类对象
SubClass obj = new SubClass();//子类对象
SuperClass xx=new SubClass();//父类对象的引用
System.out.println("a="+obj.a);//调用子类变量
obj.superout();//调用父类变量
//强制转换
System.out.println("a="+((SuperClass)obj).a);//子类的对象可以调用父类变量
System.out.println("a="+((SubClass)xx).a);//父类的引用可以调用子类的变量
//System.out.println("a="+((SubClass)mm).a);//父类的对象不能调用子类的变量
}
}
运行结果:
5.方法的重写
解决子类和父类的方法同名问题,又称方法覆盖。
在类层次结构中,如果子类中的一个方法与父类中的方法有相同的方法名并具有相同数量和类型的参数列表,这种情况称为方法覆盖。
方法的重写和前面讲过的重载是面向对象多态性的两大体现。
Java的多态性机制导致了声明的变量类型和实际引用的类型可能不一致,为准确的鉴别一个对象的真正类型,可以用instanceof关键字进行判断。
package happy;
public class test {
public static void main(String[] args)
{
Fu f=new zi();
System.out.println(f.num);
System.out.println(f.fun1());
f.show();
String result= ((zi)f).func2();
System.out.println(result);
System.out.println(f instanceof Fu);
System.out.println(f instanceof zi);
}}
class Fu{ public String num="父类成员变量";
public void show() {
System.out.println(this.num);
System.out.println(this.fun1());
}
public String fun1() {
System.out.println(this.num);
return "父类调用";
}}
class zi extends Fu{
public String num="子类成员变量";
public String fun1() {
System.out.println(this.num);
return "子类调用";
}
public String func2(){
return "子类特有方法";
}}
6.类型转换
Class A{
int x = 1;
void f(){
…..
}
}
Class B extends A{
int x = 2;
void f(){
…..
}
}
B b = new B();
父类对象与子类对象之间进行转换的原则:
a.子类对象可以看作是父类对象。
b.父类对象不可以看作子类对象。
c.函数参数是父类,可以传递子类对象。
d.父类对象实际指向子类对象,可以转换为子类。//反之出错
在上述例子中,如果有A a = new B(),那么
a.x//要看等号左侧的引用类型,变量即使同名也不算重写,故a.x为A的变量
a.f()//当父类中的函数被子类重写时,优先执行子类的函数,故a.f()执行的是B的函数
多态:接收同一消息,表现不同形式。
对于方法的覆盖,new的谁就调用谁,这就是多态。
对于变量,只是隐藏,this在哪个类就指向哪个类的变量,没有多态。(变量要看左边的引用)
三、内部类
Java允许在一个类的类体之内再定义一个类,该情况下外面的类成为“外部类”,里面的类称为“内部类”,内部类是外部类的一个成员,并且依附于外部类而存在。
引入内部类的原因:
内部类能够隐藏起来,不为同一包的其它类所用;
内部类可以访问其所在的外部类的所有属性。
Java内部类有以下4种:
成员内部类:在外部类内;
局部内部类:在方法中定义的内部类;
静态内部类:使用static关键字修饰的内部类;
匿名内部类:没有名字的内部类。
1.成员内部类(类中类)
成员内部类的定义结构很简单,就是在“外部类”的内部定义一个类。
Cow.java:
public class Cow{
private double weight;
public Cow(){
}
public Cow(double weight){
this.weight = weight;
}
private class CowLeg{
double length;
String color;
public CowLeg(){
}
public CowLeg(double length,String color){
this.length = length;
this.color = color;
}
public void info(){
System.out.println("当前牛腿的颜色是:" + color + ",高:" + length);
System.out.println("当前牛的重量是:" + weight);
}
}
public void test(){
CowLeg c1 = new CowLeg(1.12,"黑白相间");
c1.info();
}
public static void main(String[] args){
Cow cow = new Cow(378.9);
cow.test();
}
}
上述代码中,在Cow类中定义了一个CowLeg成员内部类,并在该内部类内直接访问了Cow的私有成员weight。
上述代码编译后生成两个class文件:Cow.class和Cow$CowLeg.class(注意内部类的文件名)
2.局部内部类(方法中类)
在方法中定义的内部类称为局部内部类,与局部变量类似(除final外,局部变量不允许被其它修饰符修饰),局部内部类不能用public或private访问修饰符进行声明。
局部内部类的优点:
对外界完全隐藏,除所在方法之外,对其他方法是不透明的。
与其他内部类相比较,局部内部类可以访问包含它的外部类的成员,还可以访问被final修饰的局部变量。
LocalInnerClass.java
public class LocalInnerClass{
public static void main(String[] args){
class InnerBase{
int a;
}
class InnerSub extends InnerBase{
int b;
}
InnerSub is = new InnerSub();
is.a = 1;
is.b = 3;
System.out.println("is.a = " + is.a + "\nis.b = " + is.b);
}
}
代码中有两个局部内部类,编译后生成三个class文件:
LocalInnerClass.class
LocalInnerClass$1InnerBase.class
LocalInnerClass$1InnerSub.class
其中数字1是为了区分两个以上的同名局部类(处于不同方法中)。
实际开发中,由于作用域很小,局部内部类很少使用。
3.静态内部类(相当于外部类)
使用static修饰内部类,则该内部类称为“静态内部类”。
静态内部类属于外部类本身,不属于外部类的某个对象。
静态内部类在实际工作中使用较少。
4.匿名内部类
匿名内部类就是没有名字的内部类,适合只需要使用一次的类。
当创建一个匿名内部类时会立即创建该类的一个对象,该类的定义会立即消失,不能重复使用。
AnonyClassDemo.java
public class AnonyClassDemo{
public static void main(String[] args){
System.out.println(new Object(){
public String toString(){
return "匿名类对象";
}
});
}
}
上述代码中,创建了一个匿名类的对象,该匿名类重写Object父类的toString()方法。
总结
本文的重点在于类的继承,域的隐藏、方法的重写、类型转换及多态都需要仔细反复的理解,是很重要易混淆的地方。类之间的关系分为纵向关系:继承和实现,还有横向关系:依赖、关联、聚合、组成。其中,关联、聚合、组成这三种关系更多体现的一种语义上的区别,而在代码上则是无法分开的。对于内部类的应用,我们主要掌握成员内部类(类中类)以及局部内部类(方法中类)即可,静态内部类相当于外部类,可以用类名直接调用,匿名内部类只能用一次。