一、类与对象
1.类(Class)
类是对现实世界中一类具有共同属性和行为的对象的抽象描述。在Java中,类是一种引用数据类型,它定义了对象的结构(即属性和方法)。
类的定义
Java中使用class
关键字来定义类。类的定义包括类名、属性和方法。
public class Dog {
// 属性(成员变量)
String name;
int age;
// 方法(成员方法)
void bark() {
System.out.println("汪汪汪!");
}
}
类的成员
- 属性(成员变量):用于描述对象的特征或状态。属性可以是基本数据类型或引用数据类型。
- 方法(成员方法):用于描述对象的行为或功能。方法定义了对象可以执行的操作。
2.对象(Object)
对象是类的实例化,即根据类的定义创建的具体实例。每个对象都具有类所定义的属性和方法,并且可以有自己的状态和行为。
创建对象
在Java中,使用new
关键字和类的构造方法来创建对象。
Dog myDog = new Dog(); // 创建Dog类的一个对象
访问对象的属性和方法
通过对象引用,可以访问和修改对象的属性,以及调用对象的方法。
myDog.name = "旺财"; // 访问并修改对象的属性
myDog.age = 3;
myDog.bark(); // 调用对象的方法
构造方法
构造方法是一种特殊的方法,用于初始化对象的状态。构造方法与类名相同,没有返回类型。
public class Dog {
String name;
int age;
// 构造方法
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
void bark() {
System.out.println("汪汪汪!");
}
}
使用构造方法创建对象时,可以传递参数来初始化对象的属性。
Dog myDog = new Dog("旺财", 3); // 使用构造方法创建对象并初始化属性
二、封装(Encapsulation)
封装是面向对象编程的四大基本特性之一,它强调将对象的内部状态(属性)和行为(方法)隐藏起来,仅对外提供公共的访问方式。封装的主要目的是增强数据的安全性和保护内部状态的完整性,同时降低代码的耦合度,提高代码的可维护性和重用性。
在Java中,封装主要通过以下方式实现:
访问修饰符
Java提供了四种访问修饰符:public
、protected
、defalut和private
。通过合理地使用这些修饰符,可以控制类、属性和方法的可见性和访问权限。
private
:表示该成员只能被当前类中的方法访问,其他类无法直接访问。这是封装性的最重要体现,它隐藏了类的内部实现细节。default
:表示该成员只能被同一个包中的类访问。protected
:表示该成员既可以被同一个包中的其他类访问,也可以被不同包中的子类访问。public
:表示该成员可以被任何类访问。
构造方法
构造方法用于初始化对象的状态。通过私有构造方法,可以防止其他类创建该类的实例,从而实现单例模式或工厂模式等设计模式。
public class Singleton {
private static Singleton instance;
private Singleton() {
// 私有构造方法,防止外部类创建实例
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Getter和Setter方法
对于需要被外部访问的属性,通常不会直接将其声明为public
,而是提供公共的getter和setter方法来获取和设置属性的值。这样可以在获取或设置属性值时添加额外的逻辑,如验证输入的有效性或触发某些事件。
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
if (name != null && !name.isEmpty()) {
this.name = name;
} else {
System.out.println("Name cannot be null or empty");
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age >= 0) {
this.age = age;
} else {
System.out.println("Age cannot be negative");
}
}
}
封装的好处
-
数据隐藏:封装隐藏了对象的内部状态,只暴露必要的接口给外部。这有助于保护数据的完整性和安全性,防止外部代码随意修改对象的状态。
-
减少耦合:通过封装,对象之间的依赖关系被最小化。一个对象只需要知道如何与其他对象交互,而不需要了解其他对象的内部实现细节。这降低了代码的耦合度,提高了系统的可维护性和可扩展性。
-
代码重用:封装使得类更加独立和可重用。一旦一个类被正确地封装,它就可以在不同的上下文和应用程序中被重复使用。
-
易于维护:由于封装隐藏了实现细节,当内部实现发生变化时,只需要修改类的内部代码,而不需要修改使用该类的代码。这简化了代码的维护和修改过程。
在实际编程中,封装是构建健壮、可维护代码的基础。通过合理地使用封装,我们可以创建出更加安全、可靠和易于理解的程序。
三、继承
在Java中,继承是一个核心概念,它允许我们创建一个新的类(称为子类或派生类)来继承另一个类(称为父类或基类)的属性和方法。这种机制使得代码复用变得更为高效,同时也为软件的可维护性和可扩展性提供了坚实的基础。
首先,继承定义了类之间如何相互关联并共享特性。对于具有相同或相似属性的多个类,我们可以提取出这些共有的特性,并将其定义为一个父类或超类。然后,这些类可以通过继承这个父类来共享这些特性。这样,子类不仅可以拥有父类的属性和方法,还可以定义自己独有的属性和方法。
在Java中,子类可以访问父类的非private属性和方法。这意味着父类中定义的公共(public)和保护(protected)属性和方法都可以被子类继承并直接使用。而私有(private)属性和方法则只能在父类内部使用,无法被子类继承。
除了继承父类的属性和方法,子类还可以对父类进行扩展,即添加新的属性和方法。这使得子类能够根据具体需求进行定制,同时保留父类的通用功能。
值得注意的是,Java只支持单继承,即一个类只能有一个直接的父类。这种设计有助于减少代码的复杂性,并避免可能出现的继承层次混乱问题。如果需要实现多个类的功能,Java提供了接口(interface)这一机制,允许一个类实现多个接口。
在实际编程中,继承的使用需要谨慎。过度使用继承可能导致代码结构变得复杂,难以维护。因此,在设计类结构时,应优先考虑使用组合(composition)而不是继承。
总的来说,Java中的继承是一个强大的工具,它允许我们创建具有层次结构的类体系,实现代码的复用和扩展。通过合理地使用继承,我们可以构建出更加灵活、可维护和可扩展的Java应用程序。
以下是一个具体的实例演示:
首先,我们定义一个父类Animal
,它描述了动物的基本属性和方法:
public class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + " is eating.");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后,我们创建一个子类Dog
,它继承了Animal
类。在Dog
类中,我们不仅可以访问Animal
类的属性和方法,还可以定义自己特有的属性和方法:
public class Dog extends Animal {
private String breed; // Dog特有的属性
public Dog(String name, String breed) {
super(name); // 调用父类的构造方法
this.breed = breed;
}
public void bark() {
System.out.println(name + " is barking.");
}
public String getBreed() {
return breed;
}
public void setBreed(String breed) {
this.breed = breed;
}
}
在Dog
类的构造方法中,我们使用super
关键字调用了父类Animal
的构造方法,以便初始化从父类继承的属性。
现在,我们可以创建一个Dog
对象,并调用其方法和访问其属性:
public class AnimalTest {
public static void main(String[] args) {
Dog myDog = new Dog("Buddy", "Golden Retriever");
myDog.eat(); // 继承自Animal的方法
myDog.bark(); // Dog特有的方法
System.out.println("Dog's name: " + myDog.getName()); // 继承自Animal的属性
System.out.println("Dog's breed: " + myDog.getBreed()); // Dog特有的属性
}
}
运行这个程序,你会看到输出:
Buddy is eating.
Buddy is barking.
Dog's name: Buddy
Dog's breed: Golden Retriever
从这个例子中,我们可以看到继承在Java中的实际应用。Dog
类继承了Animal
类,因此它自动拥有了Animal
类的name
属性和eat
方法。同时,Dog
类还定义了自己特有的breed
属性和bark
方法。通过继承,我们减少了代码的冗余,提高了代码的可维护性和复用性。
四、多态
多态是面向对象编程的三大基本特性之一,它表示同一个操作或方法可以在不同的对象上表现出不同的行为。在Java中,多态允许我们使用父类或者接口类型的引用变量来调用子类或者实现类的方法。多态的实现主要依赖于继承、接口以及方法重写等机制。
首先,多态的存在需要满足三个条件:继承、重写和父类引用指向子类对象。继承是多态的基础,它使得子类能够继承父类的属性和方法。重写则是子类对父类中已有的方法进行重新实现,这样当使用父类引用调用该方法时,实际执行的是子类中的方法。最后,父类引用指向子类对象是多态的直接体现,它使得我们可以在运行时确定具体调用哪个类的方法。
在Java中,多态的实现方式有多种。最常见的是通过继承来实现多态。子类继承父类并重写父类的方法,然后我们可以使用父类类型的引用来指向子类对象,并调用其方法。此外,接口也是实现多态的重要手段。定义一个接口,多个类实现该接口并重写接口中的方法,然后我们可以使用接口类型的引用来指向实现类对象并调用其方法。
多态的好处主要体现在代码的灵活性、可扩展性和可维护性上。通过多态,我们可以以一种统一的方式处理不同类型的对象,减少了代码的冗余和复杂性。同时,多态也使得程序更加易于扩展和维护,因为当添加新的子类或者实现类时,只需要遵循相同的接口或继承关系,就可以无缝地集成到现有的代码中。
需要注意的是,多态只适用于父类或接口类型引用指向子类或实现类对象的情况。对于同一类的不同对象之间的方法调用,并不涉及多态的概念。此外,在使用多态时,还需要注意访问权限的问题。父类引用只能访问父类中定义的方法和属性,对于子类新增的方法和属性是无法直接访问的。
总的来说,多态是Java面向对象编程中非常重要的一个概念。通过合理地使用多态,我们可以编写出更加灵活、可扩展和可维护的代码。
五、抽象类与接口
在Java编程中,抽象类和接口是两个非常重要的概念,它们各自具有特定的用途和特性。
首先,抽象类是一种不能被直接实例化的类,它主要用于定义一些具有共性的类。抽象类中可以包含抽象方法和具体方法。抽象方法是没有具体实现的方法声明,需要在子类中进行实现。具体方法则是已经实现的方法,子类可以直接调用它们。此外,抽象类还可以定义变量和构造方法。抽象类的主要作用是限制规定子类必须实现的某些方法,但不关注实现细节。
接口则是一种完全不同的概念。接口是一种定义了一组需要由多个类实现的方法的规范,它不能被直接实例化,接口变量必须指向实现所有接口方法的类对象。接口中的方法都是抽象的,且只能做方法申明,不能做方法实现。此外,接口中定义的变量只能是公共的静态的常量,接口也不能有构造方法。接口的主要作用是为实现类提供一个统一的规范,使得不同的类可以实现相同的接口,从而具有相同的行为。
在使用场景上,抽象类通常用于定义一个基类,其中包含一些通用的属性和方法,并可让子类去继承和扩展。而接口则用于定义一组需要由多个类实现的方法,例如定义一个可排序接口,由多个类去实现排序算法。
抽象类和接口的主要区别在于:抽象类是一种不能被直接实例化的类,用于定义具有共性的类,可以包含抽象方法和具体实现;而接口是一种规范,定义了需要由多个类实现的方法,且所有方法都是抽象的,不能有具体实现。一句话概括来说,抽象类提供了部分实现,而接口则完全定义了行为规范。在实际编程中,应根据具体需求选择使用抽象类还是接口。
六、访问修饰符
访问修饰符是编程语言中用于控制类、方法、属性等成员的可见性和访问权限的关键字。在Java中,主要的访问修饰符有四种:private
、default
(无修饰符)、protected
和public
。
- private:
- 成员只能被定义该成员的类访问。
- 提供了最高级别的封装性,隐藏类的内部状态和实现细节。
- default(或称为包级访问权限):
- 成员可以被同一个包内的所有类访问。
- 如果没有显式指定访问修饰符,则使用此访问级别。
- protected:
- 成员可以被定义该成员的类、同一个包内的其他类以及所有子类访问。
- 常用于子类继承父类时,希望子类可以访问某些父类的成员,但又不希望其他包中的类能够直接访问。
- public:
- 成员可以被任何类访问,无论它们是否在同一个包中。
- 提供了最低的封装级别,通常用于暴露API给外部使用。
正确使用访问修饰符是编写高质量、可维护代码的重要部分。它有助于控制代码的复杂性,确保类的内部状态不被不恰当地访问和修改,同时也允许类和对象之间进行必要的交互。
在Java中,访问修饰符的限制强度从强到弱依次是:private
> default
> protected
> public
。也就是说,如果一个成员被声明为private
,那么它就不能被声明为其他任何访问级别。同样地,protected
成员不能被声明为private
或default
,依此类推。
七、构造方法
构造方法(也称为构造函数或构造器)是一种特殊的方法,用于在创建对象时初始化对象的状态。在Java中,构造方法的名称必须与类名完全相同,且没有返回类型(包括void)。构造方法的主要目的是为新创建的对象分配内存,并设置其初始状态。
构造方法的特点和用法如下:
-
名称与类名相同:构造方法的名称必须与定义它的类的名称完全相同,包括大小写。
-
没有返回类型:构造方法不返回任何值,因此它们的声明中没有返回类型,包括void类型。
-
自动调用:在创建类的对象时,构造方法会被自动调用。这是Java虚拟机(JVM)在内存中为新对象分配空间后做的第一件事。
-
重载:与类中的其他方法一样,构造方法也可以重载。这意味着可以在同一个类中定义多个构造方法,每个构造方法都有不同的参数列表。
-
初始化对象状态:构造方法通常用于初始化对象的属性或执行其他必要的设置操作。
下面是一个简单的Java类示例,其中包含一个构造方法:
public class Person {
private String name;
private int age;
// 构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 其他方法...
public void introduce() {
System.out.println("Hello, my name is " + name + " and I am " + age + " years old.");
}
}
在这个例子中,Person
类有一个构造方法,它接受两个参数:一个String
类型的name
和一个int
类型的age
。当创建Person
对象时,这个构造方法会被调用,并且传递的参数会用来初始化对象的name
和age
属性。
创建Person
对象并调用其方法的示例:
public class Main {
public static void main(String[] args) {
// 创建Person对象时调用构造方法
Person person = new Person("Alice", 30);
// 调用Person对象的方法
person.introduce(); // 输出: Hello, my name is Alice and I am 30 years old.
}
}
在这个main
方法中,通过new Person("Alice", 30)
语句创建了一个Person
对象,并传递了"Alice"和30作为构造方法的参数。构造方法被调用后,新创建的person
对象的name
属性被设置为"Alice",age
属性被设置为30。随后,通过调用person.introduce()
方法输出了对象的介绍信息。
八、this关键字
this
是 Java 中的一个特殊关键字,它引用当前对象的实例。在类的非静态方法中,this
关键字可以用来访问当前对象的成员变量、成员方法或构造方法。使用 this
关键字可以帮助我们区分成员变量和局部变量,当它们的名称相同时,避免产生混淆。
以下是 this
关键字的一些主要用途和示例:
1. 引用当前对象的成员变量
当局部变量和成员变量同名时,可以用 this
来区分它们:
public class Person {
private String name; // 成员变量
public Person(String name) {
this.name = name; // this.name 指的是成员变量,name 指的是构造方法的参数
}
public void setName(String name) {
this.name = name; // 同样,this.name 指的是成员变量
}
public String getName() {
return this.name; // 返回成员变量
}
}
2. 调用当前对象的成员方法
this
也可以用来调用当前对象的另一个成员方法:
public class Person {
public void introduce() {
System.out.println("Hello, my name is " + this.getName()); // 使用 this 调用 getName() 方法
}
public String getName() {
return "Alice";
}
}
3. 在构造方法中调用另一个构造方法
this
关键字还可以用于构造方法之间的调用,这通常称为构造方法的重载或构造方法链。这种用法只能在构造方法中使用,且必须位于构造方法的第一行:
public class Person {
private String name;
private int age;
public Person(String name) {
this(name, 0); // 调用另一个构造方法
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
在这个例子中,第一个构造方法通过 this(name, 0)
调用了第二个构造方法,从而避免了重复的代码。
4. 在方法中引用当前对象本身
this
也可以作为方法的返回值类型,用于返回当前对象本身,这在链式调用中很常见:
public class Person {
private String name;
public Person setName(String name) {
this.name = name;
return this; // 返回当前对象,允许链式调用
}
public String getName() {
return this.name;
}
}
使用上述 setName
方法时,可以这样链式调用:
Person person = new Person();
person.setName("Alice").setName("Bob"); // 链式调用 setName 方法
总的来说,this
关键字在 Java 中是一个强大的工具,它帮助我们明确地区分成员变量和局部变量,提供对当前对象的引用,并在构造方法和成员方法之间建立联系。
九、静态成员
静态成员是Java编程中的一个重要概念,它属于类本身而不是类的实例。这意味着静态成员变量和静态成员方法在所有类的实例之间共享,并且可以在不创建类的实例的情况下访问。
静态成员变量
静态成员变量也称为类变量或静态域。它们被static
关键字修饰,并存储在内存中的一个固定位置。每个对象都有属于自己的非静态域,这些变量存储在不同的内存位置,而静态成员变量则属于整个类,而不是每个对象。这意味着类的所有实例都共享同一个静态成员变量的值。
例如,如果你有一个Student
类,并且你想记录总共有多少学生,你可以使用一个静态成员变量来实现。因为这个变量不需要每个学生对象都存储一份,而是需要让所有的学生来共享。
静态成员变量的定义语法如下:
public class MyClass {
public static int myStaticVariable;
}
静态成员变量可以通过类名直接访问,无需创建类的实例。例如:
MyClass.myStaticVariable = 42;
静态成员方法
静态成员方法也是用static
关键字修饰的,并且它们属于类而不是类的实例。静态成员方法应该使用类名来调用。静态成员方法内部可以直接调用静态成员方法和静态成员变量,但不能直接调用非静态成员方法和非静态成员变量,因为非静态成员是依赖于具体对象的。
静态成员方法的定义语法如下:
public class MyClass {
public static void myStaticMethod() {
// 方法体
}
}
调用静态成员方法的示例:
MyClass.myStaticMethod();
总结
静态成员(包括静态成员变量和静态成员方法)在Java中扮演着重要的角色,它们提供了一种机制来定义与类本身相关联的数据和行为,而不是与类的特定实例相关联。这使得静态成员非常适合用于存储需要在整个程序中共享的数据或执行不依赖于具体对象的行为。