Java笔记——面向对象编程(高级)

类变量和类方法

类变量

static变量 被类的所有对象实例共享 jdk8以后,静态变量在内存的中 class实例的尾部 共识:

  1. static变量是同一个类所有对象共享
  2. static类变量,在类加载的时候就生成了,没创建实例也能访问

定义:访问修饰符 static 数据类型 变量名 访问:类名.类变量名

使用细节

  1. 什么时候需要用类变量 当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):例如:定义学生类,统计所有学生共交了多少钱。Student(name, static fee)
  2. 类变量与实例变量(普通变量)区别 类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
  3. 实例变量不能通过类名.类变量名方式访问
  4. 类变量是在类加载时就初始化了,即使没创建对象,只要类加载了,就可以使用类变量
  5. 类变量的生命周期时随类的加载开始,随类的消亡而销毁

类方法

定义:访问修饰符 static 数据返回类型 方法名(){ } 访问:类名.类方法名 前提满足访问修饰符的访问权限和范围 使用场景:当方法不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,不需要创建对象就可以使用,提高开发效率。比如:工具类中的方法utils, Math类,Arrays类,Collections集合类

使用细节

  1. 类方法和普通方法都是随类的加载而加载,将结构信息存储在方法区: 类方法中无this的参数,普通方法中隐含this的参数
  2. 类方法可以通过类名调用,也可以通过对象名调用
  3. 类方法中不允许使用和对象有关的关键字,比如thissuper,普通方法(成员方法)可以。
  4. 类方法中只能访问静态变量或静态方法,普通成员方法还能访问普通变量和方法

理解main方法语法

public static void main(String[] args) 1. Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须时public 2. Java虚拟机在执行main()方法时不必创建对象,所以该方法必须时static 3. 该方法在接收String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数 4. java 执行的程序 参数1 参数2 参数3 ...

特别提示:

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

代码块

代码块又称初始化块,属于类中的成员,即类的一部分,类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。 基本语法 [static] { 代码 }

代码块的好处

  1. 相当于另一种形式的构造器(对构造器的补充机制),可以做初始化操作
  2. 场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性

代码块使用注意事项和细节

  1. static代码块也叫静态代码块,作用是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行一次。
  2. 类被加载的三种情况(重要)
    1. 创建对象实例时(new)
    2. 创建子类对象实例,父类也会被加载
    3. 使用类的静态成员时(静态属性,静态方法)
  3. 普通代码块,在创建对象实例时,会被隐式的调用。如果只是使用类的静态成员,普通代码块不会被执行。
  4. 创建一个对象时,在一个类中调用顺序:(重要)
    1. 调用静态代码块和静态属性初始化(优先级一样,顺序调用)
    2. 调用普通代码块和普通属性的初始化 (优先级一样)
    3. 调用构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.company.codeblock_;  

public class CodeBlockExercise02 {
public static void main(String[] args) {
Test test = new Test();
}
}

class Sample {
Sample (String s) {
System.out.println(s);
}

Sample () {
System.out.println("调用 Sample 默认无参构造器");
}
}

class Test {
Sample sam1 = new Sample("sam1 成员初始化"); // 3

static Sample sam = new Sample("静态成员sam初始化"); // 1

static {
System.out.println("static块执行"); // 2
if (sam ** null) System.out.println("sam is null");
}

Test () {
System.out.println("Test默认构造器被调用"); // 4
}
}
  1. 构造器(构造方法)的最前面其实隐含了super()和调用普通代码块;静态相关的代码块、属性初始化,在类加载时就执行完毕。因此是优先于构造器和普通代码块执行的。
    1
    2
    3
    4
    5
    6
    7
    class A {
    public A() {
    // super();
    // 调用普通代码块
    System.out.println("ok");
    }
    }
  2. 创建一个子类时,调用顺序: 父静 -> 子静 -> 父普 -> 父构 -> 子普 -> 子构
    1
    2
    3
    4
    // (1) 进行类的加载
    // 1.1 先加载父类 1.2 再加载子类
    // (2) 创建对象
    // 2.1 从子类构造器开始
  3. 静态代码块只能直接调用静态成员,普通代码块可以调用任意成员

单例设计模式

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法 步骤: 1. 构造器私有化 (防止直接new) 2. 类的内部创建对象 3. 向外暴露一个静态的公共方法(getInstance)

饿汉式

没调用也会创建对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.company.single_;  

public class SingleInstance01 {
public static void main(String[] args) {
GirlFriend instance = GirlFriend.getInstance();
System.out.println(instance);
}
}

class GirlFriend {
private String name;

// 1. 将构造器私有化
private GirlFriend(String name) {
this.name = name;
}

// 2. 类内创建静态对象
private static GirlFriend joe = new GirlFriend("Jocelyn");

// 3. 静态方法返回对象引用
public static GirlFriend getInstance() {
return joe;
}

@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}

懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.company.single_;  

public class SingleTon {
public static void main(String[] args) {
System.out.println(Cat.n1);
Cat ins = Cat.getInstance();
System.out.println(ins);

Cat ins2 = Cat.getInstance();
System.out.println(ins2);

System.out.println(ins == ins2); // true
}
}

class Cat {
private String name;
public static int n1 = 100;

private static Cat cat;

private Cat(String name) {
System.out.println("创建Cat对象...");
this.name = name;
}

public static Cat getInstance() {
if (cat == null) {
cat = new Cat("mimi");
}
return cat;
}

@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}

饿汉式VS懒汉式

  1. 两种主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建;
  2. 饿汉式不存在线程安全问题,懒汉式存在线程安全问题;
  3. 饿汉式存在资源浪费的可能;(没有使用对象实例)
  4. 在JavaSE标准类中,java.lang.Runtime是经典的单例模式;

final关键字

基本介绍

final可以修饰类、属性、方法和局部变量 1. 当不希望类被继承时,可以用final修饰; 2. 当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰; 3. 当不希望类的某个属性的值被修改,可以用final修饰; 4. 当不希望某个局部变量被修改,可以使用final修饰;

注意事项

  1. final修饰的属性又叫常量,一般用XX_XX来命名;
  2. final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值方式:
    • 定义时;
    • 在构造器中;
    • 在代码块中;
  3. 如果final修饰的属性是静态的,则初始化的不能在构造器中赋值;
  4. final类不能继承,但是可以实例化;
  5. 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承;
  6. 一般来说,如果一个类已经是final类了,就没必要再将方法修饰成final方法;
  7. final不能修饰构造器
  8. final 和 static 往往搭配使用,效率更高,底层编译器做了优化处理;
    1
    2
    3
    4
    5
    6
    7
    8
    class A {   
    // 有final 静态代码块不会被提前加载
    public final static int num = 100;

    static {
    System.out.println("类A被加载...");
    }
    }
  9. 包装类(Integer, Double, Float, Boolean等都是final),String也是final类;

抽象类

  • 父类方法的不确定性 当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类,用abstract修饰;抽象类会被继承,由其子类实现具体方法
    1
    访问修饰符 abstract 返回类型 方法名(参数列表); // 没有方法体
  • 抽象类的价值更多作用在于设计,是设计者设计好后,让子类继承并实现抽象类,在框架和设计模式使用较多 ## 使用细节和注意事项
  1. 抽象类不能被实例化
  2. 抽象类不一定要包含abstract方法
  3. 一旦类包含了abstract方法,则这个类必须声明为abstract
  4. abstract 只能修饰类和方法,不能修饰属性和其它的
  5. 抽象类可以有任意成员【抽象类本质还是类】
  6. 抽象方法不能有主体,即不能实现
  7. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也申明为abstract
  8. 抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的

模板设计模式

设计一个抽象类(Template),能完成如下功能: 1. 编写一个方法calculateTime(),可以计算某段代码的耗时时间 2. 编写抽象方法job() 3. 编写一个子类Sub,继承抽象类Template,并实现job方法 4. 编写一个测试类TestTemplate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.company.abstract_;  

public class TestTemplate {
public static void main(String[] args) {
Sub sub = new Sub();
sub.calculateTime(); // 多态的动态绑定机制
}
}

abstract class Template {

public abstract void job(); // 抽象方法

public void calculateTime() {
long start = System.currentTimeMillis();
job(); // 调用 动态绑定机制
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start));
}
}

class Sub extends Template {
@Override
public void job() {
// 具体的工作
long num = 0;
for (int i = 0; i < 800000; i++) {
num += i;
}
}
}
# 接口 ## 基本介绍 接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。
1
2
3
4
5
6
7
8
9
10
interface 接口名 {
// 属性
// 方法
}

class 类名 implements 接口 {
自己属性;
自己方法;
必须实现的接口的抽象方法;
}
- 在jdk8.0后接口类可以有static静态方法,default默认方法,也就是说接口中可以有方法的具体实现; - 在接口中,抽象方法可以省略abstract关键字 ## 使用细节 1. 接口不能被实例化; 2. 接口中所有的方法是public方法,接口中抽象方法,可以不用abstract修饰; 3. 一个普通类实现接口,就必须将该接口的所有方法都实现; 4. 抽象类实现接口,可以不用实现接口的方法; 5. 一个类同时可以实现多个接口; 6. 接口中的属性,只能是final的,而且是public static final 修饰符。比如:int a = 1;实际上是public static final int a = 1;(必须初始化) 7. 接口中属性的访问形式:接口名.属性名 8. 一个接口不能继承其他类,但可以继承多个别的接口;interface A extends B, C {} 9. 接口的修饰符只能是public和默认,这点和类的修饰符是一样的; ## 实现接口 VS 继承类 1. 接口和继承解决的问题不同 - 继承的价值主要在于:解决代码的复用性和可维护性 - 接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法 2. 接口比继承更加灵活 继承是满足is-a的关系,而接口只需满足like-a的关系 3. 接口在一定程度上实现代码解耦 【接口规范性+动态绑定】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.company.interface_;  

public class ExtendsVsInterface {
public static void main(String[] args) {
LittleMonkey wuKong = new LittleMonkey("wuKong");
wuKong.climbing();
wuKong.swimming();
wuKong.flying();
}
}

class Monkey {
private final String name;

public Monkey(String name) {
this.name = name;
}

public void climbing() {
System.out.println(name + "生来会爬树...");
}

public String getName() {
return name;
}
}

// 实现接口可以理解为对 java 单继承机制的一种补充
class LittleMonkey extends Monkey implements Fishable, Birdable {
public LittleMonkey(String name) {
super(name);
}

@Override
public void swimming() {
System.out.println(getName() + "通过学习,会游泳了...");
}

@Override
public void flying() {
System.out.println(getName() + "通过学习,会飞翔了...");
}
}

interface Fishable {
void swimming();
}

interface Birdable {
void flying();
}

接口多态特性

  1. 多态参数
    接口引用可以指向实现了接口的类的对象
    1
    2
    3
    4
    5
    6
    7
    8
    public class Computer {  
    // 传入接口
    public void work(UsbInterface usbInterface) {
    // 通过接口调用方法
    usbInterface.start();
    usbInterface.stop();
    }
    }
  2. 多态数组
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    package com.company.interface_;  

    public class InterfacePolyArr {
    public static void main(String[] args) {
    Usb[] usbs = new Usb[2];
    usbs[0] = new Phone_();
    usbs[1] = new Camara_();

    for (Usb usb : usbs) {
    if (usb instanceof Phone_) ((Phone_) usb).call(); // 向下转型
    usb.work(); // 动态绑定
    }
    }
    }

    interface Usb {
    void work();
    }

    class Phone_ implements Usb {
    @Override
    public void work() {
    System.out.println("手机工作中...");
    }

    public void call() {
    System.out.println("---手机可以打电话---");
    }
    }

    class Camara_ implements Usb {
    @Override
    public void work() {
    System.out.println("相机工作中...");
    }
    }
  3. 接口存在多态传递现象
    1
    2
    3
    4
    5
    6
    7
    8
    interface A {}
    interface B extends A {}
    class C implements B {
    //xxx
    }

    B b = new C(); // 接口类型的变量指向实现该接口的类的对象实例
    A a = new C(); // B 继承了 A, C相当于也实现了接口A
    # 内部类 ## 基本介绍 一个类的内部又完整的嵌套了另一个类结构,被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。内部类最大特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。 类的五大成员:属性,方法,构造器,代码块,内部类 ## 基本语法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Outer { // 外部类
    class Inner { // 内部类

    }
    }

    class Other { // 外部其他类

    }
    ## 内部类的分类
  • 定义在外部类局部位置上(比如方法内, 代码块内):
    • 局部内部类(有类名)
    • 匿名内部类(没有类名,重点!!!
  • 定义在外部类的成员位置上:
    • 成员内部类(没用static修饰)
    • 静态内部类(使用static修饰)

局部内部类

  1. 可以直接访问外部类的所有成员,包括私有的
  2. 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final修饰
  3. 作用域:仅仅在定义它的方法或代码块中
  4. 局部内部类 - 访问 - 外部类的成员 【访问方式:直接访问】
  5. 外部类 - 访问 - 局部内部类的成员 【访问方式:创建对象再访问】
  6. 外部其他类 - 不能访问 - 局部内部类
  7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可使用外部类名.this.成员去访问 【外部类名.this本质是外部类的对象,谁调用就指谁】 ### 匿名内部类 本质是类,内部类,该类没有名字,同时还是一个对象
    1
    2
    3
    new 类或接口(参数列表) {
    类体
    };
  • 匿名内部类的使用 匿名内部类的语法比较奇特,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,既有定义类的特征,也有创建对象的特征,因此可以调用匿名内部类方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    package com.company.innerclass_;  

    public class InnerClass {
    public static void main(String[] args) {
    Outer outer = new Outer();
    outer.f1();
    }
    }

    class Outer {
    private int n1 = 10;
    public void f1() {
    // class 匿名内部类 extends Preson {}
    Person p = new Person() { // 创建一个基于类的匿名内部类
    @Override
    public void hi() {
    System.out.println("匿名内部类重写了hi()方法");
    }
    };
    System.out.println(p.getClass());
    p.hi(); // 动态绑定,运行类型是 Outer$1

    new Person() { // 直接调用
    @Override
    public void hi() {
    System.out.println("匿名内部类重写了hi()方法");
    }
    }.hi();
    }
    }

    class Person {
    public void hi() {
    System.out.println("person.hi()");
    }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.company.innerclass_;  

public class Exercise {
public static void main(String[] args) {
// 匿名内部类
f1(new AA() {
@Override
public void show() {
System.out.println("**=当作实参直接传递**=");
}
});

// 传统做法
f1(new Picture());
}
// 静态方法,形参是接口类型
public static void f1(AA a) {
a.show();
}
}

interface AA {
public void show();
}

class Picture implements AA {
@Override
public void show() {
System.out.println("**=硬编码**=");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.company.innerclass_;  

public class Exercise02 {
public static void main(String[] args) {
Cellphone cellphone = new Cellphone();
// 编译类型 ~ 运行类型
cellphone.alarmClock(new Bell() {
@Override
public void ring() {
System.out.println("懒猪起床啦");
int x = 0;
for (int i = 0; i < 8; i++) {
x += i;
}
System.out.println("x: " + x);
}
});
//lambda
cellphone.alarmClock(() -> System.out.println("小伙伴上课啦"));
}
}

interface Bell {
void ring();
}

class Cellphone {
public void alarmClock(Bell bell) {
System.out.println(bell.getClass());
bell.ring(); // 动态绑定
}
}

成员内部类

成员内部类是定义在外部类的成员位置,并且没有static修饰。 1. 可以直接访问外部类的所有成员,包括私有的 2. 可以添加任意访问修饰符,因为它的地位就是一个成员 3. 作用域和外部类的其他成员一样,为整个类体。 4. 成员内部类 - 访问 - 外部类 【访问方式:直接访问】 5. 外部类 - 访问 - 成员内部类 【访问方式:创建对象,再访问】 6. 外部其他类 - 访问 - 成员内部类

静态内部类

静态内部类是定义在外部类的成员位置,并且static修饰。 1. 可以直接访问外部类的所有静态成员,包含私有的,但是不能直接访问非静态成员 2. 可以添加任意访问修饰符(public, protected, default, private) 3. 作用域:同其他成员,为整个类体 4. 静态内部类 - 访问 - 外部类 【访问方式:直接访问所有静态成员】 5. 外部类 - 访问 - 静态内部类 【访问方式:创建对象,再访问】 6. 外部其他类 - 访问 - 成员内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.company.innerclass_;  

public class StaticInnerClass {
public static void main(String[] args) {
new Outer_().m1();

// 其他外部类使用静态内部类
// 方式1 【直接类名访问】
Outer_.Inner inner = new Outer_.Inner();
inner.say();

// 方式2 【编写一个方法,返回静态内部类的对象实例】
Outer_ outer_ = new Outer_();
Outer_.Inner inn = outer_.getInner();
inn.say();

// 方式3 【编写一个静态方法,返回静态内部类的对象实例】
Outer_.Inner innerInstance = Outer_.getInnerInstance();
innerInstance.say();
}
}

class Outer_ {
private int n1 = 10;
private static String name = "张三";
public static void cry() {
System.out.println("cry()...");
}
protected static class Inner {
public void say() {
System.out.println(name);
cry();
}
}

public void m1() {
Inner inner = new Inner();
inner.say();
}

public static Inner getInnerInstance() {
return new Inner();
}

public Inner getInner() {
return new Inner();
}
}
7. 如果外部类和静态内部类的成员重名时,静态内部类访问的时候,遵循就近原则,如果想要访问外部类的成员,可以使用外部类名.成员去访问 【注意没有this】


Java笔记——面向对象编程(高级)
https://nessaj7.github.io/2022/04/21/fe17ffcb6203.html
作者
kuhn
发布于
2022年4月21日
许可协议