Java语言学习笔记08——面向对象编程(高级)

一、类变量

问题引入:假如有一群人在玩游戏,时不时会有新的小孩加入,请问我们如何知道现在共有多少人在玩游戏?

如果用传统方法解决,那就是在main()中定义一个int型变量count,每来一个人就让count++,最后看count的值就知道有多少人在玩了。

但这么做,没有用到面向对象的思想,我们将来要访问count变量会变得很麻烦。

为此,我们引入 类变量/静态变量 的概念。

假如我们可以创建一个玩家类,在这个玩家类里面添加一个所有玩家对象都共享的int属性count,每次新创建一个玩家对象时就让count+1,这样就能解决我们的需求了!

一、语法与内存解析

class Player(){
    public static int count = 0;
}

其中,static修饰符表示这是一个类变量(英文原意是静态的,因此也叫静态变量)。

类变量也叫静态变量/静态属性,该变量的最大特点就是会被类的所有对象实例共享,即取都是取相同的值,修改也都是修改同一个变量。

定义类变量的语法:

访问修饰符 static 数据类型 变量名;[推荐]
static 访问修饰符 属性类型 变量名;

如何访问类变量

类名.类变量名;[推荐]
对象名.类变量名;

类变量是随着类的加载而创建的,因此,即使没有创建对象实例,也能被访问。

jdk8以前我们认为是在方法区内,类加载的时候加载到静态域中;jdk8以后我们认为是在堆里面对应的class对象实例里面。

二、使用细节

1、什么时候需要使用类变量?

答:当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量。

2、类变量与实例变量(普通属性)的区别?

答:类变量由该类的所有对象共享,而实例变量是由每个对象独享的。

在语法上的区别则是定义时有无用static修饰。

并且,实例变量无法通过类名.实例变量名来进行访问。

值得注意的是,类变量的访问修饰符和普通变量的规则是一样的。类变量的访问也必须遵守访问权限。

3、类变量的生命周期?

答:随着类的加载而初始化,随着类的消亡而销毁。

二、类方法

一、基本介绍

类方法也叫静态方法。定义如下:

访问修饰符 static 数据返回类型 方法名(){};[推荐]
static 访问修饰符 数据返回类型 方法名(){};

在满足访问修饰符权限的情况下,类方法的调用方式如下:

类名.类方法名;
对象名.类方法名;

当方法中不涉及到任何和对象相关的成员时,则可以将方法设计成静态方法,提高开发效率。

例如java自带的工具类中的utils。

我们在实际开发中,如果希望不创建实例也可以调用某个方法(即将其当作工具来使用,如打印一维数组、冒泡排序等),此时就很适合将方法做成静态方法。

二、注意事项

1、类方法和普通方法都是随着类的加载而加载,将结构信息储存在方法区。类方法中无this的参数(因为上面说了类方法的一般使用范围)。

2、类方法可以通过类名或者对象名调用,而普通方法只能通过类名调用。

3、类方法(静态方法)中只能访问静态变量或静态方法。(不然就违背了使用时不涉及任何与对象相关的成员这一原则)。

4、类方法中不允许使用和对象有关的关键字,如this和super。但普通方法可以同时访问普通变量/方法和静态变量/方法。

总结就是,静态方法只能访问静态成员。非静态方法可以访问静态成员和非静态成员(遵守访问权限的条件下)。

5、对于构造器来说,只有当一个实例对象被new出来的时候才会触发,且如果没有被static修饰,就将其看作非静态方法。

三、main()

一、深入理解main方法

public static void main(String[] args){}

1、java虚拟机需要调用类的main(),但与当前类不是同一个类,因此访问权限必须是 public

2、java虚拟机在执行main()时不必创建对象,因此该方法必须是 static

3、main()接受一个String类型的数组参数,用于保存执行java命令的时候传递给所运行的类的参数。

在控制台:
Java 运行的类名 第一个参数 第二个参数 ...
这些参数会存放到args[0],args[1] ...

二、注意事项

1、在main()中我们可以直接调用其所在类的静态方法和静态属性,但不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象取访问类中的非静态成员。

四、代码块

一、基本介绍

代码块又称为初始化块,属于类中的成员,类似于方法,将语句封装在方法体中,用{}包围起来。

但和方法不同的是,其没有方法名,没有返回值,没有参数,只有方法体,且不通过类或对象显式调用,而是在加载类时或创建对象时隐式调用。

基本语法为:

[修饰符]{
    代码
};

需要注意的是:

1、修饰符只有不写和static两种选择。

2、使用static修饰的叫静态代码块,没有static修饰的叫普通代码块

3、分号可以写上,也可以省略

代码块的好处是,相当于另一种形式的构造器,或构造器的补充机制,可以做初始化的操作。若多个构造器中都有重复的语句,可以抽象出来提取到代码块中,提高代码复用性。

代码块的调用优先于构造器!

二、注意事项

1、静态代码块的作用就是对类进行初始化,随着类的加载而执行,且只会执行一次。而普通代码块当每创建一个对象就会执行一次,但若只是调用类的静态成员,则普通代码块不会执行。

2、类什么时候被加载?有以下几种情况:

        1、创建本类对象实例时(new)

        2、创建子类对象实例时,父类也会被加载

        3、使用类的静态成员时(如静态属性,静态方法)

3、继承关系先加载父类再加载子类,所以会先执行父类的静态代码块再执行子类的静态代码块。

4、创建一个对象时,在一个类内的调用顺序:

        1、调用静态代码块和静态属性初始化(这俩优先级一样,按照定义时的顺序调用)

        2、调用普通代码块和普通属性初始化(这俩优先级一样,按照定义时的顺序调用)

        3、调用构造器

5、构造器最前面其实隐含了super()和调用普通代码块,而后才执行构造器自己的内容;而静态的成员在类加载的时候就执行完了,因此优先于普通代码块和构造器执行。

class A{
    public A(){//构造器
    //隐含(1)super()
    //隐含(2)调用普通代码块
    构造器内容;
    }
}

6、创建子类对象时(涉及继承关系时)的调用顺序:

        1、父类的静态代码块和静态属性(优先级一样,按照定义时的顺序调用)

        2、子类的静态代码块和静态属性(优先级一样,按照定义时的顺序调用)

        3、父类的普通代码块和普通属性(优先级一样,按照定义时的顺序调用)

        4、父类的构造器

        5、子类的普通代码块和普通属性(优先级一样,按照定义时的顺序调用)

        6、子类的构造器

7、静态代码块只能直接调用静态成员(属性和方法),普通代码块可以调用任意成员。

 总结:先加载类,再创建对象。加载类的时候就执行静态,创建对象的时候才执行普通。加载与创建都遵循继承顺序原则。继承关系先加载父类再加载子类,所以会先执行父类的静态代码块再执行子类的静态代码块。

五、单例设计模式

一、什么是设计模式?

设计模式是静态方法和属性的经典使用方式,是经过大量实践后总结出来最优的代码结构、编程风格和解决问题的思考方式

设计模式类似于经典的棋谱。

二、什么是单例模式?

所谓类的单例设计模式,就是采取一定的办法保证在整个软件系统中,对某个类只能存在一个对象实例,且该类只提供一个取得其对象实例的方法。

单例模式存在两种方式:1、饿汉式 2、懒汉式

1、饿汉式

构造步骤:

1、构造器私有化,以防止直接new

2、类的内部创建私有化对象

3、向外暴露一个静态的公共方法,可以返回刚刚创建的对象实例

class Example{
    //构造器私有化,防止直接new
    private Example(){}
    //创建一个对象实例
    private static Example instance = new Example();
    //提供一个public的静态方法返回刚刚创建的对象实例
    public static Example getInstance(){
        return instance;
    }
}
优点:

不存在线程安全问题。

缺点:

有可能还没用着该类的对象,但已经在初始化类的时候就帮你创建好了对象,很“着急”,可能造成资源浪费。

2、懒汉式

步骤如上,区别在于公共方法有所不同:

class Example{
    //构造器私有化,防止直接new
    private Example(){}
    //提供一个对象实例的引用,但先不创建对象实例
    private static Example instance;
    //提供一个public的静态方法返回对象实例,同时保证只有用到时,才会创建对象实例
    public static Example getInstance(){
        if(instance == null){
            instance = new Example();
        }
        return instance;
    }
}

优缺点和饿汉式对应,两者可以说是互补。 

六、final关键字

一、基本介绍

final(最终、最后的)关键字可以用于修饰类、属性、方法和局部变量。

在某些情况下,我们会使用到final关键字:

1、当不希望类被继承时,可以用final修饰类。

2、当不希望父类的某个方法被子类覆盖/重写时,可以用final关键字修饰:

访问修饰符 final 返回类型 方法名()

3、当不希望类的某个属性的值被修改,可以用final修饰:

class A{
    public final double TAX_RATE = 0.08;
}

4、当不希望某个局部变量被修改,可以用final修饰:

class B{
    public void method(){
        final double NUM =0.01;
    }
}

(回忆小助手:所谓局部变量就是在方法内声明的变量~)

一般用final修饰的不希望别人修改的变量我们都全部大写(还记得之前说常量如何命名吗?不能修改的变量,和常量有什么区别?)

二、使用细节与注意事项

1、final修饰的属性又叫常量,一般命名格式为XX_XX_XX,且必须赋初始值。赋初始值的位置可以在如下地方(任选其一即可):

        1、定义时

        2、在构造器中

        3、在代码块中

2、如果final修饰的属性是静态的,则初始化的位置只能是 定义时在静态代码块中,不能是构造器中赋值。(因为类加载的时候就要初始化,但此时构造器还没调用,没法给final属性赋值)

3、final类不能被继承,但可以实例化对象

4、如果类不是final类,但含有final方法,则该方法虽然不能重写,但可以被继承。

5、一般来说,如果类是final类,则没必要将类里面的方法修饰为final方法

6、final不能修饰构造器

7、final和static往往搭配使用,效率更高,不会导致类加载,因为底层编译器做了优化处理。

class Exp{
    public final static int num = 10000;
    static{
        System.out.println("静态代码块被执行");
    }
}

如果不加final的话则会导致输出下面的静态代码被执行,即加载类导致静态代码块执行。final和static的顺序是无所谓的。

8、包装类如Integer,Double,Float,Boolean都是final类,String也是final类。

9、final也可以修饰函数的形参

七、抽象类

一、概念引入

在我们之前给动物喂食的时候,我们会遇到一个麻烦:

动物都有eat()的行为,因此我们可以把eat()抽象到父类之中。但是,不同的动物吃的对象不一样,我们只能抽象出这个行为,却没办法统一提取吃的对象,也就是说,父类的方法具有不确定性。

为了解决这个问题,我们引入抽象类的概念。

当父类的某些方法需要声明,却不确定如何实现的时候,我们将其声明为抽象方法,这个类就称为抽象类。

而所谓的抽象方法,就是指没有实现的方法,具体来说即不存在方法体{}的方法。

当一个类中存在抽象方法的时候,需要将该类声明为abstract类。

一般来说,抽象类会被继承,由其子类(通过重写Override)来实现抽象方法。

例子如下:

abstract class Animal{
    String name;
    int age;
    abstract public void eat();
}

二、使用细节

1、定义抽象类时顺序如下:

访问修饰符 abstract 类名{}

(访问修饰符总是在前面)

2、定义抽象方法时顺序如下:

访问修饰符 abstract 返回类型 方法名(参数列表);//注意,没有{},即没有方法体!

3、抽象类的价值更多在于设计功能,然后让子类继承并实现抽象类。是考官比较爱问的考点,在框架和设计模式使用较多。

4、抽象类不能被实例化。

5、抽象类不一定要包含abstract方法,但包含abstract方法的一定是抽象类。

6、abstract只能修饰类和方法,不能修饰属性和其他的成员。

7、抽象类可以拥有任意的成员(例如非抽象方法,构造器之类的,因为本质上还是类)。

8、如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类。

9、抽象方法不能再用private、final和static来修饰,因为这些关键字都是和重写相违背的。

10、抽象类一般的使用场景:

提取出公共的部分,然后中间要变化修改的部分用抽象类或者抽象方法来代替,后面再根据情况进行编写。

例如:

abstract class Template{//抽象类
    public abstract void job();//抽象方法
    public void caleTimes(){//统计耗时
        long start = System.currentTimeMillis();
        job();//抽象方法动态绑定机制
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end - start));
    }
}

八、接口

一、需求引入

生活中,我们有一种接口叫USB,各种设备都可以通过这个USB接口接入我们的电脑,方便我们进行各种操作。这是由于制作电脑接口的厂家和做各种设备的厂家都遵循了统一的规定,包括尺寸、排线等等。

我们在Java编程之中也会存在类似的需求。我们希望可以有一种被制定好标准的接口,由不同的类接入,接入的时候就能实现不同的功能。

二、基本介绍

在Java语言中,接口就是给出一些没有实现的方法,封装到一起,当某个类要使用的时候,再根据具体情况把这些方法写出来。

其定义语法为:

interface 接口名{
    接口属性
    接口方法
}
class 类名 implements 接口名{
    类属性;
    类方法;
    必须实现接口的抽象方法
}

在JDK7.0之前,接口里的所有方法都是抽象方法(在接口中,抽象方法可以省略abstract关键字,即不写默认是abstract,而不是default),没有方法体。

在JDK8.0之后,接口可以有静态方法,默认方法(需要用default关键字修饰),即接口中可以有方法的具体实现。

三、使用场景

理解接口的概念并不难,但容易让人感到疑惑的地方是何时应该使用接口。

举一个例子方便大家理解:

现在我们要做一个项目,为了控制和管理软件,项目经理可以定义一些接口,让程序员去具体实现。例如,有三个程序员,分别需要各自编写不同的类,来实现对Mysql、Oracle、DB2数据库的connect()与close(),那么这时,项目经理在接口中不需要关心具体如何实现,他只需要给出这些方法叫什么,能执行什么功能即可。

不然,可能会出现程序员A将连接方法叫做mysqlConnect(),程序员B将连接方法叫做oracleConnect(),大家各叫各的,不方便统一管理和使用。

也就是说,接口的引入是为了方便统一管理方法名等规范。

四、使用细节与注意事项

1、接口不是类,不能被实例化。

2、接口中所有的方法都是public方法,而接口中的抽象方法可以省略abstract关键字,因此接口中的方法也不可以有{}。

3、一个普通类若实现接口,必须将该接口的所有方法都实现;一个抽象类实现接口,则可以不用实现接口的方法。

4、一个类同时可以实现多个接口,但只能继承一个类。

interface IA{
    void hi();
}

interface IB{
    void say();
}

class test implements IA,IB{
}

5、接口中的属性只能是public static final的,即使没写出来也是。

6、接口中属性的访问方式:

接口名.属性名

7、一个接口不可以继承其他的类,但可以继承多个别的接口。

interface IC extends IA,IB{};

8、接口的修饰符只能是public和默认,这点和类的修饰符是一样的。

注意区别类继承多个接口和接口继承多个接口!一个是implements,一个是extends!但两者可以类比,implement接口可以类比extend类!算是对extends的一种补充!

五、实现接口vs继承类

看到这,大家可能会迷茫,说这个实现接口和继承类好相似啊,那他们之间究竟有什么区别呢?

其实,我们可以将实现接口,看作是对继承类的一种补充方式。

因为Java是单继承机制,子类自动拥有父类的功能,但若子类想要扩展其他的功能,则需要通过实现接口的方式来扩展。

举个例子,Ben10大家都看过吧?小班作为人,自然继承了爷爷作为人会思考的能力(子类对父类的继承),但他要变身的话,就得通过手表这个接口来实现了。

具体而言,实现接口和继承类的区别如下:

1、接口和继承解决的问题不同。

继承的价值在于,解决代码复用性和可维护性。

接口的价值在于,设计好各种方法的规范,让其他类去实现这些方法。

2、接口比继承更加灵活。

继承需要满足is-a关系,而接口只需满足like-a的关系。

3、接口在一定程度上实现了代码解耦。

六、接口的多态特性

接口的多态特性体现为:

1、多态参数:可以传递多个实现了接口的类的对象。

2、多态数组:在一个接口类型的数组中,可以存放实现了接口的不同类的对象。

3、接口存在多态传递现象:即,某个类实现了一个接口,而这个接口又继承了另一个接口,则相当于这个类也同时实现了这两个接口。

七、类定义的完善

引入了接口之后,我们可以将类的定义进一步完善为如下形式:

package 包名;
class 类名 extends 父类 implements 接口{
    成员属性/变量;
    构造方法/构造器;
    成员方法;
    代码块;
}

小结:类的五大成员分别为:
1、属性
2、方法
3、构造器
4、代码块
5、内部类(后面会讲到)

九、内部类(待补充)

 一、基本介绍

(类的)内部类,意思就是套娃。

一个类的内部又完整地嵌套了另一个类结构。

则外层的类被称为外部类(outer class),内层的类被称为内部类(inner class)。

内部类是类的第五大成员,最大的特点是其可以直接访问外部类的私有属性(因为是在外部类的内部,根据private修饰符的权限,可以访问),并且可以体现类与类之间的包含关系。

其基本的语法如下:

class Outer{//外部类
    class Inner{//内部类
        ...
    }
}
class Other{//外部其他类
}

内部类的分类如下:

1、定义在外部类局部位置上(例如外部类的方法中)

        (1)局部内部类(有类名)

        (2)匿名内部类(没有类名,重点)

2、定义在外部类的成员位置上

        (1)成员内部类(不使用static修饰)

        (2)静态内部类(使用static修饰)

下面我们依次进行讨论。

二、局部内部类

局部内部类是定义在外部类的局部位置,比如方法之中,并且有类名

1、可以直接访问外部类的所有成员,包括私有的。

2、不能添加访问修饰符,因为其地位就是一个方法内的局部变量,而局部变量是不能用修饰符的,但局部变量可以用final修饰,因此局部内部类也可以用final修饰。

3、作用域仅在定义它的方法或代码块之中。

4、局部内部类访问外部类成员的方式是直接访问;而外部类访问局部内部类的成员则必须先创建对象然后再访问,并且注意要在作用域之内(例如,外部类可以在方法之中创建Inner02对象,然后调用方法即可)。

5、要记住局部内部类虽然地位是局部变量,但本质仍然是一个类。

6、外部其他类不能访问局部内部类,因为它地位是局部变量。

7、如果外部类和局部内部类成员重名,默认访问时遵守就近原则。但如果在局部内部类里面想要访问外部类的成员,则可以使用以下方式访问:

外部类名.this.成员

三、匿名内部类

一、基本介绍 

 匿名内部类是定义在外部类的局部位置,比如方法之中,且没有类名的类。

它本质是类,还是一个内部类,并且没有名字,同时还是一个对象

其基本语法为:

new 类/接口(参数列表){
    类体
};

二、案例引入

class Outer{//外部类
    private int n1 = 10;
    public void method(){
        new A(){
            @Override
            public void cry(){
                //实现功能
            }
        };
        new Father("zs"){
        };
    }
}

interface A{
    public void cry();
}

class Father{
    public Father(Sting name){
        super();
        //实现功能
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值