java继承和接口

子类继承父类的访问修饰符与可见性的关系

作用域当前类同一包内子类其他包
public
protected
default
private

备注:default 是缺省的访问权限,即成员无访问修饰符

向上转型

子类是父类的特殊化,每个子类的实例都是其父类的实例,但是反过来就不成立。因此,每个山地车都是一个自行车对象,但并非每个自行车都是山地车。因此,总可以将子类的实例传给需要父类类型的参数。比如以下语句是正确的:

Bicycle myBike=new MountainBike();

类MountainBike派生自Bicycle,所以myBike既是一个MountainBike,也是一个Bicycle。向上转型就是在允许的继承关系中,将子类型对象赋值给父类型引用的类型。类似的:

Object obj=new MountainBike();

这样,obj既是一个Object,也是一个Mountainbike。相反的,如果这么写:

Object obj=new MountainBike();
MountainBike myBike=obj;

任然会有编译错误,因为编译器不知道obj是指向MountainBike,编译器会认为他是Object类型的,必须通过显示类型转换将obj转换为类型MountainBike。

MountainBike myBike=(MountainBike)obj;

这一转换编译器会通过,但如果运行时obj不是MountainBike类型,会抛出异常。当然可以使用instanceof操作符做逻辑测试,判断obj是否是MountainBike类型再做转换。

if(obj instanceof MountainBike){
MountainBike myBike=(MountainBike)obj;
}

这样转换以后就不会有运行时异常了,但是,如果继承关系的层次很深,那么这种判断分支将会是噩梦。实际上,在设计良好的程序中,应该尽量避免使用这种判断。

equals()方法

操作符==可以用于比较两个基本数据类型的变量是否相等,但是若使用该运算符比较两个对象的引用变量,则实质上是比较两个引用变量是否指向了相同的对象。这里,相同的对象是指在同一块内存单元中的同类型对象。
若比较两个对象的引用变量所指向的对象的内容是否相同,则应该使用equals方法(),该方法的返回类型是boolean。equals()方法是Object类中定义的方法,默认下它比较的是对象的引用是否相同。需要注意的是,String,Integer,Date中这个方法被重写了,在这些类中equals()方法有其自身的实现,而不再是比较对象在堆内存中的存放地址了,这些对象调用equals()方法比较的是对象的内容。若用自定义的类来创建对象,则调用equals()方法比较的是连个引用是否指向了同一个对象。因此,如果自定义的类想要支持对象内容的比较,需要在类中重写equals()方法,即定义如何比较该类的对象内容。

public boolean equals(Object obj)
{
    Car ketty=(Car)obj;
    if(this.color.equals(ketty.color)&&this.name.equals(ketty.name))
    return true;
      return false;
}

这样类Car对象就可以使用equals判断对象的内容是否相等了。

抽象类

抽象类可以有属性和方法,但是不能用new操作符创建实例。抽象方法是只有方法声明而没有方法体的方法,它的实现由子类提供。包含抽象方法的类必须是抽象的,但是也允许声明没有抽象方法的抽象类。非抽象类不能包含抽象方法,从抽象父类派生的子类如果不能实现所有的抽象方法,它必须声明为抽象的。
子类可以是抽象的,即使它的父类是具体的。抽象类可以定义构造方法,该构造方法可以在子类的构造方法中调用。

final的作用

抽象类的目的是建立类的层次关系,让子类去派生该抽象类,但有时候,并不希望某些类被其他子类派生或者修改行为,这时候,就需要使用关键字final了。
当final修饰一个类定义是,表示当前类不允许被派生,即当前类不能作为其他类的父类。比如String类被使用final修饰,表面String是最终类,不能有子类。
而当final修饰方法时,意味着终止方法重写。这主要是为了防止创建子类和进行方法重写,替换原有的类从而攻击系统。比如:

public final void brake(){
}

这样,子类就不能重写brake()了。

继承与组合

继承是一种强耦合关系,如果父类变,子类就必须变。而且,继承破坏了封装性,对于父类而言,它的实现细节对于子类是可见的。因此,虽然继承能带来诸多好处,但是不是就可以大量使用继承呢?理性的做法如下:
慎用继承,尽量选择组合。
在新类里创建已有类的对象,这种创建新类的方法称为“组合”,即新类由现有的对象合并而成。组合可以重复利用已有代码的功能,是代码复用的重要手段。
组合是一个类的对象是另外一个类的成员,一般的程序都有组合的意味,比如成员变量中的基本数据类型,下面请看例子;

Class Head{
    Head(){
        System.out.println("head");
    }
}
Class Body{
    Body(){
        System.out.println("Body");
    }
}
Class Person{
    Head h=null;
    Body b=null;
    Person(){
    h=new Head();
    b=new Body();
        System.out.println("init person.");
    }
}
public class CombinationTest{
    public static void main(String []args){
        Person p=new Person();
        }
}
//程序输出:
head
body
init person

组合机制用来描述类之间的整体部分关系,被嵌入的类实现部分功能,而整体类则通过组织并调度各个部分类来实现特定的功能 。整体类不关心嵌入类的实现,只能通过嵌入类暴露的操作接口去访问它,因此,整体类和嵌入类的关系更加松散。这符合面向对象程序设计的高内聚低耦合的原则。

接口

Java不直接支持多重继承,也就是说一个类一般只继承一个父类。单继承使得类层结构更加清晰、易于管理,但是一个类可以实现多个接口。接口不是类,而是一组对类应该设计成什么样子的约定,即类的需求描述。

接口定义

接口用来建立类与类之间的协议,接口所提供的只是一种形式,而没有具体的实现,接口用关键字interface来声明,其一般格式如下:

[接口修饰符]interface 接口名[extends 接口1,接口2,接口3..]{
//声明变量
//抽象方法
}
public interface Controllable{
    public abstract void start();
    void start();
}

接口修饰符只能是public或缺省,public表明任意类和接口均可以使用该接口,而缺省表明只有该接口定义在同一个包中的类和接口才可以使用该接口。

接口的实现

类通过使用implements关键字来声明要实现的接口,如果一个类实现了某个接口,那么它必须实现该接口的所有方法。

class Trunk implements Controllable{
.......
}

接口不是类,因此不能实例化一个接口,只能实例化它的实现类。接口可以作为对象的引用类型。

Controllable cw=new Trunk();

使用接口过程中需要注意如下6个问题。

  1. 接口的所有方法的访问权限被自动声明为public.
  2. 接口中声明的属性必须是不可变的常量,它自动变为public static final.可以通过类名直接访问:ImplementClass.name.
  3. 接口中不存在方法的实现。
  4. 实现接口的非抽象类必须要实现该接口的所有方法,抽象类可以不用全部都实现。
  5. 不能使用new操作符实例化一个接口,但可以声明一个接口变量,该变量必须引用一个实现该接口的类的对象。可以使用instanceof检查一个对象是否实现了某个特定的接口。例如:

    if(anObject instanceof Controllable){
    }
  6. 在实现多接口的时候一定要避免方法名的重复。

    最后,在介绍接口设计的一个核心原则:接口隔离原则(Interface Segregation Principle),它是接口设计的最佳实践。

    1、 使用多个专门的接口比使用单一的总接口要好。
    2、一个类对另一个类的依赖性应当是建立在最小的接口上的。

接口和抽象类的区别

从语法上来说,抽象类定义中只要有一个方法是抽象的,该类就是抽象类。
接口只是方法的声明,但是抽象类中是可以有自己的方法定义的。

  1. 抽象层次不同。抽象类是对类抽象,而接口是对行为的抽象。抽象类是对整个类进行抽象,包括属性、行为,但是接口只对行为进行抽象。
  2. 建模关系不同抽象类对具有相似特点的类建模,而接口却可以对不相关的类建模。抽象类从子类中发现公共部分,然后抽取成抽象类,子类继承该父类即可。但是接口,实现他的类可以不存在任何关系。
  3. 设计层次不同。抽象类是自下而上来设计的,而接口是自顶向下设计出来的。

内部类

内部(Inner)类是指一个类的定义放在另一个类的内部。内部类拥有普通类的所有特性,也拥有类成员变量的特性。内部类可以访问其外部类的成员变量、方法和其他内部类。但访问的具体规则需要根据内部类的不同而定。
为什么要使用内部类?在Thinking in Java 一书中有这样一句话:使用内部类最吸引人的原因是每个内部类都能独立的继承一个类或接口的实现,所以无论外部类是否已经继承了某个类或者接口的实现,对内部类都没有影响。而且,内部类对象的创建时刻并不依赖于外部类对象的创建,内部类没有令人迷糊的“isa”(继承的意思)关系,它就是一个独立的实体。内部类提供了更好的封装,除了其所在的外部类,其他类都不能访问。
在创建一个内部类的时候,它无形中就与外部类产生了一种联系,依赖于这种联系,它可以无限制地访问外部类的成员。内部类有4中形态:静态内部类、成员内部类、局部内部类和匿名内部类。

静态内部类

类声明中包含static关键字的内部类,被称为静态内部类,只有静态内部类才能拥有静态成员,普通内部类只能定义普通成员。而且,静态类和静态方法一样,只能访问其外部类的静态成员。而如果在外部类的静态方法中访问内部类,只能访问静态内部类。
静态内部类与非静态内部类之间最大的一个区别是,非静态成员内部类在编译完成之后会隐含地保存一个引用,该引用指向创建它的外部类,但是静态内部类却没有。没有该引用就意味着静态内部类的创建时不需要依赖于外部类的,因此,他就不能使用任何外部类的非static成员变量和方法,同时,也只有静态内部类可以声明静态的成员变量,其他内部类不可以。

成员内部类

在外部类中可以创建成员内部(Member inner)类,在内部类可以定义与外部类同名的成员变量,这样会隐藏外部类中的变量。如果需要在内部类中访问被隐藏的变量,需要通过外部类的类名来调用。如:MemberInner.this.a;
在成员内部类中要注意亮点:成员内部类中不能存在任何static的变量和方法;成员内部类是依附于外部类的,所以只有先创建外部类对象才能创建内部类对象。

局部内部类

局部内部(local inner)类是嵌套在方法作用域内的类,它主要是解决比较复杂的问题时,想创建一个类来辅助,但又不希望该类是公共可用的,所以就创建局部内部类。局部内部类和成员内部类一样被编译,只是他的作用域发生了改变,它只能在该方法中被使用,出了方法就会失效。
局部内部类的定义和对象的创建都是在其所在的方法体内完成,在方法体外,局部内部类是不可见的,因此不能再main()方法中创建局部内部类。

匿名内部类

匿名(anonymous inner)类是一种特殊的局部类,因此局部类的特性与约束都适用于它。匿名内部类没有类名,没有class关键字也不能使用extends和implements等关键字修饰,因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写。但使用匿名内部类还有一个前提条件:
必须继承一个父类或者实现一个接口
创建匿名内部类的格式如下:

new 父类构造器(参数列表)|实现接口()
{
//匿名内部类的类体部分
}

注意到匿名内部类没有class关键字,这是因为匿名内部类是直接使用new关键字来生成一个对象的引用,当然这个引用是隐式的。由于匿名内部类不能使抽象类,所以它必须要实现它的抽象类或者接口里面所有的抽象方法。
实际上,只要一个类是抽象的或是一个接口,那么他就可以使用匿名内部类来实现。使用匿名内部类的过程中,要注意以下4点:

  1. 匿名内部类必须继承一个类或者实现一个接口,但是二者不可同时
  2. 匿名内部类不能定义构造函数
  3. 匿名内部类不能存在静态成员或方法
  4. 匿名内部类不能是抽象的,必须要实现父类或者接口的所有抽象方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值