1. static关键字

static可以用来修饰:属性、方法、代码块、内部类

1.1 static 修饰属性

使用static关键字修饰的变量(属性)叫静态变量(属性),与之相对的非静态变量称为实例变量(属性)。

实例变量:我们创建了类的多个对象,每个对象都独立拥有一套类中的非静态属性,当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。

静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量,当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,也获取到修改过的值。

关于static关键字修饰的静态变量的说明:

  • 静态变量随着类的加载而加载
  • 静态变量的加载早于对象的创建
  • 由于类只会加载一次,则静态变量在内存中也只会存在一份,存在方发区的静态域中
  • 静态变量可以被对象调用,也可以被类直接调用;类不可以直接调用实例变量。

静态变量举例:System.out.print()Math.PI

静态变量的内存结构:

public class StaticTest {
    public static void main(String[] args) {
        Chinese c1 = new Chinese();
        c1.name = "张三";
        c1.age = 30;

        Chinese c2 = new Chinese();
        c2.name = "李四";
        c2.age = 30;

        c1.nation = "CN";
        System.out.println(c2.nation);
    }
}


class Chinese {
    String name;
    int age;
    static String nation;
}

image-20210301205416335.png

1.2 static修饰方法

  • 随着类的加载而加载,可以通过类.方法()的方式调用
  • 对象可以调用实例方法,也可以调用静态方法,而类只能直接调用静态方法。
  • 静态方法中只能调用静态方法或属性,非静态方法中既可以调用静态方法或属性,也可以调用非静态方法或属性。

1.3 static关键字使用的注意点

在静态方法内,不能使用thissuper关键字。

主要原因是因为生命周期,静态方法的生命周期是类加载时开始,而对象的声明周期是对象的创建,要短于静态方法的生命周期,调用时要保证调用的目标存在于内存中,因此静态方法中只能使用静态的方法或属性,而生命周期较短的对象可以调用静态的方法或属性。

1.4 恰当地使用static关键字。

  • 在开发中,如何确定一个属性是否应声明为static:

    • 属性可以被多个对象共享,不会随着对象的不同而不同。
  • 在开发中,如何确定一个方法是否应声明为static:

    • 操作静态属性的方法通常设置为静态
    • 工具类中的方法习惯上声明为static,如MathArrays

1.5 static的应用——单例模式

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

所谓单例设计模式,就是采用一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只能怪呢提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,首先必须将类的构造器访问权限设置为private,这样,就不能用new在外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类外部还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。

单例模式的饿汉式实现(类加载即创建):

public class SingletonTest {
    public static void main(String[] args) {
        Bank b1 = Bank.getInstance();
        Bank b2 = Bank.getInstance();

        System.out.println(b1 == b2);
    }
}

// 希望只创建一个对象的类
class Bank {
    // 1. 私有化构造器
    private Bank() {
        // 初始化操作
    }

    // 2. 内部创建类的对象(必须是静态)
    private static Bank instance = new Bank();

    // 3. 提供公共的方法返回类的对象
    public static Bank getInstance() {
        return instance;
    }
}
运行结果:true,说明两个对象的地址值相同,符合单例模式的要求

单例模式的懒汉式实现(用的时候再创建):

/*
 * 单例模式的懒汉式实现
 * */
public class SingletonTest2 {
    public static void main(String[] args) {
        Order o1 = Order.getInstance();
        Order o2 = Order.getInstance();

        System.out.println(o1 == o2);
    }
}

class Order {
    // 1. 私有化类的构造器
    private Order() {

    }

    // 2. 声明当前类对象,没有初始化
    private static Order instance = null;

    // 3. 声明public static方法返回对象
    public static Order getInstance() {
        if (instance == null) {
            instance = new Order();
        }
        return instance;
    }
}
运行结果:true

饿汉式和懒汉式的区别:

  • 饿汉式:天然线程安全,但是会加长对象加载的时间
  • 懒汉式:延迟对象创建,降低类加载时间,但是上文的写法存在线程安全问题,例如两个线程几乎同时获取对象,则容易产生两个对象。修改方式在多线程内容时进行描述。

单例模式的使用场景:

  • 网站的计数器:一般使用单例模式,否则难以实现同步
  • 应用程序的日志:一般都使用单例模式,因为共享的日志文件一直处于打开状态,只能有一个实例对其进行操作
  • 数据库连接池:因为数据库连接是一种数据库资源
  • 项目中读取配置文件的类:因为没有必要每次读取配置文件时都专门生成一个对象去取
  • Application类也是单例的典型应用
  • Windows的Task Manager(任务管理器)
  • Windows的Recycle Bin(回收站)

2. 代码块的使用(初始化块)

  1. 代码块的作用:用来初始化类或对象
  2. 只能使用static修饰
  3. 分类:静态代码块以及非静态代码块

    • 静态代码块特点:

      • 内部可以有输出语句
      • 随着类的加载而执行,且只执行一次
      • 如果一个类中声明了多个静态代码块,则按照声明顺序依次执行
      • 静态代码块的执行优先于动态代码块
    • 非静态代码块特点:

      • 内部可以有输出语句
      • 随着对象的创建而执行,每创建一个对象就执行一次
      • 作用:在创建对象时为对象的属性进行初始化
      • 如果一个类中声明了多个非静态代码块,则按照声明顺序依次执行
      • 非静态代码块可以调用静态属性、静态方法,或非静态的属性、非静态的方法。
  4. 代码块的执行要先于构造器,各个部分代码执行的顺序:由父及子,静态先行

属性可以赋值的位置(按执行先后顺序):

  • 默认初始化
  • 显式初始化、代码块中初始化
  • 构造器中初始化
  • 创建对象后通过对象赋值

3. final关键字

  1. final可以用来修饰的结构:

    • 方法
    • 变量
  2. final用来修饰一个类:此类不能被其它类所继承,例如StringSystemStringBuffer
  3. final用来修饰一个方法,表明此方法不可被重写,例如Object.getClass()
  4. final用来修饰变量,此时的“变量”就称为是一个常量

    • 修饰属性时,可以考虑赋值的位置有:显示初始化、代码块中以及构造器中初始化
    • 修饰局部变量时,尤其是修饰形参,表明此形参是一个常量,当我们调用此方法时,给常量形参赋一个实参,一旦赋值以后,就只能在方法体内使用此形参,但不能重新赋值。
  5. static final用来修饰全局常量

4. 抽象类与抽象方法

4.1 abstract关键字的使用

  • abstract关键字可以用来修饰类和方法。
  • 修饰类时:

    • 此类不能实例化
    • 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
    • 开发中,都会提供抽象类的子类,让子类完成实例化,完成相关操作
  • 修饰方法时:

    • 抽象方法只有方法的声明,没有方法体。
    • 包含抽象方法的类一定是抽象类,反之,抽象类可以没有抽象方法
    • 若子类重写了父类中所有的方法,此类可以实例化
    • 若子类没有重写父类中的所有抽象方法,则此子类也是一个抽象类,需要使用abstract关键字修饰

4.2 抽象类的应用场景及注意点

抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类。

abstract使用中的注意点;

  • abstract不能用来修饰属性、构造器等结构
  • abstract不能用来修饰私有方法(不可重写)、静态方法(不认为是重写)、final方法(不可重写)、final的类(不可继承)

4.3 抽象类的匿名子类

假设存在以下两个类:

abstract class Person {
    String name;
    int age;

    public Person() {

    }

    public Person(String name, int age) {

    }

    public abstract void eat();// 抽象方法
}

class Student extends Person {

    @Override
    public void eat() {
        System.out.println("学生吃饭");

    }

}

Person类为抽象类,Student类为Person的一个子类,则:

public class PersonTest {
    public static void main(String[] args) {
        method(new Student());// 非匿名类匿名对象

        // 匿名类的对象
        Person p = new Person() {

            @Override
            public void eat() {

            }

        };
    }

    public static void method(Student s) {

    }
}
上述代码的main方法中使用了一个匿名类的对象,即直接将Person类中的抽象方法在创建对象时进行实现,这就是抽象类的匿名子类。

4.4 抽象的应用——模板方法设计模式(TemplateMethod)

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

  • 当功能内部一部分实现是确定的,一部分实现是不确定的,这时可以把不确定的部分暴露出去,让子类去实现。
  • 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一中模板模式。
public class TemplateTest {
    public static void main(String[] args) {
        Template t = new SubTemplate();
        t.spendTime();
    }
}

abstract class Template {
    public void spendTime() {
        long start = System.currentTimeMillis();
        code();// 易变的部分
        long end = System.currentTimeMillis();
        System.out.println("花费的时间为:" + (end - start) + "ms");
    }

    public abstract void code();
}

class SubTemplate extends Template {

    @Override
    public void code() {
        for (int i = 2; i <= 1000; i++) {
            boolean isFlag = true;
            for (int j = 2; j < Math.sqrt(i); j++) {
                if (i % j == 0) {
                    isFlag = false;
                    break;
                }
            }
            if (isFlag) {
                System.out.println(i);
            }
        }
    }

}

5. 接口

5.1 概述

  • 一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
  • 另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3、手机、数码相机、移动硬盘等都支持USB连接。
  • 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是······则必须能······”的思想。继承是一个“是不是”的关系,而接口实现则是“能不能”的关系。
  • 接口的本质是契约、标准、规范。就像我们的法律一样,制定好后大家都要遵守

image-20210304185807096.png

5.2 接口的使用

  • 接口使用interface定义
  • Java中,接口和类是并列的两个结构
  • 如何定义接口:定义接口中的成员

    • JDK7及以前,只能定义全局常量和抽象方法
    • JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
  • 接口中不能定义构造器,意味着接口不可以实例化
  • Java开发中,接口通过让类实现(implement)的方式来使用,如果实现类重写了接口中覆盖了接口中所有方法,则此类可以实例化,如果没有重写所有,则此类仍为一个抽象类。
  • Java类可以实现多个接口,弥补了Java类单继承性的局限性,格式:class AA extends BB implements CC, DD, EE{...}
  • 接口与接口之间可以继承,且可以多继承。
  • 接口具体使用时体现了多态性
  • 接口实际上可以看做是一种规范
public class InterfaceTest {
    public static void main(String[] args) {
        System.out.println(Flyable.MAX_SPEED);
        System.out.println(Flyable.MIN_SPEED);

        Plane plane = new Plane();
        plane.fly();
    }
}

interface Flyable {
    // 全局常量
    public static final int MAX_SPEED = 7900;
    int MIN_SPEED = 1;// 可以省略“public static final”

    // 抽象方法
    public abstract void fly();

    void stop();// 可以省略“public abstract”
}

class Plane implements Flyable {

    @Override
    public void fly() {
        System.out.println("飞机起飞");

    }

    @Override
    public void stop() {
        System.out.println("飞机降落");

    }

}

abstract class Kite implements Flyable {
    @Override
    public void fly() {
        System.out.println("风筝起飞");

    }
}
上述代码中,Plane类实现了Flyable接口中的所有方法,因此Plane可以实例化并使用相应的方法,而Kite类只实现了一个方法,因此Kite还是一个抽象类,无法实例化。

接口的多实现:

interface Attackable{
    void attack();
}

interface Flyable {
    // 全局常量
    public static final int MAX_SPEED = 7900;
    int MIN_SPEED = 1;// 可以省略“public static final”

    // 抽象方法
    public abstract void fly();

    void stop();// 可以省略“public abstract”
}

class Bullet implements Flyable,Attackable{

    @Override
    public void attack() {
        System.out.println("子弹攻击");
    }

    @Override
    public void fly() {
        System.out.println("子弹飞行");
    }

    @Override
    public void stop() {
        System.out.println("子弹停止");
    }

}
Bullet类同时实现了FlyableAttackable两个接口

接口之间的多继承

interface AA {
    void method1();
}

interface BB {
    void method2();
}

interface CC extends AA, BB {

}
CC接口同时继承AABB接口

5.3 接口的匿名实现类对象

与抽象类一样,接口也可以直接实现匿名对象

public class InterfaceTest {
    public static void main(String[] args) {
        // 匿名实现类
        method(new Flyable() {

            @Override
            public void fly() {
                // TODO Auto-generated method stub

            }

            @Override
            public void stop() {
                // TODO Auto-generated method stub

            }

        });
    }

    public static void method(Flyable flly) {

    }
}

5.4 接口的应用——代理模式

  • 应用场景

    • 安全代理:屏蔽对真实角色的直接访问
    • 远程代理:通过代理类处理远程方法调用(RM)
    • 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
  • 分类

    • 静态代理:静态的定义代理类
    • 动态代理:动态生成代理类(JDK自带的代理类,需要了解反射等知识)
public class NetworkTest {
    public static void main(String[] args) {
        Server server = new Server();
        ProxyServer proxyServer = new ProxyServer(server);
        proxyServer.browse();
    }
}

interface NetWork {
    public void browse();
}

class Server implements NetWork{
    @Override
    public void browse() {
        System.out.println("实际访问网络的操作");
    }
}

class ProxyServer implements NetWork {

    private NetWork work;

    public ProxyServer(NetWork work) {
        this.work = work;
    }

    @Override
    public void browse() {
        check();
        work.browse();
    }

    public void check() {
        System.out.println("联网前的准备工作");
    }
}

5.5 接口使用中的注意点

  • 如果父类与实现的接口中有属性重名,则调用父类中的属性时需要使用super关键字,调用接口中的变量时使用接口名。变量,这是因为接口中的变量默认为全局常量
  • 接口中定义的变量一定是静态、final
  • 如果实现的两个接口中有方法重名,则认为此方法同时是两个接口的实现。

5.6 Java8中接口新特性

  • 可以为接口添加静态方法默认方法。从技术角度来说,这是完全合法的,只是看起来违反了接口作为一个抽象类定义的理念

    • 静态方法:使用static关键字修饰,可以通过接口直接调用静态方法,并执行其他方法体。我们经常在相互一起使用的类中使用静态方法。
    • 默认方法:默认方法使用default关键字修饰。可以通过实现类对象调用。在已有接口中提供新方法的同时,还保持了与旧版代码的兼容性。如果实现类重写了接口中的默认方法,调用时仍然调用重写后的方法。
  • 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法,那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。(类优先原则)
  • 如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,会报错(接口冲突)。这就需要我们必须在实现类中重写此方法。
public class SubClassTest {
    public static void main(String[] args) {
        SubClass s = new SubClass();
        CompareA.method1();
        s.method2();
        s.method3();
    }
}

class SubClass implements CompareA {
    @Override
    public void method2() {
        CompareA.super.method2();// 调用接口中的默认方法
    }
}

interface CompareA {
    // 静态方法
    public static void method1() {
        System.out.println("CompareA.method1");
    }

    // 默认方法
    public default void method2() {
        System.out.println("CompareA.method2");
    }

    default void method3() {
        System.out.println("CompareA.method3");
    }
}

6. 内部类

  • 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整结构又只为外部事物提供服务,那么整个内部结构最好使用内部类。
  • 在Java中,允许一个类定义一于另一个类的内部,前者称为内部类,后者称为外部类
  • Inner class一般定义在它的类或语句块之内,在外部引用时必须给出完整的名称(Inner class的名字不能与包含它的外部类类名相同)
  • 分类:

    • 成员内部类(static成员内部类和非static成员内部类)

      1. 类内可以定义属性、方法、构造器等
      2. 可以被final修饰,表示此类不能被继承(不使用final修饰即可以被继承)。
      3. 可以被abstract修饰
      4. 可以调用外部类的成员:外部类.this.成员
      5. 可以被static修饰可以被四种不同权限修饰
    • 局部内部类(不谈修饰符)、匿名内部类,可位于方法、代码块、构造器内
class Person {

    public Person() {
        class CC {

        }
    }

    public void method() {
        // 局部内部类
        class AA {

        }
    }

    {
        // 局部内部类
        class BB {

        }
    }

    // 成员内部类
    class DD{

    }
    // 静态成员内部类
    static class EE{

    }

}

6.1 如何实例化成员内部类对象

Person.EE ee = new Person.EE();// 实例化静态内部类
        Person p = new Person();
        Person.DD dd = p.new DD();// 实例化非静态内部类
静态内部类可以使用外部类.内部类 变量名 = new 外部类.内部类()的方式进行实例化,而非静态内部类则必须先实例化外部类,用外部类的对象调用内部类的构造函数。

6.2 开发中局部内部类的使用

public class InnerClass1 {

    // 开发中很少见
    public void method() {
        // 局部内部类
        class AA {

        }
    }

    // 返回一个实现了Compareable接口的类的对象(方式一)
    public Comparable getComparable() {
        class MyComparable implements Comparable {
            @Override
            public int compareTo(Object o) {
                return 0;
            }
        }

        return new MyComparable();
    }
  
        // 方式二
    public Comparable getComparable2() {
        return new Comparable() {
            @Override
            public int compareTo(Object o) {
                return 0; 
            }
        };
    }
}
最后修改:2021 年 03 月 08 日
如果觉得我的文章对你有用,请随意赞赏