final关键字、类之间的关系、转型、多态的缺陷、构造器和多态

1、final关键字

final关键字通常是表示无法改变的,有三种使用情况:数据、方法、类。
(1)final数据
分两种情况:表示永不改变的编译时常量,可在编译时执行计算,必须是基本类型且必须在定义时确切赋值,若同时还被static修饰则表示是一段不能改变的存储空间,通常大写;一个在运行时被初始化的值,它不希望被改变,可以是基本类型,也可是引用,若是引用,表示引用只能指向一个确切对象(但对象本身可以改变),而在初始化之后不能指向别的对象。
但是不能因为一个基本类型数据被final修饰就认定在编译时知道其值,例如用生成的随机数值初始化它,那么每创建一个对象,其值都不一样,和编译时常量的定义不相符
空白final:被声明为final但没有给定初始值的域,空白final域提供了更大的灵活性,可以根据对象不同而有所不同,一般在构造器中用表达式进行赋值。
final参数:将方法的形参(引用)用final修饰,表示无法在方法中更改参数引用所指向的对象或者参数的值本身,即可读但不可写

(2)final方法

使用final方法是为了把方法锁定,防止继承时被覆盖从而修改其行为,只有明确禁止覆盖时,才将方法设置为final,不要因为性能而将方法设置为final。

final关键字与private关键字:类中所有private方法都隐式指定为final的。这可能会带来混淆,如果基类有一个同名的private方法,导出类是否真的覆盖了这个方法?并没有,为了避免这个问题,要么覆盖时加上注解,要么就避免方法名和基类private方法同名。

(3)final类

将类定义为final,表示该类不能被任何人继承,即该类不需要有任何改动且不能有子类。final类的域可以选择是或不是final,和非final类的域定义为final域的规则一样,但是由于final类禁止继承,所以final类的所有方法都隐式的是final的。

final类用得不多,因为要预见一个类如何被复用通常非常困难。

2. 类之间的关系(复用类的两种形式)

  • 组合

在新类中显示地放置子对象, 各个子对象之间没有什么联系,子对象是新类的一部分,新类与放置的子对象是has-a关系,比如电脑有CPU、RAM、显卡等等。


class CPU{
    public String toString(){
        return " CPU ";
    }
}
class RAM{
    public String toString(){
        return " RAM ";
    }
}
class GraphicChip{
    public String toString(){
        return " GraphicChip ";
    }
}
public class Computer {
    private CPU cpu;
    private RAM ram;
    private GraphicChip graphicChip;
    public Computer(){
        cpu = new CPU();
        ram = new RAM();
        graphicChip = new GraphicChip();
    }
    public String toString(){
        return "A computer has"+cpu+ram+graphicChip;
    }
    public static void main(String[] args){
        Computer c = new Computer();
        System.out.println(c);
    }
}
/**
 * 输出
 A computer has CPU  RAM  GraphicChip 
 */

在组合和继承之间选择,组合是显示放置子对象,继承是隐式放置。组合通常用于想在新类中使用现有类的功能而非它的接口,这种情况下可以在新类中嵌入一个对象,让其实现若需要的功能,如Computer类中的cpu ram 。组合中放置的对象一般为private,但有时将放置的对象声明为public将有助于用户理解如何去使用类。

通过继承,使用现有的通用类开发它的特殊版本,这时用组合是毫无意义的,比如车子继承自交通工具,是is-a关系,使用组合车子与交通工具就是has-a关系,这显然说不通。

利用现有类来开发新的类,使用组合和继承都可以,但是继承其实并不常用,如果需要从新类向基类进行向上转型,就应该选择继承,否则应该考虑组合

  • 代理

Java并不直接支持代理,它是继承和组合的折中,将一个类的对象置于新类中(像组合),但同时也暴露了该对象的所有方法(像继承)。

3. 转型

向上转型:将某个对象的引用视为其基类类型的引用,在继承层次中向上移动。由于导出类可能会扩展基类,故向上转型会丢失导出类的信息(成员变量或方法),无法再访问导出类中基类没有的信息。

向下转型:与向上转型相反,在继承层次中向下移动,向下转型要先向上转型,即导出类的对象先向上转型然后再向下转型,因为基类对象如果直接向下转型,由于导出类信息会比基类多,逻辑上说不通,会抛出异常。

转型的原因:向上转型,可以不管导出类的存在,直接和基类打交道,不必为每个导出类编写特定类型的方法;向下转型,由于向上转型会丢失导出类的信息,向下转型的正确性有运行时类型识别(RTTI)作为保证。转型是通过多态来实现的。

class Useful{
    public void f(){}
    public void g(){}
}
class MoreUseful extends Useful{
    public void f(){}//覆盖基类
    public void g(){}//覆盖基类
    public void u(){}
    public void v(){}
    public void w(){}
}
public class casting{
    public static void main(String[] args){
        Useful[] x = {
                new Useful(),
                new MoreUseful(),//向上转型
        };
        x[0].f();
        x[1].g();
        //x[1].u();//向上转型丢失了信息(u方法),
        ((MoreUseful)x[1]).u();//向下转型
        ((MoreUseful)x[0]).u();//向下转型先要向上转型,否则运行会抛出ClassCastException
    }
}

4. 多态的缺陷

1、试图覆盖私有方法,private方法是隐式的final方法,故不能覆盖,同前面final带来的混淆一样,

2、域与静态方法
只有方法才有多态,域没有多态。若导出类可以访问基类的域,且导出类和基类的域同名,那么在导出类中直接访问域时,访问在编译期就进行解析,而不是运行时确定,故没有多态性,在导出类中实际上是有两个域,一个是它自己的,一个是基类的。 实际中不会发生,因为域一般是private的,且导出类的域也不会和基类的域同名。
静态方法属于类,肯定不会有多态。

class Super{
    public int field = 0;
    public int getField(){
        return field;
    }
}
class Sub extends Super{
    public int field = 1;
    public int getField(){//覆盖基类方法
        return field;
    }
    public int getSuperField(){
        return super.field;
    }
}
public class FieldAccess {
    public static void main(String[] args) {
        Super sup = new Sub();//向上转型
        System.out.println("sup.field="+sup.field+" ,sup.getField="+sup.getField());
        Sub sub = new Sub();
        System.out.println("sub.field="+sub.field+" ,sub.getField="+sub.getField()+" ,sub.getSuperField="+sub.getSuperField());
    }
}
/**
 * 输出
sup.field=0 ,sup.getField=1
sub.field=1 ,sub.getField=1 ,sub.getSuperField=0
*/

5. 构造器和多态

构造器显然不具有多态性,因为是隐式static方法,是属于类的。

在基类构造器内部调用一个多态的方法,结果将难于预料,因为在构造器内部,整个对象只是部分形成,只知道基类对象已经进行了初始化,导出类的对象没有初始化。

class Glyph{
    Glyph(){
        System.out.println("Glyph() before draw()");
        draw();
        System.out.println("Glyph() after draw()");
    }
    void draw(){
        System.out.println("Glyph.draw()");
    }
}
public class RoundGlyph extends Glyph{
    private int radius = 1;

    RoundGlyph(int radius){
        this.radius = radius;
        System.out.println("RoundGlyph.RoundGlyph(), radius = "+radius);
    }
    void draw(){
        System.out.println("RoundGlyph.draw(), radius = "+radius);
    }
    public static void main(String[] args) {
        new RoundGlyph(5);
    }
}
/**
 * 输出
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
 */
初始化的顺序:
(1)在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零(如果是引用,则为null,如果忘记初始化,所有东西都将是0)
(2)调用构造器,由于调用导出类的构造器第一步是调用基类构造器,所以从根类开始调用构造器,即先调用基类的构造器,由于多态,此时调用的draw方法是导出类的draw方法,由于步骤1,radius此时为0
(3)按照声明顺序调用成员的初始化方法
(4)调用导出类的构造器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值