14-面向对象6

1. static

1.1 引入

当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过 new 关键字才会产生出对象,这时系统才会分配内存空间给对象, 其 !static 方法才可以供外部调用。

我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。

1.2 Tips

(1) 静态和动态的区别,截自《编译原理(龙书)》

(2) this 和 static,截自《Java编程思想》

1.3 使用

1.4 构造器是否是static?

1.5 成员变量是自身的对象


2. 单例设计模式

2.1 说明

什么是设计模式?

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

什么是单例设计模式?

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

2.2 代码实现

// 饿汉式:对象加载时间过长; 线程安全
// 懒汉式:延迟对象的创建; 当前写法存在线程安全问题
public class SingletonTest {
    public static void main(String[] args) {
        Bank bank1 = Bank.getInstance();
        Bank bank2 = Bank.getInstance();
        System.out.println(bank1 == bank2);
        Factory fact1 = Factory.getInstance();
        Factory fact2 = Factory.getInstance();
        System.out.println(fact1 == fact2);
    }
}

// 饿汉式
class Bank {
    // 1. 私有化类的构造器
    private Bank() {}

    // 2. 该类对象的变量也必须定义成静态的
    private static Bank instance = new Bank();
    // public static final Bank instance = new Bank(); 则无需提供get方法
    
    // 3. 提供一个公共静态方法以返回类内部创建的对象
    public static Bank getInstance() {
        return instance;
    }
}

// 懒汉式
class Factory {
    // 1. 私有化类的构造器
    private Factory() {}

    // 2. 仅声明
    private static Factory instance = null;

    // 3. 提供一个公共静态方法以返回类内部创建的对象
    public static Factory getInstance() {
        if(instance == null)
            instance = new Factory();
        return instance;
    }
}

2.3 优点

由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可 以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。

2.4 应用场景

  • 网站的计数器,一般也是单例模式实现,否则难以同步
  • 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加
  • 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源
  • 读取配置文件的类,一般也只有一个对象。没有必要每次使用配置 文件数据,都生成一个对象去读取
  • Application 也是单例的典型应用
  • Windows的 Task Manager(任务管理器) 就是很典型的单例模式
  • Windows的 Recycle Bin(回收站) 也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例

3. main 方法

因为 JVM 需要调用类的 main() 方法(作为程序的入口),所以该方法的访问权限必须是 public;又因为 JVM 在执行 main()方法时不必创建对象,所以该方法必须是 static;

该方法接收一个 String 类型的数组参数,该数组中保存执行 java 命令时传递给所运行的类的参数(作为和控制台交互的一种方式)

4. 代码块

4.1 简述

  • 作用:对Java类或对象进行初始化
  • 分类:静态代码块 & 非静态代码块
    • 静态代码块(用static修饰的代码块)
      • 初始化类的信息
      • 随着类的加载而执行,且只执行一次
      • 若有多个静态的代码块,那么按照从上到下的顺序依次执行
      • 静态代码块的执行要先于非静态代码块
      • 静态代码块内之只能调用类的结构(属性、方法),不能调用非静态
    • 非静态代码块
      • 在创建对象时,对对象的属性进行初始化,也称为"构造代码块"
      • 随着对象的创建而执行;每次创建对象的时候,都会执行一次
      • 若有多个非静态的代码块,那么按照从上到下的顺序依次执行
      • 代码块内静态结构、非静态结构均可调用

4.2 测试代码

4.2.1 Test1

public class InitBlockDemo {
    public static void main(String[] args) {
        System.out.println(Person.nation);
        // ------------------------------
        Person p1 = new Person();
        System.out.println(p1.name);
        Person p2 = new Person();
    }
}

class Person {
    String name;
    int age;
    static String nation = "China";

    static {
        System.out.println("static block 1");
        nation = "CN";
    }

    static {
        System.out.println("static block 2");
        // func_1(); Cannot make a static reference to the non-static method
        func_2();
    }

    {
        System.out.println("block 1");
        name = "LJQ";
    }

    {
        System.out.println("block 2");
        func_1();
        func_2();
    }

    public void func_1() {
        System.out.println("成员方法");
    }

    public static void func_2() {
        System.out.println("静态方法");
    }
}

/*
控制台:
    static block 1
    static block 2
    静态方法
    CN
    block 1
    block 2
    成员方法
    静态方法
    LJQ
    block 1
    block 2
    成员方法
    静态方法
*/

4.2.2 Test2

class Root{
    static{
        System.out.println("Root的静态初始化块");
    }
    {
        System.out.println("Root的普通初始化块");
    }
    public Root() {
        System.out.println("Root的无参数的构造器");
    }
}

class Mid extends Root{
    static {
        System.out.println("Mid的静态初始化块");
    }
    {
        System.out.println("Mid的普通初始化块");
    }
    public Mid() {
        System.out.println("Mid的无参数的构造器");
    }
    public Mid(String msg){
        this(); // 通过this调用同一类中重载的构造器
        System.out.println("Mid的带参数构造器:" + msg);
    }
}

class Leaf extends Mid{
    static{
        System.out.println("Leaf的静态初始化块");
    }
    {
        System.out.println("Leaf的普通初始化块");
    }
    public Leaf(){
        super("LJQ"); // 通过super调用父类中有一个字符串参数的构造器
        System.out.println("Leaf的构造器");
    }
}

public class LeafTest{
    public static void main(String[] args){
        new Leaf();
        System.out.println("------------------------");
        new Leaf();
    }
}

/*
控制台:
	Root的静态初始化块
	Mid的静态初始化块
	Leaf的静态初始化块
	Root的普通初始化块
	Root的无参数的构造器
	Mid的普通初始化块
	Mid的无参数的构造器
	Mid的带参数构造器:LJQ
	Leaf的普通初始化块
	Leaf的构造器
	------------------------
	Root的普通初始化块
	Root的无参数的构造器
	Mid的普通初始化块
	Mid的无参数的构造器
	Mid的带参数构造器:LJQ
	Leaf的普通初始化块
	Leaf的构造器
*/

4.2.3 Test3

public class Son extends Father {
    static {
        System.out.println("44444444444");
    }
    {
        System.out.println("55555555555");
    }
    public Son() {
        System.out.println("66666666666");
    }
    public static void main(String[] args) {
        System.out.println("77777777777"); // 147
        System.out.println("***********");
        new Son(); // 2356
        System.out.println("***********");
        new Son(); // 2356
        System.out.println("***********");
        new Father(); // 23
    }
}

class Father {
    static {
        System.out.println("11111111111");
    }
    {
        System.out.println("22222222222");
    }
    public Father() {
        System.out.println("33333333333");
    }
}

/*
控制台:
    11111111111
    44444444444
    77777777777
    ***********
    22222222222
    33333333333
    55555555555
    66666666666
    ***********
    22222222222
    33333333333
    55555555555
    66666666666
    ***********
    22222222222
    33333333333
*/

4.3 使用情景举例

// 数据库连接池
public class JDBCUtils {
    private static DataSource dataSource = null;
    static {
        InputStream is = null;
        try {
            is = DBCPTest.class.getClassLoader().getResourceAsStream("dbcp.properties");
            Properties pros = new Properties();
            pros.load(is);
            //调用BasicDataSourceFactory的静态方法,获取数据源
            dataSource = BasicDataSourceFactory.createDataSource(pros);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 使用DBCP数据库连接池实现数据库的连接
    public static Connection getConnection2() throws SQLException {
        Connection conn = dataSource.getConnection();
        System.out.println(conn);
        return conn;
    }
}

4.4 属性赋值顺序

  1. 默认初始化
  2. 显式初始化 | 非静态代码块中赋值
  3. 构造器中初始化
  4. 通过对象进行赋值
// 针对step2, 做个测试
public class InitAttrDemo {
    public static void main(String[] args) {
        System.out.println(new Test1().attr); // 7
        System.out.println(new Test2().attr); // 1101
    }
}

class Test1 {
    int attr = 6;

    {
        attr = 7;
    }
}

class Test2 {
    {
        attr = 13;
    }

    int attr = 1101;
}

5. final

在 Java 中声明类、变量和方法时,可使用关键字 final 来修饰,表示“最终的”。

  • final 标记的类不能被继承
  • final 标记的方法不能被子类重写。确保在继承中使方法行为保持不变,并且不会被覆盖。类中所有 private 方法都隐式地指定为是 final 的。由于无法取用 private 方法,所以也就无法覆盖它。可以对 private 方法添加 final 修饰词,但这并不能给该方法增加任何额外的意义
  • final 标记的变量即称为"常量";名称大写,且只能被赋值一次
  • final 标记的成员变量必须在 {声明 / 每个构造器中 / 代码块中} 显式赋值,然后才能使用
  • final 标记的局部变量
    • 修饰方法体中的变量;使用前必须赋值
    • 修饰方法声明中的形参;例:public void method(final int num) {...}
  • static final 修饰变量,称为“全局常量”。必须在 {声明 / 静态代码块中} 显式赋值,然后才能使用;占据一段不能改变的存储空间。

【例题】

原文地址:https://www.cnblogs.com/liujiaqi1101/p/13113506.html