← Backend / Java

02_Java 对象和类

Java 类定义、对象创建、实例变量与方法、构造器、面向对象基本概念

Java 作为一种面向对象的编程语言,支持以下基本概念:

1、类(Class):

  • 定义对象的蓝图,包括属性和方法。
  • 示例:public class Car { ... }

2、对象(Object):

  • 类的实例,具有状态和行为。
  • 示例:Car myCar = new Car();

3、继承(Inheritance):

  • 一个类可以继承另一个类的属性和方法。
  • 示例:public class Dog extends Animal { ... }

4、封装(Encapsulation):

  • 将对象的状态(字段)私有化,通过公共方法访问。
  • 示例:
    private String name; 
    public String getName() { return name; }
    

5、多态(Polymorphism):

  • 对象可以表现为多种形态,主要通过方法重载和方法重写实现。
  • 示例:
    • 方法重载:public int add(int a, int b) { ... } 和 public double add(double a, double b) { ... }
    • 方法重写:@Override public void makeSound() { System.out.println("Meow"); }

6、抽象(Abstraction):

  • 使用抽象类和接口来定义必须实现的方法,不提供具体实现。
  • 示例:
    • 抽象类:public abstract class Shape { abstract void draw(); }
    • 接口:public interface Animal { void eat(); }

7、接口(Interface):

  • 定义类必须实现的方法,支持多重继承。
  • 示例:public interface Drivable { void drive(); }

8、方法(Method):

  • 定义类的行为,包含在类中的函数。
  • 示例:public void displayInfo() { System.out.println("Info"); }

9、方法重载(Method Overloading):

  • 同一个类中可以有多个同名的方法,但参数不同。
  • 示例:
    public class MathUtils {
    	public int add(int a, int b) {
    		return a + b;
    	}
    
    	public double add(double a, double b) {
    		return a + b;
    	}
    }
    

Java 中的类

类可以看成是创建 Java 对象的模板。 ![[java-对象和类.png]]

通过上图创建一个简单的类来理解下 Java 中类的定义:

public class Dog {
    String breed;
    int size;
    String colour;
    int age;
 
    void eat() {
    }
 
    void run() {
    }
 
    void sleep(){
    }
 
    void name(){
    }
}

一个类可以包含以下类型变量:

  • 局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
  • 成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。
  • 类变量:类变量也声明在类中,方法体之外,但必须声明为 static 类型。

一个类可以拥有多个方法,在上面的例子中:eat()、run()、sleep() 和 name() 都是 Dog 类的方法。


创建对象

对象是根据类创建的。在Java中,使用关键字 new 来创建一个新的对象。创建对象需要以下三步:

  • 声明:声明一个对象,包括对象名称和对象类型。
  • 实例化:使用关键字 new 来创建一个对象。
  • 初始化:使用 new 创建对象时,会调用构造方法初始化对象。

下面是一个创建对象的例子

	public class Puppy{
	   public Puppy(String name){
	      //这个构造器仅有一个参数:name
	      System.out.println("小狗的名字是 : " + name ); 
	   }
	   public static void main(String[] args){
	      // 下面的语句将创建一个Puppy对象
	      Puppy myPuppy = new Puppy( "tommy" );
	   }
}

编译并运行上面的程序,会打印出下面的结果:

小狗的名字是 : tommy

访问实例变量和方法

通过已创建的对象来访问成员变量和成员方法,如下所示:

	/* 实例化对象 */
	Object referenceVariable = new Constructor();
	/* 访问类中的变量 */
	referenceVariable.variableName;
	/* 访问类中的方法 */
	referenceVariable.methodName();

使用 Object 类型声明变量只能在编译时访问 Object 类中的方法和属性,但在运行时,你可以通过强制类型转换将其转换为特定类型,以便访问特定类型的方法和属性。


实例

下面的例子展示如何访问实例变量和调用成员方法:

	public class Puppy {
		private String name;
		private int age
		
		// 构造器
		public Puppy (String name) {
			this.name = name;
		}
		
		// age 设置
		public void setAge(int age) {
			this.age = age;
		}
		
		// age 获取
		public int getAge() {
			return age;
		}
		
		// name 获取
		public String getName() {
			return name;
		}
		
		public static void main(String[] args) {
			// 创建对象
			Puppy myDog = new Puppy("Lily");
			
			// 通过方法设定age
			myDog.setAge(2)
			
			// 调用另一个方法获取age
			int age = myDog.getAge()
			System.out.println("小狗的年龄是" + age)
			
			//也可以直接访问成员变量
			System.out.println("变量值:" + myDog.getAge())
		}
	} 

编译并运行上面的程序,产生如下结果:

小狗的名字是 : Lily
小狗的年龄为 : 2
变量值 : 2

源文件声明规则

在本节的最后部分,我们将学习源文件的声明规则。当在一个源文件中定义多个类,并且还有 import 语句和 package 语句时,要特别注意这些规则。

  • 一个源文件中只能有一个 public 类
  • 一个源文件可以有多个非 public 类
  • 源文件的名称应该和 public 类的类名保持一致。例如:源文件中 public 类的类名是 Employee,那么源文件应该命名为Employee.java。
  • 如果一个类定义在某个包中,那么 package 语句应该在源文件的首行。
  • 如果源文件包含 import 语句,那么应该放在 package 语句和类定义之间。如果没有 package 语句,那么 import 语句应该在源文件中最前面。
  • import 语句和 package 语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。

类有若干种访问级别,并且类也分不同的类型:抽象类和 final 类等。这些将在访问控制章节介绍。

除了上面提到的几种类型,Java 还有一些特殊的类,如:内部类、匿名类。


构造方法(Constructor)

构造方法是创建对象时自动调用的特殊方法,用于初始化对象的状态。

规则:

  • 方法名必须和类名完全相同
  • 没有返回类型(连 void 也没有)
  • 每个类至少有一个构造方法;如果不写,Java 会提供默认的无参构造方法

无参与有参构造方法

public class Car {
    String brand;
    int speed;

    // 无参构造
    public Car() {
        brand = "未知";
        speed = 0;
    }

    // 有参构造
    public Car(String brand, int speed) {
        this.brand = brand;  // this 指向当前对象,区分同名参数和成员变量
        this.speed = speed;
    }
}

Car c1 = new Car();
Car c2 = new Car("Toyota", 120);

构造方法重载与链式调用(this())

public class Car {
    String brand;
    int speed;
    String color;

    public Car(String brand, int speed, String color) {
        this.brand = brand;
        this.speed = speed;
        this.color = color;
    }

    // this() 调用其他构造方法,必须是第一行
    public Car(String brand, int speed) {
        this(brand, speed, "白色");
    }

    public Car(String brand) {
        this(brand, 0);
    }
}

Car c1 = new Car("BMW", 200, "黑色");
Car c2 = new Car("Audi", 180);    // color 默认白色
Car c3 = new Car("Tesla");         // speed 默认 0,color 默认白色

this 与 super 关键字

this 关键字

this 指向当前对象,有三个主要用途:

public class Person {
    String name;
    int age;

    // 用途1:区分同名局部变量与实例变量
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 用途2:在本类方法中调用其他方法
    public void introduce() {
        System.out.println("我是 " + this.name + ",今年 " + this.getAge() + " 岁");
    }

    public int getAge() { return this.age; }

    // 用途3:将当前对象传递给其他方法
    public Person getSelf() { return this; }
}

super 关键字

super 指向父类,用于访问父类成员或调用父类构造方法:

class Animal {
    String name;

    Animal(String name) { this.name = name; }

    void introduce() {
        System.out.println("我是动物,名字叫 " + name);
    }
}

class Dog extends Animal {
    String breed;

    Dog(String name, String breed) {
        super(name);       // 调用父类构造方法,必须是第一行
        this.breed = breed;
    }

    @Override
    void introduce() {
        super.introduce();  // 调用父类的 introduce()
        System.out.println("我是 " + breed + " 犬");
    }
}

Dog d = new Dog("旺财", "金毛");
d.introduce();
// 我是动物,名字叫 旺财
// 我是 金毛 犬

toString()、equals() 与 hashCode()

这三个方法来自所有类的基类 Object,通常需要在自定义类中重写。

toString()——对象的字符串表示

public class Point {
    int x, y;

    Point(int x, int y) { this.x = x; this.y = y; }

    // 不重写时,打印结果类似 Point@7ef88735
    @Override
    public String toString() {
        return "Point(" + x + ", " + y + ")";
    }
}

Point p = new Point(3, 4);
System.out.println(p);  // Point(3, 4)

equals()——对象内容比较

== 比较引用(内存地址),equals() 比较内容:

public class Student {
    String id;

    Student(String id) { this.id = id; }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;                    // 同一对象直接返回
        if (!(obj instanceof Student)) return false;     // 类型不匹配
        Student other = (Student) obj;
        return this.id.equals(other.id);
    }
}

Student s1 = new Student("001");
Student s2 = new Student("001");

System.out.println(s1 == s2);       // false(不同对象)
System.out.println(s1.equals(s2));  // true(id 相同)

hashCode()——与 equals() 配套

重要规则: 重写 equals() 时必须同时重写 hashCode()。否则存入 HashMap / HashSet 时行为错误。

@Override
public int hashCode() {
    return id.hashCode();  // 依赖同一字段保证一致性
}

实际开发: 使用 IDE 右键 → Generate → equals() and hashCode(),或使用 Java 16+ 的 record 自动生成。


Records(Java 16+)

Record 是专为"只存数据"设计的不可变类,自动生成构造方法、getter、toString、equals、hashCode。

// 传统写法:大量样板代码
public class Point {
    private final int x;
    private final int y;
    public Point(int x, int y) { this.x = x; this.y = y; }
    public int x() { return x; }
    public int y() { return y; }
    @Override public String toString() { return "Point[x=" + x + ", y=" + y + "]"; }
    // 还需要 equals 和 hashCode...
}

// Record 写法:一行搞定
public record Point(int x, int y) {}

// 使用方式完全一样
Point p = new Point(3, 4);
System.out.println(p.x());        // 3
System.out.println(p.y());        // 4
System.out.println(p);            // Point[x=3, y=4]
System.out.println(new Point(3, 4).equals(new Point(3, 4)));  // true

Record 的限制:

  • 字段是 final 的,不可变,没有 setter
  • 不能继承其他类(但可以实现接口)
  • 适合做 DTO(数据传输对象)、坐标、配置项等值对象

面向对象概念速查

概念 关键字 / 语法 要点
构造方法 ClassName() 与类同名,无返回类型
调用本类构造 this() 必须是第一行
调用父类构造 super() 必须是第一行
当前对象引用 this 区分同名变量、传递自身
父类引用 super 访问父类成员或构造
内容比较 equals() 需重写;== 比地址
打印对象 toString() 重写后 println 自动调用
不可变数据类 record Java 16+,自动生成所有样板代码