JAVA面向对象
1、类与对象
1.1、类(class)
1.1.1、定义类
通过关键字class
来定义类。类是一种用户自定义的数据类型,类是具有相同属性和行为的集合,类是用于构建对象的模板,对象的实质是内存的一块存储区域,定义了类的属性和行为。
1.1.2、成员变量
类中的成员变量用于存储对象的状态信息,也被称为实例变量,因为每个类的实例都有自己独立的成员变量
java语言中,类的成员变量的定义可以使用如下语法规则:
class 类名{
成员变量的类型 变量的名称;
... ...
}
public class Person {
String name;
String sex;
int age;
1.1.3、成员方法
类中的方法用于定义对象的行为和操作,这些方法可以访问和操作成员变量
类中除了定义成员变量,可以定义成员方法。
java语言中,可以按照下列方式定义类中的方法:
class 类名{
返回值类型 方法名称(参数列表){
// code
}
}
1.1.4、访问修饰符
在类中,可以使用访问修饰符来控制类的成员(成员变量和成员方法)的可见性。常见的修饰符有“public”、“private"、”protected"和默认修饰符(没有显示修饰符)
public:
被public
修饰的成员可以被任何类访问,无论是同一个包还是不同的包,类的访问修饰符只能是public
或默认,如果一个类由public修饰,它必须与文件名字相同
private:
被private
修饰的成员只能在同一个类内部访问,其他类无法直接访问
。可以保护数据的安全性,只允许类的内部访问和修改变量。成员变量通常用private修饰。,并通过getter方法和setter方法访问和修改。
getter方法
public class Person {
private String name; // 私有成员变量
// Getter方法
public String getName() {
return name;
}
}
setter方法
public class Person {
private String name; // 私有成员变量
// Setter方法
public void setName(String newName) {
name = newName;
}
}
Getter方法和Setter方法是Java中常见的用于提供对私有变量的访问和修改
protected
被protected
修饰的成员可以在同一包内访问,也可以在不同包的子类中访问,在不同包的非子类中无法直接访问
// 在同一包内的类
package mypackage;
public class Parent {
protected int age; // 声明一个protected成员变量
protected void showInfo() { // 声明一个protected成员方法
System.out.println("Parent class: age = " + age);
}
}
// 不同包的子类
package otherpackage;
import mypackage.Parent;
public class Child extends Parent {
public void displayInfo() {
age = 25; // 子类可以访问父类的protected成员变量
showInfo(); // 子类可以调用父类的protected成员方法
}
}
// 不同包的非子类
package otherpackage;
import mypackage.Parent;
public class NonChild {
public void displayInfo() {
Parent parent = new Parent();
// 下面两行代码会导致编译错误,因为age是protected成员,不同包的非子类无法直接访问
// parent.age = 30;
// parent.showInfo();
}
}
static
static
修饰符用于创建静态成员,即类级别的成员而不是实例级别的成员。静态成员在内存中只有一份拷贝,所有类的实例共享同一个静态成员。
public class MyClass {
// 静态成员变量
public static int count = 0;
// 实例成员变量
private int number;
// 静态方法
public static void incrementCount() {
count++;
}
// 实例方法
public void setNumber(int num) {
number = num;
}
public int getNumber() {
return number;
}
}
-
count
:这是一个静态成员变量,由关键字static
修饰。它属于类本身,而不属于类的实例。count
变量会被所有MyClass
类的实例共享,可以通过类名直接访问:MyClass.count
。 -
number
:这是一个实例成员变量,它属于类的实例。每个MyClass
类的实例都有自己的number
变量,必须通过实例对象来访问:myObject.getNumber()
。 -
incrementCount()
:这是一个静态方法,由关键字static
修饰。它属于类本身,不依赖于类的实例。因此,可以通过类名直接调用静态方法:MyClass.incrementCount()
。public class Main { public static void main(String[] args) { // 创建两个MyClass类的实例 MyClass obj1 = new MyClass(); MyClass obj2 = new MyClass(); // 访问静态成员变量并调用静态方法 System.out.println("Initial count: " + MyClass.count); // 输出:Initial count: 0 MyClass.incrementCount(); System.out.println("Updated count: " + MyClass.count); // 输出:Updated count: 1 // 访问实例成员变量 obj1.setNumber(10); obj2.setNumber(20); System.out.println("obj1 number: " + obj1.getNumber()); // 输出:obj1 number: 10 System.out.println("obj2 number: " + obj2.getNumber()); // 输出:obj2 number: 20 } }
静态成员可以通过类名直接访问和调用,而实例成员必须通过实例对象来访问和调用。
final
final类:应用于类、方法和变量,并表示被修饰的实体是不可改变的、不可继承的或不可重写的。
如果一个类被声明为final
,则表示该类是不可继承的,即不能有子类。这样的类通常是为了防止其他类对其进行继承和修改。
final class MyFinalClass {
// 类的定义
}
final方法:一个方法被声明为final
,则表示该方法不能被子类重写(override)。子类无法改变或覆盖final
方法的实现,而必须使用父类中定义的方法。
class Parent {
final void myFinalMethod() {
// 方法的实现
}
}
class Child extends Parent {
// 编译错误:无法重写父类的final方法
// void myFinalMethod() {
// // 子类的实现
// }
}
final变量:一个变量被声明为final
,则表示该变量是一个常量,一旦被赋值后就不能再改变它的值。final
变量必须在声明时或构造方法中进行初始化,并且不能再修改。
class MyClass {
final int myConstant = 100;
void myMethod() {
final int myLocalConstant = 200;
// myLocalConstant = 300; // 编译错误:final变量无法重新赋值
}
}
final
关键字在Java中用于表示不可改变的实体。
abstract
abstract
修饰符用于创建抽象类和抽象方法。
抽象类:
抽象类使用abstract
关键字来声明,不能用new
关键字实例化,只能被继承。
抽象类可以包含普通成员变量、普通方法、抽象方法,也可以包含构造方法。
抽象类中的抽象方法没有方法体,只有方法的声明,具体的实现留给其子类。
如果一个类中包含一个或多个抽象方法,那么这个类必须被声明为抽象类。
抽象方法:
抽象方法使用abstract
关键字来声明,没有方法体,以分号结束。
抽象方法必须在抽象类中声明,不能在普通的非抽象类中声明。
子类必须实现其抽象类中所有的抽象方法,否则子类也必须声明为抽象类。
// 抽象类
abstract class Shape {
// 抽象方法
public abstract double calculateArea();
// 普通方法
public void display() {
System.out.println("This is a shape.");
}
}
// 子类继承抽象类
class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
// 实现抽象方法
public double calculateArea() {
return Math.PI * radius * radius;
}
}
public class Main {
public static void main(String[] args) {
// 无法实例化抽象类
// Shape shape = new Shape();
// 使用子类创建对象
Circle circle = new Circle(5.0);
System.out.println("Circle area: " + circle.calculateArea()); // 输出:Circle area: 78.53981633974483
circle.display(); // 输出:This is a shape.
}
}
abstract
关键字用于声明抽象类和抽象方法,抽象类是不能被实例化的,而抽象方法是没有具体实现的方法,必须在子类中进行实现。
默认类型
没有修饰符的成员即为默认访问修饰符,也称为package-private。被默认修饰符修饰的成员可以在同一包内访问,但在不同包中无法直接访问。
1.1.5、构造方法
public class MyClass {
private int value;
// private构造方法,禁止直接创建对象
private MyClass(int v) {
this.value = v;
}
// 公共的静态方法,用于创建对象并调用private构造方法
public static MyClass create(int v) {
return new MyClass(v);
}
// 默认访问修饰符的构造方法
MyClass() {
this.value = 0;
}
// protected构造方法
protected MyClass(String message) {
System.out.println(message);
}
}
构造方法是用于创建对象和初始化对象状态的特殊方法。通过使用不同的访问修饰符,可以控制构造方法的可见性,从而实现更好的封装和安全性。
1.2、对象(Object)
1.2.1、创建对象
在Java中,使用关键字new
来创建类的对象。创建对象的过程称为实例化。
// MyClass.java
public class MyClass {
private int value;
private MyClass(int v) {
this.value = v;
}
// 公共的静态方法,用于创建对象并调用private构造方法
public static MyClass create(int v) {
return new MyClass(v);
}
// 默认访问修饰符的构造方法
MyClass() {
this.value = 0;
}
// protected构造方法
protected MyClass(String message) {
System.out.println(message);
}
}
// Main.java
public class Main {
public static void main(String[] args) {
// public构造方法:可以在任何地方创建对象
MyClass obj1 = new MyClass();
// private构造方法:只能在同一类的内部使用公共方法创建对象
MyClass obj2 = MyClass.create(10);
// protected构造方法:在子类中使用super关键字创建对象
ChildClass obj3 = new ChildClass();
// 默认访问修饰符的构造方法:在同一包内可以创建对象
OtherClass obj4 = new OtherClass();
}
}
// ChildClass.java
class ChildClass extends MyClass {
ChildClass() {
super("Using protected constructor.");
}
}
// OtherClass.java(在MyClass类所在的同一包内)
class OtherClass {
OtherClass() {
MyClass obj = new MyClass("Using default access constructor.");
}
}
创建对象需要使用new
关键字来调用类的构造方法。构造方法的访问修饰符可以用public
、private
、protected
和默认访问修饰符来控制对象的可见性和访问权限。不同的访问修饰符可以用于实现对象的封装和限制对象的创建方式。
1.2.2、访问成员
通过对象可以访问类的成员变量和成员方法。访问成员变量使用.
运算符,调用成员方法使用.
运算符和方法的名称。
// MyClass.java
public class MyClass {
public int publicVar; // 可以在任何地方访问
private int privateVar; // 只能在本类内部访问
protected int protectedVar; // 在同一包内和子类中可以访问
int defaultVar; // 同一包内可以访问,默认访问修饰符
public void publicMethod() {
System.out.println("This is a public method.");
}
private void privateMethod() {
System.out.println("This is a private method.");
}
protected void protectedMethod() {
System.out.println("This is a protected method.");
}
void defaultMethod() {
System.out.println("This is a default method.");
}
}
// Main.java
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
// 访问不同访问修饰符的成员
obj.publicVar = 10; // public 成员可以在任何地方访问
// obj.privateVar = 20; // 编译错误:private 成员只能在本类内部访问
obj.protectedVar = 30; // protected 成员在同一包内和子类中可以访问
obj.defaultVar = 40; // default 成员在同一包内可以访问
obj.publicMethod(); // 输出:This is a public method.
// obj.privateMethod(); // 编译错误:private 方法只能在本类内部访问
obj.protectedMethod(); // 输出:This is a protected method.
obj.defaultMethod(); // 输出:This is a default method.
}
}
访问修饰符用于控制类的成员对其他类的可见性和访问权限。public
是最开放的,可以在任何地方访问;private
是最严格的,只能在本类内部访问;protected
允许在同一包内和子类中访问;默认访问修饰符允许在同一包内访问。通过选择适当的访问修饰符,可以实现类的封装性和数据隐藏。
1.2.3、引用
在Java中,对象被存储在堆内存中,而变量存储的是对象的引用(内存地址)。这样,变量可以指向对象并对其进行操作。
MyClass obj = new MyClass(); // 强引用
1.2.4、销毁对象
Java的垃圾回收机制会自动检测不再使用的对象并释放它们所占用的内存。一旦对象不再被引用,垃圾回收器会在合适的时候回收该对象。
2、封装与访问控制
封装(Encapsulation)是面向对象编程中的一个重要概念,它将数据和操作数据的方法(行为)组合在一个单元内,并对外部提供有限的接口,以控制数据的访问和操作。封装的目的是隐藏对象的内部实现细节,将数据的修改和访问限制在类的内部,从而提高代码的可维护性和安全性。封装主要通过访问控制修饰符来实现。
2.1、访问控制修饰符
- public:
public
是最开放的访问修饰符,表示成员可以被任何地方的类访问,包括不同的包。使用public
修饰的成员在整个项目中都是可见的。 - private:
private
是最严格的访问修饰符,表示成员只能在所属类的内部访问,其他类无法直接访问。这种方式保护了成员的隐私和安全性。 - protected:
protected
修饰符允许成员在同一包内的其他类以及不同包中的子类中访问。它比private
更开放,但比public
更受限制。 - 默认(package-private):如果成员没有明确指定任何访问修饰符(即没有
public
、private
或protected
关键字),则称为默认访问修饰符。默认
访问修饰符允许成员在同一包内的其他类中访问,但在不同包中是不可见的。
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String newName) {
if (newName != null && !newName.isEmpty()) {
name = newName;
}
}
public int getAge() {
return age;
}
public void setAge(int newAge) {
if (newAge >= 0 && newAge <= 120) {
age = newAge;
}
}
}
2.2、封装的原则:
- 隐藏对象的内部实现细节:使用
private
修饰符将成员变量隐藏在类的内部,只允许通过公共方法访问和修改这些成员变量。 - 提供有限的接口:通过
public
修饰符提供公共方法,这些方法是类与外部交互的接口,可以控制外部对类的访问和操作。 - 限制数据的修改:使用
private
修饰符可以限制外部直接修改成员变量的值,通过公共方法可以进行验证和控制数据的修改。 - 提高代码的可维护性和安全性:封装使得类的内部实现对外部代码是透明的,可以在不影响外部代码的情况下修改内部实现,从而提高代码的可维护性和安全性。
3、继承与多态
3.1、继承
继承是一种创建新类的机制,通过继承现有的类(称为父类或基类),新类(称为子类或派生类)可以获取父类的属性和方法,并可以在此基础上添加新的成员。继承使得代码重用成为可能,子类可以重用父类的代码,从而减少代码的冗余,提高代码的复用性。
在 Java 中,使用 extends
关键字来实现继承。子类继承了父类的非私有成员(成员变量和成员方法),并且可以在子类中重写父类的方法,以实现自己的特定行为。
class Animal {
void sound() {
System.out.println("Animal makes a sound.");
}
}
class Dog extends Animal {
void sound() {
System.out.println("Dog barks.");
}
}
3.2、多态
多态是指同一个方法名可以具有多种不同的实现方式,即一个对象可以以多种形态出现。多态允许子类对象可以替代父类对象,从而实现一种统一的接口,使得程序在不考虑具体子类的情况下可以更灵活地调用方法。
class Animal {
void sound() {
System.out.println("Animal makes a sound.");
}
}
class Dog extends Animal {
void sound() {
System.out.println("Dog barks.");
}
}
public class Main {
public static void main(String[] args) {
Animal animal1 = new Animal();
Animal animal2 = new Dog();
animal1.sound(); // Output: Animal makes a sound.
animal2.sound(); // Output: Dog barks.
}
}
3.2.1、方法重写
当子类提供了与父类具有相同名称和参数列表的方法时,子类的方法将覆盖(重写)父类的方法。通过重写,子类可以改变方法的实现,实现自己的特定行为。
class Animal {
void sound() {
System.out.println("Animal makes a sound.");
}
}
class Dog extends Animal {
void sound() {
System.out.println("Dog barks.");
}
}
3.2.2、方法重载
在同一个类中,可以定义多个方法,它们具有相同的名称但参数列表不同(参数个数、参数类型或参数顺序不同)。方法重载允许在同一个类中提供多个同名的方法,使得方法可以根据传入的参数类型的不同而执行不同的操作。
class Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
}
4、抽象类与接口
4.1、抽象类
抽象类是一种不能被实例化的类,它只能被用作其他类的父类。抽象类可以包含抽象方法(没有具体实现的方法)和具体方法(有实现的方法)。子类继承抽象类时,必须实现所有的抽象方法,否则子类也必须声明为抽象类。
在 Java 中,使用 abstract
关键字来声明一个抽象类。
abstract class Shape {
int x, y;
// 抽象方法,没有具体实现
abstract void draw();
// 具体方法,有实现
void move(int newX, int newY) {
this.x = newX;
this.y = newY;
}
}
4.2、接口
接口是一种定义了一组抽象方法和常量的类似契约的结构。接口定义了一组方法的签名(方法名、参数列表和返回类型),但没有方法的具体实现。类通过实现接口来实现多继承,一个类可以实现多个接口,从而使得一个类可以具有多个接口定义的行为。
接口中可以定义默认方法(default 方法)和静态方法(static 方法)。
在 Java 中,使用 interface
关键字来声明一个接口。
interface Drawable {
void draw(); // 抽象方法,没有具体实现
default void sayHello() { // 默认方法,可以有实现
System.out.println("Hello, I am a Drawable.");
}
}
4.3、区别
- 实现方式:抽象类是类的一种,可以包含构造方法、成员变量和具体方法,但可以包含抽象方法;接口是一种特殊的类,它只包含抽象方法和默认方法,不能包含构造方法和成员变量。
- 继承和实现:一个类只能继承一个抽象类,但可以实现多个接口。
- 构造方法:抽象类可以有构造方法,而接口不能有构造方法。
- 成员变量:抽象类可以有成员变量,而接口只能有常量(使用
final static
修饰的变量)。 - 方法实现:在抽象类中,可以有抽象方法和具体方法,子类继承抽象类时必须实现抽象方法,可以选择重写具体方法;在接口中,所有方法都是抽象的,子类必须实现所有接口中的抽象方法。
- 应用场景:抽象类通常用于创建类的层次结构,提供通用的基类,而接口通常用于定义共享行为,以及实现多继承。
5、构造方法
构造方法是一种特殊的方法,用于创建和初始化对象。当使用 new
关键字创建一个对象时,构造方法会被调用来初始化对象的状态。构造方法的名称必须与类名相同,没有返回类型(包括 void
),并且不能被直接调用。
public class MyClass {
private int x;
private int y;
// 无参构造方法
public MyClass() {
x = 0;
y = 0;
}
// 带参构造方法
public MyClass(int x, int y) {
this.x = x;
this.y = y;
}
}
构造方法是用于创建和初始化对象的特殊方法,它没有返回类型,名称与类名相同。Java 中的构造方法可以重载,根据不同的参数提供不同的初始化方式。
6、成员变量与静态成员
6.1、成员变量
成员变量也称为实例变量,是定义在类中但在方法外的变量。成员变量可以是任何数据类型。
class MyClass {
int x; // 成员变量
void printX() {
System.out.println("x = " + x);
}
}
6.2、静态成员
静态成员也称为类成员,是使用 static
关键字修饰的成员。它属于类而不是属于对象实例。无论创建多少个对象实例,静态成员的值在整个类中都是共享的。
class MyClass {
static int count; // 静态变量
void increment() {
count++;
}
static void printCount() { // 静态方法
System.out.println("Count = " + count);
}
}
7、成员方法与静态方法
7.1、成员方法
成员方法也称为实例方法,是定义在类中但在方法外的方法。成员方法是属于对象实例的,必须通过对象实例来调用。在成员方法内部可以访问和操作对象的成员变量,因为成员方法是与对象实例绑定的。
class MyClass {
int x; // 成员变量
void setX(int value) { // 成员方法
x = value;
}
int getX() { // 成员方法
return x;
}
}
7.2、静态方法
静态方法也称为类方法,是使用 static
关键字修饰的方法。静态方法属于类而不是对象实例,可以直接通过类名调用,不需要创建对象实例。因为静态方法不依赖于对象实例,所以在静态方法内部不能访问非静态的成员变量和成员方法,只能访问静态成员(静态变量和静态方法)。
class MyClass {
static int count; // 静态变量
static void increment() { // 静态方法
count++;
}
static int getCount() { // 静态方法
return count;
}
}
7.3、调用方式
成员方法:需要通过对象实例来调用,即通过 对象实例名.方法名()
的方式调用。
静态方法:可以直接通过类名来调用,即通过 类名.方法名()
的方式调用。
7.4、访问成员
成员方法:可以访问和操作对象的成员变量和其他成员方法,因为成员方法是与对象实例绑定的。
静态方法:不能访问对象的成员变量和非静态的成员方法,只能访问静态成员(静态变量和静态方法)
7.5、内存存储
成员方法:成员方法在每个对象实例中都有一份拷贝,属于对象实例的一部分。
静态方法:静态方法只有一份拷贝,属于类的一部分,与对象实例无关。
8、内部类
内部类是一种定义在其他类内部的类,它具有特殊的作用域和访问权限,可以访问包含它的外部类的成员,包括私有成员。内部类允许在一个类内部创建另一个类,从而实现更紧密的关联和组织代码。
8.1、成员内部类
成员内部类是定义在其他类内部的普通类,它与外部类的实例相关联。成员内部类可以访问外部类的所有成员,包括私有成员,因为它们在同一个类作用域内。
class Outer {
private int x;
class Inner {
void display() {
System.out.println("Value of x: " + x);
}
}
}
8.2、静态内部类
静态内部类是定义在其他类内部并使用 static
关键字修饰的类。静态内部类与外部类的实例无关,它类似于外部类的静态成员,只能访问外部类的静态成员,不能访问非静态成员。
class Outer {
private static int x;
static class Nested {
void display() {
System.out.println("Value of x: " + x);
}
}
}
8.3、局部内部类
局部内部类是定义在方法内部的类,它的作用域限定在方法内部。局部内部类可以访问方法的参数和局部变量,但只能在方法内部被实例化和使用。
class Outer {
void display() {
int y = 10;
class LocalInner {
void printY() {
System.out.println("Value of y: " + y);
}
}
LocalInner inner = new LocalInner();
inner.printY();
}
}
8.4、匿名内部类
匿名内部类是一种特殊的局部内部类,它没有类名,直接作为表达式或参数传递。通常用于实现接口或抽象类的匿名实现。
interface MyInterface {
void display();
}
public class Main {
public static void main(String[] args) {
MyInterface anonymous = new MyInterface() {
@Override
public void display() {
System.out.println("Anonymous inner class implementation.");
}
};
anonymous.display();
}
}
9、包(Package)
包(Package)是一种用于组织类和接口的方式,它类似于文件系统中的文件夹(目录)。包可以包含其他包、类、接口和其他资源文件,用于将相关的类和接口组织在一起,提供更好的代码管理和模块化。
在 Java 中,一个类可以声明属于一个特定的包,通过使用 package
关键字来指定包名。包名应该按照反向域名的约定来命名,例如:com.example.mypackage
。
package com.example.mypackage;
public class MyClass {
// 类的代码
}
使用其他包中的类时,可以使用 import
关键字来导入类,或者直接使用完整的包名进行类的引用。
import com.example.otherpackage.OtherClass;
public class MyClass {
public static void main(String[] args) {
OtherClass obj = new OtherClass();
// 使用 OtherClass 类的代码
}
}
使用包有助于防止类名冲突,通过包名来区分不同的类和接口。
10、equals()和hashCode()方法
10.1、equals() 方法
equals()
方法用于比较两个对象是否相等。在 Java 中,默认情况下,equals()
方法继承自 Object
类,它实际上是比较两个对象的引用是否相等,即判断两个对象是否指向同一个内存地址。为了实现自定义的对象内容比较,我们需要重写 equals()
方法。在重写 equals()
方法时,通常需要比较对象的关键属性,以确定对象是否相等。
equals()的重写:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getters and setters
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 判断是否是同一个对象的引用
if (obj == null || getClass() != obj.getClass()) return false; // 判断对象是否为null,并且是否是同一个类的实例
Person person = (Person) obj; // 将 obj 强制转换为 Person 类型
return age == person.age && Objects.equals(name, person.name); // 比较对象的内容
}
}
Person person1 = new Person("Alice", 25);
Person person2 = new Person("Bob", 30);
Person person3 = new Person("Alice", 25);
System.out.println(person1.equals(person2)); // 输出:false
System.out.println(person1.equals(person3)); // 输出:true
10.2、hashCode方法
hashCode()
方法用于计算对象的散列码(哈希码)。散列码是一个整数,用于在散列表等数据结构中确定对象的存储位置。在 Java 中,散列表的实现类(如 HashMap、HashSet 等)使用散列码来确定对象的存储位置和查找对象。
hashCode()
方法同样继承自 Object
类,但默认实现是根据对象的内存地址计算散列码。为了保证散列表的正确性,当重写 equals()
方法时,通常也需要重写 hashCode()
方法,以确保相等的对象具有相同的散列码。
public class Person {
private String name;
private int age;
// 省略构造方法、getter 和 setter
@Override
public int hashCode() {
int result = 17; // 初始化一个常数作为初始值
result = 31 * result + name.hashCode(); // 使用属性的散列码来计算结果
result = 31 * result + age; // 使用属性的值来计算结果
return result;
}
}
我们重写了 hashCode()
方法,根据 name
和 age
两个属性来计算散列码。在计算过程中,我们使用了一些常数(如 17 和 31),这是因为这些数是被广泛认为可以产生较好散列码分布的质数。
11、泛型
泛型(Generics)是 Java 中的一种特性,它允许在类和方法中使用类型参数,以实现代码的通用性和安全性。泛型的主要目的是在编译时期提供更强的类型检查,并避免在运行时出现类型转换错误。
使用泛型可以让我们编写更灵活、可复用的代码,而无需为每种数据类型编写重复的代码。泛型可以应用于类、接口和方法。
11.1、泛型类
泛型类是具有一个或多个类型参数的类。通过在类名后面使用尖括号 <T>
来定义类型参数。在类内部,可以使用类型参数 T
来代表实际的类型,就像是一个占位符。在实例化泛型类时,可以传入具体的类型作为类型参数。
public class Box<T> {
private T content;
public Box(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
11.2、泛型接口
泛型接口是具有一个或多个类型参数的接口。使用方式与泛型类类似,通过在接口名后面使用尖括号 <T>
来定义类型参数。在实现泛型接口时,可以指定具体的类型参数。
public interface List<T> {
void add(T element);
T get(int index);
}
11.3、泛型方法
泛型方法是在方法中使用类型参数的方法。使用方式是在方法的返回类型之前使用尖括号 <T>
来定义类型参数。在方法内部,可以使用类型参数 T
来代表实际的类型。
public <T> T getElement(T[] array, int index) {
return array[index];
}
泛型代码更简洁,不再需要进行显式的类型转换。
// 没有使用泛型的写法
List list = new ArrayList();
list.add("Hello");
String str = (String) list.get(0);
// 使用泛型的写法
List<String> list = new ArrayList<>();
list.add("Hello");
String str = list.get(0); // 不需要显式类型转换
12、异常处理
异常处理是在程序中预测并处理可能出现的异常情况的一种机制。在 Java 中,异常(Exception)是指在程序运行时出现的不正常情况,例如除以零、数组越界、空指针引用等。当异常发生时,如果没有进行适当的处理,程序可能会中断并终止执行。
12.1、try-catch 块
使用 try-catch 块可以捕获并处理异常。try 块用于包含可能引发异常的代码,catch 块用于捕获并处理异常。当异常发生时,程序会跳转到对应的 catch 块,并执行相应的异常处理代码。
try {
// 可能会引发异常的代码
} catch (ExceptionType e) {
// 异常处理代码
}
12.2、多重 catch 块
可以使用多个 catch 块来处理不同类型的异常,从而提供更具体的异常处理逻辑。
try {
// 可能会引发异常的代码
} catch (ExceptionType1 e1) {
// 处理 ExceptionType1 类型的异常
} catch (ExceptionType2 e2) {
// 处理 ExceptionType2 类型的异常
}
12.3、finally 块
finally 块用于包含无论异常是否发生都必须执行的代码。通常在 finally 块中释放资源、关闭文件等清理操作。
try {
// 可能会引发异常的代码
} catch (ExceptionType e) {
// 异常处理代码
} finally {
// 必定会执行的清理操作
}
12.4、抛出异常
使用 throw 关键字可以手动抛出异常。当程序遇到某种特定条件时,可以使用 throw 关键字抛出相应的异常对象。
if (condition) {
throw new ExceptionType("Error message");
}
12.5、异常类型
受检异常: 受检异常在编译时必须进行处理,要么在方法中使用 try-catch 块捕获,要么在方法的声明中使用 throws 关键字声明该异常将被抛出。
非受检异常: 非受检异常(运行时异常)通常是程序逻辑错误,不要求强制处理。例如,空指针异常和数组越界异常属于非受检异常。
public class ExceptionHandlingExample {
public static void main(String[] args) {
try {
int result = divide(10, 0);
System.out.println("Result: " + result);
} catch (ArithmeticException e) {
System.out.println("Exception caught: " + e.getMessage());
} finally {
System.out.println("Finally block executed.");
}
}
public static int divide(int dividend, int divisor) {
if (divisor == 0) {
throw new ArithmeticException("Divisor cannot be zero.");
}
return dividend / divisor;
}
}
13、Java标准库中的面向对象相关类
- Object 类:
java.lang.Object
是所有类的根类,在 Java 中所有类都直接或间接继承自 Object 类。它提供了一些通用的方法,如equals()
、hashCode()
、toString()
等。 - String 类:
java.lang.String
表示字符串,它是不可变的,提供了许多处理字符串的方法,如拼接、截取、查找等。 - StringBuilder 和 StringBuffer 类:
java.lang.StringBuilder
和java.lang.StringBuffer
是可变的字符串类,提供了更高效的字符串操作方法。 - ArrayList 和 LinkedList 类:
java.util.ArrayList
和java.util.LinkedList
是用于存储和操作列表数据的类。ArrayList 是基于数组实现的,而 LinkedList 是基于链表实现的。 - HashSet 和 TreeSet 类:
java.util.HashSet
和java.util.TreeSet
是用于存储不重复元素的集合类。HashSet 使用哈希表实现,而 TreeSet 使用红黑树实现,并对元素进行排序。 - HashMap 和 TreeMap 类:
java.util.HashMap
和java.util.TreeMap
是用于存储键值对的映射类。HashMap 使用哈希表实现,而 TreeMap 使用红黑树实现,并对键进行排序。 - Iterator 和 Iterable 接口:
java.util.Iterator
是用于遍历集合元素的接口,java.lang.Iterable
是支持迭代的接口,实现该接口的类可以通过foreach
循环进行遍历。 - Comparable 和 Comparator 接口:
java.lang.Comparable
是用于定义对象之间自然顺序的接口,实现该接口的类可以直接使用Arrays.sort()
和Collections.sort()
进行排序。java.util.Comparator
是用于定义对象之间其他排序方式的接口。 - ObjectInputStream 和 ObjectOutputStream 类:
java.io.ObjectInputStream
和java.io.ObjectOutputStream
是用于对象的序列化和反序列化的类,可以将对象转换为字节流进行存储和传输。 - Cloneable 接口:
java.lang.Cloneable
是用于标记类支持对象的克隆(拷贝)操作。 - Math 类:
java.lang.Math
提供了一些常用的数学运算方法,如绝对值、取整、幂运算等。