类的高级概念

169 阅读11分钟

类的高级概念

static

    static关键字 -- 是一个可选修饰符,它本身的含义叫做“静态”。 它不仅只能修饰方法,
    
    它也可以修饰:属性、代码块、内部类 。

    那么static可以修饰其中两个:属性和方法。不能修饰构造方法。能够在类里面书写的两
    
    个内容:代码块(初始化块)以及内部类。

        类里面总共可以书写的内容是5个:属性、构造、方法、初始化块和内部类。其中:前
        
    三个是最常操作的。初始化块在大型项目当中可能会在部分关键类里面出现。

static 与 非static 的属性

    在一个类当中,属性也可以选择使用static进行修饰。

    A、语法上的对比:

    有static

        1、有static修饰的属性可以用"类名."的方式直接访问,也可以通过“对象.”的方式
        
        进行访问(但第二种方式,不推荐)。

        2、用static修饰的属性,是全类的所有对象共享一个值,无论是该类的哪个对象把这
        
        个值改了,那么大家的值都跟着变。

    非static

        1、非static修饰的属性是只能用“对象.”的方式访问;

        2、非static修饰的属性是每个对象身上有一个该属性的变量,存放了该对象该属性的
        
        值。从两者的第2点语法对比,我们领会Java语法设计者的设计意图。之所以让
        
        static修饰的属性能够用“类名.”的方式去访问,不是为了让我们方便,而是为了体现
        
        这个属性是全类共享的。而非static修饰的属性用"对象."的方式去访问,是为了清楚
        
        的表达到底要操作是哪个对象的该属性。

        在一个类当中,能够用static修饰的属性是不多见的,通常都需要在某个特定的问题
        
        域限制当中才找得到。static才是特例,通常都是非static的属性。根据经验,只有
        
        常量,我们能够直接设计为static

    原因:

        1、常量属性的值是在类的定义中给定的,每个对象身上都是同样的值(常量也不能
        
        改);所以没有必要在每个对象身上存放一份,只需要全类共享一个就可以了。

        2、常量属性的值在类定义中给定以后,外部也没有更改的权利,所以可以不用保密,
        
        直接public公布给外部看;

        3、所以,通常常量属性的修饰符是:public static final的。

        千万不要为了方便去把属性设计为static,一定要找到全类共享一个值的属性才做这
        
        种设计。

    B、内存上的区别:

        1、存放的位置不同

            非static修饰的属性是在对象本身身上,也就是在堆当中;

            static修饰的属性没有存在对象身上,也就不是在堆当中;而是单独作为类的共
            
            享信息存放在一个叫做“静态区”的内存空间里;一个类的一个static属性只划分
            
            一个存放空间。

        2、该属性在内存中产生的时机不同:

            非static属性是在产生对象的时候,在内存中产生;

            static的属性是在 *加载期* 产生的于内存中。

两个凡是:

    1、凡是用static修饰的内容,都是表示它与对象无关,只与类相关;

    所以:用static修饰的属性,我们又叫做“类属性”/“静态属性”,

    没有static修饰的属性,我们一般叫做“成员属性”

    2、凡是用static修饰的内容,都会在加载期搞事情。
    

static 和 非static修饰的 方法

    A、在语法上的对比:

    有static的方法:

        1、在调用该方法的时候,可以通过“类名.”的方式来访问,也可以通过"对象."的方式
        
        来访问;只不过第2种方式不推荐,但不会报错;

        2、在方法的内部实现中,静态方法只能调用本类的静态属性 或 静态方法;

    之所以不能调用的原因:

        原因1:静态方法是通过"类名."的方式调用,这个时候方法内部是没有当前对象这一目
        
        标,也就是说没有“this”,所以无法确认这里访问的非静态属性或方法到底是哪个对
        
        象的。这是站在面向对象的设计思想来表达的;

        原因2:当我们在进行类的编译和加载的时候,在进行加载的顺序划分时,JVM都是首先
        
        加载静态的,然后再加载非静态的。如果在加载静态内容时,发现里面有非静态的调
        
        用,这个时候JVM是不认识这些非静态内容的,因此报错。反过来,在加载非静态的方
        
        法时,由于静态内容已经加载过了,所以JVM认识,因此通过。

    非static的方法:

        1、在调用该方法的时候,只能通过“对象.”的方式访问。

        2、在方法的实现内部,即可以操作本类的静态属性和静态方法,也可以调用到本类的
        
        非静态属性和非静态方法;

        这样的设计同样表明了用static修饰的方法是跟对象无关的;非static的方法是一定
        
        要确定到底是哪个对象去做这件事情。所以,用static修饰的方法又被称之为“类方
        
        法”或“静态方法”;非static修饰的方法被称为“成员方法”。
        

   B、设计上的区别:

     我们要把工具类的工具方法,设计为static的;其他的都是非static的。

对初始化块的简单认知

    1、初始化块是我们可以在一个类里面定义的第4个内容。它的语法非常简单,就是直接在类
    
    里面,打上一对"{}",然后在"{}"里面书写代码。

    2、它从外观形式上非常像一个没有方法声明,只有方法实现的特殊语句块。在它内部,我
    
    们可以书写任意指令语句,包括:输入输出、变量声明、运算表达、流程控制;

    3、由于这个代码块是没有方法声明的,也不能接受参数和返回,所以很明显它是不能够在
    
    类的外部随意调用的。它的执行只能是在一个特定的时间点运行。 什么时候呢? 产生对象
    
    的时候,所以它也被称之为“实例初始化块”。每产生一个对象,就会被执行一次。

    实例初始化块到底在初始化一些什么呢?如果初始化对象的话,那么它的作用就和我们的构
    
    造方法重叠了。

        1、先后问题

            首先调用到构造方法,构造方法就会默认执行三个动作,

            先在内存中划分空间(对象存放的空间);

            然后在该空间划分属性;

            接下来就会先执行我们在实例初始化块当中书写的代码;

            再对属性进行初始化(这里的初始化是指有没有在声明属性的时候用代码赋初始值);

            完了以后再执行我们在构造方法当中书写的代码。

        2、哪些初始化动作放在初始化块,哪些动作是放在构造方法里面做?

            构造方法里面的初始化,主要做的事通过外部调用者传入的参数,给当前对象的
            
            属性赋值;

            如果某些属性不需要从外部接受参数赋初始值,那么我们更多的选用的是,在声
            
            明属性的时候直接使用“=”去赋值;

            而实例初始化块更多的作用不是用来给对象中的属性赋初始值的,而是用来在一
            
            些特殊场景当中执行非赋值的初始代码,比如:开启资源,开启通讯管道,开启
            
            线程、开启文件等等。

初始化块也分static 和 非static的

    前者也叫“静态初始化块”,后者叫做“实例初始化块”

        "静态初始化块"是在加载类的时候被执行的,而一个类在JVM当中只需要加载一次,所
        
        以它是首先被执行,且以后无论产生多少对象,甚至不产生对象,也不再被执行。

        “实例初始化块”是在产生对象的时候被执行,且产生多少个对象就会被执行多少次。

    如果说“实例初始化块”的执行时机和次数与构造方法有一定的重叠,导致它的使用量很少。
    
    但是“静态初始化块”由于其独特的执行时机和执行次数,导致它是没有被替换的可能的。

    很多资源的操作,包括开启呀,预加载呀,都是一次性完成的,没有必要每new一个对象就
    
    做一次,而且很多时候我们会把做这些动作放到程序的加载期而不是运行期,这是为了提升
    
    程序的运行速度。

静态初始化块也满足两个凡是:

    1、与类有关; --- 类加载的时候被执行

    与对象无关;--- 不能在其内部访问对象的非静态内容,没有this当前对象,与产生
        
    对象与否无关。

    2、加载期搞事情 --- 它搞的事情是在加载期被执行

内部类

    故名思义 -- 就是在一个类A的内部定义另外一个类B,那么类A就是外部类,类B就是A的内
    
    部类。

    首先需要明确:

        1、内部类一定要定义在外部类的"{}"里面,而不是简单的写在一篇java文件当中;写
        
        在一篇java文件当中,它们两个是平行关系,不是嵌套关系。

        2、内部类除了定义的位置有特殊性以外,它在本质上和普通的类没有区别;

        包括:

            a、仍然拥有属性、构造、行为、初始化块、甚至是它的内部类;

            b、内部类虽然和外部类在同一篇java文件当中定义,但是编译以后它拥有自己独
            
            立的class文件。
     
            c、内部类的类名,不是我们书写的简单名,而是和外部类的类名共同组成的。

内部类的分类

    首先根据内部类书写的位置,分为两大类:

    1、成员内部类;

        如果一个内部类是直接定义在外部类的“{}”当中,它的位置和外部类的属性、方法、
        
        构造,都处于平行位置,那么就叫做“成员内部类”。

        当然作为外部类的成员,它是可以有访问修饰符的,用来控制这个内部类是否可以被外
        
        部使用。

        成员内部类编译后的class文件名:外部类名字$内部类的名字

    2、局部内部类;

        如果一个内部类是定义在外部类的某个方法当中,它的位置和这个方法的局部变量保持
        
        一致,那么就叫做“局部内部类”。

        作为方法的内部定义,跟局部变量一样出了方法就认为消失不在了,所以它只能在这个
        
        方法内部使用,不能有访问修饰符。

        局部内部类编译后的class文件名:外部类的名$序号内部类名字

        这里序号从1开始,是同名局部内部类编译的顺序序号。
        

    再根据语法特殊性,对“成员内部类”和“局部内部类”再分类:

        1、成员内部类分为:普通成员内部类 和 静态内部类;

        区别就在于 加不加 static 关键字;

        2、局部内部类分为:普通的局部内部类 和 匿名内部类

        所谓“匿名”就是没有给这个类起类名,匿名内部类是在定义的时候马上产生对象,只
        
        能使用一次。

四种内部类的使用语法

1、普通的成员内部类

    要想用普通的成员内部类,首先要先产生外部类的对象,然后用"外部类对象.new" 的语法 
    
    去产生内部类对象。

        ```java

            OutClass1 out = new OutClass1();

            OutClass1.InnerClass1 inner1 = out.new InnerClass1();

        ```

2、静态内部类

    要用静态内部类,由于有static修饰符,所以这个静态内部类与它所属的外部类对象无
    
    关,因此可以不需要产生外部类对象,直接用外部类类名访问。

        ```java

            OutClass1.InnerClass2 inner2 = new OutClass1.InnerClass2();

         ```

3、局部内部类

    局部内部类只能在定义它的方法之内使用。
    
        ```java

            InnerClass3 inner3 = new InnerClass3();

        ```
        
    内部类当中书写this,代表的是当前的内部类对象,如果要表示它所关联的外部类对象,那
    
    么要写成“外部类类名.this”;

    局部内部类的方法里面,能够操作到外部类的属性,但是不能操作到所属方法的局部变量。

4、匿名内部类

    匿名内部类是在new对象的同时,去定义这个对象里面的属性和行为,然后由于没有给这个
    
    类型取名字,所以只能用这一次。
    
        ```java

            new Object(){

            private int a;

            public void test(){



            }

            }.test();

    在new对象的同时,去定义这个对象里面有哪些属性和行为。由于这个对象有了新增的属性
    
    和行为,所以它不属于new关键字后面那个类型了,而是属于一种新的具有a属性和test方
    
    法的类型,但这种类型没有取名字。