Java面向对象编程基础
类与对象
类的定义
在Java中,类是一种用户自定义的数据类型,它是对现实世界中一类具有相同属性和行为的事物的抽象。类定义了对象的状态(通过成员变量表示)和行为(通过成员方法表示)。 以下是一个简单类的定义示例:
public class Dog {
// 成员变量(表示狗的属性)
String name;
int age;
// 成员方法(表示狗的行为)
void bark() {
System.out.println(name + " is barking.");
}
}
在上述代码中,Dog
类有两个成员变量 name
和 age
,分别表示狗的名字和年龄。还有一个成员方法 bark
,表示狗叫的行为。
对象的创建与使用
对象是类的实例。一旦定义了类,就可以创建该类的对象,并通过对象来访问类的成员变量和调用成员方法。
public class Main {
public static void main(String[] args) {
// 创建Dog类的对象
Dog myDog = new Dog();
myDog.name = "Buddy";
myDog.age = 3;
// 调用对象的方法
myDog.bark();
}
}
在 main
方法中,首先使用 new
关键字创建了 Dog
类的一个对象 myDog
。然后给 myDog
的成员变量 name
和 age
赋值,并调用了 bark
方法。
构造方法
构造方法是一种特殊的方法,用于在创建对象时初始化对象的状态。构造方法的名称必须与类名相同,并且没有返回类型。
public class Dog {
String name;
int age;
// 构造方法
public Dog(String dogName, int dogAge) {
name = dogName;
age = dogAge;
}
void bark() {
System.out.println(name + " is barking.");
}
}
public class Main {
public static void main(String[] args) {
// 使用构造方法创建对象
Dog myDog = new Dog("Buddy", 3);
myDog.bark();
}
}
在上述代码中,Dog
类有一个构造方法 Dog(String dogName, int dogAge)
,在创建 Dog
对象时,通过构造方法传递参数来初始化 name
和 age
成员变量。
访问修饰符
访问修饰符用于控制类、成员变量和成员方法的访问权限。Java 中有四种访问修饰符:public
、private
、protected
和默认(没有修饰符)。
public
:被public
修饰的类、成员变量和成员方法可以被任何其他类访问。private
:被private
修饰的成员变量和成员方法只能在本类内部被访问。protected
:被protected
修饰的成员变量和成员方法可以被本类、同一包中的其他类以及不同包中的子类访问。- 默认(没有修饰符):默认访问权限的类、成员变量和成员方法可以被同一包中的其他类访问。
以下是一个示例展示不同访问修饰符的使用:
public class AccessModifiersExample {
// public 成员变量
public int publicVar;
// private 成员变量
private int privateVar;
// protected 成员变量
protected int protectedVar;
// 默认访问权限成员变量
int defaultVar;
// public 方法
public void publicMethod() {
System.out.println("This is a public method.");
}
// private 方法
private void privateMethod() {
System.out.println("This is a private method.");
}
// protected 方法
protected void protectedMethod() {
System.out.println("This is a protected method.");
}
// 默认访问权限方法
void defaultMethod() {
System.out.println("This is a default method.");
}
}
封装
封装的概念
封装是面向对象编程的一个重要原则,它将对象的状态(成员变量)和行为(成员方法)包装在一起,并对外部隐藏对象的内部实现细节。通过封装,可以提高代码的安全性和可维护性。
使用封装的好处
- 数据隐藏:通过将成员变量设为
private
,外部类无法直接访问和修改对象的内部数据,从而保护数据的完整性和安全性。 - 简化接口:外部类只需要通过公开的成员方法来与对象进行交互,不需要了解对象内部的复杂实现,使得代码的使用更加简单。
- 可维护性:当对象的内部实现发生变化时,只要公开的接口不变,对外部类的影响就很小,便于代码的维护和扩展。
实现封装
在Java中,通常将成员变量声明为 private
,然后提供 public
的 getter
和 setter
方法来访问和修改这些成员变量。
public class Person {
private String name;
private int age;
// getter 方法获取 name
public String getName() {
return name;
}
// setter 方法设置 name
public void setName(String name) {
this.name = name;
}
// getter 方法获取 age
public int getAge() {
return age;
}
// setter 方法设置 age
public void setAge(int age) {
if (age >= 0) {
this.age = age;
} else {
System.out.println("Age cannot be negative.");
}
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setName("Alice");
person.setAge(25);
System.out.println("Name: " + person.getName());
System.out.println("Age: " + person.getAge());
person.setAge(-5);
}
}
在上述代码中,Person
类的 name
和 age
成员变量被声明为 private
,通过 getName
和 getAge
方法获取其值,通过 setName
和 setAge
方法设置其值。在 setAge
方法中,还进行了数据验证,确保年龄不会被设置为负数。
继承
继承的概念
继承是面向对象编程的另一个重要特性,它允许一个类(子类)从另一个类(父类)中获取属性和行为。通过继承,可以实现代码的复用,提高开发效率。
继承的语法
在Java中,使用 extends
关键字来表示继承关系。
class Animal {
String name;
void eat() {
System.out.println(name + " is eating.");
}
}
class Dog extends Animal {
int age;
void bark() {
System.out.println(name + " is barking.");
}
}
在上述代码中,Dog
类继承自 Animal
类,Dog
类自动拥有了 Animal
类的 name
成员变量和 eat
成员方法,同时还可以定义自己特有的成员变量 age
和成员方法 bark
。
重写(Override)
当子类需要对从父类继承的方法进行不同的实现时,可以重写该方法。重写的方法必须满足以下条件:
- 方法名、参数列表和返回类型必须与父类中的方法完全相同(对于返回类型,在Java 5.0及以上版本,子类重写方法的返回类型可以是父类方法返回类型的子类)。
- 访问修饰符不能比父类中被重写方法的访问修饰符更严格。
class Animal {
void makeSound() {
System.out.println("Animal makes a sound.");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Dog barks.");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
animal.makeSound();
Dog dog = new Dog();
dog.makeSound();
Animal animalAsDog = new Dog();
animalAsDog.makeSound();
}
}
在上述代码中,Dog
类重写了 Animal
类的 makeSound
方法。在 main
方法中,分别创建了 Animal
对象、Dog
对象以及将 Dog
对象赋值给 Animal
类型的变量,然后调用 makeSound
方法,根据对象的实际类型,会调用相应的方法实现。
super
关键字
super
关键字用于在子类中访问父类的成员变量、成员方法和构造方法。
- 访问父类成员变量:当子类的成员变量与父类的成员变量同名时,可以使用
super
关键字来访问父类的成员变量。 - 调用父类成员方法:子类重写父类方法后,如果还需要调用父类的方法实现,可以使用
super
关键字。 - 调用父类构造方法:在子类的构造方法中,可以使用
super
关键字调用父类的构造方法,并且super
调用必须是子类构造方法的第一行代码。
class Animal {
String name;
Animal(String name) {
this.name = name;
}
void eat() {
System.out.println(name + " is eating.");
}
}
class Dog extends Animal {
int age;
Dog(String name, int age) {
super(name);
this.age = age;
}
@Override
void eat() {
super.eat();
System.out.println(name + " is a dog and it's " + age + " years old.");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog("Buddy", 3);
dog.eat();
}
}
在上述代码中,Dog
类的构造方法通过 super(name)
调用了 Animal
类的构造方法来初始化 name
成员变量。Dog
类重写的 eat
方法中,先通过 super.eat()
调用了父类的 eat
方法,然后输出了狗特有的信息。
多态
多态的概念
多态是指同一个方法调用,根据对象的实际类型不同,会产生不同的行为。多态是通过继承和重写来实现的,它提高了代码的灵活性和可扩展性。
编译时多态(方法重载,Overload)
方法重载是指在同一个类中,允许存在多个同名方法,但这些方法的参数列表(参数个数、参数类型或参数顺序)必须不同。方法重载是在编译时确定调用哪个方法,因此也称为编译时多态。
public class Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
}
public class Main {
public static void main(String[] args) {
Calculator calculator = new Calculator();
int result1 = calculator.add(2, 3);
double result2 = calculator.add(2.5, 3.5);
int result3 = calculator.add(2, 3, 4);
System.out.println("Result 1: " + result1);
System.out.println("Result 2: " + result2);
System.out.println("Result 3: " + result3);
}
}
在上述代码中,Calculator
类有三个 add
方法,它们具有不同的参数列表,构成了方法重载。在 main
方法中,根据传递的参数类型和个数,编译器会选择合适的 add
方法进行调用。
运行时多态(方法重写,Override)
运行时多态是指在程序运行时,根据对象的实际类型来决定调用哪个类的重写方法。运行时多态需要满足以下条件:
- 存在继承关系。
- 子类重写了父类的方法。
- 父类类型的变量引用子类对象。
class Shape {
void draw() {
System.out.println("Drawing a shape.");
}
}
class Circle extends Shape {
@Override
void draw() {
System.out.println("Drawing a circle.");
}
}
class Rectangle extends Shape {
@Override
void draw() {
System.out.println("Drawing a rectangle.");
}
}
public class Main {
public static void main(String[] args) {
Shape shape1 = new Circle();
Shape shape2 = new Rectangle();
shape1.draw();
shape2.draw();
}
}
在上述代码中,Circle
和 Rectangle
类继承自 Shape
类,并重写了 draw
方法。在 main
方法中,Shape
类型的变量 shape1
和 shape2
分别引用了 Circle
和 Rectangle
对象,在调用 draw
方法时,会根据对象的实际类型,调用相应子类的 draw
方法,体现了运行时多态。
抽象类与接口
抽象类
- 抽象类的定义:抽象类是一种不能被实例化的类,它通常用于作为其他类的基类,为子类提供一个通用的框架。抽象类可以包含抽象方法和具体方法。抽象方法是只有方法声明而没有方法体的方法,需要由子类来实现。
- 语法:使用
abstract
关键字来定义抽象类和抽象方法。
abstract class Animal {
String name;
// 抽象方法
abstract void makeSound();
// 具体方法
void eat() {
System.out.println(name + " is eating.");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Dog barks.");
}
}
在上述代码中,Animal
类是一个抽象类,它有一个抽象方法 makeSound
和一个具体方法 eat
。Dog
类继承自 Animal
类,并实现了 makeSound
抽象方法。
接口
- 接口的定义:接口是一种特殊的抽象类型,它只包含抽象方法(在Java 8及以上版本,接口还可以包含默认方法和静态方法)。接口不能被实例化,它用于定义一组方法的签名,实现接口的类必须实现接口中定义的所有方法。
- 语法:使用
interface
关键字来定义接口,使用implements
关键字来实现接口。
interface Drawable {
void draw();
}
class Circle implements Drawable {
@Override
void draw() {
System.out.println("Drawing a circle.");
}
}
class Rectangle implements Drawable {
@Override
void draw() {
System.out.println("Drawing a rectangle.");
}
}
在上述代码中,Drawable
是一个接口,它定义了一个抽象方法 draw
。Circle
和 Rectangle
类实现了 Drawable
接口,并实现了 draw
方法。
抽象类与接口的区别
- 抽象类可以有构造方法,接口不能有构造方法:抽象类的构造方法用于初始化子类继承的成员变量,而接口只是定义方法签名,不需要构造方法。
- 抽象类可以包含成员变量和具体方法,接口只能包含常量和抽象方法(Java 8 之前):在Java 8及以上版本,接口可以包含默认方法和静态方法,但成员变量只能是
public static final
类型的常量。 - 一个类只能继承一个抽象类,但可以实现多个接口:这使得接口在实现多继承方面更具灵活性。
内部类
成员内部类
- 定义:成员内部类是定义在另一个类内部的类,它作为外部类的一个成员存在。成员内部类可以访问外部类的所有成员,包括
private
成员。 - 语法:
public class Outer {
private int outerVar = 10;
public class Inner {
void display() {
System.out.println("Outer variable: " + outerVar);
}
}
}
public class Main {
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.display();
}
}
在上述代码中,Inner
类是 Outer
类的成员内部类。在 main
方法中,先创建了 Outer
类的对象 outer
,然后通过 outer.new Inner()
创建了 Inner
类的对象 inner
,并调用了 inner
的 display
方法,该方法可以访问外部类 Outer
的 private
成员变量 outerVar
。
静态内部类
- 定义:静态内部类是使用
static
关键字修饰的内部类。静态内部类不能直接访问外部类的非静态成员,因为静态内部类不依赖于外部类的实例。 - 语法:
public class Outer {
private static int outerStaticVar = 20;
private int outerNonStaticVar = 30;
public static class Inner {
void display() {
System.out.println("Outer static variable: " + outerStaticVar);
// 以下代码会报错,不能访问外部类的非静态成员
// System.out.println("Outer non - static variable: " + outerNonStaticVar);
}
}
}
public class Main {
public static void main(String[] args) {
Outer.Inner inner = new Outer.Inner();
inner.display();
}
}
在上述代码中,Inner
类是 Outer
类的静态内部类。在 main
方法中,直接通过 new Outer.Inner()
创建了静态内部类的对象,静态内部类的 display
方法可以访问外部类的静态成员变量 outerStaticVar
,但不能访问非静态成员变量 outerNonStaticVar
。
局部内部类
- 定义:局部内部类是定义在方法内部的类,它的作用域仅限于该方法内部。局部内部类可以访问方法的局部变量,但这些局部变量必须是
final
类型(在Java 8及以上版本,实际上是effectively final
,即虽然没有显式声明为final
,但在使用过程中没有被重新赋值)。 - 语法:
public class Outer {
void outerMethod() {
int localVar = 40;
class Inner {
void display() {
System.out.println("Local variable: " + localVar);
}
}
Inner inner = new Inner();
inner.display();
}
}
public class Main {
public static void main(String[] args) {
Outer outer = new Outer();
outer.outerMethod();
}
}
在上述代码中,Inner
类是定义在 outerMethod
方法内部的局部内部类。Inner
类的 display
方法可以访问 outerMethod
方法的局部变量 localVar
。
匿名内部类
- 定义:匿名内部类是没有名字的内部类,它通常用于创建只使用一次的类实例。匿名内部类必须继承一个类或实现一个接口。
- 语法:
interface Drawable {
void draw();
}
public class Main {
public static void main(String[] args) {
Drawable drawable = new Drawable() {
@Override
void draw() {
System.out.println("Drawing anonymously.");
}
};
drawable.draw();
}
}
在上述代码中,通过 new Drawable() {... }
创建了一个实现 Drawable
接口的匿名内部类实例,并将其赋值给 drawable
变量。然后调用了 drawable
的 draw
方法。匿名内部类适用于创建临时的、只使用一次的对象。
通过以上对Java面向对象编程基础的详细介绍,包括类与对象、封装、继承、多态、抽象类与接口以及内部类等方面,希望能帮助读者深入理解Java面向对象编程的核心概念和技术,并能在实际开发中熟练运用。