Bootstrap

JavaSE基础知识点记录 08章 面向对象编程(高级)

目录

8-1 static修饰成员变量与方法

8-2 单例的设计模式

1、饿汉式

2、懒汉式

8-3 main()的理解

8-4 类的成员之四:代码块

1、静态代码块

2、非静态代码块

8-5 实例变量赋值位置与赋值顺序

8-6 final关键字的使用

8-7 abstract关键字修饰类、方法

8-8 接口的理解与基本语法

接口的实现

8-9 jdk8,jdk9中接口的新特性

8-10 类的成员之五:内部类

1、成员内部类

2、局部内部类

8-11 枚举类的使用(自定义、enum)

自定义用类定义枚举类

用enum定义枚举类

8-12 枚举类常用方法、实现接口

8-13 注解Annotation的理解与三个常用注解

8-14 JUnit单元测试的使用

8-15 包装类的理解、基本数据类型与包装类的转换

8-16 基本数据类型、包装类和String间的转换

装箱:把基本数据类型转换成包装类对象

拆箱:把包装类对象拆为基本数据类型

基本数据类型、包装类与字符串之间的转换


8-1 static修饰成员变量与方法

static:让一个成员变量被类的所有实例所共享

所有的实例都有这样一个相同的变量!

static所修饰的类变量、类方法称为静态变量、静态方法,它被所有对象所共享,优先于对象存在

语法格式:[修饰符] static 数据类型 变量名;

eg. private static String country;

静态变量的get/set方法也是静态的,如果静态变量与局部变量重名,可以用“类名.静态变量”引用静态变量(其他情况下用“对象.静态变量”也可以,但是更推荐前者)

用private修饰时,只能用get/set访问,有时用缺省修饰,则可以用“类名.静态变量”访问

(ps:静态变量原本放在方法区,在jdk8后转移到堆内存中)

如图上面加粗字,静态方法和静态变量可以不用实例化直接用类名访问

在static方法内部只能访问类中static修饰的属性或方法,不能访问非static的结构

静态方法可以被子类继承但是不能被重写,且调用只看编译时类型

由于不需要实例化就可以访问,因而static方法内部不能出现this.和super.

8-2 单例的设计模式

设计模式:大量工作后总结的思考方式/风格(经典的有23种)

单例模式:整个系统中对某个类只存在一个对象实例,且只提供一个取得对象实例的方法

(也就是说我们无论如果只能产生一个对象实例,多一个也不行)

两种实现方式:

1、饿汉式

class A{
    //①私有化构造器(避免外部new)
    private A(){}
    //②内部提供一个当前类的实例,并且也必须静态化
    private static A a = new A();
    //③提供公共的静态方法(让哪里都能用这个方法返回类,并且这个类永远是相同的一个)
    public static A getInstance(){
            return a;
    }
}

2、懒汉式

class A{
    //①私有化构造器,外部不能new
    private A(){}
    //②内部提供一个当前类的实例,也必须静态保证不能改
    private static A a;
    //③提供方法返回这个实例,同样也是公共、静态的
    public static A getInstance(){
        if(a == null){ // 判断a有没有创建过,没有就新开一个
            a = new A();
        } // 如果有创建,那么直接返回之前那个
        return a;
    }
}

区分饿汉式与懒汉式:

饿汉式:在使用类的对象时,就已经分配了这个static变量,尽管还不知道要不要用,是立即加载,占用内存时间更长 

懒汉式:调用静态方法时才创建static变量,为其分配内存,是延迟加载,占用内存时间更短

(更加推荐饿汉式)懒汉式在多线程中是不安全的,不能保证唯一性,可能出现两个先后判断null的情况导致错误

8-3 main()的理解

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

jvm需要调用,所以说public;调用main时不用创建对象,所以是static;接收String类型的数组,里面保存了执行Java命令传递给运行类的参数

运行格式:java  准备运行的程序  第一个参数  第二个参数  第三个参数……

(例如运行test.java)

eg. java test a b c

8-4 类的成员之四:代码块

使用情景:成员变量初始化的值不是一个简单的常量而是需要通过复杂计算或者读取文件活动时

分类:静态代码块(用static修饰)与非静态代码块(不用static修饰)

1、静态代码块

格式:

static{

        // 静态代码块

}

特点:可以有输出语句、对各种成员初始化,但是不能调用非静态的属性与方法,有多个静态代码块时由上到下执行,静态代码块执行优先级高于非静态代码块,静态代码块随类加载而加载且只加载一次

2、非静态代码块

{

        // 非静态代码块

}

特点:可以有输出语句、对各种成员初始化,可以调用静态与非静态的属性与方法,有多个代码块时由上到下执行,每次创建对象都会执行一次,先于构造器执行

8-5 实例变量赋值位置与赋值顺序

重要!

以class test{int a = 5;}举例

1、声明成员变量的默认初始化

int a = 0;(默认)

2、显式初始化、多个初始化块依次被执行(同级别下按先后顺序执行)

int a = 5; { int a = 5; }

3、构造器再对成员初始化

public test(){ this.a = 5; }

4、通过“对象.属性/方法”进行赋值

test t = new test();  t.a = 5;

核心:由父及子,静态先行!(先把父子里面所有静态的都执行完!)

父与子都有静态代码块时:父类静态代码块->子类静态代码块->父类非静态代码块->父类构造器->子类非静态代码块->子类构造器

8-6 final关键字的使用

final表示最终的,无法更改的,可以修饰类、方法、变量。

修饰类时:类无法被继承,没有子类

修饰方法时:方法不能被子类重写

修饰变量时:一旦被赋值,它的值无法被改变,成为常量(常量名建议用大写)

某个变量被final修饰后,没有set方法且必须初始化(显式赋值、初始化块赋值、构造器赋值,如果时构造器赋值那么每一个可能用到的构造器都必须给它赋值)

如果是形参设置为final,那么也不能更改

8-7 abstract关键字修饰类、方法

abstract修饰成为抽象类、抽象方法

没有方法体的方法称为抽象方法,包含抽象方法的类必须是抽象类

语法格式:

[权限修饰符] abstract class 类名 extends 父类{

        [权限修饰符] abstract 返回值类型 方法名 ([形参列表]); // 没有方法体,没有{}

}

实现方法:指子类对父类抽象方法的完成实现、重写的操作

使用注意:

1、抽象类不能创建对象

2、抽象类是用来被继承的,其子类必须重写父类的全部抽象方法并提供方法体。如果没有全部重写,那么子类仍属于抽象类

3、抽象类也有构造方法,是子类继承父类后,创建对象时初始化父类成员变量用的

4、抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类

5、不能用abstract修饰变量、代码块、构造器

6、不能用abstract修饰私有方法(这个方法不能被子类重写)、静态方法(这个是要求能用类调用而不实例化)、final的方法(不能被重写)、final的类(不能被继承)

7、抽象类里面可以有非抽象方法和非抽象成员变量

8-8 接口的理解与基本语法

接口interface:各模块间面向接口的低耦合

接口是规范,接口实现是“能不能”的has-a关系(比如摄像机能不能拍照、能不能打电话的功能)

(如果你要……你就必须能……)你如果要使用,就必须遵守给出的规范

声明格式:

[修饰符] interface 接口名{

        // 接口的成员列表

        // 公共的静态常量 public static final

        // 公共的抽象方法 public abstract

        // 公共的默认方法(jdk1.8以上)

        // 公共的静态方法(jdk1.8以上)

        // 私有方法(jdk1.9以上)

}

成员说明:jdk8.0之前只允许出现:

公共的静态常量 public static final和公共的抽象方法 public abstract(单词都可以省略)

如果出现了没有前缀的变量或者方法都默认是上面这两种!!!

jdk8.0后允许声明默认方法(public可以省略但default不能省略)与静态方法(public可以省略但是static不能省略

jdk9.0后允许私有方法private,一般是接口只能自己用的时候声明

接口的实现

1、类实现接口implements

[修饰符] class 实现类 implements 接口{……}

要求:重写接口中的所有抽象方法,但如果实现类也是抽象类,那么可以不重写所有的。默认方法则可以重写也可以不重写,但如果重写的话那么default单词就不要写了(只是在接口里面表示默认方法,到类里面就没有默认方法的概念了)

接口里面的静态方法不能被继承也不能被重写

2、接口的多实现implements

一个类可以实现多个接口

[修饰符] class 实现类 implements 接口1,接口2,接口3……{……}

同理如果实现类不是抽象类,必须重写所有接口的抽象方法。而如果抽象方法有重名,只需要重写一次

3、接口的多继承性extends

一个接口能够继承另一个接口或多个接口,也使用extends

所有父接口的抽象方法都要重写,方法签名相同的抽象方法只需要实现一次

4、接口与实现类对象构成多态引用

实现类实现接口类似子类继承父亲。通过接口类型的变量调用方法,最终执行为new的实现类对象的方法体

也就是实现类实现接口可以看作继承父亲,也能用多态

eg. 一直Mouse和Keyboard的接口都为USB3。

USB3 usb = new Mouse();

由于接口没有构造器,这里new的后面必须为实现类

5、使用接口的静态成员

可以使用接口名直接调用接口的静态方法和静态常量(不能用实现类调用)

6、使用接口的非静态方法

对于非静态的,必须要通过实现类对象才能调用,不能通过接口

8-9 jdk8,jdk9中接口的新特性

jdk8:类优先原则

如果一个类继承了一个父亲又实现了若干接口,父类的成员方法与接口抽象/默认方法重名,就近执行父类的成员方法

(如果要执行接口A的方法,如果为静态则A.method();,否则为默认default方法则是A.super.method();)

在接口默认方法调用的语境中,A.super 表示引用接口 A 的成员,是一种专门为解决接口默认方法调用问题而设计的语法

接口冲突:一个类实现的多个接口中出现相同的默认方法

直接整个在class里面重写

常量冲突:子类的父类和父接口中存在同名成员变量时,二者不会覆盖,均会存在,但是调用是需要特别说明是哪一个(接口的:接口名.常量名;父类的:super.常量名)

关于default:

子接口重写默认方法,default关键字可以保留

重写默认方法,default关键字不能保留

8-10 类的成员之五:内部类

内部类:一个类A定义在另一个类B里面,则类A称为内部类,类B称为外部类

为什么需要内部类:外部类B的内部有一部分需要完整的结构类A去刻画,而这个类A又只服务于外部类B

对象开发原则:高内聚、低耦合

内部类分类:成员内部类(有静态成员内部类与非静态成员内部类)与局部内部类(有非匿名局部内部类与匿名局部内部类)

1、成员内部类

如果内部类不使用外部类的非静态成员则称为静态内部类,否则是非静态内部类

成员内部类作为一个类的成员而存在,特别的是它可以声明为private和protected(外部类不行)

可以调用外部类的结构(静态内部类则不能调用外部类的非静态成员),可以声明为static

同时它本身也是一个类,也能有自己的代码块、内部类……

外部类访问内部类成员:内部类.成员 或 内部类对象.成员

想在外部类的静态成员部分中使用内部类时,可以考虑内部类声明为静态

创建内部类对象:

实例化静态内部类:外部类名.静态内部类名 变量 = new 外部类名.静态内部类名();

调用方法:变量.非静态方法();

实例化非静态内部类:外部类名 变量1 = new 外部类();

外部类名.非静态内部类名 变量 = 变量1.new 外部类名.非静态内部类名();

(建立在外部类对象的基础上的)

调用方法:变量2.非静态方法();

2、局部内部类

(也就是方法里面的类)

非匿名局部内部类:

[修饰符] class 外部类{

        [修饰符] 返回值类型 方法名(形参列表){

                 [final/abstract] class 内部类{

                

                }

        }

}

特别:没有权限修饰符,因为只是一个局部的,有作用域

匿名内部类:一次性使用

new 父类([实参列表]){ 重写方法…… }

new 父接口(){ 重写方法…… }

如果需要使用可以在{}后面直接点上重写后的方法直接使用

eg. interface A{ void a(); }

(使用匿名内部类的对象)
new A(){
        // 重写a()
    }.a();

(使用父类或父接口的变量多态)
A obj = new A(){
        // 重写a()
    };
obj.a();

第一个new A()是多态,是新造的匿名子类,并且调用a()方法

第二个new A()也是多态,但虽然是new了A(),但后面重写了方法,实际上new的是A()的匿名子类/接口。而后面obj.a()则不是匿名,是直接调方法

还用一种情况,内部类作为实参传入方法中

8-11 枚举类的使用(自定义、enum)

枚举类本质也是一种类,但是类的对象是有限、固定、事先定好的,不能让用户随便创建。

(例如:星期:周一、周二……周日;性别:男、女;月份:1月、2月……12月)

如果枚举只有一个对象,则可以作为一种单利模式的实现方式

枚举支持enum关键字快速定义枚举类型(jdk5.0以后)

自定义用类定义枚举类

1、私有化类的构造器

2、在类的内部创建枚举类的实例public static final,每一个对象都要实例化,对外暴露这些常量对象

3、对象如果有实例变量,也就是枚举类的各种对象,最好为private final防止被修改也不能随便引用,并且在构造器的初始化

class Season{
    // private final 修饰实例变量
    private final String SEASONNAME;
    private final String SEASONDESC;
    
    // 私有化构造器
    private Season(String seasonname, String seasondesc){
        this.SEASONNAME = seasonname;
        this.SEASONDESC = seasondesc;
    }
    
    // 用public static final 修饰枚举类的实例
    public static final Season SPRING = new SPRING("春天","春意盎然");
    public static final Season SPRING = new SUMMER("夏天","夏日炎炎");
    public static final Season SPRING = new AUTUMN("秋天","秋意浓浓");
    public static final Season SPRING = new WINTER("冬天","白雪皑皑");
}

使用的时候可以Season.SPRING……调用“类名.实例名”

用enum定义枚举类

[修饰符] enum 枚举类名{

        常量对象列表;

        对象的实例变量列表;

}

说明:

1、常量对象列表必须在类的首行,且因为是常量建议大写,会自动用private static final修饰

2、常量对象列表用逗号隔开

3、默认提供private无参构造器,如果需要有参构造器则要手动定义,在对象列表的每个后面加上(参数列表)就可以

4、enum类默认继承java.lang.Enum类,所以不能再继承其他类

// 案例1:
public enum Week{
    MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY;
}

// 案例2:
public enum SeasonEnum{
    // 常量对象列表,直接进行构造
    SPRING("春天","春意盎然"),
    SUMMER("夏天","夏日炎炎"),
    AUTUMN("秋天","秋意浓浓"),
    WINTER("冬天","白雪皑皑");

    // 实例变量
    private final String SEASONNAME;
    private final String SEASONDESC;
    
    // 私有化构造器
    private Season(String seasonname, String seasondesc){
        this.SEASONNAME = seasonname;
        this.SEASONDESC = seasondesc;
    }
}

8-12 枚举类常用方法、实现接口

常用方法:

1、String toString():默认返回常量名(对象名),可重写

2、static 枚举类型[] values():返回枚举类型的对象数组,能够遍历所有枚举值,为静态方法

3、static 枚举类型 valueOf(String name):把一个字符串转化为对应的枚举类对象,但是要求这个字符串必须是枚举类对象的“名字”,否则会有运行时异常

4、String name():得到当前枚举常量的名称,但优先使用toString()

5、int ordinal():返回当前枚举常量的次序号,类中第几个声明,默认从0开始

实现接口的枚举类:

enum A implements 接口1,接口2{……}

同样地,要求枚举类中重写抽象方法

如果都一样,那么直接在类中重写,如果每个对象各不相同,则要在每个实例中单独实现不同的方法

8-13 注解Annotation的理解与三个常用注解

格式:@注解名

用处:加入一些补充信息,但不同于注释,注解可以被编译器和其他程序员读取

常见作用:生成文档相关信息、编译时进行格式检查、替代配置文件功能

三个常见注解:

1、@Override:用于检测被标记的方法为有效的重写方法

2、@Deprecated:用于表示标记的数据已经过时,不推荐使用

3、@SupperWarnings("要抑制的类型"):抑制编译警告,不想看到编译警告信息时可以进行抑制

元注解:对已有的注解进行解释,可以理解为注解的注解

1、@Target:表示注解的适用范围

2、@Retention:表示注解的生命周期

3、@Document:表示注解应该被javadoc工具记录在生成的html里

4、@Inherited:允许子类继承父类中的注解

同时可以进行自定义注解,自己编写注解

8-14 JUnit单元测试的使用

测试分类:

黑盒测试:不写代码,看程序的输出是否符合预期

白盒测试:写代码,要看代码执行流程

具体使用略

@Test的使用:是表示对一个模块方法进行测试

要求:所在的类必须是public的,非抽象的,包含唯一的无参构造器;标记的方法必须是public的,非抽象非静态的,void无返回值,()无参数的

8-15 包装类的理解、基本数据类型与包装类的转换

包装类:将基本数据类型封装成类的对象的操作,用来方便传入作为参数或者其他操作

比如:add(Object obj),如果想加一个double类型的数,就可以使用封装类然后传入

目的:让基本数据类型能调引用数据类型的API

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
booleanBoolean
charCharacter
// 使用举例示意
int num = 520;
Integer obj = new Integer(520); // 具备类的特性

同样地,也可以自定义包装类,重写toString方法和构造器即可

8-16 基本数据类型、包装类和String间的转换

装箱:把基本数据类型转换成包装类对象

(需要使用为对象设计的API和特性时装箱)

转化可以使用构造器构造,也可以使用包装类的各种方法(valueOf)构造

Integer i = Integer.valueOf(5);

拆箱:把包装类对象拆为基本数据类型

(需要进行运算时会拆开)

eg.

Integer obj = new Integer(4);

int num = obj.intValue();

(通常为xxxValue,xxx为基本数据类型)

同时也有自动装箱与拆箱功能,但是需要数据类型与类之间对应

基本数据类型、包装类与字符串之间的转换

(1)基本数据类型 -> 字符串

1、调用String类重载的valueOf方法(String,valueOf())

2、直接方式:基本数据变量 + " "(连接符)

(2)字符串 -> 基本数据类型

1、除了Character类其他都可以用parseXxx静态方法转换(推荐)

2、转换为包装类后自动拆箱为基本数据类型

3、用包装类构造器实现

包装类与String之间:

包装类 -> String:包装类对象或者包装类的toString方法

String -> 包装类:用字符串参数String s = new Sting("Java加油学")

但值得注意的是,包装类的缓存对象也有限制

包装类缓存对象
Byte-128~127
Short-128~127
Integer-128~127
Long-128~127
Float无限制
Double无限制
Character0~127
Booleantrue和false

注意区分以下情况:

public class IntegerComparison {
    public static void main(String[] args) {
        Integer m = 1;
        Integer n = 1;
        System.out.println(m == n); // 输出: true

        Integer x = 128;
        Integer y = 128;
        System.out.println(x == y); // 输出: false

        Integer i = new Integer(1);
        Integer j = new Integer(1);
        System.out.println(i == j); // 输出: false

        // 使用 equals 方法比较值
        System.out.println(i.equals(j)); // 输出: true
    }
}

第一个案例使用了自动装箱,自动将1转为类,所以Integer会返回相同的类,故为true

第二个案例因为超出了范围,无法返回,故为false

第三个案例是手动装箱,使用了new,会开辟两个不同的空间,因而==为false,但由于二者的值是相同的,所以使用equals比较时为true

;