Java学习-类

1. 实现一个类

定义一个完整类的格式应该是

[类访问修饰符] [修饰符] 类名 [extend 父类名] [implements 接口列表]{
...
}

public class Employee{
    private String name;
    private double salary;
    ... 
}

类访问修饰符:

类访问修饰符 说明
public 可以被所有类访问,注意public类的所在的文件名应该与该类名相同,一个java文件至多有一个public类
package (缺省) 仅能被相同包中的类访问,缺省的值

修饰符:

修饰符 说明 备注
final 使用此修饰符的类不能被继承
abstract 声明这个类是一个抽象类 含有抽象方法的类必须声明为抽象类,但是抽象类不一定含有抽象方法

Python对比:Python由于鸭子类型的存在,很少使用接口和抽象类,但在需要使用时,可以使用ABCMeta元类和abstractmethod装饰器来实现,参考Python3 Cookbook

2. 类中的成员

Java 类中的成员可以为变量、方法、构造器和初始化块。

实例成员和类成员(静态成员)

Java 类中的成员分为实例成员和类成员, 没有加 static 修饰符的成员为实例成员,加了 static 修饰符的成员为类成员,或者说静态成员,构造方法不能被 static 修饰。

public class Employee{
    private int id; // 实例变量
    private static int last_id; // 类变量

    static { // 静态初始化块
        last_id = 0;
    }

    Employee(){ // 构造方法
        last_id += 1;
        this.id = last_id;
    }

    // 实例方法
    public int get_id(){
        return this.id;
    }

    // 类方法
    public static int get_last_id(){
        return last_id;
    }
}

需要注意,静态方法不能访问实例成员,可以在实例上调用静态方法。 静态方法常用于工厂模式,关于初始化块的内容会在后面介绍。

Python对比:Python中也有实例成员和类成员,其中类变量和实例变量与Java中基本类似,但是Python中静态方法和类方法是两种不同的方法类型,分别用@staticmethod@classmethod装饰器装饰,@classmethod装饰的类方法更接近与Java中的类方法,因为其能够访问到类中定义的其他类成员。上述代码的Python实现如下:

class Employee:
    last_id  = 0

    def __init__(self):
        # (self.类变量名)只能用来访问类变量
        # 修改时必须用(类名.变量名)
        # 否则会导致类变量变成实例变量
        Employee.last_id +=1
        self.id = self.last_id
    
    def get_id(self):
        return self.id
    
    @classmethod
    def get_last_id(cls):
        return cls.last_id

成员的访问修饰符

Java 中可以对类中的成员使用访问修饰符,Java 支持四中成员访问修饰符,对应的访问等级如下, 注意其与类访问修饰符的联系。

修饰符 当前类 同一包内 子孙类 其他包 其他包子孙类
public Y Y Y Y Y
protected Y Y Y N Y/N(说明)
default Y Y N N N
private Y N N N N
  • public: 公用访问修饰符, 其修饰的成员可以被任何其他类访问,所有的公有成员都可以被子类继承。

  • protect: 受保护的访问修饰符,相较于 public,其修饰的成员不能被其他包中的非子孙类访问,对于子孙类,要分为同包子孙类和非同包子孙类分析。 对于同包子孙类,所有 procted 的成员都可以被访问; 对于非同包的子孙类,较为复杂,可以参考Java protected 关键字详解

  • default: 默认访问修饰符,可以被当前类和同一包内的其他类访问。

  • private: 私有访问修饰符,最严格的访问修饰符,只能被当前类访问。

final和static修饰符

static 修饰符在前面已经学习过了,用来定义类级成员。 而 final 修饰符在学习类修饰符时也有提到,被 final 修饰的类不能被继承,其修饰修饰变量和修饰方法时,作用也是类似的。

final修饰的变量: 表示常量,只能被赋值一次,不能被修改。final 修饰的方法:不能被子类覆盖,但是可以被子类继承。

初始化块

初始化块可以分为普通的初始化块和静态初始化块

普通初始化块: 从某种程度上来说,是构造函数的补充。

  • 普通初始化块总是在构造函数之前执行
  • 与构造函数相比,他不接受任何参数。
  • 一般的使用场景是需要初始化的内容不需要任何参数时,可以提取到初始化块中执行,比如多个构造器中的相同代码可以提取到初始化快中,提高初始化代码的复用。

静态初始化块:

  • 在类初始化的阶段调用静态初始化块,而不是创建对象时,因此总是比普通初始化块要早执行
  • 只能对静态成员进行初始化,而不能对实例成员进行初始化。
  • 只能访问静态成员。

构造器与this

构造器是类中的一个特殊的方法,要求其方法名与类名相同,并且不返回然和类型。 其访问修饰符默认是 public 的。 如果指定了其返回值,该方法就只是一个普通方法。

构造器可以重载,可以使用this语句访问类中的其他实例成员,也可以用 this() 方法访问其他的重载的构造器。

包的使用

要把一个类放到包中,需要将包的声明作为源文件的第一个声明。

package com.gaoliang.learn_java;

public class Employee{
    ...
}

如果子目录和包的声明相同,即com/gaoliang/learn_java/Employee.java,则执行

javac com/gaoliang/learn/Employee.java

会在对应的目录产生class文件,然后可以通过全限定的类名运行程序

java com.gaoliang.learn.Employee

类路径

可以把class文件放到一个或者多个JAR文件中,

jar cvf library.jar /path/to/*.class ...

如果要试用这些jar文件,则需要通过class path告诉编译器和虚拟机jar文件的位置。class path可以包含

  • 包含class文件的目录(包含匹配包名的子目录)
  • JAR文件
  • 包含JAR文件的目录

javac和java命令都有-classpath参数,可以缩写成 -cp,例如

java -cp .:../libs/lib1.jar:../libs/lib2.jar com.mycompany.Mainclass

导入包

import语句

import语句可以使用户无需使用全限定名的使用类,例如

import java.util.Random;
Random random =  new Random()

可以使用通配符一次导入一个包中的所有类

import java.util.*;

但是要注意的是通配符只能用来导入类,不能通配的导入包。
如果出现类名称冲突,需要手动导入自己想要的具体类,手动解决冲突。

静态导入

静态导入使用户可以以 import 声明的形式导入静态的方法和变量,例如

import static java.lang.Math.*;
sqrt(12);

sqrt是 Math 类的一个静态方法,可以用import static直接导入。

嵌套类

除了将类组织成包,还可以将一个类定义在另一个类的内部。 这样的类称为嵌套类。

静态嵌套类

首先看一个例子

public class Order{

    private static class Item{
        String description;
        int quality;
        double unitPrice;
        double price() {return quantity * unitPrice;}
    }
    
    private ArrayList<Item> items = new ArrayList<>();
    ...
}

这个例子中的 Item 是一个静态嵌套类,由于其使用 private 进行修饰,所以只有Order 类能够使用该类,如果使用 public 进行修饰,由于同时还是一个 static 的类, 所以可以被其他类在外部通过 Order.Item 进行使用。 所以静态嵌套类更像是为一个类增加了一个命名空间,更明显的表示Item是专属与 Order 的,而对于Order 和 Item 来说,两者并没有直接的关系。

内部类

如果去掉 static 修饰,那么这个嵌套类就变成了内部类。 内部类与静态嵌套类的最大区别是内部类可以获知自己所属的外部类实例。

import java.util.ArrayList;

public class ClassRoom {

    public class Student {
        String name;
        int age;

        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
        // 注意这里,student是可以获取外部类的引用的
        // 所以可以使用外部类的变量和方法,比如这里的students
        public void leave() {
            students.remove(this);
            // 也可以写成这样,ClassRoom.this就是外部类实例的引用。
            // ClassRoom.this.students.remove(this);
        }
    }

    private ArrayList<Student> students = new ArrayList<>();

    public Student enroll(String name, int age) {
        Student newStudent = new Student(name, age);
        // 其实是 Student newStudent  = this.new Student(name, age)
        students.add(newStudent);
        return newStudent;
    }
}

去掉了 static, 出现了本质区别, 此时的 Student 类是知道自己所属于的 ClassRoom 实例的,并且可以使用OutClass.this获取外部类实例的引用。