首页 > 技术文章 > 面向对象(四)

wyzstudy 2021-10-01 20:44 原文

类变量和类方法

static变量是被所有对象共享的;static变量在类加载的时候就生成了

定义的格式:

访问修饰符 static 类型 变量名 = 值;

访问方式:
类名.变量名或者对象名.变量名

类变量的使用细节

  1. 类变量与普通变量的区别,类变量是所有对象共享的,普通变量是对象独享的
  2. 类变量可以通过类名.类变量名或者对象.类变量名访问
  3. 类变量在类加载的时候就初始化了,即使没有创建对象也能使用类变量
  4. 类变量的生命周期随着类的加载而创建,随着类的销毁而销毁

类方法也叫静态方法,比普通方法多一个static修饰,访问方式可以通过类名.方法名或者对象.方法名访问。

类方法的使用细节

  1. 类方法和普通方法都是随着类的加载而加载,将信息存储在方法区,类方法中没有this,普通方法中有this
  2. 类方法可以通过类名.方法名或者对象.方法名调用,普通方法只能通过对象.方法名调用
  3. 类方法中不能使用this、super关键词
  4. 类方法只能访问类变量和其它类方法,不能访问非类变量和普通方法
  5. 普通方法可以访问类变量和普通变量,也可以访问其它普通方法和类方法

深入理解mian方法

格式:public static void main(String[] args)

  1. main方法是Java虚拟机调用的,必须为public,当不用public修饰的时候我们是不能调用main方法,不在同一个类中。
  2. 使用static修饰的时候,Java虚拟机不用创建类就能调用对象
  3. String[] 参数用于接受传入的参数,可以是多个,我们在执行程序的时候传入,java 程序 参数1 参数2 参数3

代码块

代码块又称为初始化块,属于类的成员,类似于方法,但是与方法又不同,没有方法名,没有返回值,访问修饰符只能使用static修饰,不能通过对象显示调用,只能在类加载的时候或者创建对象的时候隐式调用。

语法格式:

[访问修饰符static]{
}

代码块分为:静态代码块或者普通代码块

当构造器有相同的语句的时候,我们可以抽象出来,写在代码块中。

代码块的使用细节

  1. 静态代码块在类加载的时候,对类进行初始化,只会加载一次,普通代码块对对象进行初始化,在创建对象的时候进行调用,每创建一个对象就会调用一次。

  2. 类什么时候被加载:
    ①创建类的实例的时候(new)

    ②创建子类实例的时候,父类会被加载

    ③使用类的静态属性或者静态方法的时候,类会被加载

  3. 普通代码块,在创建对象实例的时候会被加载,创建一次对象,就会加载一次对象,如果只是使用类的静态成员,普通代码块是不会被加载的,普通代码块相当于构造器的补充。

  4. 创建一个对象的时候,在一个类的调用顺序是:(重点)

    ①调用静态代码块和静态属性初始化(静态代码块、静态属性的优先级是一样的,如果有多个静态代码块和静态变量初始化,按照他们定义的顺序调用)

    class Animal{
        static{
            System.out.println("静态代码块被调用");
        }
        private static int num = getNum();
    
        public static int getNum(){
            System.out.println("静态方法getNum被调用");
            return 100;
        }
    }
    Animal animal = new Animal();
    //静态代码块被调用
    //静态方法getNum被调用
    

    ②普通代码块和普通属性的初始化,普通代码块和普通属性的优先级是一样,如果定义多个普通代码块和普通属性,按照他们定义的顺序进行调用初始化

    class Animal{
        static{
            System.out.println("静态代码块被调用");//1
        }
        private static int num = getNum();
        private int num2 = getN2();
        {
            System.out.println("普通代码块1调用..");//4
        }
        public static int getNum(){
            System.out.println("静态方法getNum被调用");//2
            return 100;
        }
        public int getN2(){
            System.out.println("普通方法N2被调用");//3
            return 200;
        }
    }
    Animal animal = new Animal();
    /*
    静态代码块被调用
    静态方法getNum被调用
    普通方法N2被调用
    普通代码块1调用..
    */
    

    ③构造器调用

    class Animal{
        static{
            System.out.println("静态代码块被调用");//1
        }
        private static int num = getNum();
        private int num2 = getN2();
        {
            System.out.println("普通代码块1调用..");//4
        }
        public static int getNum(){
            System.out.println("静态方法getNum被调用");//2
            return 100;
        }
        public int getN2(){
            System.out.println("普通方法N2被调用");//3
            return 200;
        }
        public Animal(){
            System.out.println("Animal无参构造函数进行初始化");
        }
    }
    Animal animal = new Animal();
    /*
    静态代码块被调用
    静态方法getNum被调用
    普通方法N2被调用
    普通代码块1调用..
    Animal无参构造函数进行初始化
    */
    
  5. 构造器的最前面其实隐含了super()和调用普通代码块,静态代码块和属性,在类加载的时候就完成了,因此优先与普通代码块和构造器的执行

    class AAA{
        {
            System.out.println("AAA的普通代码块");
        }
        public AAA(){
            //隐含两个语句
            //1.super()
            //super()
            //2.调用普通代码块
            System.out.println("AAA的无参构造函数");
        }
    }
    class BBB extends AAA{
        {
            System.out.println("BBB的普通代码块");
        }
        public BBB(){
            //隐含两个语句
            //1.super()
            //super()
            //2.调用普通代码块
            System.out.println("BBB的无参构造函数");
        }
    }
    new BBB();
    /*
    AAA的普通代码块
    AAA的无参构造函数
    BBB的普通代码块
    BBB的无参构造函数
    */
    
  6. 创建一个具有继承关系的子类对象时,他们的静态代码块、普通代码块、构造器执行顺序:

    ①先加载类信息,父类的静态代码块、静态属性执行,按照定义的顺序执行

    ②加载子类的信息,子类的静态代码块、静态属性执行,按照定义的顺序执行

    ③父类的普通代码块和普通属性进行初始化,按照定义的顺序执行

    ④父类的构造器执行

    ⑤子类的普通代码块和普通属性执行,按照定义的顺序执行

    ⑥子类的构造器执行

    class A1{
        private static int num1 = getN1();
        static{
            System.out.println("A1的静态代码块");//2
        }
        private int num2 = getN2();
        {
            System.out.println("A1的普通代码块");//6
        }
        private static int getN1(){
            System.out.println("A1的静态方法getN1进行静态变量的初始化");//1
            return 100;
        }
        private int getN2(){
            System.out.println("A1的普通方法getN2进行普通变量初始化");//5
            return 200;
        }
    
        public A1(){
            //隐含了两个语句
            //super()
            //普通代码块和普通变量的初始化
            System.out.println("A1的无参构造函数");//7
        }
    }
    class B2 extends A1{
        private static int num1 = getN1();
        static{
            System.out.println("B2的静态代码块");//4
        }
        private int num2 = getN2();
        {
            System.out.println("B2的普通代码块");//9
        }
        private int getN2(){
            System.out.println("B2的getN2执行,进行普通变量的初始化");//8
            return 100;
        }
        public static int getN1(){
            System.out.println("B2的静态方法执行,静态变量被初始化");//3
            return 200;
        }
        public B2(){
            //隐含了两个语句
            //super()
            //普通代码块和普通变量的初始化
            System.out.println("B2的无参构造函数被执行");//10
        }
    }
    new B2();
    /*
    A1的静态方法getN1进行静态变量的初始化
    A1的静态代码块
    B2的静态方法执行,静态变量被初始化
    B2的静态代码块
    A1的普通方法getN2进行普通变量初始化
    A1的普通代码块
    A1的无参构造函数
    B2的getN2执行,进行普通变量的初始化
    B2的普通代码块
    B2的无参构造函数被执行
    */
    
  7. 静态代码块只能调用静态成员,普通代码块可以调用任意成员

单例设计模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

饿汉式:
1.构造器私有化

2.内部创建对象实例

3.对外提供一个公共方法访问这个实例

public class Singleton {
    private Singleton(){
        
    }
    private static Singleton singleton = new Singleton();
    
    public static Singleton getInstance(){
        return singleton;
    }
}

懒汉式:

1.构造器私有化

2.内部创建对象引用,不进行初始化,在调用对象的时候进行初始化

3.对外提供一个公共方法访问这个实例

public class Singleton {
    private Singleton(){

    }
    private static Singleton singleton;

    public static Singleton getInstance(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

饿汉式和懒汉式区别:
1.饿汉式在加载类信息的时候就创建了类的实例,不存在线程安全问题,但是不能实现懒加载的效果

2.懒汉式在需要使用类的实例的时候才创建类实例,实现了懒加载,但是只能在单线程的情况下使用,存在线程安全问题

final关键词

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

当我们不需要某个类被继承的时候可以使用final修饰;
当我们不需要某个方法被子类重写的时候可以使用final修饰
当我们希望某个值被赋值之后再修改(常量)可以使用final修饰

final细节

  1. final修饰属性的时候,在定义的时候必须赋值,赋值可以在下列位置之一:
    ①定义的时候
    ②构造器中
    ③代码块中

    public class Demo3 {
        private final double NUM = 1.0;//在定义的时候赋值
        private final double NUM2;
        private final double NUM3;
        public Demo3(){
            NUM2 = 2.0;//在构造器中赋值
        }
        {
            NUM3 = 3.0;//在代码块中赋值
        }
    }
    
  2. 如果final修饰的属性是静态的,那么赋值只能在定义的时候或者静态代码块中赋值,不能在构造器中赋值

    class CC{
        private static final double NUM = 1.0;
        private static final double NUM1;
        static {
            NUM1 = 2.0;
        }
    }
    
  3. final修饰的类是不能被继承的,但是可以被实例化,比如:String

  4. final修饰的方法不能被重写,但是可以被继承

  5. 如果一个类已经是final类,就没有必须再将方法修饰成final

  6. final不能修饰构造器

    不同于方法,构造器不能是abstract, static, final的.

    ①构造器不是通过继承得到的,所以没有必要把它声明为final的。

    ②同理,一个抽象的构造器将永远不会被实现,所以它也不能声明为abstract的。

    ③构造器总是关联一个对象而被调用,所以把它声明为static是没有意义的。

  7. final和static搭配使用,效率会更高,不会导致类的加载,底层会进行优化

    //优化前
    public class Demo4 {
        public static void main(String[] args) {
            System.out.println(DD.NAME);
        }
    }
    class DD{
        public  static String NAME = "NAME";
        static{
            System.out.println("静态代码块被执行");
        }
    }
    
    /*
    静态代码块被执行
    NAME
    */
    
    
    //优化后:
    public class Demo4 {
        public static void main(String[] args) {
            System.out.println(DD.NAME);
        }
    }
    class DD{
        public final static String NAME = "NAME";
        static{
            System.out.println("静态代码块被执行");
        }
    }
    /*
    NAME
    */
    
  8. 包装类和String都是final修饰的类

抽象类

当父类的某个方法不确定如何实现的时候,可以将其声明为抽象方法,将其交给子类去实现,这样父类就变成了抽象类,当一个类有一个抽象方法的时候,就必须将其声明为抽象类,抽象类不能实例化。

abstract class Animal{
    private String name;
    public void setName(String name){
        this.name = name;
    }
    
    public abstract void eat();
}

抽象类的价值更多在于设计,让子类继承并实现,在框架和设计模式中使用较多

抽象类细节

  1. 抽象类是不能被实例化的

  2. 抽象类不一定包含抽象方法,抽象类还可以包含普通方法,构造函数,但是包含抽象方法的类一定是抽象类

  3. 修饰符abstract只能修饰类和方法,不能修饰属性

  4. 抽象类还是类,可以包含任意的成员

    abstract class Animal{
        //属性
        private String name;
        public void setName(String name){
            this.name = name;
        }
        //静态方法
        public static void hi(){
            
        }
        //构造器
        public Animal(){
            
        }
    	//抽象方法
        public abstract void eat();
    }
    
  5. 抽象方法不能有主体

  6. 如果一个类继承了抽象类,则必须实现所有的抽象方法,除非自己也是抽象类

    abstract class Animal{
        public abstract void eat();
    }
    class Cat extends Animal{
    
        @Override
        public void eat() {
            
        }
    }
    
  7. 抽象方法不能使用private、final、static来修饰,因为这些关键词都与重写相违背

接口

接口就是说给一些没有实现的方法封装到一起,到某个类要使用的时候,再把具体的方法实现
语法格式:
interface 接口名{

抽象方法

属性

}

实现接口

class 类 implements 接口名{

}

注意:
在JDK7之前,接口中所有的方法都是抽象方法,在JDK8后,接口中可以有静态方法、默认方法,也就是说接口中的方法可以有实现体。

public interface AInterface {
    public int num = 10;
    //抽象方法
    public void hi();
    //默认方法
    default public void hello(){
        System.out.println("hello");
    }
    //静态方法
    public static void cat(){
        System.out.println("cat");
    }
}

接口细节

  1. 接口是不能被实例化的

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

  3. 一个普通类实现接口,必须实现接口中的所有方法

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

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

    interface A{
        
    }
    interface B{
        
    }
    class C implements A,B{
        
    }
    
  6. 接口中的属性只能是final,而且是public final static修饰的,比如:int num = 1;实际上是:public final static int num = 1;访问形式是:接口名.属性名

  7. 接口不能继承其它类,但是能继承其它接口

  8. 接口只能使用public和默认的修饰符修饰,跟类一样

接口和继承类比较

  1. 解决的问题不同
    继承类主要解决代码复用性和可维护性
    接口主要解决的设计规范让子类去实现这些规范
  2. 接口比继承更加灵活
    继承是is-a关系,接口是like-a关系
  3. 接口在一定程度是实现代码解耦

接口多态传递现象:

interface A{

}
interface B extends A{

}
class C implements B{
    
}
B b = new C();//正确
A a = new C();//正确

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

推荐阅读