JavaSE学习笔记 4

        有一段时间没更新了,但还是老话,学习仍在进行,并未放弃,接下来直接开始总结学习的内容。

        (一)内部类

        关于内部类的内容,实际上当初在学习的时候也没有学的很深,仅作了解,依照老师的意思,这块内容在数据结构的时候会经常用到,由于我才刚刚开始数据结构的学习,因此对内部类的理解肯定是不够到位的,这里也就简单做一个说明吧。

        内部类这个名字其实取的是相当的通俗易懂的,既然有内部类,那就有外部类,那啥是外部类,啥又是内部类呢?俗话说的好,内外、远近、好坏都是相对而言的,实际上,是有了内部类,才有了外部类的说法,即一开始只有一个类A,然后在这个类的内部又定义了一个类B,这个类B就叫做内部类,而类A自然就是外部类了。这里简单再提一下,内部类既然是包含再外部类内部的,那么他们必然是在同一个java源文件内的,但是在编译的时候,内部类会单独产生一个字节码文件。

                ①实例内部类

                如果内部类没有被static修饰,那么这个内部类,我们称之为实例内部类,实力内部类主要有那么几个特性,首先实例内部类可以访问外部类任意权限的成员变量和成员方法;如果实例内部类跟外部类具有相同名称的成员变量和成员方法时,当实例内部类直接调用时,会优先调用自己的,如果一定想访问外部类的同名变量或方法,那么需要采用下述格式:

外部类名.this.变量名或方法名

                在其他类里,如何实例化一个实例内部类并访问它呢?方法于普通实例化略有不同,因为实例内部类是依赖于对象的,所以如果实例化了外部类,内部类并不会被同步实例化,所以需要先实例化外部类,再实例化内部类,方法如下:

public class A {
    private int a = -1;
    int b = -2;
    protected int c = -3;
    public int d = -4;
    public static int e = -5;
    class B {
        private int a = -11;
        int b = -12;
        protected int c = -13;
        public int d = -14;
        public int e = -15;
        public void print() {
            System.out.println("内部类的print");
        }
    }

    public void print() {
        System.out.println("外部类的print");
    }
}

public class Main {
    public static void main(String[] args) {
        A outClass = new A();
        A.B innerClass1 = outClass.new B();
        A.B innerClass2 = new A().new B();
//        A.B innerClass3 = new B();// 在没有一个实例化的A情况下,没法单独实例化一个B.
        innerClass1.print();// 打印结果:内部类的print
        outClass.print();// 打印结果:外部类的print
    }
}

                接下来,我们体现一下内部类于外部类变量访问权限的关系,将代码修改如下:

public class A {
    private int a = -1;
    int b = -2;
    protected int c = -3;
    public int d = -4;
    public static int e = -5;
    class B {
        private int a = -11;
        int b = -12;
        protected int c = -13;
        public int d = -14;
        public int e = -15;
        public void print() {
            System.out.println("内部类      : a = " + a + " b = " + b + " c = " + c + " d = " + d + " e = " + e);
            System.out.println("this.内部类 : a = " + this.a + " b = " + this.b + " c = " + this.c + " d = " + this.d + " e = " + this.e);
            System.out.println("A.内部类    : a = " + A.this.a + " b = " + A.this.b + " c = " + A.this.c + " d = " + A.this.d + " e = " + A.this.e);
            System.out.println("==================================================================================");
        }
    }

    public void print() {
        System.out.println("外部类      : a = " + a + " b = " + b + " c = " + c + " d = " + d + " e = " + e);
        System.out.println("this.外部类 : a = " + this.a + " b = " + this.b + " c = " + this.c + " d = " + this.d + " e = " + this.e);
//        System.out.println("B.内部类    : a = " + B.this.a + " b = " + B.this.b + " c = " + B.this.c + " d = " + B.this.d + " e = " + B.this.e);// 报错,无法编译通过
        
    }
}

public class Main {
    public static void main(String[] args) {
        A outClass = new A();
        A.B innerClass1 = outClass.new B();
        A.B innerClass2 = new A().new B();
        innerClass1.print();
        outClass.print();
    }
}

                打印结果如下:

                可以比较清晰地看到,内部类不加任何修饰符和加this关键字时,调用地都是自己地成员变量,只有在使用外部类名称.this.变量名时可以调用到外部类地成员变量;而外部类不加任何修饰符和加this关键字时,调用地都是自己地成员变量,并且外部类不能使用内部类名称.this.变量名的方式去访问内部类的变量。这些尝试都是关于成员属性的,那成员方法呢?这里就不再演示了,实际上跟成员变量的情况是一致的,直接使用this或者不加任何修饰符,都是直接访问自己的,外部类不能直接访问内部类的方法,但是内部类可以通过外部类名称.this.方法名的方式去访问外部类的方法。但是有一点要注意,如果成员名称不一样,this都是特指对应类自己的,而内部类的外部类名称.this.方式则是特指外部类的,是不能跨类访问的,当什么都不加的时候,内部类可以直接访问到外部类的对应成员,但外部类是不能访问内部类对应成员的。

                ②静态内部类

                相比于实例内部类,静态内部类规则相对简单一点,静态内部类就是被static修饰的内部类,它的特点就是它只能访问外部类的静态成员,而且实例化静态内部类的时候不用先实例化外部类。

                ③局部内部类

                局部内部类就是定义在方法内部的类,因而不能用public和static去修饰,这个类只能在这个方法的内部使用,这个方法执行结束时,这个类也就消失了。它跟正常内部类一样,也会有一个单独的字节码文件,局部内部类的应用场景也极少。

                ④匿名内部类

                匿名内部类其实就是没有名字的类,它跟实例内部类相比,就是没有了名字。在某些情况下是极其好用的。

        (二)继承

                ①概念

                我们都知道面向对象的设计,有三大特性,封装、继承、多态,前面已经讲过了封装的相关特性,接下来,将依次讲解继承和多态的相关特性。

                关于继承,我们先简单的用不抽象的说法去描述他,我们都知道猫和老鼠不一样,但是他们都是动物,并且具有动物的相关特性,即“一般以有机物为食,能够自主运动或能够活动的有感觉的生物”。假如我们定义类猫和一个类老鼠,我们要用属性、行为去描述定义他们,那么关于“能够自主运动”、“有感觉"、“生物”这三个特性,我们都要描述进去,当我们定义好以后会发现,不管是猫还是老鼠,这三个特性是一摸一样的。当前我们仅有猫和老鼠两个,假如我们有鸟、哈巴狗、绵羊、鸭子、企鹅………………,我们如果都去定义一个类,我们会发现不管哪个类,这三个特性都是不变的,那如果我们每个特性都用一行代码去定义,只有猫和老鼠的时候,我们这些代码需要敲6次,但如果有n个类,我们就需要敲3 × n次,代码都是一样的,并且十分冗余。

                所以为了解决上面这种情况,我们就想到了定义一个类,这个类叫动物,这个动物类里,有这三个特性,然后让包括猫和老鼠的这些类全部继承这个动物类,继承之后,猫和老鼠就自动拥有了这个动物类里的三个特性,这时,我们将猫、老鼠的类称为子类/派生类,而将动物类成为父类/超类/基类。

                从代码角度来看,显而易见的,本来需要敲3×n次的代码,我们只需要敲3次就结束了,因为子类会自动继承父类的成员和行为。所以继承的作用就是用来进行共性抽取,以减少代码量,实现代码复用的。

                ②继承的语法

                既然要表达上面那种猫、老鼠与动物类之间的关系,我们必然需要有配套的语法,表达这些子类与父类的继承关系,我们需要使用一个关键字“extends”,这个关键字除了在继承方面会使用,在后面的多态方面也会使用到,在这里面,我们仅谈及继承方面的作用,实际使用的代码方式如下:

class 子类名 extends 父类名 {...}

                是用extends关键字,我们可以表示出子类继承于父类,而这种继承是可以多段继承的,我们可以让父类动物继续继承生物这一类,例子如下:


class 动物 extends 生物 {...}
class 猫 extends 动物 {...}

                 从上面的例子可以看出,Java是允许多段继承的,一般来说子类会比父类多出一些属性和行为,如果没有多一些属性和行为,那么就没有了继承的必要,但是如果非要什么属性和行为都不新增,从语法角度也不会说是错的,这个问题更多的是属于程序设计角度上的问题。

                ③子类访问父类成员变量

                我们依旧是跟内部类和外部类一样,以变量名是否相同来分开讨论这个问题,我们首先讨论当子类和父类变量名不同的情况,当两者变量名不同的时候,其实是最简单的,子类可以直接使用变量名去访问父类的变量,不需要加this等其他修饰符;但是如果两者变量名相同的时候,情况就会稍微有点变化,这时候如果不加任何修饰符,子类访问相同名称的成员时,将会遵守就近原则,优先访问子类自己的成员变量。

                ④子类访问父类成员方法

                子类访问父类成员方法的情况,其实跟访问父类的成员变量时基本一致的,也就是自己有就访问自己的,自己没有就访问父类的,如果两边都没有,那么就会报错;如果子类于父类的方法是同名的,这时候会有两种情况,一种是方法名一致,但是形参列表不同,那么就会发生重载;如果是方法名、形参列表、返回值一摸一样,就会发生重写,当子类调用对应方法时,就会直接调用自己的,因为没有通过父类访问,所以不会发生实质上的重写,也就是只会访问自己写的方法。

                ⑤super关键字

                前面③、④内,我们也说了,假如当子类与父类的成员变量名一致时,子类只能直接访问到自己的变量;成员方法名一致时,假如发生重载,那么根据传入的实参情况决定能否访问到父类的方法,如果是重写的情况,那么子类只能直接访问到子类自己的成员方法,而不能访问到父类的方法,那么如果我们一定要访问父类的成员变量、成员方法,那么有办法能解决吗?

                这里Java就给出了一个关键字,super,利用super,就能直接指明我们想要访问的是父类的成员变量、成员方法,用法上跟this也是大同小异,但是跟this有很大区别,首先this是一个引用数据类型,他复制了实参对应的对象引用,而super,他只是一个标记,用来告诉程序,我们要访问的是父类,所以这两者虽然在外形上看非常相似,但在本质上是不同的。

                最后就是super的使用限制,super只能在非静态的方法中使用,原因是静态方法就不在与对象挂钩,静态方法属于类。

                ⑥子类构造方法

                在调用子类的构造方法时,需要先调用父类的构造方法,这个从常理上来说也是对的,子类继承父类,那肯定是先有父类,再有子类。

                当我们没有给出子类和父类的构造方法时,父类跟正常一样,程序会自动默认有一个空的构造方法,而在子类这边,程序会默认有一个空的super父类构造方法,并且这个super构造方法位于子类构造方法的第一层 ,由于this调用构造方法和super调用构造方法都要求位于子类构造方法的最上层,所以this和super调用构造方法的方式在子类构造方法中是不被允许的。

               ⑦this和super的异同

                先说相同点吧,this和super都能调用构造方法,在构造方法内都要求处于第一句的位置,两者不能同时存在;两者都是关键字,且都不能用于静态方法。

                两者的不同点前面也带过一下,this是作为对象的引入传入的,本质上是复制了实例化对象的引用,而super仅仅只是作为标识,告诉程序,我们要引用或者说调用的是父类的变量、方法。this用于代表子类自己的变量、方法,super用于代表访问父类的变量、方法;子类的构造方法内必然会带有super的父类构造方法,而this构造方法的调用是可以不用的。

                ⑧代码块、构造方法的执行顺序

                没有继承关系的情况下,是按静态代码块、实例代码块、构造方法;但如果有了继承关系,这里情况就更复杂了,下面列出实际的运行顺序:

                父类静态代码块、子类静态代码块、父类实例代码块、父类构造方法、子类实例代码块、子类构造方法。

                而上述顺序中,假如已经实例过依次对象,那么当下一次实例时,关于静态代码块的部分就不会再执行。而关于子类、父类构造代码块和构造方法的关系,其实也不难理解,前面我们已经知道了子类构造完成前,要先完成父类的构造,那么父类的构造必然位于子类之前。然后实例代码块在编译时会被放到对应构造方法的上面,所以可以把实例代码块也看成构造代码块的一部分,并且实例代码块位于构造方法上面的位置,所以我们可以比较自然地得出一个顺序,首先父类实例代码块、实例构造方法,然后子类实例代码块,子类构造方法。至于静态代码块,它随着类被加载就会一起被加载,父类先于子类被加载出来。

                ⑨protected关键字

                前面有关于成员变量、成员方法权限地相关说法,四个权限,private、default(默认/frendly)、protected、public,分别对应类权限、包权限、子类权限、任意。顾名思义,类权限就是说这个成员方法、成员变量只能在类内访问,要注意地一点是,这个类内访问,是代码层面意义上的,比如下面这段代码:

public class Main {
    public static void main(String[] args) {
        Student z = new Student();
        z.setA(10); // 输出结果:a = 10  student = 20
    }
}

class Student {
    private int a = 10;

    public void setA(int a) {
        this.a = a; // 是被允许的
        Student student = new Student();
        student.a = a + 10; // 是被允许的
        System.out.println("a = " + this.a + "  student = " + student.a);
    }
}

                可以看到,在Student类中,我们定义了一个setA成员方法,我们使用关键字this,直接对当前对象的私有成员变量a进行赋值,然后我们又实例化了一个Student对象,然后直接使用student.a去访问新生成对象内的私有成员变量a,并对其进行赋值。然后我们发现编译没报错,并且正常输出了结果。

                由此我们可以知道,所谓的类内访问是在代码层面上的,也就是主要这个操作是在对应类的代码范围内进行的,那就是被允许的,而不是概念意义上的类内,因为我们新生成的student对象显然跟调用这个setA的Student对象,不是同一个类对象,这是一个小细节。

                接着就是默认权限,这个权限跟类类似,允许同一个包内的情况下,去访问;

                而protected就跟我们讲的继承有关联了,当不是同一个包的情况下,如果外面包的类是继承了我们当前包内的某个类,两者是继承关系的话,那么就允许进行跨包访问。

                public就不多说了,就是哪哪都行,哪哪都允许访问。

                ⑩继承关系

                Java内不允许多继承的继承方式,也就是一个类,同时继承多个父类;但是Java允许一个父类,可以有多个子类对象;单继承、多层继承自然是不必说的。

                但是依照老师的说法,多层继承我们为了不让关系太过混乱,一般就只最多做到三层继承即停止,为了防止在三层继承情况下,还有人继续叠加继承关系,我们会用final关键字去修饰对应的子类。

                final去修饰成员变量时,这个变量就变为了常量;final修饰成员方法时,说明这个成员方法不允许被重写;final去修饰类时,说明这个类不允许被继承。

                最后就是一个组合的知识了,其实组合的内容很容易理解,说白了就是往当前类内放别的类的实例化对象,打个比方,笔记本电脑类里我们可以放显示器类、触控板类等等。通过这种类之间的组合,可以让我们进一步对代码进行复用。

                至于多态的内容,就留给下个笔记吧,这个笔记也写了不少了,写太长以后我自己看起来也不方便。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值