【CS61B】2. Defining and Using Classes

定义和使用类

Java中所有代码都必须是类的一部分, 大部分代码都是在方法中编写的

错误示范

1
2
3
4
5
public class Dog {
    public static void makeNoise() {
        System.out.println("Bark!");
    }
}

如果我们运行以上代码, 会收到报错:

1
2
3
$ java Dog
Error: Main method not found in class Dog, please define the main method as:
       public static void main(String[] args)
  • 定义的 Dog 类并不执行任何操作, 只是定义了 Dog 的一些功能
  • 要运行该类, 需要添加一个 Main 方法, 来运行 Dog 中的方法

修正后

我们通过创建一个单独的 DogLaucher 来运行 Dog 中的方法

1
2
3
4
5
public class DogLauncher {
    public static void main(String[] args) {
        Dog.makeNoise();
    }
}
1
2
$ java DogLauncher
Bark!
  • 使用另一个的类有时被称为它的客户端(client)
    • 即 DogLauncher 是 Dog 的客户端

实例变量与对象实例化

当 Dog 类需要向下细分时, 我们不需要从头编写一些重复的代码, 而是可以选择通过继承+修改等方式, 实现新的类

创建单独的类别

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class TinyDog {
    public static void makeNoise() {
        System.out.println("yip yip yip yip");
    }
}

public class MalamuteDog {
    public static void makeNoise() {
        System.out.println("arooooooooooooooo!");
    }
}

创建 Dog 类实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class Dog {
    public int weightInPounds;

    public void makeNoise() {
        if (weightInPounds < 10) {
            System.out.println("yipyipyip!");
        } else if (weightInPounds < 30) {
            System.out.println("bark. bark.");
        } else {
            System.out.println("woof!");
        }
    }    
}

这里我们给 Dog 类定义一个属性, 是其体重
然后根据不同的体重, 决定其不同的行为

1
2
3
4
5
6
7
8
public class DogLauncher {
    public static void main(String[] args) {
        Dog d;
        d = new Dog();
        d.weightInPounds = 20;
        d.makeNoise();
    }
}

这里, 我们将 Dog 类实例化, 得到一个实例
并为 d 的属性赋值, 然后观察其执行的行为

  • Java 中的Object是任何类的实例
  • Dog 类有自己的变量,也称为实例变量非静态变量 。这些必须在类内声明
  • 我们在 Dog 类中创建的方法没有 static 关键字。我们将此类方法称为实例方法非静态方法
  • 要调用 makeNoise 方法,我们必须首先使用 new 关键字实例化 Dog,然后制作特定的 Dog 吠叫
    • 我们调用了 d.makeNoise() 而不是 Dog.makeNoise()
  • 一旦对象被实例化,就可以将其分配给适当类型的声明变量
    • 例如 d = new Dog();
  • 类的变量和方法也称为类的成员(members)
  • 使用点表示法访问类的成员

我的问题: 为什么感觉 d 被定义了两遍

我觉得 Dog d;d = new Dog(); 都是在定义 d

这涉及到 java 中对象和引用的区别

  1. Dog d;
    这句话的意思是:
  • 定义了一个 变量 d,类型是 Dog
  • 但是此时 d 只是一个引用(reference),它还没有指向任何对象
  • 就像是声明了“我有一只狗的指针”,但实际还没给它拴上一只真正的狗
    如果你此时直接写 d.makeNoise();,会报错 NullPointerException,因为 d 还没有指向任何对象。

2. d = new Dog();
这句话才是 真正创建了一只狗(在堆里 new 出一个 Dog 对象),然后让 d 指向它

  • new Dog() 会在内存里开辟空间,构造一只狗对象
  • d = ... 是把这个对象的地址赋值给 d
  • 之后你就可以通过 d 来访问这只狗,比如 d.weightInPounds = 20;

  1. 合并写法
    上面两步经常合并成一步:
    Dog d = new Dog();

等价于:
Dog d; d = new Dog();

在 Java 中构造函数

1
2
3
4
5
6
public class DogLauncher {
    public static void main(String[] args) {
        Dog d = new Dog(20);
        d.makeNoise();
    }
}

这里, 实例化是参数化的(实例化过程中直接传入参数), 能够节省一些赋值时的混乱
但如果想这样做, 需要在 Dog 类中添加一个构造函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class Dog {
    public int weightInPounds;

    public Dog(int w) {
        weightInPounds = w;
    }

    public void makeNoise() {
        if (weightInPounds < 10) {
            System.out.println("yipyipyip!");
        } else if (weightInPounds < 30) {
            System.out.println("bark. bark.");
        } else {
            System.out.println("woof!");
        }    
    }
}
  • 每当我们尝试使用 new 关键字和单个整数参数创建 Dog 时,都会调用具有签名 public Dog(int w) 的构造函数
  • 和 python 中的 __init__ 方法非常相似

关于构造函数

  • 构造函数必须和类名完全相同(包括大小写)
  • 构造函数不能有返回类型, 连 void 都不行
  • 构造函数可以有多个(重载)
  • 如果不写构造函数, java 会自动生成一个无参构造函数

非静态的方法

这里的 makeNoise 在定义的时候并没有加 static, 也就是这个并不是一个静态方法
这种也被称为实例方法
通俗来讲, 如果一个方法需要调用一个实例的变量, 那这个方法必须是非静态的

数组实例化, 对象数组

数组实例化

1
2
3
4
5
6
7
8
public class ArrayDemo {
    public static void main(String[] args) {
        /* Create an array of five integers. */
        int[] someArray = new int[5];
        someArray[0] = 3;
        someArray[1] = 4;
    }
}

这里数组也是用 new 这个关键字进行实例化的

对象的数组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class DogArrayDemo {
    public static void main(String[] args) {
        /* Create an array of two dogs. */
        Dog[] dogs = new Dog[2];
        dogs[0] = new Dog(8);
        dogs[1] = new Dog(20);

        /* Yipping will result, since dogs[0] has weight 8. */
        dogs[0].makeNoise();
    }
}

new 有两种不同的使用方式:

  • 一次创建可以容纳两个 Dog 对象的数组
  • 第二次创建每个实际的 Dog

类方法与实例方法

Java 允许定义两种类型的方法:

  • 类方法, 又称为静态方法
    • 类本身执行的操作
  • 实例方法, 又称为非静态方法
    • 只能由类的特定实例执行的操作

举例与比较

现在有一个 Math 类, 其提供一个 sqrt 方法
若 sqrt 是静态的:

1
x = Math.sqrt(100);

若 sqrt 是非静态的:

1
2
Math m = new Math();
x = m.sqrt(100);

同时具有实例和静态方法

当我们想比较两只狗的能力时, 可以添加一个静态方法:

1
2
3
4
5
6
public static Dog maxDog(Dog d1, Dog d2) {
    if (d1.weightInPounds > d2.weightInPounds) {
        return d1;
    }
    return d2;
}

然后通过以下方法调用:

1
2
3
Dog d = new Dog(15);
Dog d2 = new Dog(100);
Dog.maxDog(d, d2);

因为我们使用类名调用, 因此这个方法是一个静态方法

实现成一个功能相同的非静态方法:

1
2
3
4
5
6
public Dog maxDog(Dog d2) {
    if (this.weightInPounds > d2.weightInPounds) {
        return this;
    }
    return d2;
}

通过调用实例来调用此方法:

1
2
3
Dog d = new Dog(15);
Dog d2 = new Dog(100);
d.maxDog(d2);

静态变量

  • 静态变量是类本身固有的属性
  • 静态变量通过类名, 而非特定实例来访问
1
2
3
4
5
public class Dog {
    public int weightInPounds;
    public static String binomen = "Canis familiaris";
    ...
}

Dog.binomen

public static void main(String[] args)

用于 main 方法的声明:

  • public:到目前为止,我们所有的方法都以这个关键字开头
  • static:它是一个静态方法,不与任何特定实例相关联
  • void:它没有返回类型
  • main:这是方法的名称
  • String[] args:这是传递给 main 方法的参数

命令行参数

由于 main 是由 Java 解释器本身而不是另一个 Java 类调用的,因此解释器的工作是提供这些参数。它们通常引用命令行参数

1
2
3
4
5
public class ArgsDemo {
    public static void main(String[] args) {
        System.out.println(args[0]);
    }
}
1
2
$ java ArgsDemo these are command line arguments
these
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计