Head First Java 笔记

编译

源码文件通过 javac 编译, 然后在 Java 虚拟机上执行编译过的字节码,下面是具体步骤

User.java // 源码文件

javac User.java // 编译命令

User.class // 字节码

结构

.java 扩展名 是一个源文件.一个 .java 文件中只能有一个 public 类,这是 Java 语言的一个规则如果一个类被声明为 public,则文件名必须与该类的名字完全一致(区分大小写),并且文件扩展名必须为 .java

1
2
3
4
5
6
7
8
// 文件名就是 Dog.java
pubic class Dog {
// 类的内容
public static void main(String[] args) {
System.out.println("This is the main method in MainExample.");
}
}
}

关于包(package)

  • package 语句必须是 Java 文件中的第一条语句(除非有注释或空行)。

  • 如果你在类中没有指定 package,则该类属于默认包(default package),这意味着它没有明确的包名。默认包中的类无法被其他包中的类访问,因此在实际开发中,通常避免使用默认包。

  • 在同一个包中,文件名(即类名)必须是唯一的

    1
    2
    3
    4
    5
    src
    └── com
    └── huangben
    └── base
    └── MyClass.java
    1
    package com.huangben.base;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // src/com/example/Main.java
    package com.example;

    import com.example.user.User;
    import com.example.order.Order;

    public class Main {
    public static void main(String[] args) {
    Order order = new Order("Order #1234");
    User user = new User("Alice", order);
    user.printUserOrder();
    }
    }

关于 main 方法

  • main 方法的签名

    1
    public static void main(String[] args)
    • public:表示这个方法可以被 Java 虚拟机(JVM)从任何地方调用。
    • static:表示该方法属于类本身,而不是类的实例,因此 JVM 不需要创建类的实例就可以调用它。
    • void:表示方法不返回任何值。
    • String[] args:用于接收命令行参数,是一个 String 类型的数组

执行 main 方法

  • 当你编译并运行这个文件时,你可以选择执行其中任何一个类的 main 方法。运行哪个类的 main 方法取决于你在命令行中指定的类名。

例如:

1
2
3
4
5
6
javac MainExample.java  # 编译文件

java MainExample # 运行 MainExample 的 main 方法
java AnotherClass # 运行 AnotherClass 的 main 方法
java YetAnotherClass # 运行 YetAnotherClass 的 main 方法

总结

普通的 Java 应用程序: 必须有一个 main 方法,JVM 通过这个方法启动程序。

特殊类型的 Java 应用程序: 如 Applet、Servlet、JavaFX 应用程序或使用测试框架时,main 方法可能不是必需的,因为有其他的启动机制。

类与对象

对象是靠类的模型塑造出来的实例,包含实例变量和方法

java 是强类型语言,不管是对象,还是变量 、方法返回值、参数等都有类型

类和对象的区别

类不是对象,是用来创造对象的模型

对象的声明、创建、赋值

1
Dog myDog = new Dog();
  1. 虚拟机分配空间给 引用变量 myDog 并且 固定类型为 Dog 类型
  2. 创建对象 new Dog()
  3. = 赋值

方法

  • 方法必须声明返回类型,void 类型表示该方法不返回任何东西
  • 如果方法返回不是 void 类型,就必须要返回一个和方法声明类型相同的值

使用 Java API 的步骤

  • 导入包: 通过 import 语句导入需要使用的 Java API 类。
  • 创建对象: 使用 new 关键字创建类的实例。
  • 调用方法: 使用点操作符调用对象的方法或访问其属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.ArrayList;

public class Example {
public static void main(String[] args) {
// 创建一个 ArrayList 对象
ArrayList<String> list = new ArrayList<>();

// 使用 add 方法添加元素到 ArrayList 中
list.add("Apple");
list.add("Banana");
list.add("Orange");

// 使用 size 方法获取 ArrayList 中的元素数量
System.out.println("List size: " + list.size());

// 使用 get 方法获取指定索引位置的元素
System.out.println("First element: " + list.get(0));

// 使用 for-each 循环遍历 ArrayList 中的元素
for (String fruit : list) {
System.out.println(fruit);
}
}
}

继承与多态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Animal {
void makeSound() {
System.out.println("Animal makes a sound");
}
}

class Dog extends Animal { // Dog 类继承自 Animal 类
@Override // 这里进行重写父类中的方法
void makeSound() {
System.out.println("Dog barks");
}
}

class Cat extends Animal { // Cat 类继承自 Animal 类
@Override
void makeSound() {
System.out.println("Cat meows");
}
}

public class Main {
public static void main(String[] args) {
Animal myAnimal = new Animal(); // 父类对象
Animal myDog = new Dog(); // Dog 继承自 Animal
Animal myCat = new Cat(); // Cat 继承自 Animal

myAnimal.makeSound(); // 输出 "Animal makes a sound"
myDog.makeSound(); // 输出 "Dog barks"
myCat.makeSound(); // 输出 "Cat meows"
}
}

总结

  • 继承:子类可以继承父类的属性和方法,复用代码并扩展功能。
    不会继承 private 类型的变量跟方法
  • 多态:通过使用 @Override来表示,同一个方法可以在不同的子类中表现出不同的行为,体现出灵活性和可扩展性。
    方法可以覆盖,实例变量不行

接口与抽象

我个人理解就是,有些类不能被实例化,它代表一种抽象的东西。例如,Animal 是抽象的, 狗是具体的,犬类是抽象的,猫是具体的。

设计目的

抽象类可以提供一些基本的功能(如构造函数、字段等),子类可以在此基础上扩展自己的功能,避免从头开始编写代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
abstract class Animal {
String name; // 实例变量

public Animal(String name) { // 构造函数
this.name = name;
}

abstract void makeSound(); // 抽象方法

void showName() { // 具体方法
System.out.println("Animal name: " + name);
}
}

class Dog extends Animal { // Dog 类继承 Animal 抽象类
public Dog(String name) {
super(name);
}

@Override
void makeSound() {
System.out.println("Dog barks");
}
}

class Cat extends Animal { // Cat 类继承 Animal 抽象类
public Cat(String name) {
super(name);
}

@Override
void makeSound() {
System.out.println("Cat meows");
}
}

构造器(构造函数)

自我理解

构造器就是在初始化对象,即 Dog mydog = new Dog() 时 ,Dog 类 中默认包含的一种特殊方法.

默认情况下一个类没有手动写出构造器,编译器会自动提供一个默认构造器 。实际上,new Dog() 这个动作其实就相当于调用了默认的构造函数,即 下面示例 public Dog() ,类中的实例变量是有初始值的。数值型为 0,布尔型为 false,对象引用为 null等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Dog {
String name;
int age;

// 默认构造器
public Dog() {
name = "Unknown"; // 默认值
age = 0; // 默认值
}

// 参数化构造器
public Dog(String name, int age) {
System.out.println("Animal constructor called with name: " + name); // 验证初始化有调用这个
this.name = name; // 使用参数初始化属性
this.age = age;
}

void showInfo() {
System.out.println("Dog name: " + name + ", age: " + age);
}
}

public class Main {
public static void main(String[] args) {
Dog dog1 = new Dog(); // 调用默认构造器
dog1.showInfo(); // 输出 "Dog name: Unknown, age: 0"

Dog dog2 = new Dog("Buddy", 3); // 调用参数化构造器
dog2.showInfo(); // 输出 "Dog name: Buddy, age: 3"
}
}

修饰符

构造器的访问修饰符可以是 publicprotectedprivate 或默认(包私有)。

不同修饰符就是权限不一样,然后包私有的意思是说,默认的构造函数,不同包不可以调用,只能包内才可以实例化

例如:

1
2
3
4
5
6
7
8
9
10
11
// 文件: Test.java
package com.example.test; // 不同的包

import com.example.animals.Animal;

public class Test {
public static void main(String[] args) {
Dog dog = new Dog(); // 编译错误: Dog() has package-private access in Dog
}
}

调用父类构造器

super()

  • 调用父类构造函数的唯一方法只能是执行 super()
  • 子类构造器被调用时,它会隐式地自动调用父类的构造器,就是说当 public Dog() 被调用时,下面的 super()可以写,可以不写,如果带参数就必须写了

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Dog extends Animal {
String breed;

public Dog(String name, String breed) {
// 调用父类的带参数构造函数
super(name); // 这不能!!!! 使用 Animal()
this.breed = breed;
System.out.println("子类构造器执行: " + breed);
}
// 调用父类的无参数构造函数
public Dog() {
super(); // 可以写,可以不写
System.out.println("子类无参数构造器执行");
}
}
public class SuperMain {
public static void main(String[] args) {
Dog dog = new Dog("Buddy", "Golden Retriever");
Dog dog1 = new Dog();
}
}

this

如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Student {
String name;
int age;

// 这是构造函数,相当于你拿到一个学生的记录表时要填写的信息
public Student(String name, int age) {
this.name = name; // this.name 是记录表上的名字,等于你刚填写的名字
this.age = age; // this.age 是记录表上的年龄,等于你刚填写的年龄
}

void showInfo() {
System.out.println("Name: " + this.name + ", Age: " + this.age);
// 这里的 this.name 和 this.age 是指“当前这个学生”的信息
}
}

当写 Student st1 = new Student("张三", 23); 时:

  • new Student("张三", 23) 这段代码会调用 Student 类的构造函数来创建一个新的 Student 对象。
  • 在这个构造函数执行的过程中,this 就代表正在被创建的这个对象,也就是 st1
  • 所以,this.name = name; 实际上是在说:“把传入的名字(比如 ‘张三’)赋值给 st1name 属性。”

this 和 super 区别

  • 代表当前对象this 代表当前对象st1的实例,可以用来访问当前类的属性、方法,或者调用当前类的另一个构造函数

  • 代表父类对象super 代表当前对象 st1父类部分,可以用来访问父类的属性和方法,或者调用父类的构造函数。

重载

调用同类中的其他构造函数这个就是重载

目的

在一个构造函数中使用 this 来调用同类中的另一个构造函数,从而避免重复代码。

代码示例
假设你有一个 Person 类,有多个构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Person {
String name;
int age;
String city;

// 构造函数1:接受 name 和 age 参数
public Person(String name, int age) {
this.name = name;
this.age = age;
}

// 构造函数2:接受 name, age, 和 city 参数
public Person(String name, int age, String city) {
this(name, age); // 调用第一个构造函数 避免了重复的代码。
this.city = city;
}

void display() {
System.out.println("Name: " + name + ", Age: " + age + ", City: " + city);
}
}

public class Main {
public static void main(String[] args) {
Person person1 = new Person("Alice", 30);
person1.display(); // 输出: Name: Alice, Age: 30, City: null

Person person2 = new Person("Bob", 25, "New York");
person2.display(); // 输出: Name: Bob, Age: 25, City: New York
}
}