0 对象与类

💡类(class)是构造对象的模板或蓝图,由类构造(construct)对象的过程称为创建类的实例(instance)。

📌封装(encapsulation,有时称为数据隐藏)是与对象有关的一个重要概念。从形式上看,封装不过是将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式。对象中的数据称为实例域(instance field),操纵数据的过程称为方法(method)。对于每个特定的类实例(对象)都有一组特定的实例域值。这些值的集合就是这个对象的当前状态(state)。

0.0 自定义类

📌 Java 中所有的类都继承Object

一个最基本类的定义如下:

class <ClassName> {
    // 实例域(属性)
    field1
    field2
    ...
 
    // 构造器(类对象的初始化方法)
    constructor1
    constructor2
    ...
 
    // 方法
    method1
    method2
    ...
}

💡一般情况下,习惯于将每一个类存在一个单独的源文件中。如 Person 类单独放在 Person.java 文件中

0.1 类方法的访问权限

四种访问权限

public :公开级别,对外公开。protected :受保护级别,对子类和同一个包中的类公开。默认 :向同一个包的类公开。private :只有类本身可以访问,不对外公开。

访问修饰符同类同包子类不同包
public
protected
默认
private

0.2 封装

权限的使用

虽然可以用 public 标记实例域,但这是一种极为不提倡的做法。public 数据域允许程序中的任何方法对其进行读取和修改。这就完全破坏了封装。因此在封装过程中,首先进行属性进行私有化 private(不能直接修改属性);然后提供 publicsetxxx() 方法,用于对属性的判断并赋值;最后提供 publicgetxxx() 方法,用于获取属性的值

0.3 构造器

构造器基本特点

构造器与类同名; ② 每个类可以有一个以上的构造器; ③ 构造器可以有0个、1个或多个参数; ④ 构造器没有返回值; ⑤ 构造器总是伴随着new操作一起调用

0.3 显式参数与隐式参数

😶‍🌫️显式参数:方法里传入的形参 😱隐式参数:类方法中调用的公共属性(可以用 this 表示隐式参数)

class Person {
    ...
    String name;
 
    public void say(String info) {
        System.out.print("Hello " + this.name + ". " + info)
    }
}

0.4 final实例域

  • 🐵 final 最终的意思,可以修饰类、属性、方法、局部变量(悟空定身术,不让改
    • final 修饰类:类不能被继承
    • final 修饰属性:属性的值不能被更改,成为常量,一般用 AA_BB_CC 的形式来命名。其在定义的时候必须赋值,有三个地方可以赋值:1> 定义时就赋值;2> 在构造器中赋值(如果该属性还是静态的就不行);3> 在代码块中
    • final 修饰方法:该方法不能被重写
    • final 修饰局部变量:该变量变为局部常量,不能被更改
  • 😎 如果一个类是 final 类,就没有必要再将用 final 修饰该类的方法
  • 😬 final 不能修饰构造器
  • 😱 finalstatic 搭配使用效率更高,不会导致类加载
    • 解释:访问静态属性时会导致类加载,然后执行静态代码块;但是将该静态属性再用 final 关键字修饰,再访问该静态属性时,静态代码块就不会被执行了
  • 👻包装类(Integer、Double、Float、Boolean都是 final 类),String也是 final

1 静态域与静态方法

1.0 静态域

静态的关键字为 static,用该关键字标识的域(属性)就是 静态域

class Person {
    public static int nextID = 10;
    public int age;
}

📌如以上代码中,创建 1000 个 Person 类的实例,就有 1000 个 实例域 age,但是只有一个 静态域 nextID。所有的类实例共用一个 nextID 域。即使没有一个 Person 对象,静态域 nextID 依然存在。

1.1 静态常量

静态变量一般用得比较,更常用的是静态常量,即将 staticfinal 结合使用。

public static final double PI = 3.1415926;

📌静态常量常量的区别,当没有 static 关键字时,PI 常量只能对过对应的 类对象 进行访问(先创建实例对象,再访问里面的值),多个实例对象就有多个常量。使用 static 即可通过 类名 直接访问该 静态常量,且有多个实例对象时,也共用一个 静态常量

1.2 静态方法

静态方法是一种不能向对象实施操作的方法,是没有 this 参数的方法。(因为静态方法可以在不创建类实例的情况下直接通过类名调用,所以不能使用和具体类实例绑定在一起的对象

以下第一个例子是❌错误❌错误❌错误的

class Person {
    public static int nextID = 10;
    public int age;
 
    // 该静态方法是错误的示例
    public static int getAge() {
        return this.age;
    }
 
    // 以下是正确的静态方法
    public static int getNextID() {
        return nextID;
    }
}

2 方法参数

方法参数分为基本数据类型对象引用

  • 基本数据类型:采用 按值调用。即方法得到的是所有参数值的一个拷贝,方法不能修改传递给它的任何参数变量的内容
  • 对象引用是对引用进行了拷贝(所以),和地址传递是有区别的

  • 总结 Java方法参数 的使用情况:
    • 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)
    • 一个方法可以改变一个对象参数的状态
    • 一个方法不能让对象参数引用一个新的对象

3 对象构造

构造器定义了对象的初始状态,而 Java 提供了多种编写构造器的机制。

3.0 重载

🌟如果多个方法有相同的方法名不同的参数,便产生了重载

StringBuilder a = new StringBuilder();
StringBuilder b = new StringBuilder("hello");

3.1 默认域初始化

默认域初始化

😶‍🌫️🚩如果构造器没有显示的给域赋予初值,则域会自动赋为默认值:数值为0、布尔值为false、对象引用为null。 📌强烈建议做好初始化操作,避免程序不符合预期

3.2 无参构造器

无参构造器

⚡很多类都包含一个无参数的构造函数,对象由无参数构造函数创建时,其状态会设置为适当的默认值。 📌如果在编写一个类时没有编写构造器,那么系统就会提供一个无参数构造器

3.3 显式域初始化

显式域初始化

⚡通过重载类的构造器方法,可以采用多种形式设置类的实例域的初始状态。确保不管怎样调用构造器,每个实例域都可以被设置为一个有意义的初值,这是一种很好的设计习惯。 🌟在执行构造器之前,先执行赋值操作。当一个类的所有构造器都希望把相同的值赋予某个特定的实例域时,直接赋值的方式很有用。如下:

class Person {
    // 该步骤的赋值在构造器之前执行
    private String name = "";
}

3.4 初始化块

数据域的初始化方法有3种,前两种分别为在构造器中赋值在声明中赋值,第三种就是在初始化块中赋值。如下:

class Person {
    private String name;
    private static int age;
 
    // 初始化块
    {
        name = "";
    }
 
    // 静态的初始化块
    static
    {
        age = 18;
    }
}

📌创建一个类实例时,首先运行声明中赋值,其次为初始化块,最后才是构造器

3.5 finalize方法

finalize方法

任何一个类都可以添加 finalize 方法,该方法在垃圾回收器清除对象之前调用。📌📌📌在实际应用中,不要依赖于使用finalize方法回收任何短缺的资源,这是因为很难知道这个方法什么时候才能够调用。 🌟如果某个资源需要在使用完毕后立刻被关闭,那么就需要由人工来管理。对象用完时,可以应用一个 close 方法来完成相应的清理操作。

4 包

标准的 Java 类库分布在多个包中,包括 java.langjava.utiljava.net 等。标准的 Java 包具有一个层次结构。使用包的主要原因是确保类名的唯一性。(lang 包默认引入,不用手动引入)

要将一个类放入包中,就必须将包的名字放在源文件开头,包中定义类的代码之前。

package cn.hkc.demo;
 
import java.math.BigInteger;
 
public class hello {
    public static void main(String[] args) {
        ...
    }
}

📌如果没有在源文件中放置 package 语句,这个源文件中的类就被放置在一个默认包(defaulf package)中。默认包是一个没有名字的包

5 类路径

  • 类存储在文件系统的子目录中,类的路径必须与包名匹配。
  • 类文件也可以存储在 JAR 文件中。在一个 JAR 文件中,可以包含多个压缩形式的类文件和子目录,这样既可以节省又可以改善性能。
  • JAR 文件使用 ZIP 格式组织文件和子目录。可以使用所有 ZIP 实用程序查看内部的 rt.jar 以及其他的 JAR 文件

一个类为了使多个项目都可以引用,就可以将 jar 包统一放在一个目录里,之后配置类路径即可 java -classpath ~/classdir:.:~/jarpath MyProgram

6 类的设计技巧

6.0 基本设计方法

  • [p] 数据私有:绝对不要破坏封装性。有时候,需要编写一个访问器方法或更改器方法,但是最好还是保持实例域的私有性。
  • [p] 数据初始化Java 不对局部变量进行初始化,但是会对对象的实例域进行初始化。最好不要依赖于系统的默认值,而是应该显式地初始化所有的数据。
  • [p] 不要在类中使用过多的基本类型用其他的类代替多个相关的基本类型的使用。这样会使类更加易于理解且易于修改
  • [p] 将职责过多的类进行分解:如果明显地可以将一个复杂的类分解成两个更为简单的类,就应该将其分解。
  • [p] 优先使用不可变的类:更改对象的问题在于,如果多个线程试图同时更新一个对象,就会发生并发更改。其结果是不可预料的。如果类是不可变的,就可以安全地在多个线程间共享其对象。

6.1 单例设计模式

📌 单例设计模式:就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,该类只提供一个取得对象实例的方法。

🌟有两种方式:饿汉式懒汉式。 其实现方式:1> 构造器私有化(不能new对象了) 2> 类的内部创建对象 3> 向外暴露一个静态的公共方法

😶‍🌫️ 饿汉式

class Cat {
    private String name;
    public static int age = 18;
    private static Cat cat = new Cat("Tom");  // 内部创建对象
    private Cat(String name) {  // 构造器私有化
        this.name = name;
    }
 
    public static Cat getCat() {  // 静态的公共方法
        return cat;
    }
}

以上这个 cat 类,构造器的权限为 private ,则程序不能够 new 对象,只能内部获取,则需要调用 getCat() 方法,而静态方法只能访问静态属性,所以内部创建的 cat 对象用到了 static 关键字。通过以上写法,Cat 类在整个程序中只可能有一个实例

🤪 懒汉式

class Cat {
    private String name;
    public static int age = 18;
    private static Cat cat;
    private Cat(String name) {  // 构造器私有化
        this.name = name;
    }
 
    public static Cat getCat() {  // 静态的公共方法
        if (cat == null) {
            cat = new Cat("Tom");  // 内部创建对象
        }
        return cat;
    }
}

🚨 两种方式最大的区别在于对象创建的时机不同 。当执行 Cat.age 调用这个类的静态属性时,这个类会被加载,所以饿汉式中对象会被创建,如果我们最后并没有使用这个对象,就造成了资源浪费;而懒汉式中则不会创建,只有需要用到这个对象实例,才会创建

  • [I] 🪶 两种方式对比说明
    • [>] 饿汉式不存在线程安全问题,懒汉式存在线程安全问题(多个线程同时要创建对象)
    • [>] 饿汉式可能存在资源浪费,懒汉式使用时才会创建