《JAVA:从入门到精通》part 12

十五、类的高级特性

1. JAVA类包

  • 类名冲突:JAVA中每个接口或类都来自不同的类包,无论是JAVA API中的类与接口还是自定义的类与接口,都要隶属于某一个类包,这些类包包含了一些类和接口。如果没有包的存在,管理程序中的类名称将是一件非常麻烦的事情。如果程序只是由一个类定义组成,并不会给程序带来什么影响,但是随着程序代码的增多,难免会出现类同名的问题,这会导致类名冲突,编译器不会允许存在同名的类文件。解决这类问题的方法是将这两个类放置在不同的类包中。

  • 同一个包中的类相互访问时,可以不指定包名。

  • 同一个包中的类不必放在同一个位置,如com.lzw.class1和com.lzw.class2可以一个放在C盘,一个放在D盘,只要将CLASSPATH分别指向这两个位置即可。

  • 使用import关键字导入包

    import com.lzw.*;            //指定com.lzw包中的所有类在程序中都可以使用
    import com.lzw.Math;        //指定com.lzw包中的Math类在程序中可以使用
    
  • 在使用import关键字时,可以指定类的完整描述,如果为了使用包中更多的类,可以在使用import关键字指定时在包指定后加上*,这表示可以在程序中使用包中的所有类。

  • 如果类定义中已经导入com.lzw.Math类,在类体中再使用其他包中的Math类是就必须指定完整的带有包格式的类名,如这种情况再使用java.lang包中的Math类时就要使用全名格式java.lang.Math。

  • 使用import关键字导入静态成员

    import static 静态成员
    

导入静态成员案例:

package com.lzw;

import static java.lang.Math.*;
import static java.lang.System.*;

public class ImportTest {
    public static void main(String[] args) {
        // 在主方法中可以直接使用这些静态成员
        out.println("1和4的较大值为:" + max(1, 4));
    }
}

运行结果:

  • 从本实例中可以看出,分别使用import static导入了java.lang.Math类中的静态成员方法max()和java.lang.System类中的out成员变量,这时就可以在程序中直接引用这些静态成员,如在主方法中的out.pritln()表达式以及直接使用max()方法。

2. final变量

  • final关键字可以用于变量声明,一旦该变量被设定,就不可以再改变该变量的值。通常,由final定义的变量为常量,语法如下:

    final double PI=3.14;
    
  • final关键字定义的变量必须在声明时对其进行赋值操作。final除了可以修饰基本数据类型的常量,还可以修饰对象引用。由于数组可以被看作是一个对象,所以也可以用来修饰数组。一旦一个对象被修饰为final后,它只能恒定指向一个对象,无法将其改变以指定另外一个对象。一个既是static又是final的字段只占据一段不能改变的存储空间。

定义各种类型的final变量:

package com.lzw;

import static java.lang.System.*;

import java.util.*;
class Test {
    int i = 0;
}

public class FinalData {
    static Random rand = new Random();
    private final int VALUE_1 = 9; // 声明一个final常量
    private static final int VALUE_2 = 10; // 声明一个final、static常量
    private final Test test = new Test(); // 声明一个final引用
    private Test test2 = new Test(); // 声明一个不是final的引用
    private final int[] a = { 1, 2, 3, 4, 5, 6 }; // 声明一个定义为final的数组
    private final int i4 = rand.nextInt(20);
    private static final int i5 = rand.nextInt(20);

    public String toString() {
        return i4 + " " + i5 + " ";
    }

    public static void main(String[] args) {
        FinalData data = new FinalData();
        // data.test=new Test();
        //可以对指定为final的引用中的成员变量赋值
        //但不能将定义为final的引用指向其他引用
        // data.value2++;
        //不能改变定义为final的常量值
        data.test2 = new Test(); // 可以将没有定义为final的引用指向其他引用
        for (int i = 0; i < data.a.length; i++) {
            // a[i]=9;
            // //不能对定义为final的数组赋值
        }
        out.println(data);
        out.println("data2");
        out.println(new FinalData());
        // out.println(data);
    }
}

运行结果:

  • 在该案例中,被定义为final的常量定义时需要使用大写字母命名,并且中间使用下划线进行连接,这是JAVA的编码规则。同时,定义为final的数据无论是常量、对象引用还是数组,在主函数中都不可以被改变。
  • 一个被定义为final的对象引用只能指向唯一一个对象,不可以将它再指向其他对象。但是由于一个对象本身的值是可以改变的,因此为了使一个常量真正做到不可更改,可以将常量声明为static final。
package com.lzw;

import static java.lang.System.*;

import java.util.*;

public class FinalStaticData {
    private static Random rand = new Random(); // 实例化一个Random类对象
    // 随机产生0~10之间的随机数赋予定义为final的a1
    private final int a1 = rand.nextInt(10);
    // 随机产生0~10之间的随机数赋予定义为static final的a2
    private static final int a2 = rand.nextInt(10);

    public static void main(String[] args) {
        FinalStaticData fdata = new FinalStaticData(); // 实例化一个对象
        // 调用定义为final的a1
        out.println("重新实例化对象调用a1的值:" + fdata.a1);
        // 调用定义为static final的a2
        out.println("重新实例化对象调用a1的值:" + fdata.a2);
        // 实例化另外一个对象
        FinalStaticData fdata2 = new FinalStaticData();
        out.println("重新实例化对象调用a1的值:" + fdata2.a1);
        out.println("重新实例化对象调用a2的值:" + fdata2.a2);
    }
}

运行结果:

  • 从运行结果可以看到,定义为final的变量不是恒定不变的,将随机数赋予定义为final的变量,可以做到每次运行程序时改变a1的值。但是a2与a1不同,由于它是被声明为static final形式,所以在内存中为a2开辟了一个恒定不变的区域,当再次实例化一个对象时,仍然指向a2这块区域,所以a2的值保持不变。a2是在装载时被初始化,而不是每次创建新对象时都被初始化;而a1会在重新实例化对象时被更改。

3. final方法

  • 被定义为final的方法不能被重写。

  • 将方法定义为final类型,可以防止子类修改该类的定义与实现方式,同时定义为final的方法的执行效率要高于非final方法。当定义为final方法时,就无需用private关键字修饰。

class Parents {
    private final void doit() {
        System.out.println("父类.doit()");
    }

    final void doit2() {
        System.out.println("父类.doit2()");
    }

    public void doit3() {
        System.out.println("父类.doit3()");
    }
}

class Sub extends Parents {
    public final void doit() { // 在子类中定义一个doit()方法
        System.out.print("子类.doit()");
    }
    //  final void doit2(){     //final方法不能覆盖
//      System.out.println("子类.doit2()");
//  }
    public void doit3() {
        System.out.println("子类.doit3()");
    }
}

public class FinalMethod {
    public static void main(String[] args) {
        Sub s = new Sub(); // 实例化
        s.doit(); // 调用doit()方法
        Parents p = s; // 执行向上转型操作
        // p.doit(); //不能调用private方法
        p.doit2();
        p.doit3();
    }
}

运行结果:

  • 从本例可以看出,final方法不能被覆盖,例如,doit2()方法不能在子类中被重写,但在父类中定义了一个private final的doit()方法,同时在子类中也定义了一个doit()方法,表面看子类的doit()方法覆盖了父类的doit()方法,但是覆盖必须满足一个对象向上转型为它的基本类型并调用相同方法这样一个条件。例如,在主方法中使用parents p=s;语句执行向上转型操作,对象p只能调用正常覆盖的doit3()方法,却不能调用doit()方法,可见子类中的doit()方法并不是被正常覆盖,而是生成了一个新的方法。

4. final类

  • 定义为final的类不能被继承。如果希望一个类不被任何继承,并且不允许其他人对这个类进行任何改动,可以将这个类设置为final形式。语法如下:

    final  类名{}
    

final方法案例:

final class FinalClass {
    int a = 3;

    void doit() {
    }

    public static void main(String args[]) {
        FinalClass f = new FinalClass();
        f.a++;
        System.out.println(f.a);
    }
}

运行结果:

5. 内部类

  • 在类中再定义一个类,则将在类中再定义的那个类称为内部类。

成员内部类案例:

public class OuterClass {
    innerClass in = new innerClass(); // 在外部类实例化内部类对象引用
    
    public void ouf() {
        in.inf(); // 在外部类方法中调用内部类方法
    }
    
    class innerClass {
        innerClass() { // 内部类构造方法
        }
        
        public void inf() { // 内部类成员方法
        }
        
        int y = 0; // 定义内部类成员变量
    }
    
    public innerClass doit() { // 外部类方法,返回值为内部类引用
        // y=4; //外部类不可以直接访问内部类成员变量
        in.y = 4;
        return new innerClass(); // 返回内部类引用
    }
    
    public static void main(String args[]) {
        OuterClass out = new OuterClass();
        // 内部类的对象实例化操作必须在外部类或外部类中的非静态方法中实现
        OuterClass.innerClass in = out.doit();
        OuterClass.innerClass in2 = out.new innerClass();
    }
}
  • 内部类可以随意使用外部类的成员方法以及成员变量,尽管这些成员被修饰为private。内部类的实例一定要绑在外部类的实例上,如果从外部类中初始化一个内部类对象,那么内部类对象就会绑定在外部类对象上。

  • 如果在外部类和非静态方法之外实例化内部类对象,需要使用外部类。内部类的形式指定该对象的类型。

  • 内部类对象会依赖于外部类对象,除非已经存在一个外部类对象,否则类中不会出现内部类对象。

内部类向上转型为接口:

package com.lzw;

interface OutInterface { // 定义一个接口
    public void f();
}

public class InterfaceInner {
    public static void main(String args[]) {
        OuterClass2 out = new OuterClass2(); // 实例化一个OuterClass2对象
        // 调用doit()方法,返回一个OutInterface接口
        OutInterface outinter = out.doit();
        outinter.f(); // 调用f()方法
    }
}

class OuterClass2 {
    // 定义一个内部类实现OutInterface接口
    private class InnerClass implements OutInterface {
        InnerClass(String s) { // 内部类构造方法
            System.out.println(s);
        }

        public void f() { // 实现接口中的f()方法
            System.out.println("访问内部类中的f()方法");
        }
    }

    public OutInterface doit() { // 定义一个方法,返回值类型为OutInterface接口
        return new InnerClass("访问内部类构造方法");
    }
}

运行结果:

  • 从上述代码可以看出,OuterClass2类中定义了一个修饰权限为private的内部类,这个内部类实现了OutInterface接口,然后修改了doit()方法,使方法返回一个OutInterface接口。
  • 非内部类不能被声明为private或protected访问类型。

局部内部类

  • 内部类不仅仅可以在类中进行定义,也可以在类的局部位置定义,如在类的方法或者任意的作用域中均可以定义内部类。
interface OutInterface2 { // 定义一个接口
}
class OuterClass3 {
    public OutInterface2 doit(final String x) { // doit()方法参数为final类型
        // 在doit()方法中定义一个内部类
        class InnerClass2 implements OutInterface2 {
            InnerClass2(String s) {
                s = x;
                System.out.println(s);
            }
        }
        return new InnerClass2("doit");
    }
}
  • 从上述代码可以看出,内部类被定义在了doit()方法内部。但是需要注意的是,内部类InnerClass2是doit()方法的一部分,并非OuterClass3类中的一部分,所以在doit()方法的外部不能访问该内部类,但是该内部类可以访问当前代码块的常量以及此外部类的所有成员。

静态内部类

  • 如果创建静态内部类的对象,不需要其外部类的对象。
  • 不能从静态内部类的对象中访问非静态外部类的对象。

内部类的继承

  • 内部类和其他普通类一样,可以被继承。但是继承内部类比继承普通类复杂,需要设置专门的语法来完成。
在项目中创建OutputInnerClass类,使OutputInnerClass类继承ClassA类中的内部类ClassB:
public class OutputInnerClass extends ClassA.ClassB{    //继承内部类ClassB
    public OutputInnerClass(ClassA a){
        a.super();
    }
}
class ClassA{
    class ClassB{
    }
}
  • 在某个类继承内部类时,必须硬性给予这个类一个带参数的构造方法,并且该构造方法的参数为需要继承内部类的外部类的引用,同时在构造方法体中使用a.super()语句,这样才为继承提供了必要的对象引用。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容