面向对象高级


面向对象(高级)

类变量(静态变量)和类方法(静态方法)

class Child{
    //定义一个静态变量count
    //该变量最大的特点就是会被Child类所有的对象实例共享
    //类变量,可以通过类名来访问
    public static int count = 0;
    ...
    ...
}

类变量内存布局

jdk-8:

img

类变量细节

  1. 静态变量的访问修饰符的访问权限和范围和普通属性是一样的

  2. 类变量是随着类的加载而创建, 所以即使没有创建对象实例也可以访问

  3. 什么时候需要用类变量

    当我们需要让某个类的所有对象都共享一个变量时, 就可以考虑使用类变量(静态变量) : 比如 : 定义学生类, 统计所有学生共交多少钱

  4. 类变量与实例变量(普通属性)区别

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

  5. 类变量的生命周期是随着类的加载开始, 随着类消亡而销毁

类方法细节

  1. 静态方法可以访问静态属性

  2. 什么时候需要类方法

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

    比如: 工具类中的方法utils Math类\Arrays类等等

    在实际开发中, 往往会将一些通用的方法, 设计成静态方法, 这样我们不需要创建对象就可以使用, 比如打印一维数组, 冒泡排序, 完成某个计算任务等等

  3. 类方法和普通方法都是随着类的加载而加载, 将结构信息存储在方法区: 类方法中无this的参数

  4. 普通方法和对象有关, 需要通过对象名调用, 类方法可以通过类名调用, 也可以通过对象名调用

  5. 类方法中不允许使用和对象有关的关键字, 比如this和super. 普通方法可以

  6. 类方法中只能访问静态变量或静态方法

  7. 普通成员方法, 既可以访问普通变量(方法), 也可以访问静态变量(方法) (必须遵守访问权限)

深入理解main方法

  1. main方法是虚拟机调用

  2. java虚拟机需要调用类的main()方法, 所以该方法的访问权限必须是public

  3. java虚拟机在执行main()方法时不必创建对象, 所以该方法必须是static

  4. 该方法接收String类型的数组参数, 该数组中保存执行java命令时传递给所运行的类的参数

    //测试
    public class Hello{
        public static void main(String[] args){
            //args 是如何传入
            //遍历显示
            for(int i = 0;i < args.length;i++){
                System.out.println("第" + (i + 1) + "个参数=" + args[i])
            }
        } 
    }
    

    img

  5. java 执行的程序 参数1 参数2 参数3

    img

  6. 在main()方法中, 我们可以直接调用main方法所在类的静态方法或静态属性

  7. 但是不能直接访问该类中的非静态成员, 必须创建该类的一个实例对象后, 才能通过这个对象去访问类中的非静态成员

    public class Main01{
        //静态变量
        private static String name = "jack";
        //非静态变量
        private int n1 = 100;
        public static void main(String[] args){
            //可以直接使用name
            //1.静态方法可以访问本类的静态成员
            System.out.println("name=" + name);
            //2.静态方法不可以访问本类的非静态成员
            System.out.println("n1=" + n1); //错误
            Main01 main01 = new Main01();//先创建一个本类的对象, 再调用n1
             System.out.println("n1=" + main01.n1); 
        }
        
    }
    

代码块使用细节

  1. static代码块也叫静态代码块, 作用就是对类进行初始化, 而且它随着类的加载而执行, 并且只会执行一次(因为类加载只会一次). 如果是普通代码块, 每创建一次对象, 就执行

  2. 类什么时候被加载

    1. 创建对象实例时(new)
    2. 创建子类对象实例时, 父类也会被加载
    3. 使用类的静态成员时(静态属性, 静态方法)
  3. 普通的代码块, 在创建对象实例时, 会被隐式的调用. 被创建一次, 就会被调用一次. 如果只是使用类的静态成员时, 普通代码块并不会执行(类加载与普通代码块没有关系)

  4. 创建一个对象时, 在一个类 调用顺序是 :

    1. 调用静态代码块和静态属性初始化(注意 : 静态代码块和静态属性初始调用的优先级一样, 如果有多个静态代码块和多个静态变量初始化, 则按他们定义的顺序调用)

      img

    2. 调用普通代码块和普通属性的初始化(注意 : 普通代码块和普通属性初始化调用的优先级一样, 如果有多个普通代码块和多个普通属性初始化, 则按定义顺序调用)

    3. 调用构造器(优先级最低)

  5. 构造器的最前面其实隐含了 super() 和 调用普通代码块, 静态相关的代码块, 属性初始化, 在类加载时, 就执行完毕了, 因此是优先于构造器和普通代码块执行的

    class A{
        public A(){ //构造器
            //这里有隐藏的执行要求
            //1.super();//默认调用父类无参构造器
            //2.调用普通代码块
        }
    }
    
  6. 我们看一下创建一个子类时(继承关系), 他们的静态代码块, 静态属性初始化, 普通代码块, 普通属性初始化, 构造方法调用顺序如下 :

    1. 父类的静态代码块和静态属性(优先级一样, 按定义顺序执行)

    2. 子类的静态代码块和静态属性(优先级一样, 按定义顺序执行)

    3. 父类的普通代码块和普通属性初始化(优先级一样, 按定义顺序执行)

    4. 父类的构造器

    5. 子类的普通代码块和普通属性初始化(优先级一样, 按定义顺序执行)

    6. 子类的构造器

      解读 :

      (1)进行类的加载

      1.1 先加载父类 1.2 再加载子类

      (2)创建对象

      2.1从子类的构造器开始 2.2进入父类的构造器 2.3调用父类的普通代码块 2.4执行父类的构造器 2.5返回子类执行普通代码块 2.6执行子类的构造器

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

单例设计模式

饿汉式

  1. 构造器私有化 - 防止直接 new
  2. 类的内部创建对象(该对象是static)
  3. 向外暴露一个静态的公共方法, 返回对象
  4. 代码实现

懒汉式

  1. 构造器私有化 - 防止直接 new
  2. 定义一个静态属性, 不直接new
  3. 向外暴露一个静态的公共方法, 返回对象
  4. 在静态公共方法里面进行判断此时是否创建对象, 如果没有创建对象, 那么就创建一个对象

饿汉式与懒汉式的区别

  1. 二者最主要的区别在于创建对象的时机不同 : 饿汉式是在类加载就创建了对象实例, 而懒汉式是在使用时才创建
  2. 饿汉式不存在线程安全问题, 懒汉式存在线程安全问题
  3. 饿汉式存在浪费资源的可能. 因为如果程序员一个对象都没有使用, 那么饿汉式创建的对象就浪费了, 懒汉式是使用时才创建, 就不存在这个问题.

final关键字

使用场景

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

final细节

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

  2. final修饰的属性在定义时, 必须赋初值, 并且以后不能再修改, 赋值可以在如下位置之一选择一个位置赋初值 :

    1. 定义时 : public final double TAX_RATE = 0.08;

    2. 在构造器中

    3. 在代码块中

      class AA{
          /*
          1.定义时:如 public final double TAX_RATE=0.08;
          2.在构造器中
          3.在代码块中
          */
          public final double TAX_RATE = 0.08;
          public final double TAX_RATE2;
          public final double TAX_RATE3;
          
          public AA(){
              TAX_RATE2 = 1.1;
          }
          {
              TAX_RATE3 = 0.8;
          }
      }
      
  3. 如果final修饰的属性是静态的, 则初始化的位置只能是 :

    1. 定义时
    2. 静态代码块 (不能在构造器中赋值)
  4. final类不能继承, 但是可以实例化对象

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

  6. 一般来说, 如果一个类已经是final类了, 就没有必要再将方法修饰成final方法

  7. final不能修饰构造器

  8. 包装类, String都是final类

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

    img

public class Something{
    public int addOne(final int x){//final可以修饰形参
        ++x;//错误,不能修改final x 的值
        return x + 1;//可以,没有改变x的值
    }
}

抽象类

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

抽象类细节

  1. 抽象类不能被实例化
  2. 抽象类不一定要包含abstract方法. 也就是说, 抽象类可以没有abstract方法
  3. 一旦类包含了abstract方法, 则这个类必须声明为abstract
  4. abstract只能修饰类和方法, 不能修饰属性和其他的
  5. 抽象类可以有任意成员(因为抽象类还是类), 比如 : 非抽象方法, 构造器, 静态属性等等
  6. 抽象方法不能有主体, 即不能实现
  7. 如果一个类继承了抽象类, 则它必须实现抽象类的所有抽象方法, 除非它自己也声明为abstract类
  8. 抽象方法不能使用private, final, static来修饰, 因为这些关键字都是和重写相违背的

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

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

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

接口

img

接口细节

  1. 接口不能实例化

  2. 接口中所有的方法是public方法, 接口中的抽象方法, 可以不用abstract修饰

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

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

    abstract class Cat implements IA{
        //不会报错
    }
    
  5. 一个类同时可以实现多个接口

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

    interface IA{
        int n1 = 10;//等价 public static final int n1 = 10;
    }
    
  7. 接口中属性的访问形式 : 接口名.属性名

  8. 接口不能继承其他类, 但是可以继承多个别的接口

    interface B {
        
    }
    interface C {
        
    }
    interface A extends B,C{
        
    }
    
  9. 接口的修饰符只能是public 和默认, 这点和类的修饰符是一样的

接口与继承的区别

接口实现机制是对单继承机制的补充.

  1. 当子类继承了父类, 就自动的拥有父类的功能
  2. 如果子类需要扩展功能, 可以通过实现接口的方式扩展

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

  1. 继承的价值主要在于 : 解决代码的复用性和可维护性
  2. 接口的价值主要在于 : 设计, 设计好各种规范(方法), 让其他类去实现这些方法

接口比继承更加灵活

  1. 继承是满足 is - a 的关系, 而接口只需满足 like - a 的关系

接口多态特性

  1. 多态参数

    img

  2. 多态数组
    //main中
    Usb[] usbs = new Usb[2];
    usbs[0] = new Phone_();
    usbs[0] = new Camera_();
    
    /*
    给Usb数组中, 存放Phone 和 Camera 对象, Phone类还有一个特有的方法call(),
    请遍历Usb数组, 如果是Phone对象, 除了调用Usb 接口定义的方法外, 还需要调用Phone特有方法Call()
    */
    for( int i = 0; i < usbs.length; i++){
        usb[i].work();//动态绑定..
        //进行类型判断, 然后进行类型的向下转型
        if(usb[i] instanceof Phone_ ){
            ((Phone_)usbs[i]).call();
        }
    }
    
    //接口
    interface Usb{
        void work();
    }
    class Phone_ implements Usb{
        public void call(){
            System.out.println("手机可以打电话");
        }
        @Override
        public void work() {
            System.out.println("手机工作中...");
        }
    }
    class Camera_ implements Usb{
        @Override
        public void work() {
            System.out.println("相机工作中...");
        }
    }
    
  3. 接口的多态传递现象

    img

内部类

类的五大成员: 属性, 方法, 构造器, 代码块, 内部类

img

内部类的分类

定义在外部类局部位置上 (比如方法内)

  1. 局部内部类 (有类名)
  2. 匿名内部类 (没有类名)

定义在外部类的成员位置(属性或者方法)上

  1. 成员内部类 (没用static修饰)
  2. 静态内部类 (使用static修饰)

局部内部类

class Outer01{//外部类
    private int n1 = 100;
    public void m1(){//方法
        //1.局部内部类是定义在外部类的局部位置, 通常在方法
        //3.不能添加访问修饰符, 因为它的地位就是一个局部变量. 局部变量不能使用修饰符, 但是可以使用final修饰, 因为局部变量也可以使用final
        //4.作用域 : 仅仅在定义他的方法或代码块中
        class Inner01{//局部内部类
            private int n1 = 800;//成员重名
            //2.可以直接访问外部类的所有成员, 包含私有的
            public void f1(){
                System.out.println("n1=" + n1);
                //解读: Outer01.this 本质就是外部类的对象, 即哪个对象调用了m1, Outer01.this就是哪个对象
                System.out.println("外部类的n1=" + Outer01.this.n1);
            }
        }
        //5.外部类在方法中, 可以创建内部类的对象,然后调用方法(注意: 必须在作用域内)
        new.Inner01().f1();
        
    }
}
//6.外部其他类, 不能访问局部内部类
//7.如果外部类和局部内部类的成员重名时, 默认遵循就近原则, 如果想访问外部类的成员, 则可以使用(外部类名.this.成员)去访问

匿名内部类

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

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

//基于接口的匿名内部类

//需求: 想使用IA接口, 并创建对象
//传统方式: 是写一个类(Dog类), 实现该接口, 并创建对象
//但是如果我们 Dog只是使用一次, 后面不再使用
//
//使用匿名内部类
//此时 Dog的编译类型是 IA, 运行类型是匿名内部类
IA Dog = new IA(){
    @Override
    public void cry(){
        System.out.println("狗狗叫唤...");
    }
};//注意分号

//基于类的匿名内部类

//father 编译类型 Father, 运行类型是匿名内部类
//注意("jack")参数列表会传递给 构造器
Father father = new Fatehr("jack"){
    @Override
    public void test(){
        System.out.println("匿名内部类重写了test方法");
    }
};
father.test();
//如果此时father是一个抽象类
//那么必须要是实现抽象类中的抽象方法

匿名内部类的细节

  1. 两种调用方法, 匿名内部类既是一个类的定义, 同时它本身也是一个对象

    img

  2. 可以直接访问外部类的所有成员, 包含私有的

  3. 不能添加访问修饰符, 因为它的地位就是一个局部变量

  4. 作用域 : 仅仅在定义它的方法或代码块中

  5. 外部其他类不能访问匿名内部类

  6. 如果外部类和匿名内部类的成员重名时, 默认遵循就近原则, 如果想访问外部类的成员, 则可以使用(外部类名.this.成员)去访问

匿名内部类实践-当作实参直接传递

//main中
public static void f1(IL il){
    il.show();
}
//当作实参直接传递  
    f1(new IL(){
        @Override
        public void show(){
            System.out.println("123...");
        }
    });
}
interface IL{
    void show();
}

成员内部类

class Outer01{//外部类
    private int n1 = 10;
    public String name = "jack";
    
    //成员内部类, 是定义在外部内的成员位置上
    class Inner01{//成员内部类
        public void say(){
            //可以直接访问外部类的所有成员, 包含私有的
            System.out.println("n1 =" + n1 + "name =" + name);
        }
    }
    //写方法
    public void t1(){
        //使用成员内部类
        Inner01 inner01 = new Inner01();
        inner01.say();
    }
}
//1.可以添加任意访问修饰符(public, protected, 默认, private) 因为它的地位就是一个成员
//2.成员内部类 - 访问 - 外部类成员
//    访问方式 : 直接访问
//3.外部类 - 访问 - 成员内部类
//  访问方式 : 创建对象,再访问

//4.如果外部类和内部类的成员重名时, 默认遵循就近原则, 如果想访问外部类的成员, 则可以使用(外部类名.this.成员)去访问

外部其他类 - 访问 - 成员内部类 //看别人博客

静态内部类

使用static修饰, 定义在外部类的成员位置

  1. 可以直接访问外部类的所有静态成员, 包含私有的, 但不能直接访问非静态成员

  2. 可以添加任意访问修饰符 (public, protected, 默认, private), 因为它的地位就是一个成员

  3. 静态内部类 - 访问 - 外部类

    访问方式 : 直接访问所有静态成员

  4. 外部类 - 访问 - 静态内部类

    访问方式 : 创建对象, 再访问

  5. 外部其他类 - 访问 - 静态内部类 // 看别人博客
  6. 如果外部类和静态内部类的成员重名时, 默认遵循就近原则, 如果想访问外部类的成员, 则可以使用(外部类名.成员)去访问


文章作者: 冬瓜冬瓜排骨汤
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 冬瓜冬瓜排骨汤 !
  目录