12、面向对象(高级)

2021/12/17 6:21:58

本文主要是介绍12、面向对象(高级),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

目录
  • 一、类变量和类方法
    • 1、类变量(静态变量)
    • 2、类方法(静态方法)
  • 二、main方法语法解析
  • 三、代码块{}
    • 1、什么是代码块?
    • 2、使用细节
    • 3、代码块的好处
  • 四、单例设计模式(静态方法和属性的经典使用)
  • 五、final关键字
    • 1、使用场景
    • 2、使用细节
  • 六、抽象类(abstract)
    • 1、为什么需要抽象类?
    • 2、抽象类的介绍
    • 3、使用细节
  • 七、模板设计模式——抽象类的最佳实践
    • 1、基本介绍
    • 2、作用
  • 八、接口(interface)
    • 1、为什么需要接口?
    • 2、什么是接口?
    • 3、应用场景
    • 4、注意事项
    • 5、接口与继承
    • 6、接口的多态性
  • 九、内部类
    • 1、为什么需要内部类?
    • 2、什么是内部类?
    • 3、语法
    • 4、内部类的分类
    • 5、成员内部类
    • 6、静态内部类
    • 7、局部内部类
    • 8、匿名内部类

一、类变量和类方法

独立于对象之外的变量和方法

1、类变量(静态变量)

(1)为什么需要静态变量?

在实际开发场景中,处理问题时,会需要两个类在同一内存区域中共享一个数据,或共用一个方法,此时就需要使用到静态变量和静态方法

(2)什么是静态变量?

类变量也叫静态变量,静态属性,为该类所有对象共享的变量

  • 语法:
    • 访问修饰符 static 数据类型 变量名; //(推荐)
    • static 访问修饰符 数据类型 变量名;

(3)怎么使用静态变量?

  • 访问
    • 类名.类变量名;(推荐)
    • 对象名.类变量名;

(4)使用静态变量时需要注意的问题

  • 使用时机:需要让某个类所有对象共享一个属性时,使用静态变量
  • 类变量与实例变量的区别
    • 类变量为该类所有对象共享
    • 实例变量每个对象各自独享
  • 加上static的变量称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
  • 访问时需要满足访问权限和范围这一限制
  • 实例变量不能通过类名访问
  • 类变量在类加载时就创建了,即使无实例对象也能访问
  • 类变量的生命周期随着类的加载而开始,随着类的消亡而结束

2、类方法(静态方法)

(1)什么是静态方法?

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

(2)怎么使用静态方法?

在满足访问修饰符的前提下

  • 访问
    • 类名.类方法名
    • 对象.类方法名

(3)使用静态方法时需要注意的问题

  • 类方法和普通方法随类的加载而加载,将结构信息存储在方法区
    • 类方法中无this的参数
    • 普通方法隐含this的参数
  • 类方法可以通过类名调用,也可以通过对象调用
  • 普通方法和对象有关,需要对象调用
  • 类方法中不允许使用和对象有关的关键字(thissuper
  • 类方法只能访问类变量和类方法(静态方法只能访问静态成员
  • 普通方法既可以访问静态成员,又可以访问非静态成员
  • 静态属性,方法可以别继承,但不能被重写

二、main方法语法解析

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

(1)理解main方法

  • main方法由Java虚拟机调用
  • JVM需要调用类的main方法,则该方法的访问权限必须为public
  • JVM在执行main方法时,不必创建对象,由类名直接调用,则该方法是static
  • 该方法接受String类型数组参数,该数组保存执行Java命令时传递给所运行的类的参数
  • String[] args的意义:传递参数——在控制台输入:java 文件名 参数一 参数一 ...

(2)说明

  • 在main方法中,可以直接调用main方法所在的类的静态方法或属性
  • 但不能访问该类中非静态成员,必须实例化对象才能访问

三、代码块{}

1、什么是代码块?

(1)代码块

代码块属于类中的成员(类的一部分)

类似于方法,将逻辑语句封装在方法体中,通过{}包围起来

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

(2)语法

修饰符 {
    代码;
};
  • 修饰符可选无或static
  • 逻辑语句中可以为任何逻辑语句(输入,输出,方法调用,循环,判断等)
  • ;可以省略

(3)三种代码块

  • 普通类中方法的方法体
  • 构造代码块:创建对象时被调用,优先于构造方法被执行(初始化一个类的所有构造器的共有特征
  • 静态代码块:只执行一次,用来初始化静态成员变量(随着类的加载而执行

2、使用细节

(1)静态代码块随着类的加载而执行,且只执行一次

(2)类什么时候被加载

  • 创建对象实例时
  • 创建子类对象实例时,父类也会别加载
  • 使用类的静态成员时,类被加载,静态代码块随着类的加载而执行

(3)构造代码块在创建实例时被隐式地调用,创建一次,调用一次

(4)代码块在创建一个对象时,在一个类中的调用顺序是

  • 调用静态代码块,和静态属性的初始化(二者优先级一样,按顺序执行)
  • 调用构造代码块,和普通属性的初始化(二者优先级一样,按顺序执行)
  • 调用构造方法

(5)构造器前隐含了super()构造器,和调用构造代码块,即父类构造器优先于调用本类构造代码块

(6)创建一个子类对象时,他们的静态代码块,静态属性初始化,构造代码块,普通属性初始化,构造方法的调用顺序如下:

  • 父类的静态代码块和静态属性的初始化
  • 子类的静态代码块和静态属性的初始化
  • 父类的构造代码块和普通属性的初始化
  • 父类的构造器
  • 子类的构造代码块和普通属性的初始化
  • 子类的构造器
graph LR 创建子类对象 --> 父类会被加载 --> 静态代码块被执行 执行静态代码块 --> 父类被继承子类继承 --> 父类的静态代码块和静态属性初始化优先于子类 调用父类构造代码块和普通属性的初始化 --> 构造器的调用父类优先于子类 --> 父类构造器调用完执行父类构造器 调用父类构造器 --> 调用子类构造造代码块和普通属性的初始化 调用子类构造代码块和普通属性的初始化 --> 构造代码块和普通属性的初始化优先于构造器 构造器调用

(7)静态代码块只能直接调用静态成员,构造代码块可以调用任意成员

3、代码块的好处

(1)构造器的补充机制,相当于另外一种形式的构造器,可以做初始化的操作

(2)如果多个构造器中都有重复语句,可以抽取到代码块中,提高代码的重用性

四、单例设计模式(静态方法和属性的经典使用)

(1)什么是设计模式?

设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式

(2)什么是单例模式?
所谓的单例模式,即单个实例,指采取一定的方法保证在整个的软件系统中只能存在一个实例,并且该类只提供一个取得其对象的方法

  • 饿汉式
  • 懒汉式

(3)饿汉式

  • 构造器私有化——》防止用户新建实例
  • 类的内部创建对象(静态属性)
  • 向外暴露一个静态的公共方法——》getInstance()
public class SingleTon01 {

    //通过类调用静态方法获取对象
    GirlFriend instance = GirlFriend.getInstance();
}

class GirlFriend {

    private String name;

    //2、在类的内部创建对象
    //为了能在静态方法中使用,需要将其修饰为静态方法
    private static GirlFriend gf = new GirlFriend("小花花");

    //保证只有一个实例对象
    //1、构造器私有化
    private GirlFriend(String name) {
        this.name = name;
    }

    //3、提供暴露的静态公共方法,返回gf对象
    public static GirlFriend getInstance() {
        return gf;
    }
}

(4)懒汉式

  • 构造器私有化
  • 类的内部类创建对象的引用(不直接指向对象,不直接实例化)
  • 提供静态公共方法,对引用进行判断,为空则实例化,return对象
public class SingleTon02 {
}

class Cat {
    private String name;

    //不直接实例化
    private static Cat cat;

    private Cat(String name) {
        this.name = name;
    }

    //懒汉式——在静态的公共方法中进行对象的实例化
    public static Cat getInstance() {
        if (cat == null) {//保证单例
            cat = new Cat("小可爱");
        }
        return cat;
    }
}

(5)比较

  • 二者主要区别在于创建对象的时机不同,饿汉式类加载即创建,懒汉式使用才创建
  • 饿汉式五线程安全问题,懒汉式有线程安全问题
  • 饿汉式存在资源浪费的可能

五、final关键字

可以用来修饰类,属性,方法和局部变量

  • 基本数据类型:值不变
  • 引用数据类型:对象不变
  • 方法:不能重写
  • 类:不能继承

1、使用场景

(1)当不希望类被继承时,用final修饰——修饰类

(2)当不希望父类的某个方法被子类重写时,用final——修饰方法

(3)当不希望类的某个属性被修改时,用final——修饰属性

(4)不希望某个局部变量被修改——修饰局部变量

2、使用细节

(1)final修饰的属性又叫做常量,一般用XX_XX_XX来命名

(2)final修i是的属性在定义时,必须赋初始值,且不能修改

  • 赋值的位置

    • 定义时: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类

六、抽象类(abstract)

1、为什么需要抽象类?

父类方法的不确定性

当父类的某些方法需要声明,但又不确定该如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类

  • 抽象方法:即没有实现的方法
  • 没有实现:没有方法体
  • 抽象类:当一个方法为抽象方法时,类为抽象类

2、抽象类的介绍

抽象类除了继承毫无意义

(1)用abstract关键字来修饰一个类时,类为抽象类

语法:访问修饰符 abstract class 类名 {}

(2)抽象方法

语法:访问修饰符 abstract 返回类型 方法名(形参列表);(无方法体)

(3)抽象类的价值在于设计,是设计者设计好后,让子类实现抽象类

(4)抽象类为考官常考知识点,子啊框架和设计模式中涉及较多

3、使用细节

(1)抽象类不能实例化

(2)抽象类不一定要有抽象方法,而且可以有实现方法

(3)有抽象方法一定是抽象类

(4)abstract只能修饰类和方法

(5)抽象类仍然是类,可以有任意类可以有的成员

(6)抽象方法不能有方法体

(7)如果一个类继承了抽象类,则必须实现(重写)抽象类的所有抽象方法,除非它也声明为abstract类

(8)抽象方法不能使用private,final和static来修饰,因为与重写向违背

  • private:私有,子类无法继承父类特有的成员
  • final:final方法子类不能继承
  • static:静态成员只与类有关,可以继承不能重写

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

1、基本介绍

抽象类体现的就是一种模板模式的设计,抽象类作为子类的通用模板

子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为

2、作用

(1)当内部功能一部分实现是确定的,一部分实现是不确定的,这时可以把不确定的部分暴露出去,由子类实现

(2)编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式

public class AbstractExercise01 {

    public static void main(String[] args) {

        //创建员工和经理
        CommonEmployee commonEmployee = new CommonEmployee("jack", 51, 2000);
        Manager manager = new Manager("tony", 52, 20000, 100000);

        commonEmployee.work();
        manager.work();
    }
}
//抽象类
abstract class Employee {
    private String name;
    private int id;
    private double salary;

    public Employee(String name, int id, double salary) {
        this.name = name;
        this.id = id;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public abstract void work();
}

class Manager extends Employee {
    private double bonus;//奖金

    public Manager(String name, int id, double salary, double bonus) {
        super(name, id, salary);
        this.bonus = bonus;
    }

    public double getBonus() {
        return bonus;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }

    @Override
    public void work() {
        System.out.println("经理" + this.getName() + "正在工作中...");
    }
}

class CommonEmployee extends Employee {

    public CommonEmployee(String name, int id, double salary) {
        super(name, id, salary);
    }

    @Override
    public void work() {
        System.out.println("员工" + this.getName() + "正在工作中...");
    }
}

八、接口(interface)

接口就是一个标准

1、为什么需要接口?

(1)接口被用来描述一种抽象(用来实现多继承)

(2)接口被用来实现抽象,抽象类也被用来实现抽象,为什么一定要用接口,二者之间的区别是什么?

  • 抽象类内部可能含有非final的变量
  • 但是在接口中存在的变量一定是final
  • final即不可改变,不可改变即为一个标准

(3)为不同类顺利交互提供标准

2、什么是接口?

接口就是给出一些没有实现的方法,封装到一起,在某个类要使用的时候,根据具体情况,实现方法

  • 语法:
//定义接口
interface 接口名 {
    //属性
    //方法
}

//实现
class 类名 implements 接口名 {
    //属性
    //方法
    //必须实现的接口的抽象方法
}
  • 接口中的方法:
    • 抽象方法
    • 默认实现方法
    • 静态方法

3、应用场景

指定标准,接口是需求的实际体现

4、注意事项

(1)接口不能被实例化

(2)接口中的所有方法是public方法,接口中抽象方法可以不用abstract修饰,因为interface中方法默认为public abstract修饰

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

(4)除非声明为抽象类,上(3)可不用全部实现

(5)一个类可以实现多个接口

(6)接口中的属性 ,只能是final,而且是public static final修饰

(7)接口属性的访问:接口名.属性;

(8)接口不能继承类,但可以继承多个别的接口

  • 类继承类
  • 类实现接口
  • 接口继承接口

(9)接口修饰符和类一样,只能是public和默认的

5、接口与继承

(1)解决问题不同

  • 继承:解决代码复用性和可维护性
  • 接口:设计,设计好各种方法的规范,由其他类实现

(2)接口比继承更灵活

  • 继承:满足is-a关系,子类是一个父类
  • 接口:满足like-a,类像一个接口

(3)接口在一定程度上实现代码解耦

接口规范性+动态绑定

6、接口的多态性

(1)参数的多态性

接口引用可以指向实现了接口的类的对象

(2)多态数组

public class Arr {
    
    public static void main(String[] args) {
        //多态数组
        Usb[] usbs = new Usb[2];
        usbs[0] = new Phone();
        usbs[1] = new Camera();
        //调用
        for(int i = 0; i < usbs.length; i++) {
            usbs[i].work;//动态绑定
            if (usbs[i] instenceof Phone) {
                ((Phone)usbs[i]).call();//向下转换:前提是引用指向的对象本来就是Phone类
            }
        }
    }
}
//接口
interface Usb {
    void work();
}

class Phone implements Usb {
    public void call() {
        "手机打电话".sout;//输出
    }
    //实现
    public void work() {
        "手机工作中".sout;
    }
}

class Camera implements Usb {
    public void work() {
        "相机工作中".sout;
    }
}

(3)接口的多态传递

interface IH{}
//IG 继承 IH
interface IG extends IH {}
//Teacher类实现IG接口的同时也实现了IH接口
class Teacher implements IG {}
//主方法简写
main {
    IG ig = new Teacher();//一个IG接口的引用指向了一个Teacher类的实例
    IH ih = new Teacher();//一个IH接口的引用指向了一个Teacher类的实例
}

九、内部类

1、为什么需要内部类?

每个内部类都能独立地实现一个接口,无论外部类是否实现了接口,对内部类而言都不影响,从而结局了Java多继承的问题

2、什么是内部类?

一个类的内部完整地嵌套了另一个类结构,被嵌套的类称为内部类,嵌套其他类的类为外部类,是类的第五大成员(属性,方法,构造器,代码块,内部类)

最大的特点:可以直接访问外部类的私有属性,并且可以体现类之间的层级关系

3、语法

class 外部类 {
    class 内部类 {
        
    }
}

class 外部其他类 {
    
}

4、内部类的分类

  • 定义在外部类成员的位置上的内部类,按静态非静态划分为

    • 成员内部类

    • 静态内部类

  • 定义在外部类成员的局部位置上的内部类,按有无类名区分为

    • 局部内部类
    • 匿名内部类

5、成员内部类

外部类的成员位置,且非static

(1)可以直接访问外部类的所有成员,包含私有的

(2)地位为成员,则可以添加任意访问修饰符

(3)作用域:和外部其他类一样,整个类体

(4)成员内部类访问外部类:直接访问

(5)外部类访问成员内部类成员:在外部类里建立成员内部类对象,再调用

(6)外部其他类访问成员内部类成员

  • 外部类.内部类 引用 = 外部类对象.new 内部类(实参列表);
  • 外部类编写一个方法,返回一个内部类的对象实例

(7)外部类和内部类成员重名时,就近原则,若要访问外部类——外部类名.this.成员;

外部类名.this:表示指向外部类的对象

6、静态内部类

外部类的成员位置,为静态

(1)可以访问外部类所有静态成员,不能访问非静态成员

(2)可以添加任意修饰符

(3)作用域:整个类体

(4)静态内部类直接访问外部类成员

(5)外部类访问静态内部类成员:实例化静态内部类一个对象,再调用、

静态内部类只是说明该类属于外部类,而不属于外部类实例化的对象,而静态内部类的非静态成员,依然和静态内部类的对象相联系,所以需要实例化静态内部类对象后再调用目标成员

(6)外部其他类访问静态内部类

  • 外部类.静态内部类 引用 = new 外部类.静态内部类();
    引用.成员;
    

静态成员可以通过类名来调用,静态内部类为静态成员,ps:静态内部类的成员不一定为静态

  • 在外部类编写一个方法,返回静态内部类实例对象

(7)外部类和内部类成员重名时,就近原则,若要访问外部类——外部类名.this.成员;

7、局部内部类

通常在方法中,有类名

(1)可以直接访问外部类的所有成员,包含私有的

(2)地位为一个局部变量,不能使用权限修饰符,但可以使用final关键字

(3)作用域:仅在定义它的方法或代码块中

(4)局部内部类访问外部类成员:直接访问

(5)外部类访问局部内部类:满足作用域的条件下(即在方法中),创建对象再访问

(6)外部其他类无法访问局部内部类:因为局部内部类为一个成员变量

(7)外部类和内部类成员重名时,就近原则,若要访问外部类——外部类名.this.成员;

8、匿名内部类

匿名的局部内部类,不是没名,而是系统自动分配类名

(1)语法

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

//底层运行逻辑
class 外部类名$02 implements 类或接口 {//实现了类接口或继承了类
    类体;
}
new 外部类名$02();//实例化一个匿名内部类的对象
//例子
public class AnonymousInnerClass {//外部其他类

    public static void main(String[] args) {

        Outer outer = new Outer();
        outer.method();
    }
}

class Outer {//外部类

    private int n1 = 10;//属性

    public void method() {//外部类的方法中建立内部类
        /**
         * 1、需求:使用A接口,并创建对象
         * 2、传统方式:创建一个类重写方法,实现接口,并创建对象
         * 3、若tiger类只使用一次,后面不再使用
         * 4、解决方案:使用匿名内部类,来简化开发
         * */

        //基于接口的匿名内部类
        /**
         * 匿名内部类基于接口的解析
         * 1、tiger的编译类型为:IA接口类型
         * 2、tiger的运行类型为:匿名内部类,匿名内部类并非没有名字,只是由系统分配类名,底层如下
         *    -IA() {类体};:创建了一个实现了接口的匿名内部类,注意为一个语句,需要分号“ ; ”
         *          class Outer$01 implements IA {
         *              类体
         *          };
         *    new IA() {}; :创建了一个匿名内部类的对象
         *          new Outer$01();
         *    IA tiger = new IA() {};
         *          将实例化的一个实现了A接口的匿名内部类的对象的地址传递给tiger
         *3、匿名内部类使用一次,就不能再使用了,但是tiger可以重复使用
         * */

        IA tiger = new IA() {//基于接口的匿名内部类
            @Override
            public void cay() {
                System.out.println("老虎嗷嗷叫");
            }
        };
        //在外部类中,在内部类作用域即方法中,调用内部类的成员——创建一个指向内部类对象的引用,用对象来调用方法
        tiger.cay();

//        Tiger tiger = new Tiger();
//        tiger.cay();

        //基于类的匿名内部类
        /**
         * 匿名内部类基于类的解析
         * 1、father的编译类型:Father
         * 2、father的运行类型:Outer$2   father对象的运行类型:class com.zyhstu.innerclass.Outer$2
         *      -Father("jack") {类体};:创建了一个继承了Father父类的Outer$2子类
         *          class Outer$2 extends Father {
         *              类体;
         *          };
         *      -new Father("jack") {类体};:创建一个匿名内部类Outer$2的对象,并将jack自动传给Father的构造器
         *          new Outer$2() {类体};
         *      -Father father = new Father("jack") {};
         *          实例化一个继承了Father父类的Outer子类的对象,并将地址传递给一个Father的引用
         * */
        Father father = new Father("jack") {
            @Override
            public void test() {
                System.out.println("匿名内部类重写了test()方法");
            }
        };
        father.test();
        System.out.println("father对象的运行类型:" + father.getClass());

        //基于抽象类的匿名内部类
       Animal animal = new Animal() {
           @Override
           void eat() {
               System.out.println("小狗吃骨头");
           }
       };
       animal.eat();
    }
}

interface IA {
    public void cay();
}

//class Tiger implements IA {
//    @Override
//    public void cay() {
//        System.out.println("老虎嗷嗷叫");
//    }
//}
//
//class Dog implements IA {
//    @Override
//    public void cay() {
//        System.out.println("小狗汪汪叫");
//    }
//}

class Father {//外部其他类
    public Father(String name) {//构造器

    }

    public void test() {//方法

    }
}

//抽象类
abstract class Animal {
    abstract void eat();
}

(2)匿名内部类的使用

  • new 类或接口(实参列表) {
        类体;
    }.方法(方法实参);
    
  • 类名 引用 = new 类或接口(实参列表) {
        类体;
    }
    引用.方法(方法实参);
    

(3)注意事项

  • 可以直接访问外部类的所有成员,包含私有的
  • 地位为一个局部变量,不能使用权限修饰符,但可以使用final关键字
  • 作用域:仅在定义它的方法或代码块中
  • 匿名内部类访问外部类成员:直接访问
  • 外部其他类不能访问匿名内部类:局部变量
  • 外部类和内部类成员重名时,就近原则,若要访问外部类——外部类名.this.成员;


这篇关于12、面向对象(高级)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程