六:java面向对象二

70 阅读12分钟

类变量(静态变量)

特点:该变量会被该类所有的实例共享

语法:访问修饰符 static 数据类型 变量名

// 用static修饰
public class Main {
    private String name;
    public static int count = 0; // 静态变量
    public static void main(String[] args) {

    }
}

类变量使用细节

什么时候需要使用类变量

当各个实例都有共享/访问同一个变量的时候

类变量和实例变量的区别

类变量所有实例共享,实例变量(属性)当前实例共享

类方法

什么时候用到类方法

当我们的方法中不涉及到任何静态成员的时候,可以将实例方法设计成静态方法,提高开发效率,比如工具类中的方法。或者我们希望某个方法不用创建实例也能调用,也应该设计成静态方法。

自己的理解:基本上可以理解为工具方法,相当于js中的某一个对象下创建了很多工具方法。

类方法(静态方法)注意细节
  1. 类方法和实例方法,都是随着类的加载而加载。
  2. 类方法中没有this这个参数,不能访问this,普通方法中有this
  3. 类方法可以通过类名调用,也可以通过实例调用,实例方法只能通过实例调用
  4. 类方法中不能使用实例对象有关的关键字,比如this,super,实例方法可以
  5. 类方法只能访问类变量(静态变量)和类方法,不能访问实例变量和实例方法
  6. 访问类变量和类方法的时候,依然要遵守访问权限 小结:静态方法只能访问静态成员,实例方法可以访问静态成员和实例成员
mian方法解析
  1. mian方法谁在调用 1)mian方法是java虚拟机在调用,所以一定是public,不能是别的,因为java虚拟机不是和mian方法不是同一个类。 2)为什么main方法必须是static,因为java虚拟机调用main方法的时候不创建实例。 3)main方法参数必须是字符串数组,会在执行程序的命令行中写参数传进去,比如java HelloWorld params1 params2 4)在main方法中,可以使用当前类的静态方法和静态属性 5)要在静态main方法中访问非静态属性,则可以通过先创建实例在访问的方式访问 Main01 mian01 = new Main01()

代码块

代码块属于类中的成员,既类的一部分,类似于类方法,将逻辑语句封装在方法体中,通过{}包围起来。

基本语法

[修饰符] {
  // todo
};

修饰符要么不写,要么只能写static。

对于代码块的理解: 代码块相当于另一种形式的构造器,是对构造器机制的补充,可以做初始化。

应用场景:如果多个构造器中,有重复的语句,则可以抽离到代码块中,提高代码的复用性。构造器在执行的时候,会优先执行代码块中的语句。

代码块细节

  1. static代码块,作用就是对类进行初始化,它随着类的加载而执行(重点),并且只会执行一次,如果是普通代码块,则每创建一个实例,就会执行一次。

  2. 类在什么时候会被加载(重点,必背

  • 1)创建实例对象的时候
  • 2)创建子类对象,父类也会被加载
  • 3)使用类的静态成员时
  1. 普通代码块,在创建实例时候,会被隐式调用,创建一次实例,就会调用一次,如果只是使用了类的静态成员,则不调用。

  2. 重点,必背)创建一个实例时候,在一个类中,调用顺序是:

  • 1)调用静态代码块和静态属性初始化(这两个优先级一样,按照定义顺序去调用)
  • 2)调用实例代码块和实例属性,实例代码块和实例属性调用优先级是一样的,如果有多个,依然是按照定义顺序从上到下
  • 3)调用构造器
  1. 构造器的最前面,其实隐藏了super()和调用普通代码块(先调用super在调用普通代码块),而静态代码块以及属性的初始化在类加载的时候就已经执行完毕。super执行代表

  2. 静态代码块只能调用静态成员,普通代码块能调用静态成员和非静态成员

案例(重点面试题,必背):
public class Main {
    public static void main(String[] args) {
        new Child();
    }
}
class Father {
    String name2 = "name2";
    static {
        System.out.println("父类静态代码块执行");
    }
    {
        System.out.println("父类代码块执行");
    }
    Father() {
        System.out.println("父类构造函数执行");
    }
}
class Child extends Father {
    String name3 = "name3";
    static {
        System.out.println("子类静态代码块执行");
    }
    {
        System.out.println("子类代码块执行");
    }
    Child() {
        System.out.println("子类构造函数执行");
    }
}

顺序:

  • 1)父类静态代码块执行
  • 2)子类静态代码块执行
  • 3)父类代码块执行
  • 4)父类构造函数执行
  • 5)子类代码块执行
  • 6)子类构造函数执行

解释:

  1. 要想创建子类,首先要加载子类,想要加载子类,首先又要加载父类
  2. 加载父类就要执行父类静态代码块,所以首先执行1)
  3. 加载完父类,接着加载子类,所以接着执行2)
  4. 加载完子类了,就要创建子实例,创建实例就要执行构造函数,而构造函数内,优先隐式调用super(),执行super的意思就是执行父类的构造函数,执行父类构造函数之前又优先执行父类普通代码块,所以接着执行3),然后执行4)
  5. super执行完后,接着执行子类的构造器,执行子类的构造器之前又优先调用子类普通代码块,所以执行5),然后执行6),完毕。

单例模式(静态方法的经典使用)

定义:在整个程序运行的过程中,从开始到结束,最多只能存在某个类的一个实例,且该类只提供一个获取对象实例的方法。

实现步骤

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

单例饿汉式

class GirlFriend {
    private final String name;
    private static final GirlFriend girlFriend = new GirlFriend("jane");

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

    public static GirlFriend getInstance() {
        return girlFriend;
    }
}

GirlFriend gf = GirlFriend.getInstance()

为什么叫做饿汉式,因为在类加载的时候就已经创建了实例,此时还没有调用呢

单例懒汉式

class GirlFriend {
    private final String name;
    private static GirlFriend girlFriend;

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

    public static GirlFriend getInstance() {
        if (girlFriend == null) {
            return new GirlFriend("jane");
        } else {
            return girlFriend;
        }
    }
}

饿汉式和懒汉式区别

  1. 懒汉式在调用的时候才创建,饿汉式在加载类的时候创建。
  2. 懒汉式存在线程安全的问题(多个线程同时执行getInstance时候,会在一瞬间执行new出多个实例)
  3. 饿汉式因为可能创建了却不用,有可能浪费资源

final关键字

final可以修饰类,属性,方法和局部变量。

通常以下情况会使用final:

  1. 当不希望类被继承时候,可以用final
  2. 当不希望父类的某个方法被子类覆盖,重写时候,用final
  3. 当不希望某个属性的值被修改,用final
  4. 当不希望某个局部变量被修改,用final

final使用注意事项

  1. final修饰的属性一般叫常量,常量名一般用大写XX_XX
  2. final在定义的时候必须赋值,并且不能修改,赋值可以在以下位置之中
  • 定义的时候
  • 在构造器中(非静态属性)
  • 在代码块中
  1. final如果是修饰的是静态属性,则不能在构造器中赋值
  2. final类不能被继承
  3. final方法可以被继承,但不能被覆盖
  4. final不能修饰构造方法
  5. final和static搭配修饰属性,此时访问这个属性不会导致类加载(编译器做了优化)
class B {
    public final static int num = 10;
}
B.num // 此时不会导致类加载
  1. 包装类,Boolean,String等都是final类

抽象类

抽象方法:当一个父类的某个方法需要定义,但还不知如何实现,于是只定义暂不实现,那么这个方法叫做抽象方法,真正实现等子类去实现。 抽象类:具有抽象方法的类,叫做抽象类。

代码示范

abstract class A {
  public abstract test void();
}

抽象类的价值更多在于设计,设计者设计好以后让子类去实现。面试比较喜欢问(问设计,不是问定义)。

抽象类注意细节

  1. 抽象类不能被实例化
  2. 抽象类不一定要包含抽象方法
  3. abstract只能修饰类和方法,不能修饰属性
  4. 抽象类除了有抽象方法,还可以有其他任意成员
  5. 一个类继承了抽象类,那么它必须实现抽象方法,除非这个类也是抽象类
  6. 抽象方法不能使用private,final,static这三个关键字修饰,因为这三个关键字都是和重写违背的

接口

定义:给出一些没有实现的方法,封装到一起,再根据具体情况把这些方法实现出来。 jdk8以后,接口可以有具体实现的静态方法(static),也可以有默认方法(非静态,用default关键字定义),也就是jdk8以后,接口可以有具体方法的实现 语法

interface A {
  public static final String name = "jane";
  test1 void();
  test2 void();
  // 默认方法
  defatult pulick void test3() {
    // todo
  }
  // 静态方法
  public static void test4() {
    // todo
  }
}
publick class B implements B {
  @overwrite
  test1 void() {
    // todo
  }
  @overwrite
  test2 void() {
    // tot
  }
}
接口的注意事项
  1. 接口不能被实例化
  2. 接口中的所有方法,都是public方法,没有别的修饰词,被实现的类中的对应实现的方法,也还同样的是public方法。
  3. 一个普通类实现接口,就必须实现该接口的所有方法
  4. 抽象类可以不实现接口的方法
  5. 一个类可以同时实现多个接口(但是一个类只能继承一个类)
  6. 接口中的属性,必须是public,static,final这三个修饰词,不能是别的修饰词,但可以简化不写这三个,必须初始化
  7. 接口属性的访问形式:接口名.属性名
  8. 接口不能继承类,但接口可以继承多个别的接口 interface A extends B,C {}
  9. 接口的修饰词,只能是public和默认,不能是别的,可以省略,和类的修饰符是一样的规则
接口和继承类比较
  1. 解决的问题不同:继承的价值主要解决代码复用性和维护性,接口的价值在于设计。
  2. 接口比继承更灵活,继承是is->a,接口是like->a。
  3. 接口在一定程度上实现代码解耦。

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

接口的多态

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

案例

public class Main {
    public static void main(String[] args) {
        A b = new B();
        b = new C();
    }
}
interface A {}
class B implements A {}
class C implements A {}

内部类

一个类的内部完成了嵌套了另一个类,被嵌套的类叫做内部类,嵌套其他类的类叫做外部类。

内部类最大的特点:可以直接访问私有属性,并且可以体现类与类之间的包含关系。

语法

// 外部类
class A {
    // 内部类
    class B {
        
    }
}
类的五大成员(面试题)
  1. 属性
  2. 方法
  3. 构造器
  4. 代码块
  5. 内部类
内部类的分类(4种)

定义在外部类的局部(比如方法内)

  1. 局部内部类(有类名)
  2. 匿名内部类(没有类名)(重点) 定义在外部类的成员位置上
  3. 成员内部类(没有static修饰)
  4. 静态内部类(有static修饰)
局部内部类

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

  1. 可以直接访问外部类所有成员,包含私有的。
  2. 不能添加访问修饰符,因为它就是一个局部变量(final可以添加)。
  3. 作用域:仅仅在当前方法或者代码块中。
  4. 外部类要使用局部内部类,唯一方式是创建内部类对象,然后调用方法和属性。
  5. 外部其他类不能访问局部内部类。
  6. 如果外部类的属性和局部内部类的属性重名,访问遵循就近原则,要访问外部类属性,可以写成:外部类.this.xx。
匿名内部类(重点难点)

匿名内部类定义外外部类的局部(方法中,代码块中),没有名字,同时,匿名内部类也是一个对象。

语法 new 接口/类 () {}

interface B {
    void test01();
}

// 外部类
class A {
    public void test () {
        // 匿名内部类
        B b = new B() {
            public void test01() {
                //
            }
        };
        
        b.test01();
    }
}
匿名内部类使用细节
  1. 可以访问外部类的所有成员
  2. 只能调用一次就不能再调用了
  3. 不能添加访问修饰符(final也不行)
  4. 作用域仅仅在局部(代码块/方法体中)
  5. 外部类和其他类,不能访问匿名内部类
  6. 外部类属性和匿名内部类属性重名,访问同样遵循就近原则
成员内部类

定义在外部类的成员位置

语法

// 外部类
class A {
    // 成员内部类
    class B {
        void test02() {}
    }
}
  1. 可以直接访问外部类的所有成员
  2. 可以添加任意的访问修饰符
  3. 作用域和外部类的其他成员一样
  4. 外部类访问方式:先创建内部类对象,再访问
  5. 其他类访问:外部类.new 内部类()
静态内部类
  1. 放在外部类成员位置
  2. 用static修饰
  3. 可以直接访问外部类所有成员
  4. 可以添加任意修饰符
  5. 作用域:同其他成员,为整个类体