10.面向对象编程(高级部分)|Java学习笔记

类变量和类方法

类变量

思考: 如果,设计一个 int count 表示总人数,我们在创建一个小孩时,就把 count 加 1,并且 count 是所有对象共享的就 ok 了!我们使用类变量来解决。

类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。

// 定义语法
访问修饰符 static 数据类型 变量名;

// 访问类变量
类名.类变量名
或者 对象名.类变量名
推荐使用: 类名.类变量名
  1. static 变量是同一个类所有对象共享
  2. static 类变量,在类加载的时候就生成了

类变量使用注意事项和细节

  1. 什么时候需要用类变量?
    当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):比如:定义学生类,统计所有学生共交多少钱。Student (name,staticfee)
  2. 类变量与实例变量(普通属性)区别:
    类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
  3. 加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
  4. 类变量可以通过类名.类变量名或者对象名.类变量名来访问,但java设计者推荐我们使用类名.类变量名方式访问。【前提是满足访问修饰符的访问权限和范围】
  5. 实例变量不能通过类名.类变量名方式访问。
  6. 类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了
  7. 类变量的生命周期是随类的加载开始,随着类消亡而销毁

类方法

// 定义
访问修饰符 static 数据返回类型 方法名(){}

// 调用
类名.类方法名

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

类方法使用注意事项和细节

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

小结
静态方法,只能访问静态的成员。
非静态的方法,可以访问静态成员和非静态成员(必须遵守访问权限)。

理解 main 方法语法

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

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

代码块

代码化块又称为初始化块,属于类中的成员[即是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过 { } 包围起来。

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

[修饰符] {
	代码
}

说明注意:

  1. 修饰符可选,要写的话,也只能写static
  2. 代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的,叫普通代码块/非静态代码块。
  3. 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
  4. ;号可以写上,也可以省略。

代码块的好处:

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

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

  1. static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行
  2. 类什么时候被加载[重要背!]
    ①创建对象实例时(new)
    ②创建子类对象实例,父类也会被加载
    ③使用类的静态成员时(静态属性,静态方法)
  3. 普通的代码块,在创建对象实例时,会被隐式的调用。
    被创建一次,就会调用一次。
    如果只是使用类的静态成员时,普通代码块并不会执行。

小结:

  1. static 代码块是类加载时,执行,只会执行一次。
  2. 普通代码块是在创建对象时调用的,创建一次,调用一次。
  3. 类加载的3种情况,需要记住。
  4. 创建一个对象时,在一个类调用顺序是:(重点,难点)∶
    调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
    调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
    调用构造方法
  5. 构造器的最前面其实隐含了super()和调用普通代码块,静态相关的代码块,属性初始化,在类加载时,就执行完毕。
class A {
	public A() { //构造器
		//这里有隐藏的执行要求
		// (1) super();
		// (2)调用普通代码块的
		System.out.println("ok");
	}
}
  1. 我们看一下创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
    ①父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
    ②子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
    ③父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
    ④父类的构造方法
    ⑤子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
    ⑥子类的构造方法
  2. 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。

单例设计模式

设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考和摸索。

什么是单例模式?

  1. 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
  2. 单例模式有两种方式: 1) 饿汉式 2) 懒汉式

饿汉式:类加载时就会创建对象。
懒汉式:不使用就不会创建。

单例模式应用实例

  1. 构造器私有化=》防止直接new
  2. 类的内部创建对象
  3. 向外暴露一个静态的公共方法。getInstance

饿汉式

public class SingleTon01 {
    public static void main(String[] args) {
        System.out.println(GirlFriend.n1);

        // GirlFriend instance = GirlFriend.getInstance();
        // System.out.println(instance);
        //
        // GirlFriend instance2 = GirlFriend.getInstance();
        // System.out.println(instance2);
    }
}

class GirlFriend {
    private String name;
    public static int n1= 10;

    private static GirlFriend gf = new GirlFriend("小红红");	// 类加载时就会创建对象

    private GirlFriend(String name) {
        System.out.println("构造器被调用");
        this.name = name;
    }

    public static GirlFriend getInstance() {
        return gf;
    }

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

懒汉式

public class SingleTon02 {
    public static void main(String[] args) {
        System.out.println(Cat.n1);

        Cat instance = Cat.getInstance();
        System.out.println(instance);
        Cat instance2 = Cat.getInstance();
        System.out.println(instance2);
    }
}

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

    private static Cat cat;

    private Cat(String name) {
        System.out.println("构造器被调用");
        this.name = name;
    }

    public static Cat getInstance() {
        if (cat == null) {		// 防止多次创建
            cat = new Cat("小可爱");		// 不使用就不会创建
        }

        return cat;
    }
}

饿汉式 VS 懒汉式

在这里插入图片描述

final 关键字

在这里插入图片描述

  1. 不希望类被继承
  2. 不希望父类的某个方法被子类覆盖/重写
  3. 不希望类的某个属性的值被修改
  4. 不希望某个局部变量被修改

final 使用注意事项和细节

  1. final修饰的属性又叫常量,一般用XX_ XX XX来命名
  2. final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一【选择一个位置赋初值即可】:
    ①定义时:如public final double TAX_RATE=0.08;
    ②在构造器中
    ③在代码块中。
  3. 如果final修饰的属性是静态的,则初始化的位置只能是①定义时②在静态代码块
    不能在构造器中赋值。
  4. final类不能继承,但是可以实例化对象。
  5. 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承。
  6. 一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法。
  7. final不能修饰构造方法(即构造器)
  8. final和static往往搭配使用,效率更高,不会导致类加载。底层编译器做了优化处理。
  9. 包装类(Integer,Double,Float,Boolean等都是final),String也是final类。
package com.fwedu.final_;

public class FinalExercise02 {
    public static void main(String[] args) {
        System.out.println(A.num);
    }
}

class A {
    public final static int num = 100;
    static {
        System.out.println("A代码块被执行");
    }
}

由于 finalstatic 的搭配使用,因此输出结果只会有 100。
如果删去 final ,那么输出结果会是:

A代码块被执行
100

抽象类

在这里插入图片描述

所谓抽象方法就是没有实现的方法
所谓没有实现就是指,没有方法体
当一个类中存在抽象方法时,需要将该类声明为 abstract 类

抽象类的介绍

在这里插入图片描述

抽象类使用的注意事项和细节

  1. 抽象类不能被实例化

  2. 抽象类不一定要包含 abstract 方法。也就是说,抽象类可以没有 abstract 方法。

  3. 一旦类包含了 abstract 方法,则这个类必须声明为 abstract

  4. abstract 只能修饰类和方法,不能修饰属性和其他

  5. 抽象类可以有任意成员【抽象类本质还是类】,比如:非抽象方法、构造器、静态属性等等。

  6. 抽象方法不能有主体,即不能实现。(不能有{})
    在这里插入图片描述

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

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

抽象类最佳实践——模板设计模式

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。

模板设计模式能解决的问题

  1. 当功能内部一部分实现是确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
  2. 编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式.

抽象类 Template

package com.fwedu.abstract_;

public abstract class Template {
    /**
     * 计算工作时间
     */
    public abstract void job();

    public void calculateTime() {
        long start = System.currentTimeMillis();
        job();
        long end = System.currentTimeMillis();
        System.out.println("任务执行时间 " + (end - start));
    }
}

继承 Template ,实现了抽象方法 job() 的 Aa类

package com.fwedu.abstract_;

public class Aa extends Template{

    @Override
    public void job() {
        long num = 0;
        for (long i = 1; i <= 800000; i++) {
            num += i;
        }
        System.out.println(num);
    }
}

也就是继承了 Template 的都可以计算 job() 运行的时间,但是不同的子类执行的 job() 可以是不一样的。

接口

在这里插入图片描述
在这里插入图片描述

图中写的 抽象类里的方法可以有方法体指的是非抽象方法,抽象方法是不可以有方法体的。

一个接口的例子

定义了一个接口 UsbInterface

package com.fwedu.interface_;

public interface UsbInterface {
    /**
     * 开始
     */
    public void start();

    /**
     * 结束
     */
    public void stop();
}

定义了一个类 Camera 实现了接口 UsbInterface

package com.fwedu.interface_;

public class Camera implements UsbInterface{
    @Override
    public void start() {
        System.out.println("相机开始工作。。。");
    }

    @Override
    public void stop() {
        System.out.println("相机停止工作。。。");
    }
}

定义了一个类 Phone 实现了接口 UsbInterface

package com.fwedu.interface_;

public class Phone implements UsbInterface{
    @Override
    public void start() {
        System.out.println("手机开始工作。。。");
    }

    @Override
    public void stop() {
        System.out.println("手机停止工作。。。");
    }
}

定义了一个类 Computer,它可以调用接口 UsbInterface

package com.fwedu.interface_;

public class Computer {
    public void work(UsbInterface usbInterface) {
        usbInterface.start();
        usbInterface.stop();
    }
}

一个示例:
使用 Computer,既可以使用 phone 工作,也可以使用 camera 工作。

package com.fwedu.interface_;

public class Interface01 {
    public static void main(String[] args) {
        Camera camera = new Camera();
        Phone phone = new Phone();

        Computer computer = new Computer();

        computer.work(phone);
        System.out.println("=====");

        computer.work(camera);
        System.out.println("=====");
    }
}

注意事项和细节

  1. 接口不能被实例化

  2. 接口中所有的方法是 public方法,接口中抽象方法,可以不用abstract修饰
    图示:
    void aaa();
实际上是abstract void aa);

  3. 一个普通类实现接口,就必须将该接口的所有方法都实现。

  4. 抽象类实现接口,可以不用实现接口的方法。

  5. 一个类同时可以实现多个接口

class Pig implements IB, IC {}
  1. 接口中的属性,只能是final的,而且是 public static final修饰符。比如:int a=1;实际上是 public static final int a=1;(必须初始化)
  2. 接口中属性的访问形式:接口名.属性名
  3. 接口不能继承其它的类,但是可以继承多个别的接口
interface A extends B,C {}
  1. 接口的修饰符只能是public 和默认,这点和类的修饰符是一样的。

实现接口 vs 继承类

当子类继承了父类,就自动的拥有父类的功能。
如果子类需要扩展功能,可以通过实现接口的方式扩展.。
可以理解 实现接口 是 对 java 单继承机制的一种补充。

在这里插入图片描述

接口的多态特性

多态参数、多态数组、多态传递。
在这里插入图片描述

class C extends B implements A {
	public void pX() {
		//System.out.println(x); //错误,原因不明确 x
		//可以明确的指定 x
		//访问接口的 x 就使用 A.x
		//访问父类的 x 就使用 super.x
		System.out.println(A.x + " " + super.x);
	}
}

//访问接口的 x 就使用 A.x
//访问父类的 x 就使用 super.x

内部类

如果定义类在局部位置(方法中/代码块) : (1) 局部内部类 (2) 匿名内部类
定义在成员位置 (1) 成员内部类 (2) 静态内部类

一个类的内部又完整的嵌套了另一个类结构。
被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。
是我们类的第五大成员【思考:类的五大成员是哪些?[属性、方法、构造器、代码块、内部类]】,
内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系,

底层源码中,有大量的内部类。

基本语法:

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

内部类的分类

在这里插入图片描述
如果定义类在局部位置(方法中/代码块) :
(1) 局部内部类(有类名)
(2) 匿名内部类(没有类名)
定义在成员位置上:
(1) 成员内部类(没有static修饰)
(2) 静态内部类(使用static修饰)

局部内部类

在这里插入图片描述
说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。

  1. 可以直接访问外部类的所有成员,包含私有的。
  2. 不能添加访问修饰符,因为它的地位就是一个局部变量,局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量也可以使用final。
  3. 作用域 :仅仅在定义它的方法或代码块中。
  4. 局部内部类-访向----> 外部类的成员[访问方式:直接访问]
  5. 外部类—访向—>局部内部类的成员。
    访问方式:创建对象,再访问(注意:必须在作用域内)
    记住:
    (1) 局部内部类定义在方法中/代码块
    (2) 作用域在方法体或者代码块中
    (3) 本质仍然是一个类
  6. 外部其他类–不能访问---->局部内部类(因为局部内部类地位是一一个局部变量)。
  7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。

匿名内部类

  1. 本质还是类
  2. 内部类
  3. 该类没有名字
  4. 同时还是一个对象

在这里插入图片描述

  1. 匿名内部类的语法比较奇特,请大家注意,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法。
  2. 可以直接访问外部类的所有成员,包含私有的。
  3. 不能添加访问修饰符,因为它的地位就是一个局部变量。
  4. 作用域:仅仅在定义它的方法或代码块中。
  5. 匿名内部类—访问—>外部类成员[访问方式:直接访问]
  6. 外部其他类–不能访问-----匿名内部类(因为匿名内部类地位是一个局部变量)
  7. 如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员) 去访问。

匿名内部类的最佳实践

package com.fwedu.innerclass;

public class InnerClassExercise01 {
    public static void main(String[] args) {
        Outer05 outer05 = new Outer05();
        outer05.f1();
    }
}

class Outer05 {
    private int n1 =99;

    public void f1() {
        Person p = new Person() {
            private int n1 = 98;

            @Override
            public void hi() {
                System.out.println("匿名内部类重写了 hi 方法" + n1 +
                        " 外部类的n1 = " + Outer05.this.n1);
            }
        };
        p.hi();

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

            @Override
            public void ok(String str) {
                super.ok(str);
            }
        }.ok("jack");
    }
}

class Person {
    public void hi() {
        System.out.println("Person hi()");
    }
    public void ok(String str) {
        System.out.println("Person ok() " + str);
    }
}

输出是:

匿名内部类重写了 hi 方法98 外部类的n1 = 99
Person ok() jack

匿名内部类实现接口:不怕被问到!彻底搞懂Java内部类的使用

package com.fwedu.innerclass;

public class Test {
    public static void main(String[] args) {
        MyInterface myInterface = new MyInterface() {
            @Override
            public void test() {
                System.out.println("test");
            }
        };
        myInterface.test();
    }
}

interface MyInterface {
    public void test();
}

成员内部类

将成员内部类当作外部类的一个成员变量/成员方法来使用就好了。
在这里插入图片描述

外部其他类 访问 成员内部类:(因为成员内部类不是static的,因此它的访问需要一个外部类的对象才能调用,用法和成员变量/成员方法是一致的)

// 第一种方式
// outer08.new Inner08(); 相当于把 new Inner08()当做是 outer08 成员
// 这就是一个语法, 不要特别的纠结.
Outer08.Inner08 inner08 = outer08.new Inner08();
inner08.say();
// 第二方式 在外部类中, 编写一个方法, 可以返回 Inner08 对象
Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
inner08Instance.say();

静态内部类

在这里插入图片描述

外部内部类 访问 静态内部类

//方式 1
//因为静态内部类, 是可以通过类名直接访问(前提是满足访问权限)
Outer10.Inner10 inner10 = new Outer10.Inner10();
inner10.say();
//方式 2
//编写一个方法, 可以返回静态内部类的对象实例.
Outer10.Inner10 inner101 = outer10.getInner10();
System.out.println("============");
inner101.say();
Outer10.Inner10 inner10_ = Outer10.getInner10_();
System.out.println("************");
inner10_.say();

因为静态属性和静态方法都从属于类,是类属性,类方法,可以通过类名.的方式来直接访问。


为什么要使用内部类?

采用内部类这种技术,可以隐藏细节和内部结构,封装性更好,让程序的结构更加合理。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wei *

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值